'From Squeak3.0 of 4 February 2001 [latest update: #3414] on 4 February 2001 at 1:28:53 am'! SoundCodec subclass: #ADPCMCodec instanceVariableNames: 'predicted index deltaSignMask deltaValueMask deltaValueHighBit frameSizeMask currentByte bitPosition byteIndex encodedBytes samples rightSamples sampleIndex bitsPerSample stepSizeTable indexTable ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !ADPCMCodec commentStamp: '' prior: 0! This is a simple ADPCM (adapative delta pulse code modulation) codec. This is a general audio codec that compresses speech, music, or sound effects equally well, and works at any sampling rate (i.e., it contains no frequency-sensitive filters). It compresses 16-bit sample data down to 5, 4, 3, or 2 bits per sample, with lower fidelity and increased noise at the lowest bit rates. Although it does not deliver state-of-the-art compressions, the alorithm is small, simple, and extremely fast, since the encode/decode primitives have been translated into C primitives. This codec will also encode and decode all Flash .swf file compressed sound formats, both mono and stereo. (Note: stereo Flash compression is not yet implemented, but stereo decompression works.) ! !ADPCMCodec methodsFor: 'bit streaming' stamp: 'jm 3/28/1999 16:24'! nextBits: n "Answer the next n bits of my bit stream as an unsigned integer." | result remaining shift | self inline: true. result _ 0. remaining _ n. [true] whileTrue: [ shift _ remaining - bitPosition. result _ result + (currentByte bitShift: shift). shift > 0 ifTrue: [ "consumed currentByte buffer; fetch next byte" remaining _ remaining - bitPosition. currentByte _ (encodedBytes at: (byteIndex _ byteIndex + 1)). bitPosition _ 8] ifFalse: [ "still some bits left in currentByte buffer" bitPosition _ bitPosition - remaining. "mask out the consumed bits:" currentByte _ currentByte bitAnd: (255 bitShift: (bitPosition - 8)). ^ result]]. ! ! !ADPCMCodec methodsFor: 'bit streaming' stamp: 'jm 3/28/1999 20:21'! nextBits: n put: anInteger "Write the next n bits to my bit stream." | buf bufBits bitsAvailable shift | self inline: true. buf _ anInteger. bufBits _ n. [true] whileTrue: [ bitsAvailable _ 8 - bitPosition. shift _ bitsAvailable - bufBits. "either left or right shift" "append high bits of buf to end of currentByte:" currentByte _ currentByte + (buf bitShift: shift). shift < 0 ifTrue: [ "currentByte buffer filled; output it" encodedBytes at: (byteIndex _ byteIndex + 1) put: currentByte. bitPosition _ 0. currentByte _ 0. "clear saved high bits of buf:" buf _ buf bitAnd: (1 bitShift: 0 - shift) - 1. bufBits _ bufBits - bitsAvailable] ifFalse: [ "still some bits available in currentByte buffer" bitPosition _ bitPosition + bufBits. ^ self]]. ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/27/1999 11:21'! bytesPerEncodedFrame "Answer the number of bytes required to hold one frame of compressed sound data." "Note: When used as a normal codec, the frame size is always 8 samples which results in (8 * bitsPerSample) / 8 = bitsPerSample bytes." ^ bitsPerSample ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 7/2/1999 13:29'! compressAndDecompress: aSound "Compress and decompress the given sound. Overridden to use same bits per sample for both compressing and decompressing." | compressed decoder | compressed _ self compressSound: aSound. decoder _ self class new initializeForBitsPerSample: bitsPerSample samplesPerFrame: 0. ^ decoder decompressSound: compressed ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/28/1999 15:37'! decodeFrames: frameCount from: srcByteArray at: srcIndex into: dstSoundBuffer at: dstIndex "Decode the given number of monophonic frames starting at the given index in the given ByteArray of compressed sound data and storing the decoded samples into the given SoundBuffer starting at the given destination index. Answer a pair containing the number of bytes of compressed data consumed and the number of decompressed samples produced." "Note: Assume that the sender has ensured that the given number of frames will not exhaust either the source or destination buffers." encodedBytes _ srcByteArray. byteIndex _ srcIndex - 1. bitPosition _ 0. currentByte _ 0. samples _ dstSoundBuffer. sampleIndex _ dstIndex - 1. self privateDecodeMono: (frameCount * self samplesPerFrame). ^ Array with: (byteIndex - (srcIndex - 1)) with: (sampleIndex - (dstIndex - 1)) ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/28/1999 15:28'! encodeFrames: frameCount from: srcSoundBuffer at: srcIndex into: dstByteArray at: dstIndex "Encode the given number of frames starting at the given index in the given monophonic SoundBuffer and storing the encoded sound data into the given ByteArray starting at the given destination index. Encode only as many complete frames as will fit into the destination. Answer a pair containing the number of samples consumed and the number of bytes of compressed data produced." "Note: Assume that the sender has ensured that the given number of frames will not exhaust either the source or destination buffers." samples _ srcSoundBuffer. sampleIndex _ srcIndex - 1. encodedBytes _ dstByteArray. byteIndex _ dstIndex - 1. bitPosition _ 0. currentByte _ 0. self privateEncodeMono: (frameCount * self samplesPerFrame). ^ Array with: frameCount with: (byteIndex - (dstIndex - 1)) ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/28/1999 20:12'! resetForMono "Reset my encoding and decoding state for mono." predicted _ 0. index _ 0. ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/28/1999 20:12'! resetForStereo "Reset my encoding and decoding state for stereo." "keep state as SoundBuffers to allow fast access from primitive" predicted _ SoundBuffer new: 2. index _ SoundBuffer new: 2. ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'jm 3/27/1999 08:34'! samplesPerFrame "Answer the number of sound samples per compression frame." frameSizeMask > 0 ifTrue: [^ frameSizeMask + 1]. ^ 8 "frame size when there are no running headers" ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 06:26'! decode: aByteArray bitsPerSample: bits ^ self decode: aByteArray sampleCount: (aByteArray size * 8) // bits bitsPerSample: bits frameSize: 0 stereo: false ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 15:57'! decode: aByteArray sampleCount: count bitsPerSample: bits frameSize: frameSize stereo: stereoFlag self initializeForBitsPerSample: bits samplesPerFrame: frameSize. encodedBytes _ aByteArray. byteIndex _ 0. bitPosition _ 0. currentByte _ 0. stereoFlag ifTrue: [ self resetForStereo. samples _ SoundBuffer newMonoSampleCount: count. rightSamples _ SoundBuffer newMonoSampleCount: count. sampleIndex _ 0. self privateDecodeStereo: count. ^ Array with: samples with: rightSamples] ifFalse: [ samples _ SoundBuffer newMonoSampleCount: count. sampleIndex _ 0. self privateDecodeMono: count. ^ samples] ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/30/1999 08:56'! decodeFlash: aByteArray sampleCount: sampleCount stereo: stereoFlag | bits | encodedBytes _ aByteArray. byteIndex _ 0. bitPosition _ 0. currentByte _ 0. bits _ 2 + (self nextBits: 2). "bits per sample" self initializeForBitsPerSample: bits samplesPerFrame: 4096. stereoFlag ifTrue: [ self resetForStereo. samples _ SoundBuffer newMonoSampleCount: sampleCount. rightSamples _ SoundBuffer newMonoSampleCount: sampleCount. sampleIndex _ 0. self privateDecodeStereo: sampleCount. ^ Array with: samples with: rightSamples] ifFalse: [ samples _ SoundBuffer newMonoSampleCount: sampleCount. sampleIndex _ 0. self privateDecodeMono: sampleCount. ^ Array with: samples]. ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 08:59'! encode: aSoundBuffer bitsPerSample: bits ^ self encodeLeft: aSoundBuffer right: nil bitsPerSample: bits frameSize: 0 forFlash: false ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 08:58'! encodeFlashLeft: leftSoundBuffer right: rightSoundBuffer bitsPerSample: bits ^ self encodeLeft: leftSoundBuffer right: rightSoundBuffer bitsPerSample: bits frameSize: 4096 forFlash: true ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 09:17'! encodeLeft: leftSoundBuffer right: rightSoundBuffer bitsPerSample: bits frameSize: frameSize forFlash: flashFlag | stereoFlag sampleCount sampleBitCount bitCount | self initializeForBitsPerSample: bits samplesPerFrame: frameSize. stereoFlag _ rightSoundBuffer notNil. sampleCount _ leftSoundBuffer monoSampleCount. stereoFlag ifTrue: [sampleBitCount _ 2 * (sampleCount * bitsPerSample)] ifFalse: [sampleBitCount _ sampleCount * bitsPerSample]. bitCount _ sampleBitCount + (self headerBitsForSampleCount: sampleCount stereoFlag: stereoFlag). encodedBytes _ ByteArray new: (bitCount / 8) ceiling. byteIndex _ 0. bitPosition _ 0. currentByte _ 0. flashFlag ifTrue: [self nextBits: 2 put: bits - 2]. stereoFlag ifTrue: [ samples _ Array with: leftSoundBuffer with: rightSoundBuffer. sampleIndex _ Array with: 0 with: 0. self privateEncodeStereo: sampleCount] ifFalse: [ samples _ leftSoundBuffer. sampleIndex _ 0. self privateEncodeMono: sampleCount]. ^ encodedBytes ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/27/1999 12:14'! headerBitsForSampleCount: sampleCount stereoFlag: stereoFlag "Answer the number of extra header bits required for the given number of samples. This will be zero if I am not using frame headers." | frameCount bitsPerHeader | frameSizeMask = 0 ifTrue: [^ 0]. frameCount _ (sampleCount / self samplesPerFrame) ceiling. bitsPerHeader _ 16 + 6. stereoFlag ifTrue: [bitsPerHeader _ 2 * bitsPerHeader]. ^ frameCount * bitsPerHeader ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 16:08'! indexForDeltaFrom: thisSample to: nextSample "Answer the best index to use for the difference between the given samples." "Details: Scan stepSizeTable for the first entry >= the absolute value of the difference between sample values. Since indexes are zero-based, the index used during decoding will be the one in the following stepSizeTable entry. Since the index field of a Flash frame header is only six bits, the maximum index value is 63." "Note: Since there does not appear to be any documentation of how Flash acutally computes the indices used in its frame headers, this algorithm was guessed by reverse-engineering the Flash ADPCM decoder." | diff bestIndex | self inline: true. diff _ nextSample - thisSample. diff < 0 ifTrue: [diff _ 0 - diff]. bestIndex _ 63. 1 to: 62 do: [:j | bestIndex = 63 ifTrue: [ (stepSizeTable at: j) >= diff ifTrue: [bestIndex _ j]]]. ^ bestIndex ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'jm 3/28/1999 20:48'! initializeForBitsPerSample: sampleBits samplesPerFrame: frameSize self resetForMono. stepSizeTable _ #(7 8 9 10 11 12 13 14 16 17 19 21 23 25 28 31 34 37 41 45 50 55 60 66 73 80 88 97 107 118 130 143 157 173 190 209 230 253 279 307 337 371 408 449 494 544 598 658 724 796 876 963 1060 1166 1282 1411 1552 1707 1878 2066 2272 2499 2749 3024 3327 3660 4026 4428 4871 5358 5894 6484 7132 7845 8630 9493 10442 11487 12635 13899 15289 16818 18500 20350 22385 24623 27086 29794 32767). indexTable _ nil. sampleBits = 2 ifTrue: [ indexTable _ #(-1 2)]. sampleBits = 3 ifTrue: [ indexTable _ #(-1 -1 2 4)]. sampleBits = 4 ifTrue: [ indexTable _ #(-1 -1 -1 -1 2 4 6 8)]. sampleBits = 5 ifTrue: [ indexTable _ #(-1 -1 -1 -1 -1 -1 -1 -1 1 2 4 6 8 10 13 16)]. indexTable ifNil: [self error: 'unimplemented bits/sample']. bitsPerSample _ sampleBits. deltaSignMask _ 1 bitShift: bitsPerSample - 1. deltaValueMask _ deltaSignMask - 1. deltaValueHighBit _ deltaSignMask / 2. frameSize <= 1 ifTrue: [frameSizeMask _ 0] ifFalse: [ (frameSize = (1 bitShift: frameSize highBit - 1)) ifFalse: [self error: 'frameSize must be a power of two']. frameSizeMask _ frameSize - 1]. "keep as SoundBuffer to allow fast access from primitive" indexTable _ SoundBuffer fromArray: indexTable. stepSizeTable _ SoundBuffer fromArray: stepSizeTable. ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'ar 2/3/2001 15:50'! privateDecodeMono: count | delta step predictedDelta bit | self var: #stepSizeTable declareC: 'short int *stepSizeTable'. self var: #indexTable declareC: 'short int *indexTable'. self var: #samples declareC: 'short int *samples'. self var: #encodedBytes declareC: 'unsigned char *encodedBytes'. 1 to: count do: [:i | (i bitAnd: frameSizeMask) = 1 ifTrue: [ "start of frame; read frame header" predicted _ self nextBits: 16. predicted > 32767 ifTrue: [predicted _ predicted - 65536]. index _ self nextBits: 6. samples at: (sampleIndex _ sampleIndex + 1) put: predicted] ifFalse: [ delta _ self nextBits: bitsPerSample. step _ stepSizeTable at: index + 1. predictedDelta _ 0. bit _ deltaValueHighBit. [bit > 0] whileTrue: [ (delta bitAnd: bit) > 0 ifTrue: [predictedDelta _ predictedDelta + step]. step _ step bitShift: -1. bit _ bit bitShift: -1]. predictedDelta _ predictedDelta + step. (delta bitAnd: deltaSignMask) > 0 ifTrue: [predicted _ predicted - predictedDelta] ifFalse: [predicted _ predicted + predictedDelta]. predicted > 32767 ifTrue: [predicted _ 32767] ifFalse: [predicted < -32768 ifTrue: [predicted _ -32768]]. index _ index + (indexTable at: (delta bitAnd: deltaValueMask) + 1). index < 0 ifTrue: [index _ 0] ifFalse: [index > 88 ifTrue: [index _ 88]]. samples at: (sampleIndex _ sampleIndex + 1) put: predicted]]. ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'ar 2/3/2001 15:50'! privateDecodeStereo: count | predictedLeft predictedRight indexLeft indexRight deltaLeft deltaRight stepLeft stepRight predictedDeltaLeft predictedDeltaRight bit | self var: #stepSizeTable declareC: 'short int *stepSizeTable'. self var: #indexTable declareC: 'short int *indexTable'. self var: #samples declareC: 'short int *samples'. self var: #encodedBytes declareC: 'unsigned char *encodedBytes'. self var: #rightSamples declareC: 'short int *rightSamples'. self var: #predicted declareC: 'short int *predicted'. self var: #index declareC: 'short int *index'. "make local copies of decoder state variables" predictedLeft _ predicted at: 1. predictedRight _ predicted at: 2. indexLeft _ index at: 1. indexRight _ index at: 2. 1 to: count do: [:i | (i bitAnd: frameSizeMask) = 1 ifTrue: [ "start of frame; read frame header" predictedLeft _ self nextBits: 16. indexLeft _ self nextBits: 6. predictedRight _ self nextBits: 16. indexRight _ self nextBits: 6. predictedLeft > 32767 ifTrue: [predictedLeft _ predictedLeft - 65536]. predictedRight > 32767 ifTrue: [predictedRight _ predictedRight - 65536]. samples at: (sampleIndex _ sampleIndex + 1) put: predictedLeft. rightSamples at: sampleIndex put: predictedRight] ifFalse: [ deltaLeft _ self nextBits: bitsPerSample. deltaRight _ self nextBits: bitsPerSample. stepLeft _ stepSizeTable at: indexLeft + 1. stepRight _ stepSizeTable at: indexRight + 1. predictedDeltaLeft _ predictedDeltaRight _ 0. bit _ deltaValueHighBit. [bit > 0] whileTrue: [ (deltaLeft bitAnd: bit) > 0 ifTrue: [ predictedDeltaLeft _ predictedDeltaLeft + stepLeft]. (deltaRight bitAnd: bit) > 0 ifTrue: [ predictedDeltaRight _ predictedDeltaRight + stepRight]. stepLeft _ stepLeft bitShift: -1. stepRight _ stepRight bitShift: -1. bit _ bit bitShift: -1]. predictedDeltaLeft _ predictedDeltaLeft + stepLeft. predictedDeltaRight _ predictedDeltaRight + stepRight. (deltaLeft bitAnd: deltaSignMask) > 0 ifTrue: [predictedLeft _ predictedLeft - predictedDeltaLeft] ifFalse: [predictedLeft _ predictedLeft + predictedDeltaLeft]. (deltaRight bitAnd: deltaSignMask) > 0 ifTrue: [predictedRight _ predictedRight - predictedDeltaRight] ifFalse: [predictedRight _ predictedRight + predictedDeltaRight]. predictedLeft > 32767 ifTrue: [predictedLeft _ 32767] ifFalse: [predictedLeft < -32768 ifTrue: [predictedLeft _ -32768]]. predictedRight > 32767 ifTrue: [predictedRight _ 32767] ifFalse: [predictedRight < -32768 ifTrue: [predictedRight _ -32768]]. indexLeft _ indexLeft + (indexTable at: (deltaLeft bitAnd: deltaValueMask) + 1). indexLeft < 0 ifTrue: [indexLeft _ 0] ifFalse: [indexLeft > 88 ifTrue: [indexLeft _ 88]]. indexRight _ indexRight + (indexTable at: (deltaRight bitAnd: deltaValueMask) + 1). indexRight < 0 ifTrue: [indexRight _ 0] ifFalse: [indexRight > 88 ifTrue: [indexRight _ 88]]. samples at: (sampleIndex _ sampleIndex + 1) put: predictedLeft. rightSamples at: sampleIndex put: predictedRight]]. "save local copies of decoder state variables" predicted at: 1 put: predictedLeft. predicted at: 2 put: predictedRight. index at: 1 put: indexLeft. index at: 2 put: indexRight. ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'ar 2/3/2001 15:51'! privateEncodeMono: count | step sign diff delta predictedDelta bit p | self var: #stepSizeTable declareC: 'short int *stepSizeTable'. self var: #indexTable declareC: 'short int *indexTable'. self var: #samples declareC: 'short int *samples'. self var: #encodedBytes declareC: 'unsigned char *encodedBytes'. step _ stepSizeTable at: 1. 1 to: count do: [:i | (i bitAnd: frameSizeMask) = 1 ifTrue: [ predicted _ samples at: (sampleIndex _ sampleIndex + 1). (p _ predicted) < 0 ifTrue: [p _ p + 65536]. self nextBits: 16 put: p. i < count ifTrue: [ index _ self indexForDeltaFrom: predicted to: (samples at: sampleIndex + 1)]. self nextBits: 6 put: index. ] ifFalse: [ "compute sign and magnitude of difference from the predicted sample" sign _ 0. diff _ (samples at: (sampleIndex _ sampleIndex + 1)) - predicted. diff < 0 ifTrue: [ sign _ deltaSignMask. diff _ 0 - diff]. "Compute encoded delta and the difference that this will cause in the predicted sample value during decoding. Note that this code approximates: delta _ (4 * diff) / step. predictedDelta _ ((delta + 0.5) * step) / 4; but in the shift step bits are dropped. Thus, even if you have fast mul/div hardware you cannot use it since you would get slightly different bits what than the algorithm defines." delta _ 0. predictedDelta _ 0. bit _ deltaValueHighBit. [bit > 0] whileTrue: [ diff >= step ifTrue: [ delta _ delta + bit. predictedDelta _ predictedDelta + step. diff _ diff - step]. step _ step bitShift: -1. bit _ bit bitShift: -1]. predictedDelta _ predictedDelta + step. "compute and clamp new prediction" sign > 0 ifTrue: [predicted _ predicted - predictedDelta] ifFalse: [predicted _ predicted + predictedDelta]. predicted > 32767 ifTrue: [predicted _ 32767] ifFalse: [predicted < -32768 ifTrue: [predicted _ -32768]]. "compute new index and step values" index _ index + (indexTable at: delta + 1). index < 0 ifTrue: [index _ 0] ifFalse: [index > 88 ifTrue: [index _ 88]]. step _ stepSizeTable at: index + 1. "output encoded, signed delta" self nextBits: bitsPerSample put: (sign bitOr: delta)]]. bitPosition > 0 ifTrue: [ "flush the last output byte, if necessary" encodedBytes at: (byteIndex _ byteIndex + 1) put: currentByte]. ! ! !ADPCMCodec methodsFor: 'as yet unclassified' stamp: 'ar 2/3/2001 15:51'! privateEncodeStereo: count "not yet implemented" self inline: false. self success: false.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ADPCMCodec class instanceVariableNames: ''! !ADPCMCodec class methodsFor: 'instance creation' stamp: 'jm 3/27/1999 11:15'! new ^ super new initializeForBitsPerSample: 4 samplesPerFrame: 0. ! ! !ADPCMCodec class methodsFor: 'primitive generation' stamp: 'ar 2/3/2001 15:50'! translatedPrimitives "Answer a string containing the translated C code for my primitives." "Note: This code currently must be hand-edited to remove several methods that are inlined (thus not needed) but not pruned out by the ST-to-C translator." ^#( (ADPCMCodec privateDecodeMono:) (ADPCMCodec privateDecodeStereo:) (ADPCMCodec privateEncodeMono:) (ADPCMCodec privateEncodeStereo:) (ADPCMCodec indexForDeltaFrom:to:) (ADPCMCodec nextBits:) (ADPCMCodec nextBits:put:)) ! ! InterpreterPlugin subclass: #ADPCMCodecPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ADPCMCodecPlugin class instanceVariableNames: ''! !ADPCMCodecPlugin class methodsFor: 'translation' stamp: 'ar 2/3/2001 18:34'! translateOn: cg inlining: inlineFlag to: fullName local: localFlag "ADPCMCodecPlugin translateLocally" | code | cg addClass: InterpreterPlugin. InterpreterPlugin declareCVarsIn: cg. cg addMethodsForPrimitives: ADPCMCodec translatedPrimitives. "now remove a few which will be inlined but not pruned" cg pruneMethods: #(indexForDeltaFrom:to: nextBits: nextBits:put:). code _ cg generateCodeStringForPrimitives. self storeString: code onFileNamed: fullName.! ! MacExternalData variableWordSubclass: #AEDesc instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !AEDesc commentStamp: '' prior: 0! I represent an Apple Event Descriptor. I am a low-level representation of Apple Event (and hence Applescript) information. For further Information, see Apple's Inside Macintosh: Interapplication Communications, at http://developer.apple.com/techpubs/mac/IAC/IAC-2.html. Essentially, I represent a record comprising a one-word "string" (treating the word as fourbyte characters) representing a data type, followed by a pointer to a pointer (a handle) to the data I represent. Care must be taken to assure that the Handle data is disposed after use, or memory leaks result. At this time, I make no effort to do this automatically through finalization.! ]style[(218 54 384)f1,f1Rhttp://developer.apple.com/techpubs/mac/IAC/IAC-2.html;,f1! !AEDesc methodsFor: 'accessing' stamp: 'acg 9/12/1999 21:33'! dataSize ^self handleSizeAt: 2! ! !AEDesc methodsFor: 'accessing' stamp: 'acg 9/20/1999 14:22'! dispose (0 = (self at: 2)) ifTrue: [self error: 'cannot dispose of unallocated space']. self primAEDisposeDesc isZero ifFalse: [self error: 'dispose operation failed']. self at: 1 put: 0. self at: 2 put: 0. ^nil! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/24/1999 00:35'! asCompiledApplescript | theSize | ((self at: 1) ~= 16r73637074) ifTrue: [^self error: 'AEDesc is not of type ''scpt''']. (theSize _ self dataSize) < 0 ifTrue: [^self error: 'Invalid size for data']. ^self primAEDescToString: (CompiledApplescript new: theSize). ! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/24/1999 00:31'! asCompiledApplescriptThenDispose | CAD | CAD _ self asCompiledApplescript. self dispose. ^CAD! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/26/1999 18:39'! asOSAIDThenDisposeAEDescWith: aComponent ^aComponent loadAndDisposeAEDesc: self mode: 0! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/23/1999 23:46'! asShort ^(self primAEDescToString: (ByteArray new: 2)) shortAt: 1 bigEndian: true! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/23/1999 23:47'! asShortThenDispose | short | short _ self asShort. self dispose. ^short! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/20/1999 14:19'! asString | theSize | ((self at: 1) ~= 16r54455854) ifTrue: [^self error: 'AEDesc is not of type ''TEXT''']. (theSize _ self dataSize) < 0 ifTrue: [^self error: 'Invalid size for data']. ^self primAEDescToString: (String new: theSize). ! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/21/1999 00:13'! asStringThenDispose | string | string _ self asString. self dispose. ^string! ! !AEDesc methodsFor: 'converting' stamp: 'acg 9/26/1999 01:15'! to: aString | newAEDesc result | newAEDesc _ AEDesc new. result _ self primAECoerceDesc: (DescType of: aString) to: newAEDesc. result isZero ifFalse: [^result]. self dispose. self at: 1 put: (newAEDesc at: 1). self at: 2 put: (newAEDesc at: 2). ^0! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/24/1999 00:38'! createFromScpt: aCompiledApplescriptData (aCompiledApplescriptData class = CompiledApplescript) ifFalse: [^self error: 'textType Data Not From CompiledApplescriptData']. (self primAECreateDesc: (DescType of: 'scpt') from: aCompiledApplescriptData) isZero ifTrue: [^self]. self error: 'failed to create aeDesc'. ^nil! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/20/1999 14:39'! createFromText: aString (aString class = String) ifFalse: [^self error: 'TextType Data Not From String']. (self primAECreateDesc: (DescType of: 'TEXT') from: aString) isZero ifTrue: [^self]. self error: 'failed to create aeDesc'. ^nil! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/25/1999 22:54'! createNull (self primAECreateDesc: (DescType of: 'null') from: '') isZero ifTrue: [^self]. self error: 'failed to create aeDesc'. ^nil! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/23/1999 22:51'! primAECoerceDesc: typeCode to: result ^TestOSAPlugin doPrimitive: 'primAECoerceDesc:to:' withArguments: {typeCode. result}! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/20/1999 13:25'! primAECreateDesc: typeCode from: aString ^TestOSAPlugin doPrimitive: 'primAECreateDesc:from:' withArguments: {typeCode. aString}! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/23/1999 21:13'! primAEDescToString: aString ^TestOSAPlugin doPrimitive: 'primAEDescToString:' withArguments: {aString}! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/20/1999 13:28'! primAEDisposeDesc ^TestOSAPlugin doPrimitive: 'primAEDisposeDesc' withArguments: {}! ! !AEDesc methodsFor: 'private' stamp: 'acg 9/23/1999 22:20'! primAEGetKeyPtr: keyDesc type: typeDesc actual: ignoreDesc to: aByteArray ^TestOSAPlugin doPrimitive: 'primAEGetKeyPtr:type:actual:to:' withArguments: {keyDesc. typeDesc. ignoreDesc. aByteArray}! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AEDesc class instanceVariableNames: ''! !AEDesc class methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 22:17'! new ^super new: 2! ! !AEDesc class methodsFor: 'as yet unclassified' stamp: 'acg 9/25/1999 22:53'! nullType ^self new createNull! ! !AEDesc class methodsFor: 'as yet unclassified' stamp: 'acg 9/22/1999 08:05'! scptTypeOn: aCompiledApplescriptData ^(self new) createFromScpt: aCompiledApplescriptData ! ! !AEDesc class methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 13:30'! textTypeOn: aString ^(self new) createFromText: aString ! ! !AEDesc class methodsFor: 'private' stamp: 'acg 9/12/1999 20:49'! primSizeAEDesc ^-1! ! Object subclass: #AIFFFileReader instanceVariableNames: 'in fileType channelCount frameCount bitsPerSample samplingRate channelData markers pitch gain isLooped skipDataChunk mergeIfStereo ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !AIFFFileReader commentStamp: '' prior: 0! I am a parser for AIFF (audio interchange file format) files. I can read uncompressed 8-bit and 16-bit mono, stereo, or multichannel AIFF files. I read the marker information used by the TransferStation utility to mark the loop points in sounds extracted from commercial sampled-sound CD-ROMs. ! !AIFFFileReader methodsFor: 'reading'! readFrom: binaryStream mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag "Read the AIFF file of the given name. If mergeFlag is true and the file contains stereo data, then the left and right channels will be mixed together as the samples are read in. If skipDataFlag is true, then the data chunk to be skipped; this allows the other chunks of a file to be processed in order to extract format information quickly without reading the data." "AIFFFileReader new readFromFile: 'test.aif' mergeIfStereo: false skipDataChunk: false" mergeIfStereo _ mergeFlag. skipDataChunk _ skipDataFlag. isLooped _ false. gain _ 1.0. self readFrom: binaryStream. binaryStream close. ! ! !AIFFFileReader methodsFor: 'reading' stamp: 'jm 8/2/1998 16:27'! readFromFile: fileName "Read the AIFF file of the given name." "AIFFFileReader new readFromFile: 'test.aiff'" self readFromFile: fileName mergeIfStereo: false skipDataChunk: false. ! ! !AIFFFileReader methodsFor: 'reading'! readFromFile: fileName mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag "Read the AIFF file of the given name. If mergeFlag is true and the file contains stereo data, then the left and right channels will be mixed together as the samples are read in. If skipDataFlag is true, then the data chunk to be skipped; this allows the other chunks of a file to be processed in order to extract format information quickly without reading the data." "AIFFFileReader new readFromFile: 'test.aiff' mergeIfStereo: false skipDataChunk: true" | binaryStream | binaryStream _ (FileStream readOnlyFileNamed: fileName) binary. self readFrom: binaryStream mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:25'! bitsPerSample ^ bitsPerSample ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:24'! channelCount ^ channelCount ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:25'! channelData ^ channelData ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:24'! frameCount ^ frameCount ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:25'! gain ^ gain ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 01:40'! isLooped ^ isLooped ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 20:02'! isStereo ^ channelData size = 2 ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 18:26'! leftSamples ^ channelData at: 1 ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 18:30'! loopEnd ^ markers last last ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 18:30'! loopLength ^ markers last last - markers first last ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 21:25'! markers ^ markers ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 01:48'! pitch ^ pitch ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 8/2/1998 19:34'! rightSamples ^ channelData at: 2 ! ! !AIFFFileReader methodsFor: 'accessing' stamp: 'jm 7/12/1998 18:25'! samplingRate ^ samplingRate ! ! !AIFFFileReader methodsFor: 'other' stamp: 'jm 8/17/1998 20:36'! edit | ed | ed _ WaveEditor new. ed data: channelData first. ed loopEnd: markers last last. ed loopLength: (markers last last - markers first last) + 1. ed openInWorld. ! ! !AIFFFileReader methodsFor: 'other' stamp: 'jm 7/12/1998 01:44'! pitchForKey: midiKey "Convert my MIDI key number to a pitch and return it." | indexInOctave octave p | indexInOctave _ (midiKey \\ 12) + 1. octave _ (midiKey // 12) + 1. "Table generator: (0 to: 11) collect: [:i | 16.3516 * (2.0 raisedTo: i asFloat / 12.0)]" p _ #(16.3516 17.32391 18.35405 19.44544 20.60173 21.82677 23.12466 24.49972 25.95655 27.50000 29.13524 30.86771) at: indexInOctave. ^ p * (#(0.5 1.0 2.0 4.0 8.0 16.0 32.0 64.0 128.0 256.0 512.0) at: octave) ! ! !AIFFFileReader methodsFor: 'other' stamp: 'jm 1/14/1999 10:11'! sound "Answer the sound represented by this AIFFFileReader. This method should be called only after readFrom: has been done." | snd rightSnd | snd _ SampledSound samples: (channelData at: 1) samplingRate: samplingRate. self isStereo ifTrue: [ rightSnd _ SampledSound samples: (channelData at: 2) samplingRate: samplingRate. snd _ MixedSound new add: snd pan: 0; add: rightSnd pan: 1.0]. ^ snd ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 6/29/1998 07:33'! readChunk: chunkType size: chunkSize "Read a AIFF chunk of the given type. Skip unrecognized chunks. Leave the input stream positioned chunkSize bytes past its position when this method is called." chunkType = 'COMM' ifTrue: [^ self readCommonChunk: chunkSize]. chunkType = 'SSND' ifTrue: [^ self readSamplesChunk: chunkSize]. chunkType = 'INST' ifTrue: [^ self readInstrumentChunk: chunkSize]. chunkType = 'MARK' ifTrue: [^ self readMarkerChunk: chunkSize]. in skip: chunkSize. "skip unknown chunks" ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 7/12/1998 18:24'! readCommonChunk: chunkSize "Read a COMM chunk. All AIFF files have exactly one chunk of this type." | compressionType | channelCount _ in nextNumber: 2. frameCount _ in nextNumber: 4. bitsPerSample _ in nextNumber: 2. samplingRate _ self readExtendedFloat. chunkSize > 18 ifTrue: [ fileType = 'AIFF' ifTrue: [self error: 'unexpectedly long COMM chunk size for AIFF file']. compressionType _ (in next: 4) asString. compressionType = 'NONE' ifFalse: [self error: 'cannot read compressed AIFF files']. in skip: (chunkSize - 22)]. "skip the reminder of AIFF-C style chunk" ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 6/29/1998 11:43'! readExtendedFloat "Read and answer an Apple extended-precision 80-bit floating point number from the input stream." "Details: I could not find the specification for this format, so constants were determined empirically based on assumption of 1-bit sign, 15-bit exponent, 64-bit mantissa. This format does not seem to have an implicit one before the mantissa as some float formats do." | signAndExp mantissa sign exp | signAndExp _ in nextNumber: 2. mantissa _ in nextNumber: 8. "scaled by (2 raisedTo: -64) below" (signAndExp bitAnd: 16r8000) = 0 ifTrue: [sign _ 1.0] ifFalse: [sign _ -1.0]. exp _ (signAndExp bitAnd: 16r7FFF) - 16r4000 + 2. "not sure why +2 is needed..." ^ (sign * mantissa asFloat * (2.0 raisedTo: exp - 64)) roundTo: 0.00000001 ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 19:58'! readFrom: aBinaryStream "Read AIFF data from the given binary stream." "Details: An AIFF file consists of a header (FORM chunk) followed by a sequence of tagged data chunks. Each chunk starts with a header consisting of a four-byte tag (a string) and a four byte size. These eight bytes of chunk header are not included in the chunk size. For each chunk, the readChunk:size: method consumes chunkSize bytes of the input stream, parsing recognized chunks or skipping unrecognized ones. If chunkSize is odd, it will be followed by a padding byte. Chunks may occur in any order." | sz end chunkType chunkSize p | in _ aBinaryStream. "read FORM chunk" (in next: 4) asString = 'FORM' ifFalse: [^ self error: 'not an AIFF file']. sz _ in nextNumber: 4. end _ in position + sz. fileType _ (in next: 4) asString. [in atEnd not and: [in position < end]] whileTrue: [ chunkType _ (in next: 4) asString. chunkSize _ in nextNumber: 4. p _ in position. self readChunk: chunkType size: chunkSize. (in position = (p + chunkSize)) ifFalse: [self error: 'chunk size mismatch; bad AIFF file?']. chunkSize odd ifTrue: [in skip: 1]]. "skip padding byte" ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/5/1998 17:31'! readInstrumentChunk: chunkSize | midiKey detune lowNote highNote lowVelocity highVelocity sustainMode sustainStartID sustainEndID releaseMode releaseStartID releaseEndID | midiKey _ in next. detune _ in next. lowNote _ in next. highNote _ in next. lowVelocity _ in next. highVelocity _ in next. gain _ in nextNumber: 2. sustainMode _ in nextNumber: 2. sustainStartID _ in nextNumber: 2. sustainEndID _ in nextNumber: 2. releaseMode _ in nextNumber: 2. releaseStartID _ in nextNumber: 2. releaseEndID _ in nextNumber: 2. isLooped _ sustainMode = 1. (isLooped and: [markers notNil]) ifTrue: [ ((markers first last > frameCount) or: [markers last last > frameCount]) ifTrue: [ "bad loop data; some sample CD files claim to be looped but aren't" isLooped _ false]]. pitch _ self pitchForKey: midiKey. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 21:22'! readMarkerChunk: chunkSize | markerCount id position labelBytes label | markerCount _ in nextNumber: 2. markers _ Array new: markerCount. 1 to: markerCount do: [:i | id _ in nextNumber: 2. position _ in nextNumber: 4. labelBytes _ in next. label _ (in next: labelBytes) asString. labelBytes even ifTrue: [in skip: 1]. markers at: i put: (Array with: id with: label with: position)]. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 18:58'! readMergedStereoChannelDataFrom: s "Read stereophonic channel data from the given stream, mixing the two channels to create a single monophonic channel. Each frame contains two samples." | buf w1 w2 | buf _ channelData at: 1. bitsPerSample = 8 ifTrue: [ 1 to: frameCount do: [:i | w1 _ s next. w1 > 127 ifTrue: [w1 _ w1 - 256]. w2 _ s next. w2 > 127 ifTrue: [w2 _ w2 - 256]. buf at: i put: ((w1 + w2) bitShift: 7)]] ifFalse: [ 1 to: frameCount do: [:i | w1 _ (s next bitShift: 8) + s next. w1 > 32767 ifTrue: [w1 _ w1 - 65536]. w2 _ (s next bitShift: 8) + s next. w2 > 32767 ifTrue: [w2 _ w2 - 65536]. buf at: i put: ((w1 + w2) bitShift: -1)]]. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 18:53'! readMonoChannelDataFrom: s "Read monophonic channel data from the given stream. Each frame contains a single sample." | buf w | buf _ channelData at: 1. "the only buffer" bitsPerSample = 8 ifTrue: [ 1 to: frameCount do: [:i | w _ s next. w > 127 ifTrue: [w _ w - 256]. buf at: i put: (w bitShift: 8)]] ifFalse: [ 1 to: frameCount do: [:i | w _ (s next bitShift: 8) + s next. w > 32767 ifTrue: [w _ w - 65536]. buf at: i put: w]]. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 18:55'! readMultiChannelDataFrom: s "Read multi-channel data from the given stream. Each frame contains channelCount samples." | w | bitsPerSample = 8 ifTrue: [ 1 to: frameCount do: [:i | 1 to: channelCount do: [:ch | w _ s next. w > 127 ifTrue: [w _ w - 256]. (channelData at: ch) at: i put: (w bitShift: 8)]]] ifFalse: [ 1 to: frameCount do: [:i | 1 to: channelCount do: [:ch | w _ (s next bitShift: 8) + s next. w > 32767 ifTrue: [w _ w - 65536]. (channelData at: ch) at: i put: w]]]. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/3/1998 14:55'! readSamplesChunk: chunkSize "Read a SSND chunk. All AIFF files with a non-zero frameCount contain exactly one chunk of this type." | offset blockSize bytesOfSamples s | skipDataChunk ifTrue: [in skip: chunkSize. ^ self]. offset _ in nextNumber: 4. blockSize _ in nextNumber: 4. ((offset ~= 0) or: [blockSize ~= 0]) ifTrue: [^ self error: 'this AIFF reader cannot handle blocked sample chunks']. bytesOfSamples _ chunkSize - 8. bytesOfSamples = (channelCount * frameCount * (bitsPerSample // 8)) ifFalse: [self error: 'actual sample count does not match COMM chunk']. (mergeIfStereo and: [channelCount = 2]) ifTrue: [ channelData _ Array with: (SoundBuffer newMonoSampleCount: frameCount)] ifFalse: [ channelData _ (1 to: channelCount) collect: [:i | SoundBuffer newMonoSampleCount: frameCount]]. (bytesOfSamples < (Smalltalk garbageCollectMost - 300000)) ifTrue: [s _ ReadStream on: (in next: bytesOfSamples)] "bulk-read, then process" ifFalse: [s _ in]. "not enough space to buffer; read directly from file" "mono and stereo are special-cased for better performance" channelCount = 1 ifTrue: [^ self readMonoChannelDataFrom: s]. channelCount = 2 ifTrue: [ mergeIfStereo ifTrue: [channelCount _ 1. ^ self readMergedStereoChannelDataFrom: s] ifFalse: [^ self readStereoChannelDataFrom: s]]. self readMultiChannelDataFrom: s. ! ! !AIFFFileReader methodsFor: 'private' stamp: 'jm 8/2/1998 18:56'! readStereoChannelDataFrom: s "Read stereophonic channel data from the given stream. Each frame contains two samples." | left right w | left _ channelData at: 1. right _ channelData at: 2. bitsPerSample = 8 ifTrue: [ 1 to: frameCount do: [:i | w _ s next. w > 127 ifTrue: [w _ w - 256]. left at: i put: (w bitShift: 8). w _ s next. w > 127 ifTrue: [w _ w - 256]. right at: i put: (w bitShift: 8)]] ifFalse: [ 1 to: frameCount do: [:i | w _ (s next bitShift: 8) + s next. w > 32767 ifTrue: [w _ w - 65536]. left at: i put: w. w _ (s next bitShift: 8) + s next. w > 32767 ifTrue: [w _ w - 65536]. right at: i put: w]]. ! ! Animation subclass: #AbsoluteAnimation instanceVariableNames: 'lastStartState ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !AbsoluteAnimation commentStamp: '' prior: 0! An AbsoluteAnimation is any animation where the final state of the animation is always the same. Every time this animation runs we store the initial state, so that when the animation is reversed and run we can determine what that end point should be. ! !AbsoluteAnimation methodsFor: 'management' stamp: 'jsp 2/16/1999 16:36'! prologue: currentTime "Extends the AbstractAnimation prologue by saving the start state of the animation." undoable ifTrue: [ (myWonderland getUndoStack) push: (UndoAnimation new: (self makeUndoVersion)). ]. (direction = Forward) ifTrue: [ startState _ getStartStateFunction value. lastStartState _ startState. endState _ getEndStateFunction value. ] ifFalse: [ startState _ getStartStateFunction value. endState _ lastStartState. ]. super prologue: currentTime. ! ! !AbsoluteAnimation methodsFor: 'initialization' stamp: 'jsp 3/9/1999 15:49'! object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland "This method initializes the Animation with all the information that it needs to run." lastStartState _ startFunc value. super object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland. ! ! !AbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 3/9/1999 15:49'! copy "Creates a copy of the animation" | anim | anim _ AbsoluteAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction style: styleFunction duration: duration undoable: undoable inWonderland: myWonderland. (direction = Forward) ifFalse: [ anim reverseDirection ]. ^ anim. ! ! !AbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 3/9/1999 15:49'! makeUndoVersion "Creates the undo version of an animation" | anim | anim _ AbsoluteAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction style: styleFunction duration: 0.5 undoable: false inWonderland: myWonderland. anim stop. (direction = Forward) ifTrue: [ anim reverseDirection ]. ^ anim. ! ! !AbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 4/9/1999 14:22'! reversed "Creates a reversed version of an animation" | anim | anim _ AbsoluteAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction style: styleFunction duration: duration undoable: true inWonderland: myWonderland. anim stop. (direction = Forward) ifTrue: [ anim reverseDirection ]. ^ anim. ! ! Object subclass: #AbstractAnimation instanceVariableNames: 'startTime endTime duration state direction loopCount undoable myScheduler myWonderland pausedInterval animatedObject ' classVariableNames: 'Finished Forward Infinity Paused Reverse Running Stopped Waiting ' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !AbstractAnimation commentStamp: '' prior: 0! This class implements the basic functionality of Animations for Wonderlands. All animations pass through 4 specific stages: Waiting - this is the state animations are in when they are just started, before they run their prologue (perform any tasks they need to do before the animation actually starts) Running - this is the state animations are in when they are actually running Stopped - this is the state animations are in after they stop running but before they execute their prologue Finished - this is the state animations are in after they finish their epilogue (perform any tasks they need to do after the animation completes). ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 3/9/1999 15:45'! getAnimatedObject "Return the object that this animation affects" ^ animatedObject. ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/26/1999 12:41'! getLoopCount "Returns the animation's current loop count" ^ loopCount. ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/4/1999 10:22'! getState "Returns the current state of the animation." ^ state. ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/3/1999 14:23'! isDone "Returns true if the animation is running" ^ (state = Stopped). ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/26/1999 12:01'! isLooping "Returns true if the animation is looping" ^ ( loopCount > 1) or: [ loopCount = Infinity ]. ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/26/1999 12:42'! setLoopCount: count "Sets the animation's current loop count" loopCount _ count. ! ! !AbstractAnimation methodsFor: 'accessing' stamp: 'jsp 2/26/1999 12:23'! setUndoable: aBoolean "Sets the animation's undoable property" undoable _ aBoolean. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 3/3/1999 12:38'! copy self subclassResponsibility. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 3/3/1999 12:06'! epilogue: currentTime "This method does any work that needs to be done after an interation of the animation finishes." (loopCount = Infinity) ifTrue: [state _ Waiting] ifFalse: [ loopCount _ loopCount - 1. (loopCount > 0) ifTrue: [ state _ Waiting ] ifFalse: [state _ Stopped. loopCount _ 1 ]. ]. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:47'! getDuration "This method returns the duration of the animation." ^ duration. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 3/24/1999 15:48'! loop "This method causes an animation to loop forever." loopCount _ Infinity. (state = Stopped) ifTrue: [ state _ Waiting. myScheduler addAnimation: self. ]. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 15:10'! loop: numberOfTimes "This method causes an animation to loop for the specified number of times." loopCount _ numberOfTimes. (state = Stopped) ifTrue: [ state _ Waiting. myScheduler addAnimation: self. ]. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 3/3/1999 12:37'! looped "This method creates a copy of an animation and loops it forever." | anim | anim _ self copy. anim setLoopCount: Infinity. ^ anim. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 3/3/1999 12:36'! looped: numberOfTimes "This method creates a copy of an animation and loops it for the specified number of times." | anim | anim _ self copy. anim setLoopCount: numberOfTimes. ^ anim. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:43'! pause "This method pauses an active Animation." (state = Running) ifTrue: [ state _ Paused. pausedInterval _ (myScheduler getTime) - startTime.]. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:56'! prologue: currentTime "This method does any work that needs to be done before the animation starts, including possibly adding the current state to the undo stack." "Undo stack stuff here" undoable ifTrue: []. startTime _ currentTime. endTime _ startTime + duration. state _ Running. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/26/1999 15:21'! resume "This method resumes a paused animation" (state = Paused) ifTrue: [ state _ Running. startTime _ (myScheduler getTime) - pausedInterval. endTime _ startTime + duration. ] ifFalse: [(state = Stopped) ifTrue: [ state _ Waiting. myScheduler addAnimation: self. ]. ] ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 15:00'! start "This method starts an existing animation" state _ Waiting. loopCount _ 1. myScheduler addAnimation: self. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:25'! stop "This method changes the state of an animation to stopped. If it is currently active, the Scheduler will remove it from the list of active animations." state _ Stopped. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:50'! stopLooping "This method causes the animation to stop looping; the current interation of the animation completes before the animation stops." loopCount _ 1. ! ! !AbstractAnimation methodsFor: 'management' stamp: 'jsp 2/3/1999 14:53'! update: currentTime "Updates the animation using the current Wonderland time" (state = Waiting) ifTrue: [self prologue: currentTime]. (state = Running) ifTrue: []. (state = Finished) ifTrue: [self epilogue: currentTime]. ! ! !AbstractAnimation methodsFor: 'reversing' stamp: 'jsp 2/15/1999 10:28'! reverseDirection "Changes the direction an animation runs in (forward or in reverse)" (direction = Forward) ifTrue: [ direction _ Reverse ] ifFalse: [ direction _ Forward ]. ! ! !AbstractAnimation methodsFor: 'private' stamp: 'jsp 2/26/1999 14:21'! scaleDuration: scaleAmount "Scales the animation's duration by the specified amount" duration _ duration * scaleAmount. ! ! !AbstractAnimation methodsFor: 'private' stamp: 'jsp 2/26/1999 14:17'! setDirection: aDirection "Sets the animation's direction variable" direction _ aDirection. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AbstractAnimation class instanceVariableNames: ''! !AbstractAnimation class methodsFor: 'class initialization' stamp: 'jsp 3/24/1999 11:01'! initialize "Initialize the class variables" Waiting _ 1. Running _ 2. Paused _ 3. Finished _ 4. Stopped _ 5. Forward _ 0. Reverse _ 1. Infinity _ -1. ! ! Object subclass: #AbstractFont instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !AbstractFont commentStamp: '' prior: 0! AbstractFont defines the generic interface that all fonts need to implement.! !AbstractFont methodsFor: 'accessing' stamp: 'ar 5/19/2000 14:56'! characterToGlyphMap "Return the character to glyph mapping table. If the table is not provided the character scanner will query the font directly for the width of each individual character." ^nil! ! !AbstractFont methodsFor: 'accessing' stamp: 'ar 5/19/2000 14:57'! xTable "Return the xTable for the font. The xTable defines the left x-value for each individual glyph in the receiver. If such a table is not provided, the character scanner will ask the font directly for the appropriate width of each individual character." ^nil! ! !AbstractFont methodsFor: 'measuring' stamp: 'ar 5/19/2000 15:00'! composeWord: aTextLineInterval in: sourceString beginningAt: xInteger "Non-primitive composition of a word--add up widths of characters, add sum to beginning x and answer the resulting x. Similar to performance of scanning primitive, but without stop conditions." | character resultX | resultX _ xInteger. aTextLineInterval do: [:i | character _ sourceString at: i. resultX _ resultX + (self widthOf: character)]. ^resultX! ! !AbstractFont methodsFor: 'measuring' stamp: 'ar 5/19/2000 14:58'! widthOf: aCharacter "Return the width of the given character" ^self subclassResponsibility! ! !AbstractFont methodsFor: 'measuring' stamp: 'ar 5/19/2000 15:00'! widthOfString: aString ^ self composeWord: (1 to: aString size) in: aString beginningAt: 0 " TextStyle default defaultFont widthOfString: 'zort' 21 "! ! !AbstractFont methodsFor: 'displaying' stamp: 'ar 5/19/2000 14:59'! displayString: aString on: aDisplayContext from: startIndex to: stopIndex at: aPoint kern: kernDelta "Draw the given string from startIndex to stopIndex at aPoint on the (already prepared) display context." ^self subclassResponsibility! ! !AbstractFont methodsFor: 'displaying' stamp: 'ar 5/19/2000 14:59'! installOn: aDisplayContext foregroundColor: foregroundColor backgroundColor: backgroundColor "Install the receiver on the given DisplayContext (either BitBlt or Canvas) for further drawing operations." ^self subclassResponsibility! ! Model subclass: #AbstractHierarchicalList instanceVariableNames: 'currentSelection myBrowser ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Explorer'! !AbstractHierarchicalList commentStamp: '' prior: 0! Contributed by Bob Arning as part of the ObjectExplorer package. ! !AbstractHierarchicalList methodsFor: 'as yet unclassified' stamp: 'RAA 6/21/1999 15:22'! genericMenu: aMenu aMenu add: 'no menu yet' target: self selector: #yourself. ^aMenu! ! !AbstractHierarchicalList methodsFor: 'as yet unclassified' stamp: 'RAA 4/7/1999 16:44'! getCurrentSelection ^currentSelection! ! !AbstractHierarchicalList methodsFor: 'as yet unclassified' stamp: 'RAA 4/7/1999 16:46'! noteNewSelection: x currentSelection _ x. self changed: #getCurrentSelection. currentSelection ifNil: [^self]. currentSelection sendSettingMessageTo: self. ! ! !AbstractHierarchicalList methodsFor: 'as yet unclassified' stamp: 'RAA 4/7/1999 16:53'! perform: selector orSendTo: otherTarget "Selector was just chosen from a menu by a user. If can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked." (self respondsTo: selector) ifTrue: [^ self perform: selector] ifFalse: [^ otherTarget perform: selector]! ! !AbstractHierarchicalList methodsFor: 'as yet unclassified' stamp: 'RAA 4/7/1999 16:47'! update: aSymbol aSymbol == #hierarchicalList ifTrue: [ ^self changed: #getList ]. super update: aSymbol! ! Object subclass: #AbstractInstructionPrinter instanceVariableNames: 'bingo ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Methods'! !AbstractInstructionPrinter commentStamp: '' prior: 0! My job is to make it easier to scan bytecodes for specific actions, e.g. any instance variable reference. BlockContext allInstances collect: [ :x | {x. x hasInstVarRef} ].! !AbstractInstructionPrinter methodsFor: 'initialize-release' stamp: 'RAA 1/5/2001 08:43'! interpretNextInstructionUsing: aScanner bingo _ false. aScanner interpretNextInstructionFor: self. ^bingo! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! blockReturnTop "Print the Return Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! doDup "Print the Duplicate Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! doPop "Print the Remove Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:31'! jump: offset "Print the Unconditional Jump bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:32'! jump: offset if: condition "Print the Conditional Jump bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! methodReturnConstant: value "Print the Return Constant bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! methodReturnReceiver "Print the Return Self bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! methodReturnTop "Print the Return Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! popIntoLiteralVariable: anAssociation "Print the Remove Top Of Stack And Store Into Literal Variable bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! popIntoReceiverVariable: offset "Print the Remove Top Of Stack And Store Into Instance Variable bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:33'! popIntoTemporaryVariable: offset "Print the Remove Top Of Stack And Store Into Temporary Variable bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushActiveContext "Print the Push Active Context On Top Of Its Own Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushConstant: value "Print the Push Constant, value, on Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushLiteralVariable: anAssociation "Print the Push Contents Of anAssociation On Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushReceiver "Print the Push Active Context's Receiver on Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushReceiverVariable: offset "Print the Push Contents Of the Receiver's Instance Variable Whose Index is the argument, offset, On Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! pushTemporaryVariable: offset "Print the Push Contents Of Temporary Variable Whose Index Is the argument, offset, On Top Of Stack bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! send: selector super: supered numArgs: numberArguments "Print the Send Message With Selector, selector, bytecode. The argument, supered, indicates whether the receiver of the message is specified with 'super' in the source method. The arguments of the message are found in the top numArguments locations on the stack and the receiver just below them." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! storeIntoLiteralVariable: anAssociation "Print the Store Top Of Stack Into Literal Variable Of Method bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! storeIntoReceiverVariable: offset "Print the Store Top Of Stack Into Instance Variable Of Method bytecode." ! ! !AbstractInstructionPrinter methodsFor: 'instruction decoding' stamp: 'RAA 1/5/2001 08:34'! storeIntoTemporaryVariable: offset "Print the Store Top Of Stack Into Temporary Variable Of Method bytecode." ! ! Object subclass: #AbstractLauncher instanceVariableNames: 'parameters ' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !AbstractLauncher commentStamp: '' prior: 0! The class AutoStart in combination with the Launcher classes provides a mechanism for starting Squeak from the command line or a web page. Parameters on the command line or in the embed tag in the web page a parsed and stored in the lauchner's parameter dictionary. Subclasses can access these parameters to determine what to do. CommandLineLauncherExample provides an example for a command line application. if you start squeak with a command line 'class Integer' it will launch a class browser on class Integer. To enable this execute CommandLineLauncherExample activate before you save the image. To disable execute CommandLineLauncherExample deactivate The PluginLauchner is an example how to use this framework to start Squeak as a browser plugin. It looks for a parameter 'src' which should point to a file containing a squeak script.! !AbstractLauncher methodsFor: 'private' stamp: 'jm 8/20/1999 15:33'! commandLine: aString "Start up this launcher from within Squeak as if it Squeak been launched the given command line." | dict tokens cmd arg | dict _ Dictionary new. tokens _ ReadStream on: (aString findTokens: ' '). [cmd _ tokens next. arg _ tokens next. ((cmd ~~ nil) and: [arg ~~ nil])] whileTrue: [dict at: cmd put: arg]. self parameters: dict. self startUp. ! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 9/23/1999 13:18'! determineParameterNameFrom: alternateParameterNames "Determine which of the given alternate parameter names is actually used." ^alternateParameterNames detect: [:each | self includesParameter: each asUppercase] ifNone: [nil] ! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 1/11/2000 16:35'! includesParameter: parName "Return if the parameter named parName exists." ^self parameters includesKey: parName asUppercase! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 9/23/1999 12:11'! numericParameterAtOneOf: alternateParameterNames ifAbsent: aBlock "Return the parameter named using one of the alternate names or an empty string" | parameterValue | parameterValue _ self parameterAtOneOf: alternateParameterNames. parameterValue isEmpty ifTrue: [^aBlock value]. ^[Number readFrom: parameterValue] ifError: [aBlock] ! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 8/4/1999 14:19'! parameterAt: parName "Return the parameter named parName or an empty string" ^self parameterAt: parName ifAbsent: ['']! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 1/11/2000 16:36'! parameterAt: parName ifAbsent: aBlock "Return the parameter named parName. Evaluate the block if parameter does not exist." ^self parameters at: parName asUppercase ifAbsent: [aBlock value]! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 9/23/1999 12:09'! parameterAtOneOf: alternateParameterNames | parameterName | "Return the parameter named using one of the alternate names or an empty string" parameterName _ self determineParameterNameFrom: alternateParameterNames. ^parameterName isNil ifTrue: [''] ifFalse: [self parameterAt: parameterName ifAbsent: ['']]! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 1/11/2000 16:53'! parameters parameters == nil ifTrue: [parameters _ self class extractParameters]. ^parameters! ! !AbstractLauncher methodsFor: 'private' stamp: 'mir 7/29/1999 10:21'! parameters: startupParameters parameters _ startupParameters! ! !AbstractLauncher methodsFor: 'running' stamp: 'mir 7/29/1999 10:22'! startUp ! ! !AbstractLauncher methodsFor: 'initialization' stamp: 'mir 8/6/1999 18:32'! initialize! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AbstractLauncher class instanceVariableNames: ''! !AbstractLauncher class methodsFor: 'private' stamp: 'mir 8/4/1999 13:57'! autoStarter ^AutoStart! ! !AbstractLauncher class methodsFor: 'private' stamp: 'mir 1/11/2000 16:54'! extractParameters | pName value index globals | globals := Dictionary new. index := 3. "Muss bei 3 starten, da 2 documentName ist" [pName := Smalltalk getSystemAttribute: index. pName isEmptyOrNil] whileFalse:[ index := index + 1. value := Smalltalk getSystemAttribute: index. value ifNil: [value _ '']. globals at: pName asUppercase put: value. index := index + 1]. ^globals! ! !AbstractLauncher class methodsFor: 'activation' stamp: 'mir 8/6/1999 18:14'! activate "Register this launcher with the auto start class" self autoStarter addLauncher: self! ! !AbstractLauncher class methodsFor: 'activation' stamp: 'mir 8/4/1999 13:57'! deactivate "Unregister this launcher with the auto start class" self autoStarter removeLauncherClass: self! ! !AbstractLauncher class methodsFor: 'instance creation' stamp: 'mir 8/6/1999 18:33'! new ^super new initialize! ! RectangleMorph subclass: #AbstractMediaEventMorph instanceVariableNames: 'startTimeInScore endTimeInScore ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !AbstractMediaEventMorph commentStamp: '' prior: 0! An abstract representation of media events to be placed in a PianoRollScoreMorph (or others as they are developed)! !AbstractMediaEventMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/7/2000 12:58'! endTime ^endTimeInScore ifNil: [startTimeInScore + 100]! ! !AbstractMediaEventMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/7/2000 11:37'! initialize super initialize. color _ Color paleYellow. self borderColor: Color black. self borderWidth: 1. self layoutPolicy: TableLayout new. self listDirection: #leftToRight. self wrapCentering: #topLeft. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self layoutInset: 2. self rubberBandCells: true. "default"! ! !AbstractMediaEventMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 18:45'! justDroppedIntoPianoRoll: pianoRoll event: evt | ambientEvent | startTimeInScore _ pianoRoll timeForX: self left. ambientEvent _ AmbientEvent new morph: self; time: startTimeInScore. pianoRoll score addAmbientEvent: ambientEvent. "self endTime > pianoRoll scorePlayer durationInTicks ifTrue: [pianoRoll scorePlayer updateDuration]" ! ! Object subclass: #AbstractScoreEvent instanceVariableNames: 'time ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Scores'! !AbstractScoreEvent commentStamp: '' prior: 0! Abstract class for timed events in a MIDI score. ! !AbstractScoreEvent methodsFor: 'accessing' stamp: 'di 6/17/1999 14:28'! adjustTimeBy: delta time _ time + delta ! ! !AbstractScoreEvent methodsFor: 'accessing' stamp: 'jm 8/27/1998 16:38'! endTime "Subclasses should override to return the ending time if the event has some duration." ^ time ! ! !AbstractScoreEvent methodsFor: 'accessing' stamp: 'jm 12/31/97 11:43'! time ^ time ! ! !AbstractScoreEvent methodsFor: 'accessing' stamp: 'jm 12/31/97 11:43'! time: aNumber time _ aNumber. ! ! !AbstractScoreEvent methodsFor: 'classification' stamp: 'jm 9/10/1998 09:43'! isControlChange ^ false ! ! !AbstractScoreEvent methodsFor: 'classification' stamp: 'jm 12/31/97 11:46'! isNoteEvent ^ false ! ! !AbstractScoreEvent methodsFor: 'classification' stamp: 'jm 9/10/1998 09:43'! isPitchBend ^ false ! ! !AbstractScoreEvent methodsFor: 'classification' stamp: 'jm 9/10/1998 09:43'! isProgramChange ^ false ! ! !AbstractScoreEvent methodsFor: 'classification' stamp: 'jm 12/31/97 11:46'! isTempoEvent ^ false ! ! !AbstractScoreEvent methodsFor: 'midi' stamp: 'jm 9/10/1998 18:31'! outputOnMidiPort: aMidiPort "Output this event to the given MIDI port. This default implementation does nothing." ! ! Object subclass: #AbstractSound instanceVariableNames: 'envelopes mSecsSinceStart samplesUntilNextControl scaledVol scaledVolIncr scaledVolLimit ' classVariableNames: 'FloatScaleFactor MaxScaledValue PitchesForBottomOctave ScaleFactor Sounds TopOfBottomOctave UnloadedSnd ' poolDictionaries: '' category: 'Sound-Synthesis'! !AbstractSound methodsFor: 'initialization' stamp: 'jm 12/9/97 11:31'! duration: seconds "Scale my envelopes to the given duration. Subclasses overriding this method should include a resend to super." envelopes do: [:e | e duration: seconds]. ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 2/4/98 09:54'! initialize envelopes _ #(). mSecsSinceStart _ 0. samplesUntilNextControl _ 0. scaledVol _ (1.0 * ScaleFactor) rounded. scaledVolIncr _ 0. scaledVolLimit _ scaledVol. ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 3/24/1999 12:03'! loudness: aNumber "Initialize my volume envelopes and initial volume. Subclasses overriding this method should include a resend to super." | vol | vol _ (aNumber asFloat max: 0.0) min: 1.0. envelopes do: [:e | (e isKindOf: VolumeEnvelope) ifTrue: [e scale: vol]]. self initialVolume: vol. ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 7/6/1998 17:04'! nameOrNumberToPitch: aStringOrNumber "Answer the pitch in cycles/second for the given pitch specification. The specification can be either a numeric pitch or pitch name such as 'c4'." aStringOrNumber isNumber ifTrue: [^ aStringOrNumber asFloat] ifFalse: [^ AbstractSound pitchForName: aStringOrNumber] ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 8/19/1998 08:45'! setPitch: pitchNameOrNumber dur: d loudness: l "Initialize my envelopes for the given parameters. Subclasses overriding this method should include a resend to super." | p | p _ self nameOrNumberToPitch: pitchNameOrNumber. envelopes do: [:e | e volume: l. e centerPitch: p]. self initialVolume: l. self duration: d. ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 8/3/1998 17:11'! soundForMidiKey: midiKey dur: d loudness: l "Answer an initialized sound object (a copy of the receiver) that generates a note for the given MIDI key (in the range 0..127), duration (in seconds), and loudness (in the range 0.0 to 1.0)." ^ self copy setPitch: (AbstractSound pitchForMIDIKey: midiKey) dur: d loudness: l ! ! !AbstractSound methodsFor: 'initialization' stamp: 'jm 8/3/1998 16:58'! soundForPitch: pitchNameOrNumber dur: d loudness: l "Answer an initialized sound object (a copy of the receiver) that generates a note of the given pitch, duration, and loudness. Pitch may be a numeric pitch or a string pitch name such as 'c4'. Duration is in seconds and loudness is in the range 0.0 to 1.0." ^ self copy setPitch: pitchNameOrNumber dur: d loudness: l ! ! !AbstractSound methodsFor: 'envelopes' stamp: 'jm 12/17/97 22:23'! addEnvelope: anEnvelope "Add the given envelope to my envelopes list." anEnvelope target: self. envelopes _ envelopes copyWith: anEnvelope. ! ! !AbstractSound methodsFor: 'envelopes' stamp: 'jm 12/15/97 17:02'! envelopes "Return my collection of envelopes." ^ envelopes ! ! !AbstractSound methodsFor: 'envelopes' stamp: 'jm 8/18/1998 09:57'! removeAllEnvelopes "Remove all envelopes from my envelopes list." envelopes _ #(). ! ! !AbstractSound methodsFor: 'envelopes' stamp: 'jm 12/15/97 17:02'! removeEnvelope: anEnvelope "Remove the given envelope from my envelopes list." envelopes _ envelopes copyWithout: anEnvelope. ! ! !AbstractSound methodsFor: 'volume' stamp: 'RAA 8/11/2000 11:51'! adjustVolumeTo: vol overMSecs: mSecs "Adjust the volume of this sound to the given volume, a number in the range [0.0..1.0], over the given number of milliseconds. The volume will be changed a little bit on each sample until the desired volume is reached." | newScaledVol | self flag: #bob. "I removed the upper limit to allow making sounds louder. hmm..." newScaledVol _ (32768.0 * vol) truncated. newScaledVol = scaledVol ifTrue: [^ self]. scaledVolLimit _ newScaledVol. "scaledVolLimit > ScaleFactor ifTrue: [scaledVolLimit _ ScaleFactor]." scaledVolLimit < 0 ifTrue: [scaledVolLimit _ 0]. mSecs = 0 ifTrue: [ "change immediately" scaledVol _ scaledVolLimit. scaledVolIncr _ 0] ifFalse: [ scaledVolIncr _ ((scaledVolLimit - scaledVol) * 1000) // (self samplingRate * mSecs)]. ! ! !AbstractSound methodsFor: 'volume' stamp: 'jm 12/17/97 17:39'! initialVolume: vol "Set the initial volume of this sound to the given volume, a number in the range [0.0..1.0]." scaledVol _ (((vol asFloat min: 1.0) max: 0.0) * ScaleFactor) rounded. scaledVolLimit _ scaledVol. scaledVolIncr _ 0. ! ! !AbstractSound methodsFor: 'volume' stamp: 'jm 8/13/1998 16:37'! loudness "Answer the current volume setting for this sound." ^ scaledVol asFloat / ScaleFactor asFloat! ! !AbstractSound methodsFor: 'volume' stamp: 'jm 8/13/1998 16:28'! volumeEnvelopeScaledTo: scalePoint "Return a collection of values representing my volume envelope scaled by the given point. The scale point's x component is pixels/second and its y component is the number of pixels for full volume." self error: 'not yet implemented'. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 1/26/98 22:05'! computeSamplesForSeconds: seconds "Compute the samples of this sound without outputting them, and return the resulting buffer of samples." | buf | self reset. buf _ SoundBuffer newStereoSampleCount: (self samplingRate * seconds) asInteger. self playSampleCount: buf stereoSampleCount into: buf startingAt: 1. ^ buf ! ! !AbstractSound methodsFor: 'playing' stamp: 'ar 12/5/1998 22:20'! isPlaying "Return true if the receiver is currently playing" ^ SoundPlayer isPlaying: self! ! !AbstractSound methodsFor: 'playing' stamp: 'di 5/30/1999 12:46'! millisecondsSinceStart ^ mSecsSinceStart! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 8/24/97 20:48'! pause "Pause this sound. It can be resumed from this point, or reset and resumed to start from the beginning." SoundPlayer pauseSound: self.! ! !AbstractSound methodsFor: 'playing'! play "Play this sound to the sound ouput port in real time." SoundPlayer playSound: self.! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 8/13/1998 15:09'! playAndWaitUntilDone "Play this sound to the sound ouput port and wait until it has finished playing before returning." SoundPlayer playSound: self. [self samplesRemaining > 0] whileTrue. (Delay forMilliseconds: 2 * SoundPlayer bufferMSecs) wait. "ensure last buffer has been output" ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 8/18/1998 10:52'! playChromaticRunFrom: startPitch to: endPitch "Play a fast chromatic run between the given pitches. Useful for auditioning a sound." (AbstractSound chromaticRunFrom: startPitch to: endPitch on: self) play. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 8/13/1998 16:17'! playSampleCount: n into: aSoundBuffer startingAt: startIndex "Mix the next n samples of this sound into the given buffer starting at the given index. Update the receiver's control parameters periodically." | fullVol samplesBetweenControlUpdates pastEnd i remainingSamples count | fullVol _ AbstractSound scaleFactor. samplesBetweenControlUpdates _ self samplingRate // self controlRate. pastEnd _ startIndex + n. "index just after the last sample" i _ startIndex. [i < pastEnd] whileTrue: [ remainingSamples _ self samplesRemaining. remainingSamples <= 0 ifTrue: [^ self]. count _ pastEnd - i. samplesUntilNextControl < count ifTrue: [count _ samplesUntilNextControl]. remainingSamples < count ifTrue: [count _ remainingSamples]. self mixSampleCount: count into: aSoundBuffer startingAt: i leftVol: fullVol rightVol: fullVol. samplesUntilNextControl _ samplesUntilNextControl - count. samplesUntilNextControl <= 0 ifTrue: [ self doControl. samplesUntilNextControl _ samplesBetweenControlUpdates]. i _ i + count]. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 7/5/1998 17:53'! playSilently "Compute the samples of this sound without outputting them. Used for performance analysis." | bufSize buf | self reset. bufSize _ self samplingRate // 10. buf _ SoundBuffer newStereoSampleCount: bufSize. [self samplesRemaining > 0] whileTrue: [ buf primFill: 0. self playSampleCount: bufSize into: buf startingAt: 1]. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 1/26/98 22:06'! playSilentlyUntil: startTime "Compute the samples of this sound without outputting them. Used to fast foward to a particular starting time. The start time is given in seconds." | buf startSample nextSample samplesRemaining n | self reset. buf _ SoundBuffer newStereoSampleCount: (self samplingRate // 10). startSample _ (startTime * self samplingRate) asInteger. nextSample _ 1. [self samplesRemaining > 0] whileTrue: [ nextSample >= startSample ifTrue: [^ self]. samplesRemaining _ startSample - nextSample. samplesRemaining > buf stereoSampleCount ifTrue: [n _ buf stereoSampleCount] ifFalse: [n _ samplesRemaining]. self playSampleCount: n into: buf startingAt: 1. nextSample _ nextSample + n]. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 3/4/98 13:16'! resumePlaying "Resume playing this sound from where it last stopped." SoundPlayer resumePlaying: self. ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 8/19/1998 08:30'! viewSamples | stereoBuf | stereoBuf _ self computeSamplesForSeconds: self duration. WaveEditor openOn: stereoBuf extractLeftChannel. ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 8/17/1998 13:34'! doControl "Update the control parameters of this sound using its envelopes, if any." "Note: This is only called at a small fraction of the sampling rate." | pitchModOrRatioChange | envelopes size > 0 ifTrue: [ pitchModOrRatioChange _ false. 1 to: envelopes size do: [:i | ((envelopes at: i) updateTargetAt: mSecsSinceStart) ifTrue: [pitchModOrRatioChange _ true]]. pitchModOrRatioChange ifTrue: [self internalizeModulationAndRatio]]. mSecsSinceStart _ mSecsSinceStart + (1000 // self controlRate). ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 2/4/98 08:56'! internalizeModulationAndRatio "Overridden by FMSound. This default implementation does nothing." ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 7/6/1998 06:40'! mixSampleCount: n into: aSoundBuffer startingAt: startIndex leftVol: leftVol rightVol: rightVol "Mix the given number of samples with the samples already in the given buffer starting at the given index. Assume that the buffer size is at least (index + count) - 1. The leftVol and rightVol parameters determine the volume of the sound in each channel, where 0 is silence and ScaleFactor is full volume." self subclassResponsibility. ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 8/17/1998 13:45'! reset "Reset my internal state for a replay. Methods that override this method should do super reset." mSecsSinceStart _ 0. samplesUntilNextControl _ 0. envelopes size > 0 ifTrue: [ 1 to: envelopes size do: [:i | (envelopes at: i) reset]]. ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 12/17/97 17:57'! samplesRemaining "Answer the number of samples remaining until the end of this sound. A sound with an indefinite ending time should answer some large integer such as 1000000." ^ 1000000 ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 9/9/1998 21:56'! stopAfterMSecs: mSecs "Terminate this sound this note after the given number of milliseconds. This default implementation does nothing." ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 9/9/1998 21:54'! stopGracefully "End this note with a graceful decay. If the note has envelopes, determine the decay time from its envelopes." | decayInMs env | envelopes isEmpty ifTrue: [ self adjustVolumeTo: 0 overMSecs: 10. decayInMs _ 10] ifFalse: [ env _ envelopes first. decayInMs _ env attackTime + env decayTime]. self duration: (mSecsSinceStart + decayInMs) / 1000.0. self stopAfterMSecs: decayInMs. ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 1/5/98 14:21'! storeSample: sample in: aSoundBuffer at: sliceIndex leftVol: leftVol rightVol: rightVol "This method is provided for documentation. To gain 10% more speed when running sound generation in Smalltalk, this method is hand-inlined into all sound generation methods that use it." | i s | leftVol > 0 ifTrue: [ i _ (2 * sliceIndex) - 1. s _ (aSoundBuffer at: i) + ((sample * leftVol) // ScaleFactor). s > 32767 ifTrue: [s _ 32767]. "clipping!!" s < -32767 ifTrue: [s _ -32767]. "clipping!!" aSoundBuffer at: i put: s]. rightVol > 0 ifTrue: [ i _ 2 * sliceIndex. s _ (aSoundBuffer at: i) + ((sample * rightVol) // ScaleFactor). s > 32767 ifTrue: [s _ 32767]. "clipping!!" s < -32767 ifTrue: [s _ -32767]. "clipping!!" aSoundBuffer at: i put: s]. ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'jm 12/17/97 17:57'! updateVolume "Increment the volume envelope of this sound. To avoid clicks, the volume envelope must be interpolated at the sampling rate, rather than just at the control rate like other envelopes. At the control rate, the volume envelope computes the slope and next target volume volume for the current segment of the envelope (i.e., it sets the rate of change for the volume parameter). When that target volume is reached, incrementing is stopped until a new increment is set." "This method is provided for documentation. To gain 10% more speed when running sound generation in Smalltalk, it is hand-inlined into all sound generation methods that use it." scaledVolIncr ~= 0 ifTrue: [ scaledVol _ scaledVol + scaledVolIncr. ((scaledVolIncr > 0 and: [scaledVol >= scaledVolLimit]) or: [scaledVolIncr < 0 and: [scaledVol <= scaledVolLimit]]) ifTrue: [ "reached the limit; stop incrementing" scaledVol _ scaledVolLimit. scaledVolIncr _ 0]]. ! ! !AbstractSound methodsFor: 'composition'! + aSound "Return the mix of the receiver and the argument sound." ^ MixedSound new add: self; add: aSound ! ! !AbstractSound methodsFor: 'composition'! , aSound "Return the concatenation of the receiver and the argument sound." ^ SequentialSound new add: self; add: aSound ! ! !AbstractSound methodsFor: 'composition' stamp: 'jm 2/2/1999 15:53'! asSound ^ self ! ! !AbstractSound methodsFor: 'composition' stamp: 'jm 12/17/97 18:00'! delayedBy: seconds "Return a composite sound consisting of a rest for the given amount of time followed by the receiver." ^ (RestSound dur: seconds), self ! ! !AbstractSound methodsFor: 'sampling rates' stamp: 'jm 12/15/97 14:15'! controlRate "Answer the number of control changes per second." ^ 100 ! ! !AbstractSound methodsFor: 'sampling rates' stamp: 'jm 12/17/97 18:00'! samplingRate "Answer the sampling rate in samples per second." ^ SoundPlayer samplingRate ! ! !AbstractSound methodsFor: 'copying' stamp: 'jm 12/15/97 19:15'! copy "A sound should copy all of the state needed to play itself, allowing two copies of a sound to play at the same time. These semantics require a recursive copy but only down to the level of immutable data. For example, a SampledSound need not copy its sample buffer. Subclasses overriding this method should include a resend to super." ^ self clone copyEnvelopes ! ! !AbstractSound methodsFor: 'copying' stamp: 'jm 12/17/97 22:22'! copyEnvelopes "Private!! Support for copying. Copy my envelopes." envelopes _ envelopes collect: [:e | e copy target: self]. ! ! !AbstractSound methodsFor: 'copying' stamp: 'di 3/4/1999 21:29'! sounds "Allows simple sounds to behave as, eg, sequential sounds" ^ Array with: self! ! !AbstractSound methodsFor: 'file i/o' stamp: 'jm 3/13/1999 11:47'! storeAIFFOnFileNamed: fileName | f | f _ (FileStream fileNamed: fileName) binary. self storeAIFFSamples: self samples samplingRate: self originalSamplingRate on: f. f close. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'jm 3/13/1999 11:48'! storeAIFFSamples: aSoundBuffer samplingRate: rate on: aBinaryStream | sampleCount s | sampleCount _ aSoundBuffer monoSampleCount. aBinaryStream nextPutAll: 'FORM' asByteArray. aBinaryStream nextInt32Put: (2 * sampleCount) + ((7 * 4) + 18). aBinaryStream nextPutAll: 'AIFF' asByteArray. aBinaryStream nextPutAll: 'COMM' asByteArray. aBinaryStream nextInt32Put: 18. aBinaryStream nextNumber: 2 put: 1. "channels" aBinaryStream nextInt32Put: sampleCount. aBinaryStream nextNumber: 2 put: 16. "bits/sample" self storeExtendedFloat: rate on: aBinaryStream. aBinaryStream nextPutAll: 'SSND' asByteArray. aBinaryStream nextInt32Put: (2 * sampleCount) + 8. aBinaryStream nextInt32Put: 0. aBinaryStream nextInt32Put: 0. 1 to: sampleCount do: [:i | s _ aSoundBuffer at: i. aBinaryStream nextPut: ((s bitShift: -8) bitAnd: 16rFF). aBinaryStream nextPut: (s bitAnd: 16rFF)]. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'jm 3/13/1999 11:34'! storeExtendedFloat: aNumber on: aBinaryStream "Store an Apple extended-precision 80-bit floating point number on the given stream." "Details: I could not find the specification for this format, so constants were determined empirically based on assumption of 1-bit sign, 15-bit exponent, 64-bit mantissa. This format does not seem to have an implicit one before the mantissa as some float formats do." | n isNeg exp mantissa | n _ aNumber asFloat. isNeg _ false. n < 0.0 ifTrue: [ n _ 0.0 - n. isNeg _ true]. exp _ (n log: 2.0) ceiling. mantissa _ (n * (2 raisedTo: 64 - exp)) truncated. exp _ exp + 16r4000 - 2. "not sure why the -2 is needed..." isNeg ifTrue: [exp _ exp bitOr: 16r8000]. "set sign bit" aBinaryStream nextPut: ((exp bitShift: -8) bitAnd: 16rFF). aBinaryStream nextPut: (exp bitAnd: 16rFF). 8 to: 1 by: -1 do: [:i | aBinaryStream nextPut: (mantissa digitAt: i)]. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'JMV 1/19/2001 12:08'! storeWAVOnFileNamed: fileName | f | f _ (FileStream fileNamed: fileName) binary. self storeWAVSamplesSamplingRate: self samplingRate on: f. f close.! ! !AbstractSound methodsFor: 'file i/o' stamp: 'JMV 1/26/2001 11:36'! storeWAVSamplesSamplingRate: rate on: aBinaryStream "Write WAV sound file. Stereo, 16 bit. At the appropiate sampling rate." | bufferSize buffer fullBufferCount lastBufferSize finalSampleCount | self reset. finalSampleCount _ (self duration * self samplingRate) ceiling. bufferSize _ self samplingRate rounded min: finalSampleCount. "One second. Could be any size." fullBufferCount _ finalSampleCount // bufferSize. lastBufferSize _ finalSampleCount \\ bufferSize. "File header" aBinaryStream nextPutAll: 'RIFF' asByteArray; nextLittleEndianNumber: 4 put: finalSampleCount * 4 + 36; "Lenght of all chunks" nextPutAll: 'WAVE' asByteArray. "Format Chunk" aBinaryStream nextPutAll: 'fmt ' asByteArray; nextLittleEndianNumber: 4 put: 16; "Lenght of this chunk" nextLittleEndianNumber: 2 put: 1; "Format tag" nextLittleEndianNumber: 2 put: 2; "Channel count" nextLittleEndianNumber: 4 put: self samplingRate rounded; "Samples per sec" nextLittleEndianNumber: 4 put: self samplingRate rounded * 4; "Bytes per sec" nextLittleEndianNumber: 2 put: 4; "Alignment" nextLittleEndianNumber: 2 put: 16. "Bits per sample" "Data chunk" aBinaryStream nextPutAll: 'data' asByteArray; nextLittleEndianNumber: 4 put: finalSampleCount * 4. "Lenght of this chunk" fullBufferCount timesRepeat: [ buffer _ SoundBuffer newStereoSampleCount: bufferSize. self playSampleCount: bufferSize into: buffer startingAt: 1. buffer do: [ :sample | aBinaryStream nextLittleEndianNumber: 2 put: sample \\ 65536 ]. ]. buffer _ SoundBuffer newStereoSampleCount: lastBufferSize. self playSampleCount: lastBufferSize into: buffer startingAt: 1. buffer do: [ :sample | aBinaryStream nextLittleEndianNumber: 2 put: sample \\ 65536].! ! !AbstractSound methodsFor: 'conversion' stamp: 'mjg 12/3/1999 12:58'! asSampledSound ^SampledSound samples: (self computeSamplesForSeconds: self duration) samplingRate: (self samplingRate)*2. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AbstractSound class instanceVariableNames: ''! !AbstractSound class methodsFor: 'class initialization' stamp: 'jm 8/3/1998 16:13'! initialize "AbstractSound initialize" | bottomC | ScaleFactor _ 2 raisedTo: 15. FloatScaleFactor _ ScaleFactor asFloat. MaxScaledValue _ ((2 raisedTo: 31) // ScaleFactor) - 1. "magnitude of largest scaled value in 32-bits" "generate pitches for c-1 through c0" bottomC _ (440.0 / 32) * (2.0 raisedTo: -9.0 / 12.0). PitchesForBottomOctave _ (0 to: 12) collect: [:i | bottomC * (2.0 raisedTo: i asFloat / 12.0)]. TopOfBottomOctave _ PitchesForBottomOctave last. ! ! !AbstractSound class methodsFor: 'class initialization' stamp: 'jm 1/5/98 13:51'! scaleFactor ^ ScaleFactor ! ! !AbstractSound class methodsFor: 'instance creation' stamp: 'jm 1/5/98 17:40'! default "Return a default sound prototype for this class, with envelopes if appropriate. (This is in contrast to new, which returns a raw instance without envelopes.)" ^ self new ! ! !AbstractSound class methodsFor: 'instance creation' stamp: 'jm 12/17/97 17:26'! dur: d "Return a rest of the given duration." ^ self basicNew setDur: d ! ! !AbstractSound class methodsFor: 'instance creation' stamp: 'jm 12/17/97 17:26'! new ^ self basicNew initialize ! ! !AbstractSound class methodsFor: 'instance creation' stamp: 'jm 8/3/1998 17:00'! noteSequenceOn: aSound from: anArray "Build a note sequence (i.e., a SequentialSound) from the given array using the given sound as the instrument. Elements are either (pitch, duration, loudness) triples or (#rest duration) pairs. Pitches can be given as names or as numbers." | score pitch | score _ SequentialSound new. anArray do: [:el | el size = 3 ifTrue: [ pitch _ el at: 1. pitch isNumber ifFalse: [pitch _ self pitchForName: pitch]. score add: ( aSound soundForPitch: pitch dur: (el at: 2) loudness: (el at: 3) / 1000.0)] ifFalse: [ score add: (RestSound dur: (el at: 2))]]. ^ score ! ! !AbstractSound class methodsFor: 'instance creation' stamp: 'jm 12/17/97 17:27'! pitch: p dur: d loudness: l "Return a new sound object for a note with the given parameters." ^ self new setPitch: p dur: d loudness: l ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'DSM 9/5/2000 13:50'! busySignal: count "AbstractSound busySignal: 3" | m s | s _ SequentialSound new. m _ MixedSound new. m add: (FMSound new setPitch: 480 dur: 0.5 loudness: 0.5); add: (FMSound new setPitch: 620 dur: 0.5 loudness: 0.5). s add: m. s add: (FMSound new setPitch: 1 dur: 0.5 loudness: 0). ^ (RepeatingSound repeat: s count: count) play. ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'DSM 9/5/2000 13:56'! dial: aString | index lo hi m s | "AbstractSound dial: '867-5309'" "ask for Jenny" s _ SequentialSound new. aString do: [ :c | c = $, ifTrue: [ s add: (FMSound new setPitch: 1 dur: 1 loudness: 0) ] ifFalse: [ (index _ ('123A456B789C*0#D' indexOf: c)) > 0 ifTrue: [ lo _ #(697 770 852 941) at: (index - 1 // 4 + 1). hi _ #(1209 1336 1477 1633) at: (index - 1 \\ 4 + 1). m _ MixedSound new. m add: (FMSound new setPitch: lo dur: 0.15 loudness: 0.5). m add: (FMSound new setPitch: hi dur: 0.15 loudness: 0.5). s add: m. s add: (FMSound new setPitch: 1 dur: 0.05 loudness: 0)]]]. ^ s play. ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'DSM 9/5/2000 13:49'! dialTone: duration "AbstractSound dialTone: 2" | m | m _ MixedSound new. m add: (FMSound new setPitch: 350 dur: duration loudness: 0.5). m add: (FMSound new setPitch: 440 dur: duration loudness: 0.5). m play. ^ m! ! !AbstractSound class methodsFor: 'utilities' stamp: 'DSM 9/5/2000 13:50'! hangUpWarning: count "AbstractSound hangUpWarning: 20" | m s | s _ SequentialSound new. m _ MixedSound new. m add: (FMSound new setPitch: 1400 dur: 0.1 loudness: 0.5); add: (FMSound new setPitch: 2060 dur: 0.1 loudness: 0.5). s add: m; add: (FMSound new setPitch: 1 dur: 0.1 loudness: 0). ^ (RepeatingSound repeat: s count: count) play ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'jm 8/3/1998 16:16'! indexOfBottomOctavePitch: p "Answer the index of the first pitch in the bottom octave equal to or higher than the given pitch. Assume that the given pitch is below the top pitch of the bottom octave." 1 to: PitchesForBottomOctave size do: [:i | (PitchesForBottomOctave at: i) >= p ifTrue: [^ i]]. self error: 'implementation error: argument pitch should be below or within the bottom octave'. ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'jm 8/3/1998 16:16'! midiKeyForPitch: pitchNameOrNumber "Answer the midiKey closest to the given pitch. Pitch may be a numeric pitch or a pitch name string such as 'c4'." "AbstractSound midiKeyForPitch: 440.0" | p octave i midiKey | pitchNameOrNumber isNumber ifTrue: [p _ pitchNameOrNumber asFloat] ifFalse: [p _ AbstractSound pitchForName: pitchNameOrNumber]. octave _ -1. [p >= TopOfBottomOctave] whileTrue: [ octave _ octave + 1. p _ p / 2.0]. i _ self indexOfBottomOctavePitch: p. (i > 1) ifTrue: [ (p - (PitchesForBottomOctave at: i - 1)) < ((PitchesForBottomOctave at: i) - p) ifTrue: [i _ i - 1]]. midiKey _ ((octave * 12) + 11 + i). midiKey > 127 ifTrue: [midiKey _ 127]. ^ midiKey ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'jm 8/3/1998 16:43'! pitchForMIDIKey: midiKey "Answer the pitch for the given MIDI key." "(1 to: 127) collect: [:i | AbstractSound pitchForMIDIKey: i]" | indexInOctave octave | indexInOctave _ (midiKey \\ 12) + 1. octave _ (midiKey // 12) + 1. ^ (PitchesForBottomOctave at: indexInOctave) * (#(1.0 2.0 4.0 8.0 16.0 32.0 64.0 128.0 256.0 512.0 1024.0) at: octave) ! ! !AbstractSound class methodsFor: 'utilities'! pitchForName: aString "AbstractSound pitchForName: 'c2'" "#(c 'c#' d eb e f fs g 'g#' a bf b) collect: [ :s | AbstractSound pitchForName: s, '4']" | s modifier octave i j noteName p | s _ ReadStream on: aString. modifier _ $n. noteName _ s next. (s atEnd not and: [s peek isDigit]) ifFalse: [ modifier _ s next ]. s atEnd ifTrue: [ octave _ 4 ] ifFalse: [ octave _ Integer readFrom: s ]. octave < 0 ifTrue: [ self error: 'cannot use negative octave number' ]. i _ 'cdefgab' indexOf: noteName. i = 0 ifTrue: [ self error: 'bad note name: ', noteName asString ]. i _ #(2 4 6 7 9 11 13) at: i. j _ 's#fb' indexOf: modifier. j = 0 ifFalse: [ i _ i + (#(1 1 -1 -1) at: j) ]. "i is now in range: [1..14]" "Table generator: (1 to: 14) collect: [ :i | 16.3516 * (2.0 raisedTo: (i - 2) asFloat / 12.0)]" p _ #(15.4339 16.3516 17.3239 18.354 19.4454 20.6017 21.8268 23.1247 24.4997 25.9565 27.5 29.1352 30.8677 32.7032) at: i. octave timesRepeat: [ p _ 2.0 * p ]. ^ p ! ! !AbstractSound class methodsFor: 'utilities' stamp: 'jm 7/6/1998 15:47'! pitchTable "AbstractSound pitchTable" | out note i | out _ WriteStream on: (String new: 1000). i _ 12. 0 to: 8 do: [:octave | #(c 'c#' d eb e f fs g 'g#' a bf b) do: [:noteName | note _ noteName, octave printString. out nextPutAll: note; tab. out nextPutAll: i printString; tab. out nextPutAll: (AbstractSound pitchForName: note) printString; cr. i _ i + 1]]. ^ out contents ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 6/30/1998 18:40'! chromaticPitchesFrom: aPitch | halfStep pitch | halfStep _ 2.0 raisedTo: (1.0 / 12.0). pitch _ aPitch isNumber ifTrue: [aPitch] ifFalse: [self pitchForName: aPitch]. pitch _ pitch / halfStep. ^ (0 to: 14) collect: [:i | pitch _ pitch * halfStep] ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 8/18/1998 11:32'! chromaticRunFrom: startPitch to: endPitch on: aSound "Answer a composite sound consisting of a rapid chromatic run between the given pitches on the given sound." "(AbstractSound chromaticRunFrom: 'c3' to: 'c#5' on: FMSound oboe1) play" | scale halfStep pEnd p | scale _ SequentialSound new. halfStep _ 2.0 raisedTo: (1.0 / 12.0). endPitch isNumber ifTrue: [pEnd _ endPitch asFloat] ifFalse: [pEnd _ AbstractSound pitchForName: endPitch]. startPitch isNumber ifTrue: [p _ startPitch asFloat] ifFalse: [p _ AbstractSound pitchForName: startPitch]. [p <= pEnd] whileTrue: [ scale add: (aSound soundForPitch: p dur: 0.2 loudness: 0.5). p _ p * halfStep]. ^ scale ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/5/98 17:35'! chromaticScale "PluckedSound chromaticScale play" ^ self chromaticScaleOn: self default ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/31/98 16:14'! chromaticScaleOn: aSound "PluckedSound chromaticScale play" ^ self noteSequenceOn: aSound from: (((self chromaticPitchesFrom: #c4) copyFrom: 1 to: 13) collect: [:pitch | Array with: pitch with: 0.5 with: 300]) ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/5/98 17:36'! hiMajorScale "FMSound hiMajorScale play" ^ self hiMajorScaleOn: self default ! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 1/30/98 16:00'! hiMajorScaleOn: aSound "FMSound hiMajorScale play" ^ self majorScaleOn: aSound from: #c6! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/5/98 17:36'! lowMajorScale "PluckedSound lowMajorScale play" ^ self lowMajorScaleOn: self default ! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 1/30/98 16:01'! lowMajorScaleOn: aSound "PluckedSound lowMajorScale play" ^ self majorScaleOn: aSound from: #c3! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 1/30/98 16:04'! majorChord "FMSound majorChord play" ^ self majorChordOn: self default from: #c4! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 8/3/1998 17:00'! majorChordOn: aSound from: aPitch "FMSound majorChord play" | score majorScale leadingRest pan note | majorScale _ self majorPitchesFrom: aPitch. score _ MixedSound new. leadingRest _ pan _ 0. #(1 3 5 8) do: [:noteIndex | note _ aSound soundForPitch: (majorScale at: noteIndex) dur: 2.0 - leadingRest loudness: 0.3. score add: (RestSound dur: leadingRest), note pan: pan. leadingRest _ leadingRest + 0.2. pan _ pan + 0.3]. ^ score ! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 1/30/98 14:45'! majorPitchesFrom: aPitch | chromatic | chromatic _ self chromaticPitchesFrom: aPitch. ^ #(1 3 5 6 8 10 12 13 15 13 12 10 8 6 5 3 1) collect: [:i | chromatic at: i]. ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/5/98 17:34'! majorScale "FMSound majorScale play" ^ self majorScaleOn: self default ! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 1/30/98 16:00'! majorScaleOn: aSound "FMSound majorScale play" ^ self majorScaleOn: aSound from: #c5! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 7/13/1998 13:09'! majorScaleOn: aSound from: aPitch "FMSound majorScale play" ^ self noteSequenceOn: aSound from: ((self majorPitchesFrom: aPitch) collect: [:pitch | Array with: pitch with: 0.5 with: 300]) ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/4/1999 09:26'! majorScaleOn: aSound from: aPitch octaves: octaveCount "(AbstractSound majorScaleOn: FMSound oboe1 from: #c2 octaves: 5) play" | startingPitch pitches chromatic | startingPitch _ aPitch isNumber ifTrue: [aPitch] ifFalse: [self pitchForName: aPitch]. pitches _ OrderedCollection new. 0 to: octaveCount - 1 do: [:i | chromatic _ self chromaticPitchesFrom: startingPitch * (2 raisedTo: i). #(1 3 5 6 8 10 12) do: [:j | pitches addLast: (chromatic at: j)]]. pitches addLast: startingPitch * (2 raisedTo: octaveCount). ^ self noteSequenceOn: aSound from: (pitches collect: [:pitch | Array with: pitch with: 0.5 with: 300]) ! ! !AbstractSound class methodsFor: 'examples' stamp: 'jm 1/5/98 17:32'! scaleTest "AbstractSound scaleTest play" ^ MixedSound new add: FMSound majorScale pan: 0; add: (PluckedSound lowMajorScale delayedBy: 0.5) pan: 1.0. ! ! !AbstractSound class methodsFor: 'examples' stamp: 'di 4/13/1999 13:53'! testFMInteractively "Experiment with different settings of the FM modulation and multiplier settings interactively by moving the mouse. The top-left corner of the screen is 0 for both parameters. Stop when the mouse is pressed." "AbstractSound testFMInteractively" | s mousePt lastVal status mod ratio | SoundPlayer startPlayerProcessBufferSize: 1100 rate: 11025 stereo: false. s _ FMSound pitch: 440.0 dur: 200.0 loudness: 0.2. SoundPlayer playSound: s. lastVal _ nil. [Sensor anyButtonPressed] whileFalse: [ mousePt _ Sensor cursorPoint. mousePt ~= lastVal ifTrue: [ mod _ mousePt x asFloat / 20.0. ratio _ mousePt y asFloat / 20.0. s modulation: mod ratio: ratio. lastVal _ mousePt. status _ 'mod: ', mod printString, ' ratio: ', ratio printString. status displayOn: Display at: 10@10]]. SoundPlayer shutDown. ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 1/5/98 17:38'! bachFugue "Play a fugue by J. S. Bach using and instance of me as the sound for all four voices." "PluckedSound bachFugue play" ^ self bachFugueOn: self default ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 1/5/98 18:27'! bachFugueOn: aSound "Play a fugue by J. S. Bach using the given sound as the sound for all four voices." "PluckedSound bachFugue play" ^ MixedSound new add: (self bachFugueVoice1On: aSound) pan: 1.0; add: (self bachFugueVoice2On: aSound) pan: 0.0; add: (self bachFugueVoice3On: aSound) pan: 1.0; add: (self bachFugueVoice4On: aSound) pan: 0.0. ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 12/17/97 16:51'! bachFugueVoice1On: aSound "Voice one of a fugue by J. S. Bach." ^ self noteSequenceOn: aSound from: #( (1047 0.15 268) (988 0.15 268) (1047 0.30 268) (784 0.30 268) (831 0.30 268) (1047 0.15 268) (988 0.15 268) (1047 0.30 268) (1175 0.30 268) (784 0.30 268) (1047 0.15 268) (988 0.15 268) (1047 0.30 268) (1175 0.30 268) (698 0.15 268) (784 0.15 268) (831 0.60 268) (784 0.15 268) (698 0.15 268) (622 0.15 268) (1047 0.15 268) (988 0.15 268) (880 0.15 268) (784 0.15 268) (698 0.15 268) (622 0.15 268) (587 0.15 268) (523 0.30 268) (1245 0.30 268) (1175 0.30 268) (1047 0.30 268) (932 0.30 268) (880 0.30 268) (932 0.30 268) (1047 0.30 268) (740 0.30 268) (784 0.30 268) (880 0.30 268) (740 0.30 268) (784 0.60 268) (rest 0.15) (523 0.15 268) (587 0.15 268) (622 0.15 268) (698 0.15 268) (784 0.15 268) (831 0.45 268) (587 0.15 268) (622 0.15 268) (698 0.15 268) (784 0.15 268) (880 0.15 268) (932 0.45 268) (622 0.15 268) (698 0.15 268) (784 0.15 268) (831 0.15 268) (784 0.15 268) (698 0.15 268) (622 0.15 268) (587 0.30 268) (1047 0.15 268) (988 0.15 268) (1047 0.60 268) (rest 0.9) (1397 0.30 268) (1245 0.30 268) (1175 0.30 268) (rest 0.3) (831 0.30 268) (784 0.30 268) (698 0.30 268) (784 0.30 268) (698 0.15 268) (622 0.15 268) (698 0.30 268) (587 0.30 268) (784 0.60 268) (rest 0.3) (988 0.30 268) (1047 0.30 268) (1047 0.15 268) (988 0.15 268) (1047 0.30 268) (784 0.30 268) (831 0.60 268) (rest 0.3) (880 0.30 268) (932 0.30 268) (932 0.15 268) (880 0.15 268) (932 0.30 268) (698 0.30 268) (784 0.60 268) (rest 0.3) (784 0.30 268) (831 0.30 268) (831 0.30 268) (784 0.30 268) (698 0.30 268) (rest 0.3) (415 0.30 268) (466 0.30 268) (523 0.30 268) (rest 0.3) (415 0.15 268) (392 0.15 268) (415 0.30 268) (349 0.30 268) (466 0.30 268) (523 0.30 268) (466 0.30 268) (415 0.30 268) (466 0.30 268) (392 0.30 268) (349 0.30 268) (311 0.30 268) (349 0.30 268) (554 0.30 268) (523 0.30 268) (466 0.30 268) (523 0.30 268) (415 0.30 268) (392 0.30 268) (349 0.30 268) (392 0.30 268) (784 0.15 268) (740 0.15 268) (784 0.30 268) (523 0.30 268) (622 0.30 268) (784 0.15 268) (740 0.15 268) (784 0.30 268) (880 0.30 268) (587 0.30 268) (784 0.15 268) (740 0.15 268) (784 0.30 268) (880 0.30 268) (523 0.15 268) (587 0.15 268) (622 0.60 268) (587 0.15 268) (523 0.15 268) (466 0.30 346) (rest 0.45) (587 0.15 346) (659 0.15 346) (740 0.15 346) (784 0.15 346) (880 0.15 346) (932 0.45 346) (659 0.15 346) (698 0.15 346) (784 0.15 346) (880 0.15 346) (932 0.15 346) (1047 0.45 346) (740 0.15 346) (784 0.15 346) (880 0.15 346) (932 0.30 346) (622 0.15 346) (587 0.15 346) (622 0.30 346) (392 0.30 346) (415 0.30 346) (698 0.15 346) (622 0.15 346) (698 0.30 346) (440 0.30 346) (466 0.30 346) (784 0.15 346) (698 0.15 346) (784 0.30 346) (494 0.30 346) (523 0.15 346) (698 0.15 346) (622 0.15 346) (587 0.15 346) (523 0.15 346) (466 0.15 346) (440 0.15 346) (392 0.15 346) (349 0.30 346) (831 0.30 346) (784 0.30 346) (698 0.30 346) (622 0.30 346) (587 0.30 346) (622 0.30 346) (698 0.30 346) (494 0.30 346) (523 0.30 346) (587 0.30 346) (494 0.30 346) (523 0.60 346) (rest 0.3) (659 0.30 346) (698 0.30 346) (698 0.15 346) (659 0.15 346) (698 0.30 346) (523 0.30 346) (587 0.60 346) (rest 0.3) (587 0.30 346) (622 0.30 346) (622 0.15 346) (587 0.15 346) (622 0.30 346) (466 0.30 346) (523 1.20 346) (523 0.30 346) (587 0.15 346) (622 0.15 346) (698 0.15 346) (622 0.15 346) (698 0.15 346) (587 0.15 346) (494 0.30 457) (rest 0.6) (494 0.30 457) (523 0.30 457) (rest 0.6) (622 0.30 457) (587 0.30 457) (rest 0.6) (698 0.60 457) (rest 0.6) (698 0.30 457) (622 0.30 457) (831 0.30 457) (784 0.30 457) (698 0.30 457) (622 0.30 457) (587 0.30 457) (622 0.30 457) (698 0.30 457) (494 0.30 457) (523 0.30 457) (587 0.30 457) (494 0.30 457) (494 0.30 457) (523 0.30 457) (rest 0.3) (523 0.30 457) (698 0.15 457) (587 0.15 457) (622 0.15 457) (523 0.45 457) (494 0.30 457) (523 0.60 457) (rest 0.3) (659 0.30 268) (698 0.60 268) (rest 0.3) (698 0.30 268) (698 0.30 268) (622 0.15 268) (587 0.15 268) (622 0.30 268) (698 0.30 268) (587 0.40 268) (rest 0.4) (587 0.40 268) (rest 0.4) (523 1.60 268)).! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 12/17/97 16:52'! bachFugueVoice2On: aSound "Voice two of a fugue by J. S. Bach." ^ self noteSequenceOn: aSound from: #( (rest 4.8) (1568 0.15 346) (1480 0.15 346) (1568 0.30 346) (1047 0.30 346) (1245 0.30 346) (1568 0.15 346) (1480 0.15 346) (1568 0.30 346) (1760 0.30 346) (1175 0.30 346) (1568 0.15 346) (1480 0.15 346) (1568 0.30 346) (1760 0.30 346) (1047 0.15 346) (1175 0.15 346) (1245 0.60 346) (1175 0.15 346) (1047 0.15 346) (932 0.30 346) (1245 0.15 346) (1175 0.15 346) (1245 0.30 346) (784 0.30 346) (831 0.30 346) (1397 0.15 346) (1245 0.15 346) (1397 0.30 346) (880 0.30 346) (932 0.30 346) (1568 0.15 346) (1397 0.15 346) (1568 0.30 346) (988 0.30 346) (1047 0.30 346) (1175 0.15 346) (1245 0.15 346) (1397 0.90 346) (1245 0.15 346) (1175 0.15 346) (1047 0.15 346) (932 0.15 346) (831 0.15 346) (784 0.15 346) (698 0.30 346) (1661 0.30 346) (1568 0.30 346) (1397 0.30 346) (1245 0.30 346) (1175 0.30 346) (1245 0.30 346) (1397 0.30 346) (988 0.30 346) (1047 0.30 346) (1175 0.30 346) (988 0.30 346) (1047 0.30 457) (1568 0.15 457) (1480 0.15 457) (1568 0.30 457) (1175 0.30 457) (1245 0.60 457) (rest 0.3) (1319 0.30 457) (1397 0.30 457) (1397 0.15 457) (1319 0.15 457) (1397 0.30 457) (1047 0.30 457) (1175 0.60 457) (rest 0.3) (1175 0.30 457) (1245 0.30 457) (1245 0.15 457) (1175 0.15 457) (1245 0.30 457) (932 0.30 457) (1047 0.30 457) (1245 0.15 457) (1175 0.15 457) (1245 0.30 457) (1397 0.30 457) (932 0.30 457) (1245 0.15 457) (1175 0.15 457) (1245 0.30 457) (1397 0.30 457) (831 0.15 457) (932 0.15 457) (1047 0.60 457) (932 0.15 457) (831 0.15 457) (784 0.15 457) (622 0.15 457) (698 0.15 457) (784 0.15 457) (831 0.15 457) (932 0.15 457) (1047 0.15 457) (1175 0.15 457) (1245 0.15 457) (1175 0.15 457) (1047 0.15 457) (1175 0.15 457) (1245 0.15 457) (1397 0.15 457) (1568 0.15 457) (1760 0.15 457) (1865 0.15 457) (698 0.15 457) (784 0.15 457) (831 0.15 457) (932 0.15 457) (1047 0.15 457) (1175 0.15 457) (1319 0.15 457) (1397 0.15 457) (1245 0.15 457) (1175 0.15 457) (1245 0.15 457) (1397 0.15 457) (1568 0.15 457) (1760 0.15 457) (1976 0.15 457) (2093 0.30 457) (1976 0.15 457) (1760 0.15 457) (1568 0.15 457) (1397 0.15 457) (1245 0.15 457) (1175 0.15 457) (1047 0.30 457) (1245 0.30 457) (1175 0.30 457) (1047 0.30 457) (932 0.30 457) (880 0.30 457) (932 0.30 457) (1047 0.30 457) (740 0.30 457) (784 0.30 457) (880 0.30 457) (740 0.30 457) (784 0.30 457) (1175 0.15 457) (1047 0.15 457) (1175 0.30 457) (rest 0.6) (1319 0.15 457) (1175 0.15 457) (1319 0.30 457) (rest 0.6) (1480 0.15 457) (1319 0.15 457) (1480 0.30 457) (rest 0.6) (784 0.15 457) (698 0.15 457) (784 0.30 457) (rest 0.6) (880 0.15 457) (784 0.15 457) (880 0.30 457) (rest 0.6) (988 0.15 457) (880 0.15 457) (988 0.30 457) (rest 0.6) (1047 0.15 457) (988 0.15 457) (1047 0.30 457) (784 0.30 457) (831 0.30 457) (1047 0.15 457) (988 0.15 457) (1047 0.30 457) (1175 0.30 457) (784 0.30 457) (1047 0.15 457) (988 0.15 457) (1047 0.30 457) (1175 0.30 457) (698 0.15 457) (784 0.15 457) (831 0.60 457) (784 0.15 457) (698 0.15 457) (622 0.30 457) (1047 0.15 457) (988 0.15 457) (1047 0.30 457) (784 0.30 457) (831 0.60 457) (rest 0.3) (880 0.30 457) (932 0.30 457) (932 0.15 457) (880 0.15 457) (932 0.30 457) (698 0.30 457) (784 0.60 457) (rest 0.3) (784 0.60 457) (831 0.15 457) (932 0.15 457) (1047 0.15 457) (988 0.15 457) (1047 0.15 457) (831 0.15 457) (698 1.20 457) (698 0.30 591) (1175 0.15 591) (1047 0.15 591) (1175 0.30 591) (698 0.30 591) (622 0.30 591) (1245 0.15 591) (1175 0.15 591) (1245 0.30 591) (784 0.30 591) (698 0.30 591) (1397 0.15 591) (1245 0.15 591) (1397 0.30 591) (831 0.30 591) (784 0.15 591) (1397 0.15 591) (1245 0.15 591) (1175 0.15 591) (1047 0.15 591) (988 0.15 591) (880 0.15 591) (784 0.15 591) (1047 0.30 591) (1397 0.30 591) (1245 0.30 591) (1175 0.30 591) (rest 0.3) (831 0.30 591) (784 0.30 591) (698 0.30 591) (784 0.30 591) (698 0.15 591) (622 0.15 591) (698 0.30 591) (587 0.30 591) (831 0.30 591) (784 0.30 591) (rest 0.3) (880 0.30 591) (988 0.30 591) (1047 0.30 591) (698 0.15 591) (622 0.15 591) (587 0.15 591) (523 0.15 591) (523 0.30 591) (1047 0.15 346) (988 0.15 346) (1047 0.30 346) (784 0.30 346) (831 0.30 346) (1047 0.15 346) (988 0.15 346) (1047 0.30 346) (1175 0.30 346) (784 0.30 346) (1047 0.15 346) (988 0.15 346) (1047 0.30 346) (1175 0.30 346) (698 0.20 346) (784 0.20 346) (831 0.80 346) (784 0.20 346) (698 0.20 346) (659 1.60 346)). ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 12/17/97 16:52'! bachFugueVoice3On: aSound "Voice three of a fugue by J. S. Bach." ^ self noteSequenceOn: aSound from: #( (rest 14.4) (523 0.15 457) (494 0.15 457) (523 0.30 457) (392 0.30 457) (415 0.30 457) (523 0.15 457) (494 0.15 457) (523 0.30 457) (587 0.30 457) (392 0.30 457) (523 0.15 457) (494 0.15 457) (523 0.30 457) (587 0.30 457) (349 0.15 457) (392 0.15 457) (415 0.60 457) (392 0.15 457) (349 0.15 457) (311 0.15 457) (523 0.15 457) (494 0.15 457) (440 0.15 457) (392 0.15 457) (349 0.15 457) (311 0.15 457) (294 0.15 457) (262 0.15 457) (294 0.15 457) (311 0.15 457) (294 0.15 457) (262 0.15 457) (233 0.15 457) (208 0.15 457) (196 0.15 457) (175 0.15 457) (466 0.15 457) (415 0.15 457) (392 0.15 457) (349 0.15 457) (311 0.15 457) (294 0.15 457) (262 0.15 457) (233 0.15 457) (262 0.15 457) (294 0.15 457) (262 0.15 457) (233 0.15 457) (208 0.15 457) (196 0.15 457) (175 0.15 457) (156 0.15 457) (415 0.15 457) (392 0.15 457) (349 0.15 457) (311 0.15 457) (277 0.15 457) (262 0.15 457) (233 0.15 457) (208 0.30 457) (523 0.30 457) (466 0.30 457) (415 0.30 457) (392 0.30 457) (349 0.30 457) (392 0.30 457) (415 0.30 457) (294 0.30 457) (311 0.30 457) (349 0.30 457) (294 0.30 457) (311 0.30 457) (415 0.30 457) (392 0.30 457) (349 0.30 457) (392 0.30 457) (311 0.30 457) (294 0.30 457) (262 0.30 457) (294 0.30 457) (466 0.30 457) (415 0.30 457) (392 0.30 457) (415 0.30 457) (349 0.30 457) (311 0.30 457) (294 0.30 457) (311 0.30 457) (rest 1.2) (262 0.30 457) (233 0.30 457) (220 0.30 457) (rest 0.3) (311 0.30 457) (294 0.30 457) (262 0.30 457) (294 0.30 457) (262 0.15 457) (233 0.15 457) (262 0.30 457) (294 0.30 457) (196 0.30 591) (466 0.15 591) (440 0.15 591) (466 0.30 591) (294 0.30 591) (311 0.30 591) (523 0.15 591) (466 0.15 591) (523 0.30 591) (330 0.30 591) (349 0.30 591) (587 0.15 591) (523 0.15 591) (587 0.30 591) (370 0.30 591) (392 0.60 591) (rest 0.15) (196 0.15 591) (220 0.15 591) (247 0.15 591) (262 0.15 591) (294 0.15 591) (311 0.45 591) (220 0.15 591) (233 0.15 591) (262 0.15 591) (294 0.15 591) (311 0.15 591) (349 0.45 591) (247 0.15 591) (262 0.15 591) (294 0.15 591) (311 0.30 591) (rest 0.6) (330 0.30 591) (349 0.30 591) (175 0.30 591) (156 0.30 591) (147 0.30 591) (rest 0.3) (208 0.30 591) (196 0.30 591) (175 0.30 591) (196 0.30 591) (175 0.15 591) (156 0.15 591) (175 0.30 591) (196 0.30 591) (262 0.15 591) (294 0.15 591) (311 0.15 591) (294 0.15 591) (262 0.15 591) (233 0.15 591) (208 0.15 591) (196 0.15 591) (175 0.15 591) (466 0.15 591) (415 0.15 591) (392 0.15 591) (349 0.15 591) (311 0.15 591) (294 0.15 591) (262 0.15 591) (233 0.15 591) (262 0.15 591) (294 0.15 591) (262 0.15 591) (233 0.15 591) (208 0.15 591) (196 0.15 591) (175 0.15 591) (156 0.15 591) (415 0.15 591) (392 0.15 591) (349 0.15 591) (311 0.15 591) (294 0.15 591) (262 0.15 591) (233 0.15 591) (208 0.15 591) (233 0.15 591) (262 0.15 591) (233 0.15 591) (208 0.15 591) (196 0.15 591) (175 0.15 591) (156 0.15 591) (147 0.15 591) (392 0.15 591) (349 0.15 591) (311 0.15 591) (294 0.15 591) (262 0.15 591) (247 0.15 591) (220 0.15 591) (196 0.60 772) (196 0.60 772) (rest 0.15) (196 0.15 772) (220 0.15 772) (247 0.15 772) (262 0.15 772) (294 0.15 772) (311 0.15 772) (349 0.15 772) (392 0.15 772) (349 0.15 772) (415 0.15 772) (392 0.15 772) (349 0.15 772) (311 0.15 772) (294 0.15 772) (262 0.15 772) (247 0.30 772) (262 0.15 772) (494 0.15 772) (262 0.30 772) (196 0.30 772) (208 0.30 772) (262 0.15 772) (247 0.15 772) (262 0.30 772) (294 0.30 772) (196 0.30 772) (262 0.15 772) (247 0.15 772) (262 0.30 772) (294 0.30 772) (175 0.15 772) (196 0.15 772) (208 0.60 772) (196 0.15 772) (175 0.15 772) (156 0.60 772) (rest 0.3) (311 0.30 772) (294 0.30 772) (262 0.30 772) (392 0.30 772) (196 0.30 772) (262 3.60 268) (494 0.40 268) (rest 0.4) (494 0.40 268) (rest 0.4) (392 1.60 268)). ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 12/17/97 16:52'! bachFugueVoice4On: aSound "Voice four of a fugue by J. S. Bach." ^ self noteSequenceOn: aSound from: #( (rest 61.2) (131 0.15 500) (123 0.15 500) (131 0.30 500) (98 0.30 500) (104 0.30 500) (131 0.15 500) (123 0.15 500) (131 0.30 500) (147 0.30 500) (98 0.30 500) (131 0.15 500) (123 0.15 500) (131 0.30 500) (147 0.30 500) (87 0.15 500) (98 0.15 500) (104 0.60 500) (98 0.15 500) (87 0.15 500) (78 0.60 500) (rest 0.3) (156 0.30 500) (147 0.30 500) (131 0.30 500) (196 0.30 500) (98 0.30 500) (131 3.60 268) (131 3.20 205)). ! ! !AbstractSound class methodsFor: 'examples-bach fugue' stamp: 'jm 1/5/98 17:45'! stereoBachFugue "Play fugue by J. S. Bach in stereo using different timbres." "AbstractSound stereoBachFugue play" "(AbstractSound bachFugueVoice1On: FMSound flute1) play" "(AbstractSound bachFugueVoice1On: PluckedSound default) play" ^ MixedSound new add: (self bachFugueVoice1On: FMSound oboe1) pan: 0.2; add: (self bachFugueVoice2On: FMSound organ1) pan: 0.8; add: (self bachFugueVoice3On: PluckedSound default) pan: 0.4; add: (self bachFugueVoice4On: FMSound brass1) pan: 0.6. ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 8/14/1998 13:27'! initSounds "AbstractSound initSounds" Sounds _ Dictionary new. (FMSound class organization listAtCategoryNamed: #instruments) do: [:sel | Sounds at: sel asString put: (FMSound perform: sel)]. ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 8/14/1998 13:25'! soundNamed: soundName ^ Sounds at: soundName ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 3/4/98 10:29'! soundNamed: soundName ifAbsent: aBlock ^ Sounds at: soundName ifAbsent: aBlock ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'di 11/7/2000 12:12'! soundNamed: soundName put: aSound Sounds at: soundName put: aSound. AbstractSound updateScorePlayers. ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 8/19/1998 14:11'! soundNames ^ Sounds keys asSortedCollection asArray ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 8/4/1998 18:26'! sounds ^ Sounds ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'jm 1/14/1999 13:00'! updateFMSounds "AbstractSound updateFMSounds" Sounds keys do: [:k | ((Sounds at: k) isKindOf: FMSound) ifTrue: [ Sounds removeKey: k ifAbsent: []]]. (FMSound class organization listAtCategoryNamed: #instruments) do: [:sel | Sounds at: sel asString put: (FMSound perform: sel)]. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'sge 2/13/2000 05:20'! fileInSoundLibrary "Prompt the user for a file name and the file in the sound library with that name." "AbstractSound fileInSoundLibrary" | fileName | fileName _ FillInTheBlank request: 'Sound library file name?'. fileName isEmptyOrNil ifTrue: [^ self]. (fileName endsWith: '.sounds') ifFalse: [fileName _ fileName, '.sounds']. self fileInSoundLibraryNamed: fileName. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'di 11/7/2000 12:50'! fileInSoundLibraryNamed: fileName "File in the sound library with the given file name, and add its contents to the current sound library." | s newSounds | s _ FileStream oldFileNamed: fileName. newSounds _ s fileInObjectAndCode. s close. newSounds associationsDo: [:assoc | self storeFiledInSound: assoc value named: assoc key]. AbstractSound updateScorePlayers. Smalltalk garbageCollect. "Large objects may have been released" ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'jm 8/19/1998 12:42'! fileOutSoundLibrary "File out the current sound library." "AbstractSound fileOutSoundLibrary" self fileOutSoundLibrary: Sounds. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'sge 2/13/2000 05:22'! fileOutSoundLibrary: aDictionary "File out the given dictionary, which is assumed to contain sound and instrument objects keyed by their names." "Note: This method is separated out so that one can file out edited sound libraries, as well as the system sound library. To make such a collection, you can inspect AbstractSound sounds and remove the items you don't want. Then do: 'AbstractSound fileOutSoundLibrary: self' from the Dictionary inspector." | fileName refStream | (aDictionary isKindOf: Dictionary) ifFalse: [self error: 'arg should be a dictionary of sounds']. fileName _ FillInTheBlank request: 'Sound library file name?'. fileName isEmptyOrNil ifTrue: [^ self]. refStream _ SmartRefStream fileNamed: fileName, '.sounds'. refStream nextPut: aDictionary. refStream close. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'jm 9/12/1998 21:35'! storeFiledInSound: snd named: sndName "Store the given sound in the sound library. Use the given name if it isn't in use, otherwise ask the user what to do." | menu choice i | (Sounds includesKey: sndName) ifFalse: [ "no name clash" Sounds at: sndName put: snd. ^ self]. (Sounds at: sndName) == UnloadedSnd ifTrue: [ "re-loading a sound that was unloaded to save space" Sounds at: sndName put: snd. ^ self]. "the given sound name is already used" menu _ SelectionMenu selections: #('replace the existing sound' 'rename the new sound' 'skip it'). choice _ menu startUpWithCaption: '"', sndName, '" has the same name as an existing sound'. (choice beginsWith: 'replace') ifTrue: [ Sounds at: sndName put: snd. ^ self]. (choice beginsWith: 'rename') ifTrue: [ i _ 2. [Sounds includesKey: (sndName, ' v', i printString)] whileTrue: [i _ i + 1]. Sounds at: (sndName, ' v', i printString) put: snd]. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'jm 9/12/1998 22:18'! unloadSampledTimbres "This can be done to unload those bulky sampled timbres to shrink the image. The unloaded sounds are replaced by a well-known 'unloaded sound' object to enable the unloaded sounds to be detected when the process is reversed." "AbstractSound unloadSampledTimbres" Sounds keys copy do: [:soundName | (((Sounds at: soundName) isKindOf: SampledInstrument) or: [(Sounds at: soundName) isKindOf: LoopedSampledSound]) ifTrue: [ Sounds at: soundName put: self unloadedSound]]. self updateScorePlayers. Smalltalk garbageCollect. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'jm 9/11/1998 16:47'! unloadSoundNamed: soundName (Sounds includesKey: soundName) ifTrue: [ Sounds at: soundName put: self unloadedSound]. self updateScorePlayers. ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'jm 9/12/1998 21:48'! unloadedSound "Answer a sound to be used as the place-holder for sounds that have been unloaded." UnloadedSnd ifNil: [UnloadedSnd _ UnloadedSound default copy]. ^ UnloadedSnd ! ! !AbstractSound class methodsFor: 'sound library-file in/out' stamp: 'di 11/7/2000 13:00'! updateScorePlayers | soundsBeingEdited | "Force all ScorePlayers to update their instrument list from the sound library. This may done after loading, unloading, or replacing a sound to make all ScorePlayers feel the change." ScorePlayer allSubInstancesDo: [:p | p pause]. SoundPlayer shutDown. soundsBeingEdited _ EnvelopeEditorMorph allSubInstances collect: [:ed | ed soundBeingEdited]. ScorePlayerMorph allSubInstancesDo: [:p | p updateInstrumentsFromLibraryExcept: soundsBeingEdited]. ! ! !AbstractSound class methodsFor: 'primitive generation' stamp: 'ar 2/3/2001 15:30'! translatedPrimitives ^#( (FMSound mixSampleCount:into:startingAt:leftVol:rightVol:) (PluckedSound mixSampleCount:into:startingAt:leftVol:rightVol:) (LoopedSampledSound mixSampleCount:into:startingAt:leftVol:rightVol:) (SampledSound mixSampleCount:into:startingAt:leftVol:rightVol:) (ReverbSound applyReverbTo:startingAt:count:) ). ! ! PluggableTextMorph subclass: #AcceptableCleanTextMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !AcceptableCleanTextMorph methodsFor: 'as yet unclassified' stamp: 'di 6/22/1998 21:38'! accept "Overridden to allow accept of clean text" | textToAccept ok | textToAccept _ textMorph asText. ok _ (setTextSelector == nil) or: [setTextSelector numArgs = 2 ifTrue: [model perform: setTextSelector with: textToAccept with: self] ifFalse: [model perform: setTextSelector with: textToAccept]]. ok ifTrue: [self setText: self getText. self hasUnacceptedEdits: false]! ! FileDirectory subclass: #AcornFileDirectory instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Files'! !AcornFileDirectory methodsFor: 'file name utilities' stamp: 'ar 12/18/1999 00:47'! fullPathFor: path path isEmpty ifTrue:[^pathName]. ((path includes: $$ ) or:[path includes: $:]) ifTrue:[^path]. ^pathName, self slash, path! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AcornFileDirectory class instanceVariableNames: ''! !AcornFileDirectory class methodsFor: 'platform specific' stamp: 'sma 6/25/2000 09:25'! isActiveDirectoryClass "Does this class claim to be that properly active subclass of FileDirectory for the current platform? On Acorn, the test is whether platformName is 'RiscOS' (on newer VMs) or if the primPathNameDelimiter is $. (on older ones), which is what we would like to use for a dirsep if only it would work out. See pathNameDelimiter for more woeful details - then just get on and enjoy Squeak" ^ Smalltalk platformName = 'RiscOS' or: [self primPathNameDelimiter = $.]! ! !AcornFileDirectory class methodsFor: 'platform specific' stamp: 'TPR 7/20/1999 17:52'! maxFileNameLength ^ 255 ! ! !AcornFileDirectory class methodsFor: 'platform specific' stamp: 'TPR 5/10/1998 21:45'! pathNameDelimiter "Acorn RiscOS uses a dot as the directory separator and has no real concept of filename extensions. We tried to make code handle this, but there are just too many uses of dot as a filename extension - so fake it out by pretending to use a slash. The file prims do conversions instead. Sad, but pragmatic" ^ $/ ! ! Object subclass: #Action instanceVariableNames: 'actionTask paused affectedObject lifetime stopCondition myScheduler ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !Action commentStamp: '' prior: 0! This class implements Actions for Wonderlands. An Action is some task that should be executed every frame either forever, until a specified amount of time has elapsed, or until a specified condition holds true. ! !Action methodsFor: 'accessing' stamp: 'jsp 3/9/1999 16:08'! getAffectedObject "Returns the object affected by the action" ^ affectedObject. ! ! !Action methodsFor: 'accessing' stamp: 'jsp 2/1/1999 15:13'! isDone "Returns true if the Action is done executing either because it's lifetime has expired or because the specified condition is true" (lifetime > 0) ifTrue: [^ (lifetime < (myScheduler getTime))] ifFalse: [^ (stopCondition value)]. ! ! !Action methodsFor: 'accessing' stamp: 'jsp 3/9/1999 16:22'! isPaused "Returns true if the action is paused" ^ paused. ! ! !Action methodsFor: 'accessing' stamp: 'jsp 3/9/1999 16:21'! pause "Pause the action" paused _ true. ! ! !Action methodsFor: 'accessing' stamp: 'jsp 3/9/1999 16:21'! resume "resume the action" paused _ false. ! ! !Action methodsFor: 'management' stamp: 'jsp 3/9/1999 16:20'! execute "Execute the Action's task" paused ifFalse: [ actionTask value ]. ! ! !Action methodsFor: 'management' stamp: 'jsp 3/9/1999 16:08'! setAffectedObject: anObject "Sets the object affected by the action" affectedObject _ anObject. ! ! !Action methodsFor: 'management' stamp: 'jsp 2/1/1999 11:44'! setLifetime: howlong andCondition: condition "Sets how long the action should run, or the condition under which it should stop" lifetime _ howlong. stopCondition _ condition. ! ! !Action methodsFor: 'management' stamp: 'jsp 2/1/1999 16:20'! setScheduler: scheduler "Sets the scheduler the Action is active in" myScheduler _ scheduler. ! ! !Action methodsFor: 'management' stamp: 'jsp 3/9/1999 16:20'! setTask: task "Sets the task the Action should perform each frame" actionTask _ task. paused _ false. ! ! !Action methodsFor: 'management' stamp: 'jsp 3/30/1999 11:50'! stop "This method removes the Action from myScheduler's list of active actions" stopCondition _ [ true ]. myScheduler removeAction: self. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Action class instanceVariableNames: ''! !Action class methodsFor: 'initialize-release' stamp: 'jsp 3/9/1999 16:09'! do: task eachframefor: time toObject: anObject inScheduler: scheduler "Creates a new Action that performs the specified task each frame for (time) seconds" | newAction | newAction _ Action new. newAction setTask: task. newAction setLifetime: (time + (scheduler getTime)) andCondition: [false]. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addAction: newAction. ^ newAction. ! ! !Action class methodsFor: 'initialize-release' stamp: 'jsp 3/9/1999 16:09'! do: task eachframeuntil: condition toObject: anObject inScheduler: scheduler "Creates a new Action that performs the specified task each frame until the specified condition holds true" | newAction | newAction _ Action new. newAction setTask: task. newAction setLifetime: -1 andCondition: condition. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addAction: newAction. ^ newAction. ! ! !Action class methodsFor: 'initialize-release' stamp: 'jsp 3/9/1999 16:10'! do: task toObject: anObject inScheduler: scheduler "Creates a new Action that executes the specified task each frame" | newAction | newAction _ Action new. newAction setTask: task. newAction setLifetime: -1 andCondition: [false]. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addAction: newAction. ^ newAction. ! ! SwikiAction subclass: #ActiveSwikiAction instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !ActiveSwikiAction methodsFor: 'as yet unclassified' stamp: 'mjg 9/1/1998 12:44'! browse: pageRef from: request "Just reply with a page in HTML format" | formattedPage liveText| liveText _ HTMLformatter evalEmbedded: (pageRef text) with: request unlessContains: (self dangerSet). formattedPage _ pageRef copy. "Make a copy, then format the text." formattedPage formatted: (formatter swikify: liveText linkhandler: [:link | urlmap linkFor: link from: request peerName storingTo: OrderedCollection new page: formattedPage]). request reply: ((self formatterFor: 'page') format: formattedPage). ! ! !ActiveSwikiAction methodsFor: 'as yet unclassified' stamp: 'tk 2/4/98 12:52'! dangerSet ^#('Smalltalk' 'view' 'open' 'perform:' 'FileStream' 'FileDirectory' 'fileIn' 'Compiler' 'halt' 'PWS' 'Swiki') ! ! !ActiveSwikiAction methodsFor: 'as yet unclassified' stamp: 'mjg 9/10/1998 15:33'! inputFrom: request "Take user's input and respond with a searchresult or store the edit" | coreRef page theText | coreRef _ request message size < 2 ifTrue: ['1'] ifFalse: [request message at: 2]. coreRef = 'searchresult' ifTrue: [ "If contains search string, do search" request reply: PWS crlf, (HTMLformatter evalEmbedded: (self fileContents: source, 'results.html') with: (urlmap searchFor: (request fields at: 'searchFor' ifAbsent: ['nothing']))). ^ #return]. (theText _ request fields at: 'text' ifAbsent: [nil]) ifNotNil: [ "It's a response from an edit, so store the page" page _ urlmap atID: coreRef. page user: request peerName. "Address is machine, user only if logged in" page pageStatus = #new ifTrue: [page pageStatus: #standard]. page _ urlmap storeID: coreRef text: theText withSqueakLineEndings from: request peerName. ^ self]. "return self means do serve the edited page afterwards" request fields keys do: [:aTag | (aTag beginsWith: 'text-') ifTrue: [ urlmap storeID: coreRef text: (request fields at: aTag) withSqueakLineEndings insertAt: (aTag copyFrom: 6 to: aTag size). "string" ^ self]]. "oops, a new kind!! -- but don't complain!! Could be for ActivePage!!" " Transcript show: 'Unknown data from client. '; show: request fields printString; cr."! ! Object subclass: #ActorState instanceVariableNames: 'owningPlayer penDown penSize penColor fractionalPosition instantiatedUserScriptsDictionary ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Support'! !ActorState commentStamp: '' prior: 0! Holds a record of data representing actor-like slots in the Morph, on behalf of an associated Player. Presently also holds onto the scriptInstantion objects that represent active scripts in an instance, but this will probably change soon.! !ActorState methodsFor: 'initialization' stamp: 'sw 4/30/1998 22:32'! copyWithPlayerReferenceNilled "Answer a copy of the receiver in which all the items referring to the corresponding Player object are nilled out, for the purpose of being set up with fresh values, after the copy, by the caller" | holdPlayer holdScriptDict copy copyScriptDict | holdPlayer _ owningPlayer. owningPlayer _ nil. holdScriptDict _ self instantiatedUserScriptsDictionary. instantiatedUserScriptsDictionary _ nil. copy _ self deepCopy. owningPlayer _ holdPlayer. instantiatedUserScriptsDictionary _ holdScriptDict. holdScriptDict ifNotNil: [copyScriptDict _ IdentityDictionary new. holdScriptDict associationsDo: [:assoc | copyScriptDict add: (assoc key -> (assoc value copyWithPlayerObliterated))]. copy instantiatedUserScriptsDictionary: copyScriptDict]. ^ copy ! ! !ActorState methodsFor: 'initialization' stamp: 'sw 5/13/1998 16:37'! initializeFor: aPlayer | aNewDictionary | owningPlayer _ aPlayer. instantiatedUserScriptsDictionary ifNil: [^ self]. aNewDictionary _ IdentityDictionary new. instantiatedUserScriptsDictionary associationsDo: [:assoc | aNewDictionary at: assoc key put: (assoc value shallowCopy player: aPlayer)]. instantiatedUserScriptsDictionary _ aNewDictionary.! ! !ActorState methodsFor: 'pen' stamp: 'ar 10/5/2000 18:50'! choosePenColor: evt owningPlayer costume changeColorTarget: owningPlayer costume selector: #penColor: originalColor: owningPlayer penColor hand: evt hand.! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:44'! choosePenSize | menu sz | menu _ CustomMenu new. 1 to: 10 do: [:w | menu add: w printString action: w]. sz _ menu startUp. sz ifNotNil: [penSize _ sz]! ! !ActorState methodsFor: 'pen' stamp: 'sw 2/4/98 15:16'! defaultPenColor ^ Color blue! ! !ActorState methodsFor: 'pen' stamp: 'sw 2/4/98 15:03'! defaultPenSize ^ 1! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:35'! getPenColor penColor ifNil: [penColor _ self defaultPenColor]. ^ penColor! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:40'! getPenDown ^ penDown == true! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:43'! getPenSize penSize ifNil: [penSize _ self defaultPenSize]. ^ penSize! ! !ActorState methodsFor: 'pen' stamp: 'sw 2/4/98 18:07'! liftPen penDown _ false! ! !ActorState methodsFor: 'pen' stamp: 'sw 2/4/98 14:58'! lowerPen penDown _ true! ! !ActorState methodsFor: 'pen' stamp: 'sw 2/4/98 18:03'! penColor: aColor penColor _ aColor! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:51'! setPenColor: aColor penColor _ aColor ! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:47'! setPenDown: aBoolean penDown _ aBoolean! ! !ActorState methodsFor: 'pen' stamp: 'sw 4/22/1998 13:45'! setPenSize: aNumber penSize _ aNumber! ! !ActorState methodsFor: 'position' stamp: 'jm 4/24/1998 21:34'! fractionalPosition "Return my player's costume's position including the fractional part. This allows the precise position to be retained to avoid cummulative rounding errors, while letting Morphic do all its calculations with integer pixel coordinates. See the implementation of forward:." ^ fractionalPosition ! ! !ActorState methodsFor: 'position' stamp: 'jm 4/24/1998 21:31'! fractionalPosition: aPoint fractionalPosition _ aPoint asFloatPoint. ! ! !ActorState methodsFor: 'script instantiations' stamp: 'sw 4/9/98 22:35'! instantiatedUserScriptsDictionary instantiatedUserScriptsDictionary ifNil: [instantiatedUserScriptsDictionary _ IdentityDictionary new]. ^ instantiatedUserScriptsDictionary! ! !ActorState methodsFor: 'script instantiations' stamp: 'sw 4/30/1998 21:51'! instantiatedUserScriptsDictionary: aDict "Used for copying code only" instantiatedUserScriptsDictionary _ aDict! ! !ActorState methodsFor: 'other' stamp: 'sw 4/22/1998 17:02'! addPlayerMenuItemsTo: aMenu hand: aHandMorph self getPenDown ifTrue: [aMenu add: 'pen up' action: #liftPen] ifFalse: [aMenu add: 'pen down' action: #lowerPen]. aMenu add: 'pen size' action: #choosePenSize. aMenu add: 'pen color' action: #choosePenColor:.! ! !ActorState methodsFor: 'other' stamp: 'sw 4/13/1998 19:36'! costume ^ owningPlayer costume! ! !ActorState methodsFor: 'other' stamp: 'sw 5/12/1998 23:35'! printOn: aStream aStream nextPutAll: 'ActorState for ', owningPlayer externalName, ' '. penDown ifNotNil: [aStream cr; nextPutAll: 'penDown ', penDown printString]. penColor ifNotNil: [aStream cr; nextPutAll: 'penColor ', penColor printString]. penSize ifNotNil: [aStream cr; nextPutAll: 'penSize ', penSize printString]. instantiatedUserScriptsDictionary ifNotNil: [aStream cr; nextPutAll: '+ ', instantiatedUserScriptsDictionary size printString, ' user scripts']. ! ! !ActorState methodsFor: 'other' stamp: 'MPW 1/1/1901 21:53'! printOnStream: aStream aStream print: 'ActorState for '; print:owningPlayer externalName; print:' '. penDown ifNotNil: [aStream cr; print: 'penDown '; write:penDown]. penColor ifNotNil: [aStream cr; print: 'penColor '; write:penColor]. penSize ifNotNil: [aStream cr; print: 'penSize '; write:penSize]. instantiatedUserScriptsDictionary ifNotNil: [aStream cr; print: '+ '; write: instantiatedUserScriptsDictionary size; print:' user scripts']. ! ! B3DSceneMorph subclass: #AdvancedB3DSceneMorph instanceVariableNames: 'rotationAngle stepTime isRotating oldPoint headLightStatus savedHeadLight ' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Demo Morphs'! !AdvancedB3DSceneMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 16:36'! rotationAngle ^rotationAngle! ! !AdvancedB3DSceneMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 16:36'! rotationAngle: aNumber rotationAngle := aNumber! ! !AdvancedB3DSceneMorph methodsFor: 'accessing'! scene: aScene super scene: (self updateSceneWithDefaults: aScene). self updateUpVectorForCamera: self scene defaultCamera. self updateHeadlight. self changed! ! !AdvancedB3DSceneMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 16:35'! stepTime ^stepTime! ! !AdvancedB3DSceneMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 16:35'! stepTime: aNumber stepTime := aNumber! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions'! addDolly: delta | camera new | camera := scene defaultCamera. new := camera position - (camera direction * delta). camera target = new ifFalse: [ camera position: new]. "new := camera direction * delta. camera position: camera position - new. camera target: camera target - new." self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 3/27/2000 16:55'! addFovAngle: delta | camera new | camera := scene defaultCamera. new := camera fov + delta. 0 < new ifTrue: [ camera fov: new]. self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 3/27/2000 16:54'! panBy: aPoint | camera pt | pt := B3DVector3 x: aPoint x y: aPoint y negated z: 0.0. camera := scene defaultCamera. pt := pt * (camera direction length) / 200. pt := camera asMatrix4x4 inverseTransformation localPointToGlobal: pt. pt := pt - camera position. camera position: camera position + pt. camera target: camera target + pt. self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 6/21/2000 10:38'! rotateFrom: anOldPoint to: aCurrentPoint | camera matrix anOldPointOnSphere aCurrentPointOnSphere center radius | center := self bounds center. radius := self bounds extent r / 2. anOldPointOnSphere := self pointOnSphereCentered: center radius: radius atPoint: anOldPoint. aCurrentPointOnSphere := self pointOnSphereCentered: center radius: radius atPoint: aCurrentPoint. camera := scene defaultCamera. matrix := B3DMatrix4x4 rotatedBy: ((anOldPointOnSphere dot: aCurrentPointOnSphere) min: 1.0) arcCos radiansToDegrees around: (camera asMatrix4x4 inverseTransformation localPointToGlobal: (anOldPointOnSphere cross: aCurrentPointOnSphere)) - camera position centeredAt: camera target. camera position: (matrix localPointToGlobal: camera position). camera up: (matrix localPointToGlobal: camera up). self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 3/27/2000 16:55'! rotateX: angle | camera matrix | camera := scene defaultCamera. matrix := B3DMatrix4x4 rotatedBy: angle around: ((camera position - camera target) cross: camera up) centeredAt: camera target. camera position: (matrix localPointToGlobal: camera position). camera up: (matrix localPointToGlobal: camera up). self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 3/27/2000 16:55'! rotateY: angle | camera matrix | camera := scene defaultCamera. matrix := B3DMatrix4x4 rotatedBy: angle around: camera up centeredAt: camera target. camera position: (matrix localPointToGlobal: camera position). self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 3/27/2000 16:54'! rotateZ: angle | camera matrix | camera := scene defaultCamera. matrix := B3DMatrix4x4 rotatedBy: angle around: (camera position - camera target) centeredAt: camera target. camera up: (matrix localPointToGlobal: camera up). self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions' stamp: 'ti 6/20/2000 15:47'! transformWithMatrix: matrix | camera | camera := scene defaultCamera. camera position: (matrix localPointToGlobal: camera position). camera up: (matrix localPointToGlobal: camera up). self updateHeadlight. self changed.! ! !AdvancedB3DSceneMorph methodsFor: 'camera actions'! updateHeadlight | headLight camera | camera := scene defaultCamera. (self scene lights isKindOf: Dictionary) ifTrue: [headLight := self scene lights at: '$HeadLight$' ifAbsent: []] ifFalse: [headLight := nil]. headLight ifNil: [ ((headLightStatus = #on) and: [self scene lights isKindOf: Dictionary]) ifTrue: [ self scene lights at: '$HeadLight$' put: savedHeadLight. headLight := savedHeadLight]] ifNotNil: [ (headLightStatus = #off) ifTrue: [ savedHeadLight := headLight. self scene lights removeKey: '$HeadLight$']]. headLight ifNotNil: [ headLight position: camera position; target: camera target]. ! ! !AdvancedB3DSceneMorph methodsFor: 'drawing' stamp: 'ti 3/24/2000 17:12'! renderOn: aRenderer aRenderer getVertexBuffer flags: (aRenderer getVertexBuffer flags bitOr: VBTwoSidedLighting). super renderOn: aRenderer! ! !AdvancedB3DSceneMorph methodsFor: 'event handling' stamp: 'ti 3/24/2000 17:01'! handlesMouseDown: evt evt yellowButtonPressed ifTrue: [^false] ifFalse: [^true] ! ! !AdvancedB3DSceneMorph methodsFor: 'event handling' stamp: 'ti 6/20/2000 18:18'! mouseDown: evt oldPoint := evt cursorPoint. super mouseDown: evt.! ! !AdvancedB3DSceneMorph methodsFor: 'event handling' stamp: 'ti 6/21/2000 09:00'! mouseMove: evt oldPoint ifNil: [^super mouseMove: evt]. (evt redButtonPressed) ifTrue: [ (evt shiftPressed) ifTrue: [self panBy: oldPoint - evt cursorPoint] ifFalse: [ (oldPoint = evt cursorPoint) ifFalse: [ (self rotateFrom: oldPoint to: evt cursorPoint)]]. oldPoint := evt cursorPoint].! ! !AdvancedB3DSceneMorph methodsFor: 'initialization' stamp: 'ti 3/27/2000 10:51'! createDefaultScene | camera headLight | super createDefaultScene. camera _ B3DCamera new. camera position: 0@0@-6. camera target: 0@0@0. camera fov: 15.0. scene defaultCamera: camera. headLight := B3DSpotLight new. headLight position: 0@-1@0. headLight target: 0@0@0. headLight lightColor: (B3DMaterialColor color: (Color blue)). headLight attenuation: (B3DLightAttenuation constant: 1.0 linear: 0.0 squared: 0.0). headLight minAngle: 5. headLight maxAngle: 6. scene lights add: headLight. scene objects do: [ :object | object material: nil]! ! !AdvancedB3DSceneMorph methodsFor: 'initialization' stamp: 'ti 5/10/2000 11:21'! initialize super initialize. self stepTime: 0. self rotationAngle: 1. self beRotating. self switchHeadLightOn.! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 3/24/2000 16:37'! beRotating isRotating := true.! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 3/24/2000 16:37'! beStill isRotating := false.! ! !AdvancedB3DSceneMorph methodsFor: 'properties'! headLightIsOn ^(headLightStatus = #on)! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 3/24/2000 16:37'! isRotating ^isRotating! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 5/10/2000 11:20'! switchHeadLightOff headLightStatus := #off. self updateHeadlight. self changed! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 5/10/2000 11:20'! switchHeadLightOn headLightStatus := #on. self updateHeadlight. self changed! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 5/10/2000 11:21'! switchHeadLightStatus (headLightStatus = #on) ifTrue: [self switchHeadLightOff] ifFalse: [self switchHeadLightOn]! ! !AdvancedB3DSceneMorph methodsFor: 'properties' stamp: 'ti 3/24/2000 16:37'! switchRotationStatus self isRotating ifTrue: [self beStill] ifFalse: [self beRotating]! ! !AdvancedB3DSceneMorph methodsFor: 'stepping' stamp: 'ti 3/27/2000 16:55'! step self isRotating ifTrue: [ scene defaultCamera rotateBy: self rotationAngle. self updateHeadlight. self changed.].! ! !AdvancedB3DSceneMorph methodsFor: 'private' stamp: 'ti 6/21/2000 10:39'! pointOnSphereCentered: center radius: radius atPoint: aPoint | x y z r s | x := (aPoint x - center x) / radius. y := (aPoint y - center y) / radius. r := (x * x) + (y * y). (r > 1.0) ifTrue: [ s := 1.0 / (r sqrt). x := s * x negated. y := s * y. z := 0.0] ifFalse: [ z := (1.0 - r) sqrt]. ^B3DVector3 x: x y: y negated z: z! ! !AdvancedB3DSceneMorph methodsFor: 'private'! updateSceneWithDefaults: myScene | headLight mat | myScene lights at: 'Ambient1' put: (B3DAmbientLight color: (Color gray: 0.2)). headLight := B3DSpotLight new. headLight position: myScene defaultCamera position. headLight target: myScene defaultCamera target. headLight lightColor: (B3DMaterialColor color: (Color gray: 0.7)). headLight attenuation: (B3DLightAttenuation constant: 1.0 linear: 0.0 squared: 0.0). headLight minAngle: 80. headLight maxAngle: 90. myScene lights at: '$HeadLight$' put: headLight copy. mat := B3DMaterial new. mat diffusePart: (Color gray: 0.25). mat ambientPart: (Color gray: 0.01). myScene objects do: [:o| o material: mat]. ^myScene! ! !AdvancedB3DSceneMorph methodsFor: 'private'! updateUpVectorForCamera: aCamera | oldUp | oldUp := aCamera up. aCamera up: ((aCamera direction cross: oldUp) cross: (aCamera direction))! ! ScrollPane subclass: #AlansTextPlusMorph instanceVariableNames: 'theTextMorph thePasteUp ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !AlansTextPlusMorph commentStamp: '' prior: 0! The code is here, but the class you really want to use is GeeMailMorph (nicer name).! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 13:58'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'make a book of me' action: #convertToBook. ! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/30/2000 15:06'! adjustPasteUpSize | newBottom | thePasteUp ifNil: [^self]. newBottom _ thePasteUp bottom max: thePasteUp boundingBoxOfSubmorphs bottom + 20. thePasteUp height: (newBottom - thePasteUp top max: self height). thePasteUp width: (thePasteUp width max: scroller innerBounds width - 5).! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 12:03'! convertToBook GeeBookMorph new geeMail: thePasteUp; rebuildPages; openInWorld! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 11:55'! drawOn: aCanvas super drawOn: aCanvas.! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 15:33'! handlesMouseDown: evt ^false! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 12:02'! initialize super initialize. color _ Color white. thePasteUp _ TextPlusPasteUpMorph new borderWidth: 0; color: color. scroller addMorph: thePasteUp. theTextMorph _ TextPlusMorph new position: 4@4; scrollerOwner: self. thePasteUp theTextMorph: theTextMorph. self position: 100@100. self extent: Display extent // 3. self useRoundedCorners. ! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 11:32'! layoutChanged self setScrollDeltas. super layoutChanged. self adjustPasteUpSize. ! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 11:58'! leftoverScrollRange "Return the entire scrolling range minus the currently viewed area." ^ self totalScrollRange - bounds height max: 0 ! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/6/2000 16:36'! mouseUp: evt inMorph: aMorph evt hand grabMorph: aMorph "old instances may have a handler we no longer use"! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:25'! printPSToFile thePasteUp printer doPages! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/13/2000 15:11'! scrollSelectionIntoView: event alignTop: alignTop "Scroll my text into view if necessary and return true, else return false" | selRects delta selRect rectToTest transform cpHere | selRects _ theTextMorph 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" alignTop ifTrue: [ self scrollBy: 0@(bounds top - selRect top). ^ true ]. selRect bottom > bounds bottom ifTrue: [ self scrollBy: 0@(bounds bottom - selRect bottom - 30). ^ true ]. (delta _ selRect amountToTranslateWithin: self bounds) y ~= 0 ifTrue: [ "Scroll end of selection into view if necessary" self scrollBy: 0@delta y. ^ true]. ^ false! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/7/2000 11:42'! wantsDroppedMorph: aMorph event: evt "Return true if the receiver wishes to accept the given morph, which is being dropped by a hand in response to the given event. The default implementation returns false. NOTE: the event is assumed to be in global (world) coordinates." ^false! ! !AlansTextPlusMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/6/2000 16:25'! wantsSlot ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AlansTextPlusMorph class instanceVariableNames: ''! !AlansTextPlusMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 9/10/2000 12:52'! includeInNewMorphMenu ^ false "to encourage the use of GeeMail instead"! ! Object subclass: #Alarm instanceVariableNames: 'alarmTask alarmTime myScheduler ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !Alarm commentStamp: '' prior: 0! This class implements the alarms for Wonderlands. The user specifies the time the alarm should go off (either in a certain amount of time or at a specific moment) and the task the system should execute when the alarm goes off. ! !Alarm methodsFor: 'accessing' stamp: 'jsp 2/1/1999 14:50'! checkTime "Returns the time the alarm is set to go off at" ^ alarmTime. ! ! !Alarm methodsFor: 'management' stamp: 'jsp 1/29/1999 14:49'! execute "Execute the appointed task because it's the appointed hour" alarmTask value. ! ! !Alarm methodsFor: 'management' stamp: 'jsp 2/1/1999 12:12'! setScheduler: scheduler "Set the Scheduler that manages this Alarm" myScheduler _ scheduler. ! ! !Alarm methodsFor: 'management' stamp: 'jsp 2/1/1999 10:58'! setTask: task "Specifies the task the alarm executes when it goes off" alarmTask _ task. ! ! !Alarm methodsFor: 'management' stamp: 'jsp 2/1/1999 10:59'! setTime: time "Specifies the time the alarm goes off" alarmTime _ time. ! ! !Alarm methodsFor: 'management' stamp: 'jsp 2/1/1999 16:33'! stop "This method removes the Alarm from myScheduler's list of active Alarms" myScheduler removeAlarm: self. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Alarm class instanceVariableNames: ''! !Alarm class methodsFor: 'intialize-release' stamp: 'jsp 2/8/1999 16:07'! do: task at: executeTime inScheduler: scheduler "Creates an alarm that does the specified task at the specified time" | newAlarm | newAlarm _ Alarm new. newAlarm setTime: executeTime. newAlarm setTask: task. newAlarm setScheduler: scheduler. scheduler addAlarm: newAlarm. ^ newAlarm.! ! !Alarm class methodsFor: 'intialize-release' stamp: 'jsp 2/8/1999 16:06'! do: task in: waitTime inScheduler: scheduler "This sets an alarm that will expire in waitTime seconds and execute the specified task" | newAlarm | newAlarm _ Alarm new. newAlarm setTask: task. newAlarm setTime: waitTime + (scheduler getTime). newAlarm setScheduler: scheduler. scheduler addAlarm: newAlarm. ^ newAlarm. ! ! EllipseMorph subclass: #AlertMorph instanceVariableNames: 'onColor offColor myObjSock socketOwner ' classVariableNames: '' poolDictionaries: '' category: 'Audio-Chat'! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'TBP 3/5/2000 13:47'! canHaveFillStyles ^false! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'TBP 3/5/2000 13:47'! color: aColor super color: aColor. onColor _ aColor.! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'TBP 3/5/2000 13:47'! initialize super initialize. self color: Color red. self extent: 25@25. self borderWidth: 2. ! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/2/2000 10:39'! socketOwner: aChatGUI socketOwner _ aChatGUI.! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/7/2000 08:22'! step super step. offColor ifNil: [offColor _ onColor mixed: 0.5 with: Color black]. socketOwner objectsInQueue = 0 ifTrue: [ color = offColor ifFalse: [super color: offColor]. ] ifFalse: [ super color: (color = onColor ifTrue: [offColor] ifFalse: [onColor]). ]. ! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'TBP 3/5/2000 13:47'! stepTime "Answer the desired time between steps in milliseconds." ^ 500! ! AliceAbstractAnimation subclass: #AliceAbsoluteAnimation instanceVariableNames: 'lastStartState ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceAbsoluteAnimation methodsFor: 'initialization' stamp: 'jsp 7/20/1999 00:12'! prologue: currentTime "Extends the AliceAbstractAnimation prologue by saving the start state of the animation." undoable ifTrue: [ (myWonderland getUndoStack) push: (AliceUndoAnimation new: (self makeUndoVersion)). ]. (direction = Forward) ifTrue: [ startState _ getStartStateFunction value. lastStartState _ startState. endState _ getEndStateFunction value. ] ifFalse: [ startState _ getStartStateFunction value. endState _ lastStartState. ]. super prologue: currentTime. ! ! !AliceAbsoluteAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 00:12'! object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland "This method initializes the animation with all the information that it needs to run." lastStartState _ startFunc value. super object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland. ! ! !AliceAbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:02'! copy "Creates a copy of the animation" | anim | anim _ AliceAbsoluteAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction style: styleFunction duration: duration undoable: undoable inWonderland: myWonderland. (direction = Forward) ifFalse: [ anim reverseDirection ]. ^ anim. ! ! !AliceAbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:03'! makeUndoVersion "Creates the undo version of an animation" | anim | anim _ AliceAbsoluteAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction style: styleFunction duration: 0.5 undoable: false inWonderland: myWonderland. anim stop. (direction = Forward) ifTrue: [ anim reverseDirection ]. ^ anim. ! ! !AliceAbsoluteAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:07'! reversed "Creates a reversed version of an animation" | anim | anim _ self copy reverseDirection. ^ anim. ! ! AliceUpdateable subclass: #AliceAbstractAnimation instanceVariableNames: 'startTime endTime duration state direction loopCount undoable myWonderland pausedInterval animatedObject startState endState proportionDone getStartStateFunction getEndStateFunction updateFunction styleFunction ' classVariableNames: 'Finished Forward Infinity Paused Reverse Running Stopped Waiting ' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:02'! getAnimatedObject "Return the object that this animation affects" ^ animatedObject. ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:02'! getLoopCount "Returns the animation's current loop count" ^ loopCount. ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:02'! getState "Returns the current state of the animation." ^ state. ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:03'! isDone "Returns true if the animation is running" ^ (state = Stopped). ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:03'! isLooping "Returns true if the animation is looping" ^ ( loopCount > 1) or: [ loopCount = Infinity ]. ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:03'! setLoopCount: count "Sets the animation's current loop count" loopCount _ count. ! ! !AliceAbstractAnimation methodsFor: 'accessing' stamp: 'jsp 7/19/1999 23:03'! setUndoable: aBoolean "Sets the animation's undoable property" undoable _ aBoolean. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:04'! copy self subclassResponsibility. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:04'! epilogue: currentTime "This method does any work that needs to be done after an interation of the animation finishes." (loopCount = Infinity) ifTrue: [state _ Waiting] ifFalse: [ loopCount _ loopCount - 1. (loopCount > 0) ifTrue: [ state _ Waiting ] ifFalse: [state _ Stopped. loopCount _ 1 ]. ]. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:04'! getDuration "This method returns the duration of the animation." ^ duration. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:04'! loop "This method causes an animation to loop forever." loopCount _ Infinity. (state = Stopped) ifTrue: [ state _ Waiting. myScheduler addAnimation: self. ]. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:05'! loop: numberOfTimes "This method causes an animation to loop for the specified number of times." loopCount _ numberOfTimes. (state = Stopped) ifTrue: [ state _ Waiting. myScheduler addAnimation: self. ]. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:05'! looped "This method creates a copy of an animation and loops it forever." | anim | anim _ self copy. anim setLoopCount: Infinity. ^ anim. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:05'! looped: numberOfTimes "This method creates a copy of an animation and loops it for the specified number of times." | anim | anim _ self copy. anim setLoopCount: numberOfTimes. ^ anim. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:05'! pause "This method pauses an active Animation." (state = Running) ifTrue: [ state _ Paused. pausedInterval _ (myScheduler getTime) - startTime.]. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/19/1999 23:07'! prologue: currentTime "This method does any work that needs to be done before the animation starts, including possibly adding the current state to the undo stack." startTime _ currentTime. endTime _ startTime + duration. state _ Running. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 01:04'! resume "This method resumes a paused animation" (state = Paused) ifTrue: [ state _ Running. startTime _ (myScheduler getTime) - pausedInterval. endTime _ startTime + duration. ] ifFalse: [(state = Stopped) ifTrue: [ state _ Waiting. myScheduler addUpdateItem: self. ]. ] ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 01:10'! start "This method starts an existing animation" state _ Waiting. loopCount _ 1. myScheduler addUpdateItem: self. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 01:10'! stop "This method changes the state of an animation to stopped. If it is currently active, the Scheduler will remove it from the list of active animations." state _ Stopped. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 01:10'! stopLooping "This method causes the animation to stop looping; the current interation of the animation completes before the animation stops." loopCount _ 1. ! ! !AliceAbstractAnimation methodsFor: 'management' stamp: 'jsp 7/28/1999 21:51'! update: currentTime "Updates the animation using the current Wonderland time" | newState | (state = Waiting) ifTrue: [self prologue: currentTime]. (state = Running) ifTrue: [ proportionDone _ styleFunction value: (currentTime - startTime) value: duration. newState _ startState interpolateTo: endState at: proportionDone. updateFunction value: newState. (currentTime >= endTime) ifTrue: [ state _ Finished. ]. ]. (state = Finished) ifTrue: [self epilogue: currentTime].! ! !AliceAbstractAnimation methodsFor: 'reversing' stamp: 'jsp 7/19/1999 23:01'! reverseDirection "Changes the direction an animation runs in (forward or in reverse)" (direction = Forward) ifTrue: [ direction _ Reverse ] ifFalse: [ direction _ Forward ]. ! ! !AliceAbstractAnimation methodsFor: 'private' stamp: 'jsp 7/19/1999 23:00'! scaleDuration: scaleAmount "Scales the animation's duration by the specified amount" duration _ duration * scaleAmount. ! ! !AliceAbstractAnimation methodsFor: 'private' stamp: 'jsp 7/19/1999 23:01'! setDirection: aDirection "Sets the animation's direction variable" direction _ aDirection. ! ! !AliceAbstractAnimation methodsFor: 'initialization' stamp: 'jsp 7/28/1999 21:51'! object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland "This method initializes the Animation with all the information that it needs run." animatedObject _ anObject. updateFunction _ func. styleFunction _ styleFunc. getStartStateFunction _ startFunc. getEndStateFunction _ endFunc. duration _ time. undoable _ canUndo. myScheduler _ aWonderland getScheduler. myWonderland _ aWonderland. loopCount _ 1. direction _ Forward. state _ Waiting. myScheduler addAnimation: self.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceAbstractAnimation class instanceVariableNames: ''! !AliceAbstractAnimation class methodsFor: 'class initialization' stamp: 'jsp 7/19/1999 22:57'! initialize "Initialize the class variables" Waiting _ 1. Running _ 2. Paused _ 3. Finished _ 4. Stopped _ 5. Forward _ 0. Reverse _ 1. Infinity _ -1. ! ! AliceUpdateable subclass: #AliceAction instanceVariableNames: 'actionTask paused affectedObject lifetime stopCondition ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceAction methodsFor: 'accessing' stamp: 'jsp 7/19/1999 22:06'! getAffectedObject "Returns the object affected by the action" ^ affectedObject. ! ! !AliceAction methodsFor: 'accessing' stamp: 'jsp 7/19/1999 22:08'! isDone "Returns true if the action is done executing either because it's lifetime has expired or because the specified condition is true" (lifetime > 0) ifTrue: [^ (lifetime < (myScheduler getTime))] ifFalse: [^ (stopCondition value)]. ! ! !AliceAction methodsFor: 'accessing' stamp: 'jsp 7/19/1999 22:27'! isPaused "Returns true if the action is paused" ^ paused. ! ! !AliceAction methodsFor: 'accessing' stamp: 'jsp 7/19/1999 22:28'! pause "Pause the action" paused _ true. ! ! !AliceAction methodsFor: 'accessing' stamp: 'jsp 7/19/1999 22:28'! resume "resume the action" paused _ false. ! ! !AliceAction methodsFor: 'management' stamp: 'jsp 7/19/1999 22:29'! setAffectedObject: anObject "Sets the object affected by the action" affectedObject _ anObject. ! ! !AliceAction methodsFor: 'management' stamp: 'jsp 7/19/1999 22:29'! setLifetime: howlong andCondition: condition "Sets how long the action should run, or the condition under which it should stop" lifetime _ howlong. stopCondition _ condition. ! ! !AliceAction methodsFor: 'management' stamp: 'jsp 7/19/1999 22:29'! setTask: task "Sets the task the Action should perform each frame" actionTask _ task. paused _ false. ! ! !AliceAction methodsFor: 'management' stamp: 'jsp 7/19/1999 22:29'! stop "This method removes the Action from myScheduler's list of active actions" stopCondition _ [ true ]. myScheduler removeAction: self. ! ! !AliceAction methodsFor: 'update' stamp: 'jsp 7/19/1999 22:30'! update: currentTime "Execute the Action's task" paused ifFalse: [ actionTask value ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceAction class instanceVariableNames: ''! !AliceAction class methodsFor: 'initialize-release' stamp: 'jsp 7/19/1999 22:31'! do: task eachframefor: time toObject: anObject inScheduler: scheduler "Creates a new AliceAction that performs the specified task each frame for (time) seconds" | newAction | newAction _ AliceAction new. newAction setTask: task. newAction setLifetime: (time + (scheduler getTime)) andCondition: [false]. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addUpdateItem: newAction. ^ newAction. ! ! !AliceAction class methodsFor: 'initialize-release' stamp: 'jsp 7/19/1999 22:32'! do: task eachframeuntil: condition toObject: anObject inScheduler: scheduler "Creates a new AliceAction that performs the specified task each frame until the specified condition holds true" | newAction | newAction _ AliceAction new. newAction setTask: task. newAction setLifetime: -1 andCondition: condition. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addUpdateItem: newAction. ^ newAction. ! ! !AliceAction class methodsFor: 'initialize-release' stamp: 'jsp 7/19/1999 22:33'! do: task toObject: anObject inScheduler: scheduler "Creates a new AliceAction that executes the specified task each frame" | newAction | newAction _ AliceAction new. newAction setTask: task. newAction setLifetime: -1 andCondition: [false]. newAction setAffectedObject: anObject. newAction setScheduler: scheduler. scheduler addUpdateItem: newAction. ^ newAction. ! ! AliceHierarchical subclass: #AliceActor instanceVariableNames: 'myName myWorld myMesh myTexture myMaterial myColor compositeMatrix scaleMatrix isHidden isFirstClass ' classVariableNames: '' poolDictionaries: 'AliceConstants ' category: 'Balloon3D-Alice Cast'! !AliceActor methodsFor: 'initialization' stamp: 'jsp 6/9/1999 00:11'! initializeFor: anAliceWorld "Initialize the instance variables for the AliceActor" super initialize. myName _ 'Unnamed'. myWorld _ myWorld. myParent _ myWorld getScene. myParent addChild: self. "Initialize our material" myMaterial _ B3DMaterial new. myMaterial ambientPart: Color white. myMaterial diffusePart: Color white. myMaterial specularPart: Color white. "Set up our default properties" myColor _ B3DColor4 r: 1.0 g: 1.0 b: 1.0 a: 1.0. compositeMatrix _ B3DMatrix4x4 identity. scaleMatrix _ B3DMatrix4x4 identity. isHidden _ false. isFirstClass _ true. ! ! !AliceActor methodsFor: 'drawing' stamp: 'jsp 6/9/1999 00:16'! drawMesh: aRenderer "Draw the mesh for this actor." myMaterial ifNotNil: [ aRenderer pushMaterial. aRenderer material: myMaterial. ]. myTexture ifNotNil: [ aRenderer pushTexture. aRenderer texture: myTexture. ]. "Note from Andreas: Using myMesh>>renderOn: here prevents meshes from being picked!!" myMesh ifNotNil: [ myMesh renderOn: aRenderer ]. myTexture ifNotNil: [ aRenderer popTexture ]. myMaterial ifNotNil: [ aRenderer popMaterial ]. ! ! !AliceActor methodsFor: 'drawing' stamp: 'jsp 6/9/1999 00:14'! renderOn: aRenderer "Draw the actor." "Save the old transformation matrix" aRenderer pushMatrix. "Modify the matrix using our composite matrix for position and orientation" aRenderer transformBy: compositeMatrix. "Save the new transformation matrix" aRenderer pushMatrix. "Modify the matrix using our scale matrix - we do this seperately to avoid scaling space" aRenderer transformBy: scaleMatrix. "Draw our mesh if the object is not hidden" (isHidden) ifFalse: [ self drawMesh: aRenderer ]. "Remove the scaling matrix" aRenderer popMatrix. "Draw our children. Note: For correct picking it is important to use B3DRenderEngine>>render: here." myChildren do: [:child | aRenderer render: child]. "Restore the old transformation matrix" aRenderer popMatrix.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceActor class instanceVariableNames: ''! !AliceActor class methodsFor: 'instance creation' stamp: 'jsp 6/9/1999 00:10'! newFor: anAliceWorld "Create a new instance for this World." ^ super new initializeFor: anAliceWorld. ! ! !AliceActor class methodsFor: 'unique name creation' stamp: 'jsp 6/9/1999 00:09'! uniqueNameFrom: aName "If aName is not an instance variable of this class, returns aName. Otherwise it returns a unique name based on aName that is not an instance var." | index | (self instVarNames includes: aName) ifFalse: [ ^ aName ]. index _ 2. [ self instVarNames includes: (aName , (index asString)) ] whileTrue: [ index _ index + 1 ]. ^ aName , (index asString). ! ! AliceUpdateable subclass: #AliceAlarm instanceVariableNames: 'alarmTask alarmTime ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceAlarm methodsFor: 'accessing' stamp: 'jsp 7/19/1999 21:17'! checkTime "Returns the time the alarm is set to go off at" ^ alarmTime. ! ! !AliceAlarm methodsFor: 'accessing' stamp: 'jsp 7/20/1999 01:06'! isDone "Returns true if the alarm has expired." ^ (myScheduler getTime) > alarmTime. ! ! !AliceAlarm methodsFor: 'management' stamp: 'jsp 7/20/1999 01:06'! setTask: task "Specifies the task the alarm executes when it goes off. Also sets isDone to false because the task has not yet been executed." alarmTask _ task. ! ! !AliceAlarm methodsFor: 'management' stamp: 'jsp 7/19/1999 21:21'! setTime: time "Specifies the time the alarm goes off" alarmTime _ time. ! ! !AliceAlarm methodsFor: 'management' stamp: 'jsp 7/20/1999 01:06'! stop "This method stops the alarm." myScheduler removeUpdateItem: self. ! ! !AliceAlarm methodsFor: 'update' stamp: 'jsp 7/20/1999 01:06'! update: currentTime "If the alarm's time has expired, then execute the task associated with the alarm." (alarmTime < currentTime) ifTrue: [ self execute ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceAlarm class instanceVariableNames: ''! !AliceAlarm class methodsFor: 'instance creation' stamp: 'jsp 7/19/1999 22:00'! do: task at: executeTime inScheduler: scheduler "Creates an alarm that does the specified task at the specified time" | newAlarm | newAlarm _ AliceAlarm new. newAlarm setTime: executeTime. newAlarm setTask: task. newAlarm setScheduler: scheduler. scheduler addUpdateItem: newAlarm. ^ newAlarm.! ! !AliceAlarm class methodsFor: 'instance creation' stamp: 'jsp 7/19/1999 22:01'! do: task in: waitTime inScheduler: scheduler "This sets an alarm that will expire in waitTime seconds and execute the specified task" | newAlarm | newAlarm _ AliceAlarm new. newAlarm setTask: task. newAlarm setTime: waitTime + (scheduler getTime). newAlarm setScheduler: scheduler. scheduler addAlarm: newAlarm. ^ newAlarm. ! ! AliceActor subclass: #AliceCamera instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Cast'! Object subclass: #AliceHierarchical instanceVariableNames: 'myParent myChildren ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Cast'! !AliceHierarchical methodsFor: 'initialization' stamp: 'jsp 6/8/1999 23:52'! initialize "Initialize this instance" myChildren _ OrderedCollection new. ! ! !AliceHierarchical methodsFor: 'parent-child' stamp: 'jsp 6/8/1999 23:57'! getAllChildren "Return all of this instance's children" | children | children _ OrderedCollection new. myChildren do: [:child | children addLast: child. children _ children , (child getAllChildren). ]. ^ children.! ! !AliceHierarchical methodsFor: 'parent-child' stamp: 'jsp 6/8/1999 23:58'! getChildren "Return the object's immediate children." ^ (myChildren copy). ! ! !AliceHierarchical methodsFor: 'parent-child' stamp: 'jsp 6/8/1999 23:59'! getParent "Return the object's parent." ^ myParent. ! ! !AliceHierarchical methodsFor: 'parent-child' stamp: 'jsp 6/8/1999 23:59'! setParent: anObject "Set this instance's parent" myParent _ anObject. ! ! !AliceHierarchical methodsFor: 'private' stamp: 'jsp 6/8/1999 23:54'! addChild: aChild "Add an object to this instance's list of children. Checks to make sure that aChild is not already a child of this object" ((myChildren identityIndexOf: aChild) = 0) ifTrue: [ myChildren addLast: aChild ]. ! ! !AliceHierarchical methodsFor: 'private' stamp: 'jsp 6/8/1999 23:59'! appendChildrenNamesTo: prefix "Return the object's children's names, each appended to the prefix." | nameList | nameList _ OrderedCollection new. myChildren do: [:child | nameList addLast: (prefix , (child getName)). nameList _ nameList , (child appendChildrenNamesTo: (prefix , ' '))]. ^ nameList. ! ! !AliceHierarchical methodsFor: 'private' stamp: 'jsp 6/8/1999 23:58'! getChildrenNames "Return the object's children." ^ myChildren collect: [: child | child asString ]. ! ! !AliceHierarchical methodsFor: 'private' stamp: 'jsp 6/8/1999 23:57'! removeChild: aChild "Remove an object from this instance's list of children" myChildren remove: aChild ifAbsent: []. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceHierarchical class instanceVariableNames: ''! !AliceHierarchical class methodsFor: 'instance creation' stamp: 'jsp 6/8/1999 23:52'! new "Create and initialize a new instance." ^ super new initialize. ! ! AliceActor subclass: #AliceLight instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Cast'! Object subclass: #AliceNamespace instanceVariableNames: 'myDictionary myWorkspace ' classVariableNames: '' poolDictionaries: 'AliceConstants ' category: 'Balloon3D-Alice Misc'! !AliceNamespace methodsFor: 'initialize' stamp: 'jsp 6/7/1999 21:49'! initialize "Initialize the namespace" myDictionary _ AliceConstants copy. myWorkspace _ Workspace new. myWorkspace setBindings: myDictionary. myWorkspace embeddedInMorphicWindowLabeled: 'Namespace'. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:43'! at: key "Return the value in the namespace associated with the key" ^ myDictionary at: key. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:43'! at: key put: value "Store the value in the namespace under the key" myDictionary at: key put: value. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:42'! getDictionary "Return the namespace dictionary" ^ myDictionary. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:44'! getEvaluationContext "Return a context containing the namespace for evaluating a statement " ^ (myWorkspace dependents last select model: myWorkspace). ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:55'! includesKey: aKey "Return true if the namespace includes the key" ^ myDictionary includesKey: aKey. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/8/1999 22:57'! removeKey: theKey "Remove the key from the namespace" myDictionary removeKey: theKey ifAbsent: []. ! ! !AliceNamespace methodsFor: 'accessing' stamp: 'jsp 6/8/1999 22:57'! removeKey: theKey ifAbsent: failBlock "Remove the key from the namespace. If the key isn't there, run the code in the fail block." myDictionary removeKey: theKey ifAbsent: failBlock. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceNamespace class instanceVariableNames: ''! !AliceNamespace class methodsFor: 'instance creation' stamp: 'jsp 6/7/1999 21:46'! new "Create a new namespace for an Alice world" ^ super new initialize. ! ! Object subclass: #AlicePoolDefiner instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Misc'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AlicePoolDefiner class instanceVariableNames: ''! !AlicePoolDefiner class methodsFor: 'class initialization' stamp: 'jsp 6/7/1999 14:58'! initialize "Initialize the Alice 2.0 pool dictionary" self initPool. ! ! !AlicePoolDefiner class methodsFor: 'pool definition' stamp: 'jsp 6/29/1999 00:06'! initPool "Create the pool dictionary if necessary" | poolName | poolName _ #AliceConstants. (Smalltalk includesKey: poolName) ifFalse:[ Smalltalk declare: poolName from: Undeclared. ]. (Smalltalk at: poolName) isNil ifTrue:[ (Smalltalk associationAt: poolName) value: ((Smalltalk at: #WonderlandConstants) copy). ]. self initPool: (Smalltalk at: poolName).! ! !AlicePoolDefiner class methodsFor: 'pool definition' stamp: 'jsp 6/7/1999 15:03'! initPool: aDictionary "Initialize the dictionary" aDictionary at: #inOrder put: #inOrder. aDictionary at: #together put: #together. ! ! AliceAbstractAnimation subclass: #AliceRelativeAnimation instanceVariableNames: 'getReverseStateFunction ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceRelativeAnimation methodsFor: 'initialization' stamp: 'jsp 7/20/1999 00:15'! object: anObject update: func getStartState: startFunc getEndState: endFunc getReverseState: reverseFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland "This method initializes the Animation with all the information that it needs to run." getReverseStateFunction _ reverseFunc. super object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland. ! ! !AliceRelativeAnimation methodsFor: 'management' stamp: 'jsp 7/20/1999 00:18'! prologue: currentTime "Extends the AbstractAnimation prologue by saving the start state of the animation." undoable ifTrue: [ (myWonderland getUndoStack) push: (AliceUndoAnimation new: (self makeUndoVersion)). ]. (direction = Forward) ifTrue: [ startState _ getStartStateFunction value. endState _ getEndStateFunction value. ] ifFalse: [ startState _ getStartStateFunction value. endState _ getReverseStateFunction value. ]. super prologue: currentTime. ! ! !AliceRelativeAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:16'! copy "Creates a copy of the animation" | anim | anim _ AliceRelativeAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction getReverseState: getReverseStateFunction style: styleFunction duration: duration undoable: undoable inWonderland: myWonderland. (direction = Forward) ifFalse: [ anim reverseDirection ]. ^ anim. ! ! !AliceRelativeAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:18'! makeUndoVersion "Creates the undo version of an animation" | anim | anim _ AliceRelativeAnimation new. anim object: animatedObject update: updateFunction getStartState: getStartStateFunction getEndState: getEndStateFunction getReverseState: getReverseStateFunction style: styleFunction duration: 0.5 undoable: false inWonderland: myWonderland. anim stop. (direction = Forward) ifTrue: [ anim reverseDirection ]. ^ anim. ! ! !AliceRelativeAnimation methodsFor: 'copying' stamp: 'jsp 7/20/1999 00:18'! reversed "Creates a reversed version of an animation" | anim | anim _ self copy reverseDirection. ^ anim. ! ! AliceHierarchical subclass: #AliceScene instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Cast'! Object subclass: #AliceScheduler instanceVariableNames: 'currentTime elapsedTime lastSystemTime speed isRunning updateList ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceScheduler methodsFor: 'initialize' stamp: 'jsp 6/7/1999 16:06'! initialize "Initialize the scheduler" "The scheduler starts at time 0" currentTime _ 0. elapsedTime _ 0. "The scheduler starts executing at 1:1 time" speed _ 1. "The scheduler starts running" isRunning _ true. "Determine the system time we're starting at" lastSystemTime _ Time millisecondClockValue / 1000.0. "Create the list of items to update" updateList _ OrderedCollection new. ! ! !AliceScheduler methodsFor: 'initialize' stamp: 'jsp 6/7/1999 15:51'! reset "Resets the Wonderland time to 0" self initialize. ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:53'! getElapsedTime "Returns the time that elapsed in the last Scheduler tick" ^ elapsedTime. ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:53'! getFPS "Returns the instantaneous frames per second (1 / elapsedTime)" ^ (1.0 / elapsedTime). ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:53'! getTime "Returns the current scheduler time" ^ currentTime. ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:55'! pause "Pause the scheduler. Pauses all script executiong, but any active cameras continue to render." isRunning _ false. ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:55'! resume "If the scheduler was paused, resume it." isRunning ifFalse: [ isRunning _ true. lastSystemTime _ (Time millisecondClockValue) / 1000.0. ]. ! ! !AliceScheduler methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:55'! setSpeed: newSpeed "This method sets the speed for the Scheduler. 1 is a 1:1 mapping with clock time, 2 is a 2:1 mapping, etc." (speed > 0) ifTrue: [speed _ newSpeed] ifFalse: [self error: 'Scheduler speed must be greater than 0.']. ! ! !AliceScheduler methodsFor: 'update list maintenance' stamp: 'jsp 6/7/1999 16:05'! addUpdateItem: newItem "Add a new item to the scheduler's update list (a running animation, active script, etc" updateList addLast: newItem. ! ! !AliceScheduler methodsFor: 'update list maintenance' stamp: 'jsp 6/7/1999 16:06'! removeUpdateItem: anItem "Add a new item to the scheduler's update list (a running animation, active script, etc)" updateList remove: anItem ifAbsent: []. ! ! !AliceScheduler methodsFor: 'ticking' stamp: 'jsp 6/7/1999 16:02'! tick "Figure out how much time has elapsed since the last Scheduler tick and update all the scripts" isRunning ifTrue: [ elapsedTime _ ((Time millisecondClockValue / 1000.0) - lastSystemTime) * speed. "if elapsedTime is negative the clock rolled over; deal with it" (elapsedTime < 0) ifTrue: [lastSystemTime _ 0. elapsedTime _ (Time millisecondClockValue) / 1000.0]. currentTime _ currentTime + elapsedTime. lastSystemTime _ lastSystemTime + elapsedTime. "Process scripts here" updateList do: [:item | item update: currentTime. (item isDone) ifTrue: [self removeUpdateItem: item] ]. ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceScheduler class instanceVariableNames: ''! !AliceScheduler class methodsFor: 'instance creation' stamp: 'jsp 6/7/1999 15:46'! new "Create a new scheduler and initialize it" ^ super new initialize. ! ! AliceUpdateable subclass: #AliceScript instanceVariableNames: 'scriptName myCommands activeAnimations pendingCommands scriptType isRunning myWorld ' classVariableNames: '' poolDictionaries: 'AliceConstants ' category: 'Balloon3D-Alice Scripts'! !AliceScript methodsFor: 'initialize' stamp: 'jsp 7/20/1999 01:08'! initialize: anAliceWorld "Initialize script by assigning the scheduler and putting default values in the instance variables" "Set the script name" scriptName _ 'Unnamed'. "Set the scheduler for this script" myWorld _ anAliceWorld. myScheduler _ myWorld getScheduler. "By default a script contains no commands" myCommands _ OrderedCollection new. "By default there are no active commands" pendingCommands _ OrderedCollection new. "By default there are no active animations" activeAnimations _ OrderedCollection new. "By default scripts run in order (one command after another)" scriptType _ inOrder. "By default the script isn't running" isRunning _ false. ! ! !AliceScript methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:59'! getScriptName "Returns the name of the script" ^ scriptName. ! ! !AliceScript methodsFor: 'accessing' stamp: 'jsp 6/7/1999 16:12'! isDone "Returns true if the script is not currently running" ^ isRunning not. ! ! !AliceScript methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:15'! setCommands: commands "Set the commands in the script" myCommands _ commands. ! ! !AliceScript methodsFor: 'accessing' stamp: 'jsp 6/7/1999 21:59'! setScriptName: aName "Sets the name of the script" scriptName _ aName. ! ! !AliceScript methodsFor: 'accessing' stamp: 'jsp 6/7/1999 15:14'! setScriptType: type "Set the script type (inOrder or Together)" scriptType _ type. ! ! !AliceScript methodsFor: 'executing' stamp: 'jsp 7/20/1999 01:09'! start "Start running this script" | result | (scriptType = inOrder) ifTrue: [ pendingCommands _ OrderedCollection new. 1 to: (myCommands size) do: [:i | pendingCommands addLast: i ]. ] ifFalse: [ myCommands do: [:command | result _ command. result _ Compiler new evaluate: command in: nil to: nil notifying: (myWorld getNamespace getEvaluationContext) ifFail: []. myWorld addOutputText: (result printString). (result isKindOf: Animation) ifTrue: [ activeAnimations add: result ]. ]. ]. isRunning _ true. "Need to add this script to the scheduler so it gets updated" myScheduler addUpdateItem: self. "Update the script once with the current time" self update: (myScheduler getTime). ! ! !AliceScript methodsFor: 'executing' stamp: 'jsp 6/7/1999 21:38'! update: currentTime "Determine how to update this script based on the type of script it is" (scriptType = inOrder) ifTrue: [ self updateInOrder: currentTime ] ifFalse: [ self updateTogether: currentTime ]. ! ! !AliceScript methodsFor: 'executing' stamp: 'jsp 6/8/1999 17:33'! updateInOrder: currentTime "Update this script assuming that one command runs after the previous command finishes" | nextCommand result | "Update the previous command if it's still active" activeAnimations do: [:anim | anim update: currentTime. (anim isDone) ifTrue: [activeAnimations remove: anim ]]. "Check if all active animations are complete, if not keep pulling and executing script commands until we hit one that doesn't complete immediately" (activeAnimations isEmpty) ifTrue: [ [ (pendingCommands isEmpty) or: [activeAnimations isEmpty not] ] whileFalse: [ nextCommand _ myCommands at: (pendingCommands removeFirst). "evaluate the command in my namespace" result _ Compiler new evaluate: nextCommand in: nil to: nil notifying: (myWorld getNamespace getEvaluationContext) ifFail: []. myWorld addOutputText: (result printString). (result isKindOf: Animation) ifTrue: [ activeAnimations addLast: result ]. ]. ((activeAnimations isEmpty) and: [ pendingCommands isEmpty ]) ifTrue: [ isRunning _ false ]. ]. ! ! !AliceScript methodsFor: 'executing' stamp: 'jsp 6/7/1999 21:39'! updateTogether: currentTime "Update this script assuming that all script commands begin simultaneously" activeAnimations do: [:anim | anim update: currentTime. (anim isDone) ifTrue: [activeAnimations remove: anim ]]. (activeAnimations isEmpty) ifTrue: [ isRunning _ false ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceScript class instanceVariableNames: ''! !AliceScript class methodsFor: 'instance creation' stamp: 'jsp 6/8/1999 13:36'! new: type withCommands: commands in: anAliceWorld "Create a new nameless (lambda) script containing the specified commands" | newScript | newScript _ AliceScript new initialize: anAliceWorld. newScript setScriptType: type. newScript setCommands: commands. ^ newScript. ! ! PluggableTextMorph subclass: #AliceTextOutputWindow instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Interface'! !AliceTextOutputWindow methodsFor: 'initialization' stamp: 'jsp 7/25/1999 23:11'! initialize "Initialize the window for output." super initialize. self color: (Color r: 0.627 g: 0.909 b: 0.972). self openInWorld. ! ! !AliceTextOutputWindow methodsFor: 'output text' stamp: 'jsp 7/25/1999 23:09'! addText: aString "Adds the specified string to the output window" | textLength | self setText: ((textMorph contents) , aString) asText. textLength _ textMorph contents size + 1. self selectFrom: textLength to: textLength. self scrollSelectionIntoView. ! ! !AliceTextOutputWindow methodsFor: 'output text' stamp: 'jsp 7/25/1999 23:05'! addTextOnNewLine: aString "Adds the specified string to the output window as a new line" | textLength | self setText: ((textMorph contents) , (Character cr asString) , aString) asText. textLength _ textMorph contents size + 1. self selectFrom: textLength to: textLength. self scrollSelectionIntoView. ! ! Object subclass: #AliceUndoAnimation instanceVariableNames: 'wrappedAnimation ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Undo'! !AliceUndoAnimation methodsFor: 'accessing' stamp: 'jsp 7/20/1999 00:10'! setAnimation: anAnimation "Set wrapped animation." wrappedAnimation _ anAnimation. ! ! !AliceUndoAnimation methodsFor: 'undoing' stamp: 'jsp 7/20/1999 00:10'! undoIt "Undo by running the wrapped animation." wrappedAnimation start. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceUndoAnimation class instanceVariableNames: ''! !AliceUndoAnimation class methodsFor: 'instance creation' stamp: 'jsp 7/20/1999 00:11'! new: anAnimation "Create a wrapper for undoing an animation" | newUndo | newUndo _ UndoAnimation new. newUndo setAnimation: anAnimation. ^ newUndo.! ! Object subclass: #AliceUpdateable instanceVariableNames: 'myScheduler ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Alice Time'! !AliceUpdateable methodsFor: 'management' stamp: 'jsp 7/20/1999 00:59'! setScheduler: scheduler "Set the Scheduler that manages this updateable item" myScheduler _ scheduler. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceUpdateable class instanceVariableNames: ''! !AliceUpdateable class methodsFor: 'instance creation' stamp: 'jsp 7/19/1999 21:54'! new "Create and initialize a new instance" super new initialize. ! ! Object subclass: #AliceWorld instanceVariableNames: 'myScheduler myNamespace myUndoStack sharedMeshDict sharedTextureDict cameraList lightList myScene actorClassList myTextOutputWindow ' classVariableNames: 'ActorPrototypeClasses ' poolDictionaries: 'AliceConstants ' category: 'Balloon3D-Alice Worlds'! !AliceWorld methodsFor: 'temporary' stamp: 'jsp 6/8/1999 22:52'! makeActorFrom: filename "Creates a new actor using the specification from the given file" | aFile words line startSubstr index parent name texture meshFile matrix baseActor newActor protoClass actorClass fileVersion | myUndoStack closeStack. words _ (filename findTokens: #.). ((words last) = 'mdl') ifTrue: [ aFile _ (CrLfFileStream readOnlyFileNamed: filename) ascii. "First see if we need to create a prototype class for this model" (ActorPrototypeClasses includesKey: (aFile localName)) ifTrue: [ protoClass _ ActorPrototypeClasses at: (aFile localName) ] ifFalse: [ "Make a new prototype class for this model" protoClass _ (WonderlandActor newUniqueClassInstVars: '' classInstVars: ''). ActorPrototypeClasses at: (aFile localName) put: protoClass. ]. "Check what version this mdl file is" line _ aFile upTo: (Character cr). line _ aFile upTo: (Character cr). line _ aFile upTo: (Character cr). ((line truncateTo: 7) = 'version') ifTrue: [ fileVersion _ 1 ] ifFalse: [ fileVersion _ 0 ]. [ line _ aFile upTo: (Character cr). (aFile atEnd) ifTrue: [ true ] ifFalse: [ words _ line findTokens: '='. false ] ] whileFalse: [ "See if we're creating a new object" (((words size) > 1) and: [ ((words at: 2) beginsWith: ' _MakeObject') or: [ (words at: 2) beginsWith: ' Alice.MakeObject' ] ]) ifTrue: [ (fileVersion = 0) ifTrue: [ words _ line findTokens: #,. parent _ (words at: 2) withBlanksTrimmed. name _ (((words at: 3) withBlanksTrimmed) findBetweenSubStrs: '"') at: 1. ] ifFalse: [ name _ (words at: 1) truncateTo: (((words at: 1) size) - 1). parent _ ((words at: 3) findTokens: #,) at: 1. ]. "Now pull in the texture to use" startSubstr _ name , '.SetTexture'. [(line _ aFile upTo: (Character cr)) beginsWith: startSubstr] whileFalse: []. texture _ (line findBetweenSubStrs: '"') at: 2. texture _ (aFile directory pathName), FileDirectory slash, texture. "Read the composite matrix to use" startSubstr _ name , '._SetLocalTransformation'. [(line _ aFile upTo: (Character cr)) beginsWith: startSubstr] whileFalse: []. matrix _ B3DMatrix4x4 new. words _ line findBetweenSubStrs: ',()'. words removeAllSuchThat: [:str | str = ' ']. index _ words size. 4 to: 1 by: -1 do: [:i | 4 to: 1 by: -1 do: [:j | matrix at: i at: j put: ((words at: index) withBlanksTrimmed) asNumber. index _ index - 1. ]. ]. 1 to: 4 do: [:i | index _ matrix at: i at: 4. matrix at: i at: 4 put: (matrix at: 4 at: i). matrix at: 4 at: i put: index. ]. matrix a14: (matrix a14 negated). "Read the mesh file to use" startSubstr _ 'LoadGeometry'. [(line _ aFile upTo: (Character cr)) beginsWith: startSubstr] whileFalse: []. meshFile _ (line findBetweenSubStrs: '"') at: 2. meshFile _ (aFile directory pathName), FileDirectory slash, meshFile. "Now build the actor name" words _ name findTokens: '.'. name _ words last. name at: 1 put: ((name at: 1) asLowercase). "Now build the parent name" parent _ parent copyReplaceAll: '.' with: ' '. "Now create the object" (parent = 'None') ifTrue: [ actorClass _ protoClass newUniqueClassInstVars: '' classInstVars: ''. baseActor _ actorClass createFor: self. actorClassList addLast: actorClass. baseActor setName: name. baseActor setTexture: texture. baseActor loadMeshFromFile: meshFile. baseActor setComposite: matrix. ] "end base actor creation" ifFalse: [ actorClass _ WonderlandActor newUniqueClassInstVars: '' classInstVars: ''. newActor _ actorClass createFor: self. actorClassList addLast: actorClass. newActor setName: name. parent _ (baseActor getChildNamed: parent). newActor reparentTo: parent. newActor becomePart. newActor setTexture: texture. newActor loadMeshFromFile: meshFile. newActor setComposite: matrix. ]. "end new actor with parent" ]. "end MakeObject parsing" ]. "end file parsing" aFile close. myUndoStack openStack. "Ensure that the new actor's name is unique" name _ self uniqueNameFrom: (baseActor getName). baseActor setName: name. myNamespace at: name put: baseActor. "Add an undo item to undo the creation of this object" myUndoStack push: (UndoAction new: [ baseActor removeFromScene. myNamespace removeKey: name ifAbsent: []. ] ). ^ baseActor. ]. " end mdl file parsing" ! ! !AliceWorld methodsFor: 'temporary' stamp: 'jsp 6/8/1999 23:06'! makeLight "Create a light of the specified type and add it to the Wonderland" | theLight lightType name | lightType _ positional. "Make sure the user gave us a type of light" [ WonderlandVerifier VerifyLight: lightType ] ifError: [ :msg :rcvr | self reportErrorToUser: 'Squeak could not determine the type of light to create because ', msg. ^ nil ]. "The user gave us a valid type type, so proceed" (lightType = ambient) ifTrue: [ theLight _ WonderlandAmbientLight createFor: self. ] ifFalse: [ (lightType = positional) ifTrue: [ theLight _ WonderlandPositionalLight createFor: self. ] ifFalse: [ (lightType = directional) ifTrue: [ theLight _ WonderlandDirectionalLight createFor: self. ] ifFalse: [ theLight _ WonderlandSpotLight createFor: self. ] ] ]. name _ self uniqueNameFrom: 'light'. theLight setName: name. myNamespace at: name put: theLight. lightList addLast: theLight. ^ theLight. ! ! !AliceWorld methodsFor: 'temporary' stamp: 'jsp 6/8/1999 23:05'! makeLight: lightType "Create a light of the specified type and add it to the Wonderland" | theLight name | "Make sure the user gave us a type of light" [ WonderlandVerifier VerifyLight: lightType ] ifError: [ :msg :rcvr | self reportErrorToUser: 'Squeak could not determine the type of light to create because ', msg. ^ nil ]. "The user gave us a valid type type, so proceed" (lightType = ambient) ifTrue: [ theLight _ WonderlandAmbientLight createFor: self. ] ifFalse: [ (lightType = positional) ifTrue: [ theLight _ WonderlandPositionalLight createFor: self. ] ifFalse: [ (lightType = directional) ifTrue: [ theLight _ WonderlandDirectionalLight createFor: self. ] ifFalse: [ theLight _ WonderlandSpotLight createFor: self. ] ] ]. name _ self uniqueNameFrom: 'light'. theLight setName: name. myNamespace at: name put: theLight. lightList addLast: theLight. ^ theLight. ! ! !AliceWorld methodsFor: 'temporary' stamp: 'jsp 6/8/1999 22:51'! renderWonderland: aRenderer "Temporary method" self renderWorld: aRenderer. ! ! !AliceWorld methodsFor: 'initialize-reset-release' stamp: 'jsp 7/25/1999 23:10'! initialize "Initialize the Alice world" | defaultCamera | "Initialize this Wonderland's shared namespace" myNamespace _ AliceNamespace new. myNamespace at: 'world' put: self. "Create the Wonderland's scheduler" myScheduler _ AliceScheduler new. myNamespace at: 'scheduler' put: myScheduler. "Initialize the list of actor UniClasses" actorClassList _ OrderedCollection new. "Initialize the shared mesh and texture directories" sharedMeshDict _ Dictionary new. sharedTextureDict _ Dictionary new. "Create an output window for us to dump text to" myTextOutputWindow _ AliceTextOutputWindow new. myTextOutputWindow setText: 'Squeak Alice v2.0.'. cameraList _ OrderedCollection new. lightList _ OrderedCollection new. "-------------------------------" "Create the undo stack for this Wonderland." myUndoStack _ WonderlandUndoStack new. "The scene object is the root of the object tree - all objects in the Wonderland are children (directly or indirectly) of the scene. " myScene _ WonderlandScene newFor: self. myNamespace at: 'scene' put: myScene. "Create the default camera" defaultCamera _ WonderlandCamera createFor: self. cameraList addLast: defaultCamera. myNamespace at: 'camera' put: defaultCamera. myNamespace at: 'cameraWindow' put: (defaultCamera getMorph). defaultCamera setName: 'camera'. myUndoStack reset. ! ! !AliceWorld methodsFor: 'initialize-reset-release' stamp: 'jsp 6/8/1999 22:46'! release "This method cleans up the world." "Clean up any uniclasses we created" actorClassList do: [:aClass | aClass removeFromSystem ]. "Clean up the output window" myTextOutputWindow delete. "Get rid of our cameras" cameraList do: [:camera | camera release]. ! ! !AliceWorld methodsFor: 'initialize-reset-release' stamp: 'jsp 6/8/1999 17:19'! reset "Reset this Wonderland" "Initialize this Wonderland's shared namespace" myNamespace _ AliceNamespace new. "Reset the scheduler" myScheduler reset. "Reset the shared mesh and texture directories" sharedMeshDict _ Dictionary new. sharedTextureDict _ Dictionary new. "Reset the list of actor uniclasses" actorClassList do: [:aClass | aClass removeFromSystem ]. actorClassList _ OrderedCollection new. "Rebuild the namespace" myNamespace at: 'scheduler' put: myScheduler. myNamespace at: 'world' put: self. "Create a new text output window" myTextOutputWindow setText: 'Reset'. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 22:55'! getActorClassList "Return the list of actor classes" ^ actorClassList. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 22:39'! getCameras "Return the list of cameras in the scene" ^ cameraList. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:36'! getDefaultCamera "Return the default camera, which is the camera at the front of the camera list" ^ cameraList first. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 22:39'! getLights "Return the list of lights in the scene" ^ lightList. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 17:38'! getNamespace "Return this world's namespace" ^ myNamespace. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:37'! getScene "Return the world's scene" ^ myScene. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 17:56'! getScheduler "Return this world's scheduler" ^ myScheduler. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:47'! getSharedMeshDict "Return the shared mesh dictionary" ^ sharedMeshDict. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:47'! getSharedTextureDict "Return the shared texture dictionary" ^ sharedTextureDict. ! ! !AliceWorld methodsFor: 'accessing' stamp: 'jsp 6/8/1999 21:38'! getUndoStack "Return the world's undo stack" ^ myUndoStack. ! ! !AliceWorld methodsFor: 'creating scripts' stamp: 'jsp 6/8/1999 13:37'! doInOrder: commands "Create a nameless inOrder script with the specified commands" ^ AliceScript new: inOrder withCommands: commands in: self. ! ! !AliceWorld methodsFor: 'creating scripts' stamp: 'jsp 6/8/1999 13:37'! doTogether: commands "Create a nameless together script with the specified commands" ^ AliceScript new: together withCommands: commands in: self. ! ! !AliceWorld methodsFor: 'creating actors' stamp: 'jsp 6/8/1999 23:12'! fixNameFrom: aString "Fix the name to be a valid Smalltalk name (e.g., so that we can compile it as an inst var and accessor message)" | aName | aName _ aString select: [:c | c isAlphaNumeric]. "If the name is empty use 'unknown'" aName isEmpty ifTrue:[aName _ 'unknown']. "Make sure the first letter is lowercase" aName first isUppercase ifTrue: [aName _ (aName first asLowercase asString) , (aName copyFrom: 2 to: aName size) ]. "Make sure the first letter is a letter, otherwise use 'a' as the first letter" aName first isLetter ifFalse: [aName _ 'a' , aName]. ^ aName. ! ! !AliceWorld methodsFor: 'creating actors' stamp: 'jsp 6/8/1999 23:12'! uniqueNameFrom: aString "If aName is unique to this world's namespace, returns that name. Otherwise creates a unique variant and returns that." | index aName | aName _ self fixNameFrom: aString. (myNamespace includesKey: aName) ifFalse: [ ^ aName ] ifTrue: [ index _ 2. [ myNamespace includesKey: (aName , (index asString)) ] whileTrue: [ index _ index + 1 ]. ^ aName , (index asString). ]. ! ! !AliceWorld methodsFor: 'user feedback' stamp: 'jsp 6/8/1999 17:20'! addOutputText: thisText "Appends the given text to the Alice output window" myTextOutputWindow addTextOnNewLine: thisText. ! ! !AliceWorld methodsFor: 'user feedback' stamp: 'jsp 6/8/1999 21:52'! reportErrorToUser: errorString "When any object in an Alice World discovers an error it creates an error report and then calls this method to display the error to the user." | errWin tm | errWin _ SystemWindowWithButton labelled: 'Ooops'. errWin openInWorldExtent: 400@100. errWin color: (Color white). tm _ TextMorph new. tm initialize. errWin addMorph: tm. tm color: (Color red). tm contents: errorString wrappedTo: 380. tm position: ((errWin position) + (10@20)). tm lock. errWin height: (tm height) + 30. errorSound play. ! ! !AliceWorld methodsFor: 'undoing actions' stamp: 'jsp 6/8/1999 21:52'! undo "Undo the last action the user performed in the Wonderland. This pulls a block context off the animation stack and executes it." myUndoStack popAndUndo. ! ! !AliceWorld methodsFor: 'drawing' stamp: 'jsp 6/8/1999 21:48'! renderWorld: aRenderer "Tell all the objects in the World to render themselves." myScene renderOn: aRenderer. ! ! !AliceWorld methodsFor: 'private' stamp: 'jsp 6/8/1999 22:42'! getTextOutputWindow "Returns the current text output window" ^ myTextOutputWindow. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AliceWorld class instanceVariableNames: ''! !AliceWorld class methodsFor: 'instance creation' stamp: 'jsp 6/7/1999 22:27'! new "AliceWorld new" "Create and initialize a new AliceWorld." B3DPrimitiveEngine isAvailable ifFalse: [ (self confirm: 'WARNING: This Squeak does not have real 3D support. Opening a Wonderland will EXTREMELY time consuming. Are you sure you want to do this? (NO is probably the right answer :-)') ifFalse: [^ self]]. Display depth < 8 ifTrue: [(self confirm: 'The display depth should be set to at least 8 bit. Shall I do this now for you?') ifTrue: [Display newDepth: 8]]. ^ super new initialize. ! ! !AliceWorld class methodsFor: 'class initialization' stamp: 'jsp 6/7/1999 22:17'! initialize "Initialize the AliceWorld class by creating the ActorPrototypeClasses collection" ActorPrototypeClasses _ Dictionary new. ! ! !AliceWorld class methodsFor: 'actor prototype mgmt' stamp: 'jsp 6/7/1999 22:18'! removeActorPrototypesFromSystem "Clean out all the actor prototypes - this involves removing those classes from the Smalltalk dictionary" ActorPrototypeClasses do: [:aClass | aClass removeFromSystem ]. ActorPrototypeClasses _ Dictionary new.! ! RectangleMorph subclass: #AlignmentMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !AlignmentMorph methodsFor: 'initialization' stamp: 'ar 10/25/2000 17:53'! addUpDownArrowsFor: aMorph "Add a column of up and down arrows that serve to send upArrowHit and downArrowHit to aMorph when they're pressed/held down" | holder downArrow upArrow | holder _ Morph new extent: 16 @ 16; beTransparent. downArrow _ ImageMorph new image: (ScriptingSystem formAtKey: 'DownArrow'). upArrow _ ImageMorph new image: (ScriptingSystem formAtKey: 'UpArrow'). upArrow position: holder bounds topLeft + (2@2). downArrow align: downArrow bottomLeft with: holder topLeft + (0 @ TileMorph defaultH) + (2@-2). holder addMorph: upArrow. holder addMorph: downArrow. self addMorphBack: holder. upArrow on: #mouseDown send: #upArrowHit to: aMorph. upArrow on: #mouseStillDown send: #upArrowHit to: aMorph. downArrow on: #mouseDown send: #downArrowHit to: aMorph. downArrow on: #mouseStillDown send: #downArrowHit to: aMorph.! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 22:26'! initialize super initialize. borderWidth _ 0. self layoutPolicy: TableLayout new. self listDirection: #leftToRight. self wrapCentering: #topLeft. self hResizing: #spaceFill. self vResizing: #spaceFill. self layoutInset: 2. color _ Color r: 0.8 g: 1.0 b: 0.8. self rubberBandCells: true. "default"! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 20:34'! openInWindowLabeled: aString inWorld: aWorld self layoutInset: 0. ^super openInWindowLabeled: aString inWorld: aWorld.! ! !AlignmentMorph methodsFor: 'classification' stamp: 'sw 5/13/1998 14:50'! demandsBoolean "unique to the TEST frame inside a CompoundTileMorph" ^ self hasProperty: #demandsBoolean! ! !AlignmentMorph methodsFor: 'classification' stamp: 'di 5/7/1998 01:20'! isAlignmentMorph ^ true ! ! !AlignmentMorph methodsFor: 'accessing' stamp: 'panda 4/25/2000 15:44'! configureForKids self disableDragNDrop. super configureForKids ! ! !AlignmentMorph methodsFor: 'private' stamp: 'sw 5/6/1998 15:58'! wantsKeyboardFocusFor: aSubmorph aSubmorph wouldAcceptKeyboardFocus ifTrue: [^ true]. ^ super wantsKeyboardFocusFor: aSubmorph! ! !AlignmentMorph methodsFor: 'object fileIn' stamp: 'RAA 12/21/2000 11:25'! convertOldAlignmentsNov2000: varDict using: smartRefStrm "major change - much of AlignmentMorph is now implemented more generally in Morph" "These are going away #('orientation' 'centering' 'hResizing' 'vResizing' 'inset' 'minCellSize' 'layoutNeeded' 'priorFullBounds')" | orientation centering hResizing vResizing inset minCellSize inAlignment | orientation _ varDict at: 'orientation'. centering _ varDict at: 'centering'. hResizing _ varDict at: 'hResizing'. vResizing _ varDict at: 'vResizing'. inset _ varDict at: 'inset'. minCellSize _ varDict at: 'minCellSize'. (orientation == #horizontal or:[orientation == #vertical]) ifTrue:[self layoutPolicy: TableLayout new]. self cellPositioning: #topLeft. self rubberBandCells: true. orientation == #horizontal ifTrue:[self listDirection: #leftToRight]. orientation == #vertical ifTrue:[self listDirection: #topToBottom]. centering == #topLeft ifTrue:[self wrapCentering: #topLeft]. centering == #bottomRight ifTrue:[self wrapCentering: #bottomRight]. centering == #center ifTrue:[self wrapCentering: #center. orientation == #horizontal ifTrue:[self cellPositioning: #leftCenter] ifFalse:[self cellPositioning: #topCenter]]. (inset isNumber or:[inset isPoint]) ifTrue:[self layoutInset: inset]. (minCellSize isNumber or:[minCellSize isPoint]) ifTrue:[self minCellSize: minCellSize]. (self hasProperty: #clipToOwnerWidth) ifTrue:[self clipSubmorphs: true]. "now figure out if our owner was an AlignmentMorph, even if it is reshaped..." inAlignment _ false. (owner isKindOf: Morph) ifTrue:[ (owner isKindOf: AlignmentMorph) ifTrue:[inAlignment _ true]. ] ifFalse:[ "e.g., owner may be reshaped" (owner class instanceVariablesString findString: 'orientation centering hResizing vResizing') > 0 ifTrue:["this was an alignment morph being reshaped" inAlignment _ true]. ]. "And check for containment in system windows" (owner isKindOf: SystemWindow) ifTrue:[inAlignment _ true]. (hResizing == #spaceFill and:[inAlignment not]) ifTrue:[self hResizing: #shrinkWrap] ifFalse:[self hResizing: hResizing]. (vResizing == #spaceFill and:[inAlignment not]) ifTrue:[self vResizing: #shrinkWrap] ifFalse:[self vResizing: vResizing]. ! ! !AlignmentMorph methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 18:00'! convertToCurrentVersion: varDict refStream: smartRefStrm super convertToCurrentVersion: varDict refStream: smartRefStrm. "major change - much of AlignmentMorph is now implemented more generally in Morph" varDict at: 'hResizing' ifPresent: [ :x | self convertOldAlignmentsNov2000: varDict using: smartRefStrm ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AlignmentMorph class instanceVariableNames: ''! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'ar 11/9/2000 20:51'! newColumn ^ self new listDirection: #topToBottom; hResizing: #spaceFill; extent: 1@1; vResizing: #spaceFill ! ! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'ar 11/9/2000 20:50'! newRow ^ self new listDirection: #leftToRight; hResizing: #spaceFill; vResizing: #spaceFill; extent: 1@1; borderWidth: 0 ! ! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'ar 11/9/2000 20:37'! newSpacer: aColor "Answer a space-filling instance of me of the given color." ^ self new hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 0; extent: 1@1; color: aColor. ! ! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'ar 11/9/2000 20:37'! newVariableTransparentSpacer "Answer a space-filling instance of me of the given color." ^ self new hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 0; extent: 1@1; color: Color transparent ! ! AlignmentMorph subclass: #AlignmentMorphBob1 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Mail Reader'! !AlignmentMorphBob1 commentStamp: '' prior: 0! A quick and easy to space things vertically in absolute or proportional amounts.! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'RAA 7/23/2000 16:42'! addAColumn: aCollectionOfMorphs | col | col _ self inAColumn: aCollectionOfMorphs. self addMorphBack: col. ^col! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'RAA 7/23/2000 16:42'! addARow: aCollectionOfMorphs | row | row _ self inARow: aCollectionOfMorphs. self addMorphBack: row. ^row! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:09'! addARowCentered: aCollectionOfMorphs ^(self addARow: aCollectionOfMorphs) hResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 10/26/2000 20:09'! fancyText: aString ofSize: pointSize color: aColor | answer tm | answer _ self inAColumn: { tm _ TextMorph new beAllFont: ((TextStyle default fontOfSize: pointSize) emphasized: 1); color: aColor; contents: aString }. tm addDropShadow. tm shadowPoint: (5@5) + tm bounds center. tm lock. ^answer ! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:10'! inAColumn: aCollectionOfMorphs | col | col _ AlignmentMorph newColumn color: Color transparent; vResizing: #shrinkWrap; layoutInset: 1; wrapCentering: #center; cellPositioning: #topCenter. aCollectionOfMorphs do: [ :each | col addMorphBack: each]. ^col! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:10'! inARow: aCollectionOfMorphs | row | row _ AlignmentMorph newRow color: Color transparent; vResizing: #shrinkWrap; layoutInset: 1; wrapCentering: #center; cellPositioning: #leftCenter. aCollectionOfMorphs do: [ :each | row addMorphBack: each]. ^row! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:18'! initialize super initialize. self listDirection: #topToBottom. self layoutInset: 0. borderWidth _ 0. self hResizing: #rigid. "... this is very unlikely..." self vResizing: #rigid. ! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:43'! simpleToggleButtonFor: target attribute: attribute help: helpText ^(EtoyUpdatingThreePhaseButtonMorph checkBox) target: target; actionSelector: #toggleChoice:; arguments: {attribute}; getSelector: #getChoice:; setBalloonText: helpText; step ! ! AlignmentMorph subclass: #AllScriptsTool instanceVariableNames: 'presenter showingOnlyActiveScripts showingAllInstances ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting'! !AllScriptsTool methodsFor: 'initialization' stamp: 'sw 1/30/2001 23:21'! initializeFor: aPresenter "Initialize the receiver as a tool which shows, and allows the user to change the status of, all the instantiations of all the user-written scripts in the world" | aButton aRow outerButton | presenter _ aPresenter. showingOnlyActiveScripts _ true. showingAllInstances _ true. self color: Color brown muchLighter muchLighter; wrapCentering: #center; cellPositioning: #topCenter; vResizing: #shrinkWrap; hResizing: #shrinkWrap. self useRoundedCorners. self borderWidth: 4; borderColor: Color brown darker. self addMorph: ScriptingSystem scriptControlButtons. aButton _ SimpleButtonMorph new target: aPresenter; actionSelector: #updateContentsFor:; arguments: (Array with: self); label: 'Update'; color: Color lightYellow; actWhen: #buttonDown. aButton setBalloonText: 'Press here to get the lists of scripts updated'. aRow _ AlignmentMorph newRow listCentering: #center; color: Color transparent. aRow addMorphBack: aButton. outerButton _ AlignmentMorph newRow. outerButton wrapCentering: #center; cellPositioning: #leftCenter. outerButton color: Color transparent. outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap. outerButton addMorph: (aButton _ UpdatingThreePhaseButtonMorph checkBox). aButton target: self; actionSelector: #toggleWhetherShowingOnlyActiveScripts; getSelector: #showingOnlyActiveScripts. outerButton addMorphBack: (StringMorph contents: 'tickers only') lock. outerButton setBalloonText: 'If checked, then only scripts that are paused or ticking will be shown'. aRow addMorphBack: outerButton. outerButton _ AlignmentMorph newRow. outerButton wrapCentering: #center; cellPositioning: #leftCenter. outerButton color: Color transparent. outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap. outerButton addMorph: (aButton _ UpdatingThreePhaseButtonMorph checkBox). aButton target: self; actionSelector: #toggleWhetherShowingAllInstances; getSelector: #showingAllInstances. outerButton addMorphBack: (StringMorph contents: 'all instances') lock. outerButton setBalloonText: 'If checked, then status of all instances will be shown, but if not checked, scripts for only one exemplar of each uniclass will be shown'. aRow addMorphBack: outerButton. self addMorphBack: aRow. aPresenter updateContentsFor: self. self layoutChanged.! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 1/30/2001 23:18'! showingAllInstances "Answer whether the receiver is currently showing controls for all instances of each uniclass." ^ showingAllInstances ! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 1/30/2001 23:18'! showingOnlyActiveScripts "Answer whether the receiver is currently showing only active scripts" ^ showingOnlyActiveScripts ! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 1/31/2001 00:58'! toggleWhetherShowingAllInstances "Toggle whether the receiver is showing all instances or only one exemplar per uniclass" showingAllInstances _ showingAllInstances not. presenter updateContentsFor: self! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 1/31/2001 00:58'! toggleWhetherShowingOnlyActiveScripts "Toggle whether the receiver is showing only active scripts" showingOnlyActiveScripts _ showingOnlyActiveScripts not. presenter updateContentsFor: self! ! !AllScriptsTool methodsFor: 'stepping' stamp: 'sw 1/31/2001 00:31'! step "Update the contents of the tool -- but this is currently not reached because of some drastic performance bug at present" presenter updateContentsFor: self.! ! !AllScriptsTool methodsFor: 'stepping' stamp: 'sw 1/31/2001 23:12'! stepTime "Answer the interval between steps -- in this case a leisurely 4 seconds" ^ 4000! ! !AllScriptsTool methodsFor: 'stepping' stamp: 'sw 1/31/2001 23:12'! wantsSteps "Answer whether the receiver wishes to receive the #step message" ^ true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AllScriptsTool class instanceVariableNames: ''! !AllScriptsTool class methodsFor: 'as yet unclassified' stamp: 'sw 1/30/2001 23:06'! launchAllScriptsToolFor: aPresenter "Launch an AllScriptsTool to view scripts of the given presenter" | aTool | aTool _ self newColumn. aTool initializeFor: aPresenter. self currentHand attachMorph: aTool. aPresenter associatedMorph world startSteppingSubmorphsOf: aTool ! ! AbstractScoreEvent subclass: #AmbientEvent instanceVariableNames: 'morph target selector arguments ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Scores'! !AmbientEvent methodsFor: 'as yet unclassified' stamp: 'di 8/3/1998 21:27'! morph ^ morph! ! !AmbientEvent methodsFor: 'as yet unclassified' stamp: 'di 8/3/1998 20:09'! morph: m morph _ m! ! !AmbientEvent methodsFor: 'as yet unclassified' stamp: 'di 10/21/2000 13:18'! occurAtTime: ticks inScorePlayer: player atIndex: index inEventTrack: track secsPerTick: secsPerTick (target == nil or: [selector == nil]) ifTrue: [morph ifNil: [^ self]. ^ morph encounteredAtTime: ticks inScorePlayer: player atIndex: index inEventTrack: track secsPerTick: secsPerTick]. target perform: selector withArguments: arguments! ! !AmbientEvent methodsFor: 'as yet unclassified' stamp: 'di 8/3/1998 20:08'! target: t selector: s arguments: a target _ t. selector _ s. arguments _ a. ! ! AbstractAnimation subclass: #Animation instanceVariableNames: 'startState endState proportionDone getStartStateFunction getEndStateFunction updateFunction styleFunction ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !Animation commentStamp: '' prior: 0! The Animation class extends the AbstractAnimation class with methods designed for simple (non-composite) animations. ! !Animation methodsFor: 'initialization' stamp: 'jsp 3/9/1999 15:48'! object: anObject update: func getStartState: startFunc getEndState: endFunc style: styleFunc duration: time undoable: canUndo inWonderland: aWonderland "This method initializes the Animation with all the information that it needs run." animatedObject _ anObject. updateFunction _ func. styleFunction _ styleFunc. getStartStateFunction _ startFunc. getEndStateFunction _ endFunc. duration _ time. undoable _ canUndo. myScheduler _ aWonderland getScheduler. myWonderland _ aWonderland. loopCount _ 1. direction _ Forward. state _ Waiting. myScheduler addAnimation: self.! ! !Animation methodsFor: 'management' stamp: 'jsp 3/3/1999 12:02'! update: currentTime "Updates the animation using the current Wonderland time" | newState | (state = Waiting) ifTrue: [self prologue: currentTime]. (state = Running) ifTrue: [ proportionDone _ styleFunction value: (currentTime - startTime) value: duration. newState _ startState interpolateTo: endState at: proportionDone. updateFunction value: newState. (currentTime >= endTime) ifTrue: [ state _ Finished. ]. ]. (state = Finished) ifTrue: [self epilogue: currentTime].! ! Object subclass: #Applescript instanceVariableNames: 'compiledScript source ' classVariableNames: 'ApplescriptGeneric ' poolDictionaries: '' category: 'VMConstruction-Applescript'! !Applescript commentStamp: '' prior: 0! I represent a Squeak front-end to Applescript. My instances represent either compiled scripts, contexts or both. My instances maintain separately the original source code from which I was compiled, and then a CompiledApplescript corresponding to that source code in its "current state." I provide facilities for executing my scripts, alone or in various contexts, as well as for recompiling my script to restore the script to its initial state (if the script bears context information). Examples: To execute some text: Applescript doIt: 'beep 3' To compile code into a script object (for MUCH faster execution of repeated tasks, and to maintain state between execution), and then to execute the code: |aVariable| aVariable _ Applescript on: ' property sam: 0 set sam to sam + 1 beep sam'. aVariable doIt Other. somewhat more general operations Applescript doIt: aString mode: anInteger Applescript doIt: aString in: aContext mode: anInteger s _ Applescript on: aString mode: anInteger s doItMode: anInteger s doItIn: aContext s doItIn: aContext mode: anInteger s recompile Also note the examples in the class side of me. ! !Applescript methodsFor: 'accessing' stamp: 'acg 9/26/1999 01:00'! compiledScript ^compiledScript! ! !Applescript methodsFor: 'accessing' stamp: 'acg 9/27/1999 00:32'! modeDocumentation " 16r0000 kOSAModeNull (kOSANullMode) 16r0001 kOSAModePreventGetSource 16r0002 kOSAModeCompileIntoContext 16r0004 kOSAModeAugmentContext 16r0008 kOSAModeDisplayForHumans kOSAModeNeverInteract kOSAModeCanInteract kOSAModeAlwaysInteract kOSAModeDontReconnect 16r0040 kOSAModeCantSwitchLayer 16r1000 kOSAModeDoRecord 16r4000 kOSAModeDontStoreParent"! ! !Applescript methodsFor: 'accessing' stamp: 'acg 9/26/1999 00:59'! source ^source! ! !Applescript methodsFor: 'testing' stamp: 'acg 9/26/1999 22:40'! hasSource ^self doAsOSAID: [:o | Applescript generic hasSource: o] ! ! !Applescript methodsFor: 'testing' stamp: 'acg 9/26/1999 22:39'! isCompiledScript ^self doAsOSAID: [:o | Applescript generic isCompiledScript: o] ! ! !Applescript methodsFor: 'testing' stamp: 'acg 9/26/1999 22:39'! isScriptContext ^self doAsOSAID: [:o | Applescript generic isScriptContext: o] ! ! !Applescript methodsFor: 'testing' stamp: 'acg 9/26/1999 22:39'! isScriptValue ^self doAsOSAID: [:o | Applescript generic isScriptValue: o] ! ! !Applescript methodsFor: 'testing' stamp: 'acg 9/26/1999 22:39'! timesModified ^self doAsOSAID: [:o | Applescript generic timesModified: o] ! ! !Applescript methodsFor: 'interpreting' stamp: 'acg 9/26/1999 21:13'! asContextDoOSAID: scptOSAID mode: anInteger "Answer a string corresponding to the result of executing preloaded scptOSAID using my compiledScript as the context, and using mode anInteger. As a side-effect, update my script information as necessary. (This routine will not update any stored versions of scptOSAID" ^self doAsOSAID: [:contextOSAID | ApplescriptGeneric executeAndDisplayOSAID: scptOSAID in: contextOSAID mode: anInteger] onErrorDo: [ApplescriptError syntaxErrorFor: (String streamContents: [:aStream | aStream nextPutAll: (ApplescriptGeneric sourceOfOSAID: scptOSAID); cr; cr; nextPutAll: '<=== Source Code of Context ===>'; cr; nextPutAll: source]) withComponent: ApplescriptGeneric]! ! !Applescript methodsFor: 'interpreting' stamp: 'acg 9/26/1999 20:43'! doIt "Answer a string corresponding to the result of executing my script in the default context. mode 0. As a side-effect, update my script information as necessary." ^self doAsOSAID: [:scptOSAID | ApplescriptGeneric executeAndDisplayOSAID: scptOSAID in: (OSAID new) mode: 0]! ! !Applescript methodsFor: 'interpreting' stamp: 'acg 9/26/1999 21:22'! doItIn: aContext "Answer a string corresponding to the result of executing my script in aContext. mode 0. As a side-effect, update my script and the aContext information as necessary." ^self doAsOSAID: [:scptContext | aContext asContextDoOSAID: scptContext mode: 0]! ! !Applescript methodsFor: 'interpreting' stamp: 'acg 9/26/1999 20:43'! doItIn: aContext mode: anInteger "Answer a string corresponding to the result of executing my script in aContext. mode anInteger. As a side-effect, update my script and the aContext information as necessary." ^self doAsOSAID: [:scptContext | aContext asContextDoOSAID: scptContext mode: anInteger]! ! !Applescript methodsFor: 'interpreting' stamp: 'acg 9/26/1999 20:43'! doItMode: anInteger "Answer a string corresponding to the result of executing my script in the default context. mode anInteger. As a side-effect, update my script information as necessary." ^self doAsOSAID: [:scptOSAID | ApplescriptGeneric executeAndDisplayOSAID: scptOSAID in: (OSAID new) mode: anInteger]! ! !Applescript methodsFor: 'recompiling' stamp: 'acg 9/26/1999 20:55'! recompile self on: source! ! !Applescript methodsFor: 'recompiling' stamp: 'acg 9/26/1999 20:55'! recompileMode: anInteger self on: source mode: anInteger! ! !Applescript methodsFor: 'printing' stamp: 'acg 9/26/1999 22:52'! printOn: aStream aStream nextPutAll: 'an Applescript('. self isCompiledScript ifTrue: [aStream nextPutAll: 'script ']. self isScriptContext ifTrue: [aStream nextPutAll: 'context ']. aStream nextPutAll: compiledScript size asString; nextPutAll: ' bytes)' ! ! !Applescript methodsFor: 'private' stamp: 'acg 9/26/1999 20:59'! doAsOSAID: aBlock "Answer the result of performing aBlock on my compiledScript, converted to OSAID form. As a side-effect, update compiledScript to conform to any changes that may have occurred inside the Applescript scripting component." ^self doAsOSAID: aBlock onErrorDo: [ApplescriptError syntaxErrorFor: source withComponent: ApplescriptGeneric]! ! !Applescript methodsFor: 'private' stamp: 'acg 9/27/1999 00:04'! doAsOSAID: aCodeBlock onErrorDo: anErrorBlock "Answer the result of performing aBlock on my compiledScript, converted to OSAID form. As a side-effect, update compiledScript to conform to any changes that may have occurred inside the Applescript scripting component." | anOSAID result | anOSAID _ compiledScript asAEDesc asOSAIDThenDisposeAEDescWith: ApplescriptGeneric. result _ aCodeBlock value: anOSAID. compiledScript _ (anOSAID asCompiledApplescriptWith: ApplescriptGeneric) ifNil: [compiledScript]. anOSAID disposeWith: ApplescriptGeneric. ^result ifNil: [anErrorBlock value]! ! !Applescript methodsFor: 'private' stamp: 'acg 9/26/1999 21:00'! on: aString ^self on: aString mode: 2 onErrorDo: [ApplescriptError syntaxErrorFor: aString withComponent: ApplescriptGeneric]! ! !Applescript methodsFor: 'private' stamp: 'acg 9/26/1999 20:59'! on: aString mode: anInteger ^self on: aString mode: anInteger onErrorDo: [ApplescriptError syntaxErrorFor: aString withComponent: ApplescriptGeneric]! ! !Applescript methodsFor: 'private' stamp: 'acg 9/26/1999 20:47'! on: aString mode: anInteger onErrorDo: aBlock source _ aString. compiledScript _ ApplescriptGeneric compile: aString mode: anInteger. compiledScript ifNil: [^aBlock value]. ^self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Applescript class instanceVariableNames: ''! !Applescript class methodsFor: 'instance creation' stamp: 'acg 9/25/1999 23:36'! on: aString ^super new on: aString! ! !Applescript class methodsFor: 'instance creation' stamp: 'acg 9/26/1999 20:49'! on: aString mode: anInteger ^super new on: aString mode: anInteger! ! !Applescript class methodsFor: 'generic scripting component' stamp: 'acg 9/26/1999 02:19'! doIt: aString ^(self on: aString) doIt! ! !Applescript class methodsFor: 'generic scripting component' stamp: 'acg 9/26/1999 20:50'! doIt: aString in: aContext mode: anInteger ^(self on: aString mode: anInteger) doItIn: aContext mode: anInteger! ! !Applescript class methodsFor: 'generic scripting component' stamp: 'acg 9/26/1999 20:50'! doIt: aString mode: anInteger ^(self on: aString mode: anInteger) doItMode: anInteger! ! !Applescript class methodsFor: 'generic scripting component' stamp: 'acg 9/25/1999 23:43'! generic "Answer an ApplescriptInstance (Applescript Generic Scripting Component) that is guaranteed to be active from startUp, but is not (at present) guaranteed to be identical across startups. Additional instances can be created for multi-threaded applications by using ApplescriptInstance." ^ApplescriptGeneric ifNil: [ApplescriptGeneric _ ApplescriptInstance new]! ! !Applescript class methodsFor: 'generic scripting component' stamp: 'acg 9/25/1999 23:28'! lastError ^self generic lastError! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! beep: anInteger "Beep n times" ^self doIt: 'beep ', anInteger asString! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! browse: anUrl "Open Microsoft's Web Browser to a page" ^self doIt: 'tell application "Internet Explorer" activate openURL "', anUrl, '" end tell'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! distill Applescript doIt: ' set prompt to "Select a file to convert to .pdf format" set myFile to (choose file with prompt prompt of type "TEXT") tell application "Acrobatª Distillerª 3.02" activate open myFile end tell'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! mandatoryDemo "A mandatory first script" ^self doIt: '3 + 4'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/27/1999 08:12'! playQT4Movie "Demonstrate Access to Quicktime" ^Applescript doIt: '-- Play QuickTime File -- ©1999 Sal Soghoian, Apple Computer property source_folder : "" property container_kind : "folder" property reset_string : "Pick New Source Folder" -- Check the version of QuickTime copy my gestaltVersion_info("qtim", 8) to {QT_version, QT_string} if the QT_version is less than "0400" then display dialog "This script requires QuickTime 4.0 or higher." &  return & return & "The currently installed version is: " &  QT_string buttons {"Cancel"} default button 1 end if -- Check the version of the OS copy my gestaltVersion_info("sysv", 4) to {system_version, system_string} if the system_version is less than "0850" then display dialog "This script requires Mac OS 8.5 or higher." &  return & return & "The currently installed version is: " &  system_string buttons {"Cancel"} default button 1 end if -- check to see if source folder exists try if the source_folder is "" then error set the source_folder to alias (source_folder as text) on error set the source_folder to choose_source_folder() if the result is false then return "user canceled" end try -- set the target folder to the source folder set the target_folder to the source_folder repeat -- search the target folder for folders or QT files try tell application "Finder" set the item_list to (the name of every item of  the target_folder whose  (creator type is "TVOD") or  (kind is the container_kind)) as list set the item_list to my ASCII_Sort(item_list) set the beginning of the item_list to "Pick New Source Folder" end tell on error beep display dialog "The chosen folder contains no folders or QuickTime files." buttons {"Show Me", "Cancel"} default button 2 tell application "Finder" activate open the target_folder end tell return "no items" end try -- prompt the user to pick a folder or file set the chosen_item to choose from list the item_list with prompt  "Pick an item:" if the chosen_item is false then return set the chosen_item to the chosen_item as string if the chosen_item is reset_string then set the source_folder to choose_source_folder() if the result is false then return "user canceled" set the target_folder to the source_folder else -- Check the user''s choice to determine whether it''s a file or folder tell application "Finder" if the kind of item chosen_item of the target_folder is the container_kind then -- The user picked a folder. Set the new target folder and repeat the process. set the target_folder to folder chosen_item of the the target_folder else -- The user picked a file. Get the path to the file and exit the repeat. set the chosen_item to (item chosen_item of the target_folder) as alias exit repeat end if end tell end if end repeat -- Find out if the user wants to play the item in the front or back. set play_in_background to true display dialog "Play the media in the foreground or background?" buttons {"Cancel", "Foreground", "Background"} default button 3 if the button returned of the result is "Foreground" then set play_in_background to false -- Quit the QuickTime Player if it is open tell application "Finder" if (the creator type of every process) contains Çclass TVODÈ then  tell application "QuickTime Player" to quit end tell -- Convert the alias to a URL format string set this_file to "file:///" & my filepath_to_URL(the chosen_item, true, false) -- Tell the QuickTime Player to open the file. -- NOTE: to autoplay, Check the Auto-Play preference in the General setting in the QuickTime Player. tell application "QuickTime Player" if play_in_background is false then activate open location this_file end tell on gestaltVersion_info(gestalt_code, string_length) try tell application "Finder" to  copy my NumToHex((computer gestalt_code),  string_length) to {a, b, c, d} set the numeric_version to {a, b, c, d} as string if a is "0" then set a to "" set the version_string to (a & b & "." & c & "." & d) as string return {numeric_version, version_string} on error return {"", "unknown"} end try end gestaltVersion_info on NumToHex(hexData, stringLength) set hexString to {} repeat with i from stringLength to 1 by -1 set hexString to ((hexData mod 16) as string) & hexString set hexData to hexData div 16 end repeat return (hexString as string) end NumToHex on choose_source_folder() try set the source_folder to choose folder with prompt  "Pick a folder containing Quicktime content:" return the source_folder on error return false end try end choose_source_folder -- this sub-routine converts a filepath to an encoded URL -- My Disk:My Folder:My File -- My%20Disk/My%20Folder/My%20File on filepath_to_URL(this_file, encode_URL_A, encode_URL_B) set this_file to this_file as text set AppleScript''s text item delimiters to ":" set the path_segments to every text item of this_file repeat with i from 1 to the count of the path_segments set this_segment to item i of the path_segments set item i of the path_segments to  my encode_text(this_segment, encode_URL_A, encode_URL_B) end repeat set AppleScript''s text item delimiters to "/" set this_file to the path_segments as string set AppleScript''s text item delimiters to "" return this_file end filepath_to_URL -- this sub-routine is used to encode text on encode_text(this_text, encode_URL_A, encode_URL_B) set the standard_characters to  "abcdefghijklmnopqrstuvwxyz0123456789" set the URL_A_chars to "$+!!''/?;&@=#%><{}[]\"~`^\\|*" set the URL_B_chars to ".-_:" set the acceptable_characters to the standard_characters if encode_URL_A is false then  set the acceptable_characters to  the acceptable_characters & the URL_A_chars if encode_URL_B is false then  set the acceptable_characters to  the acceptable_characters & the URL_B_chars set the encoded_text to "" repeat with this_char in this_text if this_char is in the acceptable_characters then set the encoded_text to  (the encoded_text & this_char) else set the encoded_text to  (the encoded_text & encode_char(this_char)) as string end if end repeat return the encoded_text end encode_text -- this sub-routine is used to encode a character on encode_char(this_char) set the ASCII_num to (the ASCII number this_char) set the hex_list to  {"0", "1", "2", "3", "4", "5", "6", "7", "8",  "9", "A", "B", "C", "D", "E", "F"} set x to item ((ASCII_num div 16) + 1) of the hex_list set y to item ((ASCII_num mod 16) + 1) of the hex_list return ("%" & x & y) as string end encode_char -- This routine sorts a list of strings passed to it on ASCII_Sort(my_list) set the index_list to {} set the sorted_list to {} repeat (the number of items in my_list) times set the low_item to "" repeat with i from 1 to (number of items in my_list) if i is not in the index_list then set this_item to item i of my_list as text if the low_item is "" then set the low_item to this_item set the low_item_index to i else if this_item comes before the low_item then set the low_item to this_item set the low_item_index to i end if end if end repeat set the end of sorted_list to the low_item set the end of the index_list to the low_item_index end repeat return the sorted_list end ASCII_Sort'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! say: aString "Speak the string" ^self doIt: 'say "', aString, '"' ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! selectFile "Harness Apple's select file dialog for Squeak" ^self doIt: '(choose file with prompt "Hi guys!!" of type "TEXT") as string' ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:16'! selectFolder "Harness Apple's select Folder dialog for Squeak" ^self doIt: '(choose folder with prompt "Hi guys!!") as string'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/21/1999 21:33'! silly Applescript say: 'please prez a button for me'. Applescript sillyButtons. Applescript say: 'thank you for pressing the button' ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:17'! sillyButtons "A silly Apple GUI demo" ^self doIt: ' display dialog "The Mouse that Roars!!" ', 'buttons {"One", "Two", "Three"} default button "One"' ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:17'! sillyDialog "A silly Apple GUI demo" self doIt: ' display dialog "Enter a number between 1 and 10." default answer "" set userValue to {text returned of result} as real if (userValue < 1) or (userValue > 10) then display dialog "That Value is out of range." buttons {"OK"} default button 1 else display dialog "Thanks for playing." buttons {"OK"} default button 1 end if' ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:17'! sillyList "A silly Apple GUI demo" ^Applescript doIt: 'choose from list {"dogs", "cats", "lions", "pick the mouse!!"}', 'with prompt "hi there"', 'default items {"dogs"}', 'OK button name "DoIt!!"', 'cancel button name "Chicken!!"'! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:17'! sleep ^self doIt: ' tell application "Finder" sleep end tell' "Applescript sleep" ! ! !Applescript class methodsFor: 'sample scripts' stamp: 'acg 9/26/1999 02:17'! with: voiceString say: contentString "Speak the string" ^self doIt: 'say "', contentString, '" using "', voiceString, '"' ! ! !Applescript class methodsFor: 'initialize-release' stamp: 'acg 9/27/1999 08:35'! initialize Smalltalk addToStartUpList: self after: nil. ApplescriptGeneric _ nil. Applescript generic! ! !Applescript class methodsFor: 'initialize-release' stamp: 'acg 9/25/1999 23:29'! reopen ^self generic reopen! ! !Applescript class methodsFor: 'initialize-release' stamp: 'ar 2/1/2000 15:42'! startUp Smalltalk platformName = 'Mac OS' "Can be *really* annoying otherwise" ifTrue:[^self reopen]! ! StringHolder subclass: #ApplescriptError instanceVariableNames: 'errorMessage from to ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !ApplescriptError commentStamp: '' prior: 0! I represent a syntax or execution error report for errors encountered when processing Applescripts. As a StringHolder, the string to be viewed is generally the method code or expression containing the error.! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 02:10'! canDiscardEdits ^true! ! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 01:26'! code: codeString errorMessage: errString from: fromInteger to: toInteger contents _ codeString. from _ fromInteger. to _ toInteger. errorMessage _ errString! ! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 01:25'! contentsSelection ^from to: to! ! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 01:27'! list ^Array with: errorMessage! ! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 01:27'! listIndex ^1! ! !ApplescriptError methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 22:10'! listMenu: aMenu ^aMenu labels: '' lines: #() selections: #() ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ApplescriptError class instanceVariableNames: ''! !ApplescriptError class methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 21:00'! buildMVCViewOn: aSyntaxError "Answer an MVC view on the given SyntaxError." | topView aListView aCodeView | topView _ StandardSystemView new model: aSyntaxError; label: 'Applescript Error'; minimumSize: 380@220. aListView _ PluggableListView on: aSyntaxError list: #list selected: #listIndex changeSelected: nil menu: #listMenu:. aListView window: (0@0 extent: 380@20). topView addSubView: aListView. aCodeView _ PluggableTextView on: aSyntaxError text: #contents accept: nil readSelection: #contentsSelection menu: #codePaneMenu:shifted:. aCodeView window: (0@0 extent: 380@200). topView addSubView: aCodeView below: aListView. ^ topView ! ! !ApplescriptError class methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 21:01'! buildMorphicViewOn: aSyntaxError "Answer an Morphic view on the given SyntaxError." | window | window _ (SystemWindow labelled: 'Applescript Error') model: aSyntaxError. window addMorph: (PluggableListMorph on: aSyntaxError list: #list selected: #listIndex changeSelected: nil menu: #listMenu:) frame: (0@0 corner: 1@0.15). window addMorph: (PluggableTextMorph on: aSyntaxError text: #contents accept: nil readSelection: #contentsSelection menu: #codePaneMenu:shifted:) frame: (0@0.15 corner: 1@1). ^ window openInWorldExtent: 380@220! ! !ApplescriptError class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:12'! open: aSyntaxError "Answer a standard system view whose model is an instance of me." | topView | "Simulation guard" Smalltalk isMorphic ifTrue: [self buildMorphicViewOn: aSyntaxError. CurrentProjectRefactoring newProcessIfUI: Processor activeProcess. ^ Processor activeProcess suspend]. topView _ self buildMVCViewOn: aSyntaxError. topView controller openNoTerminateDisplayAt: Display extent // 2. Cursor normal show. Processor activeProcess suspend! ! !ApplescriptError class methodsFor: 'as yet unclassified' stamp: 'acg 9/26/1999 01:38'! syntaxErrorFor: aString withComponent: anApplescriptInstance |range | range _ anApplescriptInstance lastErrorCodeRange. self open: (super new code: aString errorMessage: anApplescriptInstance lastErrorString from: range first to: range last)! ! ComponentInstance variableWordSubclass: #ApplescriptInstance instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !ApplescriptInstance commentStamp: '' prior: 0! I represent an Applescript Scripting Component, derived from the Component Manager. For more information about Scripting Components, see Inside Macintosh: Interapplication Communications, at: http://developer.apple.com/techpubs/mac/IAC/IAC-2.html. Essentially, I represent a record comprising a one-word handle to the scripting component. That handle is passed as a matter of course to almost every important Applescript call. Accordingly, I am also the repository for most of the primitives for the Applescript/Squeak interface.! ]style[(195 54 285)f1,f1Rhttp://developer.apple.com/techpubs/mac/IAC/IAC-2.html;,f1! !ApplescriptInstance methodsFor: 'testing' stamp: 'acg 9/26/1999 22:43'! hasSource: anOSAID | result | result _ IntegerArray new: 1. (self primOSAGetScriptInfo: anOSAID type: (DescType of: 'gsrc') to: result) isZero ifFalse: [^nil]. ^(result at: 1) > 0! ! !ApplescriptInstance methodsFor: 'testing' stamp: 'acg 9/26/1999 22:43'! isCompiledScript: anOSAID | result | result _ IntegerArray new: 1. (self primOSAGetScriptInfo: anOSAID type: (DescType of: 'cscr') to: result) isZero ifFalse: [^nil]. ^(result at: 1) > 0! ! !ApplescriptInstance methodsFor: 'testing' stamp: 'acg 9/26/1999 22:43'! isScriptContext: anOSAID | result | result _ IntegerArray new: 1. (self primOSAGetScriptInfo: anOSAID type: (DescType of: 'cntx') to: result) isZero ifFalse: [^nil]. ^(result at: 1) > 0! ! !ApplescriptInstance methodsFor: 'testing' stamp: 'acg 9/26/1999 22:43'! isScriptValue: anOSAID | result | result _ IntegerArray new: 1. (self primOSAGetScriptInfo: anOSAID type: (DescType of: 'valu') to: result) isZero ifFalse: [^nil]. ^(result at: 1) > 0! ! !ApplescriptInstance methodsFor: 'testing' stamp: 'acg 9/26/1999 22:42'! timesModified: anOSAID | result | result _ IntegerArray new: 1. (self primOSAGetScriptInfo: anOSAID type: (DescType of: 'modi') to: result) isZero ifFalse: [^nil]. ^result at: 1! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'sma 3/15/2000 21:46'! compile: aString ^ self compile: aString mode: 0! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/27/1999 00:05'! compile: aString mode: anInteger | sourceAEDesc objectOSAID objectAEDesc | sourceAEDesc _ AEDesc textTypeOn: aString. (objectOSAID _ self compileAndDisposeAEDesc: sourceAEDesc mode: anInteger) ifNil: [^nil]. (objectAEDesc _ self storeAndDisposeOSAID: objectOSAID type: 'scpt' mode: anInteger) ifNil: [^nil]. ^objectAEDesc asCompiledApplescriptThenDispose ! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/27/1999 00:05'! do: aString "Answer text result of compiling script in null context" ^self doScript: aString in: OSAID new mode: 0! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/25/1999 14:14'! do: aString in: contextOSAID mode: anInteger "Answer text result of executing Applescript aString in context contexOSAID in mode: anInteger" | source object result | source _ AEDesc textTypeOn: aString. object _ AEDesc new. result _ self primOSADoScript: source in: contextOSAID mode: anInteger resultType: (DescType of: 'TEXT') to: object. source dispose. result isZero ifFalse: [^nil]. ^object asStringThenDispose! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/25/1999 14:14'! doCompiledScript: aCompiledApplescriptData in: contextOSAID mode: anInteger "Answer text result of executing Applescript aString in context contexOSAID in mode: anInteger" ^self valueOf: aCompiledApplescriptData in: contextOSAID mode: anInteger! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/25/1999 14:14'! doScript: aString "Answer text result of compiling script in null context" ^self do: aString! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/25/1999 14:15'! doScript: aString in: contextOSAID mode: anInteger "Answer text result of executing Applescript aString in context contexOSAID in mode: anInteger" ^self do: aString in: contextOSAID mode: anInteger! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/27/1999 00:06'! scriptingName "Answer the name of my generic scripting component" |aeDesc result | aeDesc _ AEDesc new. result _ self primOSAScriptingComponentNameTo: aeDesc. result isZero ifFalse: [^nil]. ^aeDesc asStringThenDispose. ! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/26/1999 21:08'! sourceOfOSAID: anOSAID | anAEDesc result | anAEDesc _ AEDesc new. result _ self primOSAGetSource: anOSAID type: 'TEXT' to: anAEDesc. anOSAID disposeWith: self. result isZero ifFalse: [^'']. ^anAEDesc asStringThenDispose ! ! !ApplescriptInstance methodsFor: 'interpreting' stamp: 'acg 9/25/1999 21:53'! valueOf: aCompiledApplescript in: contextOSAID mode: anInteger "Answer text result of executing Applescript aString in context contexOSAID in mode: anInteger" | sourceAEDesc sourceOSAID objectOSAID objectAEDesc | sourceAEDesc _ AEDesc scptTypeOn: aCompiledApplescript. sourceOSAID _ self loadAndDispose: sourceAEDesc mode: anInteger. sourceOSAID ifNil: [^nil]. objectOSAID _ self executeAndDispose: sourceOSAID in: contextOSAID mode: anInteger. objectOSAID ifNil: [^nil]. objectAEDesc _ self displayAndDispose: objectOSAID as: 'TEXT' mode: anInteger. objectAEDesc ifNil: [^nil]. ^objectAEDesc asStringThenDispose! ! !ApplescriptInstance methodsFor: 'error handling' stamp: 'acg 9/25/1999 23:27'! lastBriefErrorString "Answer the brief error message for the last error" | aeDesc | aeDesc _ AEDesc new. Applescript generic primOSAScriptError: (DescType of: 'errb') type: (DescType of: 'TEXT') to: aeDesc. ^aeDesc asStringThenDispose! ! !ApplescriptInstance methodsFor: 'error handling' stamp: 'acg 9/24/1999 00:06'! lastError |range| range _ self lastErrorCodeRange. ^String streamContents: [:aStream | aStream nextPutAll: 'Error #'; nextPutAll: self lastErrorNumber asString; nextPutAll: ': '; nextPutAll: self lastErrorString; nextPutAll: ' (code '; nextPutAll: range first asString; nextPutAll: ' to '; nextPutAll: range last asString; nextPutAll: ').']! ! !ApplescriptInstance methodsFor: 'error handling' stamp: 'acg 9/25/1999 23:27'! lastErrorCodeRange "Answer the brief error message for the last error" | aeDesc recordDesc data from to | aeDesc _ AEDesc new. recordDesc _ AEDesc new. Applescript generic primOSAScriptError: (DescType of: 'erng') type: (DescType of: 'erng') to: aeDesc. aeDesc primAECoerceDesc: (DescType of: 'reco') to: recordDesc. aeDesc dispose. data _ ByteArray new: 2. recordDesc primAEGetKeyPtr: (DescType of: 'srcs') type: (DescType of: 'shor') actual: (DescType of: 'shor') to: data. from _ data shortAt: 1 bigEndian: true. recordDesc primAEGetKeyPtr: (DescType of: 'srce') type: (DescType of: 'shor') actual: (DescType of: 'shor') to: data. to _ data shortAt: 1 bigEndian: true. recordDesc dispose. ^ (from + 1) to: (to + 1) ! ! !ApplescriptInstance methodsFor: 'error handling' stamp: 'acg 9/25/1999 23:27'! lastErrorNumber "Answer the error code number of the last error" | aeDesc | aeDesc _ AEDesc new. Applescript generic primOSAScriptError: (DescType of: 'errn') type: (DescType of: 'shor') to: aeDesc. ^aeDesc asShortThenDispose ! ! !ApplescriptInstance methodsFor: 'error handling' stamp: 'acg 9/25/1999 23:27'! lastErrorString "Answer the error message for the last error" | aeDesc | aeDesc _ AEDesc new. Applescript generic primOSAScriptError: (DescType of: 'errs') type: (DescType of: 'TEXT') to: aeDesc. ^aeDesc asStringThenDispose! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/25/1999 13:55'! compileAndDisposeAEDesc: sourceAEDesc mode: anInteger | objectOSAID result | objectOSAID _ OSAID new. result _ self primOSACompile: sourceAEDesc mode: anInteger to: objectOSAID. sourceAEDesc dispose. result isZero ifFalse: [^nil]. ^objectOSAID ! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 00:14'! displayAndDisposeOSAID: anOSAID as: aString mode: anInteger | anAEDesc result | anOSAID isEmpty ifTrue: [^AEDesc textTypeOn: '']. anAEDesc _ AEDesc new. result _ self primOSADisplay: anOSAID as: (DescType of: aString) mode: anInteger to: anAEDesc. anOSAID disposeWith: self. result isZero ifFalse: [^nil]. ^anAEDesc! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 20:29'! executeAndDisplayOSAID: anOSAID in: contextOSAID mode: anInteger | resultOSAID resultAEDesc | resultOSAID _ (self executeOSAID: anOSAID in: contextOSAID mode: anInteger) ifNil: [^nil]. resultAEDesc _ (self displayAndDisposeOSAID: resultOSAID as: 'TEXT' mode: anInteger) ifNil: [^nil]. ^resultAEDesc asStringThenDispose ! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/25/1999 09:57'! executeAndDisposeOSAID: sourceOSAID in: contextOSAID mode: anInteger | objectOSAID result | objectOSAID _ OSAID new. result _ self primOSAExecute: sourceOSAID in: contextOSAID mode: anInteger to: objectOSAID. sourceOSAID disposeWith: self. result isZero ifFalse: [^nil]. ^objectOSAID! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 00:03'! executeOSAID: sourceOSAID in: contextOSAID mode: anInteger | objectOSAID result | objectOSAID _ OSAID new. result _ self primOSAExecute: sourceOSAID in: contextOSAID mode: anInteger to: objectOSAID. result isZero ifFalse: [^nil]. ^objectOSAID! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/25/1999 09:57'! loadAndDisposeAEDesc: anAEDesc mode: anInteger | anOSAID result | anOSAID _ OSAID new. result _ self primOSALoad: anAEDesc mode: anInteger to: anOSAID. anAEDesc dispose. result isZero ifFalse: [^nil]. ^anOSAID! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 02:59'! makeContextAndDiposeOSAID: anOSAID | result contextOSAID contextAEDesc | contextOSAID _ OSAID new. result _ self primOSAMakeContext: (AEDesc nullType) parent: anOSAID to: contextOSAID. anOSAID dispose. result isZero ifFalse: [^nil]. contextAEDesc _ self storeAndDisposeOSAID: contextOSAID type: 'scpt' mode: 0. contextAEDesc ifNil: [^nil]. ^ contextAEDesc asCompiledApplescriptThenDispose! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 02:49'! makeContextAndDisposeOSAID: anOSAID | result contextAEDesc | contextAEDesc _ AEDesc new. result _ self primOSAMakeContext: (AEDesc nullType) parent: anOSAID to: contextAEDesc. result isZero ifFalse: [^nil]. anOSAID disposeWith: self. ^ contextAEDesc asCompiledApplescriptThenDispose! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/26/1999 02:58'! makeContextOSAID: anOSAID | result contextOSAID contextAEDesc | contextOSAID _ OSAID new. result _ self primOSAMakeContext: (AEDesc nullType) parent: anOSAID to: contextOSAID. result isZero ifFalse: [^nil]. contextAEDesc _ self storeAndDisposeOSAID: contextOSAID type: 'scpt' mode: 0. contextAEDesc ifNil: [^nil]. ^ contextAEDesc asCompiledApplescriptThenDispose! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/25/1999 14:17'! storeAndDisposeOSAID: anOSAID type: aString mode: anInteger | theAEDesc result | theAEDesc _ AEDesc new. result _ self primOSAStore: anOSAID resultType: (DescType of: aString) mode: 0 to: (theAEDesc). anOSAID disposeWith: self. result isZero ifFalse: [^nil]. ^theAEDesc ! ! !ApplescriptInstance methodsFor: 'intermediate operations' stamp: 'acg 9/25/1999 16:41'! storeOSAID: anOSAID type: aString mode: anInteger | theAEDesc result | theAEDesc _ AEDesc new. result _ self primOSAStore: anOSAID resultType: (DescType of: aString) mode: 0 to: (theAEDesc). result isZero ifFalse: [^nil]. ^theAEDesc ! ! !ApplescriptInstance methodsFor: 'printing' stamp: 'acg 9/26/1999 00:52'! printOn: aStream aStream nextPutAll: 'an '; nextPutAll: self species asString; nextPutAll: '('; nextPutAll: self scriptingName; nextPutAll: ')'! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 21:23'! initialize super type: 'osa ' subtype: 'scpt'! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/22/1999 03:14'! primOSACompile: anAEDesc mode: anInteger to: anOSAID ^TestOSAPlugin doPrimitive: 'primOSACompile:mode:to:' withArguments: {anAEDesc. anInteger. anOSAID}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 20:57'! primOSADisplay: source as: type mode: mode to: result ^TestOSAPlugin doPrimitive: 'primOSADisplay:as:mode:to:' withArguments: {source. type. mode. result}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 20:57'! primOSADispose: anOSAID ^TestOSAPlugin doPrimitive: 'primOSADispose:' withArguments: {anOSAID}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 20:57'! primOSADoScript: source in: context mode: mode resultType: type to: result ^TestOSAPlugin doPrimitive: 'primOSADoScript:in:mode:resultType:to:' withArguments: {source. context. mode. type. result}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 20:57'! primOSAExecute: script in: context mode: mode to: result ^TestOSAPlugin doPrimitive: 'primOSAExecute:in:mode:to:' withArguments: { script. context. mode. result }! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/26/1999 22:24'! primOSAGetScriptInfo: aScriptID type: aDescType to: resultData ^TestOSAPlugin doPrimitive: 'primOSAGetScriptInfo:type:to:' withArguments: {aScriptID. aDescType. resultData}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/25/1999 17:27'! primOSAGetSource: aScriptID type: aDescType to: resultData ^TestOSAPlugin doPrimitive: 'primOSAGetSource:type:to:' withArguments: {aScriptID. aDescType. resultData}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/22/1999 03:17'! primOSALoad: anAEDesc mode: anInteger to: anOSAID ^TestOSAPlugin doPrimitive: 'primOSALoad:mode:to:' withArguments: {anAEDesc. anInteger. anOSAID}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/25/1999 22:56'! primOSAMakeContext: name parent: parent to: result ^TestOSAPlugin doPrimitive: 'primOSAMakeContext:parent:to:' withArguments: {name. parent. result}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/23/1999 20:43'! primOSAScriptError: anOSType type: aDescType to: anAEDesc ^TestOSAPlugin doPrimitive: 'primOSAScriptError:type:to:' withArguments: {anOSType. aDescType. anAEDesc}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 20:57'! primOSAScriptingComponentNameTo: anAEDesc ^TestOSAPlugin doPrimitive: 'primOSAScriptingComponentNameTo:' withArguments: {anAEDesc}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/22/1999 04:22'! primOSAStore: a resultType: b mode: c to: d ^TestOSAPlugin doPrimitive: 'primOSAStore:resultType:mode:to:' withArguments: {a. b. c. d}! ! !ApplescriptInstance methodsFor: 'private' stamp: 'acg 9/21/1999 21:25'! reopen ^super type: 'osa ' subtype: 'scpt'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ApplescriptInstance class instanceVariableNames: ''! !ApplescriptInstance class methodsFor: 'as yet unclassified' stamp: 'acg 9/21/1999 21:22'! new ^super new initialize! ! ObjectSocket subclass: #ArbitraryObjectSocket instanceVariableNames: 'encodingOfLastEncodedObject lastEncodedObject ' classVariableNames: '' poolDictionaries: '' category: 'Network-ObjectSocket'! !ArbitraryObjectSocket commentStamp: '' prior: 0! A network connection that passes objects instead of bytes. The objects are encoded with SmartRefStreams. Of course, one can send Arrays of Strings if one is unsure of what exactly SmartRefStream's are going to do. ! !ArbitraryObjectSocket methodsFor: 'private' stamp: 'ls 4/25/2000 19:18'! encodeObject: object into: buffer startingAt: startIndex "encode the given object into the given buffer" | encoded | encoded := self smartRefStreamEncode: object. buffer putInteger32: encoded size at: startIndex. buffer replaceFrom: startIndex+4 to: startIndex+4+(encoded size)-1 with: encoded. ! ! !ArbitraryObjectSocket methodsFor: 'private' stamp: 'ls 4/25/2000 19:19'! nextObjectLength "read the next object length from inBuf. Returns nil if less than 4 bytes are available in inBuf" self inBufSize < 4 ifTrue: [ ^nil ]. ^inBuf getInteger32: inBufIndex! ! !ArbitraryObjectSocket methodsFor: 'private' stamp: 'ls 4/25/2000 19:34'! processInput "recieve some data" | inObjectData | "read as much data as possible" [ self isConnected and: [ socket dataAvailable ] ] whileTrue: [ self addToInBuf: socket getData. ]. "decode as many objects as possible" [self nextObjectLength ~~ nil and: [ self nextObjectLength <= (self inBufSize + 4) ]] whileTrue: [ "a new object has arrived" inObjectData _ inBuf copyFrom: (inBufIndex + 4) to: (inBufIndex + 3 + self nextObjectLength). inBufIndex := inBufIndex + 4 + self nextObjectLength. inObjects addLast: (RWBinaryOrTextStream with: inObjectData) reset fileInObjectAndCode ]. self shrinkInBuf.! ! !ArbitraryObjectSocket methodsFor: 'private' stamp: 'ls 4/25/2000 19:33'! smartRefStreamEncode: anObject | encodingStream | "encode an object using SmartRefStream" anObject == lastEncodedObject ifTrue: [ ^encodingOfLastEncodedObject ]. encodingStream := RWBinaryOrTextStream on: ''. encodingStream reset. (SmartRefStream on: encodingStream) nextPut: anObject. lastEncodedObject := anObject. encodingOfLastEncodedObject := encodingStream contents. ^encodingOfLastEncodedObject! ! !ArbitraryObjectSocket methodsFor: 'private' stamp: 'ls 4/25/2000 19:36'! spaceToEncode: anObject "return the number of characters needed to encode the given object" ^ 4 + (self smartRefStreamEncode: anObject) size! ! Path subclass: #Arc instanceVariableNames: 'quadrant radius center ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Paths'! !Arc commentStamp: '' prior: 0! Arcs are an unusual implementation of splines due to Ted Kaehler. Imagine two lines that meet at a corner. Now imagine two moving points; one moves from the corner to the end on one line, the other moves from the end of the other line in to the corner. Now imagine a series of lines drawn between those moving points at each step along the way (they form a sort of spider web pattern). By connecting segments of the intersecting lines, a smooth curve is achieved that is tangent to both of the original lines. Voila.! !Arc methodsFor: 'accessing'! center "Answer the point at the center of the receiver." ^center! ! !Arc methodsFor: 'accessing'! center: aPoint "Set aPoint to be the receiver's center." center _ aPoint! ! !Arc methodsFor: 'accessing'! center: aPoint radius: anInteger "The receiver is defined by a point at the center and a radius. The quadrant is not reset." center _ aPoint. radius _ anInteger! ! !Arc methodsFor: 'accessing'! center: aPoint radius: anInteger quadrant: section "Set the receiver's quadrant to be the argument, section. The size of the receiver is defined by the center and its radius." center _ aPoint. radius _ anInteger. quadrant _ section! ! !Arc methodsFor: 'accessing'! quadrant "Answer the part of the circle represented by the receiver." ^quadrant! ! !Arc methodsFor: 'accessing'! quadrant: section "Set the part of the circle represented by the receiver to be the argument, section." quadrant _ section! ! !Arc methodsFor: 'accessing'! radius "Answer the receiver's radius." ^radius! ! !Arc methodsFor: 'accessing'! radius: anInteger "Set the receiver's radius to be the argument, anInteger." radius _ anInteger! ! !Arc methodsFor: 'display box access'! computeBoundingBox | aRectangle aPoint | aRectangle _ center - radius + form offset extent: form extent + (radius * 2) asPoint. aPoint _ center + form extent. quadrant = 1 ifTrue: [^ aRectangle encompass: center x @ aPoint y]. quadrant = 2 ifTrue: [^ aRectangle encompass: aPoint x @ aPoint y]. quadrant = 3 ifTrue: [^ aRectangle encompass: aPoint x @ center y]. quadrant = 4 ifTrue: [^ aRectangle encompass: center x @ center y]! ! !Arc methodsFor: 'displaying'! displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm | nSegments line angle sin cos xn yn xn1 yn1 | nSegments _ 12.0. line _ Line new. line form: self form. angle _ 90.0 / nSegments. sin _ (angle * (2 * Float pi / 360.0)) sin. cos _ (angle * (2 * Float pi / 360.0)) cos. quadrant = 1 ifTrue: [xn _ radius asFloat. yn _ 0.0]. quadrant = 2 ifTrue: [xn _ 0.0. yn _ 0.0 - radius asFloat]. quadrant = 3 ifTrue: [xn _ 0.0 - radius asFloat. yn _ 0.0]. quadrant = 4 ifTrue: [xn _ 0.0. yn _ radius asFloat]. nSegments asInteger timesRepeat: [xn1 _ xn * cos + (yn * sin). yn1 _ yn * cos - (xn * sin). line beginPoint: center + (xn asInteger @ yn asInteger). line endPoint: center + (xn1 asInteger @ yn1 asInteger). line displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm. xn _ xn1. yn _ yn1]! ! !Arc methodsFor: 'displaying'! displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm | newArc tempCenter | newArc _ Arc new. tempCenter _ aTransformation applyTo: self center. newArc center: tempCenter x asInteger @ tempCenter y asInteger. newArc quadrant: self quadrant. newArc radius: (self radius * aTransformation scale x) asInteger. newArc form: self form. newArc displayOn: aDisplayMedium at: 0 @ 0 clippingBox: clipRect rule: anInteger fillColor: aForm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Arc class instanceVariableNames: ''! !Arc class methodsFor: 'examples'! example "Click the button somewhere on the screen. The designated point will be the center of an Arc with radius 50 in the 4th quadrant." | anArc aForm | aForm _ Form extent: 1 @ 30. "make a long thin Form for display" aForm fillBlack. "turn it black" anArc _ Arc new. anArc form: aForm. "set the form for display" anArc radius: 50.0. anArc center: Sensor waitButton. anArc quadrant: 4. anArc displayOn: Display. Sensor waitButton "Arc example"! ! ArrayedCollection variableSubclass: #Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !Array commentStamp: '' prior: 0! I present an ArrayedCollection whose elements are objects.! !Array methodsFor: 'comparing'! hashMappedBy: map "Answer what my hash would be if oops changed according to map." self size = 0 ifTrue: [^self hash]. ^(self first hashMappedBy: map) + (self last hashMappedBy: map)! ! !Array methodsFor: 'converting' stamp: 'sma 5/12/2000 17:32'! asArray "Answer with the receiver itself." ^ self! ! !Array methodsFor: 'converting' stamp: 'di 3/28/1999 10:22'! elementsExchangeIdentityWith: otherArray "This primitive performs a bulk mutation, causing all pointers to the elements of this array to be replaced by pointers to the corresponding elements of otherArray. At the same time, all pointers to the elements of otherArray are replaced by pointers to the corresponding elements of this array. The identityHashes remain with the pointers rather than with the objects so that objects in hashed structures should still be properly indexed after the mutation." self primitiveFailed! ! !Array methodsFor: 'converting' stamp: 'di 3/28/1999 10:23'! elementsForwardIdentityTo: otherArray "This primitive performs a bulk mutation, causing all pointers to the elements of this array to be replaced by pointers to the corresponding elements of otherArray. The identityHashes remain with the pointers rather than with the objects so that the objects in this array should still be properly indexed in any existing hashed structures after the mutation." self primitiveFailed! ! !Array methodsFor: 'converting'! evalStrings "Allows you to construct literal arrays. #(true false nil '5@6' 'Set new' '''text string''') evalStrings gives an array with true, false, nil, a Point, a Set, and a String instead of just a bunch of Symbols" | it | ^ self collect: [:each | it _ each. each == #true ifTrue: [it _ true]. each == #false ifTrue: [it _ false]. each == #nil ifTrue: [it _ nil]. each class == String ifTrue: [ it _ Compiler evaluate: each]. each class == Array ifTrue: [it _ it evalStrings]. it]! ! !Array methodsFor: 'printing' stamp: 'sma 5/12/2000 14:11'! isLiteral ^ self allSatisfy: [:each | each isLiteral]! ! !Array methodsFor: 'printing' stamp: 'sma 6/1/2000 09:39'! printOn: aStream aStream nextPut: $#. self printElementsOn: aStream! ! !Array methodsFor: 'printing'! storeOn: aStream "Use the literal form if possible." self isLiteral ifTrue: [aStream nextPut: $#; nextPut: $(. self do: [:element | element printOn: aStream. aStream space]. aStream nextPut: $)] ifFalse: [super storeOn: aStream]! ! !Array methodsFor: 'private' stamp: 'sma 6/3/2000 21:39'! hasLiteral: literal "Answer true if literal is identical to any literal in this array, even if imbedded in further array structure. This method is only intended for private use by CompiledMethod hasLiteralSymbol:" | lit | 1 to: self size do: [:index | (lit _ self at: index) == literal ifTrue: [^ true]. (lit class == Array and: [lit hasLiteral: literal]) ifTrue: [^ true]]. ^ false! ! !Array methodsFor: 'private' stamp: 'di 8/15/97 09:55'! hasLiteralSuchThat: litBlock "Answer true if litBlock returns true for any literal in this array, even if imbedded in further array structure. This method is only intended for private use by CompiledMethod hasLiteralSuchThat:" | lit | 1 to: self size do: [:index | lit _ self at: index. (litBlock value: lit) ifTrue: [^ true]. (lit class == Array and: [lit hasLiteralSuchThat: litBlock]) ifTrue: [^ true]]. ^false! ! !Array methodsFor: 'private'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." super replaceFrom: start to: stop with: replacement startingAt: repStart! ! !Array methodsFor: 'filter streaming' stamp: 'MPW 1/1/1901 01:42'! byteEncode:aStream aStream writeArray:self. ! ! !Array methodsFor: 'filter streaming' stamp: 'MPW 1/1/1901 01:55'! storeOnStream:aStream self isLiteral ifTrue: [super storeOnStream:aStream] ifFalse:[aStream writeCollection:self]. ! ! !Array methodsFor: 'file in/out' stamp: 'tk 9/28/2000 15:35'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. If I am one of two shared global arrays, write a proxy instead." self == (TextConstants at: #DefaultTabsArray) ifTrue: [ dp _ DiskProxy global: #TextConstants selector: #at: args: #(DefaultTabsArray). refStrm replace: self with: dp. ^ dp]. self == (TextConstants at: #DefaultMarginTabsArray) ifTrue: [ dp _ DiskProxy global: #TextConstants selector: #at: args: #(DefaultMarginTabsArray). refStrm replace: self with: dp. ^ dp]. ^ super objectForDataStream: refStrm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Array class instanceVariableNames: ''! !Array class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:12'! ccg: cg emitLoadFor: aString from: anInteger on: aStream cg emitLoad: aString asIntPtrFrom: anInteger on: aStream! ! !Array class methodsFor: 'plugin generation' stamp: 'acg 9/19/1999 13:10'! ccg: cg prolog: aBlock expr: aString index: anInteger ^cg ccgLoad: aBlock expr: aString asIntPtrFrom: anInteger andThen: (cg ccgValBlock: 'isIndexable')! ! !Array class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:12'! ccgDeclareCForVar: aSymbolOrString ^'int *', aSymbolOrString! ! !Array class methodsFor: 'brace support' stamp: 'di 11/18/1999 22:53'! braceStream: nElements "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." ^ WriteStream basicNew braceArray: (self new: nElements) ! ! !Array class methodsFor: 'brace support' stamp: 'di 11/19/1999 08:16'! braceWith: a "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." | array | array _ self new: 1. array at: 1 put: a. ^ array! ! !Array class methodsFor: 'brace support' stamp: 'di 11/19/1999 08:15'! braceWith: a with: b "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." | array | array _ self new: 2. array at: 1 put: a. array at: 2 put: b. ^ array! ! !Array class methodsFor: 'brace support' stamp: 'di 11/19/1999 08:17'! braceWith: a with: b with: c "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." | array | array _ self new: 3. array at: 1 put: a. array at: 2 put: b. array at: 3 put: c. ^ array! ! !Array class methodsFor: 'brace support' stamp: 'di 11/19/1999 08:17'! braceWith: a with: b with: c with: d "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." | array | array _ self new: 4. array at: 1 put: a. array at: 2 put: b. array at: 3 put: c. array at: 4 put: d. ^ array! ! !Array class methodsFor: 'brace support' stamp: 'di 11/19/1999 08:16'! braceWithNone "This method is used in compilation of brace constructs. It MUST NOT be deleted or altered." ^ self new: 0! ! ArrayedCollection subclass: #Array2D instanceVariableNames: 'width contents ' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !Array2D commentStamp: '' prior: 0! A simple 2D-Array implementation. Neither storing nor sorting (otherwise inherited from ArrayedCollection) will work. Neither comparing nor most accessing mehods inherited from Sequenceable collection will work. Actually, it's a bad idea to inherit this class from collection at all!!! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:16'! at: x at: y "Answer the element at index x,y." ^ contents at: (self indexX: x y: y)! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:20'! at: x at: y add: value "Add value (using #+) to the existing element at index x,y." | index | index _ self indexX: x y: y. ^ contents at: index put: (contents at: index) + value! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:20'! at: x at: y put: value "Store value at index x,y and answer it." ^ contents at: (self indexX: x y: y) put: value! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:20'! atAllPut: anObject "Put anObject at every one of the receiver's indices." contents atAllPut: anObject! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:22'! extent "Answer the receiver's dimensions as point." ^ self width @ self height! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:21'! height "Answer the receiver's second dimension." ^ contents size // width! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:22'! size ^ contents size! ! !Array2D methodsFor: 'accessing' stamp: 'sma 4/22/2000 18:22'! width "Answer the receiver's first dimension." ^ width! ! !Array2D methodsFor: 'accessing rows/columns' stamp: 'sma 4/22/2000 18:27'! atCol: x "Answer a whole column." | column | column _ contents class new: self height. 1 to: self height do: [:index | column at: index put: (self at: x at: index)]. ^ column! ! !Array2D methodsFor: 'accessing rows/columns' stamp: 'sma 4/22/2000 18:30'! atCol: x put: aCollection "Put in a whole column." aCollection size = self height ifFalse: [self error: 'wrong column size']. aCollection doWithIndex: [:value :y | self at: x at: y put: value]. ^ aCollection! ! !Array2D methodsFor: 'accessing rows/columns' stamp: 'sma 4/22/2000 18:27'! atRow: y "Answer a whole row." (y < 1 or: [y > self height]) ifTrue: [self errorSubscriptBounds: y]. ^ contents copyFrom: y - 1 * width + 1 to: y * width! ! !Array2D methodsFor: 'accessing rows/columns' stamp: 'sma 4/22/2000 18:30'! atRow: y put: aCollection "Put in a whole row." aCollection size = self width ifFalse: [self error: 'wrong row size']. aCollection doWithIndex: [:value :x | self at: x at: y put: value]. ^ aCollection! ! !Array2D methodsFor: 'converting' stamp: 'sma 4/22/2000 18:38'! asArray ^ contents copy! ! !Array2D methodsFor: 'copying' stamp: 'sma 4/22/2000 18:37'! copy ^ super copy setContents: contents copy! ! !Array2D methodsFor: 'enumeration' stamp: 'sma 4/22/2000 18:14'! do: aBlock "Iterate with X varying most quickly. 6/20/96 tk" contents do: aBlock! ! !Array2D methodsFor: 'enumeration' stamp: 'sma 4/22/2000 18:39'! rowAndColumnValuesDo: aBlock 1 to: self width do: [:col | 1 to: self height do: [:row | aBlock value: row value: col value: (self at: row at: col)]]! ! !Array2D methodsFor: 'enumeration' stamp: 'sma 4/22/2000 18:39'! rowsAndColumnsDo: aBlock 1 to: self width do: [:col | 1 to: self height do: [:row | aBlock value: row value: col]]! ! !Array2D methodsFor: 'private' stamp: 'sma 4/22/2000 18:24'! extent: extent fromArray: anArray "Load receiver up from a 1-D array. X varies most quickly. 6/20/96 tk" extent x * extent y = anArray size ifFalse: [^ self error: 'dimensions don''t match']. width _ extent x. contents _ anArray! ! !Array2D methodsFor: 'private' stamp: 'sma 4/22/2000 18:16'! indexX: x y: y (x < 1 or: [x > width]) ifTrue: [self errorSubscriptBounds: x]. ^ y - 1 * width + x! ! !Array2D methodsFor: 'private' stamp: 'sma 4/22/2000 18:37'! setContents: aCollection contents _ aCollection! ! !Array2D methodsFor: 'private' stamp: 'sma 4/22/2000 18:13'! width: x height: y type: collectionClass "Set the number of elements in the first and second dimension. collectionClass can be Array or String or ByteArray." contents == nil ifFalse: [self error: 'No runtime size change yet']. "later move all the elements to the new sized array" width _ x. contents _ collectionClass new: x * y! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Array2D class instanceVariableNames: ''! !Array2D class methodsFor: 'instance creation' stamp: 'sma 4/22/2000 18:40'! extent: aPoint ^ self width: aPoint x height: aPoint y! ! !Array2D class methodsFor: 'instance creation'! new "Override ArrayedCollection. 6/20/96 tk" ^ self basicNew! ! !Array2D class methodsFor: 'instance creation' stamp: 'sma 4/22/2000 18:11'! new: size self error: 'Use >>self width: x height: y<< instead'! ! !Array2D class methodsFor: 'instance creation' stamp: 'sma 4/22/2000 18:10'! width: width height: height ^ self basicNew width: width height: height type: Array! ! !Array2D class methodsFor: 'instance creation' stamp: 'sma 4/22/2000 18:10'! width: width height: height type: collectionClass ^ self basicNew width: width height: height type: collectionClass! ! SequenceableCollection subclass: #ArrayedCollection instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Abstract'! !ArrayedCollection commentStamp: '' prior: 0! I am an abstract collection of elements with a fixed range of integers (from 1 to n>=1) as external keys.! !ArrayedCollection methodsFor: 'accessing' stamp: 'sma 5/12/2000 11:36'! size "Answer how many elements the receiver contains." ^ self basicSize! ! !ArrayedCollection methodsFor: 'adding' stamp: 'sma 5/12/2000 14:09'! add: newObject self shouldNotImplement! ! !ArrayedCollection methodsFor: 'filter streaming' stamp: 'sma 5/12/2000 14:20'! flattenOnStream: aStream aStream writeArrayedCollection: self! ! !ArrayedCollection methodsFor: 'printing'! storeOn: aStream aStream nextPutAll: '(('. aStream nextPutAll: self class name. aStream nextPutAll: ' new: '. aStream store: self size. aStream nextPut: $). (self storeElementsFrom: 1 to: self size on: aStream) ifFalse: [aStream nextPutAll: '; yourself']. aStream nextPut: $)! ! !ArrayedCollection methodsFor: 'printing' stamp: 'RAA 6/23/2000 08:24'! writeOnGZIPByteStream: aStream aStream nextPutAllBytes: self! ! !ArrayedCollection methodsFor: 'private'! defaultElement ^nil! ! !ArrayedCollection methodsFor: 'private'! storeElementsFrom: firstIndex to: lastIndex on: aStream | noneYet defaultElement arrayElement | noneYet _ true. defaultElement _ self defaultElement. firstIndex to: lastIndex do: [:index | arrayElement _ self at: index. arrayElement = defaultElement ifFalse: [noneYet ifTrue: [noneYet _ false] ifFalse: [aStream nextPut: $;]. aStream nextPutAll: ' at: '. aStream store: index. aStream nextPutAll: ' put: '. aStream store: arrayElement]]. ^noneYet! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 18:18'! asSortedArray self isSorted ifTrue: [^ self asArray]. ^ super asSortedArray! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 6/1/2000 11:57'! isSorted "Return true if the receiver is sorted by the given criterion. Optimization for isSortedBy: [:a :b | a <= b]." | lastElm elm | self isEmpty ifTrue: [^ true]. lastElm _ self first. 2 to: self size do: [:index | elm _ self at: index. lastElm <= elm ifFalse: [^ false]. lastElm _ elm]. ^ true! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 6/1/2000 11:58'! isSortedBy: aBlock "Return true if the receiver is sorted by the given criterion." | lastElm elm | self isEmpty ifTrue: [^ true]. lastElm _ self first. 2 to: self size do: [:index | elm _ self at: index. (aBlock value: lastElm value: elm) ifFalse: [^ false]. lastElm _ elm]. ^ true! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 14:28'! mergeFirst: first middle: middle last: last into: dst by: aBlock "Private. Merge the sorted ranges [first..middle] and [middle+1..last] of the receiver into the range [first..last] of dst." | i1 i2 val1 val2 out | i1 _ first. i2 _ middle + 1. val1 _ self at: i1. val2 _ self at: i2. out _ first - 1. "will be pre-incremented" "select 'lower' half of the elements based on comparator" [(i1 <= middle) and: [i2 <= last]] whileTrue: [(aBlock value: val1 value: val2) ifTrue: [dst at: (out _ out + 1) put: val1. val1 _ self at: (i1 _ i1 + 1)] ifFalse: [dst at: (out _ out + 1) put: val2. i2 _ i2 + 1. i2 <= last ifTrue: [val2 _ self at: i2]]]. "copy the remaining elements" i1 <= middle ifTrue: [dst replaceFrom: out + 1 to: last with: self startingAt: i1] ifFalse: [dst replaceFrom: out + 1 to: last with: self startingAt: i2]! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 14:25'! mergeSortFrom: startIndex to: stopIndex by: aBlock "Sort the given range of indices using the mergesort algorithm. Mergesort is a worst-case O(N log N) sorting algorithm that usually does only half as many comparisons as heapsort or quicksort." "Details: recursively split the range to be sorted into two halves, mergesort each half, then merge the two halves together. An extra copy of the data is used as temporary storage and successive merge phases copy data back and forth between the receiver and this copy. The recursion is set up so that the final merge is performed into the receiver, resulting in the receiver being completely sorted." self size <= 1 ifTrue: [^ self]. "nothing to do" startIndex = stopIndex ifTrue: [^ self]. self assert: [startIndex >= 1 and: [startIndex < stopIndex]]. "bad start index" self assert: [stopIndex <= self size]. "bad stop index" self mergeSortFrom: startIndex to: stopIndex src: self clone dst: self by: aBlock! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 14:26'! mergeSortFrom: first to: last src: src dst: dst by: aBlock "Private. Split the range to be sorted in half, sort each half, and merge the two half-ranges into dst." | middle | first = last ifTrue: [^ self]. middle _ (first + last) // 2. self mergeSortFrom: first to: middle src: dst dst: src by: aBlock. self mergeSortFrom: middle + 1 to: last src: dst dst: src by: aBlock. src mergeFirst: first middle: middle last: last into: dst by: aBlock! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 14:22'! sort "Sort this array into ascending order using the '<=' operator." self sort: [:a :b | a <= b]! ! !ArrayedCollection methodsFor: 'sorting' stamp: 'sma 5/12/2000 14:21'! sort: aSortBlock "Sort this array using aSortBlock. The block should take two arguments and return true if the first element should preceed the second one." self mergeSortFrom: 1 to: self size by: aSortBlock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ArrayedCollection class instanceVariableNames: ''! !ArrayedCollection class methodsFor: 'instance creation'! new "Answer a new instance of me, with size = 0." ^self new: 0! ! !ArrayedCollection class methodsFor: 'instance creation'! new: size withAll: value "Answer an instance of me, with number of elements equal to size, each of which refers to the argument, value." ^(self new: size) atAllPut: value! ! !ArrayedCollection class methodsFor: 'instance creation'! newFrom: aCollection "Answer an instance of me containing the same elements as aCollection." | newArray | newArray _ self new: aCollection size. 1 to: aCollection size do: [:i | newArray at: i put: (aCollection at: i)]. ^ newArray " Array newFrom: {1. 2. 3} {1. 2. 3} as: Array {1. 2. 3} as: ByteArray {$c. $h. $r} as: String {$c. $h. $r} as: Text "! ! !ArrayedCollection class methodsFor: 'instance creation'! with: anObject "Answer a new instance of me, containing only anObject." | newCollection | newCollection _ self new: 1. newCollection at: 1 put: anObject. ^newCollection! ! !ArrayedCollection class methodsFor: 'instance creation'! with: firstObject with: secondObject "Answer a new instance of me, containing firstObject and secondObject." | newCollection | newCollection _ self new: 2. newCollection at: 1 put: firstObject. newCollection at: 2 put: secondObject. ^newCollection! ! !ArrayedCollection class methodsFor: 'instance creation'! with: firstObject with: secondObject with: thirdObject "Answer a new instance of me, containing only the three arguments as elements." | newCollection | newCollection _ self new: 3. newCollection at: 1 put: firstObject. newCollection at: 2 put: secondObject. newCollection at: 3 put: thirdObject. ^newCollection! ! !ArrayedCollection class methodsFor: 'instance creation'! with: firstObject with: secondObject with: thirdObject with: fourthObject "Answer a new instance of me, containing only the three arguments as elements." | newCollection | newCollection _ self new: 4. newCollection at: 1 put: firstObject. newCollection at: 2 put: secondObject. newCollection at: 3 put: thirdObject. newCollection at: 4 put: fourthObject. ^newCollection! ! !ArrayedCollection class methodsFor: 'instance creation'! with: firstObject with: secondObject with: thirdObject with: fourthObject with: fifthObject "Answer a new instance of me, containing only the five arguments as elements." | newCollection | newCollection _ self new: 5. newCollection at: 1 put: firstObject. newCollection at: 2 put: secondObject. newCollection at: 3 put: thirdObject. newCollection at: 4 put: fourthObject. newCollection at: 5 put: fifthObject. ^newCollection! ! !ArrayedCollection class methodsFor: 'instance creation' stamp: 'sw 10/24/1998 22:22'! with: firstObject with: secondObject with: thirdObject with: fourthObject with: fifthObject with: sixthObject "Answer a new instance of me, containing only the 6 arguments as elements." | newCollection | newCollection _ self new: 6. newCollection at: 1 put: firstObject. newCollection at: 2 put: secondObject. newCollection at: 3 put: thirdObject. newCollection at: 4 put: fourthObject. newCollection at: 5 put: fifthObject. newCollection at: 6 put: sixthObject. ^ newCollection! ! !ArrayedCollection class methodsFor: 'instance creation' stamp: 'sma 5/12/2000 17:37'! withAll: aCollection "Create a new collection containing all the elements from aCollection." ^ (self new: aCollection size) replaceFrom: 1 to: aCollection size with: aCollection! ! !ArrayedCollection class methodsFor: 'plugin generation' stamp: 'acg 9/20/1999 10:03'! ccg: cg generateCoerceToOopFrom: aNode on: aStream self instSize > 0 ifTrue: [self error: 'cannot auto-coerce arrays with named instance variables']. cg generateCoerceToObjectFromPtr: aNode on: aStream! ! !ArrayedCollection class methodsFor: 'plugin generation' stamp: 'acg 10/5/1999 06:18'! ccg: cg generateCoerceToValueFrom: aNode on: aStream cg generateCoerceToPtr: (self ccgDeclareCForVar: '') fromObject: aNode on: aStream! ! Halt subclass: #AssertionFailure instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Extensions'! ParseNode subclass: #AssignmentNode instanceVariableNames: 'variable value ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !AssignmentNode commentStamp: '' prior: 0! AssignmentNode comment: 'I represent a (var_expr) construct.'! !AssignmentNode methodsFor: 'initialize-release'! toDoIncrement: var var = variable ifFalse: [^ nil]. (value isMemberOf: MessageNode) ifTrue: [^ value toDoIncrement: var] ifFalse: [^ nil]! ! !AssignmentNode methodsFor: 'initialize-release'! value ^ value! ! !AssignmentNode methodsFor: 'initialize-release'! variable: aVariable value: expression variable _ aVariable. value _ expression! ! !AssignmentNode methodsFor: 'initialize-release' stamp: 'di 3/22/1999 12:00'! variable: aVariable value: expression from: encoder (aVariable isMemberOf: MessageAsTempNode) ifTrue: ["Case of remote temp vars" ^ aVariable store: expression from: encoder]. variable _ aVariable. value _ expression! ! !AssignmentNode methodsFor: 'code generation'! emitForEffect: stack on: aStream value emitForValue: stack on: aStream. variable emitStorePop: stack on: aStream! ! !AssignmentNode methodsFor: 'code generation'! emitForValue: stack on: aStream value emitForValue: stack on: aStream. variable emitStore: stack on: aStream! ! !AssignmentNode methodsFor: 'code generation'! sizeForEffect: encoder ^(value sizeForValue: encoder) + (variable sizeForStorePop: encoder)! ! !AssignmentNode methodsFor: 'code generation'! sizeForValue: encoder ^(value sizeForValue: encoder) + (variable sizeForStore: encoder)! ! !AssignmentNode methodsFor: 'printing' stamp: 'di 6/7/2000 10:32'! printOn: aStream indent: level aStream dialect = #SQ00 ifTrue: [aStream withStyleFor: #setOrReturn do: [aStream nextPutAll: 'Set ']. variable printOn: aStream indent: level. aStream withStyleFor: #setOrReturn do: [aStream nextPutAll: ' to ']. value printOn: aStream indent: level + 2] ifFalse: [variable printOn: aStream indent: level. aStream nextPutAll: ' _ '. value printOn: aStream indent: level + 2]! ! !AssignmentNode methodsFor: 'printing' stamp: 'di 4/25/2000 13:52'! printOn: aStream indent: level precedence: p (aStream dialect = #SQ00 ifTrue: [p < 3] ifFalse: [p < 4]) ifTrue: [aStream nextPutAll: '('. self printOn: aStream indent: level. aStream nextPutAll: ')'] ifFalse: [self printOn: aStream indent: level]! ! !AssignmentNode methodsFor: 'equation translation'! variable ^variable! ! !AssignmentNode methodsFor: 'C translation' stamp: 'hg 8/14/2000 15:33'! asTranslatorNode ^TAssignmentNode new setVariable: variable asTranslatorNode expression: value asTranslatorNode; comment: comment! ! !AssignmentNode methodsFor: 'tiles' stamp: 'di 11/10/2000 17:55'! asMorphicSyntaxIn: parent | row | row _ parent addRow: #assignment on: self. variable asMorphicSyntaxIn: row. row addToken: ' _ ' type: #assignment on: self. value asMorphicSyntaxIn: row. ^row ! ! !AssignmentNode methodsFor: 'tiles' stamp: 'RAA 8/15/1999 16:31'! explanation ^'The value of ',value explanation,' is being stored in ',variable explanation ! ! TileMorph subclass: #AssignmentTileMorph instanceVariableNames: 'assignmentRoot assignmentSuffix dataType ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 1/17/1999 21:10'! computeOperatorOrExpression | aSuffix | operatorOrExpression _ (assignmentRoot, assignmentSuffix) asSymbol. aSuffix _ ScriptingSystem wordingForAssignmentSuffix: assignmentSuffix. operatorReadoutString _ assignmentRoot, ' ', aSuffix. self line1: operatorReadoutString. self addArrowsIfAppropriate! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 11/17/97 14:36'! initialize super initialize. type _ #operator. assignmentSuffix _ ':'! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 1/17/1999 21:10'! setAssignmentSuffix: aString assignmentSuffix _ aString. self computeOperatorOrExpression. type _ #operator. self line1: (ScriptingSystem wordingForOperator: operatorOrExpression). self addArrowsIfAppropriate; updateLiteralLabel! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 2/16/98 01:12'! setRoot: aString dataType: aSymbol assignmentRoot _ aString. assignmentSuffix _ ':'. dataType _ aSymbol. self updateLiteralLabel! ! !AssignmentTileMorph methodsFor: 'arrow' stamp: 'sw 10/13/2000 12:58'! addArrowsIfAppropriate "If the receiver's slot is of an appropriate type, add arrows to the tile. The list of types wanting arrows is at this point simply hard-coded." (#(number sound boolean menu buttonPhase) includes: dataType) ifTrue: [self addArrows]! ! !AssignmentTileMorph methodsFor: 'arrow' stamp: 'sw 12/12/97 01:24'! arrowAction: delta | index aList | owner ifNil: [^ self]. operatorOrExpression ifNotNil: [aList _ #(: Incr: Decr: Mult:). index _ aList indexOf: assignmentSuffix asSymbol. index > 0 ifTrue: [self setAssignmentSuffix: (aList atWrap: index + delta). self acceptNewLiteral]]! ! !AssignmentTileMorph methodsFor: 'code generation' stamp: 'sw 1/25/2001 12:16'! storeCodeOn: aStream indent: tabCount "Generate code for an assignment statement. The code generated looks presentable in the case of simple assignment, though the code generated for the increment/decrement/multiply cases is still the same old assignGetter... sort for now" assignmentSuffix = ':' ifTrue: "Simple assignment, don't need existing value" [aStream nextPutAll: (ScriptingSystem setterSelectorFor: assignmentRoot). aStream space] ifFalse: "Assignments that require that old values be retrieved" [aStream nextPutAll: ' assign', (assignmentSuffix copyWithout: $:), 'Getter: #'. aStream nextPutAll: (ScriptingSystem getterSelectorFor: assignmentRoot). aStream nextPutAll: ' setter: #'. aStream nextPutAll: (ScriptingSystem setterSelectorFor: assignmentRoot). aStream nextPutAll: ' amt: ']! ! !AssignmentTileMorph methodsFor: 'display' stamp: 'sw 1/31/98 00:42'! updateLiteralLabel self computeOperatorOrExpression. super updateLiteralLabel! ! LookupKey subclass: #Association instanceVariableNames: 'value ' classVariableNames: '' poolDictionaries: '' category: 'Collections-Support'! !Association commentStamp: '' prior: 0! I represent a pair of associated objects--a key and a value. My instances can serve as entries in a dictionary.! !Association methodsFor: 'accessing'! key: aKey value: anObject "Store the arguments as the variables of the receiver." key _ aKey. value _ anObject! ! !Association methodsFor: 'accessing'! value "Answer the value of the receiver." ^value! ! !Association methodsFor: 'accessing'! value: anObject "Store the argument, anObject, as the value of the receiver." value _ anObject! ! !Association methodsFor: 'printing'! printOn: aStream super printOn: aStream. aStream nextPutAll: '->'. value printOn: aStream! ! !Association methodsFor: 'printing' stamp: 'MPW 1/4/1901 08:31'! propertyListOn: aStream aStream write:key; print:'='; write:value. ! ! !Association methodsFor: 'printing'! storeOn: aStream "Store in the format (key->value)" aStream nextPut: $(. key storeOn: aStream. aStream nextPutAll: '->'. value storeOn: aStream. aStream nextPut: $)! ! !Association methodsFor: 'filter streaming' stamp: 'MPW 1/1/1901 20:53'! byteEncode: aStream aStream writeAssocation:self.! ! !Association methodsFor: 'objects from disk' stamp: 'tk 10/3/2000 13:03'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. If I am a known global, write a proxy that will hook up with the same resource in the destination system." ^ (Smalltalk associationAt: key ifAbsent: [nil]) == self ifTrue: [dp _ DiskProxy global: #Smalltalk selector: #associationOrUndeclaredAt: args: (Array with: key). refStrm replace: self with: dp. dp] ifFalse: [self]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Association class instanceVariableNames: ''! !Association class methodsFor: 'instance creation'! key: newKey value: newValue "Answer an instance of me with the arguments as the key and value of the association." ^(super key: newKey) value: newValue! ! Object subclass: #AsyncFile instanceVariableNames: 'name writeable semaphore fileHandle ' classVariableNames: 'Busy Error ' poolDictionaries: '' category: 'System-Files'! !AsyncFile commentStamp: '' prior: 0! An asynchronous file allows simple file read and write operations to be performed in parallel with other processing. This is useful in multimedia applications that need to stream large amounts of sound or image data from or to a file while doing other work. ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primClose: fHandle "Close this file. Do nothing if primitive fails." ! ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primOpen: fileName forWrite: openForWrite semaIndex: semaIndex "Open a file of the given name, and return a handle for that file. Answer the receiver if the primitive succeeds, nil otherwise." ^ nil ! ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primReadResult: fHandle intoBuffer: buffer at: startIndex count: count "Copy the result of the last read operation into the given buffer starting at the given index. The buffer may be any sort of bytes or words object, excluding CompiledMethods. Answer the number of bytes read. A negative result means: -1 the last operation is still in progress -2 the last operation encountered an error" self primitiveFailed ! ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primReadStart: fHandle fPosition: fPosition count: count "Start a read operation of count bytes starting at the given offset in the given file." self error: 'READ THE COMMENT FOR THIS METHOD.' "NOTE: This method will fail if there is insufficient C heap to allocate an internal buffer of the required size (the value of count). If you are trying to read a movie file, then the buffer size will be height*width*2 bytes. Each Squeak image retains a value to be used for this allocation, and it it initially set to 0. If you are wish to play a 640x480 movie, you need room for a buffer of 640*480*2 = 614400 bytes. You should execute the following... Smalltalk extraVMMemory 2555000. Then save-and-quit, restart, and try to open the movie file again. If you are using Async files in another way, find out the value of count when this failure occurs (call it NNNN), and instead of the above, execute... Smalltalk extraVMMemory: Smalltalk extraVMMemory + NNNN then save-and-quit, restart, and try again. " ! ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primWriteResult: fHandle "Answer the number of bytes written. A negative result means: -1 the last operation is still in progress -2 the last operation encountered an error" self primitiveFailed ! ! !AsyncFile methodsFor: 'primitives' stamp: 'ar 2/2/2001 15:09'! primWriteStart: fHandle fPosition: fPosition fromBuffer: buffer at: startIndex count: count "Start a write operation of count bytes starting at the given index in the given buffer. The buffer may be any sort of bytes or words object, excluding CompiledMethods. The contents of the buffer are copied into an internal buffer immediately, so the buffer can be reused after the write operation has been started. Fail if there is insufficient C heap to allocate an internal buffer of the requested size." writeable ifFalse: [^ self error: 'attempt to write a file opened read-only']. self primitiveFailed ! ! !AsyncFile methodsFor: 'as yet unclassified'! close fileHandle ifNil: [^ self]. "already closed" self primClose: fileHandle. Smalltalk unregisterExternalObject: semaphore. semaphore _ nil. fileHandle _ nil. ! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'di 7/6/1998 10:58'! fileHandle ^ fileHandle! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'jm 6/25/1998 07:54'! open: fullFileName forWrite: aBoolean "Open a file of the given name, and return a handle for that file. Answer the receiver if the primitive succeeds, nil otherwise. If openForWrite is true, then: if there is no existing file with this name, then create one else open the existing file in read-write mode otherwise: if there is an existing file with this name, then open it read-only else answer nil." "Note: if an exisiting file is opened for writing, it is NOT truncated. If truncation is desired, the file should be deleted before being opened as an asynchronous file." "Note: On some platforms (e.g., Mac), a file can only have one writer at a time." | semaIndex | name _ fullFileName. writeable _ aBoolean. semaphore _ Semaphore new. semaIndex _ Smalltalk registerExternalObject: semaphore. fileHandle _ self primOpen: name forWrite: writeable semaIndex: semaIndex. fileHandle ifNil: [ Smalltalk unregisterExternalObject: semaphore. semaphore _ nil. ^ nil]. ! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'jm 6/25/1998 08:28'! readByteCount: byteCount fromFilePosition: fPosition onCompletionDo: aBlock "Start a read operation to read byteCount's from the given position in this file. and fork a process to await its completion. When the operation completes, evaluate the given block. Note that, since the completion block may run asynchronous, the client may need to use a SharedQueue or a semaphore for synchronization." | buffer n | buffer _ String new: byteCount. self primReadStart: fileHandle fPosition: fPosition count: byteCount. "here's the process that awaits the results:" [ [ semaphore wait. n _ self primReadResult: fileHandle intoBuffer: buffer at: 1 count: byteCount. n = Busy. ] whileTrue. "loop while busy in case the semaphore had excess signals" n = Error ifTrue: [^ self error: 'asynchronous read operation failed']. aBlock value: buffer. ] forkAt: Processor userInterruptPriority. ! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'jm 6/25/1998 10:07'! test: byteCount fileName: fileName "AsyncFile new test: 10000 fileName: 'testData'" | buf1 buf2 bytesWritten bytesRead | buf1 _ String new: byteCount withAll: $x. buf2 _ String new: byteCount. self open: fileName forWrite: true. self primWriteStart: fileHandle fPosition: 0 fromBuffer: buf1 at: 1 count: byteCount. semaphore wait. bytesWritten _ self primWriteResult: fileHandle. self close. self open: fileName forWrite: false. self primReadStart: fileHandle fPosition: 0 count: byteCount. semaphore wait. bytesRead _ self primReadResult: fileHandle intoBuffer: buf2 at: 1 count: byteCount. self close. buf1 = buf2 ifFalse: [self error: 'buffers do not match']. ^ 'wrote ', bytesWritten printString, ' bytes; ', 'read ', bytesRead printString, ' bytes' ! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'di 7/6/1998 10:58'! waitForCompletion semaphore wait! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'jm 6/25/1998 17:28'! writeBuffer: buffer atFilePosition: fPosition onCompletionDo: aBlock "Start an operation to write the contents of the buffer at given position in this file, and fork a process to await its completion. When the write completes, evaluate the given block. Note that, since the completion block runs asynchronously, the client may need to use a SharedQueue or a semaphore for synchronization." | n | self primWriteStart: fileHandle fPosition: fPosition fromBuffer: buffer at: 1 count: buffer size. "here's the process that awaits the results:" [ [ semaphore wait. n _ self primWriteResult: fileHandle. n = Busy. ] whileTrue. "loop while busy in case the semaphore had excess signals" n = Error ifTrue: [^ self error: 'asynchronous write operation failed']. n = buffer size ifFalse: [^ self error: 'did not write the entire buffer']. aBlock value. ] forkAt: Processor userInterruptPriority. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AsyncFile class instanceVariableNames: ''! !AsyncFile class methodsFor: 'class initialization' stamp: 'jm 6/25/1998 17:33'! initialize "AsyncFile initialize" "Possible abnormal I/O completion results." Busy _ -1. Error _ -2. ! ! TestInterpreterPlugin subclass: #AsynchFilePlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !AsynchFilePlugin commentStamp: '' prior: 0! Implements the asynchronous file primtives! !AsynchFilePlugin methodsFor: 'initialize-release' stamp: 'ar 5/12/2000 16:52'! initialiseModule "Initialise the module" self export: true. ^self cCode: 'asyncFileInit()' inSmalltalk:[true]! ! !AsynchFilePlugin methodsFor: 'initialize-release' stamp: 'ar 5/12/2000 16:54'! shutdownModule "Initialise the module" self export: true. ^self cCode: 'asyncFileShutdown()' inSmalltalk:[true]! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'TPR 2/7/2000 13:01'! asyncFileValueOf: oop "Return a pointer to the first byte of the async file record within the given Smalltalk bytes object, or nil if oop is not an async file record." self returnTypeC: 'AsyncFile *'. interpreterProxy success: ((interpreterProxy isIntegerObject: oop) not and: [(interpreterProxy isBytes: oop) and: [(interpreterProxy slotSizeOf: oop) = (self cCode: 'sizeof(AsyncFile)')]]). interpreterProxy failed ifTrue: [^ nil]. ^ self cCode: '(AsyncFile *) (oop + 4)' ! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'TPR 3/21/2000 17:08'! primitiveAsyncFileClose: fh | f | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileClose' parameters: #(Oop ). f _ self asyncFileValueOf: fh. self asyncFileClose: f! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'TPR 3/21/2000 17:10'! primitiveAsyncFileOpen: fileName forWrite: writeFlag semaIndex: semaIndex | fileNameSize fOop f | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileOpen' parameters: #(String Boolean SmallInteger ). fileNameSize _ interpreterProxy slotSizeOf: (fileName asOop: String). fOop _ interpreterProxy instantiateClass: interpreterProxy classByteArray indexableSize: (self cCode: 'sizeof(AsyncFile)'). f _ self asyncFileValueOf: fOop. interpreterProxy failed ifFalse: [self cCode: 'asyncFileOpen(f, (int)fileName, fileNameSize, writeFlag, semaIndex)']. ^ fOop! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'JMM 8/10/2000 13:04'! primitiveAsyncFileReadResult: fhandle intoBuffer: buffer at: start count: num | bufferSize bufferPtr r f count startIndex | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileReadResult' parameters: #(Oop Oop SmallInteger SmallInteger ). f _ self asyncFileValueOf: fhandle. count _ num. startIndex _ start. bufferSize _ interpreterProxy slotSizeOf: buffer. "in bytes or words" (interpreterProxy isWords: buffer) ifTrue: ["covert word counts to byte counts" count _ count * 4. startIndex _ startIndex - 1 * 4 + 1. bufferSize _ bufferSize * 4]. interpreterProxy success: (startIndex >= 1 and: [startIndex + count - 1 <= bufferSize]). bufferPtr _ (self cCoerce: (interpreterProxy firstIndexableField: buffer) to: 'int') + startIndex - 1. "adjust for zero-origin indexing" interpreterProxy failed ifFalse: [r _ self cCode: 'asyncFileReadResult(f, bufferPtr, count)']. ^ r asOop: SmallInteger! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'ar 5/13/2000 16:00'! primitiveAsyncFileReadStart: fHandle fPosition: fPosition count: count | f | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileReadStart' parameters: #(Oop SmallInteger SmallInteger). f _ self asyncFileValueOf: fHandle. self cCode: 'asyncFileReadStart(f, fPosition, count)' ! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'TPR 2/7/2000 16:09'! primitiveAsyncFileWriteResult: fHandle | f r | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileWriteResult' parameters:#(Oop). f _ self asyncFileValueOf: fHandle. r _ self cCode:' asyncFileWriteResult(f)'. ^r asOop: SmallInteger! ! !AsynchFilePlugin methodsFor: 'primitives' stamp: 'JMM 8/10/2000 13:05'! primitiveAsyncFileWriteStart: fHandle fPosition: fPosition fromBuffer: buffer at: start count: num | f bufferSize bufferPtr count startIndex | self var: #f declareC: 'AsyncFile *f'. self primitive: 'primitiveAsyncFileWriteStart' parameters: #(Oop SmallInteger Oop SmallInteger SmallInteger ). f _ self asyncFileValueOf: fHandle. interpreterProxy failed ifTrue: [^ nil]. count _ num. startIndex _ start. bufferSize _ interpreterProxy slotSizeOf: buffer. "in bytes or words" (interpreterProxy isWords: buffer) ifTrue: ["covert word counts to byte counts" count _ count * 4. startIndex _ startIndex - 1 * 4 + 1. bufferSize _ bufferSize * 4]. interpreterProxy success: (startIndex >= 1 and: [startIndex + count - 1 <= bufferSize]). bufferPtr _ (self cCoerce: (interpreterProxy firstIndexableField: buffer) to: 'int') + startIndex - 1. "adjust for zero-origin indexing" interpreterProxy failed ifFalse: [self cCode: 'asyncFileWriteStart(f, fPosition, bufferPtr, count)']! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AsynchFilePlugin class instanceVariableNames: ''! !AsynchFilePlugin class methodsFor: 'translation' stamp: 'ar 5/11/2000 22:21'! headerFile ^'/* Header file for AsynchFile plugin */ /* module initialization/shutdown */ int asyncFileInit(void); int asyncFileShutdown(void); /*** Experimental Asynchronous File I/O ***/ typedef struct { int sessionID; void *state; } AsyncFile; int asyncFileClose(AsyncFile *f); int asyncFileOpen(AsyncFile *f, int fileNamePtr, int fileNameSize, int writeFlag, int semaIndex); int asyncFileRecordSize(); int asyncFileReadResult(AsyncFile *f, int bufferPtr, int bufferSize); int asyncFileReadStart(AsyncFile *f, int fPosition, int count); int asyncFileWriteResult(AsyncFile *f); int asyncFileWriteStart(AsyncFile *f, int fPosition, int bufferPtr, int bufferSize); '! ! EllipseMorph subclass: #AtomMorph instanceVariableNames: 'velocity ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !AtomMorph methodsFor: 'as yet unclassified' stamp: 'jm 8/10/1998 17:40'! bounceIn: aRect "Move this atom one step along its velocity vector and make it bounce if it goes outside the given rectangle. Return true if it is bounced." | p vx vy px py bounced | p _ self position. vx _ velocity x. vy _ velocity y. px _ p x + vx. py _ p y + vy. bounced _ false. px > aRect right ifTrue: [ px _ aRect right - (px - aRect right). vx _ velocity x negated. bounced _ true]. py > aRect bottom ifTrue: [ py _ aRect bottom - (py - aRect bottom). vy _ velocity y negated. bounced _ true]. px < aRect left ifTrue: [ px _ aRect left - (px - aRect left). vx _ velocity x negated. bounced _ true]. py < aRect top ifTrue: [ py _ aRect top - (py - aRect top). vy _ velocity y negated. bounced _ true]. self position: px @ py. bounced ifTrue: [self velocity: vx @ vy]. ^ bounced ! ! !AtomMorph methodsFor: 'as yet unclassified'! drawOn: aCanvas "Note: Set 'drawAsRect' to true to make the atoms draw faster. When testing the speed of other aspects of Morphic, such as its damage handling efficiency for large numbers of atoms, it is useful to make drawing faster." | drawAsRect | drawAsRect _ false. "rectangles are faster to draw" drawAsRect ifTrue: [aCanvas fillRectangle: self bounds color: color] ifFalse: [super drawOn: aCanvas].! ! !AtomMorph methodsFor: 'as yet unclassified'! infected ^ color = Color red! ! !AtomMorph methodsFor: 'as yet unclassified'! infected: aBoolean aBoolean ifTrue: [self color: Color red] ifFalse: [self color: Color blue].! ! !AtomMorph methodsFor: 'as yet unclassified'! initialize "Make a new atom with a random position and velocity." super initialize. self extent: 8@7. self color: Color blue. self borderWidth: 0. self randomPositionIn: (0@0 corner: 300@300) maxVelocity: 10. ! ! !AtomMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/15/2000 07:32'! randomPositionIn: aRectangle maxVelocity: maxVelocity "Give this atom a random position and velocity." | origin extent | origin _ aRectangle origin. extent _ (aRectangle extent - self bounds extent) rounded. self position: (origin x + extent x atRandom) @ (origin y + extent y atRandom). velocity _ (maxVelocity - (2 * maxVelocity) atRandom) @ (maxVelocity - (2 * maxVelocity) atRandom). ! ! !AtomMorph methodsFor: 'as yet unclassified'! velocity ^ velocity! ! !AtomMorph methodsFor: 'as yet unclassified'! velocity: newVelocity velocity _ newVelocity.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AtomMorph class instanceVariableNames: ''! !AtomMorph class methodsFor: 'as yet unclassified' stamp: 'di 6/22/97 09:07'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! Stream subclass: #AttributedTextStream instanceVariableNames: 'characters attributeRuns currentAttributes attributesChanged ' classVariableNames: '' poolDictionaries: '' category: 'Collections-Streams'! !AttributedTextStream commentStamp: '' prior: 0! a stream on Text's which keeps track of the last attribute put; new characters are added with those attributes. instance vars: characters - a WriteStream of the characters in the stream attributeRuns - a RunArray with the attributes for the stream currentAttributes - the attributes to be used for new text attributesChanged - whether the attributes have changed since the last addition! !AttributedTextStream methodsFor: 'retrieving the text' stamp: 'ls 6/27/1998 15:04'! contents | ans | ans _ Text new: characters size. ans setString: characters contents setRuns: attributeRuns. "this is declared private, but it's exactly what I need, and it's declared as exactly what I want it to do...." ^ans! ! !AttributedTextStream methodsFor: 'stream protocol' stamp: 'ls 6/27/1998 14:59'! nextPut: aChar attributesChanged ifTrue: [ attributeRuns addLast: currentAttributes. attributesChanged _ false ] ifFalse: [ attributeRuns repeatLastIfEmpty: [ OrderedCollection new ] ]. characters nextPut: aChar! ! !AttributedTextStream methodsFor: 'stream protocol' stamp: 'ls 6/27/1998 15:02'! nextPutAll: aString "add an entire string with the same attributes" attributesChanged ifTrue: [ attributeRuns addLast: currentAttributes times: aString size. attributesChanged _ false. ] ifFalse: [ attributeRuns repeatLast: aString size ifEmpty: [ OrderedCollection new ] ]. characters nextPutAll: aString.! ! !AttributedTextStream methodsFor: 'access' stamp: 'ls 6/27/1998 15:09'! currentAttributes "return the current attributes" ^currentAttributes! ! !AttributedTextStream methodsFor: 'access' stamp: 'ls 7/28/1998 02:08'! currentAttributes: newAttributes "set the current attributes" attributesChanged _ currentAttributes ~= newAttributes. currentAttributes _ newAttributes. ! ! !AttributedTextStream methodsFor: 'access' stamp: 'ls 9/10/1998 03:36'! size "number of characters in the stream so far" ^characters size! ! !AttributedTextStream methodsFor: 'private-initialization' stamp: 'ls 6/27/1998 15:08'! initialize characters _ WriteStream on: String new. currentAttributes _ OrderedCollection new. attributesChanged _ true. attributeRuns _ RunArray new. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AttributedTextStream class instanceVariableNames: ''! !AttributedTextStream class methodsFor: 'instance creation' stamp: 'ls 6/27/1998 15:07'! new ^super basicNew initialize! ! EToyCommunicatorMorph subclass: #AudioChatGUI instanceVariableNames: 'mycodec myrecorder mytargetip myalert playOnArrival theConnectButton soundBlockNumber soundMessageID queueForMultipleSends transmitWhileRecording theTalkButton handsFreeTalking handsFreeTalkingFlashTime ' classVariableNames: 'DebugLog LiveMessages NewAudioMessages PlayOnArrival ' poolDictionaries: '' category: 'Morphic-Experimental'! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/7/2000 06:51'! changeTalkButtonLabel | bText | self transmitWhileRecording. handsFreeTalking ifTrue: [ theTalkButton labelUp: 'Talk'; labelDown: 'Release'; label: 'Talk'. bText _ 'Click once to begin a message. Click again to end the message.' ] ifFalse: [ theTalkButton labelUp: 'Talk'; labelDown: (transmitWhileRecording ifTrue: ['TALKING'] ifFalse: ['RECORDING']); label: 'Talk'. bText _ 'Press and hold to record a message.' ]. transmitWhileRecording ifTrue: [ bText _ bText , ' The message will be sent while you are speaking.' ] ifFalse: [ bText _ bText , ' The message will be sent when you are finished.' ]. theTalkButton setBalloonText: bText. ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/4/2000 13:25'! connect mytargetip _ FillInTheBlank request: 'Connect to?' initialAnswer: (mytargetip ifNil: ['']). mytargetip _ NetNameResolver stringFromAddress: ( (NetNameResolver addressFromString: mytargetip) ifNil: [^mytargetip _ ''] ) ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/4/2000 13:09'! currentConnectionStateString ^'?' ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'TBP 3/5/2000 16:22'! defaultBackgroundColor "In a better design, this would be handled by preferences." ^Color yellow."r: 1.0 g: 0.7 b: 0.8"! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/6/2000 18:27'! getChoice: aSymbol aSymbol == #playOnArrival ifTrue: [^self class playOnArrival]. aSymbol == #transmitWhileRecording ifTrue: [^self transmitWhileRecording]. aSymbol == #handsFreeTalking ifTrue: [^self handsFreeTalking]. ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'TBP 3/5/2000 16:02'! initialExtent "Nice and small--that was the idea. It shouldn't take up much screen real estate." ^200@100! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/4/2000 13:01'! objectsInQueue ^self class numberOfNewMessages! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/4/2000 12:26'! playNextMessage self class playNextAudioMessage. ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/4/2000 14:59'! removeConnectButton theConnectButton ifNotNil: [ theConnectButton delete. theConnectButton _ nil. ].! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/12/2000 18:11'! step | now | super step. self transmitWhileRecording ifTrue: [self sendAnyCompletedSounds]. self handsFreeTalking & myrecorder isRecording ifTrue: [ now _ Time millisecondClockValue. ((handsFreeTalkingFlashTime ifNil: [0]) - now) abs > 200 ifTrue: [ theTalkButton color: ( theTalkButton color = self buttonColor ifTrue: [Color white] ifFalse: [self buttonColor] ). handsFreeTalkingFlashTime _ now. ]. ]. self class playOnArrival ifTrue: [self playNextMessage]. "myrecorder ifNotNil: [ myrecorder recorder samplingRate printString ,' ', SoundPlayer samplingRate printString,' ' displayAt: 0@0 ]."! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/12/2000 18:09'! stepTime myrecorder ifNil: [^200]. myrecorder isRecording ifFalse: [^200]. ^20! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/2/2000 07:47'! stepTimeIn: aSystemWindow ^self stepTime ! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'Tbp 4/11/2000 16:49'! stop myrecorder stop. self send.! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'RAA 8/7/2000 06:52'! toggleChoice: aSymbol aSymbol == #playOnArrival ifTrue: [ ^PlayOnArrival _ self class playOnArrival not ]. aSymbol == #transmitWhileRecording ifTrue: [ transmitWhileRecording _ self transmitWhileRecording not. self changeTalkButtonLabel. ^transmitWhileRecording ]. aSymbol == #handsFreeTalking ifTrue: [ handsFreeTalking _ self handsFreeTalking not. self changeTalkButtonLabel. ^handsFreeTalking ]. ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/2/2000 16:33'! buttonColor ^Color lightBrown! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/2/2000 16:36'! connectButton ^SimpleButtonMorph new label: 'Connect'; color: self buttonColor; target: self; actWhen: #buttonUp; actionSelector: #connect; setBalloonText: 'Press to connect to another audio chat user.' ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/7/2000 06:51'! initialize super initialize. transmitWhileRecording _ false. handsFreeTalking _ false. mycodec _ GSMCodec new. myrecorder _ ChatNotes new. mytargetip _ ''. color _ Color yellow. borderWidth _ 4. borderColor _ Color black. self start2. self changeTalkButtonLabel. ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/4/2000 14:26'! ipAddress: aString mytargetip _ aString! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:22'! messageWaitingAlertIndicator | messageCounter | myalert _ AlertMorph new socketOwner: self. messageCounter _ UpdatingStringMorph on: self selector: #objectsInQueue. myalert addMorph: messageCounter. messageCounter contents: '0'; color: Color white. messageCounter align: messageCounter center with: myalert center. myalert setBalloonText: 'New messages indicator. This will flash and show the number of messages when there are messages that you haven''t listened to. You can click here to play the next message.'. myalert on: #mouseUp send: #playNextMessage to: self. ^myalert! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/2/2000 16:34'! playButton ^SimpleButtonMorph new label: 'Play'; color: self buttonColor; target: self; actWhen: #buttonUp; actionSelector: #playNextMessage; setBalloonText: 'Play the next new message.' ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/2/2000 16:37'! recordAndStopButton ^ChatButtonMorph new labelUp: 'Record'; labelDown: 'RECORDING'; label: 'Record'; color: self buttonColor; target: self; actionUpSelector: #stop; actionDownSelector: #record; setBalloonText: 'Press and hold to record a message. It will be sent when you release the mouse.' ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/4/2000 14:05'! start | myUpdatingText playButton myOpenConnectionButton myStopButton window | " --- old system window version --- " Socket initializeNetwork. myrecorder initialize. window _ (SystemWindow labelled: 'iSCREAM') model: self. myalert _ AlertMorph new. myalert socketOwner: self. window addMorph: myalert frame: (0.35@0.4 corner: 0.5@0.7). (playButton _ self playButton) center: 200@300. window addMorph: playButton frame: (0.5@0.4 corner: 1.0@0.7). (myOpenConnectionButton _ self connectButton) center: 250@300. window addMorph: myOpenConnectionButton frame: (0.5@0 corner: 1.0@0.4). (myStopButton _ self recordAndStopButton) center: 300@300. window addMorph: myStopButton frame: (0.5@0.7 corner: 1.0@1.0). myUpdatingText _ UpdatingStringMorph on: self selector: #objectsInQueue. window addMorph: myUpdatingText frame: (0.41@0.75 corner: 0.45@0.95). "myUserList init."! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:25'! start2 Socket initializeNetwork. myrecorder initialize. self addARow: { self inAColumn: { ( self inARow: { self inAColumn: {self toggleForSendWhileTalking}. self inAColumn: {self toggleForHandsFreeTalking}. self inAColumn: {self toggleForPlayOnArrival}. } ) hResizing: #shrinkWrap. self inARow: { self talkBacklogIndicator. self messageWaitingAlertIndicator. }. }. self inAColumn: { theConnectButton _ self connectButton. self playButton. theTalkButton _ self talkButton. }. }. ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:24'! talkBacklogIndicator ^(UpdatingStringMorph on: self selector: #talkBacklog) setBalloonText: 'Approximate number of seconds of delay in your messages getting to the other end.'! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/7/2000 06:52'! talkButton ^ChatButtonMorph new labelUp: 'xxx'; labelDown: 'xxx'; label: 'xxx'; color: self buttonColor; target: self; actionUpSelector: #talkButtonUp; actionDownSelector: #talkButtonDown; setBalloonText: 'xxx' ! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:14'! toggleForHandsFreeTalking ^self simpleToggleButtonFor: self attribute: #handsFreeTalking help: 'Whether you want to talk without holding the mouse down.'! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:15'! toggleForPlayOnArrival ^self simpleToggleButtonFor: self attribute: #playOnArrival help: 'Whether you want to play messages automatically on arrival.'! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'RAA 8/12/2000 16:14'! toggleForSendWhileTalking ^self simpleToggleButtonFor: self attribute: #transmitWhileRecording help: 'Whether you want to send messages while recording.'! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/6/2000 18:25'! handsFreeTalking ^handsFreeTalking ifNil: [handsFreeTalking _ false].! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/6/2000 09:47'! record queueForMultipleSends _ nil. myrecorder record.! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/12/2000 15:01'! samplingRateForTransmission ^11025 "try to cut down on amount of data sent for live chats"! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/13/2000 11:44'! send | null rawSound aSampledSound | mytargetip isEmpty ifTrue: [ ^self inform: 'You must connect with someone first.'. ]. rawSound _ myrecorder recorder recordedSound ifNil: [^self]. aSampledSound _ rawSound asSampledSound. "Smalltalk at: #Q3 put: {rawSound. rawSound asSampledSound. aCompressedSound}." self transmitWhileRecording ifTrue: [ self sendOneOfMany: rawSound asSampledSound. queueForMultipleSends ifNotNil: [queueForMultipleSends nextPut: nil]. queueForMultipleSends _ nil. ^self ]. null _ String with: 0 asCharacter. EToyPeerToPeer new sendSomeData: { EToyIncomingMessage typeAudioChat,null. Preferences defaultAuthorName,null. aSampledSound originalSamplingRate asInteger printString,null. (mycodec compressSound: aSampledSound) channels first. } to: mytargetip for: self. ! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/12/2000 14:34'! sendAnyCompletedSounds | soundsSoFar firstCompleteSound | myrecorder isRecording ifFalse: [^self]. mytargetip isEmpty ifTrue: [^self]. soundsSoFar _ myrecorder recorder recordedSound ifNil: [^self]. firstCompleteSound _ soundsSoFar removeFirstCompleteSoundOrNil ifNil: [^self]. self sendOneOfMany: firstCompleteSound.! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/12/2000 18:22'! sendOneOfMany: aSampledSound | null message aCompressedSound ratio resultBuf oldSamples newCount t fromIndex val maxVal | self samplingRateForTransmission = aSampledSound originalSamplingRate ifTrue: [ aCompressedSound _ mycodec compressSound: aSampledSound. ] ifFalse: [ t _ [ ratio _ aSampledSound originalSamplingRate // self samplingRateForTransmission. oldSamples _ aSampledSound samples. newCount _ oldSamples monoSampleCount // ratio. resultBuf _ SoundBuffer newMonoSampleCount: newCount. fromIndex _ 1. maxVal _ 0. 1 to: newCount do: [ :i | maxVal _ maxVal max: (val _ oldSamples at: fromIndex). resultBuf at: i put: val. fromIndex _ fromIndex + ratio. ]. ] timeToRun. NebraskaDebug at: #soundReductionTime add: {t. maxVal}. maxVal < 400 ifTrue: [ NebraskaDebug at: #soundReductionTime add: {'---dropped---'}. ^self ]. "awfully quiet" aCompressedSound _ mycodec compressSound: ( SampledSound new setSamples: resultBuf samplingRate: aSampledSound originalSamplingRate // ratio ). ]. null _ String with: 0 asCharacter. message _ { EToyIncomingMessage typeAudioChatContinuous,null. Preferences defaultAuthorName,null. aCompressedSound samplingRate asInteger printString,null. aCompressedSound channels first. }. queueForMultipleSends ifNil: [ queueForMultipleSends _ EToyPeerToPeer new sendSomeData: message to: mytargetip for: self multiple: true. ] ifNotNil: [ queueForMultipleSends nextPut: message ]. ! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/12/2000 16:18'! talkBacklog ^(queueForMultipleSends ifNil: [^0]) size // 2! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/9/2000 18:05'! talkButtonDown EToyListenerMorph confirmListening. self handsFreeTalking ifFalse: [^self record]. theTalkButton label: 'Release'. ! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/9/2000 18:13'! talkButtonUp theTalkButton recolor: self buttonColor. self handsFreeTalking ifFalse: [^self stop]. myrecorder isRecording ifTrue: [ theTalkButton label: 'Talk'. ^self stop. ]. self record. theTalkButton label: 'TALKING'. ! ! !AudioChatGUI methodsFor: 'sending' stamp: 'RAA 8/6/2000 13:08'! transmitWhileRecording ^transmitWhileRecording ifNil: [transmitWhileRecording _ false]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AudioChatGUI class instanceVariableNames: ''! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 16:12'! debugLog: x " AudioChatGUI debugLog: nil AudioChatGUI debugLog: OrderedCollection new DebugLog LiveMessages NewAudioMessages PlayOnArrival " DebugLog _ x. ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/11/2000 11:54'! handleNewAudioChat2From: dataStream sentBy: senderName ipAddress: ipAddressString | newSound seqSound compressed | compressed _ self newCompressedSoundFrom: dataStream. newSound _ compressed asSound. "-------an experiment to try newSound adjustVolumeTo: 7.0 overMSecs: 10 --------" DebugLog ifNotNil: [ DebugLog add: {compressed. newSound}. ]. LiveMessages ifNil: [LiveMessages _ Dictionary new]. seqSound _ LiveMessages at: ipAddressString ifAbsentPut: [SequentialSound new]. seqSound isPlaying ifTrue: [ seqSound add: newSound; pruneFinishedSounds. ] ifFalse: [ seqSound initialize; add: newSound. ]. seqSound isPlaying ifFalse: [seqSound play].! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 19:28'! handleNewAudioChatFrom: dataStream sentBy: senderName ipAddress: ipAddressString | compressed | compressed _ self newCompressedSoundFrom: dataStream. DebugLog ifNotNil: [ DebugLog add: {compressed}. ]. self newAudioMessages nextPut: compressed. self playOnArrival ifTrue: [self playNextAudioMessage]. ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/5/2000 19:22'! initialize EToyIncomingMessage forType: EToyIncomingMessage typeAudioChat send: #handleNewAudioChatFrom:sentBy:ipAddress: to: self. EToyIncomingMessage forType: EToyIncomingMessage typeAudioChatContinuous send: #handleNewAudioChat2From:sentBy:ipAddress: to: self. ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 12:16'! newAudioMessages ^NewAudioMessages ifNil: [NewAudioMessages _ SharedQueue new].! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 19:28'! newCompressedSoundFrom: dataStream | samplingRate | samplingRate _ (dataStream upTo: 0 asCharacter) asNumber. ^CompressedSoundData new withEToySound: dataStream upToEnd samplingRate: samplingRate. ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 13:01'! numberOfNewMessages ^self newAudioMessages size! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 14:06'! openAsMorph AudioChatGUI new openInWorld. "old syswindow version in #start" ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/6/2000 14:23'! playNextAudioMessage (self newAudioMessages nextOrNil ifNil: [^self]) asSound play.! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 13:14'! playOnArrival ^PlayOnArrival ifNil: [PlayOnArrival _ false]! ! EmbeddedServerAction subclass: #AuthorizedServerAction instanceVariableNames: 'authorizer ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !AuthorizedServerAction commentStamp: '' prior: 0! An EmbeddedServerAction that also has an Authorizer to verify username and password.! !AuthorizedServerAction methodsFor: 'URL processing' stamp: 'mjg 11/17/97 11:20'! authorizer ^authorizer! ! !AuthorizedServerAction methodsFor: 'URL processing' stamp: 'mjg 11/17/97 11:20'! authorizer: anAuthorizer authorizer _ anAuthorizer ! ! !AuthorizedServerAction methodsFor: 'URL processing' stamp: 'mjg 11/17/97 13:09'! checkAuthorization: request ^authorizer user: request userID. ! ! !AuthorizedServerAction methodsFor: 'URL processing' stamp: 'tk 5/21/1998 16:46'! mapName: nameString password: pwdString to: aPerson "Insert/remove the username:password combination into/from the users Dictionary. *** Use this method to add or delete users!! If you ask for the authorizer and talk to it, the change will not be recorded on the disk!! *** We use encoding per RFC1421." authorizer mapName: nameString password: pwdString to: aPerson. self authorizer: authorizer. "force it to be written to the disk" "*** Authorizer not saved to disk yet for this class ***"! ! SwikiAction subclass: #AuthorizedSwikiAction instanceVariableNames: 'authorizer ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !AuthorizedSwikiAction commentStamp: '' prior: 0! A Server with a login name and password for the entire Swiki area. Can be multiple users each with a different password. Each sees and can modify the whole Swiki area. To restart an existing Authorized Swiki: AuthorizedSwikiAction new restore: 'SWSecure'. The front page URL is: http://serverMachine:80/SWSecure.1 To make a completely new one: | a s | a := Authorizer new. a realm: 'SwikiArea'. a mapName: 'viki' password: 'hard2guess' to: 'viki'. AuthorizedSwikiAction setUp: 'SWSecure'. s := AuthorizedSwikiAction new restore: 'SWSecure'. s authorizer: a. ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'tk 9/21/1998 08:23'! authorizer "*** Do not use this method to add or delete users!! The change will not be recorded on the disk!! Instead call mapName:password:to: in this class.***" ^authorizer! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'tk 5/22/1998 07:46'! authorizer: anAuthorizer "Smash all old name/password pairs with this new set. Overwrites the file on the disk" | fName refStream | authorizer _ anAuthorizer. fName _ ServerAction serverDirectory, name, (ServerAction pathSeparator), 'authorizer'. refStream _ SmartRefStream fileNamed: fName. refStream nextPut: authorizer; close. ! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'tk 7/6/1998 07:31'! checkAuthorization: request ^ authorizer ifNotNil: [authorizer user: request userID]. ! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'tk 5/21/1998 16:30'! mapName: nameString password: pwdString to: aPerson "Insert/remove the username:password combination into/from the users Dictionary. *** Use this method to add or delete users!! If you ask for the authorizer and talk to it, the change will not be recorded on the disk!! *** We use encoding per RFC1421." authorizer mapName: nameString password: pwdString to: aPerson. self authorizer: authorizer. "force it to be written to the disk"! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'mjg 8/31/1998 15:32'! process: request self checkAuthorization: request. ^(super process: request).! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'tk 9/13/1998 20:45'! processSpecial: request "Let SwikiAction process this with no authorization check." ^(super process: request).! ! !AuthorizedSwikiAction methodsFor: 'URL processing' stamp: 'mdr 8/31/2000 18:41'! restore: nameOfSwiki "Read all files in the directory 'nameOfSwiki'. Reconstruct the url map." | fName | super restore: nameOfSwiki. fName _ ServerAction serverDirectory, name, (ServerAction pathSeparator), 'authorizer'. (FileDirectory new fileExists: fName) ifTrue: [ authorizer _ (FileStream readOnlyFileNamed: fName) fileInObjectAndCode]. ! ! AuthorizedSwikiAction subclass: #AuthorizedWriteSwiki instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !AuthorizedWriteSwiki commentStamp: '' prior: 0! Allows anyone to read the pages of this Swiki, but only authorized users can edit or change pages. Can have multiple users, each with a different password. Each can modify the whole Swiki area. To restart an existing Authorized Swiki: AuthorizedWriteSwiki new restore: 'SWSecure'. The front page URL is: http://serverMachine:80/SWSecure.1 To make a completely new one: | a s | a := Authorizer new. a realm: 'SwikiArea'. a mapName: 'viki' password: 'hard2guess' to: 'viki'. AuthorizedWriteSwiki setUp: 'SWSecure'. s := AuthorizedWriteSwiki new restore: 'SWSecure'. s authorizer: a. ! !AuthorizedWriteSwiki methodsFor: 'as yet unclassified' stamp: 'tk 9/13/1998 20:59'! process: request "Only demand authorization of name and password when requesting the edit page, requesting the append page, receiving an edit, or receiving an append." | command coreRef | request fields ifNotNil: ["Are there input fields?" coreRef _ request message size < 2 ifTrue: ['1'] ifFalse: [request message at: 2]. coreRef = 'searchresult' ifFalse: ["Must be text for an edit!!" self checkAuthorization: request]]. request message size > 2 ifTrue: ["SearchResult, All, Versions, or Edit" command _ request message at: 3. command = 'edit' ifTrue: [self checkAuthorization: request]. command = 'insert' ifTrue: [self checkAuthorization: request]]. ^(super processSpecial: request). "all the way up to SwikiAction"! ! Object subclass: #Authorizer instanceVariableNames: 'users realm ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !Authorizer commentStamp: '' prior: 0! The Authorizer does user authorization checking. Each instance of authorizer keeps track of the realm that it is authorizing for, and the table of authorized users. An authorizer can be asked to return the user name/symbol associated with a userID (which concatenates the username and password from the HTTP request) with the user: method. ! !Authorizer methodsFor: 'realms' stamp: 'mjg 11/3/97 12:33'! realm ^realm! ! !Authorizer methodsFor: 'realms' stamp: 'mjg 11/3/97 12:33'! realm: aString realm := aString ! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/3/97 13:01'! encode: nameString password: pwdString "Encode per RFC1421 of the username:password combination." | clear code clearSize idx map | clear := (nameString, ':', pwdString) asByteArray. clearSize := clear size. [ clear size \\ 3 ~= 0 ] whileTrue: [ clear := clear, #(0) ]. idx := 1. map := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'. code := WriteStream on: ''. [ idx < clear size ] whileTrue: [ code nextPut: (map at: (clear at: idx) // 4 + 1); nextPut: (map at: (clear at: idx) \\ 4 * 16 + ((clear at: idx + 1) // 16) + 1); nextPut: (map at: (clear at: idx + 1) \\ 16 * 4 + ((clear at: idx + 2) // 64) + 1); nextPut: (map at: (clear at: idx + 2) \\ 64 + 1). idx := idx + 3 ]. code := code contents. idx := code size. clear size - clearSize timesRepeat: [ code at: idx put: $=. idx := idx - 1]. ^code! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/3/97 12:31'! mapFrom: aKey to: aPerson "Establish a mapping from a RFC 1421 key to a user." users isNil ifTrue: [ users := Dictionary new ]. aPerson isNil ifTrue: [ users removeKey: aKey ] ifFalse: [ users removeKey: (users keyAtValue: aPerson ifAbsent: []) ifAbsent: []. users at: aKey put: aPerson ] ! ! !Authorizer methodsFor: 'authentication' stamp: 'tk 5/21/1998 16:32'! mapName: nameString password: pwdString to: aPerson "Insert/remove the encoding per RFC1421 of the username:password combination into/from the UserMap. DO NOT call this directly, use mapName:password:to: in your ServerAction class. Only it knows how to record the change on the disk!!" self mapFrom: (self encode: nameString password: pwdString) to: aPerson ! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/17/97 13:07'! user: userId "Return the requesting user." ^users at: userId ifAbsent: [ self error: (PWS unauthorizedFor: realm) ]! ! Object subclass: #AutoStart instanceVariableNames: 'parameters ' classVariableNames: 'InstalledLaunchers ' poolDictionaries: '' category: 'System-Support'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AutoStart class instanceVariableNames: ''! !AutoStart class methodsFor: 'class initialization' stamp: 'mir 7/28/1999 17:44'! deinstall "AutoStart deinstall" Smalltalk removeFromStartUpList: AutoStart. InstalledLaunchers _ nil! ! !AutoStart class methodsFor: 'class initialization' stamp: 'mir 7/28/1999 17:43'! initialize "AutoStart initialize" Smalltalk addToStartUpList: AutoStart! ! !AutoStart class methodsFor: 'class initialization' stamp: 'RAA 12/17/2000 12:07'! startUp | startupParameters launchers | startupParameters _ AbstractLauncher extractParameters. launchers _ self installedLaunchers collect: [:launcher | launcher new]. launchers do: [:launcher | launcher parameters: startupParameters]. launchers do: [:launcher | Smalltalk at: #WorldState ifPresent: [ :ws | ws addDeferredUIMessage: [launcher startUp]]]! ! !AutoStart class methodsFor: 'accessing' stamp: 'mir 7/28/1999 17:47'! addLauncher: launcher self installedLaunchers add: launcher! ! !AutoStart class methodsFor: 'accessing' stamp: 'mir 7/28/1999 17:47'! removeLauncher: launcher self installedLaunchers remove: launcher ifAbsent: []! ! !AutoStart class methodsFor: 'accessing' stamp: 'mir 8/6/1999 18:14'! removeLauncherClass: launcherClass " | launchersToBeRemoved | launchersToBeRemoved _ self installedLaunchers select: [:launcher | launcher class == launcherClass]. launchersToBeRemoved do: [:launcher | self removeLauncher: launcher]" self removeLauncher: launcherClass! ! !AutoStart class methodsFor: 'private' stamp: 'mir 7/28/1999 17:43'! installedLaunchers InstalledLaunchers ifNil: [ InstalledLaunchers _ OrderedCollection new]. ^InstalledLaunchers! ! InterpreterPlugin subclass: #B3DAcceleratorPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Acceleration'! !B3DAcceleratorPlugin commentStamp: '' prior: 0! B3DAcceleratorPlugin translate! !B3DAcceleratorPlugin methodsFor: 'initialize-release' stamp: 'ar 5/26/2000 17:23'! initialiseModule self export: true. ^self b3dxInitialize! ! !B3DAcceleratorPlugin methodsFor: 'initialize-release' stamp: 'ar 5/26/2000 17:23'! shutdownModule self export: true. ^self b3dxShutdown! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:54'! primitiveBltFromDisplay | result extent srcOrigin dstOrigin extentX extentY sourceX sourceY destX destY formHandle displayHandle | self export: true. interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. extent _ interpreterProxy stackObjectValue: 0. srcOrigin _ interpreterProxy stackObjectValue: 1. dstOrigin _ interpreterProxy stackObjectValue: 2. formHandle _ interpreterProxy stackIntegerValue: 3. displayHandle _ interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. ((interpreterProxy isPointers: extent) and:[(interpreterProxy slotSizeOf: extent) = 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: srcOrigin) and:[(interpreterProxy slotSizeOf: srcOrigin) = 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: dstOrigin) and:[(interpreterProxy slotSizeOf: dstOrigin) = 2]) ifFalse:[^interpreterProxy primitiveFail]. extentX _ interpreterProxy fetchInteger: 0 ofObject: extent. extentY _ interpreterProxy fetchInteger: 1 ofObject: extent. sourceX _ interpreterProxy fetchInteger: 0 ofObject: srcOrigin. sourceY _ interpreterProxy fetchInteger: 1 ofObject: srcOrigin. destX _ interpreterProxy fetchInteger: 0 ofObject: dstOrigin. destY _ interpreterProxy fetchInteger: 1 ofObject: dstOrigin. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxBltFromDisplay(displayHandle, formHandle, destX, destY, sourceX, sourceY, extentX, extentY)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 5. "pop args; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:55'! primitiveBltToDisplay | result extent srcOrigin dstOrigin extentX extentY sourceX sourceY destX destY formHandle displayHandle | self export: true. interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. extent _ interpreterProxy stackObjectValue: 0. srcOrigin _ interpreterProxy stackObjectValue: 1. dstOrigin _ interpreterProxy stackObjectValue: 2. formHandle _ interpreterProxy stackIntegerValue: 3. displayHandle _ interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. ((interpreterProxy isPointers: extent) and:[(interpreterProxy slotSizeOf: extent) = 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: srcOrigin) and:[(interpreterProxy slotSizeOf: srcOrigin) = 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: dstOrigin) and:[(interpreterProxy slotSizeOf: dstOrigin) = 2]) ifFalse:[^interpreterProxy primitiveFail]. extentX _ interpreterProxy fetchInteger: 0 ofObject: extent. extentY _ interpreterProxy fetchInteger: 1 ofObject: extent. sourceX _ interpreterProxy fetchInteger: 0 ofObject: srcOrigin. sourceY _ interpreterProxy fetchInteger: 1 ofObject: srcOrigin. destX _ interpreterProxy fetchInteger: 0 ofObject: dstOrigin. destY _ interpreterProxy fetchInteger: 1 ofObject: dstOrigin. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxBltToDisplay(displayHandle, formHandle, destX, destY, sourceX, sourceY, extentX, extentY)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 5. "pop args; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:15'! primitiveCreateDisplaySurface | h w d result | self export: true. interpreterProxy methodArgumentCount = 3 ifFalse:[^interpreterProxy primitiveFail]. h _ interpreterProxy stackIntegerValue: 0. w _ interpreterProxy stackIntegerValue: 1. d _ interpreterProxy stackIntegerValue: 2. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxCreateDisplaySurface(w, h, d)' inSmalltalk:[-1]. result = -1 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 4. "args+rcvr" interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:16'! primitiveDestroyDisplaySurface | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxDestroyDisplaySurface(handle)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop arg; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 21:36'! primitiveDisplayGetColorMasks | handle result masks array | self export: true. self var: #masks declareC:'int masks[4]'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. array _ interpreterProxy stackObjectValue: 0. handle _ interpreterProxy stackIntegerValue: 1. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: array) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: array) = 4 ifFalse:[^interpreterProxy primitiveFail]. result _ self cCode:'b3dxDisplayColorMasks(handle, masks)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. 0 to: 3 do:[:i| interpreterProxy storePointer: i ofObject: array withValue: (interpreterProxy positive32BitIntegerFor: (masks at: i))]. interpreterProxy pop: 2. "pop args return receiver"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:16'! primitiveFillDisplaySurface | h w result y x pv handle | self export: true. interpreterProxy methodArgumentCount = 6 ifFalse:[^interpreterProxy primitiveFail]. h _ interpreterProxy stackIntegerValue: 0. w _ interpreterProxy stackIntegerValue: 1. y _ interpreterProxy stackIntegerValue: 2. x _ interpreterProxy stackIntegerValue: 3. pv _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 4). handle _ interpreterProxy stackIntegerValue: 5. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxFillDisplaySurface(handle, pv, x, y, w, h)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 6. "pop args; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:17'! primitiveFinishDisplaySurface | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxFinishDisplaySurface(handle)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop arg; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:17'! primitiveFlushDisplaySurface | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxFlushDisplaySurface(handle)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop arg; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 21:37'! primitiveSupportsDisplayDepth | result depth | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. depth _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self b3dxSupportsDisplayDepth: depth. interpreterProxy pop: 1. interpreterProxy pushBool: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/28/2000 01:17'! primitiveAllocateTexture | h w d result | self export: true. interpreterProxy methodArgumentCount = 3 ifFalse:[^interpreterProxy primitiveFail]. h _ interpreterProxy stackIntegerValue: 0. w _ interpreterProxy stackIntegerValue: 1. d _ interpreterProxy stackIntegerValue: 2. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxAllocateTexture(w, h, d)' inSmalltalk:[-1]. result = -1 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 4. "args+rcvr" interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/28/2000 01:18'! primitiveDestroyTexture | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxDestroyTexture(handle)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop arg; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 21:37'! primitiveTextureDepth | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxActualTextureDepth(handle)' inSmalltalk:[-1]. result < 0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 2. interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 21:37'! primitiveTextureGetColorMasks | handle result masks array | self export: true. self var: #masks declareC:'int masks[4]'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. array _ interpreterProxy stackObjectValue: 0. handle _ interpreterProxy stackIntegerValue: 1. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: array) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: array) = 4 ifFalse:[^interpreterProxy primitiveFail]. result _ self cCode:'b3dxTextureColorMasks(handle, masks)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. 0 to: 3 do:[:i| interpreterProxy storePointer: i ofObject: array withValue: (interpreterProxy positive32BitIntegerFor: (masks at: i))]. interpreterProxy pop: 2. "pop args return receiver"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/28/2000 01:18'! primitiveTextureHeight | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxActualTextureHeight(handle)' inSmalltalk:[-1]. result < 0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 2. interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-textures' stamp: 'ar 5/28/2000 01:19'! primitiveTextureWidth | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxActualTextureWidth(handle)' inSmalltalk:[-1]. result < 0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 2. interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-forms' stamp: 'ar 5/28/2000 01:19'! primitiveAllocateForm | h w d result | self export: true. interpreterProxy methodArgumentCount = 3 ifFalse:[^interpreterProxy primitiveFail]. h _ interpreterProxy stackIntegerValue: 0. w _ interpreterProxy stackIntegerValue: 1. d _ interpreterProxy stackIntegerValue: 2. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxAllocateForm(w, h, d)' inSmalltalk:[-1]. result = -1 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: 4. "args+rcvr" interpreterProxy pushInteger: result.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-forms' stamp: 'ar 5/28/2000 01:19'! primitiveDestroyForm | handle result | self export: true. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxDestroyForm(handle)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop arg; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 21:38'! primitiveFormGetColorMasks | handle result masks array | self export: true. self var: #masks declareC:'int masks[4]'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. array _ interpreterProxy stackObjectValue: 0. handle _ interpreterProxy stackIntegerValue: 1. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: array) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: array) = 4 ifFalse:[^interpreterProxy primitiveFail]. result _ self cCode:'b3dxFormColorMasks(handle, masks)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. 0 to: 3 do:[:i| interpreterProxy storePointer: i ofObject: array withValue: (interpreterProxy positive32BitIntegerFor: (masks at: i))]. interpreterProxy pop: 2. "pop args return receiver"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-rasterizer' stamp: 'ar 5/28/2000 01:19'! primitiveClearDepthBuffer | result | self export: true. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. result _ self cCode:'b3dxClearDepthBuffer()'. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 4. "pop args; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitives-rasterizer' stamp: 'ar 6/29/2000 06:55'! primitiveProcessVertexBuffer | idxCount vtxCount vtxArray idxArray texHandle primType result box array | self export: true. self var: #idxArray type: 'int *'. self var: #vtxArray type: 'float *'. self var: #box declareC:'int box[4] = { 0, 0, 0, 0 }'. interpreterProxy methodArgumentCount = 6 ifFalse:[^interpreterProxy primitiveFail]. idxCount _ interpreterProxy stackIntegerValue: 0. vtxCount _ interpreterProxy stackIntegerValue: 2. texHandle _ interpreterProxy stackIntegerValue: 4. primType _ interpreterProxy stackIntegerValue: 5. interpreterProxy failed ifTrue:[^nil]. vtxArray _ self stackPrimitiveVertexArray: 3 ofSize: vtxCount. idxArray _ self stackPrimitiveIndexArray: 1 ofSize: idxCount validate: true forVertexSize: vtxCount. (vtxArray == nil or:[idxArray == nil or:[primType < 1 or:[primType > PrimTypeMax or:[interpreterProxy failed]]]]) ifTrue:[^interpreterProxy primitiveFail]. result _ self cCode:'b3dxRasterizeVertexBuffer(primType, texHandle, vtxArray, vtxCount, idxArray, idxCount, box)' inSmalltalk:[false]. result ifFalse:[^interpreterProxy primitiveFail]. array _ interpreterProxy instantiateClass: interpreterProxy classArray indexableSize: 4. interpreterProxy storeInteger: 0 ofObject: array withValue: (box at: 0). interpreterProxy storeInteger: 1 ofObject: array withValue: (box at: 1). interpreterProxy storeInteger: 2 ofObject: array withValue: (box at: 2). interpreterProxy storeInteger: 3 ofObject: array withValue: (box at: 3). interpreterProxy failed ifTrue:[^nil]. interpreterProxy pop: 7. "pop args + rcvr" interpreterProxy push: array.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-rasterizer' stamp: 'ar 5/26/2000 17:24'! primitiveRasterizerVersion self export: true. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. interpreterProxy pushInteger: 1.! ! !B3DAcceleratorPlugin methodsFor: 'primitives-rasterizer' stamp: 'ar 5/28/2000 01:20'! primitiveSetViewport | h w y x result | self export: true. interpreterProxy methodArgumentCount = 4 ifFalse:[^interpreterProxy primitiveFail]. h _ interpreterProxy stackIntegerValue: 0. w _ interpreterProxy stackIntegerValue: 1. y _ interpreterProxy stackIntegerValue: 2. x _ interpreterProxy stackIntegerValue: 3. interpreterProxy failed ifTrue:[^nil]. result _ self cCode:'b3dxSetViewport(x, y, w, h)'. result ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 4. "pop args; return rcvr"! ! !B3DAcceleratorPlugin methodsFor: 'primitive support' stamp: 'ar 5/26/2000 12:37'! stackPrimitiveIndexArray: stackIndex ofSize: nItems validate: aBool forVertexSize: maxIndex "Load a primitive index array from the interpreter stack. If aBool is true then check that all the indexes are in the range (1,maxIndex). Return a pointer to the index data if successful, nil otherwise." | oop oopSize idxPtr index | self inline: false. self returnTypeC:'void*'. self var: #idxPtr declareC:'int *idxPtr'. oop _ interpreterProxy stackObjectValue: stackIndex. oop = nil ifTrue:[^nil]. (interpreterProxy isWords: oop) ifFalse:[^nil]. oopSize _ interpreterProxy slotSizeOf: oop. oopSize < nItems ifTrue:[^nil]. idxPtr _ self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int *'. aBool ifTrue:[ 0 to: nItems-1 do:[:i| index _ idxPtr at: i. (index < 0 or:[index > maxIndex]) ifTrue:[^nil]]]. ^idxPtr! ! !B3DAcceleratorPlugin methodsFor: 'primitive support' stamp: 'ar 5/26/2000 12:38'! stackPrimitiveVertex: index "Load a primitive vertex from the interpreter stack. Return a pointer to the vertex data if successful, nil otherwise." | oop | self inline: false. self returnTypeC:'void*'. oop _ interpreterProxy stackObjectValue: index. oop = nil ifTrue:[^nil]. ((interpreterProxy isWords: oop) and:[(interpreterProxy slotSizeOf: oop) = PrimVertexSize]) ifTrue:[^interpreterProxy firstIndexableField: oop]. ^nil! ! !B3DAcceleratorPlugin methodsFor: 'primitive support' stamp: 'ar 5/26/2000 12:38'! stackPrimitiveVertexArray: index ofSize: nItems "Load a primitive vertex array from the interpreter stack. Return a pointer to the vertex data if successful, nil otherwise." | oop oopSize | self inline: false. self returnTypeC:'void*'. oop _ interpreterProxy stackObjectValue: index. oop = nil ifTrue:[^nil]. (interpreterProxy isWords: oop) ifTrue:[ oopSize _ interpreterProxy slotSizeOf: oop. (oopSize >= nItems * PrimVertexSize and:[oopSize \\ PrimVertexSize = 0]) ifTrue:[^interpreterProxy firstIndexableField: oop]]. ^nil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DAcceleratorPlugin class instanceVariableNames: ''! !B3DAcceleratorPlugin class methodsFor: 'translation' stamp: 'ar 6/29/2000 06:53'! headerFile ^'/* Header file for 3D accelerator plugin */ /* module initialization support */ int b3dxInitialize(void); /* return true on success, false on error */ int b3dxShutdown(void); /* return true on success, false on error */ /* Display support primitives */ int b3dxCreateDisplaySurface(int w, int h, int d); /* return handle or -1 on error */ int b3dxDestroyDisplaySurface(int handle); /* return true on success, false on error */ int b3dxDisplayColorMasks(int handle, int masks[4]); /* return true on success, false on error */ int b3dxSupportsDisplayDepth(int depth); /* return true or false */ int b3dxFlushDisplaySurface(int handle); /* return true on success, false on error */ int b3dxFinishDisplaySurface(int handle); /* return true on success, false on error */ /* optional accelerated blt primitives */ int b3dxFillDisplaySurface(int handle, int pv, int x, int y, int w, int h); /* return true on success, false on error */ int b3dxBltToDisplay(int displayHandle, int formHandle, int dstX, int dstY, int srcX, int srcY, int w, int h); /* return true on success, false on error */ int b3dxBltFromDisplay(int displayHandle, int formHandle, int dstX, int dstY, int srcX, int srcY, int w, int h); /* return true on success, false on error */ /* Texture support primitives */ int b3dxAllocateTexture(int w, int h, int d); /* return handle or -1 on error */ int b3dxDestroyTexture(int handle); /* return true on success, false on error */ int b3dxActualTextureDepth(int handle); /* return depth or <0 on error */ int b3dxActualTextureWidth(int handle); /* return width or <0 on error */ int b3dxActualTextureHeight(int handle); /* return height or <0 on error */ int b3dxTextureColorMasks(int handle, int masks[4]); /* return true on success, false on error */ /* Form support primitives */ int b3dxAllocateForm(int w, int h, int d); /* return handle or -1 on error */ int b3dxDestroyForm(int handle); /* return true on success, false on error */ int b3dxFormColorMasks(int handle, int masks[4]); /* return true on success, false on error */ /* Rasterizer support primitives */ int b3dxSetViewport(int x, int y, int w, int h); /* return true on success, false on error */ int b3dxClearDepthBuffer(void); /* return true on success, false on error */ int b3dxRasterizeVertexBuffer(int primType, int texHandle, float *vtxArray, int vtxSize, int *idxArray, int idxSize, int *bounds); /* return true on success, false on error */ '.! ! !B3DAcceleratorPlugin class methodsFor: 'translation' stamp: 'ar 5/26/2000 17:25'! moduleName ^'Squeak3DX'! ! Object subclass: #B3DActiveEdgeTable instanceVariableNames: 'start stop array ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DActiveEdgeTable methodsFor: 'initialize' stamp: 'ar 4/4/1999 20:55'! initialize array _ Array new: 100. start _ 0. stop _ 0.! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/6/1999 02:21'! at: index ^array at: index! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:48'! first ^array at: 1! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/6/1999 23:20'! indexOf: anEdge 1 to: stop do:[:i| (array at: i) = anEdge ifTrue:[^i]]. ^0! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:48'! last ^array at: stop! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/3/1999 05:28'! size ^stop! ! !B3DActiveEdgeTable methodsFor: 'accessing' stamp: 'ar 4/6/1999 03:51'! xValues ^(array copyFrom: 1 to: stop) collect:[:e| e xValue]! ! !B3DActiveEdgeTable methodsFor: 'streaming' stamp: 'ar 4/3/1999 05:28'! atEnd ^start >= stop! ! !B3DActiveEdgeTable methodsFor: 'streaming' stamp: 'ar 4/3/1999 05:28'! next "Return the next entry from the AET and advance start" ^array at: (start _ start + 1)! ! !B3DActiveEdgeTable methodsFor: 'streaming' stamp: 'ar 4/5/1999 23:24'! peek "Peek the next entry from the AET" ^array at: (start + 1)! ! !B3DActiveEdgeTable methodsFor: 'streaming' stamp: 'ar 4/3/1999 05:28'! reset start _ 0.! ! !B3DActiveEdgeTable methodsFor: 'merging' stamp: 'ar 4/4/1999 21:52'! mergeEdgesFrom: inputList "Merge all the edges from the given input list in the AET" | srcIndex dstIndex outIndex srcEdge dstEdge | srcIndex _ inputList size. srcIndex = 0 ifTrue:[^self]. dstIndex _ stop. "Make room for adding the stuff" [stop + srcIndex > array size] whileTrue:[self grow]. "Adjust size" stop _ stop + srcIndex. "If the receiver is empty, simply copy the stuff" dstIndex = 0 ifTrue:[ 1 to: srcIndex do:[:i| array at: i put: (inputList at: i)]. ^self]. "Merge inputList by walking backwards through the AET and checking each edge." outIndex _ dstIndex+srcIndex. srcEdge _ inputList at: srcIndex. dstEdge _ array at: dstIndex. [true] whileTrue:[ srcEdge xValue >= dstEdge xValue ifTrue:[ "Insert srcEdge" array at: outIndex put: srcEdge. srcIndex _ srcIndex - 1. srcIndex = 0 ifTrue:[^self]. srcEdge _ inputList at: srcIndex. ] ifFalse:[ "Insert dstEdge" array at: outIndex put: dstEdge. dstIndex _ dstIndex - 1. dstIndex = 0 ifTrue:[ 1 to: srcIndex do:[:i| array at: i put: (inputList at: i)]. ^self]. dstEdge _ array at: dstIndex. ]. outIndex _ outIndex-1. ].! ! !B3DActiveEdgeTable methodsFor: 'removing' stamp: 'ar 4/5/1999 03:15'! removeFirst stop _ stop - 1. array replaceFrom: start to: stop with: array startingAt: start+1. start _ start - 1. array at: stop+1 put: nil.! ! !B3DActiveEdgeTable methodsFor: 'sorting' stamp: 'ar 4/3/1999 05:27'! resortFirst "Resort the first entry in the active edge table" | edge xValue leftEdge newIndex | start = 1 ifTrue:[^self]. "Nothing to do" "Fetch the edge to test." edge _ array at: start. xValue _ edge xValue. "Fetch the next edge left to it." leftEdge _ array at: start-1. leftEdge xValue <= xValue ifTrue:[^self]. "Okay." "Move the edge left to its correct insertion point." newIndex _ start. [newIndex > 1 and:[(leftEdge _ array at: newIndex-1) xValue > xValue]] whileTrue:[ array at: newIndex put: leftEdge. newIndex _ newIndex - 1]. array at: newIndex put: edge.! ! !B3DActiveEdgeTable methodsFor: 'testing' stamp: 'ar 4/4/1999 21:21'! isEmpty ^stop = 0! ! !B3DActiveEdgeTable methodsFor: 'enumerating' stamp: 'ar 4/5/1999 02:19'! do: aBlock 1 to: stop do:[:i| aBlock value: (array at: i)].! ! !B3DActiveEdgeTable methodsFor: 'private' stamp: 'ar 4/5/1999 02:19'! asArray ^array copyFrom: 1 to: stop! ! !B3DActiveEdgeTable methodsFor: 'private' stamp: 'ar 4/3/1999 05:25'! grow | newArray | newArray _ array species new: array size + 100. "Grow linearly" newArray replaceFrom: 1 to: array size with: array startingAt: 1. array _ newArray.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DActiveEdgeTable class instanceVariableNames: ''! !B3DActiveEdgeTable class methodsFor: 'instance creation' stamp: 'ar 4/4/1999 04:27'! new ^super new initialize! ! B3DLightSource subclass: #B3DAmbientLight instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DAmbientLight methodsFor: 'shading' stamp: 'ar 2/7/1999 17:16'! computeAttenuationFor: distance ^1.0! ! !B3DAmbientLight methodsFor: 'shading' stamp: 'ar 2/7/1999 16:56'! computeDirectionTo: aB3DPrimitiveVertex ^B3DVector3 zero! ! !B3DAmbientLight methodsFor: 'shading' stamp: 'ar 2/8/1999 00:33'! shadeVertexBuffer: vb with: aMaterial into: colorArray "Overridden for simplicity and speed" | color | false ifTrue:[^super shadeVertexBuffer: vb with: aMaterial into: colorArray]. self flag: #b3dPrimitive. vb trackAmbientColor ifTrue:[ 1 to: vb vertexCount do:[:i| color _ (vb primitiveB3dColorAt: i) * lightColor ambientPart. colorArray add: color at: i. ]. ] ifFalse:[ color _ aMaterial ambientPart * lightColor ambientPart. colorArray += color. ].! ! !B3DAmbientLight methodsFor: 'testing' stamp: 'ar 2/8/1999 00:33'! hasDiffusePart ^false! ! !B3DAmbientLight methodsFor: 'testing' stamp: 'ar 2/8/1999 00:33'! hasSpecularPart ^false! ! !B3DAmbientLight methodsFor: 'converting' stamp: 'ar 2/15/1999 21:52'! asPrimitiveLight "Convert the receiver into a B3DPrimitiveLight" | primLight | primLight _ B3DPrimitiveLight new. primLight ambientPart: lightColor ambientPart. primLight flags: FlagAmbientPart. ^primLight! ! !B3DAmbientLight methodsFor: 'converting' stamp: 'ar 2/8/1999 01:29'! transformedBy: aTransformer ^self! ! B3DGeometry subclass: #B3DBox instanceVariableNames: 'vertices ' classVariableNames: 'BoxColors BoxFaceIndexes BoxNormals ' poolDictionaries: '' category: 'Balloon3D-Objects'! !B3DBox methodsFor: 'displaying' stamp: 'ar 2/16/1999 17:25'! renderOn: aRenderer "Note: The use of BoxColors is an example for pre-lighting." 1 to: 6 do:[:i| "Enable simple additive computation of box colors. Note: This must be turned on on per-primitive basis." aRenderer trackEmissionColor: true; "Turn on pre-lit colors" normal: (BoxNormals at: i); color: (BoxColors at: i); "Set pre-lit color per polygon" drawPolygonAfter:[ aRenderer texCoords: (vertices at: ((BoxFaceIndexes at: i) at: 1)); vertex: (vertices at: ((BoxFaceIndexes at: i) at: 1)); texCoords: (vertices at: ((BoxFaceIndexes at: i) at: 2)); vertex: (vertices at: ((BoxFaceIndexes at: i) at: 2)); texCoords: (vertices at: ((BoxFaceIndexes at: i) at: 3)); vertex: (vertices at: ((BoxFaceIndexes at: i) at: 3)); texCoords: (vertices at: ((BoxFaceIndexes at: i) at: 4)); vertex: (vertices at: ((BoxFaceIndexes at: i) at: 4)). ]. ].! ! !B3DBox methodsFor: 'private'! buildBoxFrom: origin to: corner vertices := Array new: 8. 1 to: 8 do:[:i| vertices at: i put: B3DVector3 new]. (vertices at: 1) x: origin x. (vertices at: 1) y: origin y. (vertices at: 1) z: origin z. (vertices at: 2) x: origin x. (vertices at: 2) y: origin y. (vertices at: 2) z: corner z. (vertices at: 3) x: origin x. (vertices at: 3) y: corner y. (vertices at: 3) z: corner z. (vertices at: 4) x: origin x. (vertices at: 4) y: corner y. (vertices at: 4) z: origin z. (vertices at: 5) x: corner x. (vertices at: 5) y: origin y. (vertices at: 5) z: origin z. (vertices at: 6) x: corner x. (vertices at: 6) y: origin y. (vertices at: 6) z: corner z. (vertices at: 7) x: corner x. (vertices at: 7) y: corner y. (vertices at: 7) z: corner z. (vertices at: 8) x: corner x. (vertices at: 8) y: corner y. (vertices at: 8) z: origin z. ! ! !B3DBox methodsFor: 'accessing' stamp: 'ar 3/12/2000 21:11'! boundingBox ^Rectangle origin: vertices first corner: (vertices at: 7)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DBox class instanceVariableNames: ''! !B3DBox class methodsFor: 'class initialization' stamp: 'ar 2/4/1999 20:20'! initialize "B3DBox initialize" | nrmls | nrmls := #( (-1.0 0.0 0.0) (0.0 1.0 0.0) (1.0 0.0 0.0) (0.0 -1.0 0.0) (0.0 0.0 1.0) (0.0 0.0 -1.0)) collect:[:spec| B3DVector3 x: spec first y: spec second z: spec third]. BoxNormals := nrmls. "BoxNormals := Array new: 6. 1 to: 6 do:[:i| BoxNormals at: i put: (FloatVector3 new). 1 to: 3 do:[:j| (BoxNormals at: i) at: j put: ((nrmls at: i) at: j)]]." BoxFaceIndexes := #( (1 2 3 4) (4 3 7 8) (8 7 6 5) (5 6 2 1) (6 7 3 2) (8 5 1 4)). BoxColors _ #(red green blue yellow gray cyan) collect:[:s| (Color perform: s) alpha: 0.5].! ! !B3DBox class methodsFor: 'instance creation'! from: origin to: corner ^self new buildBoxFrom: origin to: corner! ! Object subclass: #B3DCamera instanceVariableNames: 'position target up perspective ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Viewing'! !B3DCamera commentStamp: '' prior: 0! I represent a simple perspective camera. Instance variables: position where the camera is located target where the camera is aiming at up what is considered to be 'up' on screen perspective the actual camera perspective! !B3DCamera methodsFor: 'accessing'! aspectRatio ^perspective aspectRatio! ! !B3DCamera methodsFor: 'accessing'! aspectRatio: aFloat ^perspective aspectRatio: aFloat! ! !B3DCamera methodsFor: 'accessing' stamp: 'ar 2/8/1999 16:48'! direction ^target - position! ! !B3DCamera methodsFor: 'accessing' stamp: 'ar 2/8/1999 16:48'! direction: aVector target _ position + aVector.! ! !B3DCamera methodsFor: 'accessing'! farDistance ^perspective farDistance! ! !B3DCamera methodsFor: 'accessing'! farDistance: aFloat ^perspective farDistance: aFloat! ! !B3DCamera methodsFor: 'accessing'! fieldOfView ^perspective fieldOfView! ! !B3DCamera methodsFor: 'accessing'! fieldOfView: aFloat ^perspective fieldOfView: aFloat! ! !B3DCamera methodsFor: 'accessing'! fov ^self fieldOfView! ! !B3DCamera methodsFor: 'accessing'! fov: aNumber self fieldOfView: aNumber! ! !B3DCamera methodsFor: 'accessing'! nearDistance ^perspective nearDistance! ! !B3DCamera methodsFor: 'accessing'! nearDistance: aFloat ^perspective nearDistance: aFloat! ! !B3DCamera methodsFor: 'accessing'! perspective ^perspective! ! !B3DCamera methodsFor: 'accessing'! perspective: aPerspective perspective _ aPerspective! ! !B3DCamera methodsFor: 'accessing'! position ^position! ! !B3DCamera methodsFor: 'accessing'! position: aVector position _ aVector! ! !B3DCamera methodsFor: 'accessing'! target ^target! ! !B3DCamera methodsFor: 'accessing'! target: aVector target _ aVector! ! !B3DCamera methodsFor: 'accessing'! up ^up! ! !B3DCamera methodsFor: 'accessing'! up: aVector up _ aVector! ! !B3DCamera methodsFor: 'initialize' stamp: 'ar 3/19/2000 14:12'! from3DS: aDictionary "Initialize the receiver from a 3DS camera. Note: #near and #far are NOT clipping planes in 3DS!!" self position: (aDictionary at: #position). self target: (aDictionary at: #target). self up: (0@1@0). self flag: #TODO. "Include #roll value for upDirection" self fieldOfView: 2400.0 / (aDictionary at: #focal).! ! !B3DCamera methodsFor: 'initialize'! from: positionVector to: targetVector up: upVector position := positionVector. target := targetVector. up := upVector.! ! !B3DCamera methodsFor: 'initialize' stamp: 'ar 2/5/1999 21:22'! initialize position := B3DVector3 x: 0.0 y: 0.0 z: 1.0. target := B3DVector3 x: 0.0 y: 0.0 z: 0.0. up := B3DVector3 x: 0.0 y: 1.0 z: 0.0. perspective := B3DCameraPerspective new. self fov: 45.0. self aspectRatio: 1.0. self nearDistance: 0.0001. self farDistance: 10000.0.! ! !B3DCamera methodsFor: 'initialize' stamp: 'ti 3/27/2000 17:03'! setClippingPlanesFrom: anObject "Set the clipping planes from the given object" | box center radius avgDist | box _ anObject boundingBox. center _ (box origin + box corner) * 0.5. radius _ (center - box origin) length. avgDist _ (position - center) length. self farDistance: avgDist + radius. avgDist > radius ifTrue:[self nearDistance: ((((center - position) normalized dot: (self direction normalized)) * avgDist - radius) max: 1.0e-31)] ifFalse:[self nearDistance: (self farDistance * 0.00001)].! ! !B3DCamera methodsFor: 'initialize' stamp: 'ar 2/15/1999 01:04'! setTargetFrom: anObject "Make the camera point at the given object" | box | box _ anObject boundingBox. self target: (box origin + box corner) * 0.5.! ! !B3DCamera methodsFor: 'rendering'! renderOn: aRenderer aRenderer lookFrom: self position to: self target up: self up. aRenderer perspective: self perspective.! ! !B3DCamera methodsFor: 'experimental' stamp: 'ar 2/17/1999 05:41'! changeDistanceBy: delta position _ target + (position - target * delta)! ! !B3DCamera methodsFor: 'experimental' stamp: 'ar 2/17/1999 16:05'! moveToFit: aScene "Move the camera to fit the given scene. Experimental." | distance center | self setTargetFrom: aScene. center _ (aScene boundingBox origin + aScene boundingBox corner) * 0.5. distance _ (aScene boundingBox origin - center) length * 1.3. distance _ distance / (target - position) length. "self inform:'Distance ', distance printString." self changeDistanceBy: distance.! ! !B3DCamera methodsFor: 'experimental' stamp: 'ar 2/15/1999 23:47'! rotateBy: angle "Experimental -- rotate around the current up vector by angle degrees. Center at the target point." position _ (B3DMatrix4x4 rotatedBy: angle around: up centeredAt: target) localPointToGlobal: position.! ! !B3DCamera methodsFor: 'converting' stamp: 'ti 3/22/2000 10:46'! asMatrix4x4 | xDir yDir zDir m | "calculate z vector" zDir _ self target - self position. zDir safelyNormalize. "calculate x vector" xDir _ self up cross: zDir. xDir safelyNormalize. "recalc y vector" yDir _ zDir cross: xDir. yDir safelyNormalize. m := B3DMatrix4x4 new. m a11: xDir x; a12: xDir y; a13: xDir z; a14: 0.0; a21: yDir x; a22: yDir y; a23: yDir z; a24: 0.0; a31: zDir x; a32: zDir y; a33: zDir z; a34: 0.0; a41: 0.0; a42: 0.0; a43: 0.0; a44: 1.0. m := m composeWith: (B3DMatrix4x4 identity setTranslation: self position negated). ^m! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DCamera class instanceVariableNames: ''! !B3DCamera class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 23:37'! from3DS: aDictionary ^self new from3DS: aDictionary! ! !B3DCamera class methodsFor: 'instance creation'! new ^super new initialize! ! Object subclass: #B3DCameraPerspective instanceVariableNames: 'nearDistance farDistance fieldOfView aspectRatio ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Viewing'! !B3DCameraPerspective commentStamp: '' prior: 0! I represent a perspective projection. Instance variables: nearDistance Near clipping plane distance farDistance Far clipping plane distance fieldOfView The field of view covered by the perspective aspectRatio The aspect ratio to be included ! !B3DCameraPerspective methodsFor: 'converting'! asFrustum ^B3DViewingFrustum near: nearDistance far: farDistance fov: fieldOfView aspect: aspectRatio! ! !B3DCameraPerspective methodsFor: 'converting'! asMatrix4x4 ^self asFrustum asPerspectiveMatrix! ! !B3DCameraPerspective methodsFor: 'accessing'! aspectRatio ^aspectRatio! ! !B3DCameraPerspective methodsFor: 'accessing'! aspectRatio: aNumber aspectRatio _ aNumber! ! !B3DCameraPerspective methodsFor: 'accessing'! farDistance ^farDistance! ! !B3DCameraPerspective methodsFor: 'accessing'! farDistance: aNumber farDistance _ aNumber! ! !B3DCameraPerspective methodsFor: 'accessing'! fieldOfView ^fieldOfView! ! !B3DCameraPerspective methodsFor: 'accessing'! fieldOfView: aNumber fieldOfView _ aNumber! ! !B3DCameraPerspective methodsFor: 'accessing'! nearDistance ^nearDistance! ! !B3DCameraPerspective methodsFor: 'accessing'! nearDistance: aNumber nearDistance _ aNumber! ! B3DEnginePlugin subclass: #B3DClipperPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DClipperPlugin methodsFor: 'primitives' stamp: 'ar 11/20/2000 22:47'! b3dClipPolygon "Primitive. Clip the polygon given in the vertexArray using the temporary vertex array which is assumed to have sufficient size." | outMask vtxCount vtxArray tempVtxArray count | self export: true. self inline: false. self var: #vtxArray declareC:'int *vtxArray'. self var: #tempVtxArray declareC:'int *tempVtxArray'. interpreterProxy methodArgumentCount = 4 ifFalse:[^interpreterProxy primitiveFail]. outMask _ interpreterProxy stackIntegerValue: 0. vtxCount _ interpreterProxy stackIntegerValue: 2. vtxArray _ self stackPrimitiveVertexArray: 3 ofSize: vtxCount + 4. tempVtxArray _ self stackPrimitiveVertexArray: 1 ofSize: vtxCount + 4. (vtxArray == nil or:[tempVtxArray == nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. "Hack pointers for one-based indexes" vtxArray _ vtxArray - PrimVertexSize. tempVtxArray _ tempVtxArray - PrimVertexSize. count _ self clipPolygon: vtxArray count: vtxCount with: tempVtxArray mask: outMask. interpreterProxy pop: 5. interpreterProxy pushInteger: count.! ! !B3DClipperPlugin methodsFor: 'primitives' stamp: 'ar 4/16/1999 01:54'! b3dDetermineClipFlags "Primitive. Determine the clipping flags for all vertices." | vtxCount vtxArray result | self export: true. self inline: false. self var: #vtxArray declareC:'void *vtxArray'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. vtxCount _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. vtxArray _ self stackPrimitiveVertexArray: 1 ofSize: vtxCount. (vtxArray == nil or:[interpreterProxy failed]) ifTrue:[^interpreterProxy primitiveFail]. result _ self determineClipFlags: vtxArray count: vtxCount. interpreterProxy failed ifFalse:[ interpreterProxy pop: 3. interpreterProxy pushInteger: result. ].! ! !B3DClipperPlugin methodsFor: 'primitives' stamp: 'ar 4/18/1999 02:59'! b3dPrimitiveNextClippedTriangle "Primitive. Return the next clipped triangle from the vertex buffer and return its index." | idxCount vtxCount firstIndex vtxArray idxArray idx1 idx2 idx3 triMask | self export: true. self inline: false. self var: #idxArray declareC:'int *idxArray'. self var: #vtxArray declareC:'int *vtxArray'. interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. idxCount _ interpreterProxy stackIntegerValue: 0. vtxCount _ interpreterProxy stackIntegerValue: 2. firstIndex _ interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. vtxArray _ self stackPrimitiveVertexArray: 3 ofSize: vtxCount. idxArray _ self stackPrimitiveIndexArray: 1 ofSize: idxCount validate: true forVertexSize: vtxCount. (vtxArray == nil or:[idxArray == nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. "Hack idxArray and vtxArray for 1-based indexes" idxArray _ idxArray - 1. vtxArray _ vtxArray - PrimVertexSize. firstIndex to: idxCount by: 3 do:[:i| idx1 _ idxArray at: i. idx2 _ idxArray at: i+1. idx3 _ idxArray at: i+2. (idx1 == 0 or:[idx2 == 0 or:[idx3 == 0]]) ifFalse:[ triMask _ ((vtxArray at: idx1 * PrimVertexSize + PrimVtxClipFlags) bitAnd: ((vtxArray at: idx2 * PrimVertexSize + PrimVtxClipFlags) bitAnd: (vtxArray at: idx3 * PrimVertexSize + PrimVtxClipFlags))). "Check if tri is completely inside" (InAllMask bitAnd: triMask) = InAllMask ifFalse:[ "Tri is not completely inside -> needs clipping." (triMask anyMask: OutAllMask) ifTrue:[ "tri is completely outside. Store all zeros" idxArray at: i put: 0. idxArray at: i+1 put: 0. idxArray at: i+2 put: 0. ] ifFalse:[ "tri must be partially clipped." interpreterProxy pop: 6. "args + rcvr" interpreterProxy pushInteger: i. ^nil ]. ]. ]. ]. "No more entries" interpreterProxy pop: 6. "args + rcvr" interpreterProxy pushInteger: 0. ! ! !B3DClipperPlugin methodsFor: 'clipping' stamp: 'ar 4/16/1999 06:03'! clipPolygon: vtxArray count: vtxCount with: tempVtxArray mask: outMask | count | self var: #vtxArray declareC:'int *vtxArray'. self var: #tempVtxArray declareC:'int *tempVtxArray'. "Check if the polygon is outside one boundary only. If so, just do this single clipping operation avoiding multiple enumeration." outMask = OutLeftBit ifTrue:[^self clipPolygonLeftFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutRightBit ifTrue:[^self clipPolygonRightFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutTopBit ifTrue:[^self clipPolygonTopFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutBottomBit ifTrue:[^self clipPolygonBottomFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutFrontBit ifTrue:[^self clipPolygonFrontFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutBackBit ifTrue:[^self clipPolygonBackFrom: tempVtxArray to: vtxArray count: vtxCount]. "Just do each of the clipping operations" count _ vtxCount. count _ self clipPolygonLeftFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonRightFrom: tempVtxArray to: vtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonTopFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonBottomFrom: tempVtxArray to: vtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonFrontFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonBackFrom: tempVtxArray to: vtxArray count: count. ^count! ! !B3DClipperPlugin methodsFor: 'clipping' stamp: 'ar 4/16/1999 01:57'! determineClipFlags: vtxArray count: count | vtxPtr fullMask w w2 flags x y z | self var: #vtxPtr declareC:'float *vtxPtr'. self var: #vtxArray declareC:'void *vtxArray'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #w declareC:'double w'. self var: #w2 declareC:'double w2'. vtxPtr _ self cCoerce: vtxArray to: 'float *'. fullMask _ InAllMask + OutAllMask. 1 to: count do:[:i| w _ vtxPtr at: PrimVtxRasterPosW. w2 _ 0.0 - w. flags _ 0. x _ vtxPtr at: PrimVtxRasterPosX. x >= w2 ifTrue:[flags _ flags bitOr: InLeftBit] ifFalse:[flags _ flags bitOr: OutLeftBit]. x <= w ifTrue:[flags _ flags bitOr: InRightBit] ifFalse:[flags _ flags bitOr: OutRightBit]. y _ vtxPtr at: PrimVtxRasterPosY. y >= w2 ifTrue:[flags _ flags bitOr: InBottomBit] ifFalse:[flags _ flags bitOr: OutBottomBit]. y <= w ifTrue:[flags _ flags bitOr: InTopBit] ifFalse:[flags _ flags bitOr: OutTopBit]. z _ vtxPtr at: PrimVtxRasterPosZ. z >= w2 ifTrue:[flags _ flags bitOr: InFrontBit] ifFalse:[flags _ flags bitOr: OutFrontBit]. z <= w ifTrue:[flags _ flags bitOr: InBackBit] ifFalse:[flags _ flags bitOr: OutBackBit]. fullMask _ fullMask bitAnd: flags. (self cCoerce: vtxPtr to:'int *') at: PrimVtxClipFlags put: flags. vtxPtr _ vtxPtr + PrimVertexSize. ]. ^fullMask! ! !B3DClipperPlugin methodsFor: 'clipping' stamp: 'ar 4/21/1999 01:26'! interpolateFrom: last to: next at: t into: out "Interpolate the primitive vertices last/next at the parameter t" | delta rgbaLast lastValue rgbaNext nextValue newValue x y z w w2 flags | self var: #last declareC:'float *last'. self var: #next declareC:'float *next'. self var: #out declareC:'float *out'. self var: #t declareC: 'double t'. self var: #delta declareC: 'double delta'. self var: #rgbaLast declareC:'unsigned int rgbaLast'. self var: #rgbaNext declareC:'unsigned int rgbaNext'. self var: #lastValue declareC:'unsigned int lastValue'. self var: #nextValue declareC:'unsigned int nextValue'. self var: #newValue declareC:'unsigned int newValue'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #w declareC:'double w'. self var: #w2 declareC:'double w2'. "Interpolate raster position" delta _ (next at: PrimVtxRasterPosX) - (last at: PrimVtxRasterPosX). x _ (last at: PrimVtxRasterPosX) + (delta * t). out at: PrimVtxRasterPosX put: (self cCoerce: x to: 'float'). delta _ (next at: PrimVtxRasterPosY) - (last at: PrimVtxRasterPosY). y _ (last at: PrimVtxRasterPosY) + (delta * t). out at: PrimVtxRasterPosY put: (self cCoerce: y to: 'float'). delta _ (next at: PrimVtxRasterPosZ) - (last at: PrimVtxRasterPosZ). z _ (last at: PrimVtxRasterPosZ) + (delta * t). out at: PrimVtxRasterPosZ put: (self cCoerce: z to: 'float'). delta _ (next at: PrimVtxRasterPosW) - (last at: PrimVtxRasterPosW). w _ (last at: PrimVtxRasterPosW) + (delta * t). out at: PrimVtxRasterPosW put: (self cCoerce: w to: 'float'). "Determine new clipFlags" w2 _ 0.0 - w. flags _ 0. x >= w2 ifTrue:[flags _ flags bitOr: InLeftBit] ifFalse:[flags _ flags bitOr: OutLeftBit]. x <= w ifTrue:[flags _ flags bitOr: InRightBit] ifFalse:[flags _ flags bitOr: OutRightBit]. y >= w2 ifTrue:[flags _ flags bitOr: InBottomBit] ifFalse:[flags _ flags bitOr: OutBottomBit]. y <= w ifTrue:[flags _ flags bitOr: InTopBit] ifFalse:[flags _ flags bitOr: OutTopBit]. z >= w2 ifTrue:[flags _ flags bitOr: InFrontBit] ifFalse:[flags _ flags bitOr: OutFrontBit]. z <= w ifTrue:[flags _ flags bitOr: InBackBit] ifFalse:[flags _ flags bitOr: OutBackBit]. (self cCoerce: out to: 'int *') at: PrimVtxClipFlags put: flags. "Interpolate color" rgbaLast _ (self cCoerce: last to:'unsigned int *') at: PrimVtxColor32. lastValue _ rgbaLast bitAnd: 255. rgbaLast _ rgbaLast >> 8. rgbaNext _ (self cCoerce: next to: 'unsigned int *') at: PrimVtxColor32. nextValue _ rgbaNext bitAnd: 255. rgbaNext _ rgbaNext >> 8. delta _ (self cCoerce: (nextValue - lastValue) to:'int') * t. newValue _ (lastValue + delta) asInteger. lastValue _ rgbaLast bitAnd: 255. rgbaLast _ rgbaLast >> 8. nextValue _ rgbaNext bitAnd: 255. rgbaNext _ rgbaNext >> 8. delta _ (self cCoerce: (nextValue - lastValue) to:'int') * t. newValue _ newValue + ((lastValue + delta) asInteger << 8). lastValue _ rgbaLast bitAnd: 255. rgbaLast _ rgbaLast >> 8. nextValue _ rgbaNext bitAnd: 255. rgbaNext _ rgbaNext >> 8. delta _ (self cCoerce: (nextValue - lastValue) to:'int') * t. newValue _ newValue + ((lastValue + delta) asInteger << 16). lastValue _ rgbaLast bitAnd: 255. nextValue _ rgbaNext bitAnd: 255. delta _ (self cCoerce: (nextValue - lastValue) to:'int') * t. newValue _ newValue + ((lastValue + delta) asInteger << 24). (self cCoerce: out to:'unsigned int*') at: PrimVtxColor32 put: newValue. "Interpolate texture coordinates" delta _ (next at: PrimVtxTexCoordU) - (last at: PrimVtxTexCoordU). out at: PrimVtxTexCoordU put: (self cCoerce: (last at: PrimVtxTexCoordU) + (delta * t) to:'float'). delta _ (next at: PrimVtxTexCoordV) - (last at: PrimVtxTexCoordV). out at: PrimVtxTexCoordV put: (self cCoerce: (last at: PrimVtxTexCoordV) + (delta * t) to:'float'). ! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 01:56'! backClipValueFrom: last to: next self returnTypeC:'double'. ^(((self cCoerce: last to: 'float *') at: PrimVtxRasterPosZ) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW)) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) - (((self cCoerce: next to:'float *') at: PrimVtxRasterPosZ) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosZ)) ).! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 06:43'! bottomClipValueFrom: last to: next self returnTypeC:'double'. ^(0.0 - (((self cCoerce: last to: 'float *') at: PrimVtxRasterPosY) + ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW))) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) + (((self cCoerce: next to:'float *') at: PrimVtxRasterPosY) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosY)) ).! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:18'! clipPolygonBackFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InBackBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InBackBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self backClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:18'! clipPolygonBottomFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InBottomBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InBottomBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self bottomClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:18'! clipPolygonFrontFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InFrontBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InFrontBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self frontClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:18'! clipPolygonLeftFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InLeftBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InLeftBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self leftClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:18'! clipPolygonRightFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InRightBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InRightBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self rightClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/17/1999 22:19'! clipPolygonTopFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext | self var: #buf1 declareC:'int *buf1'. self var: #buf2 declareC:'int *buf2'. self var: #last declareC:'int *last'. self var: #next declareC:'int *next'. self var: #t declareC: 'double t'. outIndex _ 0. last _ buf1 + (n * PrimVertexSize). next _ buf1 + PrimVertexSize. inLast _ (last at: PrimVtxClipFlags) anyMask: InTopBit. 1 to: n do:[:i| inNext _ (next at: PrimVtxClipFlags) anyMask: InTopBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self topClipValueFrom: last to: next. outIndex _ outIndex + 1. self interpolateFrom: (self cCoerce: last to:'float *') to: (self cCoerce: next to:'float *') at: t into: (self cCoerce: (buf2 + (outIndex * PrimVertexSize)) to:'float*')]. inNext ifTrue:[ outIndex _ outIndex+1. 0 to: PrimVertexSize-1 do:[:j| buf2 at: (outIndex*PrimVertexSize + j) put: (next at: j)]. ]. last _ next. inLast _ inNext. next _ next + PrimVertexSize. ]. ^outIndex! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 06:43'! frontClipValueFrom: last to: next self returnTypeC:'double'. ^(0.0 - (((self cCoerce: last to: 'float *') at: PrimVtxRasterPosZ) + ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW))) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) + (((self cCoerce: next to:'float *') at: PrimVtxRasterPosZ) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosZ)) ).! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 06:43'! leftClipValueFrom: last to: next self returnTypeC:'double'. ^(0.0 - (((self cCoerce: last to: 'float *') at: PrimVtxRasterPosX) + ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW))) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) + (((self cCoerce: next to:'float *') at: PrimVtxRasterPosX) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosX)) ).! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 01:56'! rightClipValueFrom: last to: next self returnTypeC:'double'. ^(((self cCoerce: last to: 'float *') at: PrimVtxRasterPosX) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW)) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) - (((self cCoerce: next to:'float *') at: PrimVtxRasterPosX) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosX)) ).! ! !B3DClipperPlugin methodsFor: 'clipping utilities' stamp: 'ar 4/16/1999 01:56'! topClipValueFrom: last to: next self returnTypeC:'double'. ^(((self cCoerce: last to: 'float *') at: PrimVtxRasterPosY) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosW)) / ( (((self cCoerce: next to:'float *') at: PrimVtxRasterPosW) - ((self cCoerce: last to: 'float *') at: PrimVtxRasterPosW)) - (((self cCoerce: next to:'float *') at: PrimVtxRasterPosY) - ((self cCoerce: last to:'float *') at: PrimVtxRasterPosY)) ).! ! B3DFloatArray variableWordSubclass: #B3DColor4 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DColor4 commentStamp: '' prior: 0! I represent an RGBA color value in floating point format. I am used during the lighting and shading computations.! !B3DColor4 methodsFor: 'accessing'! alpha ^self floatAt: 4! ! !B3DColor4 methodsFor: 'accessing'! alpha: aNumber self floatAt: 4 put: aNumber! ! !B3DColor4 methodsFor: 'accessing'! blue ^self floatAt: 3! ! !B3DColor4 methodsFor: 'accessing'! blue: aNumber self floatAt: 3 put: aNumber! ! !B3DColor4 methodsFor: 'accessing'! green ^self floatAt: 2! ! !B3DColor4 methodsFor: 'accessing'! green: aNumber self floatAt: 2 put: aNumber! ! !B3DColor4 methodsFor: 'accessing'! red ^self floatAt: 1! ! !B3DColor4 methodsFor: 'accessing'! red: aNumber self floatAt: 1 put: aNumber! ! !B3DColor4 methodsFor: 'converting' stamp: 'ar 5/4/2000 17:59'! asB3DColor ^self! ! !B3DColor4 methodsFor: 'converting'! asColor ^Color r: self red g: self green b: self blue alpha: self alpha! ! !B3DColor4 methodsFor: 'converting' stamp: 'ar 2/4/1999 20:21'! pixelValue32 ^self asColor pixelWordForDepth: 32! ! !B3DColor4 methodsFor: 'private'! privateLoadFrom: srcObject | color | color _ srcObject asColor. self red: color red. self green: color green. self blue: color blue. self alpha: color alpha.! ! !B3DColor4 methodsFor: 'initialize' stamp: 'ar 2/7/1999 16:21'! r: rValue g: gValue b: bValue a: aValue self red: rValue. self green: gValue. self blue: bValue. self alpha: aValue.! ! !B3DColor4 methodsFor: 'testing' stamp: 'ar 2/15/1999 22:12'! isZero ^self alpha isZero! ! !B3DColor4 methodsFor: 'interpolating' stamp: 'jsp 2/8/1999 19:57'! interpolateTo: end at: amountDone "Return the color vector yielded by interpolating from the state of the object to the specified end state at the specified amount done" | newColor r g b a | r _ self red. g _ self green. b _ self blue. a _ self alpha. newColor _ B3DColor4 new. newColor red: r + (((end red) - r) * amountDone). newColor green: g + (((end green) - g) * amountDone). newColor blue: b + (((end blue) - b) * amountDone). newColor alpha: a + (((end alpha) - a) * amountDone). ^ newColor. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DColor4 class instanceVariableNames: ''! !B3DColor4 class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:22'! numElements ^4! ! !B3DColor4 class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 16:21'! r: rValue g: gValue b: bValue a: aValue ^self new r: rValue g: gValue b: bValue a: aValue! ! !B3DColor4 class methodsFor: 'instance creation' stamp: 'jsp 2/8/1999 18:46'! red: r green: g blue: b alpha: a "Create an initialize a color vector." | newColor | newColor _ B3DColor4 new. newColor red: r. newColor green: g. newColor blue: b. newColor alpha: a. ^ newColor. ! ! B3DInplaceArray variableWordSubclass: #B3DColor4Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! !B3DColor4Array commentStamp: '' prior: 0! I am an inplace storage area for B3DColor4 items used during lighting and shading.! !B3DColor4Array methodsFor: 'special ops' stamp: 'ar 2/4/1999 01:50'! += aColor "Add the given color to all the elements in the receiver" | r g b a | r _ aColor red. g _ aColor green. b _ aColor blue. a _ aColor alpha. 1 to: self basicSize by: 4 do:[:i| self floatAt: i put: (self floatAt: i) + r. self floatAt: i+1 put: (self floatAt: i+1) + g. self floatAt: i+2 put: (self floatAt: i+2) + b. self floatAt: i+3 put: (self floatAt: i+3) + a. ].! ! !B3DColor4Array methodsFor: 'special ops' stamp: 'ar 2/7/1999 16:44'! add: aB3dColor4 at: index | baseIdx | baseIdx _ index-1*4. self floatAt: baseIdx+1 put: (self floatAt: baseIdx+1) + aB3dColor4 red. self floatAt: baseIdx+2 put: (self floatAt: baseIdx+2) + aB3dColor4 green. self floatAt: baseIdx+3 put: (self floatAt: baseIdx+3) + aB3dColor4 blue. self floatAt: baseIdx+4 put: (self floatAt: baseIdx+4) + aB3dColor4 alpha. ! ! !B3DColor4Array methodsFor: 'special ops'! clampAllFrom: minValue to: maxValue "Clamp all elements in the receiver to be in the range (minValue, maxValue)" | value | 1 to: self basicSize do:[:i| value _ self floatAt: i. value _ value min: maxValue. value _ value max: minValue. self floatAt: i put: value. ].! ! !B3DColor4Array methodsFor: 'special ops'! fillWith: anInteger self primitiveFailed! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DColor4Array class instanceVariableNames: ''! !B3DColor4Array class methodsFor: 'instance creation' stamp: 'ar 2/5/1999 22:50'! contentsClass ^B3DColor4! ! B3DLightSource subclass: #B3DDirectionalLight instanceVariableNames: 'direction ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DDirectionalLight methodsFor: 'shading' stamp: 'ar 2/7/1999 16:54'! computeAttenuationFor: distance "Since a directional light is positioned at virtual infinity, it cannot have any attenuation" ^1.0! ! !B3DDirectionalLight methodsFor: 'shading' stamp: 'ar 2/7/1999 16:53'! computeDirectionTo: aB3DPrimitiveVertex "A directional light has an explicit direction regardless of the vertex position" ^direction! ! !B3DDirectionalLight methodsFor: 'converting' stamp: 'ar 2/15/1999 21:55'! asPrimitiveLight "Convert the receiver into a B3DPrimitiveLight" | primLight flags | primLight _ B3DPrimitiveLight new. primLight direction: direction. flags _ FlagDirectional. lightColor ambientPart isZero ifFalse:[ primLight ambientPart: lightColor ambientPart. flags _ flags bitOr: FlagAmbientPart]. lightColor diffusePart isZero ifFalse:[ primLight diffusePart: lightColor diffusePart. flags _ flags bitOr: FlagDiffusePart]. lightColor specularPart isZero ifFalse:[ primLight specularPart: lightColor specularPart. flags _ flags bitOr: FlagSpecularPart]. primLight flags: flags. ^primLight! ! !B3DDirectionalLight methodsFor: 'converting' stamp: 'ar 2/8/1999 01:28'! transformedBy: aTransformer ^(super transformedBy: aTransformer) direction: (aTransformer transformDirection: direction)! ! ExternalScreen subclass: #B3DDisplayScreen instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Acceleration'! !B3DDisplayScreen commentStamp: '' prior: 0! I represent a hardware accelerated 3D display. Usually, this means some sort of offscreen buffer (so that we can do efficient compositing for the 2D case) but it might be different. The first implementation uses Direct3D on Windows where compositing is trivially achieved by using DirectDrawSurfaces which can be accessed by either 2D or 3D operations.! !B3DDisplayScreen methodsFor: 'testing' stamp: 'ar 5/27/2000 17:16'! isB3DDisplayScreen ^true! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:47'! primBltFast: displayHandle from: sourceHandle at: destOrigin from: sourceOrigin extent: extent ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:47'! primBltFast: displayHandle to: dstHandle at: destOrigin from: sourceOrigin extent: extent "Primitive. Perform a fast blt operation. Return the receiver if successful." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:18'! primCreateDisplaySurface: d width: w height: h "Primitive. Create a new external display surface. Return the handle used to identify the receiver. Fail if the surface cannot be created." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:18'! primDestroyDisplaySurface: aHandle "Primitive. Destroy the display surface associated with the given handle." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:19'! primDisplay: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:19'! primFill: handle color: pixelWord x: x y: y w: w h: h "Primitive. Perform an accelerated fill operation on the receiver." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:20'! primFinish: aHandle "Primitive. Finish all rendering operations on the receiver. Do not return before all rendering operations have taken effect." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:21'! primFlush: aHandle "Primitive. If any rendering operations are pending, force them to be executed. Do not wait until they have taken effect." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:21'! supportsDisplayDepth: pixelDepth "Return true if this pixel depth is supported on the current host platform." ^false! ! !B3DDisplayScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:21'! primAllocateForm: d width: w height: h "Primitive. Allocate a form with the given parameters" ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:22'! primDestroyForm: aHandle "Primitive. Destroy the form associated with the given handle." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:22'! primForm: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 20:21'! primAllocateTexture: d width: w height: h "Primitive. Allocate a texture with the given dimensions. Note: The texture allocated may *not* match the specified values here." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:23'! primDestroyTexture: aHandle "Primitive. Destroy the texture associated with the given handle." ^nil! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:23'! primGetTextureDepth: aHandle "Primitive. Return the actual depth of the texture with the given handle" ^self primitiveFailed! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primGetTextureHeight: aHandle "Primitive. Return the actual height of the texture with the given handle" ^self primitiveFailed! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primGetTextureWidth: aHandle "Primitive. Return the actual width of the texture with the given handle" ^self primitiveFailed! ! !B3DDisplayScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primTexture: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! Object subclass: #B3DEnginePart instanceVariableNames: 'engine ' classVariableNames: 'PrimitiveActions ' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Engine'! !B3DEnginePart commentStamp: '' prior: 0! I am the superclass for all separate parts of the Balloon 3D engine. I define the basic interface each part of the engine must respond to. Instance variables: engine The 3D engine I am associated with! !B3DEnginePart methodsFor: 'initialize' stamp: 'ar 2/16/1999 01:47'! destroy "Destroy all resources temporarily assigned to the receiver"! ! !B3DEnginePart methodsFor: 'initialize' stamp: 'ar 2/5/1999 21:34'! flush "Flush all pending operations"! ! !B3DEnginePart methodsFor: 'initialize'! initialize ! ! !B3DEnginePart methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:52'! reset ! ! !B3DEnginePart methodsFor: 'private'! setEngine: aB3DRenderEngine engine _ aB3DRenderEngine! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/7/1999 03:39'! processIndexedLines: vb "Process an indexed line set"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/8/1999 15:36'! processIndexedQuads: vb "Process an indexed quad set"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/7/1999 03:39'! processIndexedTriangles: vb "Process an indexed triangle set"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/4/1999 04:21'! processLineLoop: vertexBuffer "Process a closed line defined by the vertex buffer"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/4/1999 04:21'! processLines: vertexBuffer "Process a series of lines defined by each two points the vertex buffer"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/4/1999 04:21'! processPoints: vertexBuffer "Process a series of points defined by the vertex buffer"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/4/1999 04:22'! processPolygon: vertexBuffer "Process a polygon defined by the vertex buffer"! ! !B3DEnginePart methodsFor: 'processing' stamp: 'ar 2/8/1999 15:35'! processVertexBuffer: vb "Process the given vertex buffer in this part of the engine." ^self perform: (PrimitiveActions at: vb primitive) with: vb! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DEnginePart class instanceVariableNames: ''! !B3DEnginePart class methodsFor: 'instance creation'! engine: aB3DRenderEngine ^self new setEngine: aB3DRenderEngine! ! !B3DEnginePart class methodsFor: 'instance creation'! new ^super new initialize! ! !B3DEnginePart class methodsFor: 'class initialization' stamp: 'ar 2/7/1999 19:52'! initialize "B3DEnginePart initialize" PrimitiveActions _ #( processPoints: processLines: processPolygon: processIndexedLines: processIndexedTriangles: processIndexedQuads: ).! ! !B3DEnginePart class methodsFor: 'testing' stamp: 'ar 2/14/1999 01:39'! isAvailable "Return true if this part of the engine is available" ^self subclassResponsibility! ! !B3DEnginePart class methodsFor: 'testing' stamp: 'ar 2/17/1999 04:39'! isAvailableFor: anOutputMedium "Return true if this part of the engine is available for the given output medium" ^self isAvailable! ! InterpreterPlugin subclass: #B3DEnginePlugin instanceVariableNames: 'loadBBFn copyBitsFn bbPluginName ' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'VMConstruction-Plugins'! !B3DEnginePlugin commentStamp: '' prior: 0! I am a generic superclass for all Balloon 3D plugins.! !B3DEnginePlugin methodsFor: 'primitive support' stamp: 'ar 2/14/1999 00:01'! stackMatrix: index "Load a 4x4 transformation matrix from the interpreter stack. Return a pointer to the matrix data if successful, nil otherwise." | oop | self inline: false. self returnTypeC:'void*'. oop _ interpreterProxy stackObjectValue: index. oop = nil ifTrue:[^nil]. ((interpreterProxy isWords: oop) and:[(interpreterProxy slotSizeOf: oop) = 16]) ifTrue:[^interpreterProxy firstIndexableField: oop]. ^nil! ! !B3DEnginePlugin methodsFor: 'primitive support' stamp: 'ar 4/12/1999 02:15'! stackPrimitiveIndexArray: stackIndex ofSize: nItems validate: aBool forVertexSize: maxIndex "Load a primitive index array from the interpreter stack. If aBool is true then check that all the indexes are in the range (1,maxIndex). Return a pointer to the index data if successful, nil otherwise." | oop oopSize idxPtr index | self inline: false. self returnTypeC:'void*'. self var: #idxPtr declareC:'int *idxPtr'. oop _ interpreterProxy stackObjectValue: stackIndex. oop = nil ifTrue:[^nil]. (interpreterProxy isWords: oop) ifFalse:[^nil]. oopSize _ interpreterProxy slotSizeOf: oop. oopSize < nItems ifTrue:[^nil]. idxPtr _ self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int *'. aBool ifTrue:[ 0 to: nItems-1 do:[:i| index _ idxPtr at: i. (index < 0 or:[index > maxIndex]) ifTrue:[^nil]]]. ^idxPtr! ! !B3DEnginePlugin methodsFor: 'primitive support' stamp: 'ar 2/14/1999 00:00'! stackPrimitiveVertex: index "Load a primitive vertex from the interpreter stack. Return a pointer to the vertex data if successful, nil otherwise." | oop | self inline: false. self returnTypeC:'void*'. oop _ interpreterProxy stackObjectValue: index. oop = nil ifTrue:[^nil]. ((interpreterProxy isWords: oop) and:[(interpreterProxy slotSizeOf: oop) = PrimVertexSize]) ifTrue:[^interpreterProxy firstIndexableField: oop]. ^nil! ! !B3DEnginePlugin methodsFor: 'primitive support' stamp: 'ar 2/14/1999 00:00'! stackPrimitiveVertexArray: index ofSize: nItems "Load a primitive vertex array from the interpreter stack. Return a pointer to the vertex data if successful, nil otherwise." | oop oopSize | self inline: false. self returnTypeC:'void*'. oop _ interpreterProxy stackObjectValue: index. oop = nil ifTrue:[^nil]. (interpreterProxy isWords: oop) ifTrue:[ oopSize _ interpreterProxy slotSizeOf: oop. (oopSize >= nItems * PrimVertexSize and:[oopSize \\ PrimVertexSize = 0]) ifTrue:[^interpreterProxy firstIndexableField: oop]]. ^nil! ! !B3DEnginePlugin methodsFor: 'initialize-release' stamp: 'ar 5/16/2000 20:05'! initialiseModule self export: true. loadBBFn _ interpreterProxy ioLoadFunction: 'loadBitBltFrom' From: bbPluginName. copyBitsFn _ interpreterProxy ioLoadFunction: 'copyBitsFromtoat' From: bbPluginName. ^(loadBBFn ~= 0 and:[copyBitsFn ~= 0])! ! !B3DEnginePlugin methodsFor: 'initialize-release' stamp: 'ar 5/16/2000 20:05'! moduleUnloaded: aModuleName "The module with the given name was just unloaded. Make sure we have no dangling references." self export: true. self var: #aModuleName type: 'char *'. (aModuleName strcmp: bbPluginName) = 0 ifTrue:[ "BitBlt just shut down. How nasty." loadBBFn _ 0. copyBitsFn _ 0. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DEnginePlugin class instanceVariableNames: ''! !B3DEnginePlugin class methodsFor: 'translation' stamp: 'ar 5/16/2000 20:05'! declareCVarsIn: cg cg var: 'bbPluginName' declareC:'char bbPluginName[256] = "BitBltPlugin"'.! ! !B3DEnginePlugin class methodsFor: 'translation' stamp: 'ar 2/8/1999 20:48'! moduleName ^'Squeak3D'! ! !B3DEnginePlugin class methodsFor: 'translation' stamp: 'TPR 5/23/2000 17:26'! translateB3D "B3DEnginePlugin translateB3D" "Translate all the basic plugins into one support module and write the C sources for the rasterizer." | cg | cg _ PluggableCodeGenerator new initialize. cg declareModuleName: self moduleNameAndVersion local: false. {InterpreterPlugin. B3DEnginePlugin. B3DTransformerPlugin. B3DVertexBufferPlugin. B3DShaderPlugin. B3DClipperPlugin. B3DPickerPlugin. B3DRasterizerPlugin} do: [:theClass | theClass initialize. cg addClass: theClass. theClass declareCVarsIn: cg]. cg storeCodeOnFile: self moduleName , '.c' doInlining: true. " cg storeCodeOnFile: '/tmp/Ballon3D.c' doInlining: true." B3DRasterizerPlugin writeSupportCode: true! ! !B3DEnginePlugin class methodsFor: 'translation' stamp: 'ar 2/3/2001 15:44'! translateOn: cg inlining: inlineFlag to: fullName local: localFlag "do the actual translation" {InterpreterPlugin. B3DEnginePlugin. B3DTransformerPlugin. B3DVertexBufferPlugin. B3DShaderPlugin. B3DClipperPlugin. B3DPickerPlugin. B3DRasterizerPlugin} do: [:theClass | theClass initialize. cg addClass: theClass. theClass declareCVarsIn: cg]. cg storeCodeOnFile: fullName doInlining: inlineFlag. B3DRasterizerPlugin writeSupportCode: true. ! ! B3DFloatArray variableWordSubclass: #B3DExponentTable instanceVariableNames: '' classVariableNames: 'DefaultExponents ' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DExponentTable commentStamp: '' prior: 0! I represent a lookup table for several exponents during lighting. Values are computed based on linear interpolation between the stored elements. New tables are created by providing a one argument initialization block from which I am created.! !B3DExponentTable methodsFor: 'initialize' stamp: 'ar 2/8/1999 00:08'! initializeFrom: aBlock | last next | last _ nil. 1 to: self size // 2 do:[:i| next _ aBlock value: (i-1) / (self size // 2 - 1) asFloat. (next isInfinite or:[next isNaN]) ifTrue:[next _ 0.0]. self at: i*2-1 put: next. i > 1 ifTrue:[self at: i-1*2 put: next - last]. last _ next. ].! ! !B3DExponentTable methodsFor: 'accessing' stamp: 'ar 2/8/1999 00:10'! valueAt: aFloat "Return the table approximation for the given float value" | index max | aFloat < 0.0 ifTrue:[^self error:'Cannot use negative numbers in table lookup']. max _ self size // 2. index _ (max * aFloat) asInteger + 1. index >= max ifTrue:[^self at: self size-1]. "Linear interpolation inbetween" ^(self at: index) + (aFloat - (index-1) * (self at: index+1))! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DExponentTable class instanceVariableNames: ''! !B3DExponentTable class methodsFor: 'class initialization' stamp: 'ar 2/8/1999 00:02'! initialize "B3DExponentTable initialize" DefaultExponents _ Dictionary new. 0 to: 2 do:[:i| DefaultExponents at: i put: (self using:[:value| value raisedTo: i]). ].! ! !B3DExponentTable class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 23:56'! new ^self using:[:value| value]! ! !B3DExponentTable class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 23:55'! numElements ^128! ! !B3DExponentTable class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 23:59'! using: aBlock "Create a new exponent table using aBlock as initialization" ^super new initializeFrom: aBlock! ! Object subclass: #B3DFillList instanceVariableNames: 'firstFace lastFace ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DFillList methodsFor: 'initialize' stamp: 'ar 4/4/1999 04:28'! initialize self reset.! ! !B3DFillList methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:39'! first ^firstFace! ! !B3DFillList methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:49'! last ^lastFace! ! !B3DFillList methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:31'! reset firstFace _ lastFace _ nil.! ! !B3DFillList methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:00'! searchForNewTopAtX: xValue y: yValue "A top face ended with no known right face. We have to search the fill list for the face with the smallest z value. Note: In theory, this should only happen on *right* boundaries of meshes and thus not affect performance too much. Having the fillList sorted by its minimal z value should help, too." | face topFace topZ faceZ floatX floatY | self isEmpty ifTrue:[^self]. "No top" floatX _ xValue / 4096.0. floatY _ yValue. face _ self first. topFace _ face. topZ _ face zValueAtX: floatX y: floatY. [face _ face nextFace. face == nil] whileFalse:[ face minZ > topZ ifTrue:[ "Done. Everything else is behind." self remove: topFace. self addFront: topFace. ^self]. faceZ _ face zValueAtX: floatX y: floatY. faceZ < topZ ifTrue:[ topZ _ faceZ. topFace _ face]]. self remove: topFace. self addFront: topFace.! ! !B3DFillList methodsFor: 'accessing' stamp: 'ar 4/4/1999 23:53'! size | n face | n _ 0. face _ firstFace. [face == nil] whileFalse:[ n _ n + 1. face _ face nextFace. ]. ^n! ! !B3DFillList methodsFor: 'adding' stamp: 'ar 4/5/1999 20:38'! addBack: aFace "Add the given face as a non-front face (e.g., insert it after the front face). Make sure that the receiver stays sorted by the minimal z values of faces." | minZ midZ face | firstFace == nil ifTrue:[^self error:'Inserting a back face with no front face']. minZ _ aFace minZ. "Quick optimization for insertion at end" (firstFace == lastFace or:[minZ >= lastFace minZ]) ifTrue:[^self addLast: aFace]. "Try an estimation for how to search" midZ _ (firstFace nextFace minZ + lastFace minZ) * 0.5. minZ <= midZ ifTrue:[ "Search front to back" face _ firstFace nextFace. [face minZ < minZ] whileTrue:[face _ face nextFace]. ] ifFalse:[ "Search back to front" face _ lastFace prevFace. "Already checked for lastFace minZ < face minZ" [face minZ > minZ] whileTrue:[face _ face prevFace]. face _ face nextFace. ]. self insert: aFace before: face.! ! !B3DFillList methodsFor: 'adding' stamp: 'ar 4/18/1999 08:04'! addFirst: aFace firstFace isNil ifTrue:[lastFace _ aFace] ifFalse:[firstFace prevFace: aFace]. aFace nextFace: firstFace. aFace prevFace: nil. firstFace _ aFace. B3DScanner doDebug ifTrue:[self validate].! ! !B3DFillList methodsFor: 'adding' stamp: 'ar 4/5/1999 20:41'! addFront: aFace "Add the given face as the new front face. Make sure the sort order stays okay." | backFace minZ tempFace | firstFace == lastFace ifFalse:["firstFace == lastFace denotes 0 or 1 elements" backFace _ firstFace nextFace. minZ _ firstFace minZ. [backFace notNil and:[backFace minZ < minZ]] whileTrue:[backFace _ backFace nextFace]. "backFace contains the face before which firstFace has to be added" firstFace nextFace == backFace ifFalse:[ tempFace _ firstFace. self remove: tempFace. backFace == nil ifTrue:[self addLast: tempFace] ifFalse:[self insert: tempFace before: backFace]. ]. ]. ^self addFirst: aFace! ! !B3DFillList methodsFor: 'adding' stamp: 'ar 4/18/1999 08:04'! addLast: aFace lastFace isNil ifTrue:[firstFace _ aFace] ifFalse:[lastFace nextFace: aFace]. aFace prevFace: lastFace. aFace nextFace: nil. lastFace _ aFace. B3DScanner doDebug ifTrue:[self validate].! ! !B3DFillList methodsFor: 'adding' stamp: 'ar 4/18/1999 08:04'! insert: aFace before: nextFace "Insert the given face before nextFace." B3DScanner doDebug ifTrue:[ (self includes: nextFace) ifFalse:[^self error:'Face not in collection']. (self includes: aFace) ifTrue:[^self error:'Face already in collection']. ]. aFace nextFace: nextFace. aFace prevFace: nextFace prevFace. aFace prevFace nextFace: aFace. nextFace prevFace: aFace. B3DScanner doDebug ifTrue:[self validate].! ! !B3DFillList methodsFor: 'removing' stamp: 'ar 4/18/1999 08:04'! remove: aFace (B3DScanner doDebug and:[(self includes: aFace) not]) ifTrue:[^self error:'Face not in list']. B3DScanner doDebug ifTrue:[self validate]. aFace prevFace isNil ifTrue:[firstFace _ aFace nextFace] ifFalse:[aFace prevFace nextFace: aFace nextFace]. aFace nextFace isNil ifTrue:[lastFace _ aFace prevFace] ifFalse:[aFace nextFace prevFace: aFace prevFace]. ^aFace! ! !B3DFillList methodsFor: 'enumerating' stamp: 'ar 4/18/1999 08:03'! do: aBlock | face | B3DScanner doDebug ifTrue:[self validate]. face _ firstFace. [face == nil] whileFalse:[ aBlock value: face. face _ face nextFace. ].! ! !B3DFillList methodsFor: 'testing' stamp: 'ar 4/5/1999 01:58'! includes: aFace | face | face _ firstFace. [face == nil] whileFalse:[ face == aFace ifTrue:[^true]. face _ face nextFace. ]. ^false! ! !B3DFillList methodsFor: 'testing' stamp: 'ar 4/3/1999 00:49'! isEmpty ^firstFace == nil! ! !B3DFillList methodsFor: 'private' stamp: 'ar 4/5/1999 03:54'! printOn: aStream super printOn: aStream. aStream nextPut:$(; print: self size; nextPut: $).! ! !B3DFillList methodsFor: 'private' stamp: 'ar 4/5/1999 20:27'! validate | face | (firstFace == nil and:[lastFace == nil]) ifTrue:[^self]. firstFace prevFace == nil ifFalse:[^self error:'Bad list']. lastFace nextFace == nil ifFalse:[^self error:'Bad list']. face _ firstFace. [face == lastFace] whileFalse:[face _ face nextFace]. self validateSortOrder.! ! !B3DFillList methodsFor: 'private' stamp: 'ar 4/5/1999 20:39'! validateSortOrder | backFace | firstFace == lastFace ifTrue:[^self]. "0 or 1 element" backFace _ firstFace nextFace. [backFace nextFace == nil] whileFalse:[ backFace minZ <= backFace nextFace minZ ifFalse:[^self error:'Sorting error']. backFace _ backFace nextFace. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DFillList class instanceVariableNames: ''! !B3DFillList class methodsFor: 'instance creation' stamp: 'ar 4/4/1999 04:27'! new ^super new initialize! ! FloatArray variableWordSubclass: #B3DFloatArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DFloatArray commentStamp: '' prior: 0! I am the superclass for all Balloon 3D vector objects.! !B3DFloatArray methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index "For subclasses that override #at:" ^Float fromIEEE32Bit: (self basicAt: index)! ! !B3DFloatArray methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index put: value "For subclasses that override #at:put:" self basicAt: index put: value asIEEE32BitWord. ^value! ! !B3DFloatArray methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:23'! numElements ^self class numElements! ! !B3DFloatArray methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:10'! wordAt: index ^self primitiveFailed! ! !B3DFloatArray methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:10'! wordAt: index put: value ^self primitiveFailed! ! !B3DFloatArray methodsFor: 'initialize'! loadFrom: srcObject self == srcObject ifTrue:[^self]. self class == srcObject class ifTrue:[self replaceFrom: 1 to: self size with: srcObject startingAt: 1] ifFalse:[self privateLoadFrom: srcObject]! ! !B3DFloatArray methodsFor: 'private'! privateLoadFrom: srcObject "Load the receiver from the given source object." self error:'Cannot load a ', srcObject class name,' into a ', self class name.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DFloatArray class instanceVariableNames: ''! !B3DFloatArray class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:20'! new ^super new: self numElements! ! !B3DFloatArray class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:21'! numElements ^0! ! Object subclass: #B3DGeometry instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Objects'! B3DPrimitiveEngine subclass: #B3DHardwareEngine instanceVariableNames: 'vpTransform ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Acceleration'! !B3DHardwareEngine commentStamp: '' prior: 0! B3DHardwareEngine is a render engine specifically designed to deal with HW accellerated implementations. The (currently only) difference to the generic render engine is that a HW accellerated engine automatically clips the virtual viewport specified by client. This is necessary since HW accellerated rasterizers can usually not render outside the actual display surface. Note: If the viewport clipping proves to be efficient enough it might be promoted to the general render engine since there is no point in rendering outside the clipping rectangle.! !B3DHardwareEngine methodsFor: 'accessing' stamp: 'ar 5/28/2000 04:05'! viewport: aRectangle "check if we need a transform override for the viewport" | vp clipRect | vp _ aRectangle. clipRect _ rasterizer clipRect. (clipRect containsRect: vp) ifTrue:[ "Good. The viewport is fully within the clip rect." vpTransform _ nil. ] ifFalse:[ "We need a transform override here" vp _ clipRect intersect: vp. "Actual viewport is vp. Now scale from aRectangle into vp. This is equivalent to picking vp center with vp extent." vp area > 0 ifTrue:[ vpTransform _ self pickingMatrixFor: aRectangle at: (vp origin + vp corner) * 0.5 extent: vp extent]. ]. "And set actual viewport" super viewport: vp.! ! !B3DHardwareEngine methodsFor: 'private-rendering' stamp: 'ar 2/27/2000 20:14'! privateTransformVB: vb vpTransform ifNil:[^transformer processVertexBuffer: vb] ifNotNil:["We must override the projection matrix here" ^transformer processVertexBuffer: vb modelView: transformer modelViewMatrix projection: (transformer projectionMatrix composedWithGlobal: vpTransform)].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DHardwareEngine class instanceVariableNames: ''! !B3DHardwareEngine class methodsFor: 'accessing' stamp: 'ar 2/24/2000 00:15'! rasterizer ^B3DHardwareRasterizer! ! B3DVertexRasterizer subclass: #B3DHardwareRasterizer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Acceleration'! !B3DHardwareRasterizer commentStamp: '' prior: 0! WICHTIG: Viewport muss an den renderer gebunden sein. Viewport kann multiple sein. ! !B3DHardwareRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:13'! finish "Wait until drawing was completed so we won't get into any trouble with 2D operations afterwards. Note: Later we will synchronize this with the portions of display in use." target finish! ! !B3DHardwareRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:13'! flush "Flush the pipeline. Flushing will force processing but not wait until it's finished." target flush.! ! !B3DHardwareRasterizer methodsFor: 'initialize' stamp: 'ar 7/11/2000 11:05'! viewport: vp super viewport: vp. self primSetViewportX: viewport left asInteger y: viewport top asInteger w: viewport width asInteger h: viewport height asInteger.! ! !B3DHardwareRasterizer methodsFor: 'accessing' stamp: 'ar 5/27/2000 00:47'! clearDepthBuffer self primClearDepthBuffer.! ! !B3DHardwareRasterizer methodsFor: 'testing' stamp: 'ar 2/24/2000 00:00'! needsClip ^true! ! !B3DHardwareRasterizer methodsFor: 'processing' stamp: 'ar 9/1/2000 10:22'! processVertexBuffer: vb | box | box _ self primProcessVB: vb primitive texture: (target textureHandleOf: texture) vertices: vb vertexArray vertexCount: vb vertexCount faces: vb indexArray faceCount: vb indexCount. ^box ifNotNil:[(box at: 1) @ (box at: 2) corner: (box at: 3) @ (box at: 4)]! ! !B3DHardwareRasterizer methodsFor: 'primitives' stamp: 'ar 5/26/2000 15:06'! primClearDepthBuffer ^self primitiveFailed! ! !B3DHardwareRasterizer methodsFor: 'primitives' stamp: 'ar 9/1/2000 10:23'! primProcessVB: primitiveType texture: textureHandle vertices: vtxArray vertexCount: vtxCount faces: idxArray faceCount: idxCount "There's a bug somewhere in the primitive code leading to failures every now and then which can be safely ignored since the next frame will almost always be fine. I need to track this down but it takes time and these primitive failures are annoying..." ^nil! ! !B3DHardwareRasterizer methodsFor: 'primitives' stamp: 'ar 2/24/2000 00:06'! primSetViewportX: left y: top w: width h: height ^self primitiveFailed! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DHardwareRasterizer class instanceVariableNames: ''! !B3DHardwareRasterizer class methodsFor: 'accessing' stamp: 'ar 5/25/2000 22:00'! isAvailable ^self version > 0! ! !B3DHardwareRasterizer class methodsFor: 'accessing' stamp: 'ar 5/25/2000 22:01'! version "B3DPrimitiveRasterizer version" ^0! ! !B3DHardwareRasterizer class methodsFor: 'testing' stamp: 'ar 5/28/2000 15:04'! isAvailableFor: aForm "Return true if this part of the engine is available for the given output medium" aForm ifNil:[^false]. (aForm isDisplayScreen and:[aForm isB3DDisplayScreen]) ifFalse:[^false]. ^self isAvailable! ! B3DGeometry subclass: #B3DIndexedMesh instanceVariableNames: 'vertices vtxNormals vtxColors vtxTexCoords faces faceNormals bBox ' classVariableNames: 'FlagFanStart FlagStripStart VRML97BoxCache VRML97ConeCache VRMLCylCache VRMLSphereCache ' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DIndexedMesh commentStamp: '' prior: 0! I represent a generic indexed face mesh. My subclasses define what kind of primitive objects I can represent. ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 20:03'! animationParameter ^0.0! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 20:03'! animationParameter: param ! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! boundingBox ^bBox ifNil:[bBox _ self computeBoundingBox]! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! faceNormals ^faceNormals ifNil:[faceNormals _ self computeFaceNormals]! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! faces ^faces! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'jsp 3/11/1999 11:44'! faces: newFaces faces _ newFaces. ! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! texCoords ^vtxTexCoords! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'jsp 3/11/1999 11:43'! texCoords: newTexCoords vtxTexCoords _ newTexCoords. ! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 9/16/1999 14:49'! vertexColors ^vtxColors! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 9/16/1999 14:50'! vertexColors: aB3DColor4Array vtxColors _ aB3DColor4Array! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! vertexNormals ^vtxNormals ifNil:[vtxNormals _ self computeVertexNormals].! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'jsp 3/11/1999 11:44'! vertexNormals: newNormals vtxNormals _ newNormals.! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'ar 2/16/1999 19:08'! vertices ^vertices! ! !B3DIndexedMesh methodsFor: 'accessing' stamp: 'jsp 3/11/1999 11:43'! vertices: newVertices vertices _ newVertices.! ! !B3DIndexedMesh methodsFor: 'modifying' stamp: 'ar 2/16/1999 19:08'! centerAtZero self translateBy: (self boundingBox origin + self boundingBox corner * -0.5).! ! !B3DIndexedMesh methodsFor: 'modifying' stamp: 'jsp 9/17/1999 14:13'! transformBy: aMatrix "Modify the mesh by transforming it using a matrix; this allows us to change the insertion point of the mesh" vertices do: [:vtx | vtx privateLoadFrom: ((aMatrix composeWith: (B3DMatrix4x4 identity translation: vtx)) translation) ]. bBox ifNotNil: [ self computeBoundingBox ]. self computeVertexNormals. ! ! !B3DIndexedMesh methodsFor: 'modifying' stamp: 'ar 2/16/1999 19:08'! translateBy: amount vertices do:[:vtx| vtx += amount]. bBox ifNotNil:[bBox _ bBox translateBy: amount].! ! !B3DIndexedMesh methodsFor: 'displaying' stamp: 'ar 2/16/1999 19:08'! renderOn: aRenderer ^self subclassResponsibility! ! !B3DIndexedMesh methodsFor: 'private' stamp: 'ar 2/16/1999 19:08'! computeBoundingBox | min max | min _ max _ nil. vertices do:[:vtx| min ifNil:[min _ vtx] ifNotNil:[min _ min min: vtx]. max ifNil:[max _ vtx] ifNotNil:[max _ max max: vtx]. ]. ^Rectangle origin: min corner: max! ! !B3DIndexedMesh methodsFor: 'private' stamp: 'ar 2/16/1999 19:09'! computeFaceNormals | out face v1 v2 v3 d1 d2 normal | out _ B3DVector3Array new: faces size. 1 to: faces size do:[:i| face _ faces at: i. v1 _ vertices at: face p1Index. v2 _ vertices at: face p2Index. v3 _ vertices at: face p3Index. d1 _ v3 - v1. d2 _ v2 - v1. d1 safelyNormalize. d2 safelyNormalize. normal _ d1 cross: d2. out at: i put: normal safelyNormalize. ]. ^out! ! !B3DIndexedMesh methodsFor: 'private' stamp: 'ar 2/16/1999 19:09'! computeVertexNormals | temp normals face normal v1 v2 v3 out | temp _ Array new: vertices size. 1 to: temp size do:[:i| temp at: i put: B3DVector4 new]. normals _ self faceNormals. "Forces computation if necessary" 1 to: faces size do:[:i| face _ faces at: i. normal _ (normals at: i) asB3DVector4. v1 _ face p1Index. v2 _ face p2Index. v3 _ face p3Index. (temp at: v1) += normal. (temp at: v2) += normal. (temp at: v3) += normal. ]. out _ B3DVector3Array new: vertices size. 1 to: out size do:[:i| out at: i put: (temp at: i) asB3DVector3 safelyNormalize. ]. ^out! ! !B3DIndexedMesh methodsFor: 'optimizations' stamp: 'ar 2/8/1999 06:52'! optimizeMeshLayout "Optimize the layout of the indexed mesh for primitive operations. Optimzed layouts include triangle/quad strips and fans and will result in MUCH better performance during rendering. However, optimizations are generally time-consuming so you better don't call this method too often." ^self "Must be implemented in my subclasses"! ! !B3DIndexedMesh methodsFor: 'converting' stamp: 'ar 9/17/1999 12:37'! asSimpleMesh "Convert the receiver into a very simple mesh representation" | simpleFaces oldFace newVtx newFace newVertices pos | simpleFaces _ WriteStream on: (Array new: faces size). newVertices _ WriteStream on: (Array new: 10). 1 to: faces size do:[:i| oldFace _ faces at: i. newVertices reset. 1 to: oldFace size do:[:j| pos _ oldFace at: j. newVtx _ B3DSimpleMeshVertex new. newVtx position: (vertices at: pos). vtxNormals == nil ifFalse:[newVtx normal: (vtxNormals at: pos)]. vtxColors == nil ifFalse:[newVtx color: (vtxColors at: pos)]. vtxTexCoords == nil ifFalse:[newVtx texCoord: (vtxTexCoords at: pos)]. newVertices nextPut: newVtx]. newFace _ B3DSimpleMeshFace withAll: newVertices contents. simpleFaces nextPut: newFace]. ^B3DSimpleMesh withAll: simpleFaces contents! ! !B3DIndexedMesh methodsFor: 'testing' stamp: 'ar 9/16/1999 23:32'! hasVertexColors ^vtxColors notNil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DIndexedMesh class instanceVariableNames: ''! !B3DIndexedMesh class methodsFor: 'class initialization' stamp: 'ar 9/16/1999 23:02'! flushVRMLCache "B3DIndexedMesh flushVRMLCache" VRML97BoxCache _ VRML97ConeCache _ VRMLCylCache _ VRMLSphereCache _ nil.! ! !B3DIndexedMesh class methodsFor: 'class initialization' stamp: 'ar 2/8/1999 06:55'! initialize "B3DIndexedMesh initialize" "Optimization flags: These flags are *hints* and may be ignored by the renderer." FlagStripStart _ 1. FlagFanStart _ 2.! ! !B3DIndexedMesh class methodsFor: 'examples' stamp: 'ar 2/8/1999 21:18'! sampleRect ^self sampleRect: 10! ! !B3DIndexedMesh class methodsFor: 'examples' stamp: 'ar 2/8/1999 16:58'! sampleRect: n ^self new sampleRect: n! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:12'! vrml97Box "Return a mesh representing a VRML97 Box" ^VRML97BoxCache ifNil:[ VRML97BoxCache _ (B3DSimpleMesh withAll: self vrmlCreateBoxFaces) asIndexedMesh]! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:15'! vrml97Cone "Return a mesh representing a VRML97 Cone" ^self vrml97Cone: true bottom: true.! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:14'! vrml97Cone: doSide bottom: doBottom "Return a mesh representing a VRML97 Cone" | idx | idx _ 0. doBottom ifTrue:[idx _ idx + 2]. doSide ifTrue:[idx _ idx + 1]. VRML97ConeCache == nil ifTrue:[ VRML97ConeCache _ Array new: 3. 1 to: 3 do:[:i| VRML97ConeCache at: i put: (self vrmlCreateCone: (i anyMask: 1) bottom: (i anyMask: 2))]]. ^VRML97ConeCache at: idx! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:15'! vrml97Cylinder "Return a mesh representing a VRML97 Cylinder" ^self vrml97Cylinder: true bottom: true top: true.! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:14'! vrml97Cylinder: doSide bottom: doBottom top: doTop "Return a mesh representing a VRML97 Cylinder" | idx | idx _ 0. doTop ifTrue:[idx _ idx + 4]. doBottom ifTrue:[idx _ idx + 2]. doSide ifTrue:[idx _ idx + 1]. idx = 0 ifTrue:[^nil]. VRMLCylCache == nil ifTrue:[ VRMLCylCache _ Array new: 7. 1 to: 7 do:[:i| VRMLCylCache at: i put: (self vrmlCreateCylinder: (i anyMask: 1) bottom: (i anyMask: 2) top: (i anyMask: 4))]]. ^VRMLCylCache at: idx! ! !B3DIndexedMesh class methodsFor: 'vrml97 prototypes' stamp: 'ar 9/16/1999 22:21'! vrml97Sphere "Return a mesh representing a VRML97 Sphere" ^VRMLSphereCache ifNil:[ VRMLSphereCache _ (B3DSimpleMesh withAll: self vrmlCreateSphereFaces) asIndexedMesh].! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:07'! vrmlCreateBottomFaces | face steps dir m lastVtx nextVtx faceList midVtx | steps _ self vrmlSteps. faceList _ WriteStream on: (Array new: steps). dir _ 0@0@1. m _ (B3DRotation angle: (360.0 / steps) axis: (0@-1@0)) asMatrix4x4. lastVtx _ B3DSimpleMeshVertex new. lastVtx position: 0@-1@1. lastVtx normal: 0@-1@0. lastVtx texCoord: 0.5@1. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 3. face at: 1 put: lastVtx. dir _ (m localPointToGlobal: dir) normalized. nextVtx _ B3DSimpleMeshVertex new. nextVtx position: dir x @ -1 @ dir z. nextVtx normal: 0@-1@0. nextVtx texCoord: (dir x @ dir z) * 0.5 + 0.5. midVtx _ nextVtx copy. midVtx position: 0@-1@0. midVtx texCoord: 0.5@0.5. face at: 2 put: nextVtx. face at: 3 put: midVtx. faceList nextPut: face. lastVtx _ nextVtx]. ^faceList contents! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:11'! vrmlCreateBoxFaces | vtx face vtxSpec faceList | faceList _ WriteStream on: (Array new: 6). "front and back face" vtxSpec _ #( ((-1 -1) (0 1)) (( 1 -1) (1 1)) (( 1 1) (1 0)) ((-1 1) (0 0))). "front" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: spec first first @ spec first second @ -1. vtx normal: 0@0@-1. vtx texCoord: spec second first @ spec second second. face at: idx put: vtx. ]. faceList nextPut: face. "back" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: spec first first @ spec first second @ 1. vtx normal: 0@0@1. vtx texCoord: 1 - spec second first @ spec second second. face at: idx put: vtx. ]. faceList nextPut: face. "top" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: spec first first @ 1 @ spec first second. vtx normal: 1@0@0. vtx texCoord: spec second first @ spec second second. face at: idx put: vtx. ]. faceList nextPut: face. "bottom" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: spec first first @ -1 @ spec first second. vtx normal: -1@0@0. vtx texCoord: spec second first @ (1 - spec second second). face at: idx put: vtx. ]. faceList nextPut: face. vtxSpec _ #( ((-1 -1) (0 1)) ((-1 1) (1 1)) (( 1 1) (1 0)) (( 1 -1) (0 0))). "right" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: 1 @ spec first first @ spec first second. vtx normal: 1@0@0. vtx texCoord: spec second first @ spec second second. face at: idx put: vtx. ]. faceList nextPut: face. "left" face _ B3DSimpleMeshFace new: 4. vtxSpec doWithIndex:[:spec :idx| vtx _ B3DSimpleMeshVertex new. vtx position: -1 @ spec first first @ spec first second. vtx normal: -1@0@0. vtx texCoord: 1 - spec second first @ spec second second. face at: idx put: vtx. ]. faceList nextPut: face. ^faceList contents! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:16'! vrmlCreateCone: doSide bottom: doBottom | faces | faces _ #(). doSide ifTrue:[faces _ faces, self vrmlCreateConeFaces]. doBottom ifTrue:[faces _ faces, self vrmlCreateBottomFaces]. ^(B3DSimpleMesh withAll: faces) asIndexedMesh! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:07'! vrmlCreateConeFaces | face steps dir m lastVtx nextVtx topVtx faceList | steps _ self vrmlSteps. faceList _ WriteStream on: (Array new: steps). dir _ 0@0@1. m _ (B3DRotation angle: (360.0 / steps) axis: (0@-1@0)) asMatrix4x4. lastVtx _ B3DSimpleMeshVertex new. lastVtx position: 0@-1@1. lastVtx normal: dir. lastVtx texCoord: 0@1. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 3. face at: 1 put: lastVtx. dir _ (m localPointToGlobal: dir) normalized. nextVtx _ B3DSimpleMeshVertex new. nextVtx position: dir x @ -1 @ dir z. nextVtx normal: dir. nextVtx texCoord: (i / steps asFloat) @ 1. topVtx _ nextVtx copy. topVtx position: 0@1@0. topVtx texCoord: lastVtx texCoord x @ 0. face at: 2 put: nextVtx. face at: 3 put: topVtx. faceList nextPut: face. lastVtx _ nextVtx]. ^faceList contents! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:05'! vrmlCreateCylinder: doSide bottom: doBottom top: doTop | faces | faces _ #(). doSide ifTrue:[faces _ faces, self vrmlCreateCylinderFaces]. doBottom ifTrue:[faces _ faces, self vrmlCreateBottomFaces]. doTop ifTrue:[faces _ faces, self vrmlCreateTopFaces]. ^(B3DSimpleMesh withAll: faces) asIndexedMesh! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:07'! vrmlCreateCylinderFaces | face steps dir m lastVtx nextVtx topVtx lastTopVtx faceList | steps _ self vrmlSteps. faceList _ WriteStream on: (Array new: steps). dir _ 0@0@1. m _ (B3DRotation angle: (360.0 / steps) axis: (0@-1@0)) asMatrix4x4. lastVtx _ B3DSimpleMeshVertex new. lastVtx position: 0@-1@1. lastVtx normal: dir. lastVtx texCoord: 0@1. lastTopVtx _ lastVtx copy. lastTopVtx position: 0@1@1. lastTopVtx texCoord: 0@0. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 4. face at: 1 put: lastVtx. face at: 4 put: lastTopVtx. dir _ (m localPointToGlobal: dir) normalized. nextVtx _ B3DSimpleMeshVertex new. nextVtx position: dir x @ -1 @ dir z. nextVtx normal: dir. nextVtx texCoord: (i / steps asFloat) @ 1. topVtx _ nextVtx copy. topVtx position: dir x @ 1 @ dir z. topVtx texCoord: (i / steps asFloat) @ 0. face at: 2 put: nextVtx. face at: 3 put: topVtx. faceList nextPut: face. lastVtx _ nextVtx. lastTopVtx _ topVtx]. ^faceList contents ! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:58'! vrmlCreateSphereFaces "B3DIndexedMesh vrmlCreateSphereFaces" | faceList vtx steps m1 m2 baseDir vtxList vertices dir lastVtx nextVtx face | steps _ self vrmlSteps. faceList _ WriteStream on: (Array new: steps * steps). "<--- vertex construction --->" m1 _ (B3DRotation angle: 360.0 / steps axis: 0@-1@0) asMatrix4x4. m2 _ (B3DRotation angle: 180.0 / steps axis: 1@0@0) asMatrix4x4. baseDir _ 0@1@0. vtxList _ Array new: steps + 1. 0 to: steps do:[:i| i = steps ifTrue:[baseDir _ 0@-1@0]. "Make closed for sure" vertices _ Array new: steps + 1. vtxList at: i+1 put: vertices. dir _ baseDir. 0 to: steps do:[:j| j = steps ifTrue:[dir _ baseDir]. "Make closed for sure" vtx _ B3DSimpleMeshVertex new. vtx position: dir; normal: dir. vtx texCoord: (j / steps asFloat) @ (i / steps asFloat). vertices at: j+1 put: vtx. dir _ (m1 localPointToGlobal: dir) normalized. ]. baseDir _ (m2 localPointToGlobal: baseDir) normalized. ]. "<--- face construction --->" "Construct first round separately as triangles" lastVtx _ vtxList at: 1. nextVtx _ vtxList at: 2. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 3. face at: 1 put: (lastVtx at: i). face at: 2 put: (nextVtx at: i+1). face at: 3 put: (nextVtx at: i). faceList nextPut: face]. "Construct the next rounds as quads" 2 to: steps-1 do:[:i| lastVtx _ vtxList at: i. nextVtx _ vtxList at: i+1. 1 to: steps do:[:j| face _ B3DSimpleMeshFace new: 4. face at: 1 put: (lastVtx at: j). face at: 2 put: (lastVtx at: j+1). face at: 3 put: (nextVtx at: j+1). face at: 4 put: (nextVtx at: j). faceList nextPut: face]]. "Construct the last round separately as triangles" lastVtx _ vtxList at: steps. nextVtx _ vtxList at: steps+1. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 3. face at: 1 put: (lastVtx at: i). face at: 2 put: (lastVtx at: i+1). face at: 3 put: (nextVtx at: i). faceList nextPut: face]. ^faceList contents! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:08'! vrmlCreateTopFaces | face steps dir m lastVtx nextVtx faceList midVtx | steps _ self vrmlSteps. faceList _ WriteStream on: (Array new: steps). dir _ 0@0@1. m _ (B3DRotation angle: (360.0 / steps) axis: (0@-1@0)) asMatrix4x4. lastVtx _ B3DSimpleMeshVertex new. lastVtx position: 0@1@1. lastVtx normal: 0@1@0. lastVtx texCoord: 0.5@0. 1 to: steps do:[:i| face _ B3DSimpleMeshFace new: 3. face at: 1 put: lastVtx. dir _ (m localPointToGlobal: dir) normalized. nextVtx _ B3DSimpleMeshVertex new. nextVtx position: dir x @ 1 @ dir z. nextVtx normal: 0@1@0. nextVtx texCoord: (dir x @ dir z) * (0.5 @ -0.5) + 0.5. midVtx _ nextVtx copy. midVtx position: 0@1@0. midVtx texCoord: 0.5@0.5. face at: 2 put: nextVtx. face at: 3 put: midVtx. faceList nextPut: face. lastVtx _ nextVtx]. ^faceList contents! ! !B3DIndexedMesh class methodsFor: 'vrml support' stamp: 'ar 9/16/1999 22:15'! vrmlSteps "Return the number of steps for rotational objects" ^16! ! B3DGeometry variableWordSubclass: #B3DIndexedQuad instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DIndexedQuad methodsFor: 'initialize' stamp: 'ar 2/7/1999 20:00'! with: i1 with: i2 with: i3 with: i4 self at: 1 put: i1. self at: 2 put: i2. self at: 3 put: i3. self at: 4 put: i4.! ! !B3DIndexedQuad methodsFor: 'accessing' stamp: 'ar 2/8/1999 16:39'! flags ^0! ! !B3DIndexedQuad methodsFor: 'private' stamp: 'ar 2/7/1999 20:02'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." ^self primitiveFailed! ! !B3DIndexedQuad methodsFor: 'printing' stamp: 'ar 2/8/1999 16:39'! printOn: aStream aStream nextPutAll:'IQuad('; print: (self at: 1); nextPutAll:', '; print: (self at: 2); nextPutAll:', '; print: (self at: 3); nextPutAll:', '; print: (self at: 4); nextPutAll:', '; print: (self flags); nextPutAll:')'.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DIndexedQuad class instanceVariableNames: ''! !B3DIndexedQuad class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 19:59'! new ^self new: 4! ! !B3DIndexedQuad class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 20:03'! numElements ^4! ! !B3DIndexedQuad class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 19:59'! with: i1 with: i2 with: i3 with: i4 ^self new with: i1 with: i2 with: i3 with: i4! ! B3DInplaceArray variableWordSubclass: #B3DIndexedQuadArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DIndexedQuadArray class instanceVariableNames: ''! !B3DIndexedQuadArray class methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:58'! contentsClass ^B3DIndexedQuad! ! B3DIndexedMesh subclass: #B3DIndexedQuadMesh instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DIndexedQuadMesh methodsFor: 'displaying' stamp: 'ar 11/7/1999 18:35'! renderOn: aRenderer ^aRenderer drawIndexedQuads: faces vertices: vertices normals: vtxNormals colors: vtxColors texCoords: vtxTexCoords.! ! !B3DIndexedQuadMesh methodsFor: 'private' stamp: 'ar 9/10/1999 15:05'! plainTextureRect "Create a new plain rectangle w/ texture coords" vertices _ B3DVector3Array new: 4. vertices at: 1 put: (-1@-1@0). vertices at: 2 put: (1@-1@0). vertices at: 3 put: (1@1@0). vertices at: 4 put: (-1@1@0). vtxTexCoords _ B3DTexture2Array new: 4. vtxTexCoords at: 1 put: (0@1). vtxTexCoords at: 2 put: (1@1). vtxTexCoords at: 3 put: (1@0). vtxTexCoords at: 4 put: (0@0). faces _ B3DIndexedQuadArray new: 1. faces at: 1 put: (B3DIndexedQuad with: 1 with: 2 with: 3 with: 4).! ! !B3DIndexedQuadMesh methodsFor: 'private' stamp: 'ar 2/8/1999 02:15'! sampleRect: n "B3DIndexedQuadMesh new sampleRect" | vtx face | vtx _ WriteStream on: (B3DVector3Array new). n negated to: n do:[:x| n negated to: n do:[:y| vtx nextPut: (B3DVector3 x: x y: y z: 0) /= n asFloat. ]. ]. vertices _ vtx contents. vtxNormals _ B3DVector3Array new: (2*n+1) squared. 1 to: vtxNormals size do:[:i| vtxNormals at: i put: (0@0@-1)]. faces _ B3DIndexedQuadArray new: (2*n) squared. 0 to: 2*n-1 do:[:i| 1 to: 2*n do:[:j| face _ B3DIndexedQuad with: (i*(2*n+1)+j) with: (i*(2*n+1)+j+1) with: (i+1*(2*n+1)+j+1) with: (i+1*(2*n+1)+j). faces at: i*2*n+j put: face. ]].! ! B3DGeometry variableWordSubclass: #B3DIndexedTriangle instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DIndexedTriangle methodsFor: 'initialize' stamp: 'ar 2/16/1999 19:09'! with: index1 with: index2 with: index3 self p1Index: index1. self p2Index: index2. self p3Index: index3.! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/8/1999 05:15'! flags ^0 "May be used later"! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/8/1999 05:16'! flags: aNumber ^self "Maybe used later"! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:54'! p1Index ^self at: 1! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:55'! p1Index: aNumber self at: 1 put: aNumber! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:55'! p2Index ^self at: 2! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:55'! p2Index: aNumber self at: 2 put: aNumber! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:55'! p3Index ^self at: 3! ! !B3DIndexedTriangle methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:55'! p3Index: aNumber self at: 3 put: aNumber! ! !B3DIndexedTriangle methodsFor: 'testing' stamp: 'ar 2/8/1999 06:15'! includesIndex: idx ^(self at: 1) = idx or:[(self at: 2) = idx or:[(self at: 3) = idx]]! ! !B3DIndexedTriangle methodsFor: 'private' stamp: 'ar 2/5/1999 23:19'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." ^self primitiveFailed! ! !B3DIndexedTriangle methodsFor: 'printing' stamp: 'ar 2/8/1999 05:14'! printOn: aStream aStream nextPutAll:'IFace('; print: self p1Index; nextPutAll:', '; print: self p2Index; nextPutAll:', '; print: self p3Index; nextPutAll:', '; print: self flags; nextPutAll:')'.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DIndexedTriangle class instanceVariableNames: ''! !B3DIndexedTriangle class methodsFor: 'instance creation' stamp: 'ar 2/8/1999 05:14'! new ^self new: self numElements! ! !B3DIndexedTriangle class methodsFor: 'instance creation' stamp: 'ar 2/8/1999 05:15'! numElements ^3! ! !B3DIndexedTriangle class methodsFor: 'instance creation' stamp: 'ar 2/16/1999 19:09'! with: index1 with: index2 with: index3 ^self new with: index1 with: index2 with: index3! ! B3DInplaceArray variableWordSubclass: #B3DIndexedTriangleArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DIndexedTriangleArray class instanceVariableNames: ''! !B3DIndexedTriangleArray class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 20:56'! contentsClass ^B3DIndexedTriangle! ! B3DIndexedMesh subclass: #B3DIndexedTriangleMesh instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DIndexedTriangleMesh methodsFor: 'displaying' stamp: 'ar 11/7/1999 18:35'! renderOn: aRenderer self hasVertexColors ifTrue:[ aRenderer trackAmbientColor: true. aRenderer trackDiffuseColor: true]. ^aRenderer drawIndexedTriangles: faces vertices: vertices normals: vtxNormals colors: vtxColors texCoords: vtxTexCoords.! ! !B3DIndexedTriangleMesh methodsFor: 'private' stamp: 'ar 2/8/1999 02:15'! sampleRect: n "B3DIndexedQuadMesh new sampleRect" | vtx face | vtx _ WriteStream on: (B3DVector3Array new). n negated to: n do:[:x| n negated to: n do:[:y| vtx nextPut: (B3DVector3 x: x y: y z: 0) /= n asFloat. ]. ]. vertices _ vtx contents. vtxNormals _ B3DVector3Array new: (2*n+1) squared. 1 to: vtxNormals size do:[:i| vtxNormals at: i put: (0@0@-1)]. faces _ B3DIndexedTriangleArray new: (2*n) squared. 0 to: 2*n-1 do:[:i| 1 to: 2*n do:[:j| face _ B3DIndexedTriangle with: (i*(2*n+1)+j) with: (i*(2*n+1)+j+1) with: (i+1*(2*n+1)+j+1) "with: (i+1*(2*n+1)+j)". faces at: i*2*n+j put: face. "face _ B3DIndexedTriangle with: (i*(2*n+1)+j) with: (i*(2*n+1)+j+1) with: (i+1*(2*n+1)+j+1) with: (i+1*(2*n+1)+j). " ]].! ! !B3DIndexedTriangleMesh methodsFor: 'fan creation' stamp: 'ar 2/8/1999 06:42'! makeTriangleFans "Re-arrange the triangles so that they represent triangle fans." | vtxDict avgFacesPerVertex todo done maxShared maxSharedIndex newOrder sharedAssoc | "Compute the average size of faces per vertex (strange measure isn't it ;-)" avgFacesPerVertex _ faces size // vertices size + 3. "So we cover 99% of all cases" "vtxDict contains vertexIndex->(OrderedCollection of: IndexedFace)" vtxDict _ OrderedCollection new: vertices size. "Add all the vertex indexes. The set is larger than necessary to avoid collisions." 1 to: vertices size do:[:i| vtxDict add: i -> (IdentitySet new: avgFacesPerVertex * 3)]. "Go over all faces and add the face to all its vertices. Also store the faces in the toGo list." todo _ IdentitySet new: faces size * 3. done _ IdentitySet new: faces size * 3. faces do:[:iFace| todo add: iFace. (vtxDict at: iFace p1Index) value add: iFace. (vtxDict at: iFace p2Index) value add: iFace. (vtxDict at: iFace p3Index) value add: iFace]. "Now start creating the fans" [todo isEmpty] whileFalse:[ "Let's assume that this method is not called in real-time and spend some time to find the vertex with most shared faces" maxShared _ 0. maxSharedIndex _ nil. vtxDict doWithIndex:[:assoc :index| assoc value size > maxShared ifTrue:[maxShared _ assoc value size. maxSharedIndex _ index]]. maxSharedIndex = nil ifTrue:[^self error:'No shared vertices found']. "Now re-arrange the faces around the shared vertex" sharedAssoc _ vtxDict at: maxSharedIndex. newOrder _ self reArrangeFanFaces: sharedAssoc value around: sharedAssoc key from: todo into: done. "Remove re-arranged faces" newOrder do:[:iFace| (done includes: iFace) ifTrue:[self halt]. todo remove: iFace. done add: iFace. (vtxDict at: iFace p1Index) value remove: iFace ifAbsent:[]. (vtxDict at: iFace p2Index) value remove: iFace ifAbsent:[]. (vtxDict at: iFace p3Index) value remove: iFace ifAbsent:[]]. false ifTrue:[ "Remove the shared index if no more faces left." sharedAssoc value isEmpty ifTrue:[ vtxDict swap: maxSharedIndex with: vtxDict size. "Optimized removal ;-)" vtxDict removeLast]. ]. ].! ! !B3DIndexedTriangleMesh methodsFor: 'fan creation' stamp: 'ar 2/8/1999 06:38'! reArrangeFanFaces: sharedFaces around: maxSharedIndex from: todo into: done "Re-arrange the faces in sharedFaces to form a triangle fan. Avoid inplace-reversal of the triangles in doneList -- they have been arranged already" | out next nextIndex prevIndex index | out _ OrderedCollection new: sharedFaces size * 2. next _ sharedFaces detect:[:any| true]. sharedFaces remove: next. out addLast: next. nextIndex _ next p1Index. nextIndex = maxSharedIndex ifTrue:[nextIndex _ next p2Index]. prevIndex _ next p3Index. (prevIndex = maxSharedIndex) ifTrue:[prevIndex _ next p2Index]. "Search forward" [next _ sharedFaces detect:[:iFace| iFace includesIndex: nextIndex] ifNone:[nil]. next notNil] whileTrue:[ sharedFaces remove: next. out addLast: next. index _ next p1Index. (index = maxSharedIndex or:[index = nextIndex]) ifTrue:[ index _ next p2Index. (index = maxSharedIndex or:[index = nextIndex]) ifTrue:[index _ next p3Index]]. nextIndex _ index]. "Search backwards" nextIndex _ prevIndex. [next _ sharedFaces detect:[:iFace| iFace includesIndex: nextIndex] ifNone:[nil]. next notNil] whileTrue:[ sharedFaces remove: next. out addFirst: next. index _ next p1Index. (index = maxSharedIndex or:[index = nextIndex]) ifTrue:[ index _ next p2Index. (index = maxSharedIndex or:[index = nextIndex]) ifTrue:[index _ next p3Index]]. nextIndex _ index]. ^out! ! B3DFloatArray variableWordSubclass: #B3DInplaceArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! !B3DInplaceArray methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:49'! at: index "Return the primitive vertex at the given index" | vtx | (index < 1 or:[index > self size]) ifTrue:[^self errorSubscriptBounds: index]. vtx _ self contentsClass new. vtx replaceFrom: 1 to: vtx size with: self startingAt: index - 1 * self contentsSize + 1. ^vtx! ! !B3DInplaceArray methodsFor: 'accessing' stamp: 'ar 2/6/1999 00:12'! at: index put: anObject "Store the object at the given index in the receiver" | idx | (index < 1 or:[index > self size]) ifTrue:[^self errorSubscriptBounds: index]. idx _ index - 1 * self contentsSize. self privateReplaceFrom: idx+1 to: idx + self contentsSize with: anObject startingAt: 1. ^anObject! ! !B3DInplaceArray methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:48'! contentsClass ^self class contentsClass! ! !B3DInplaceArray methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:48'! contentsSize ^self contentsClass numElements! ! !B3DInplaceArray methodsFor: 'accessing' stamp: 'ar 2/5/1999 22:49'! size "Return the number of primitive vertices that can be stored in the receiver" ^self basicSize // self contentsSize! ! !B3DInplaceArray methodsFor: 'copying' stamp: 'ar 2/7/1999 19:48'! copyFrom: start to: stop "Answer a copy of a subset of the receiver, starting from element at index start until element at index stop." | newSize | newSize _ stop - start + 1. ^(self species new: newSize) replaceFrom: 1 to: newSize with: self startingAt: start! ! !B3DInplaceArray methodsFor: 'private' stamp: 'ar 2/6/1999 00:39'! privateReplaceFrom: start to: stop with: replacement startingAt: repStart start to: stop do:[:i| self basicAt: i put: (replacement at: i - start + repStart). ].! ! !B3DInplaceArray methodsFor: 'private' stamp: 'ar 2/7/1999 19:46'! replaceFrom: start to: stop with: replacement startingAt: repStart | max | max _ (replacement size - repStart) min: stop-start. start to: start+max do:[:i| self at: i put: (replacement at: i - start + repStart). ].! ! !B3DInplaceArray methodsFor: 'enumerating' stamp: 'ar 2/6/1999 00:37'! do: aBlock "Overridden to store the (possibly) modified argument back" | obj | 1 to: self size do:[:index| obj _ self at: index. aBlock value: obj. self at: index put: obj].! ! !B3DInplaceArray methodsFor: 'enumerating' stamp: 'ar 2/6/1999 00:37'! readOnlyDo: aBlock ^super do: aBlock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DInplaceArray class instanceVariableNames: ''! !B3DInplaceArray class methodsFor: 'instance creation' stamp: 'ar 2/5/1999 22:48'! contentsClass ^self subclassResponsibility! ! !B3DInplaceArray class methodsFor: 'instance creation' stamp: 'ar 2/5/1999 22:49'! contentsSize ^self contentsClass numElements! ! !B3DInplaceArray class methodsFor: 'instance creation' stamp: 'ar 2/5/1999 22:49'! new: n ^super new: self contentsSize*n! ! B3DMultiMesh subclass: #B3DInterpolatedMesh instanceVariableNames: 'index ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DInterpolatedMesh methodsFor: 'initialize' stamp: 'ar 8/31/2000 19:36'! initialize index _ 1.! ! !B3DInterpolatedMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 20:02'! animationParameter ^index-1 asFloat / meshes size asFloat! ! !B3DInterpolatedMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 20:11'! animationParameter: param index _ ((param \\ 1.0) * (meshes size-1)) asInteger + 1.! ! !B3DInterpolatedMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 23:19'! boundingBox | box origin corner | box _ meshes first boundingBox. origin _ box origin. corner _ box corner. 2 to: meshes size do:[:i| box _ (meshes at: i) boundingBox. origin _ origin min: box origin. corner _ corner max: box corner. ]. ^Rectangle origin: origin corner: corner! ! !B3DInterpolatedMesh methodsFor: 'displaying' stamp: 'ar 8/31/2000 19:43'! renderOn: aRenderer ^(meshes at: index) renderOn: aRenderer! ! B3DFloatArray variableWordSubclass: #B3DLightAttenuation instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DLightAttenuation commentStamp: '' prior: 0! I represent the attenuation for any given light source, e.g., how the intensity of the light is reduced with increasing distance from the object. I consist of three parts, a constant part, a linear part and a squared part. The resulting intensity for any given distance d is computed by: intensity _ 1.0 / (constantPart + (distance * linearPart) + (distance^2 * squaredPart)). ! !B3DLightAttenuation methodsFor: 'initialize' stamp: 'ar 2/7/1999 19:02'! constant: constantFactor linear: linearFactor squared: squaredFactor self constantPart: constantFactor. self linearPart: linearFactor. self squaredPart: squaredFactor.! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:44'! constantPart ^self floatAt: 1! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:45'! constantPart: aNumber self floatAt: 1 put: aNumber! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:45'! linearPart ^self floatAt: 2! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:45'! linearPart: aNumber self floatAt: 2 put: aNumber! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:45'! squaredPart ^self floatAt: 3! ! !B3DLightAttenuation methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:45'! squaredPart: aNumber self floatAt: 3 put: aNumber! ! !B3DLightAttenuation methodsFor: 'lighting' stamp: 'ar 2/6/1999 18:44'! computeAttenuationFor: distance "Compute the light attenuation for the given distance" ^1.0 / (self constantPart + (distance * (self linearPart + (distance * self squaredPart))))! ! !B3DLightAttenuation methodsFor: 'testing' stamp: 'ar 2/15/1999 21:58'! isIdentity "Return true if the attenuation results in a constant lighting" ^self constantPart = 1.0 and:[self linearPart = 0.0 and:[self squaredPart = 0.0]]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DLightAttenuation class instanceVariableNames: ''! !B3DLightAttenuation class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 19:01'! constant: constantFactor linear: linearFactor squared: squaredFactor ^self new constant: constantFactor linear: linearFactor squared: squaredFactor! ! !B3DLightAttenuation class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 18:46'! numElements ^3! ! Object subclass: #B3DLightSource instanceVariableNames: 'lightColor ' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Lights'! !B3DLightSource methodsFor: 'shading' stamp: 'ar 2/7/1999 16:51'! computeAttenuationFor: distance ^self subclassResponsibility! ! !B3DLightSource methodsFor: 'shading' stamp: 'ar 2/7/1999 16:51'! computeDirectionTo: aB3DPrimitiveVertex ^self subclassResponsibility! ! !B3DLightSource methodsFor: 'shading' stamp: 'ar 2/15/1999 02:26'! computeSpotFactor: light2Vertex "Compute the spot factor for a spot light" | lightDirection cosAngle minCos deltaCos maxCos | lightDirection _ self direction. cosAngle _ (lightDirection dot: light2Vertex) negated. (cosAngle < (minCos _ self hotSpotMinCosine)) ifTrue:[^0.0]. maxCos _ self hotSpotMaxCosine. " maxCos = 1.0 ifFalse:[" deltaCos _ self hotSpotDeltaCosine. deltaCos <= 0.00001 ifTrue:[ "No delta -- a sharp boundary between on and off. Since off has already been determined above, we are on" ^1.0]. "Scale the angle to 0/1 range" cosAngle _ (cosAngle - minCos) / deltaCos. self flag: #TODO. "Don't scale by (maxCos - minCos)" " ]." self flag: #TODO. "Use table lookup for spot exponent" ^cosAngle raisedTo: self spotExponent! ! !B3DLightSource methodsFor: 'shading' stamp: 'ar 2/15/1999 03:55'! shadeVertexBuffer: vb with: aMaterial into: colorArray "This is the generic shading function similar to the primitive. Subclasses may implement optimized versions but should evaluate exactly to the same value as in here if they are to be converted into B3DPrimitiveLights." | color vtxArray ambientColor vtx direction distance scale cosAngle diffusePart specularPart specDir specularFactor | self flag: #b3dPrimitive. vtxArray _ vb vertexArray. (self hasAmbientPart and:[vb trackAmbientColor not]) ifTrue:[ambientColor _ aMaterial ambientPart * lightColor ambientPart]. (self hasDiffusePart and:[vb trackDiffuseColor not]) ifTrue:[diffusePart _ aMaterial diffusePart]. (self hasSpecularPart and:[vb trackSpecularColor not]) ifTrue:[specularPart _ aMaterial specularPart]. 1 to: vb vertexCount do:[:i| vtx _ vtxArray at: i. color _ colorArray at: i. "Compute the direction and distance of light source from vertex" direction _ self computeDirectionTo: vtx. distance _ direction length. (distance = 0.0 or:[distance = 1.0]) ifFalse:[direction /= distance negated]. "Compute the attenuation for the given distance" self isAttenuated ifTrue:[scale _ self computeAttenuationFor: distance] ifFalse:[scale _ 1.0]. "Compute spot light factor" self hasSpot ifTrue:[scale _ scale * (self computeSpotFactor: direction)]. "Compute ambient part" self hasAmbientPart ifTrue:[ vb trackAmbientColor ifTrue:[ambientColor _ vtx b3dColor * lightColor ambientPart]. color += (ambientColor * scale). ]. "Compute the diffuse part of the light" self hasDiffusePart ifTrue:[ "Compute angle from light->vertex to vertex normal" cosAngle _ vtx normal dot: direction. "For one-sided lighting negate cosAngle if necessary" (vb twoSidedLighting not and:[cosAngle < 0.0]) ifTrue:[cosAngle _ 0.0 - cosAngle]. "For two-sided lighting check if cosAngle > 0.0 meaning that it is a front face" cosAngle > 0.0 ifTrue:[ vb trackDiffuseColor ifTrue:[diffusePart _ vtx b3dColor]. color += (diffusePart * lightColor diffusePart * (cosAngle * scale)). ]. ]. "Compute specular part of the light" (self hasSpecularPart and:[aMaterial shininess > 0.0]) ifTrue:[ vb useLocalViewer ifTrue:[specDir _ direction - vtx position safelyNormalized] ifFalse:[specDir _ direction - (0@0@1.0)]. cosAngle _ vtx normal dot: specDir. cosAngle > 0.0 ifTrue:[ "Normalize the angle" cosAngle _ cosAngle / specDir length. "cosAngle should be somewhere between 0 and 1. If not, then the vertex normal was not normalized" cosAngle > 1.0 ifTrue:[ specularFactor _ cosAngle raisedTo: aMaterial shininess. ] ifFalse:[ self flag: #TODO. "Use table lookup later" specularFactor _ cosAngle raisedTo: self shininess. ]. color += (specularPart * lightColor specularPart * specularFactor). ]. ]. self flag: #TODO. "Check specular part" colorArray at: i put: color. ].! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:17'! direction "If the light is directional, return the NORMALIZED direction of the light" ^B3DVector3 zero! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:28'! hotSpotDeltaCosine "Return the cosine value of the delta radius of a spot light (the fall off region)" ^self hotSpotMaxCosine - self hotSpotMinCosine! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:27'! hotSpotMaxCosine "Return the cosine value of the outer radius of a spot light (the unlit region)" ^0.0! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:26'! hotSpotMinCosine "Return the cosine value of the inner radius of a spot light (the fully lit region)" ^0.0! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/8/1999 16:53'! lightColor ^lightColor! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ar 2/8/1999 16:53'! lightColor: aMaterialColor lightColor _ aMaterialColor! ! !B3DLightSource methodsFor: 'accessing' stamp: 'ti 3/27/2000 14:21'! spotExponent "Return the exponent to be used for the spot fall off computation" ^1.0! ! !B3DLightSource methodsFor: 'testing' stamp: 'ar 2/7/1999 17:22'! hasAmbientPart "Return true if the receiver contains an ambient part in its color" ^true! ! !B3DLightSource methodsFor: 'testing' stamp: 'ar 2/15/1999 23:07'! hasDiffusePart "Return true if the receiver contains a diffuse part in its color" ^true! ! !B3DLightSource methodsFor: 'testing' stamp: 'ar 2/15/1999 23:07'! hasSpecularPart "Return true if the receiver contains a specular part in its color" ^true! ! !B3DLightSource methodsFor: 'testing' stamp: 'ar 2/7/1999 18:52'! hasSpot "Return true if the receiver has a hot spot." ^false! ! !B3DLightSource methodsFor: 'testing' stamp: 'ar 2/7/1999 17:27'! isAttenuated "Return true if the receiver contains an attenuation. If so, #computeAttenuationFor: must return the attenuation for the given distance." ^true! ! !B3DLightSource methodsFor: 'converting' stamp: 'ar 2/7/1999 06:45'! asPrimitiveLight "Convert the receiver into a B3DPrimitiveLight that can be handled by the shader primitive directly. Light sources that cannot be represented as primitive should return nil. This will result in the callback of #shadeVertexBuffer from the shader." ^nil! ! !B3DLightSource methodsFor: 'converting' stamp: 'ar 2/8/1999 01:29'! transformedBy: aTransformer ^self clone! ! !B3DLightSource methodsFor: 'private' stamp: 'ar 2/7/1999 16:37'! setColor: aColor lightColor _ B3DMaterialColor new. lightColor ambientPart: aColor. lightColor diffusePart: aColor. lightColor specularPart: aColor.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DLightSource class instanceVariableNames: ''! !B3DLightSource class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 16:09'! color: aColor ^self new setColor: aColor.! ! B3DMaterialColor variableWordSubclass: #B3DMaterial instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DMaterial methodsFor: 'initialize' stamp: 'ar 2/16/1999 03:05'! from3DS: aDictionary self ambientPart: (aDictionary at: #ambient ifAbsent:[B3DColor4 r: 0.0 g: 0.0 b: 0.0 a: 1.0]). self diffusePart: (aDictionary at: #diffuse ifAbsent:[B3DColor4 r: 0.0 g: 0.0 b: 0.0 a: 1.0]). self specularPart: (aDictionary at: #specular ifAbsent:[B3DColor4 r: 0.0 g: 0.0 b: 0.0 a: 1.0]). (aDictionary includesKey: #textureName) ifTrue:[^(aDictionary at: #textureName) -> self].! ! !B3DMaterial methodsFor: 'accessing' stamp: 'ar 2/8/1999 00:54'! emission ^B3DColor4 r: self emissionRed g: self emissionGreen b: self emissionBlue a: self emissionAlpha! ! !B3DMaterial methodsFor: 'accessing' stamp: 'ar 2/8/1999 00:53'! emission: aColor self emissionRed: aColor red. self emissionGreen: aColor green. self emissionBlue: aColor blue. self emissionAlpha: aColor alpha.! ! !B3DMaterial methodsFor: 'accessing' stamp: 'ar 2/8/1999 00:58'! shininess ^self floatAt: 17! ! !B3DMaterial methodsFor: 'accessing' stamp: 'ar 2/8/1999 00:59'! shininess: aFloat ^self floatAt: 17 put: (aFloat max: 0.0).! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:58'! emissionAlpha ^self floatAt: 16! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:59'! emissionAlpha: aFloat self floatAt: 16 put: aFloat! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:58'! emissionBlue ^self floatAt: 15! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:59'! emissionBlue: aFloat self floatAt: 15 put: aFloat! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:58'! emissionGreen ^self floatAt: 14! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:59'! emissionGreen: aFloat self floatAt: 14 put: aFloat! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:58'! emissionRed ^self floatAt: 13! ! !B3DMaterial methodsFor: 'element access' stamp: 'ar 2/8/1999 00:59'! emissionRed: aFloat self floatAt: 13 put: aFloat! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DMaterial class instanceVariableNames: ''! !B3DMaterial class methodsFor: 'instance creation' stamp: 'ar 2/8/1999 01:01'! from3DS: aDictionary ^self new from3DS: aDictionary! ! !B3DMaterial class methodsFor: 'instance creation' stamp: 'ar 2/8/1999 01:00'! numElements ^17! ! B3DFloatArray variableWordSubclass: #B3DMaterialColor instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:58'! ambientPart ^B3DColor4 r: self ambientRed g: self ambientGreen b: self ambientBlue a: self ambientAlpha! ! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 19:00'! ambientPart: aColor self ambientRed: aColor red. self ambientGreen: aColor green. self ambientBlue: aColor blue. self ambientAlpha: aColor alpha.! ! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:58'! diffusePart ^B3DColor4 r: self diffuseRed g: self diffuseGreen b: self diffuseBlue a: self diffuseAlpha! ! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 19:00'! diffusePart: aColor self diffuseRed: aColor red. self diffuseGreen: aColor green. self diffuseBlue: aColor blue. self diffuseAlpha: aColor alpha.! ! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 18:59'! specularPart ^B3DColor4 r: self specularRed g: self specularGreen b: self specularBlue a: self specularAlpha! ! !B3DMaterialColor methodsFor: 'accessing' stamp: 'ar 2/6/1999 19:01'! specularPart: aColor self specularRed: aColor red. self specularGreen: aColor green. self specularBlue: aColor blue. self specularAlpha: aColor alpha.! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! ambientAlpha ^self floatAt: 4! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! ambientAlpha: aFloat ^self floatAt: 4 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! ambientBlue ^self floatAt: 3! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! ambientBlue: aFloat ^self floatAt: 3 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! ambientGreen ^self floatAt: 2! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! ambientGreen: aFloat ^self floatAt: 2 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! ambientRed ^self floatAt: 1! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! ambientRed: aFloat ^self floatAt: 1 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! diffuseAlpha ^self floatAt: 8! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! diffuseAlpha: aFloat ^self floatAt: 8 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! diffuseBlue ^self floatAt: 7! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! diffuseBlue: aFloat ^self floatAt: 7 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! diffuseGreen ^self floatAt: 6! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! diffuseGreen: aFloat ^self floatAt: 6 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:01'! diffuseRed ^self floatAt: 5! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! diffuseRed: aFloat ^self floatAt: 5 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! specularAlpha ^self floatAt: 12! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! specularAlpha: aFloat ^self floatAt: 12 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! specularBlue ^self floatAt: 11! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:03'! specularBlue: aFloat ^self floatAt: 11 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! specularGreen ^self floatAt: 10! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:04'! specularGreen: aFloat ^self floatAt: 10 put: aFloat! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:02'! specularRed ^self floatAt: 9! ! !B3DMaterialColor methodsFor: 'element access' stamp: 'ar 2/6/1999 19:04'! specularRed: aFloat ^self floatAt: 9 put: aFloat! ! !B3DMaterialColor methodsFor: 'private' stamp: 'ar 2/7/1999 18:41'! setColor: aColor self ambientPart: aColor. self diffusePart: aColor. self specularPart: aColor.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DMaterialColor class instanceVariableNames: ''! !B3DMaterialColor class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 18:41'! color: aColor ^self new setColor: aColor! ! !B3DMaterialColor class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 19:04'! numElements ^12! ! B3DFloatArray variableWordSubclass: #B3DMatrix4x4 instanceVariableNames: '' classVariableNames: 'B3DIdentityMatrix B3DZeroMatrix ' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DMatrix4x4 commentStamp: '' prior: 0! I represent a general 4x4 transformation matrix commonly used in computer graphics.! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:26'! setBSplineBase "Set the receiver to the BSpline base matrix" "for further information see: Foley, van Dam, Feiner, Hughes 'Computer Graphics: Principles and Practice' Addison-Wesley Publishing Company Second Edition, pp. 505" self a11: -1.0 / 6.0; a12: 3.0 / 6.0; a13: -3.0 / 6.0; a14: 1.0 / 6.0; a21: 3.0 / 6.0; a22: -6.0 / 6.0; a23: 3.0 / 6.0; a24: 0.0 / 6.0; a31: -3.0 / 6.0; a32: 0.0 / 6.0; a33: 3.0 / 6.0; a34: 0.0 / 6.0; a41: 1.0 / 6.0; a42: 4.0 / 6.0; a43: 1.0 / 6.0; a44: 0.0 / 6.0 ! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:26'! setBetaSplineBaseBias: beta1 tension: beta2 "Set the receiver to the betaSpline base matrix if beta1=1 and beta2=0 then the bSpline base matrix will be returned" "for further information see: Foley, van Dam, Feiner, Hughes 'Computer Graphics: Principles and Practice' Addison-Wesley Publishing Company Second Edition, pp. 505" | b12 b13 delta | b12 := beta1 * beta1. b13 := beta1 * b12. delta := 1.0 / (beta2 + (2.0 * b13) + 4.0 * (b12 + beta1) +2.0). self a11: delta * -2.0 * b13; a12: delta * 2.0 * (beta2 + b13 + b12 + beta1); a13: delta * -2.0 * (beta2 + b12 + beta1 + 1.0); a14: delta * 2.0; a21: delta * 6.0 * b13; a22: delta * -3.0 * (beta2 + (2.0 * (b13 + b12))); a23: delta * 3.0 * (beta2 + (2.0 * b12)); a24: 0.0; a31: delta * -6.0 * b13; a32: delta * 6.0 * (b13 - beta1); a33: delta * 6.0 * beta1; a34: 0.0; a41: delta * 2.0 * b13; a42: delta * (beta2 + 4.0 * (b12 + beta1)); a43: delta * 2.0; a44: 0.0 ! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:27'! setBezierBase "Set the receiver to the bezier base matrix" "for further information see: Foley, van Dam, Feiner, Hughes 'Computer Graphics: Principles and Practice' Addison-Wesley Publishing Company Second Edition, pp. 505" self a11: -1.0; a12: 3.0; a13: -3.0; a14: 1.0; a21: 3.0; a22: -6.0; a23: 3.0; a24: 0.0; a31: -3.0; a32: 3.0; a33: 0.0; a34: 0.0; a41: 1.0; a42: 0.0; a43: 0.0; a44: 0.0! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:27'! setCardinalBase "Set the receiver to the cardinal spline base matrix - just catmull * 2" "for further information see: Foley, van Dam, Feiner, Hughes 'Computer Graphics: Principles and Practice' Addison-Wesley Publishing Company Second Edition, pp. 505" self a11: -1.0; a12: 3.0; a13: -3.0; a14: 1.0; a21: 2.0; a22: -5.0; a23: 4.0; a24: -1.0; a31: -1.0; a32: 0.0; a33: 1.0; a34: 0.0; a41: 0.0; a42: 2.0; a43: 0.0; a44: 0.0 ! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:27'! setCatmullBase "Set the receiver to the Catmull-Rom base matrix" "for further information see: Foley, van Dam, Feiner, Hughes 'Computer Graphics: Principles and Practice' Addison-Wesley Publishing Company Second Edition, pp. 505" self a11: -0.5; a12: 1.5; a13: -1.5; a14: 0.5; a21: 1.0; a22: -2.5; a23: 2.0; a24: -0.5; a31: -0.5; a32: 0.0; a33: 0.5; a34: 0.0; a41: 0.0; a42: 1.0; a43: 0.0; a44: 0.0 ! ! !B3DMatrix4x4 methodsFor: 'initialize'! setIdentity "Set the receiver to the identity matrix" self loadFrom: B3DIdentityMatrix! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/1/1999 21:27'! setPolylineBase "Set the receiver to the polyline base matrix :)" self a11: 0.0; a12: 0.0; a13: 0.0; a14: 0.0; a21: 0.0; a22: 0.0; a23: 0.0; a24: 0.0; a31: 0.0; a32: -1.0; a33: 1.0; a34: 0.0; a41: 0.0; a42: 1.0; a43: 0.0; a44: 0.0 ! ! !B3DMatrix4x4 methodsFor: 'initialize' stamp: 'ar 2/15/1999 02:55'! setScale: aVector self a11: aVector x; a22: aVector y; a33: aVector z! ! !B3DMatrix4x4 methodsFor: 'initialize'! setTranslation: aVector self a14: aVector x; a24: aVector y; a34: aVector z! ! !B3DMatrix4x4 methodsFor: 'initialize'! setZero "Set the receiver to the zero matrix" self loadFrom: B3DZeroMatrix! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a11 "Return the element a11" ^self at: 1! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a11: aNumber "Store the element a11" self at: 1 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a12 "Return the element a12" ^self at: 2! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a12: aNumber "Store the element a12" self at: 2 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a13 "Return the element a13" ^self at: 3! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a13: aNumber "Store the element a13" self at: 3 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a14 "Return the element a14" ^self at: 4! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a14: aNumber "Store the element a14" self at: 4 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a21 "Return the element a21" ^self at: 5! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a21: aNumber "Store the element a21" self at: 5 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a22 "Return the element a22" ^self at: 6! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a22: aNumber "Store the element a22" self at: 6 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a23 "Return the element a23" ^self at: 7! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a23: aNumber "Store the element a23" self at: 7 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a24 "Return the element a24" ^self at: 8! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a24: aNumber "Store the element a24" self at: 8 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a31 "Return the element a31" ^self at: 9! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a31: aNumber "Store the element a31" self at: 9 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a32 "Return the element a32" ^self at: 10! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a32: aNumber "Store the element a32" self at: 10 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a33 "Return the element a33" ^self at: 11! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a33: aNumber "Store the element a33" self at: 11 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a34 "Return the element a34" ^self at: 12! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a34: aNumber "Store the element a34" self at: 12 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a41 "Return the element a41" ^self at: 13! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a41: aNumber "Store the element a41" self at: 13 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a42 "Return the element a42" ^self at: 14! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a42: aNumber "Store the element a42" self at: 14 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a43 "Return the element a43" ^self at: 15! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a43: aNumber "Store the element a43" self at: 15 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:29'! a44 "Return the element a44" ^self at: 16! ! !B3DMatrix4x4 methodsFor: 'element-access' stamp: 'ar 2/1/1999 21:28'! a44: aNumber "Store the element a44" self at: 16 put: aNumber! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/25/1999 13:58'! alternateRotation "Return the angular rotation around each axis of the matrix" | cp sp cy sy cr sr vAngles | vAngles _ B3DVector3 new. ((self a13) = 0) ifTrue: [ ((self a33) >= 0) ifTrue: [ vAngles at: 2 put: 0. cr _ (self a11). sr _ (self a12). cp _ (self a33). ] ifFalse: [ vAngles at: 2 put: (Float pi). cr _ (self a11) negated. sr _ (self a12) negated. cp _ (self a33) negated. ] ] ifFalse: [ vAngles at: 2 put: (((self a13) negated) arcTan: (self a33)). cy _ (vAngles at: 3) cos. sy _ (vAngles at: 3) sin. cr _ (cy * (self a11)) + (sy * (self a31)). sr _ (cy* (self a12)) + (sy * (self a32)). cp _ (cy * (self a33)) - (sy * (self a13)). ]. sp _ (self a23). vAngles at: 1 put: (sp arcTan: cp). vAngles at: 3 put: (sr arcTan: cr). vAngles at: 1 put: ((vAngles at: 1) radiansToDegrees). vAngles at: 2 put: ((vAngles at: 2) radiansToDegrees). vAngles at: 3 put: ((vAngles at: 3) radiansToDegrees). ^ vAngles. ! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/11/1999 14:09'! at: i at: j ^ self at: ((i - 1) * 4 + j). ! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/11/1999 14:09'! at: i at: j put: aValue ^ self at: ((i - 1) * 4 + j) put: aValue. ! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/25/1999 13:58'! rotation "Return the angular rotation around each axis of the matrix" | vRow1 vRow2 vRow3 vScale vShear vAngles vRowCross determinate | vRow1 _ self row1. vRow2 _ self row2. vRow3 _ self row3. vScale _ B3DVector3 new. vShear _ B3DVector3 new. vAngles _ B3DVector3 new. vScale at: 1 put: (vRow1 length). vRow1 normalize. vShear at: 1 put: (vRow1 dot: vRow2). vRow2 _ vRow2 + (vRow1 * ((vShear at: 1) negated)). vScale at: 2 put: (vRow2 length). vRow2 normalize. vShear at: 1 put: ((vShear at: 1) / (vScale at: 2)). vShear at: 2 put: (vRow1 dot: vRow3). vRow3 _ vRow3 + (vRow1 * ((vShear at: 2) negated)). vShear at: 3 put: (vRow2 dot: vRow3). vRow3 _ vRow3 + (vRow2 * ((vShear at: 3) negated)). vScale at: 3 put: (vRow3 length). vRow3 normalize. vShear at: 2 put: ((vShear at: 2) / (vScale at: 3)). vShear at: 3 put: ((vShear at: 3) / (vScale at: 3)). vRowCross _ vRow2 cross: vRow3. determinate _ vRow1 dot: vRowCross. (determinate < 0.0) ifTrue: [ vRow1 _ vRow1 negated. vRow2 _ vRow2 negated. vRow3 _ vRow3 negated. vScale _ vScale negated. ]. vAngles at: 2 put: ((vRow1 at: 3) negated) arcSin. (((vAngles at: 2) cos) ~= 0.0) ifTrue: [ vAngles at: 1 put: ((vRow2 at: 3) arcTan: (vRow3 at: 3)). vAngles at: 3 put: ((vRow1 at: 2) arcTan: (vRow1 at: 1)). ] ifFalse: [ vAngles at: 1 put: ((vRow2 at: 1) arcTan: (vRow2 at: 2)). vAngles at: 3 put: 0.0 ]. vAngles at: 1 put: ((vAngles at: 1) radiansToDegrees). vAngles at: 2 put: ((vAngles at: 2) radiansToDegrees). vAngles at: 3 put: ((vAngles at: 3) radiansToDegrees). ^ vAngles. ! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/24/1999 09:46'! rotation: aVector | xRot yRot zRot cosPitch sinPitch cosYaw sinYaw cosRoll sinRoll | xRot _ (aVector x) degreesToRadians. yRot _ (aVector y) degreesToRadians. zRot _ (aVector z) degreesToRadians. cosPitch _ xRot cos. sinPitch _ xRot sin. cosYaw _ yRot cos. sinYaw _ yRot sin. cosRoll _ zRot cos. sinRoll _ zRot sin. self a11: (cosRoll*cosYaw). self a12: (sinRoll*cosYaw). self a13: (sinYaw negated). self a21: ((cosRoll*sinYaw*sinPitch) - (sinRoll*cosPitch)). self a22: ((cosRoll*cosPitch) + (sinRoll*sinYaw*sinPitch)). self a23: (cosYaw*sinPitch). self a31: ((cosRoll*sinYaw*cosPitch) + (sinRoll*sinPitch)). self a32: ((sinRoll*sinYaw*cosPitch) - (cosRoll*sinPitch)). self a33: (cosYaw*cosPitch). ^ self. ! ! !B3DMatrix4x4 methodsFor: 'accessing'! rotation: anAngle around: aVector3 "set up a rotation matrix around the direction aVector3" self loadFrom: (B3DRotation angle: anAngle axis: aVector3) asMatrix4x4! ! !B3DMatrix4x4 methodsFor: 'accessing'! rotation: anAngle aroundX: xValue y: yValue z: zValue "set up a rotation matrix around the direction x/y/z" ^self rotation: anAngle around:(B3DVector3 with: xValue with: yValue with: zValue)! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:34'! rotationAroundX: anAngle | rad s c | rad := anAngle degreesToRadians. s := rad sin. c := rad cos. self a22: c. self a23: s negated. self a33: c. self a32: s. ^self! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:34'! rotationAroundY: anAngle | rad s c | rad := anAngle degreesToRadians. s := rad sin. c := rad cos. self a11: c. self a13: s. self a33: c. self a31: s negated. ^self! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:35'! rotationAroundZ: anAngle | rad s c | rad := anAngle degreesToRadians. s := rad sin. c := rad cos. self a11: c. self a12: s negated. self a22: c. self a21: s. ^self! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:35'! scaling: aVector ^self scalingX: aVector x y: aVector y z: aVector z! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:35'! scalingX: xValue y: yValue z: zValue self a11: xValue. self a22: yValue. self a33: zValue. ^self! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 4/16/1999 21:51'! squaredDistanceFrom: aMatrix | sum | sum _ 0.0. 1 to: 4 do:[:i| 1 to: 4 do:[:j| sum _ sum + ((self at: i at: j) - (aMatrix at: i at: j)) squared]]. ^sum! ! !B3DMatrix4x4 methodsFor: 'accessing'! translation ^(B3DVector3 x: self a14 y: self a24 z: self a34)! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:36'! translation: aVector ^self translationX: aVector x y: aVector y z: aVector z! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:36'! translationX: xValue y: yValue z: zValue self a14: xValue. self a24: yValue. self a34: zValue. ^self! ! !B3DMatrix4x4 methodsFor: 'accessing' stamp: 'jsp 2/25/1999 13:58'! trotation "Return the angular rotation around each axis of the matrix" | cp sp cy sy cr sr vAngles | vAngles _ B3DVector3 new. ((self a13) = 0) ifTrue: [ ((self a33) >= 0) ifTrue: [ vAngles at: 2 put: 0. cr _ (self a11). sr _ (self a12). cp _ (self a33). ] ifFalse: [ vAngles at: 2 put: (Float pi). cr _ (self a11) negated. sr _ (self a12) negated. cp _ (self a33) negated. ] ] ifFalse: [ vAngles at: 2 put: (((self a13) negated) arcTan: (self a33)). cy _ (vAngles at: 3) cos. sy _ (vAngles at: 3) sin. cr _ (cy * (self a11)) + (sy * (self a31)). sr _ (cy* (self a12)) + (sy * (self a32)). cp _ (cy * (self a33)) - (sy * (self a13)). ]. sp _ (self a23). vAngles at: 1 put: (sp arcTan: cp). vAngles at: 3 put: (sr arcTan: cr). vAngles at: 1 put: ((vAngles at: 1) radiansToDegrees). vAngles at: 2 put: ((vAngles at: 2) radiansToDegrees). vAngles at: 3 put: ((vAngles at: 3) radiansToDegrees). ^ vAngles. ! ! !B3DMatrix4x4 methodsFor: 'arithmetic' stamp: 'ar 2/2/2001 15:47'! + aB3DMatrix "Optimized for Matrix/Matrix operations" ^super + aB3DMatrix! ! !B3DMatrix4x4 methodsFor: 'arithmetic' stamp: 'ar 2/2/2001 15:47'! - aB3DMatrix "Optimized for Matrix/Matrix operations" ^super - aB3DMatrix! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 11/7/2000 14:48'! composeWith: m2 "Perform a 4x4 matrix multiplication." ^self composedWithLocal: m2.! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 2/15/1999 23:56'! composedWithGlobal: aB3DMatrix4x4 | result | result _ self class new. self privateTransformMatrix: aB3DMatrix4x4 with: self into: result. ^result! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 2/15/1999 23:57'! composedWithLocal: aB3DMatrix4x4 | result | result _ self class new. self privateTransformMatrix: self with: aB3DMatrix4x4 into: result. ^result! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 5/21/2000 16:34'! inverseTransformation "Return the inverse matrix of the receiver." ^self clone inplaceHouseHolderInvert.! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 11/7/2000 17:29'! localDirToGlobal: aVector "Multiply direction vector with the receiver" | x y z rx ry rz | x := aVector x. y := aVector y. z := aVector z. rx := (x * self a11) + (y * self a12) + (z * self a13). ry := (x * self a21) + (y * self a22) + (z * self a23). rz := (x * self a31) + (y * self a32) + (z * self a33). ^B3DVector3 x: rx y: ry z: rz! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 2/15/1999 23:50'! localPointToGlobal: aVector "Multiply aVector (temporarily converted to 4D) with the receiver" | x y z rx ry rz rw | x := aVector x. y := aVector y. z := aVector z. rx := (x * self a11) + (y * self a12) + (z * self a13) + self a14. ry := (x * self a21) + (y * self a22) + (z * self a23) + self a24. rz := (x * self a31) + (y * self a32) + (z * self a33) + self a34. rw := (x * self a41) + (y * self a42) + (z * self a43) + self a44. ^B3DVector3 x:(rx/rw) y: (ry/rw) z: (rz/rw)! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 2/7/1999 06:32'! quickTransformV3ArrayFrom: srcArray to: dstArray "Transform the 3 element vertices from srcArray to dstArray. ASSUMPTION: a41 = a42 = a43 = 0.0 and a44 = 1.0" | a11 a12 a13 a14 a21 a22 a23 a24 a31 a32 a33 a34 x y z index | self flag: #b3dPrimitive. a11 _ self a11. a12 _ self a12. a13 _ self a13. a14 _ self a14. a21 _ self a21. a22 _ self a22. a23 _ self a23. a24 _ self a24. a31 _ self a31. a32 _ self a32. a33 _ self a33. a34 _ self a34. 1 to: srcArray size do:[:i| index _ i-1*3. x _ srcArray floatAt: index+1. y _ srcArray floatAt: index+2. z _ srcArray floatAt: index+3. dstArray floatAt: index+1 put: (a11*x) + (a12*y) + (a13*z) + a14. dstArray floatAt: index+2 put: (a21*x) + (a22*y) + (a23*z) + a24. dstArray floatAt: index+3 put: (a31*x) + (a32*y) + (a33*z) + a34. ]. ^dstArray! ! !B3DMatrix4x4 methodsFor: 'transforming' stamp: 'ar 2/1/1999 21:42'! transposed "Return a transposed copy of the receiver" | matrix | matrix := self class new. matrix a11: self a11; a12: self a21; a13: self a31; a14: self a41; a21: self a12; a22: self a22; a23: self a32; a24: self a42; a31: self a13; a32: self a23; a33: self a33; a34: self a43; a41: self a14; a42: self a24; a43: self a34; a44: self a44. ^matrix! ! !B3DMatrix4x4 methodsFor: 'double dispatching' stamp: 'ar 2/1/1999 21:49'! printOn: aStream "Print the receiver on aStream" 1 to: 4 do:[:r| 1 to: 4 do:[:c| (self at: r-1*4+c) printOn: aStream. aStream nextPut: Character space]. (r < 4) ifTrue:[aStream nextPut: Character cr]].! ! !B3DMatrix4x4 methodsFor: 'double dispatching' stamp: 'ar 2/8/1999 20:11'! productFromMatrix4x4: matrix "Multiply a 4x4 matrix with the receiver." | result | result := self class new. result a11: ((matrix a11 * self a11) + (matrix a12 * self a21) + (matrix a13 * self a31) + (matrix a14 * self a41)). result a12: ((matrix a11 * self a12) + (matrix a12 * self a22) + (matrix a13 * self a32) + (matrix a14 * self a42)). result a13: ((matrix a11 * self a13) + (matrix a12 * self a23) + (matrix a13 * self a33) + (matrix a14 * self a43)). result a14: ((matrix a11 * self a14) + (matrix a12 * self a24) + (matrix a13 * self a34) + (matrix a14 * self a44)). result a21: ((matrix a21 * self a11) + (matrix a22 * self a21) + (matrix a23 * self a31) + (matrix a24 * self a41)). result a22: ((matrix a21 * self a12) + (matrix a22 * self a22) + (matrix a23 * self a32) + (matrix a24 * self a42)). result a23: ((matrix a21 * self a13) + (matrix a22 * self a23) + (matrix a23 * self a33) + (matrix a24 * self a43)). result a24: ((matrix a21 * self a14) + (matrix a22 * self a24) + (matrix a23 * self a34) + (matrix a24 * self a44)). result a31: ((matrix a31 * self a11) + (matrix a32 * self a21) + (matrix a33 * self a31) + (matrix a34 * self a41)). result a32: ((matrix a31 * self a12) + (matrix a32 * self a22) + (matrix a33 * self a32) + (matrix a34 * self a42)). result a33: ((matrix a31 * self a13) + (matrix a32 * self a23) + (matrix a33 * self a33) + (matrix a34 * self a43)). result a34: ((matrix a31 * self a14) + (matrix a32 * self a24) + (matrix a33 * self a34) + (matrix a34 * self a44)). result a41: ((matrix a41 * self a11) + (matrix a42 * self a21) + (matrix a43 * self a31) + (matrix a44 * self a41)). result a42: ((matrix a41 * self a12) + (matrix a42 * self a22) + (matrix a43 * self a32) + (matrix a44 * self a42)). result a43: ((matrix a41 * self a13) + (matrix a42 * self a23) + (matrix a43 * self a33) + (matrix a44 * self a43)). result a44: ((matrix a41 * self a14) + (matrix a42 * self a24) + (matrix a43 * self a34) + (matrix a44 * self a44)). ^result! ! !B3DMatrix4x4 methodsFor: 'double dispatching'! productFromVector3: aVector3 "Multiply aVector (temporarily converted to 4D) with the receiver" | x y z rx ry rz rw | x := aVector3 x. y := aVector3 y. z := aVector3 z. rx := (x * self a11) + (y * self a21) + (z * self a31) + self a41. ry := (x * self a12) + (y * self a22) + (z * self a32) + self a42. rz := (x * self a13) + (y * self a23) + (z * self a33) + self a43. rw := (x * self a14) + (y * self a24) + (z * self a34) + self a44. ^B3DVector3 x:(rx/rw) y: (ry/rw) z: (rz/rw)! ! !B3DMatrix4x4 methodsFor: 'double dispatching'! productFromVector4: aVector4 "Multiply aVector with the receiver" | x y z w rx ry rz rw | x := aVector4 x. y := aVector4 y. z := aVector4 z. w := aVector4 w. rx := (x * self a11) + (y * self a21) + (z * self a31) + (w * self a41). ry := (x * self a12) + (y * self a22) + (z * self a32) + (w * self a42). rz := (x * self a13) + (y * self a23) + (z * self a33) + (w * self a43). rw := (x * self a14) + (y * self a24) + (z * self a34) + (w * self a44). ^B3DVector4 x:rx y: ry z: rz w: rw! ! !B3DMatrix4x4 methodsFor: 'solving' stamp: 'ar 2/1/1999 21:50'! inplaceDecomposeLU "Decompose the receiver in place by using gaussian elimination w/o pivot search" | x | 1 to: 4 do:[:j| "i-th equation (row)" j+1 to: 4 do:[:i| x := (self at: i at: j) / (self at: j at: j). j to: 4 do:[:k| self at: i at: k put: (self at: i at: k) - ((self at: j at: k) * x)]. self at: i at: j put: x]]. ! ! !B3DMatrix4x4 methodsFor: 'solving' stamp: 'ar 5/22/2000 17:13'! inplaceHouseHolderInvert "Solve the linear equation self * aVector = x by using HouseHolder's transformation. Note: This scheme is numerically better than using gaussian elimination even though it takes somewhat longer" | d x sigma beta sum s| x _ B3DMatrix4x4 identity. d _ B3DMatrix4x4 new. 1 to: 4 do:[:j| sigma := 0.0. j to: 4 do:[:i| sigma := sigma + ((self at: i at: j) squared)]. sigma isZero ifTrue:[^nil]. "matrix is singular" ((self at: j at: j) < 0.0) ifTrue:[ s:= sigma sqrt] ifFalse:[ s:= sigma sqrt negated]. 1 to: 4 do:[:r| d at: j at: r put: s]. beta := 1.0 / ( s * (self at: j at: j) - sigma). self at: j at: j put: ((self at: j at: j) - s). "update remaining columns" j+1 to: 4 do:[:k| sum := 0.0. j to: 4 do:[:i| sum := sum + ((self at: i at: j) * (self at: i at: k))]. sum := sum * beta. j to: 4 do:[:i| self at: i at: k put: ((self at: i at: k) + ((self at: i at: j) * sum))]]. "update vector" 1 to: 4 do:[:r| sum := nil. j to: 4 do:[:i| sum := sum isNil ifTrue:[(x at: i at: r) * (self at: i at: j)] ifFalse:[sum + ((x at: i at: r) * (self at: i at: j))]]. sum := sum * beta. j to: 4 do:[:i| x at: i at: r put:((x at: i at: r) + (sum * (self at: i at: j)))]. ]. ]. "Now calculate result" 1 to: 4 do:[:r| 4 to: 1 by: -1 do:[:i| i+1 to: 4 do:[:j| x at: i at: r put: ((x at: i at: r) - ((x at: j at: r) * (self at: i at: j))) ]. x at: i at: r put: ((x at: i at: r) / (d at: i at: r))]. ]. self loadFrom: x. "Return receiver"! ! !B3DMatrix4x4 methodsFor: 'solving'! inplaceHouseHolderTransform: aVector "Solve the linear equation self * aVector = x by using HouseHolder's transformation. Note: This scheme is numerically better than using gaussian elimination even though it takes somewhat longer" | d x sigma beta sum s| x := Array with: aVector x with: aVector y with: aVector z with: aVector w. d := Array new: 4. 1 to: 4 do:[:j| sigma := 0.0. j to: 4 do:[:i| sigma := sigma + ((self at: i at: j) squared)]. sigma isZero ifTrue:[^nil]. "matrix is singular" ((self at: j at: j) < 0.0) ifTrue:[ s:= d at: j put: (sigma sqrt)] ifFalse:[ s:= d at: j put: (sigma sqrt negated)]. beta := 1.0 / ( s * (self at: j at: j) - sigma). self at: j at: j put: ((self at: j at: j) - s). "update remaining columns" j+1 to: 4 do:[:k| sum := 0.0. j to: 4 do:[:i| sum := sum + ((self at: i at: j) * (self at: i at: k))]. sum := sum * beta. j to: 4 do:[:i| self at: i at: k put: ((self at: i at: k) + ((self at: i at: j) * sum))]]. "update vector" sum := nil. j to: 4 do:[:i| sum := sum isNil ifTrue:[(x at: i) * (self at: i at: j)] ifFalse:[sum + ((x at: i) * (self at: i at: j))]]. sum := sum * beta. j to: 4 do:[:i| x at: i put:((x at: i) + (sum * (self at: i at: j)))]. ]. "Now calculate result" 4 to: 1 by: -1 do:[:i| i+1 to: 4 do:[:j| x at: i put: ((x at: i) - ((x at: j) * (self at: i at: j))) ]. x at: i put: ((x at: i) / (d at: i))]. ^B3DVector4 x: (x at: 1) y: (x at: 2) z: (x at: 3) w: (x at: 4) ! ! !B3DMatrix4x4 methodsFor: 'solving' stamp: 'ar 2/1/1999 21:52'! solve: aVector ^self clone inplaceHouseHolderTransform: aVector "or: ^self clone inplaceDecomposeLU solveLU: aVector "! ! !B3DMatrix4x4 methodsFor: 'solving'! solveLU: aVector "Given a decomposed matrix using gaussian elimination solve the linear equations." | x v | v := Array with: aVector x with: aVector y with: aVector z with: aVector w. "L first" 1 to: 4 do:[:i| "Top to bottom" x := 0.0. 1 to: i-1 do:[:j| "From left to right w/o diagonal element" x := x + ((v at: j) * (self at: i at: j))]. "No need to divide by the diagonal element - this is always 1.0 in L" v at: i put: (v at: i) - x]. "Now U" 4 to: 1 by: -1 do:[:i| "Bottom to top" x := 0.0. 4 to: i+1 by: -1 do:[:j| "From right to left w/o diagonal element" x := x + ((v at: j) * (self at: i at: j))]. "Divide by diagonal element" v at: i put: (v at: i) - x / (self at: i at: i)]. ^B3DVector4 x: (v at: 1) y: (v at: 2) z: (v at: 3) w: (v at: 4) ! ! !B3DMatrix4x4 methodsFor: 'comparing' stamp: 'ar 2/1/1999 21:53'! squaredErrorDistanceTo: anotherMatrix | result temp | result := self - anotherMatrix. temp := 0. 1 to: 4 do: [:i | 1 to: 4 do: [:j| temp := temp + ((result at: i-1*4+j) squared)]]. ^temp sqrt.! ! !B3DMatrix4x4 methodsFor: 'testing' stamp: 'ar 2/1/1999 21:54'! isIdentity ^self = B3DIdentityMatrix! ! !B3DMatrix4x4 methodsFor: 'testing' stamp: 'ar 2/1/1999 21:54'! isZero ^self = B3DZeroMatrix! ! !B3DMatrix4x4 methodsFor: 'converting'! asMatrix4x4 ^self! ! !B3DMatrix4x4 methodsFor: 'converting' stamp: 'jsp 3/5/1999 15:31'! asQuaternion "Convert the matrix to a quaternion" | x y z a a2 x2 y2 a4 | a2 _ 0.25 * (1.0 + (self a11) + (self a22) + (self a33)). (a2 > 0) ifTrue: [ a _ a2 sqrt. a4 _ 4.0 * a. x _ ((self a32) - (self a23)) / a4. y _ ((self a13) - (self a31)) / a4. z _ ((self a21) - (self a12)) / a4. ] ifFalse: [ a _ 0. x2 _ -0.5 * ((self a22) + (self a33)). (x2 > 0) ifTrue: [ x _ x2 sqrt. x2 _ 2 * x. y _ (self a21) / x2. z _ (self a31) / x2. ] ifFalse: [ x _ 0. y2 _ 0.5 * (1.0 - (self a33)). (y2 > 0) ifTrue: [ y _ y2 sqrt. y2 _ 2 * y. z _ (self a32) / y2. ] ifFalse: [ y _ 0.0. z _ 1.0. ] ] ]. ^ (B3DRotation a: a b: x c: y d: z). ! ! !B3DMatrix4x4 methodsFor: 'private' stamp: 'ar 11/7/2000 14:48'! privateTransformMatrix: m1 with: m2 into: m3 "Perform a 4x4 matrix multiplication m2 * m1 = m3 being equal to first transforming points by m2 and then by m1. Note that m1 may be identical to m3. NOTE: The primitive implementation does NOT return m3 - and so don't we!!" | c1 c2 c3 c4 | m2 == m3 ifTrue:[^self error:'Argument and result matrix identical']. c1 _ ((m1 a11 * m2 a11) + (m1 a12 * m2 a21) + (m1 a13 * m2 a31) + (m1 a14 * m2 a41)). c2 _ ((m1 a11 * m2 a12) + (m1 a12 * m2 a22) + (m1 a13 * m2 a32) + (m1 a14 * m2 a42)). c3 _ ((m1 a11 * m2 a13) + (m1 a12 * m2 a23) + (m1 a13 * m2 a33) + (m1 a14 * m2 a43)). c4 _ ((m1 a11 * m2 a14) + (m1 a12 * m2 a24) + (m1 a13 * m2 a34) + (m1 a14 * m2 a44)). m3 a11: c1; a12: c2; a13: c3; a14: c4. c1 _ ((m1 a21 * m2 a11) + (m1 a22 * m2 a21) + (m1 a23 * m2 a31) + (m1 a24 * m2 a41)). c2 _ ((m1 a21 * m2 a12) + (m1 a22 * m2 a22) + (m1 a23 * m2 a32) + (m1 a24 * m2 a42)). c3 _ ((m1 a21 * m2 a13) + (m1 a22 * m2 a23) + (m1 a23 * m2 a33) + (m1 a24 * m2 a43)). c4 _ ((m1 a21 * m2 a14) + (m1 a22 * m2 a24) + (m1 a23 * m2 a34) + (m1 a24 * m2 a44)). m3 a21: c1; a22: c2; a23: c3; a24: c4. c1 _ ((m1 a31 * m2 a11) + (m1 a32 * m2 a21) + (m1 a33 * m2 a31) + (m1 a34 * m2 a41)). c2 _ ((m1 a31 * m2 a12) + (m1 a32 * m2 a22) + (m1 a33 * m2 a32) + (m1 a34 * m2 a42)). c3 _ ((m1 a31 * m2 a13) + (m1 a32 * m2 a23) + (m1 a33 * m2 a33) + (m1 a34 * m2 a43)). c4 _ ((m1 a31 * m2 a14) + (m1 a32 * m2 a24) + (m1 a33 * m2 a34) + (m1 a34 * m2 a44)). m3 a31: c1; a32: c2; a33: c3; a34: c4. c1 _ ((m1 a41 * m2 a11) + (m1 a42 * m2 a21) + (m1 a43 * m2 a31) + (m1 a44 * m2 a41)). c2 _ ((m1 a41 * m2 a12) + (m1 a42 * m2 a22) + (m1 a43 * m2 a32) + (m1 a44 * m2 a42)). c3 _ ((m1 a41 * m2 a13) + (m1 a42 * m2 a23) + (m1 a43 * m2 a33) + (m1 a44 * m2 a43)). c4 _ ((m1 a41 * m2 a14) + (m1 a42 * m2 a24) + (m1 a43 * m2 a34) + (m1 a44 * m2 a44)). m3 a41: c1; a42: c2; a43: c3; a44: c4.! ! !B3DMatrix4x4 methodsFor: 'row-access' stamp: 'jsp 2/24/1999 17:10'! row1 "Return row 1" ^ (B3DVector3 x: (self a11) y: (self a12) z: (self a13)). ! ! !B3DMatrix4x4 methodsFor: 'row-access' stamp: 'jsp 2/24/1999 17:11'! row2 "Return row 2" ^ (B3DVector3 x: (self a21) y: (self a22) z: (self a23)). ! ! !B3DMatrix4x4 methodsFor: 'row-access' stamp: 'jsp 2/24/1999 17:11'! row3 "Return row 3" ^ (B3DVector3 x: (self a31) y: (self a32) z: (self a33)). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DMatrix4x4 class instanceVariableNames: ''! !B3DMatrix4x4 class methodsFor: 'class initialization' stamp: 'ar 2/1/1999 21:58'! initialize "B3DMatrix4x4 initialize" B3DZeroMatrix _ self new. B3DIdentityMatrix _ self new. B3DIdentityMatrix a11: 1.0; a22: 1.0; a33: 1.0; a44: 1.0.! ! !B3DMatrix4x4 class methodsFor: 'instance creation'! identity ^self new setIdentity! ! !B3DMatrix4x4 class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:25'! numElements ^16! ! !B3DMatrix4x4 class methodsFor: 'instance creation' stamp: 'ar 2/15/1999 23:58'! rotatedBy: angle around: axis centeredAt: origin "Create a matrix rotating points around the given origin using the angle/axis pair" | xform | xform _ self withOffset: origin negated. xform _ xform composedWithGlobal:(B3DRotation angle: angle axis: axis) asMatrix4x4. xform _ xform composedWithGlobal: (self withOffset: origin). ^xform! ! !B3DMatrix4x4 class methodsFor: 'instance creation' stamp: 'ar 2/15/1999 23:48'! withOffset: amount ^self identity setTranslation: amount! ! !B3DMatrix4x4 class methodsFor: 'instance creation'! zero ^self new! ! Morph subclass: #B3DMorph instanceVariableNames: 'camera geometry angle texture ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Demo Morphs'! !B3DMorph methodsFor: 'initialize' stamp: 'mjg 9/28/1999 10:19'! initialize super initialize. geometry _ B3DBox from: (-0.7@-0.7@-0.7) to: (0.7@0.7@0.7). camera _ B3DCamera new. (self confirm:'Put me into a clipping frame?') ifTrue:[camera position: 0@0@1.5] ifFalse:[camera position: 0@0@2. color _ nil]. camera nearDistance: 0.1. camera farDistance: 5.0. self extent: 100@100. texture _ (Form extent: 100@100) asTexture. angle _ 0.! ! !B3DMorph methodsFor: 'drawing' stamp: 'ar 2/8/1999 02:48'! drawOn: aCanvas color ifNotNil:["aCanvas frameAndFillRectangle: self bounds fillColor: color borderWidth: 1 borderColor: Color black." aCanvas frameRectangle: self bounds color: self color]. aCanvas asBalloonCanvas render: self. ! ! !B3DMorph methodsFor: 'drawing' stamp: 'ar 2/16/1999 17:26'! renderOn: aRenderer camera ifNotNil:[ aRenderer viewport: (self bounds insetBy: 1@1). aRenderer clearDepthBuffer. aRenderer loadIdentity. camera renderOn: aRenderer]. aRenderer texture: texture. aRenderer transformBy: (B3DRotation angle: angle axis: 0@1@0). geometry ifNotNil:[geometry renderOn: aRenderer].! ! !B3DMorph methodsFor: 'stepping' stamp: 'ar 2/4/1999 20:15'! step angle _ angle + 5. self changed.! ! !B3DMorph methodsFor: 'stepping' stamp: 'ar 2/4/1999 20:15'! stepTime ^50! ! !B3DMorph methodsFor: 'stepping' stamp: 'ar 2/4/1999 20:15'! wantsSteps ^true! ! !B3DMorph methodsFor: 'menu' stamp: 'ar 2/16/1999 17:22'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add:'set texture' action: #setTexture.! ! !B3DMorph methodsFor: 'menu' stamp: 'ar 2/16/1999 17:28'! setTexture | tex | tex _ B3DTexture fromDisplay:(Rectangle originFromUser: 128@128). tex wrap: true. tex interpolate: false. tex envMode: 0. texture _ tex. self changed! ! B3DGeometry subclass: #B3DMultiMesh instanceVariableNames: 'meshes ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DMultiMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 19:32'! meshes ^meshes! ! !B3DMultiMesh methodsFor: 'accessing' stamp: 'ar 8/31/2000 19:32'! meshes: aCollection meshes _ aCollection asArray! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DMultiMesh class instanceVariableNames: ''! !B3DMultiMesh class methodsFor: 'instance creation' stamp: 'ar 8/31/2000 19:31'! withAll: meshList ^self new meshes: meshList! ! B3DVertexRasterizer subclass: #B3DNullRasterizer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DNullRasterizer commentStamp: '' prior: 0! The only purpose of this rasterizer is to measure the actual transform/lighting/clipping speed of an engine.! !B3DNullRasterizer methodsFor: 'testing' stamp: 'ar 2/16/1999 02:31'! needsClip "Yepp. We want to see how well our clipper performs." ^true! ! !B3DNullRasterizer methodsFor: 'processing' stamp: 'ar 5/26/2000 15:34'! clearViewport: aColor "Do nothing"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DNullRasterizer class instanceVariableNames: ''! !B3DNullRasterizer class methodsFor: 'testing' stamp: 'ar 2/16/1999 17:37'! isAvailable "Return true if this part of the engine is available" ^true! ! B3DRenderEngine subclass: #B3DPickerEngine instanceVariableNames: 'pickMatrix pickList objects maxVtx ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DPickerEngine methodsFor: 'initialize' stamp: 'ar 4/18/1999 00:21'! flush "Ignored"! ! !B3DPickerEngine methodsFor: 'initialize' stamp: 'ar 6/2/1999 12:08'! initialize "Do not call super initialize here. We get our components directly by the creating engine." pickList _ SortedCollection new: 100. pickList sortBlock:[:a1 :a2| a1 value rasterPosZ < a2 value rasterPosZ]. objects _ OrderedCollection new: 100. objects resetTo: 1. maxVtx _ B3DPrimitiveVertex new. maxVtx rasterPosZ: 1.0e30. maxVtx rasterPosW: 1.0.! ! !B3DPickerEngine methodsFor: 'initialize' stamp: 'ar 4/17/1999 23:11'! loadFrom: aRenderEngine "Load our components from the given render engine. The idea is that all of the state is shared so that transformations send during picking will be preserved in the given render engine." vertexBuffer _ aRenderEngine getVertexBuffer. transformer _ aRenderEngine getTransformer. shader _ aRenderEngine getShader. clipper _ aRenderEngine getClipper. rasterizer _ aRenderEngine getRasterizer. ! ! !B3DPickerEngine methodsFor: 'picking' stamp: 'ar 4/18/1999 02:25'! pickAt: aPoint extent: extentPoint "Initialize the receiver for picking at the given point using the given extent." pickMatrix _ self pickingMatrixAt: aPoint extent: extentPoint.! ! !B3DPickerEngine methodsFor: 'picking' stamp: 'ar 6/2/1999 12:03'! render: anObject | assoc | assoc _ Association key: anObject value: maxVtx. objects addLast: assoc. anObject renderOn: self. (objects removeLast == assoc) ifFalse:[^self error:'Object stack is confused']. assoc value rasterPosZ > 2.0 ifFalse:[pickList add: assoc].! ! !B3DPickerEngine methodsFor: 'picking' stamp: 'ar 4/18/1999 00:08'! topMostObject "Return the top most of all picked objects" ^pickList isEmpty ifTrue:[nil] ifFalse:[pickList first key]! ! !B3DPickerEngine methodsFor: 'picking' stamp: 'ar 6/2/1999 12:08'! topMostVertex "Return the top most primitive vertex of all picked objects. Note: Except from the z value the vertex is *not* normalized yet (e.g., there was no division by w)" ^pickList isEmpty ifTrue:[nil] ifFalse:[pickList first value]! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 6/2/1999 11:54'! primComputeMinIndexZ: primType vtxArray: vtxArray vtxSize: vtxSize idxArray: idxArray idxSize: idxSize "" ^nil "Indicates failure"! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 03:01'! primComputeMinZ: primType vtxArray: vtxArray vtxSize: vtxSize idxArray: idxArray idxSize: idxSize ^nil "Indicates failure"! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 02:26'! privateTransformVB: vb "Transform the contents of the vertex buffer. Transforming may include normals (if lighting enabled) and textures (if textures enabled)." ^transformer processVertexBuffer: vb modelView: transformer modelViewMatrix projection: (transformer projectionMatrix composedWithGlobal: pickMatrix)! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 6/4/1999 10:28'! processIndexed: vb | idxArray vtxArray index vtx zValue minIndex minZ wValue | idxArray _ vb indexArray. vtxArray _ vb vertexArray. minZ _ 10.0. minIndex _ 0. 1 to: vb indexCount do:[:i| index _ idxArray at: i. index = 0 ifFalse:[ vtx _ vtxArray at: index. zValue _ vtx rasterPosZ. wValue _ vtx rasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. (minIndex = 0 or:[zValue < minZ]) ifTrue:[ minIndex _ index. minZ _ zValue]. ]. ]. ^minIndex! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:42'! processIndexedLines: vb ^self processIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:41'! processIndexedQuads: vb ^self processIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:41'! processIndexedTriangles: vb ^self processIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:43'! processLineLoop: vb ^self processNonIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:43'! processLines: vb ^self processNonIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 6/2/1999 11:54'! processNonIndexed: vb | vtxArray vtx zValue minZ minIndex wValue | vtxArray _ vb vertexArray. minZ _ 10.0. minIndex _ 0. 1 to: vb vertexCount do:[:i| vtx _ vtxArray at: i. zValue _ vtx rasterPosZ. wValue _ vtx rasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. (minIndex = 0 or:[zValue < minZ]) ifTrue:[ minIndex _ i. minZ _ zValue]. ]. ^minIndex! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:44'! processPoints: vb ^self processNonIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 4/18/1999 00:43'! processPolygon: vb ^self processNonIndexed: vb! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 6/4/1999 10:28'! processVertexBuffer: vb | minIndex minVertex minW | minIndex _ self primComputeMinIndexZ: vb primitive vtxArray: vb vertexArray vtxSize: vb vertexCount idxArray: vb indexArray idxSize: vb indexCount. minIndex == nil ifTrue:[minIndex _ super processVertexBuffer: vb]. minIndex = 0 ifTrue:[^maxVtx]. minVertex _ vb vertexArray at: minIndex. minW _ minVertex rasterPosW. minW = 0.0 ifFalse:[minVertex rasterPosZ: minVertex rasterPosZ / minW]. ^minVertex! ! !B3DPickerEngine methodsFor: 'private-rendering' stamp: 'ar 11/7/1999 18:12'! renderPrimitive "This is the main rendering loop for all operations" | visible minVertex | "Step 1: Check if the mesh is visible at all" visible _ self privateVisibleVB: vertexBuffer. visible == false ifTrue:[^nil]. "Step 2: Transform vertices, normals, texture coords of the mesh" self privateTransformVB: vertexBuffer. "Step 3: Clip the mesh if necessary" visible _ self privateClipVB: vertexBuffer. visible == false ifTrue:[^nil]. "Step 4: Collect the minimal/maximal distances for the current object." minVertex _ self processVertexBuffer: vertexBuffer. objects isEmpty ifFalse:[ objects last value rasterPosZ > minVertex rasterPosZ ifTrue:[objects last value: minVertex]. ]. ^nil! ! B3DEnginePlugin subclass: #B3DPickerPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DPickerPlugin methodsFor: 'primitives' stamp: 'ar 6/2/1999 11:59'! b3dComputeMinIndexZ "Primitive. Compute and return the index for the minimal z value of all objects in the vertex buffer." | idxSize vtxSize primType vtxArray idxArray minIndex | self export: true. self inline: false. self var: #vtxArray declareC:'float *vtxArray'. self var: #idxArray declareC:'int *idxArray'. interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. idxSize _ interpreterProxy stackIntegerValue: 0. vtxSize _ interpreterProxy stackIntegerValue: 2. primType _ interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. vtxArray _ self stackPrimitiveVertexArray: 3 ofSize: vtxSize. idxArray _ self stackPrimitiveIndexArray: 1 ofSize: idxSize validate: true forVertexSize: vtxSize. (vtxArray == nil or:[idxArray == nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. (primType < 1 or:[primType > 6]) ifTrue:[^interpreterProxy primitiveFail]. primType <= 3 ifTrue:[ minIndex _ self processNonIndexedIDX: vtxArray ofSize: vtxSize. ] ifFalse:[ minIndex _ self processIndexedIDX: vtxArray ofSize: vtxSize idxArray: idxArray idxSize: idxSize. ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 6. "nArgs+rcvr" interpreterProxy pushInteger: minIndex. ].! ! !B3DPickerPlugin methodsFor: 'primitives' stamp: 'ar 4/18/1999 02:59'! b3dComputeMinZ "Primitive. Compute and return the minimal z value of all objects in the vertex buffer." | idxSize vtxSize primType vtxArray idxArray minZ | self export: true. self inline: false. self var: #vtxArray declareC:'float *vtxArray'. self var: #idxArray declareC:'int *idxArray'. self var: #minZ declareC:'double minZ'. interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. idxSize _ interpreterProxy stackIntegerValue: 0. vtxSize _ interpreterProxy stackIntegerValue: 2. primType _ interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. vtxArray _ self stackPrimitiveVertexArray: 3 ofSize: vtxSize. idxArray _ self stackPrimitiveIndexArray: 1 ofSize: idxSize validate: true forVertexSize: vtxSize. (vtxArray == nil or:[idxArray == nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. (primType < 1 or:[primType > 6]) ifTrue:[^interpreterProxy primitiveFail]. primType <= 3 ifTrue:[ minZ _ self processNonIndexed: vtxArray ofSize: vtxSize. ] ifFalse:[ minZ _ self processIndexed: vtxArray ofSize: vtxSize idxArray: idxArray idxSize: idxSize. ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 6. "nArgs+rcvr" interpreterProxy pushFloat: minZ. ].! ! !B3DPickerPlugin methodsFor: 'processing' stamp: 'ar 4/18/1999 03:05'! processIndexed: vtxArray ofSize: vtxSize idxArray: idxArray idxSize: idxSize | vtxPtr zValue wValue minZ index | self returnTypeC:'double'. self var: #vtxArray declareC:'float *vtxArray'. self var: #vtxPtr declareC:'float *vtxPtr'. self var: #idxArray declareC:'int *idxArray'. self var: #wValue declareC:'double wValue'. self var: #zValue declareC:'double zValue'. self var: #minZ declareC:'double minZ'. minZ _ 10.0. 1 to: idxSize do:[:i| index _ idxArray at: i. index > 0 ifTrue:[ vtxPtr _ vtxArray + (index-1 * PrimVertexSize). zValue _ vtxPtr at: PrimVtxRasterPosZ. wValue _ vtxPtr at: PrimVtxRasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. zValue < minZ ifTrue:[minZ _ zValue]. ]. ]. ^minZ! ! !B3DPickerPlugin methodsFor: 'processing' stamp: 'ar 6/2/1999 12:00'! processIndexedIDX: vtxArray ofSize: vtxSize idxArray: idxArray idxSize: idxSize | vtxPtr zValue wValue minZ minIndex index | self var: #vtxArray declareC:'float *vtxArray'. self var: #vtxPtr declareC:'float *vtxPtr'. self var: #idxArray declareC:'int *idxArray'. self var: #wValue declareC:'double wValue'. self var: #zValue declareC:'double zValue'. self var: #minZ declareC:'double minZ'. minZ _ 10.0. minIndex _ 0. 1 to: idxSize do:[:i| index _ idxArray at: i. index > 0 ifTrue:[ vtxPtr _ vtxArray + (index-1 * PrimVertexSize). zValue _ vtxPtr at: PrimVtxRasterPosZ. wValue _ vtxPtr at: PrimVtxRasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. (minIndex = 0 or:[zValue < minZ]) ifTrue:[ minIndex _ i. minZ _ zValue]. ]. ]. ^minIndex! ! !B3DPickerPlugin methodsFor: 'processing' stamp: 'ar 4/18/1999 02:49'! processNonIndexed: vtxArray ofSize: vtxSize | vtxPtr zValue wValue minZ | self returnTypeC:'double'. self var: #vtxArray declareC:'float *vtxArray'. self var: #vtxPtr declareC:'float *vtxPtr'. self var: #wValue declareC:'double wValue'. self var: #zValue declareC:'double zValue'. self var: #minZ declareC:'double minZ'. minZ _ 10.0. vtxPtr _ vtxArray. 1 to: vtxSize do:[:i| zValue _ vtxPtr at: PrimVtxRasterPosZ. wValue _ vtxPtr at: PrimVtxRasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. zValue < minZ ifTrue:[minZ _ zValue]. ]. ^minZ! ! !B3DPickerPlugin methodsFor: 'processing' stamp: 'ar 6/2/1999 12:00'! processNonIndexedIDX: vtxArray ofSize: vtxSize | vtxPtr zValue wValue minZ minIndex | self var: #vtxArray declareC:'float *vtxArray'. self var: #vtxPtr declareC:'float *vtxPtr'. self var: #wValue declareC:'double wValue'. self var: #zValue declareC:'double zValue'. self var: #minZ declareC:'double minZ'. minZ _ 10.0. minIndex _ 0. vtxPtr _ vtxArray. 1 to: vtxSize do:[:i| zValue _ vtxPtr at: PrimVtxRasterPosZ. wValue _ vtxPtr at: PrimVtxRasterPosW. wValue = 0.0 ifFalse:[zValue _ zValue / wValue]. (minIndex = 0 or:[zValue < minZ]) ifTrue:[ minIndex _ i. minZ _ zValue]. ]. ^minIndex! ! Object subclass: #B3DPoolDefiner instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DPoolDefiner commentStamp: '' prior: 0! This class is used to define the pool dictionary B3DConstants.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPoolDefiner class instanceVariableNames: ''! !B3DPoolDefiner class methodsFor: 'class initialization' stamp: 'ar 2/8/1999 17:21'! initialize "B3DPoolDefiner initialize" self initPool.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/13/1999 20:30'! defineClipConstants: dict "Initialize the clipper constants" "B3DPoolDefiner initPool" self initFromSpecArray: #( (InLeftBit 16r001) (OutLeftBit 16r002) (InRightBit 16r004) (OutRightBit 16r008) (InTopBit 16r010) (OutTopBit 16r020) (InBottomBit 16r040) (OutBottomBit 16r080) (InFrontBit 16r100) (OutFrontBit 16r200) (InBackBit 16r400) (OutBackBit 16r800) (InAllMask 16r555) (OutAllMask 16rAAA) ) in: dict.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 9/9/2000 23:16'! defineMaterialAndLights: dict "Initialize constants used for materials and lights" "B3DPoolDefiner initPool" self initFromSpecArray: #( "MaterialColor stuff" (AmbientPart 0) (AmbientRed 0) (AmbientGreen 1) (AmbientBlue 2) (AmbientAlpha 3) (DiffusePart 4) (DiffuseRed 4) (DiffuseGreen 5) (DiffuseBlue 6) (DiffuseAlpha 7) (SpecularPart 8) (SpecularRed 8) (SpecularGreen 9) (SpecularBlue 10) (SpecularAlpha 11) (MaterialColorSize 12) "Size of B3DMaterialColor" "Material definition" (EmissionPart 12) (EmissionRed 12) (EmissionGreen 13) (EmissionBlue 14) (EmissionAlpha 15) (MaterialShininess 16) (MaterialSize 17) "Size of B3DMaterial" "PrimitiveLight definition" (PrimLightPosition 12) (PrimLightPositionX 12) (PrimLightPositionY 13) (PrimLightPositionZ 14) (PrimLightDirection 15) (PrimLightDirectionX 15) (PrimLightDirectionY 16) (PrimLightDirectionZ 17) (PrimLightAttenuation 18) (PrimLightAttenuationConstant 18) (PrimLightAttenuationLinear 19) (PrimLightAttenuationSquared 20) (PrimLightFlags 21) "Spot light stuff" (SpotLightMinCos 22) (SpotLightMaxCos 23) (SpotLightDeltaCos 24) (SpotLightExponent 25) (PrimLightSize 32) "Round up to power of 2" "Primitive light flags" (FlagPositional 16r0001) "Light has an associated position" (FlagDirectional 16r0002) "Light has an associated direction" (FlagAttenuated 16r0004) "Light is attenuated" (FlagHasSpot 16r0008) "Spot values are valid" (FlagAmbientPart 16r0100) "Light has ambient part" (FlagDiffusePart 16r0200) "Light has diffuse part" (FlagSpecularPart 16r0400) "Light has specular part" ) in: dict.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 20:15'! defineMatrixFlags: dict "Define the flags for analyzing vertices" "B3DPoolDefiner initPool" self initFromSpecArray: #( (FlagM44Identity 1) "Matrix is identity" (FlagM44NoPerspective 2) "Matrix has no perspective part" (FlagM44NoTranslation 4) "Matrix has no translation" ) in: dict! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/13/1999 23:41'! definePrimitiveTypes: dict "Initialize the types of Primitives" "B3DPoolDefiner initPool" self initFromSpecArray: #( (PrimTypePoints 1) (PrimTypeLines 2) (PrimTypePolygon 3) (PrimTypeIndexedLines 4) (PrimTypeIndexedTriangles 5) (PrimTypeIndexedQuads 6) (PrimTypeMax 6) "Max used primitive type" ) in: dict.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 4/4/1999 00:46'! definePrimitiveVertexIndexes: dict "Define the indexes for primitive vertices" "B3DPoolDefiner initPool" self initFromSpecArray: #( "Full vertex size is 16 to simplify index computation" (PrimVertexSize 16) "Position" (PrimVtxPosition 0) (PrimVtxPositionX 0) (PrimVtxPositionY 1) (PrimVtxPositionZ 2) "Normal" (PrimVtxNormal 3) (PrimVtxNormalX 3) (PrimVtxNormalY 4) (PrimVtxNormalZ 5) "Tex coord" (PrimVtxTexCoords 6) (PrimVtxTexCoordU 6) (PrimVtxTexCoordV 7) "RasterPos" (PrimVtxRasterPos 8) (PrimVtxRasterPosX 8) (PrimVtxRasterPosY 9) (PrimVtxRasterPosZ 10) (PrimVtxRasterPosW 11) "Color" (PrimVtxColor32 12) "Clip flags" (PrimVtxClipFlags 13) "(Integer) window position" (PrimVtxWindowPosX 14) (PrimVtxWindowPosY 15) ) in: dict! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:34'! defineVBConstants: dict "Initialize the vertex buffer constants" "B3DPoolDefiner initPool" self initFromSpecArray: #( "Vertex color tracking flags. These tracks define what part of the material in the shader is determined by the vertex color (if given)." (VBTrackAmbient 1) "ambient part" (VBTrackDiffuse 2) "diffuse part" (VBTrackSpecular 4) "specular part" (VBTrackEmission 8) "emission part -- i.e. simply add vertex color to output" (VBNoTrackMask 4294967280) "Mask out the above flags" "Vertex attribute flags. These flags determine if the primitive vertices include these attributes. Note that color is not included below - it is fully specified by the color tracking flags above." (VBVtxHasNormals 16) "per vertex normals included" (VBVtxHasTexCoords 32) "per vertex tex coords inclueded" "Shader flags stored in the vertex buffer" (VBTwoSidedLighting 64) "Do we shade front and back faces differently?!!" (VBUseLocalViewer 128) "Do we use a local viewer model for specular colors?!!" ) in: dict.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:20'! initFromSpecArray: specArray in: aDictionary specArray do:[:spec| self initPoolVariable: spec first value: spec last in: aDictionary. ]! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:23'! initPool "B3DPoolDefiner initPool" | poolName | poolName _ self poolName asSymbol. (Smalltalk includesKey: poolName) ifFalse:[ Smalltalk declare: poolName from: Undeclared. ]. (Smalltalk at: poolName) isNil ifTrue:[ (Smalltalk associationAt: poolName) value: Dictionary new. ]. self initPool: (Smalltalk at: poolName).! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/15/1999 04:14'! initPool: aDictionary "B3DPoolDefiner initPool" self defineVBConstants: aDictionary. self definePrimitiveVertexIndexes: aDictionary. self defineMatrixFlags: aDictionary. self defineClipConstants: aDictionary. self definePrimitiveTypes: aDictionary. self defineMaterialAndLights: aDictionary.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:23'! initPoolFull "B3DPoolDefiner initPoolFull" "Move old stuff to Undeclared and re-initialize the receiver" | pool | pool _ Smalltalk at: self poolName asSymbol ifAbsent:[Dictionary new]. pool associationsDo:[:assoc| Undeclared declare: assoc key from: pool. ]. self initPool. Undeclared removeUnreferencedKeys.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:20'! initPoolVariable: token value: value in: aDictionary aDictionary declare: token from: Undeclared. (aDictionary associationAt: token) value: value.! ! !B3DPoolDefiner class methodsFor: 'pool definition' stamp: 'ar 2/8/1999 17:22'! poolName ^#B3DEngineConstants! ! B3DLightSource subclass: #B3DPositionalLight instanceVariableNames: 'position attenuation ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DPositionalLight methodsFor: 'initialize' stamp: 'ar 2/7/1999 19:14'! from3DS: aDictionary "Initialize the receiver from a 3DS point light" | color | position _ aDictionary at: #position. color _ aDictionary at: #color. lightColor _ B3DMaterialColor color: color. attenuation _ B3DLightAttenuation constant: 1.0 linear: 0.0 squared: 0.0.! ! !B3DPositionalLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:05'! attenuation ^attenuation! ! !B3DPositionalLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:05'! attenuation: aLightAttenuation attenuation _ aLightAttenuation! ! !B3DPositionalLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:04'! position ^position! ! !B3DPositionalLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:04'! position: aVector position _ aVector! ! !B3DPositionalLight methodsFor: 'shading' stamp: 'ar 2/7/1999 16:54'! computeAttenuationFor: distance "Compute the attenuation for the given distance" ^attenuation computeAttenuationFor: distance! ! !B3DPositionalLight methodsFor: 'shading' stamp: 'ar 2/8/1999 02:01'! computeDirectionTo: aB3DPrimitiveVertex "Compute the lights direction to the given vertex" ^aB3DPrimitiveVertex position - position! ! !B3DPositionalLight methodsFor: 'converting' stamp: 'ar 2/15/1999 21:58'! asPrimitiveLight "Convert the receiver into a B3DPrimitiveLight" | primLight flags | primLight _ B3DPrimitiveLight new. primLight position: position. flags _ FlagPositional. self attenuation isIdentity not ifTrue:[ primLight attenuation: self attenuation. flags _ flags bitOr: FlagAttenuated]. lightColor ambientPart isZero ifFalse:[ primLight ambientPart: lightColor ambientPart. flags _ flags bitOr: FlagAmbientPart]. lightColor diffusePart isZero ifFalse:[ primLight diffusePart: lightColor diffusePart. flags _ flags bitOr: FlagDiffusePart]. lightColor specularPart isZero ifFalse:[ primLight specularPart: lightColor specularPart. flags _ flags bitOr: FlagSpecularPart]. primLight flags: flags. ^primLight! ! !B3DPositionalLight methodsFor: 'converting' stamp: 'ar 2/8/1999 01:29'! transformedBy: aTransformer ^(super transformedBy: aTransformer) position: (aTransformer transformPosition: position)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPositionalLight class instanceVariableNames: ''! !B3DPositionalLight class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 23:42'! from3DS: aDictionary ^self new from3DS: aDictionary! ! B3DVertexClipper subclass: #B3DPrimitiveClipper instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveClipper methodsFor: 'clip flags' stamp: 'ar 4/18/1999 02:05'! determineClipFlags: vtxArray count: vtxCount ^super determineClipFlags: vtxArray count: vtxCount! ! !B3DPrimitiveClipper methodsFor: 'clipping polygons' stamp: 'ar 4/18/1999 02:08'! clipPolygon: vtxArray count: vtxCount with: tempVtxArray mask: outMask ^super clipPolygon: vtxArray count: vtxCount with: tempVtxArray mask: outMask! ! !B3DPrimitiveClipper methodsFor: 'private' stamp: 'ar 4/18/1999 02:07'! primNextClippedTriangleAfter: firstIndex vertices: vtxArray count: vtxCount indexes: idxArray count: idxCount ^super primNextClippedTriangleAfter: firstIndex vertices: vtxArray count: vtxCount indexes: idxArray count: idxCount! ! Object subclass: #B3DPrimitiveEdge instanceVariableNames: 'v0 v1 leftFace rightFace flags xValue yValue zValue nLines xIncrement zIncrement ' classVariableNames: 'DepthScale FixedScale FixedToInt ' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DPrimitiveEdge methodsFor: 'initialize' stamp: 'ar 4/3/1999 04:25'! from: vtx0 to: vtx1 (vtx0 sortsBefore: vtx1) ifTrue:[v0 _ vtx0. v1 _ vtx1] ifFalse:[v1 _ vtx0. v0 _ vtx1].! ! !B3DPrimitiveEdge methodsFor: 'initialize' stamp: 'ar 4/18/1999 08:05'! initializePass1 "Assume: v0 sortsBefore: v1" xValue _ v0 windowPosX. yValue _ v0 windowPosY. zValue _ v0 rasterPosZ. xIncrement _ (v1 windowPosX - v0 windowPosX) // nLines. zIncrement _ (v1 rasterPosZ - v0 rasterPosZ) / nLines.! ! !B3DPrimitiveEdge methodsFor: 'initialize' stamp: 'ar 4/4/1999 21:34'! v0: vtx0 v1: vtx1 v0 _ vtx0. v1 _ vtx1. flags _ 0. nLines _ (vtx1 windowPosY bitShift: -12) - (vtx0 windowPosY bitShift: -12).! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/4/1999 02:41'! flags ^flags! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/4/1999 02:41'! flags: aNumber flags _ aNumber! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! leftFace ^leftFace! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! leftFace: aFace leftFace _ aFace! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 03:25'! nLines ^nLines! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 03:25'! nLines: aNumber nLines _ aNumber! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! rightFace ^rightFace! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! rightFace: aFace rightFace _ aFace.! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/4/1999 20:58'! vertex0 ^v0! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/4/1999 20:58'! vertex1 ^v1! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/6/1999 23:21'! xIncrement ^xIncrement! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:48'! xValue ^xValue! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/5/1999 22:25'! xValue: aNumber xValue _ aNumber! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:48'! yValue ^yValue! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:48'! zValue ^zValue! ! !B3DPrimitiveEdge methodsFor: 'accessing' stamp: 'ar 4/6/1999 01:23'! zValue: aNumber zValue _ aNumber! ! !B3DPrimitiveEdge methodsFor: 'processing' stamp: 'ar 4/5/1999 02:45'! stepToNextLine "Step to the next scan line" xValue _ xValue + xIncrement. yValue _ yValue + 4096. zValue _ zValue + zIncrement.! ! !B3DPrimitiveEdge methodsFor: 'printing' stamp: 'ar 4/4/1999 23:35'! printOn: aStream super printOn: aStream. aStream nextPut:$(; print: (v0 windowPos bitShiftPoint:-12); nextPutAll:' - '; print: (v1 windowPos bitShiftPoint: -12); nextPutAll:' nLines = '; print: nLines; nextPut:$).! ! Object subclass: #B3DPrimitiveEdgeList instanceVariableNames: 'tally array ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DPrimitiveEdgeList methodsFor: 'initialize' stamp: 'ar 4/4/1999 01:38'! initialize array _ Array new: 100. tally _ 0.! ! !B3DPrimitiveEdgeList methodsFor: 'initialize' stamp: 'ar 4/4/1999 21:20'! reset 1 to: tally do:[:i| array at: i put: nil]. tally _ 0.! ! !B3DPrimitiveEdgeList methodsFor: 'accessing' stamp: 'ar 4/4/1999 21:42'! at: index ^array at: index! ! !B3DPrimitiveEdgeList methodsFor: 'accessing' stamp: 'ar 4/4/1999 21:01'! first ^array at: 1! ! !B3DPrimitiveEdgeList methodsFor: 'accessing' stamp: 'ar 4/4/1999 21:42'! size ^tally! ! !B3DPrimitiveEdgeList methodsFor: 'accessing' stamp: 'ar 4/6/1999 03:58'! xValues ^(array copyFrom: 1 to: tally) collect:[:e| e xValue]! ! !B3DPrimitiveEdgeList methodsFor: 'adding' stamp: 'ar 4/4/1999 23:44'! add: edge1 and: edge2 beforeIndex: index tally+1 >= array size ifTrue:[self grow]. tally+2 to: index+2 by: -1 do:[:i|array at: i put: (array at:i-2)]. "array replaceFrom: index+2 to: tally+2 with: array startingAt: index." array at: index put: edge1. array at: index+1 put: edge2. tally _ tally + 2.! ! !B3DPrimitiveEdgeList methodsFor: 'adding' stamp: 'ar 4/4/1999 23:45'! add: edge beforeIndex: index tally = array size ifTrue:[self grow]. tally+1 to: index+1 by: -1 do:[:i|array at: i put: (array at:i-1)]. "array replaceFrom: index+1 to: tally+1 with: array startingAt: index." array at: index put: edge. tally _ tally + 1! ! !B3DPrimitiveEdgeList methodsFor: 'enumerating' stamp: 'ar 4/5/1999 02:27'! do: aBlock 1 to: tally do:[:i| aBlock value: (array at: i)].! ! !B3DPrimitiveEdgeList methodsFor: 'enumerating' stamp: 'ar 4/4/1999 01:39'! xValue: xValue from: firstIndex do: aBlock "Enumerate the entries in the insertion list starting at the given first index. Evaluate aBlock with the entries having the requested x value. Return the index after the last element touched." | edge | firstIndex to: tally do:[:i| edge _ array at: i. edge xValue = xValue ifFalse:[^i]. aBlock value: edge. ]. ^tally+1! ! !B3DPrimitiveEdgeList methodsFor: 'testing' stamp: 'ar 4/4/1999 23:09'! isEmpty ^tally = 0! ! !B3DPrimitiveEdgeList methodsFor: 'sorting' stamp: 'ar 4/5/1999 01:41'! firstIndexForInserting: xValue "Return the first possible index for inserting an object with the given xValue" | index | index _ self indexForInserting: xValue. [index > 1 and:[(array at: index-1) xValue = xValue]] whileTrue:[index _ index-1]. ^index! ! !B3DPrimitiveEdgeList methodsFor: 'sorting' stamp: 'ar 4/5/1999 01:41'! indexForInserting: xValue "Return the appropriate index for inserting the given x value" | index low high | low _ 1. high _ tally. [index _ high + low // 2. low > high] whileFalse:[ (array at: index) xValue <= xValue ifTrue: [low _ index + 1] ifFalse: [high _ index - 1]]. ^low! ! !B3DPrimitiveEdgeList methodsFor: 'private' stamp: 'ar 4/4/1999 01:38'! grow | newArray | newArray _ array species new: array size + 100. newArray replaceFrom: 1 to: array size with: array startingAt: 1. array _ newArray.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveEdgeList class instanceVariableNames: ''! !B3DPrimitiveEdgeList class methodsFor: 'instance creation' stamp: 'ar 4/4/1999 04:27'! new ^super new initialize! ! B3DRenderEngine subclass: #B3DPrimitiveEngine instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveEngine commentStamp: '' prior: 0! I am a basic render engine with some primitive level support for transformation, lighting and (once it is done) clipping.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveEngine class instanceVariableNames: ''! !B3DPrimitiveEngine class methodsFor: 'accessing' stamp: 'ar 4/16/1999 06:45'! clipper ^B3DPrimitiveClipper "^B3DVertexClipper"! ! !B3DPrimitiveEngine class methodsFor: 'accessing' stamp: 'ar 4/12/1999 03:47'! rasterizer "Return the rasterizer to use with this engine" ^B3DPrimitiveRasterizer! ! !B3DPrimitiveEngine class methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:24'! shader "Return the shader to use with this engine" ^B3DPrimitiveShader! ! !B3DPrimitiveEngine class methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:24'! transformer "Return the transformer to use with this engine" ^B3DPrimitiveTransformer! ! Object subclass: #B3DPrimitiveFace instanceVariableNames: 'v0 v1 v2 prevFace nextFace leftEdge rightEdge flags majorDx majorDy minorDx minorDy oneOverArea dzdx dzdy minZ maxZ texture attributes ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/5/1999 18:29'! initializeDepthBounds "Compute minZ/maxZ" v0 rasterPosZ <= v1 rasterPosZ ifTrue:[ v1 rasterPosZ <= v2 rasterPosZ ifTrue:[minZ _ v0 rasterPosZ. maxZ _ v2 rasterPosZ] ifFalse:[v0 rasterPosZ <= v2 rasterPosZ ifTrue:[minZ _ v0 rasterPosZ. maxZ _ v1 rasterPosZ] ifFalse:[minZ _ v2 rasterPosZ. maxZ _ v1 rasterPosZ]]. ] ifFalse:[ v2 rasterPosZ <= v1 rasterPosZ ifTrue:[minZ _ v2 rasterPosZ. maxZ _ v0 rasterPosZ] ifFalse:[v0 rasterPosZ <= v2 rasterPosZ ifTrue:[minZ _ v1 rasterPosZ. maxZ _ v2 rasterPosZ] ifFalse:[minZ _ v1 rasterPosZ. maxZ _ v0 rasterPosZ]]. ]. ! ! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/8/1999 04:32'! initializePass1 "Assume: v0 sortsBefore: v1 sortsBefore: v2" | area majorDz minorDz | self initializeDepthBounds. "Compute minZ/maxZ" "Compute the major and minor reference edges" majorDx _ v2 rasterPosX - v0 rasterPosX. majorDy _ v2 rasterPosY - v0 rasterPosY. minorDx _ v1 rasterPosX - v0 rasterPosX. minorDy _ v1 rasterPosY - v0 rasterPosY. "Compute the inverse area of the face" area _ (majorDx * minorDy) - (minorDx * majorDy). ((area > -0.001) and:[area < 0.001]) ifTrue:[oneOverArea _ 0.0] ifFalse:[oneOverArea _ 1.0 / area]. "Compute dzdx and dzdy" majorDz _ v2 rasterPosZ - v0 rasterPosZ. minorDz _ v1 rasterPosZ - v0 rasterPosZ. dzdx _ oneOverArea * ((majorDz * minorDy) - (minorDz * majorDy)). dzdy _ oneOverArea * ((majorDx * minorDz) - (majorDz * minorDx)). ! ! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/18/1999 06:35'! initializePass2 "The receiver is about to be drawn. Initialize all the attributes deferred until now." | majorDv minorDv dvdx dvdy w0 w1 w2 baseValue rAttr gAttr bAttr aAttr wAttr sAttr tAttr | "Red" majorDv _ v2 redValue - v0 redValue. minorDv _ v1 redValue - v0 redValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). attributes _ rAttr _ B3DPrimitiveFaceAttributes new. rAttr value: v0 redValue; dvdx: dvdx; dvdy: dvdy. "Green" majorDv _ v2 greenValue - v0 greenValue. minorDv _ v1 greenValue - v0 greenValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). gAttr _ B3DPrimitiveFaceAttributes new. gAttr value: v0 greenValue; dvdx: dvdx; dvdy: dvdy. rAttr nextAttr: gAttr. "Blue" majorDv _ v2 blueValue - v0 blueValue. minorDv _ v1 blueValue - v0 blueValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). bAttr _ B3DPrimitiveFaceAttributes new. bAttr value: v0 blueValue; dvdx: dvdx; dvdy: dvdy. gAttr nextAttr: bAttr. "Alpha" majorDv _ v2 alphaValue - v0 alphaValue. minorDv _ v1 alphaValue - v0 alphaValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). aAttr _ B3DPrimitiveFaceAttributes new. aAttr value: v0 alphaValue; dvdx: dvdx; dvdy: dvdy. bAttr nextAttr: aAttr. "W part" texture == nil ifFalse:[ w0 _ v0 rasterPosW. w1 _ v1 rasterPosW. w2 _ v2 rasterPosW. majorDv _ w2 - w0. minorDv _ w1 - w0. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). wAttr _ B3DPrimitiveFaceAttributes new. wAttr value: w0; dvdx: dvdx; dvdy: dvdy. aAttr nextAttr: wAttr. baseValue _ v0 texCoordS * w0. majorDv _ (v2 texCoordS * w2) - baseValue. minorDv _ (v1 texCoordS * w1) - baseValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). sAttr _ B3DPrimitiveFaceAttributes new. sAttr value: baseValue; dvdx: dvdx; dvdy: dvdy. wAttr nextAttr: sAttr. baseValue _ v0 texCoordT * w0. majorDv _ (v2 texCoordT * w2) - baseValue. minorDv _ (v1 texCoordT * w1) - baseValue. dvdx _ oneOverArea * ((majorDv * minorDy) - (minorDv * majorDy)). dvdy _ oneOverArea * ((majorDx * minorDv) - (majorDv * minorDx)). tAttr _ B3DPrimitiveFaceAttributes new. tAttr value: baseValue; dvdx: dvdx; dvdy: dvdy. sAttr nextAttr: tAttr. ].! ! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/4/1999 21:54'! v0: vtx0 v1: vtx1 v2: vtx2 v0 _ vtx0. v1 _ vtx1. v2 _ vtx2. flags _ 0.! ! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/7/1999 01:01'! validateDepthSetup oneOverArea = 0.0 ifTrue:[^self]. (v0 rasterPosZ - (self zValueAtX: v0 rasterPosX y: v0 rasterPosY)) abs >= 1.0e-10 ifTrue:[self error:'Depth problem']. (v1 rasterPosZ - (self zValueAtX: v1 rasterPosX y: v1 rasterPosY)) abs >= 1.0e-10 ifTrue:[self error:'Depth problem']. (v2 rasterPosZ - (self zValueAtX: v2 rasterPosX y: v2 rasterPosY)) abs >= 1.0e-10 ifTrue:[self error:'Depth problem'].! ! !B3DPrimitiveFace methodsFor: 'initialize' stamp: 'ar 4/3/1999 21:24'! validateVertexOrder (v0 sortsBefore: v1) ifFalse:[self error:'Vertex order problem']. (v0 sortsBefore: v2) ifFalse:[self error:'Vertex order problem']. (v1 sortsBefore: v2) ifFalse:[self error:'Vertex order problem'].! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/6/1999 22:40'! attributes ^attributes! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 04:13'! dzdx ^dzdx! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 04:13'! dzdy ^dzdy! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:34'! flags ^flags! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:34'! flags: anInteger flags _ anInteger! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 19:05'! leftEdge ^leftEdge! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 19:06'! leftEdge: anEdge leftEdge _ anEdge! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 18:22'! maxZ ^maxZ! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 18:21'! minZ ^minZ! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! nextFace ^nextFace! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:34'! nextFace: aFace nextFace _ aFace! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 19:03'! oneOverArea ^oneOverArea! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:33'! prevFace ^prevFace! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 00:34'! prevFace: aFace prevFace _ aFace! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 19:05'! rightEdge ^rightEdge! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/5/1999 19:06'! rightEdge: anEdge rightEdge _ anEdge! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:20'! texture ^texture! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:20'! texture: aTexture texture _ aTexture! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 21:32'! vertex0 ^v0! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 21:32'! vertex1 ^v1! ! !B3DPrimitiveFace methodsFor: 'accessing' stamp: 'ar 4/3/1999 21:32'! vertex2 ^v2! ! !B3DPrimitiveFace methodsFor: 'processing' stamp: 'ar 4/18/1999 06:34'! attrValue: attr atX: xValue y: yValue "Return the value of the attribute at position xValue@yValue" ^attr valueAtX: (xValue - v0 rasterPosX) y: (yValue - v0 rasterPosY).! ! !B3DPrimitiveFace methodsFor: 'processing' stamp: 'ar 4/8/1999 04:31'! zValueAtX: xValue y: yValue "Return the z value of the receiver at position xValue@yValue" ^v0 rasterPosZ + (yValue - v0 rasterPosY * dzdy) + (xValue - v0 rasterPosX * dzdx)! ! !B3DPrimitiveFace methodsFor: 'printing' stamp: 'ar 4/5/1999 01:22'! printOn: aStream super printOn: aStream. aStream nextPut:$(; print: (v0 windowPos bitShiftPoint:-12); nextPutAll:' - '; print: (v1 windowPos bitShiftPoint: -12); nextPutAll:' - '; print: (v2 windowPos bitShiftPoint: -12); nextPut:$).! ! Object subclass: #B3DPrimitiveFaceAttributes instanceVariableNames: 'nextAttr value dvdx dvdy ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:33'! dvdx ^dvdx! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:33'! dvdx: aNumber dvdx _ aNumber! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:33'! dvdy ^dvdy! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:33'! dvdy: aNumber dvdy _ aNumber! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:32'! nextAttr ^nextAttr! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:32'! nextAttr: attr nextAttr _ attr.! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:32'! value ^value! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:32'! value: aNumber value _ aNumber! ! !B3DPrimitiveFaceAttributes methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:34'! valueAtX: xValue y: yValue "Return the value of the attribute at position xValue@yValue" ^value + (yValue * dvdy) + (xValue * dvdx)! ! B3DMaterialColor variableWordSubclass: #B3DPrimitiveLight instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:51'! attenuation "Return the light attenuation. This member is only valid if the light is attenuated." ^B3DLightAttenuation constant: self constantAttenuation linear: self linearAttenuation squared: self squaredAttenuation! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:52'! attenuation: aLightAttenuation "Set the light attenuation. This member is only valid if the light is attenuated." self constantAttenuation: aLightAttenuation constantPart. self linearAttenuation: aLightAttenuation linearPart. self squaredAttenuation: aLightAttenuation squaredPart.! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:47'! direction "Return the direction of the light. This member is valid only if the light is not positional (e.g., the direction must be computed for every vertex)" ^B3DVector3 x: self directionX y: self directionY z: self directionZ! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:49'! direction: aB3DVector "Set the direction of the light. This member is valid only if the light is positional (e.g., the direction must be computed for every vertex)" self directionX: aB3DVector x. self directionY: aB3DVector y. self directionZ: aB3DVector z.! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! flags ^self wordAt: PrimLightFlags+1! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! flags: aValue ^self wordAt: PrimLightFlags+1 put: aValue! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:49'! position "Return the position of the light. This member is valid only if the light is not positional (e.g., the direction must be computed for every vertex)" ^B3DVector3 x: self positionX y: self positionY z: self positionZ! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 03:48'! position: aB3DVector "Set the position of the light. This member is valid only if the light is positional (e.g., the direction must be computed for every vertex)" self positionX: aB3DVector x. self positionY: aB3DVector y. self positionZ: aB3DVector z.! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! spotDeltaCos ^self floatAt: SpotLightDeltaCos+1! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! spotDeltaCos: aFloat ^self floatAt: SpotLightDeltaCos+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! spotExponent ^self floatAt: SpotLightExponent+1! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:17'! spotExponent: aFloat ^self floatAt: SpotLightExponent+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:18'! spotMaxCos ^self floatAt: SpotLightMaxCos+1! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:18'! spotMaxCos: aFloat ^self floatAt: SpotLightMaxCos+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:18'! spotMinCos ^self floatAt: SpotLightMinCos+1! ! !B3DPrimitiveLight methodsFor: 'accessing' stamp: 'ar 2/15/1999 22:18'! spotMinCos: aFloat ^self floatAt: SpotLightMinCos+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:14'! constantAttenuation ^self floatAt: PrimLightAttenuationConstant+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:14'! constantAttenuation: aFloat ^self floatAt: PrimLightAttenuationConstant+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:14'! directionX ^self floatAt: PrimLightDirectionX+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:14'! directionX: aFloat ^self floatAt: PrimLightDirectionX+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:14'! directionY ^self floatAt: PrimLightDirectionY+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! directionY: aFloat ^self floatAt: PrimLightDirectionY+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! directionZ ^self floatAt: PrimLightDirectionZ+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! directionZ: aFloat ^self floatAt: PrimLightDirectionZ+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! linearAttenuation ^self floatAt: PrimLightAttenuationLinear+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! linearAttenuation: aFloat ^self floatAt: PrimLightAttenuationLinear+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionX ^self floatAt: PrimLightPositionX+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionX: aFloat ^self floatAt: PrimLightPositionX+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionY ^self floatAt: PrimLightPositionY+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionY: aFloat ^self floatAt: PrimLightPositionY+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionZ ^self floatAt: PrimLightPositionZ+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! positionZ: aFloat ^self floatAt: PrimLightPositionZ+1 put: aFloat! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! squaredAttenuation ^self floatAt: PrimLightAttenuationSquared+1! ! !B3DPrimitiveLight methodsFor: 'element access' stamp: 'ar 2/15/1999 22:15'! squaredAttenuation: aFloat ^self floatAt: PrimLightAttenuationSquared+1 put: aFloat! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveLight class instanceVariableNames: ''! !B3DPrimitiveLight class methodsFor: 'instance creation' stamp: 'ar 2/15/1999 22:11'! numElements ^PrimLightSize! ! Object subclass: #B3DPrimitiveObject instanceVariableNames: 'next prev texture bounds minZ maxZ start faces vertices ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:43'! bounds ^bounds! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 04:49'! faces ^faces! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 04:49'! faces: anArray faces _ anArray! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:44'! nextObj ^next! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:44'! nextObj: obj next _ obj! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:44'! prevObj ^prev! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:45'! prevObj: obj prev _ obj! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:56'! texture ^texture! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 06:56'! texture: aTexture texture _ aTexture! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 04:49'! vertices ^vertices! ! !B3DPrimitiveObject methodsFor: 'accessing' stamp: 'ar 4/18/1999 04:49'! vertices: anArray vertices _ anArray! ! !B3DPrimitiveObject methodsFor: 'processing' stamp: 'ar 4/18/1999 05:44'! mapVertices: viewport "Map all the vertices in the receiver" | xOfs yOfs xScale yScale w x y z scaledX scaledY first | xOfs _ (viewport origin x + viewport corner x) * 0.5 - 0.5. yOfs _ (viewport origin y + viewport corner y) * 0.5 - 0.5. xScale _ (viewport corner x - viewport origin x) * 0.5. yScale _ (viewport corner y - viewport origin y) * -0.5. bounds _ 16r3FFFFFFF asPoint extent: 0@0. minZ _ maxZ _ 0.0. first _ true. vertices do:[:vtx| w _ vtx rasterPosW. w = 0.0 ifFalse:[w _ 1.0 / w]. x _ vtx rasterPosX * w * xScale + xOfs. y _ vtx rasterPosY * w * yScale + yOfs. z _ vtx rasterPosZ * w. vtx rasterPosW: w. vtx rasterPosZ: z. scaledX _ (x * 4096.0) asInteger. scaledY _ (y * 4096.0) asInteger. vtx windowPosX: scaledX. vtx windowPosY: scaledY. true ifTrue:[ vtx rasterPosX: scaledX / 4096.0. vtx rasterPosY: scaledY / 4096.0. ] ifFalse:[ vtx rasterPosX: x. vtx rasterPosY: y. ]. first ifTrue:[ bounds _ scaledX@scaledY extent: 0@0. minZ _ maxZ _ z. first _ false. ] ifFalse:[ bounds _ bounds encompass: scaledX@scaledY. minZ _ minZ min: z. maxZ _ maxZ max: z. ]. ]. bounds _ (bounds origin bitShiftPoint: -12) corner: (bounds corner bitShiftPoint: -12).! ! !B3DPrimitiveObject methodsFor: 'processing' stamp: 'ar 4/18/1999 05:12'! setupVertexOrder faces do:[:face| self setupVertexOrder: face].! ! !B3DPrimitiveObject methodsFor: 'processing' stamp: 'ar 4/18/1999 05:10'! setupVertexOrder: face | p1 p2 i1 i2 i3 p3 | i1 _ face p1Index. i2 _ face p2Index. i3 _ face p3Index. p1 _ vertices at: i1. p2 _ vertices at: i2. p3 _ vertices at: i3. (p1 sortsBefore: p2) ifTrue:[ (p2 sortsBefore: p3) ifTrue:[ face p1Index: i1; p2Index: i2; p3Index: i3. ] ifFalse:[ (p1 sortsBefore: p3) ifTrue:[face p1Index: i1; p2Index: i3; p3Index: i2] ifFalse:[face p1Index: i3; p2Index: i1; p3Index: i2] ]. ] ifFalse:[ (p1 sortsBefore: p3) ifTrue:[ face p1Index: i2; p2Index: i1; p3Index: i3. ] ifFalse:[ (p2 sortsBefore: p3) ifTrue:[face p1Index: i2; p2Index: i3; p3Index: i1] ifFalse:[face p1Index: i3; p2Index: i2; p3Index: i1] ] ]. B3DScanner doDebug ifTrue:[ p1 _ vertices at: face p1Index. p2 _ vertices at: face p2Index. p3 _ vertices at: face p3Index. ((p1 sortsBefore: p2) and:[(p2 sortsBefore: p3) and:[p1 sortsBefore: p3]]) ifFalse:[self error:'Vertex order problem']. ]. ! ! !B3DPrimitiveObject methodsFor: 'processing' stamp: 'ar 4/18/1999 05:13'! sortInitialFaces faces _ faces sortBy:[:face1 :face2| (vertices at: face1 p1Index) sortsBefore: (vertices at: face2 p1Index)].! ! !B3DPrimitiveObject methodsFor: 'initialize' stamp: 'ar 4/18/1999 05:22'! reset start _ 0.! ! !B3DPrimitiveObject methodsFor: 'streaming' stamp: 'ar 4/18/1999 05:26'! atEnd ^start >= faces size! ! !B3DPrimitiveObject methodsFor: 'streaming' stamp: 'ar 4/18/1999 06:55'! next | iFace face | iFace _ faces at: (start _ start + 1). face _ B3DPrimitiveFace new. face v0: (vertices at: iFace p1Index) v1: (vertices at: iFace p2Index) v2: (vertices at: iFace p3Index). face texture: texture. face initializePass1. B3DScanner doDebug ifTrue:[ face validateVertexOrder. face validateDepthSetup]. ^face! ! !B3DPrimitiveObject methodsFor: 'streaming' stamp: 'ar 4/18/1999 05:25'! peekY ^(vertices at: (faces at: start+1) p1Index) windowPosY! ! B3DVertexRasterizer subclass: #B3DPrimitiveRasterizer instanceVariableNames: 'state primObjects textures ' classVariableNames: 'B3DNoMoreAET B3DNoMoreAdded B3DNoMoreAttrs B3DNoMoreEdges B3DNoMoreFaces ' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:41'! clipRect: aRectangle super clipRect: aRectangle. state bitBlt clipRect: aRectangle.! ! !B3DPrimitiveRasterizer methodsFor: 'initialize' stamp: 'ar 4/17/1999 21:10'! flush self mainLoop.! ! !B3DPrimitiveRasterizer methodsFor: 'initialize' stamp: 'ar 4/16/1999 07:54'! initialize super initialize. primObjects _ WriteStream on: (Array new: 100). state _ B3DPrimitiveRasterizerState new. state initialize. textures _ IdentityDictionary new: 33.! ! !B3DPrimitiveRasterizer methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:53'! reset super reset. state reset.! ! !B3DPrimitiveRasterizer methodsFor: 'initialize' stamp: 'ar 5/28/2000 12:16'! target: aForm | bb span sourceForm | super target: aForm. target ifNil:[^self]. "Note: span must be Bitmap since software rasterizer expects canonical RGBA for now" span _ Bitmap new: 2048. sourceForm _ Form extent: span size@1 depth: 32 bits: span. bb _ BitBlt current toForm: target. self class primitiveSetBitBltPlugin: bb getPluginName. bb sourceForm: sourceForm. bb isFXBlt ifTrue:[ "Specific setup for FXBlt is necessary" bb colorMap: (sourceForm colormapIfNeededFor: target). bb combinationRule: (target depth >= 8 ifTrue:[34] ifFalse:[Form paint]). ] ifFalse:[ bb colorMap: (sourceForm colormapIfNeededForDepth: target depth). bb combinationRule: (target depth >= 8 ifTrue:[34] ifFalse:[Form paint]). ]. bb destX: 0; destY: 0; sourceX: 0; sourceY: 0; width: 1; height: 1. state spanBuffer: span. state bitBlt: bb.! ! !B3DPrimitiveRasterizer methodsFor: 'testing' stamp: 'ar 4/14/1999 02:08'! needsClip ^true! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 11/7/1999 18:47'! addPrimitiveObject: vb ofSize: objSize | obj textureIndex | texture == nil ifTrue:[textureIndex _ 0] ifFalse:[textureIndex _ textures at: texture ifAbsentPut:[textures size+1]]. obj _ B3DPrimitiveRasterizerData new: objSize. self primAddObject: obj primitive: vb primitive vertexArray: vb vertexArray size: vb vertexCount indexArray: vb indexArray size: vb indexCount viewport: viewport textureIndex: textureIndex. primObjects nextPut: obj. "AAARRRRGGGGGHHHH - we should do this differently!!!!!!!!" vbBounds _ (obj integerAt: 9) @ (obj integerAt: 11) corner: (obj integerAt: 10) @ (obj integerAt: 12).! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 5/26/2000 15:41'! debugDrawVB: vb | vtx idx1 idx2 idx3 v1 v2 v3 vp myCanvas | myCanvas _ target getCanvas. vp _ viewport clone. vtx _ Array new: vb vertexCount. vb vertexArray upTo: vb vertexCount doWithIndex:[:v :i| vtx at: i put: (vp mapVertex4: v rasterPos). ]. 1 to: vb indexCount-1 by: 3 do:[:i| idx1 _ vb indexArray at: i. idx2 _ vb indexArray at: i+1. idx3 _ vb indexArray at: i+2. idx1 = 0 ifFalse:[ v1 _ vtx at: idx1. v2 _ vtx at: idx2. v3 _ vtx at: idx3. myCanvas line: v1 to: v2 width: 1 color: Color black. myCanvas line: v2 to: v3 width: 1 color: Color black. myCanvas line: v3 to: v1 width: 1 color: Color black. ]. ].! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 11/7/1999 22:25'! mainLoop "Do the actual rasterization" | errCode objects textureArray | objects _ primObjects contents. objects size = 0 ifTrue:[^self]. "Nothing to do" textureArray _ Array new: textures size. textures associationsDo:[:assoc| textureArray at: assoc value put: assoc key]. state initObjects: objects size. state initTextures: textureArray size. textureArray do:[:tex| tex unhibernate]. [errCode _ self primStartRasterizer: state objects: objects textures: textureArray. errCode = 0] whileFalse:[ "Not yet finished" self processErrorCode: (errCode bitAnd: 255). state reset]. primObjects reset. textures _ IdentityDictionary new: textures capacity. false ifTrue:[self printSpaceUsage: objects]. ! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/12/1999 02:32'! processErrorCode: errCode errCode = 0 ifTrue:[^true]. "This is allowed!!" (errCode = B3DNoMoreEdges) ifTrue:[^state growEdges]. (errCode = B3DNoMoreFaces) ifTrue:[^state growFaces]. (errCode = B3DNoMoreAttrs) ifTrue:[^state growAttrs]. (errCode = B3DNoMoreAET) ifTrue:[^state growAET]. (errCode = B3DNoMoreAdded) ifTrue:[^state growAdded]. self error:'Unknown rasterizer error code ', errCode printString.! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/17/1999 20:29'! processIndexedLines: vb "Process an indexed line set" self error:'Indexed lines are not yet implemented'! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 10/30/2000 20:38'! processIndexedQuads: vb "Process an indexed quad set" | objSize | self flag: #workAround. "There's a bug in the primitive code (now fixed) overwriting more than the expected size of the buffer. But older VMs are likely to have it so here's what we do..." objSize _ self primObjectSize + (vb vertexCount + 1 * PrimVertexSize) + ( "Workaround for bug in the primitive" vb indexCount // 4 * 6 "<- this is what we really need (nQuads * 2 * 3 words per tri)" * 2 "BUG BUG BUG"). self addPrimitiveObject: vb ofSize: objSize.! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 9/10/1999 14:59'! processIndexedTriangles: vb | objSize | objSize _ self primObjectSize + (vb vertexCount + 1 * PrimVertexSize) + (vb indexCount). self addPrimitiveObject: vb ofSize: objSize.! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/17/1999 20:29'! processLineLoop: vb "Process a closed line defined by the vertex buffer" self error:'Lines are not yet implemented'! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/17/1999 20:29'! processLines: vb "Process a series of lines defined by each two points the vertex buffer" self error:'Lines are not yet implemented'! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/17/1999 20:30'! processPoints: vertexBuffer "Process a series of points defined by the vertex buffer" self error:'Points are not yet implemented'! ! !B3DPrimitiveRasterizer methodsFor: 'processing' stamp: 'ar 4/17/1999 21:02'! processPolygon: vb "Process a polygon defined by the vertex buffer" | objSize | objSize _ self primObjectSize + (vb vertexCount * PrimVertexSize) + (vb vertexCount - 2 * 3). self addPrimitiveObject: vb ofSize: objSize.! ! !B3DPrimitiveRasterizer methodsFor: 'primitives' stamp: 'ar 4/14/1999 05:12'! primAddObject: obj primitive: primitive vertexArray: vertexArray size: vertexCount indexArray: indexArray size: indexCount viewport: vp textureIndex: txIndex ^self primitiveFailed! ! !B3DPrimitiveRasterizer methodsFor: 'primitives' stamp: 'ar 4/12/1999 02:17'! primObjectSize ^self primitiveFailed! ! !B3DPrimitiveRasterizer methodsFor: 'primitives' stamp: 'ar 4/14/1999 05:18'! primStartRasterizer: primState objects: primitiveObjects textures: textureArray "Primitive. Start the rasterizer. Return an error code." ^self primitiveFailed! ! !B3DPrimitiveRasterizer methodsFor: 'private' stamp: 'ar 4/13/1999 02:13'! printSpaceUsage: objects "Print out the maximum space used for processing the given objects" | spaceUsed | spaceUsed _ state spaceUsed. objects do:[:obj| spaceUsed _ spaceUsed + obj basicSize]. spaceUsed _ spaceUsed * 4. Transcript cr; nextPutAll: spaceUsed asStringWithCommas; nextPutAll:' bytes max working set'; endEntry.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveRasterizer class instanceVariableNames: ''! !B3DPrimitiveRasterizer class methodsFor: 'class initialization' stamp: 'ar 4/13/1999 01:52'! initialize "B3DPrimitiveRasterizer initialize" B3DNoMoreEdges _ 1. B3DNoMoreFaces _ 2. B3DNoMoreAttrs _ 3. B3DNoMoreAET _ 4. B3DNoMoreAdded _ 5.! ! !B3DPrimitiveRasterizer class methodsFor: 'accessing' stamp: 'ar 4/12/1999 03:46'! version "B3DPrimitiveRasterizer version" ^0! ! !B3DPrimitiveRasterizer class methodsFor: 'testing' stamp: 'ar 4/12/1999 03:48'! isAvailable ^self version > 0! ! !B3DPrimitiveRasterizer class methodsFor: 'private' stamp: 'ar 5/28/2000 22:17'! primitiveSetBitBltPlugin: pluginName ^nil! ! Object variableWordSubclass: #B3DPrimitiveRasterizerData instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveRasterizerData commentStamp: '' prior: 0! Instances of this class represent data on the primitive level. The major reason for the existance of this class is that all memory needed by the rasterizer is allocated from Smalltalk code[*]. Instances of this class should not be modified from Smalltalk code - they may contain pointers to other memory locations and thus modification of these instances could easily break the system. [*] This is for two reasons: * Some systems (e.g., Mac) don't have the necessary allocation facilities from the primitive level (This REALLY sucks. We have 1999 and MacOS 8.5.1 still has static memory allocation!!) * Allocation from Smalltalk allows us to share memory between Smalltalk and C code, take advantage of GCs if the physically available space is small (e.g., on PDAs) as well as gracefully failing if there is no memory left (e.g., by signalling the low space condition). ! !B3DPrimitiveRasterizerData methodsFor: 'accessing' stamp: 'ar 4/10/1999 05:36'! at: index put: value "See the class comment" ^self error:'You must not modify primitive level data'! ! !B3DPrimitiveRasterizerData methodsFor: 'accessing' stamp: 'ar 11/7/1999 18:09'! integerAt: index "Return the integer at the given index" | word | word _ self basicAt: index. word < 16r3FFFFFFF ifTrue:[^word]. "Avoid LargeInteger computations" ^word >= 16r80000000 "Negative?!!" ifTrue:["word - 16r100000000" (word bitInvert32 + 1) negated] ifFalse:[word]! ! !B3DPrimitiveRasterizerData methodsFor: 'private' stamp: 'ar 4/12/1999 02:36'! replaceFrom: start to: stop with: replacement startingAt: repStart "Private. Used for growing rasterizer data only." ^self primitiveFailed! ! Object subclass: #B3DPrimitiveRasterizerState instanceVariableNames: 'faceAlloc edgeAlloc attrAlloc aet addedEdges fillList objects textures spanBuffer bitBlt ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveRasterizerState commentStamp: '' prior: 0! This class represents a set of objects that are known to the primitive level rasterizer. It should not be modified unless you know *exactly* what you're doing. The instance variables could actually be indexed but I decided to give them names for readability. Instance variables: faceAlloc - Source for primitive level face allocation. edgeAlloc - Source for primitive level edge allocation. attrAlloc - Source for primitive level attribute allocation. aet - Primitive level active edge table. addedEdges - Primitive level temporary edge storage. fillList - Primitive level fill list. objects - Primitive level list of objects. textures - Primitive level lists of textures. spanBuffer - 32bit bitmap to render into bitBlt - Final output device ! !B3DPrimitiveRasterizerState methodsFor: 'initialize' stamp: 'ar 4/14/1999 05:14'! initObjects: nObjects objects _ B3DPrimitiveRasterizerData new: nObjects! ! !B3DPrimitiveRasterizerState methodsFor: 'initialize' stamp: 'ar 4/14/1999 05:13'! initTextures: nTextures textures _ B3DPrimitiveRasterizerData new: (self primTextureSize * nTextures).! ! !B3DPrimitiveRasterizerState methodsFor: 'initialize' stamp: 'ar 4/13/1999 06:29'! initialize faceAlloc ifNil:[faceAlloc _ B3DPrimitiveRasterizerData new: 32768]. edgeAlloc ifNil:[edgeAlloc _ B3DPrimitiveRasterizerData new: 16384]. attrAlloc ifNil:[attrAlloc _ B3DPrimitiveRasterizerData new: 4096]. aet ifNil:[aet _ B3DPrimitiveRasterizerData new: 4096]. addedEdges ifNil:[addedEdges _ B3DPrimitiveRasterizerData new: 4096]. fillList ifNil:[fillList _ B3DPrimitiveRasterizerData new: 32]. self primInitializeBuffers.! ! !B3DPrimitiveRasterizerState methodsFor: 'initialize' stamp: 'ar 4/11/1999 23:47'! reset self primInitializeBuffers.! ! !B3DPrimitiveRasterizerState methodsFor: 'accessing' stamp: 'ar 4/13/1999 00:10'! bitBlt ^bitBlt! ! !B3DPrimitiveRasterizerState methodsFor: 'accessing' stamp: 'ar 4/13/1999 00:10'! bitBlt: aBitBlt bitBlt _ aBitBlt.! ! !B3DPrimitiveRasterizerState methodsFor: 'accessing' stamp: 'ar 4/13/1999 02:09'! spaceUsed ^faceAlloc basicSize + edgeAlloc basicSize + attrAlloc basicSize + aet basicSize + addedEdges basicSize + fillList basicSize + objects basicSize + spanBuffer basicSize! ! !B3DPrimitiveRasterizerState methodsFor: 'accessing' stamp: 'ar 4/13/1999 00:10'! spanBuffer ^spanBuffer! ! !B3DPrimitiveRasterizerState methodsFor: 'accessing' stamp: 'ar 4/13/1999 00:10'! spanBuffer: aBitmap spanBuffer _ aBitmap.! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/14/1999 01:45'! grow: anArray | newArray | newArray _ anArray species new: anArray size + (anArray size // 4 max: 100). newArray replaceFrom: 1 to: anArray size with: anArray startingAt: 1. ^newArray! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/13/1999 06:30'! growAET "Transcript cr; show:'Growing AET'." aet _ self grow: aet.! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/13/1999 06:30'! growAdded "Transcript cr; show:'Growing addedEdges'." aet _ self grow: addedEdges.! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/13/1999 06:30'! growAttrs "Transcript cr; show:'Growing attrAlloc'." attrAlloc _ self grow: attrAlloc.! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/13/1999 06:30'! growEdges "Transcript cr; show:'Growing edgeAlloc'." edgeAlloc _ self grow: edgeAlloc.! ! !B3DPrimitiveRasterizerState methodsFor: 'growing' stamp: 'ar 4/13/1999 06:30'! growFaces "Transcript cr; show:'Growing faceAlloc'." faceAlloc _ self grow: faceAlloc.! ! !B3DPrimitiveRasterizerState methodsFor: 'private' stamp: 'ar 4/10/1999 21:29'! primInitializeBuffers ^self primitiveFailed! ! !B3DPrimitiveRasterizerState methodsFor: 'private' stamp: 'ar 4/14/1999 05:13'! primTextureSize ^self primitiveFailed! ! B3DVertexShader subclass: #B3DPrimitiveShader instanceVariableNames: 'primitiveLights ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveShader commentStamp: '' prior: 0! I am a shader that uses primitive level support. NOTE: Currently, primitive and non-primitive lights cannot be mixed.! !B3DPrimitiveShader methodsFor: 'initialize' stamp: 'ar 2/17/1999 04:17'! initialize super initialize. primitiveLights _ #().! ! !B3DPrimitiveShader methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:56'! reset super reset. primitiveLights _ #().! ! !B3DPrimitiveShader methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:14'! addLight: aLightSource "NOTE: This does not work if primitive/non-primitive lights are mixed!!" | primLight | self flag: #b3dBug. "See above" primLight _ aLightSource asPrimitiveLight. primLight ifNotNil:[primitiveLights _ primitiveLights copyWith: primLight]. ^super addLight: aLightSource! ! !B3DPrimitiveShader methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:17'! removeLight: lightIndex | pLight | super removeLight: lightIndex. self flag: #b3dBug. "There should be a better way then doing this." primitiveLights _ #(). lights do:[:light| light ifNotNil:[pLight _ light asPrimitiveLight]. pLight ifNotNil:[primitiveLights _ primitiveLights copyWith: pLight]].! ! !B3DPrimitiveShader methodsFor: 'shading' stamp: 'ar 2/17/1999 04:10'! primShadeVB: vertexArray count: vtxCount lights: lightArray material: aMaterial vbFlags: vbFlags "Primitive. Shade all the vertices in the vertex buffer using the given array of primitive light sources. Return true on success, false otherwise." self flag: #b3dDebug. self primitiveFailed. ^false! ! !B3DPrimitiveShader methodsFor: 'shading' stamp: 'ar 2/17/1999 04:11'! processVertexBuffer: vb "Do the primitive operation" (self primShadeVB: vb vertexArray count: vb vertexCount lights: primitiveLights material: material vbFlags: vb flags) ifTrue:[^self]. "Run simulation instead" super processVertexBuffer: vb.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveShader class instanceVariableNames: ''! !B3DPrimitiveShader class methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:09'! version "Return the version of this shader" ^0! ! !B3DPrimitiveShader class methodsFor: 'testing' stamp: 'ar 2/17/1999 04:08'! isAvailable ^self version > 0! ! B3DVertexTransformer subclass: #B3DPrimitiveTransformer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-PrimitiveEngine'! !B3DPrimitiveTransformer commentStamp: '' prior: 0! I am a vertex transformer that uses some primitive level support.! !B3DPrimitiveTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:19'! privateTransformMatrix: m1 with: m2 into: m3 "Use the primitive operation" ^super privateTransformMatrix: m1 with: m2 into: m3! ! !B3DPrimitiveTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:20'! privateTransformPrimitiveNormal: primitiveVertex byMatrix: aMatrix rescale: scaleNeeded "Use the primitive operation" ^super privateTransformPrimitiveNormal: primitiveVertex byMatrix: aMatrix rescale: scaleNeeded! ! !B3DPrimitiveTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:20'! privateTransformPrimitiveVertex: primitiveVertex byModelView: aMatrix "Use the primitive operation" ^super privateTransformPrimitiveVertex: primitiveVertex byModelView: aMatrix! ! !B3DPrimitiveTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:21'! privateTransformPrimitiveVertex: primitiveVertex byProjection: aMatrix "Use the primitive operation" ^super privateTransformPrimitiveVertex: primitiveVertex byProjection: aMatrix! ! !B3DPrimitiveTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:21'! privateTransformVB: vertexArray count: vertexCount modelViewMatrix: modelViewMatrix projectionMatrix: projectionMatrix flags: flags "Use the primitive operation" ^super privateTransformVB: vertexArray count: vertexCount modelViewMatrix: modelViewMatrix projectionMatrix: projectionMatrix flags: flags! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveTransformer class instanceVariableNames: ''! !B3DPrimitiveTransformer class methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:29'! version "Return the version of this transformer" ^0! ! !B3DPrimitiveTransformer class methodsFor: 'testing' stamp: 'ar 2/17/1999 04:22'! isAvailable ^self version > 0! ! Object variableWordSubclass: #B3DPrimitiveVertex instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Engine'! !B3DPrimitiveVertex commentStamp: '' prior: 0! I represent all per vertex information used in Balloon 3D primitive operations. I store either 32bit floats or integers depending on what is requested. C representation: typedef struct B3DPrimitiveVertex { float position[3]; float normal[3]; float texCoord[2]; float rasterPos[4]; int pixelValue32; int clipFlags; int windowPos[2]; } B3DPrimitiveVertex;! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/5/1999 19:27'! b3dColor ^self color asB3DColor! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/5/1999 19:28'! b3dColor: aB3DColor4 self color: aB3DColor4 asColor! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/4/1999 23:53'! clipFlags ^self wordAt: 14! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/4/1999 23:53'! clipFlags: aNumber self wordAt: 14 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'accessing'! color ^self pixelValue32 asColorOfDepth: 32! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/4/1999 20:21'! color: aColor self pixelValue32: (aColor asColor pixelWordForDepth: 32)! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index ^Float fromIEEE32Bit: (self basicAt: index)! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index put: value value isFloat ifTrue:[self basicAt: index put: value asIEEE32BitWord] ifFalse:[self at: index put: value asFloat]. ^value! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 4/4/1999 00:29'! integerAt: index | word | word _ self basicAt: index. word < 16r3FFFFFFF ifTrue:[^word]. "Avoid LargeInteger computations" ^word >= 16r80000000 "Negative?!!" ifTrue:["word - 16r100000000" (word bitInvert32 + 1) negated] ifFalse:[word]! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 4/4/1999 00:29'! integerAt: index put: anInteger | word | anInteger < 0 ifTrue:["word _ 16r100000000 + anInteger" word _ (anInteger + 1) negated bitInvert32] ifFalse:[word _ anInteger]. self basicAt: index put: word. ^anInteger! ! !B3DPrimitiveVertex methodsFor: 'accessing'! normal ^B3DVector3 x: (self floatAt: 4) y: (self floatAt: 5) z: (self floatAt: 6)! ! !B3DPrimitiveVertex methodsFor: 'accessing'! normal: aVector self floatAt: 4 put: aVector x. self floatAt: 5 put: aVector y. self floatAt: 6 put: aVector z. ! ! !B3DPrimitiveVertex methodsFor: 'accessing'! pixelValue32 ^self wordAt: 13! ! !B3DPrimitiveVertex methodsFor: 'accessing'! pixelValue32: aNumber self wordAt: 13 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'accessing'! position ^B3DVector3 x: (self floatAt: 1) y: (self floatAt: 2) z: (self floatAt: 3)! ! !B3DPrimitiveVertex methodsFor: 'accessing'! position: aVector self floatAt: 1 put: aVector x. self floatAt: 2 put: aVector y. self floatAt: 3 put: aVector z. ! ! !B3DPrimitiveVertex methodsFor: 'accessing'! rasterPos ^B3DVector4 x: (self floatAt: 9) y: (self floatAt: 10) z: (self floatAt: 11) w: (self floatAt: 12)! ! !B3DPrimitiveVertex methodsFor: 'accessing'! rasterPos: aVector self floatAt: 9 put: aVector x. self floatAt: 10 put: aVector y. self floatAt: 11 put: aVector z. self floatAt: 12 put: aVector w.! ! !B3DPrimitiveVertex methodsFor: 'accessing'! texCoords ^(self floatAt: 7) @ (self floatAt: 8)! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 2/5/1999 19:30'! texCoords: aVector self floatAt: 7 put: aVector x. self floatAt: 8 put: aVector y. ! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 4/4/1999 00:21'! windowPos ^self windowPosX@self windowPosY! ! !B3DPrimitiveVertex methodsFor: 'accessing' stamp: 'ar 4/4/1999 00:22'! windowPos: aPoint self windowPosX: aPoint x. self windowPosY: aPoint y.! ! !B3DPrimitiveVertex methodsFor: 'accessing'! wordAt: index ^self primitiveFailed! ! !B3DPrimitiveVertex methodsFor: 'accessing'! wordAt: index put: value ^self primitiveFailed! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:48'! normalX ^self floatAt: 4! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:50'! normalX: aFloat self floatAt: 4 put: aFloat! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:48'! normalY ^self floatAt: 5! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:50'! normalY: aFloat self floatAt: 5 put: aFloat! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:48'! normalZ ^self floatAt: 6! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 2/8/1999 17:50'! normalZ: aFloat self floatAt: 6 put: aFloat! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionX ^self floatAt: 1! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionX: aNumber self floatAt: 1 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionY ^self floatAt: 2! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionY: aNumber self floatAt: 2 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionZ ^self floatAt: 3! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! positionZ: aNumber self floatAt: 3 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosW ^self floatAt: 12! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosW: aNumber self floatAt: 12 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosX ^self floatAt: 9! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosX: aNumber self floatAt: 9 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosY ^self floatAt: 10! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosY: aNumber self floatAt: 10 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosZ ^self floatAt: 11! ! !B3DPrimitiveVertex methodsFor: 'transform-support'! rasterPosZ: aNumber self floatAt: 11 put: aNumber! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 4/4/1999 00:31'! windowPosX ^self integerAt: 15! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 4/4/1999 00:31'! windowPosX: anInteger self integerAt: 15 put: anInteger! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 4/4/1999 00:31'! windowPosY ^self integerAt: 16! ! !B3DPrimitiveVertex methodsFor: 'transform-support' stamp: 'ar 4/4/1999 00:31'! windowPosY: anInteger self integerAt: 16 put: anInteger! ! !B3DPrimitiveVertex methodsFor: 'private'! privateReplaceFrom: start to: stop with: replacement startingAt: repStart start to: stop do:[:i| self basicAt: i put: (replacement basicAt: i - start + repStart). ].! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:27'! aValue ^self pixelValue32 bitShift: -24! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:29'! alphaValue ^self pixelValue32 bitShift: -24! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:28'! bValue ^self pixelValue32 bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:29'! blueValue ^self pixelValue32 bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:28'! gValue ^(self pixelValue32 bitShift: -8) bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:29'! greenValue ^(self pixelValue32 bitShift: -8) bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:28'! rValue ^(self pixelValue32 bitShift: -16) bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/6/1999 22:29'! redValue ^(self pixelValue32 bitShift: -16) bitAnd: 255! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/18/1999 06:26'! texCoordS ^self floatAt: 7! ! !B3DPrimitiveVertex methodsFor: 'rasterizer-support' stamp: 'ar 4/18/1999 06:26'! texCoordT ^self floatAt: 8! ! !B3DPrimitiveVertex methodsFor: 'testing' stamp: 'ar 4/4/1999 00:50'! sortsBefore: pVertex "Return true if the receiver should be sorted before the given primitive vertex. Support for rasterizer simulation. Only valid if window position has been computed before." | y0 y1 | y0 _ self windowPosY. y1 _ pVertex windowPosY. y0 = y1 ifTrue:[^self windowPosX <= pVertex windowPosX] ifFalse:[^y0 < y1]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveVertex class instanceVariableNames: ''! !B3DPrimitiveVertex class methodsFor: 'instance creation' stamp: 'ar 2/14/1999 01:23'! new ^self new: PrimVertexSize! ! ArrayedCollection variableWordSubclass: #B3DPrimitiveVertexArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Engine'! !B3DPrimitiveVertexArray commentStamp: '' prior: 0! I store Balloon 3D primitive vertices in place. I am used to pass data efficiently to the primitive level during high-bandwidth operations.! !B3DPrimitiveVertexArray methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:24'! at: index "Return the primitive vertex at the given index" | vtx | (index < 1 or:[index > self size]) ifTrue:[^self errorSubscriptBounds: index]. vtx _ B3DPrimitiveVertex new. vtx privateReplaceFrom: 1 to: vtx size with: self startingAt: index-1*PrimVertexSize+1. ^vtx! ! !B3DPrimitiveVertexArray methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:24'! at: index put: aB3DPrimitiveVertex "Store the primitive vertex at the given index in the receiver" | idx | (index < 1 or:[index > self size]) ifTrue:[^self errorSubscriptBounds: index]. idx _ index-1*PrimVertexSize. self privateReplaceFrom: idx+1 to: idx+PrimVertexSize with: aB3DPrimitiveVertex startingAt: 1. ^aB3DPrimitiveVertex! ! !B3DPrimitiveVertexArray methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:24'! size "Return the number of primitive vertices that can be stored in the receiver" ^self basicSize // PrimVertexSize! ! !B3DPrimitiveVertexArray methodsFor: 'private'! privateReplaceFrom: start to: stop with: replacement startingAt: repStart start to: stop do:[:i| self basicAt: i put: (replacement at: i - start + repStart). ].! ! !B3DPrimitiveVertexArray methodsFor: 'enumerating' stamp: 'ar 2/4/1999 23:57'! upTo: max do: aBlock "Special enumeration message so the client can modify the vertices" | vtx | 1 to: max do:[:i| vtx _ self at: i. aBlock value: vtx. self at: i put: vtx].! ! !B3DPrimitiveVertexArray methodsFor: 'enumerating' stamp: 'ar 2/4/1999 23:59'! upTo: max doWithIndex: aBlock "Special enumeration message so the client can modify the vertices" | vtx | 1 to: max do:[:i| vtx _ self at: i. aBlock value: vtx value: i. self at: i put: vtx].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DPrimitiveVertexArray class instanceVariableNames: ''! !B3DPrimitiveVertexArray class methodsFor: 'instance creation' stamp: 'ar 2/14/1999 01:24'! new: n ^super new: (n * PrimVertexSize)! ! B3DEnginePlugin subclass: #B3DRasterizerPlugin instanceVariableNames: 'state viewport ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/17/1999 20:57'! b3dInitPrimitiveObject | vtxSize vtxArray idxSize idxArray primitive primOop primObj primSize textureIndex | self export: true. self inline: false. self var: #vtxArray declareC:'int *vtxArray'. self var: #idxArray declareC:'int *idxArray'. self var: #primObj declareC:'void *primObj'. "Check argument count" interpreterProxy methodArgumentCount = 8 ifFalse:[^interpreterProxy primitiveFail]. "Fetch the texture index" textureIndex _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. "Load the viewport" self loadViewportFrom: 1. interpreterProxy failed ifTrue:[^nil]. "Fetch and validate the primitive vertex array" vtxSize _ interpreterProxy stackIntegerValue: 4. vtxArray _ self stackPrimitiveVertexArray: 5 ofSize: vtxSize. vtxArray = nil ifTrue:[^interpreterProxy primitiveFail]. "Fetch and validate the primitive index array" idxSize _ interpreterProxy stackIntegerValue: 2. idxArray _ self stackPrimitiveIndexArray: 3 ofSize: idxSize validate: true forVertexSize: vtxSize. idxArray = nil ifTrue:[^interpreterProxy primitiveFail]. "Fetch and validate the primitive type" primitive _ interpreterProxy stackIntegerValue: 6. (primitive < 1 or:[primitive > PrimTypeMax]) ifTrue:[^interpreterProxy primitiveFail]. "For now we only support indexed triangles, quads and polys" (primitive = 3 or:[primitive = 5 or:[primitive = 6]]) ifFalse:[^interpreterProxy primitiveFail]. "Load the primitive object" primOop _ interpreterProxy stackObjectValue: 7. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy isWords: primOop) ifFalse:[^interpreterProxy primitiveFail]. primObj _ interpreterProxy firstIndexableField: primOop. primSize _ interpreterProxy byteSizeOf: primOop. "Do the work" primitive = 3 ifTrue:[ (self cCode: 'b3dAddPolygonObject((void*) primObj, primSize, B3D_FACE_RGB, textureIndex, (B3DPrimitiveVertex*) vtxArray, vtxSize, &viewport) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. ]. primitive = 5 ifTrue:[ (self cCode:'b3dAddIndexedTriangleObject((void*) primObj, primSize, B3D_FACE_RGB, textureIndex, (B3DPrimitiveVertex*) vtxArray, vtxSize, (B3DInputFace*) idxArray, idxSize / 3, &viewport) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. ]. primitive = 6 ifTrue:[ (self cCode:'b3dAddIndexedQuadObject((void*) primObj, primSize, B3D_FACE_RGB, textureIndex, (B3DPrimitiveVertex*) vtxArray, vtxSize, (B3DInputQuad*) idxArray, idxSize / 4, &viewport) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. ]. "Pop args+rcvr; return primitive object" interpreterProxy pop: 9. interpreterProxy push: primOop.! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/14/1999 05:59'! b3dInitializeRasterizerState "Primitive. Initialize the primitive level objects of the given rasterizer." | stateOop objOop objLen obj | self export: true. self inline: false. self var: #obj declareC:'void *obj'. "Check argument count" interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. stateOop _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. ((interpreterProxy isPointers: stateOop) and:[(interpreterProxy slotSizeOf: stateOop) >= 7]) ifFalse:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 0 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeFaceAllocator(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 1 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeEdgeAllocator(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 2 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeAttrAllocator(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 3 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeAET(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 4 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeEdgeList(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. objOop _ interpreterProxy fetchPointer: 5 ofObject: stateOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objLen _ interpreterProxy byteSizeOf: objOop. obj _ interpreterProxy firstIndexableField: objOop. (self cCode: 'b3dInitializeFillList(obj, objLen) !!= B3D_NO_ERROR') ifTrue:[^interpreterProxy primitiveFail]. "Don't pop anything - return the receiver"! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/14/1999 02:06'! b3dPrimitiveObjectSize "Primitive. Return the minimal number of words needed for a primitive object." | objSize | self export: true. self inline: false. objSize _ (self cCode:'sizeof(B3DPrimitiveObject) + sizeof(B3DPrimitiveVertex)') // 4 + 1. interpreterProxy pop: 1. interpreterProxy pushInteger: objSize.! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/14/1999 05:22'! b3dPrimitiveTextureSize "Primitive. Return the minimal number of words needed for a primitive object." | objSize | self export: true. self inline: false. objSize _ (self cCode:'sizeof(B3DTexture)') // 4 + 1. interpreterProxy pop: 1. interpreterProxy pushInteger: objSize.! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/12/1999 02:19'! b3dRasterizerVersion "Primitive. Return the version of the rasterizer." self export: true. self inline: false. interpreterProxy pop: 1. interpreterProxy pushInteger: 1. "Version 1"! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 4/14/1999 20:45'! b3dStartRasterizer "Primitive. Start the rasterizer." | errCode | self export: true. self inline: false. "Check argument count" interpreterProxy methodArgumentCount = 3 ifFalse:[^interpreterProxy primitiveFail]. "Load the base rasterizer state" (self loadRasterizerState: 2) ifFalse:[^interpreterProxy primitiveFail]. "Load the textures" self loadTexturesFrom: 0. interpreterProxy failed ifTrue:[^nil]. "And the objects" self loadObjectsFrom: 1. interpreterProxy failed ifTrue:[^nil]. "And go ..." errCode _ self cCode:'b3dMainLoop(&state, B3D_NO_ERROR)'. self storeObjectsInto: 1. interpreterProxy pop: 4. interpreterProxy pushInteger: errCode.! ! !B3DRasterizerPlugin methodsFor: 'primitives' stamp: 'ar 5/16/2000 20:06'! primitiveSetBitBltPlugin "Primitive. Set the BitBlt plugin to use." | pluginName length ptr needReload | self export: true. self var: #ptr declareC:'char *ptr'. pluginName _ interpreterProxy stackValue: 0. "Must be string to work" (interpreterProxy isBytes: pluginName) ifFalse:[^interpreterProxy primitiveFail]. length _ interpreterProxy byteSizeOf: pluginName. length >= 256 ifTrue:[^interpreterProxy primitiveFail]. ptr _ interpreterProxy firstIndexableField: pluginName. needReload _ false. 0 to: length-1 do:[:i| "Compare and store the plugin to be used" (bbPluginName at: i) = (ptr at: i) ifFalse:[ bbPluginName at: i put: (ptr at: i). needReload _ true]]. (bbPluginName at: length) = 0 ifFalse:[ bbPluginName at: length put: 0. needReload _ true]. needReload ifTrue:[ self initialiseModule ifFalse:[^interpreterProxy primitiveFail]]. interpreterProxy pop: 1. "Return receiver"! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 4/12/1999 06:02'! loadObjectsFrom: stackIndex | arrayOop arraySize objArray objOop objPtr | self var:#objArray declareC:'B3DPrimitiveObject **objArray'. self var:#objPtr declareC:'B3DPrimitiveObject *objPtr'. arrayOop _ interpreterProxy stackObjectValue: stackIndex. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: arrayOop) == (interpreterProxy classArray) ifFalse:[^interpreterProxy primitiveFail]. arraySize _ interpreterProxy slotSizeOf: arrayOop. arraySize > (self cCode:'state.nObjects') ifTrue:[^interpreterProxy primitiveFail]. objArray _ self cCode:'state.objects'. 0 to: arraySize-1 do:[:i| objOop _ interpreterProxy fetchPointer: i ofObject: arrayOop. ((interpreterProxy isIntegerObject: objOop) or:[(interpreterProxy isWords: objOop) not]) ifTrue:[^interpreterProxy primitiveFail]. objPtr _ self cCoerce: (interpreterProxy firstIndexableField: objOop) to:'B3DPrimitiveObject*'. (self cCode:'objPtr->magic !!= B3D_PRIMITIVE_OBJECT_MAGIC') ifTrue:[^interpreterProxy primitiveFail]. self cCode:'objPtr->__oop__ = objOop'. objArray at: i put: objPtr. ].! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 5/16/2000 17:10'! loadRasterizerState: stackIndex "Load the rasterizer state from the given stack index." | stateOop obj objPtr objLen | self var: #objPtr declareC:'void *objPtr'. (copyBitsFn = 0 or:[loadBBFn = 0]) ifTrue:[ "We need loadBitBltFrom/copyBits here so try to load it implicitly" self initialiseModule ifFalse:[^false]. ]. stateOop _ interpreterProxy stackObjectValue: stackIndex. interpreterProxy failed ifTrue:[^false]. ((interpreterProxy isPointers: stateOop) and:[(interpreterProxy slotSizeOf: stateOop) >= 10]) ifFalse:[^false]. obj _ interpreterProxy fetchPointer: 0 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.faceAlloc = objPtr'. obj _ interpreterProxy fetchPointer: 1 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.edgeAlloc = objPtr'. obj _ interpreterProxy fetchPointer: 2 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.attrAlloc = objPtr'. obj _ interpreterProxy fetchPointer: 3 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.aet = objPtr'. obj _ interpreterProxy fetchPointer: 4 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.addedEdges = objPtr'. obj _ interpreterProxy fetchPointer: 5 ofObject: stateOop. ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.fillList = objPtr'. obj _ interpreterProxy fetchPointer: 6 ofObject: stateOop. obj == interpreterProxy nilObject ifTrue:[ self cCode:'state.nObjects = 0'. self cCode:'state.objects = NULL'. ] ifFalse:[ ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objLen _ interpreterProxy slotSizeOf: obj. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.objects = (B3DPrimitiveObject **)objPtr'. self cCode:'state.nObjects = objLen'. ]. obj _ interpreterProxy fetchPointer: 7 ofObject: stateOop. obj == interpreterProxy nilObject ifTrue:[ self cCode:'state.nTextures = 0'. self cCode:'state.textures = NULL'. ] ifFalse:[ ((interpreterProxy isIntegerObject: obj) or:[(interpreterProxy isWords: obj) not]) ifTrue:[^false]. objLen _ interpreterProxy byteSizeOf: obj. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.textures = (B3DTexture *)objPtr'. self cCode:'state.nTextures = objLen / sizeof(B3DTexture)'. ]. obj _ interpreterProxy fetchPointer: 8 ofObject: stateOop. obj == interpreterProxy nilObject ifTrue:[ self cCode:'state.spanSize = 0'. self cCode:'state.spanBuffer = NULL'. ] ifFalse:[ (interpreterProxy fetchClassOf: obj) == (interpreterProxy classBitmap) ifFalse:[^false]. objLen _ interpreterProxy slotSizeOf: obj. objPtr _ interpreterProxy firstIndexableField: obj. self cCode:'state.spanBuffer = (unsigned int *)objPtr'. self cCode:'state.spanSize = objLen'. ]. obj _ interpreterProxy fetchPointer: 9 ofObject: stateOop. obj == interpreterProxy nilObject ifTrue:[ self cCode:'state.spanDrawer = NULL'. ] ifFalse:[ (self cCode: '((int (*) (int))loadBBFn)(obj)') ifFalse:[^false]. self cCode:'state.spanDrawer = (b3dDrawBufferFunction) copyBitsFn'. ]. ^interpreterProxy failed not ! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 4/14/1999 05:50'! loadTexture: textureOop into: destPtr "Note: This still uses the old-style textures" | form formBits formWidth formHeight formDepth texWrap texInterpolate texEnvMode bitsPtr | self var: #bitsPtr declareC:'void *bitsPtr'. self var: #destPtr declareC:'B3DTexture *destPtr'. "Fetch and validate the form" form _ textureOop. (interpreterProxy isPointers: form) ifFalse:[^false]. (interpreterProxy slotSizeOf: form) < 8 ifTrue:[^false]. formBits _ interpreterProxy fetchPointer: 0 ofObject: form. formWidth _ interpreterProxy fetchInteger: 1 ofObject: form. formHeight _ interpreterProxy fetchInteger: 2 ofObject: form. formDepth _ interpreterProxy fetchInteger: 3 ofObject: form. texWrap _ interpreterProxy booleanValueOf: (interpreterProxy fetchPointer: 5 ofObject: form). texInterpolate _ interpreterProxy booleanValueOf: (interpreterProxy fetchPointer: 6 ofObject: form). texEnvMode _ interpreterProxy fetchInteger: 7 ofObject: form. interpreterProxy failed ifTrue:[^false]. (formWidth < 1 or:[formHeight < 1 or:[formDepth ~= 32]]) ifTrue:[^false]. (interpreterProxy fetchClassOf: formBits) = interpreterProxy classBitmap ifFalse:[^false]. (interpreterProxy byteSizeOf: formBits) = (formWidth * formHeight * 4) ifFalse:[^false]. (texEnvMode < 0 or:[texEnvMode > 1]) ifTrue:[^false]. "Now fetch the bits" bitsPtr _ interpreterProxy firstIndexableField: formBits. "Set the texture parameters" ^self cCode:'b3dLoadTexture(destPtr, formWidth, formHeight, formDepth, (unsigned int*) bitsPtr, 0, NULL) == B3D_NO_ERROR'.! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 4/14/1999 05:52'! loadTexturesFrom: stackIndex | arrayOop destPtr n textureOop | self var: #destPtr declareC:'B3DTexture *destPtr'. arrayOop _ interpreterProxy stackObjectValue: stackIndex. (interpreterProxy fetchClassOf: arrayOop) == interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. n _ interpreterProxy slotSizeOf: arrayOop. n _ n min: (self cCode: 'state.nTextures'). 0 to: n-1 do:[:i| destPtr _ self cCode:'state.textures + i'. textureOop _ interpreterProxy fetchPointer: i ofObject: arrayOop. (self loadTexture: textureOop into: destPtr) ifFalse:[^interpreterProxy primitiveFail]. ]. ^0! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 4/10/1999 23:24'! loadViewportFrom: stackIndex "Load the viewport from the given stack index" | oop p1 p2 x0 y0 x1 y1 | oop _ interpreterProxy stackObjectValue: stackIndex. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy isPointers: oop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: oop) < 2 ifTrue:[^interpreterProxy primitiveFail]. p1 _ interpreterProxy fetchPointer: 0 ofObject: oop. p2 _ interpreterProxy fetchPointer: 1 ofObject: oop. (interpreterProxy fetchClassOf: p1) = interpreterProxy classPoint ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy fetchClassOf: p2) = interpreterProxy classPoint ifFalse:[^interpreterProxy primitiveFail]. x0 _ interpreterProxy fetchInteger: 0 ofObject: p1. y0 _ interpreterProxy fetchInteger: 1 ofObject: p1. x1 _ (interpreterProxy fetchInteger: 0 ofObject: p2). y1 _ (interpreterProxy fetchInteger: 1 ofObject: p2). interpreterProxy failed ifTrue:[^nil]. self cCode:'viewport.x0 = x0'. self cCode:'viewport.y0 = y0'. self cCode:'viewport.x1 = x1'. self cCode:'viewport.y1 = y1'. ^0! ! !B3DRasterizerPlugin methodsFor: 'primitive support' stamp: 'ar 4/12/1999 06:01'! storeObjectsInto: stackIndex | arrayOop arraySize objOop | arrayOop _ interpreterProxy stackObjectValue: stackIndex. arraySize _ self cCode: 'state.nObjects'. 0 to: arraySize-1 do:[:i| objOop _ self cCode:'state.objects[i]->__oop__'. interpreterProxy storePointer: i ofObject: arrayOop withValue: objOop. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DRasterizerPlugin class instanceVariableNames: ''! !B3DRasterizerPlugin class methodsFor: 'translation' stamp: 'ar 5/15/2000 23:12'! declareCVarsIn: cg cg addHeaderFile:'"b3d.h"'. cg var: #viewport type: #'B3DPrimitiveViewport'. cg var: #state type: #'B3DRasterizerState'! ! !B3DRasterizerPlugin class methodsFor: 'translation' stamp: 'ar 4/18/1999 08:36'! translateSupportCode: cSrc inlining: inlineFlag "Inline the given C support code if inlineFlag is set. Inlining converts any functions of the form: /* INLINE someFunction(args) */ void someFunction(declaration args) { ... actual code ... } /* --INLINE-- */ into #define someFunction(args) \ /* void someFunction(declaration args) */ \ { \ ... actual code ... \ } \ /* --INLINE-- */ thus using a hard way of forcing inlining by the C compiler." | in out postfix line | true ifTrue:[^cSrc]. "Disabled until I had time to actually test it ;-)" inlineFlag ifFalse:[^cSrc]. in _ ReadStream on: cSrc. out _ WriteStream on: (String new: cSrc size). postfix _ ''. [in atEnd] whileFalse:[ line _ in upTo: Character cr. (line includesSubString:' INLINE ') ifTrue:[ "New inline start" postfix _ ' \'. line _ line copyFrom: (line findString: 'INLINE')+6 to: line size. line _ line copyFrom: 1 to: (line findString: '*/')-1. out nextPutAll:'#define'; nextPutAll: line; nextPutAll: postfix; cr. "Next line has function declaration -- comment this out" [line _ in upTo: Character cr. line includes: ${] whileFalse:[ out nextPutAll:'/* '; nextPutAll: line; nextPutAll:' */'; nextPutAll: postfix; cr. ]. (line first = ${) ifTrue:[ out nextPutAll: line; nextPutAll: postfix; cr. ] ifFalse:[ out nextPutAll: '/* '; nextPutAll:(line copyFrom: 1 to: (line findString:'{')-1); nextPutAll:' */'; nextPutAll:(line copyFrom: (line findString:'{') to: line size); nextPutAll: postfix; cr. ]. ] ifFalse:[ (line includesSubString:'--INLINE--') ifTrue:[postfix _ '']. out nextPutAll: line; nextPutAll: postfix; cr. ]. ]. ^out contents. "| fs | fs _ FileStream newFileNamed:'b3dr.c'. fs nextPutAll: (B3DRasterizerPlugin translateSupportCode: B3DRasterizerPlugin b3dRemapC inlining: true). fs close." ! ! !B3DRasterizerPlugin class methodsFor: 'translation' stamp: 'ar 5/15/2000 23:10'! writeSupportCode: inlineFlag "B3DRasterizerPlugin writeSupportCode: true" "B3DRasterizerPlugin writeSupportCode: false" "Translate all the C support files for the Balloon 3D rasterizer plugin." | src fs | #( (b3dTypesH 'b3dTypes.h') (b3dAllocH 'b3dAlloc.h') (b3dHeaderH 'b3d.h') (b3dInitC 'b3dInit.c') (b3dAllocC 'b3dAlloc.c') (b3dRemapC 'b3dRemap.c') (b3dDrawC 'b3dDraw.c') (b3dMainC 'b3dMain.c') ) do:[:spec| src _ self perform: (spec at: 1). src _ self translateSupportCode: src inlining: inlineFlag. fs _ CrLfFileStream newFileNamed: (spec at: 2). fs nextPutAll: src. fs close. ].! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/18/1999 08:30'! b3dAllocC ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dAlloc.c * CONTENT: Memory allocation for the Balloon 3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #include #include "b3d.h" #ifdef DEBUG_ALLOC /* DEBUG versions of allocators */ B3DPrimitiveFace *dbg_b3dAllocFace(B3DFaceAllocList *list) { B3DPrimitiveFace *result; if(list->firstFree) { result = list->firstFree; list->firstFree = list->firstFree->nextFree; if(result->flags & B3D_ALLOC_FLAG) b3dAbort("list->firstFree has allocation bit set"); } else { if(list->size < list->max) { result = list->data + list->size; list->size++; } else return NULL; } result->nextFree = NULL; result->flags = B3D_ALLOC_FLAG; list->nFree--; return result; } B3DPrimitiveEdge *dbg_b3dAllocEdge(B3DEdgeAllocList *list) { B3DPrimitiveEdge *result; if(list->firstFree) { result = list->firstFree; list->firstFree = list->firstFree->nextFree; if(result->flags & B3D_ALLOC_FLAG) b3dAbort("list->firstFree has allocation bit set"); } else { if(list->size < list->max) { result = list->data + list->size; list->size++; } else return NULL; } result->nextFree = NULL; result->flags = B3D_ALLOC_FLAG; list->nFree--; return result; } void dbg_b3dFreeFace(B3DFaceAllocList *list, B3DPrimitiveFace *face) { if(face < list->data || face >= (list->data + list->size)) b3dAbort("face to free is not in list"); if( !! (face->flags & B3D_ALLOC_FLAG) ) b3dAbort("face to free has no allocation flag set"); face->flags = 0; face->nextFree = list->firstFree; list->firstFree = face; list->nFree++; } void dbg_b3dFreeEdge(B3DEdgeAllocList *list, B3DPrimitiveEdge *edge) { if(edge < list->data || edge >= (list->data + list->size)) b3dAbort("edge to free is not in list"); if( !! (edge->flags & B3D_ALLOC_FLAG) ) b3dAbort("edge to free has no allocation flag set"); edge->flags = 0; edge->nextFree = list->firstFree; list->firstFree = edge; list->nFree++; } B3DPrimitiveAttribute *dbg_b3dAllocSingleAttr(B3DAttrAllocList *list) { B3DPrimitiveAttribute *result; if(list->firstFree) { result = list->firstFree; list->firstFree = list->firstFree->next; } else { if(list->size < list->max) { result = list->data + list->size; list->size++; } else return NULL; } list->nFree--; return result; } int dbg_b3dAllocAttrib(B3DAttrAllocList *attrList, B3DPrimitiveFace *face) { B3DPrimitiveAttribute *firstAttr, *nextAttr; int i, nAttrs = 0; assert(face->attributes == NULL); if(face->flags & B3D_FACE_RGB) nAttrs += 3; if(face->flags & B3D_FACE_ALPHA) nAttrs += 1; if(face->flags & B3D_FACE_STW) nAttrs += 3; if(!!nAttrs) return 1; firstAttr = nextAttr = NULL; for(i=0;inext = firstAttr; firstAttr = nextAttr; } face->attributes = firstAttr; return 1; } void dbg_b3dFreeAttrib(B3DAttrAllocList *list, B3DPrimitiveFace *face) { B3DPrimitiveAttribute *attr, *nextAttr = face->attributes; while(nextAttr) { attr = nextAttr; nextAttr = attr->next; if(attr < list->data || attr >= (list->data + list->size)) b3dAbort("attributes to free are not in list"); attr->next = list->firstFree; list->firstFree = attr; list->nFree++; } } #endif /* DEBUG */ '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/18/1999 08:35'! b3dAllocH ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dAlloc.h * CONTENT: Memory allocation for the Balloon 3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #ifndef B3D_ALLOC_H #define B3D_ALLOC_H #include "b3dTypes.h" /************************ Allocator definitions ************************/ #define B3D_EDGE_ALLOC_MAGIC 0x45443341 typedef struct B3DEdgeAllocList { int magic; void *This; int max; /* Note: size is ALWAYS less than max */ int size; int nFree; B3DPrimitiveEdge *firstFree; /* pointer to the first free edge (< max) */ B3DPrimitiveEdge data[1]; } B3DEdgeAllocList; #define B3D_FACE_ALLOC_MAGIC 0x46443341 typedef struct B3DFaceAllocList { int magic; void *This; int max; /* Note: size is ALWAYS less than max */ int size; int nFree; B3DPrimitiveFace *firstFree; /* pointer to the first free face (< max) */ B3DPrimitiveFace data[1]; } B3DFaceAllocList; #define B3D_ATTR_ALLOC_MAGIC 0x41443341 typedef struct B3DAttrAllocList { int magic; void *This; int max; /* Note: size is ALWAYS less than max */ int size; int nFree; B3DPrimitiveAttribute *firstFree; /* pointer to the first free attribute (< max) */ B3DPrimitiveAttribute data[1]; } B3DAttrAllocList; /* The mapping from face flags to the number of attributes needed */ extern int B3D_ATTRIBUTE_SIZES[B3D_MAX_ATTRIBUTES]; #define B3D_FACE_ATTRIB_SIZE(face) (B3D_ATTRIBUTE_SIZES[(face->flags >> B3D_ATTR_SHIFT) & B3D_ATTR_MASK]) #ifdef DEBUG_ALLOC B3DPrimitiveFace *dbg_b3dAllocFace(B3DFaceAllocList *list); B3DPrimitiveEdge *dbg_b3dAllocEdge(B3DEdgeAllocList *list); int dbg_b3dAllocAttrib(B3DAttrAllocList *attrList, B3DPrimitiveFace *face); void dbg_b3dFreeFace(B3DFaceAllocList *list, B3DPrimitiveFace *face); void dbg_b3dFreeEdge(B3DEdgeAllocList *list, B3DPrimitiveEdge *edge); void dbg_b3dFreeAttrib(B3DAttrAllocList *list, B3DPrimitiveFace *face); #define b3dAllocFace(list, face) face = dbg_b3dAllocFace(list); #define b3dAllocEdge(list, edge) edge = dbg_b3dAllocEdge(list); #define b3dAllocAttrib(attrList, face, result) result = dbg_b3dAllocAttrib(attrList, face); #define b3dFreeFace(list, face) dbg_b3dFreeFace(list, face); #define b3dFreeEdge(list, edge) dbg_b3dFreeEdge(list, edge); #define b3dFreeAttrib(list, face) dbg_b3dFreeAttrib(list, face); #else /* RELEASE */ #define b3dAlloc(list,object) \ {\ if(list->firstFree) { \ object = list->firstFree; \ list->firstFree = object->nextFree; \ object->flags = B3D_ALLOC_FLAG; \ list->nFree--;\ } else { \ if(list->size < list->max) { \ object = list->data + list->size; \ list->size++;\ object->flags = B3D_ALLOC_FLAG;\ list->nFree--;\ } else object = NULL;\ }\ } #define b3dFree(list, object) \ {\ object->flags = 0;\ object->nextFree = list->firstFree; \ list->firstFree = object;\ list->nFree++;\ } #define b3dAllocFace(list, face) b3dAlloc(list,face) #define b3dAllocEdge(list, edge) b3dAlloc(list, edge) #define b3dFreeFace(list, face) b3dFree(list, face) #define b3dFreeEdge(list, edge) b3dFree(list, edge) #define b3dAllocSingleAttr(list,object) \ {\ if(list->firstFree) { \ object = list->firstFree; \ list->firstFree = object->next; \ list->nFree--;\ } else { \ if(list->size < list->max) { \ object = list->data + list->size; \ list->size++;\ list->nFree--;\ } else object = NULL;\ }\ } #define b3dAllocAttrib(attrList,face, result) \ {\ B3DPrimitiveAttribute *firstAttr, *nextAttr;\ int nAttrs = 0;\ \ if(face->flags & B3D_FACE_RGB) nAttrs += 3;\ if(face->flags & B3D_FACE_ALPHA) nAttrs += 1;\ if(face->flags & B3D_FACE_STW) nAttrs += 3;\ firstAttr = nextAttr = NULL;\ while(nAttrs--) {\ b3dAllocSingleAttr(attrList, nextAttr);\ if(!!nextAttr) break;\ nextAttr->next = firstAttr;\ firstAttr = nextAttr;\ };\ face->attributes = firstAttr;\ result = nextAttr !!= NULL;\ } #define b3dFreeAttrib(list, face) \ {\ B3DPrimitiveAttribute *attr, *nextAttr = face->attributes;\ while(nextAttr) {\ attr = nextAttr;\ nextAttr = attr->next;\ attr->next = list->firstFree;\ list->firstFree = attr;\ list->nFree++;\ }\ } #endif #endif /* ifndef B3D_ALLOC_H */ '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/21/1999 01:58'! b3dDrawC ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dDraw.c * CONTENT: Pixel drawing functions for the B3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: LOTS of stuff missing here... * * - A note on RGBA interpolation: * For low polygon models it makes sense to compute both, the left and * the right attribute value if there might be any overflow at all. * Since we''re usually drawing many pixels in a row we can clamp the * left and right value and thus be safe during the interpolation stage. * *****************************************************************************/ #include "b3d.h" #define rasterPosX rasterPos[0] #define rasterPosY rasterPos[1] #define redValue color[RED_INDEX] #define greenValue color[GREEN_INDEX] #define blueValue color[BLUE_INDEX] #define alphaValue color[ALPHA_INDEX] /* The following defines the maximum number of pixels we treat in one loop. This value should be carefully chosen: Setting it high will increase speed for larger polygons but reduce speed for smaller ones. Setting it low will do the opposite. Also, since I''m assuming a smart compiler, the code size will probably increase with this number (if loops are unrolled by the compiler). The current value of 5 should be a good median (32 pixels are processed at most and we''ll have the overhead of 5 tests for a one-pixel polygon). */ #define MAX_PIXEL_SHIFT 5 /* USE_MULTBL: Replace up a couple of multiplications by table lookups. On PowerPC, the lookup seems to be slightly slower. On Intel, the lookup is way faster. */ #ifndef USE_MULTBL # ifdef __POWERPC__ # define USE_MULTBL 0 # else # define USE_MULTBL 1 # endif #endif /* Clamp the given value */ #define CLAMP(value, min, max)\ if((value) < (min)) (value) = (min); \ else if((value) > (max)) (value) = (max); /* Clamp a set of fixed point RGB values */ #define CLAMP_RGB(r,g,b) \ CLAMP(r,B3D_FixedHalf, (255 << B3D_IntToFixedShift) + B3D_FixedHalf)\ CLAMP(g,B3D_FixedHalf, (255 << B3D_IntToFixedShift) + B3D_FixedHalf)\ CLAMP(b,B3D_FixedHalf, (255 << B3D_IntToFixedShift) + B3D_FixedHalf) #ifdef DEBUG_ATTR double attrValueAt(B3DPrimitiveFace *face, B3DPrimitiveAttribute *attr, double xValue, double yValue) { return (attr->value + ((xValue - face->v0->rasterPosX) * attr->dvdx) + ((yValue - face->v0->rasterPosY) * attr->dvdy)); } #else #define attrValueAt(face,attr,xValue,yValue) \ ((attr)->value + \ (((double)(xValue) - (face)->v0->rasterPosX) * (attr)->dvdx) + \ (((double)(yValue) - (face)->v0->rasterPosY) * (attr)->dvdy)) #endif #define SETUP_RGB \ rValue = (int)(attrValueAt(face, attr, floatX, floatY) * B3D_FloatToFixed); \ deltaR = (int) (attr->dvdx * B3D_FloatToFixed); \ attr = attr->next; \ gValue = (int)(attrValueAt(face, attr, floatX, floatY) * B3D_FloatToFixed);\ deltaG = (int) (attr->dvdx * B3D_FloatToFixed); \ attr = attr->next; \ bValue = (int)(attrValueAt(face, attr, floatX, floatY) * B3D_FloatToFixed); \ deltaB = (int) (attr->dvdx * B3D_FloatToFixed); \ attr = attr->next;\ CLAMP_RGB(rValue, gValue, bValue); #define SETUP_STW \ wValue = attrValueAt(face, attr, floatX, floatY); \ wDelta = attr->dvdx; \ attr = attr->next; \ sValue = attrValueAt(face, attr, floatX, floatY); \ sDelta = attr->dvdx; \ attr = attr->next; \ tValue = attrValueAt(face, attr, floatX, floatY); \ tDelta = attr->dvdx; \ attr = attr->next; #define STEP_STW \ sValue += sDelta;\ tValue += tDelta;\ wValue += wDelta; /* Load the four neighbouring texels into tex00, tex01, tex10, and tex11 */ #define LOAD_4_RGB_TEXEL_32(fixedS, fixedT, texture) \ {\ int sIndex, tIndex;\ \ if(texture->sMask) {\ sIndex = (fixedS >> B3D_FixedToIntShift) & texture->sMask;\ } else {\ sIndex = (fixedS >> B3D_FixedToIntShift) % texture->width;\ }\ if(texture->tMask) {\ tIndex = (fixedT >> B3D_FixedToIntShift) & texture->tMask;\ } else {\ tIndex = (fixedT >> B3D_FixedToIntShift) % texture->height;\ }\ /* Load the 4 texels, wrapping if necessary */\ tex00 = (struct b3dPixelColor *) texture->data + (tIndex * texture->width) + sIndex;\ tex01 = tex00 + 1;\ tex10 = tex00 + texture->width;\ tex11 = tex10 + 1;\ if(sIndex+1 == texture->width) {\ tex01 -= texture->width;\ tex11 -= texture->width;\ }\ if(tIndex+1 == texture->height) {\ int tsize = texture->height * texture->width;\ tex10 -= tsize;\ tex11 -= tsize;\ }\ } #if USE_MULTBL /* Use a 16x256 table for lookups */ unsigned short MULTBL[17][256]; static int multblInit = 0; static void MULTBL_Init(void) { int i,j; for(i=0;i<17;i++) for(j=0; j<256; j++) MULTBL[i][j] = (i*j) >> 4; multblInit = 1; } #define INIT_MULTBL { if (!!multblInit) MULTBL_Init(); } #define DO_RGB_INTERPOLATION(sf, si, tf, ti) \ tr = (MULTBL[ti][(MULTBL[si][tex00->redValue] + MULTBL[sf][tex01->redValue])] + \ MULTBL[tf][(MULTBL[si][tex10->redValue] + MULTBL[sf][tex11->redValue])]);\ tg = (MULTBL[ti][(MULTBL[si][tex00->greenValue] + MULTBL[sf][tex01->greenValue])] + \ MULTBL[tf][(MULTBL[si][tex10->greenValue] + MULTBL[sf][tex11->greenValue])]);\ tb = (MULTBL[ti][(MULTBL[si][tex00->blueValue] + MULTBL[sf][tex01->blueValue])] + \ MULTBL[tf][(MULTBL[si][tex10->blueValue] + MULTBL[sf][tex11->blueValue])]); #define DO_RGBA_INTERPOLATION(sf, si, tf, ti)\ tr = (MULTBL[ti][(MULTBL[si][tex00->redValue] + MULTBL[sf][tex01->redValue])] + \ MULTBL[tf][(MULTBL[si][tex10->redValue] + MULTBL[sf][tex11->redValue])]);\ tg = (MULTBL[ti][(MULTBL[si][tex00->greenValue] + MULTBL[sf][tex01->greenValue])] + \ MULTBL[tf][(MULTBL[si][tex10->greenValue] + MULTBL[sf][tex11->greenValue])]);\ tb = (MULTBL[ti][(MULTBL[si][tex00->blueValue] + MULTBL[sf][tex01->blueValue])] + \ MULTBL[tf][(MULTBL[si][tex10->blueValue] + MULTBL[sf][tex11->blueValue])]); \ ta = (MULTBL[ti][(MULTBL[si][tex00->alphaValue] + MULTBL[sf][tex01->alphaValue])] + \ MULTBL[tf][(MULTBL[si][tex10->alphaValue] + MULTBL[sf][tex11->alphaValue])]); #else #define INIT_MULTBL #define DO_RGB_INTERPOLATION(sf, si, tf, ti) \ tr = (ti * (si * tex00->redValue + sf * tex01->redValue) +\ tf * (si * tex10->redValue + sf * tex11->redValue)) >> 8;\ tg = (ti * (si * tex00->greenValue + sf * tex01->greenValue) +\ tf * (si * tex10->greenValue + sf * tex11->greenValue)) >> 8;\ tb = (ti * (si * tex00->blueValue + sf * tex01->blueValue) +\ tf * (si * tex10->blueValue + sf * tex11->blueValue)) >> 8;\ #define DO_RGBA_INTERPOLATION(sf, si, tf, ti) \ tr = (ti * (si * tex00->redValue + sf * tex01->redValue) +\ tf * (si * tex10->redValue + sf * tex11->redValue)) >> 8;\ tg = (ti * (si * tex00->greenValue + sf * tex01->greenValue) +\ tf * (si * tex10->greenValue + sf * tex11->greenValue)) >> 8;\ tb = (ti * (si * tex00->blueValue + sf * tex01->blueValue) +\ tf * (si * tex10->blueValue + sf * tex11->blueValue)) >> 8;\ ta = (ti * (si * tex00->alphaValue + sf * tex01->alphaValue) +\ tf * (si * tex10->alphaValue + sf * tex11->alphaValue)) >> 8; #endif /* No MULTBL */ #define INTERPOLATE_RGB_TEXEL(fixedS, fixedT)\ { int sf, si, tf, ti;\ sf = (fixedS >> (B3D_FixedToIntShift - 4)) & 15; si = 16 - sf;\ tf = (fixedT >> (B3D_FixedToIntShift - 4)) & 15; ti = 16 - tf;\ DO_RGB_INTERPOLATION(sf, si, tf, ti)\ } void b3dNoDraw (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawRGB (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawRGBA (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawSTW (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawSTWA (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawSTWRGB (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); void b3dDrawSTWARGB(int leftX, int rightX, int yValue, B3DPrimitiveFace *face); b3dPixelDrawer B3D_FILL_FUNCTIONS[B3D_MAX_ATTRIBUTES] = { b3dNoDraw, /* No attributes */ b3dDrawRGB, /* B3D_FACE_RGB */ b3dNoDraw, /* B3D_FACE_ALPHA -- IGNORED!!!!!! */ b3dDrawRGBA, /* B3D_FACE_RGB | B3D_FACE_ALPHA */ b3dDrawSTW, /* B3D_FACE_STW */ b3dDrawSTWRGB, /* B3D_FACE_STW | B3D_FACE_RGB */ b3dDrawSTWA, /* B3D_FACE_STW | B3D_FACE_ALPHA */ b3dDrawSTWARGB /* B3D_FACE_STW | B3D_FACE_RGB | B3D_FACE_ALPHA */ }; void b3dNoDraw(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { if(b3dDebug) b3dAbort("b3dNoDraw called!!"); } void b3dDrawRGBFlat(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { struct b3dPixelColor { B3DPrimitiveColor color; } pv, *bits; int rValue, gValue, bValue; int deltaR, deltaG, deltaB; { B3DPrimitiveAttribute *attr = face->attributes; /* Ughh ... I''m having a sampling problem somewhere. In theory, the faces should be sampled *exactly* at integer values (the necessary offset should be done before) so that we always sample inside the triangle. For some reason that doesn''t quite work yet and that''s why here is the strange 0.5 offset and the awful lot of tests. At some time I''ll review this but for now I have more important things to do. */ double floatX = leftX; double floatY = yValue+0.5; if(b3dDebug) if(!!attr) b3dAbort("face has no RGB attributes"); SETUP_RGB; } bits = (struct b3dPixelColor *) currentState->spanBuffer; pv.redValue = (unsigned char) (rValue >> B3D_FixedToIntShift); pv.greenValue = (unsigned char) (gValue >> B3D_FixedToIntShift); pv.blueValue = (unsigned char) (bValue >> B3D_FixedToIntShift); pv.alphaValue = 255; while(leftX <= rightX) { bits[leftX++] = pv; } } void b3dDrawRGB(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { struct b3dPixelColor { B3DPrimitiveColor color; } pv, *bits; int rValue, gValue, bValue; int deltaR, deltaG, deltaB; int deltaX, pixelShift; { B3DPrimitiveAttribute *attr = face->attributes; /* Ughh ... I''m having a sampling problem somewhere. In theory, the faces should be sampled *exactly* at integer values (the necessary offset should be done before) so that we always sample inside the triangle. For some reason that doesn''t quite work yet and that''s why here is the strange 0.5 offset and the awful lot of tests. At some time I''ll review this but for now I have more important things to do. */ double floatX = leftX; double floatY = yValue+0.5; if(b3dDebug) if(!!attr) b3dAbort("face has no RGB attributes"); SETUP_RGB; } bits = (struct b3dPixelColor *) currentState->spanBuffer; pv.alphaValue = 255; /* Reduce the overhead of clamping by precomputing the deltas for each power of two step. A good question here is whether or not it is a good idea to do 2 pixels by this... */ deltaX = rightX - leftX + 1; /* Now do all the powers of two except the last one pixel */ /* Note: A smart compiler (== gcc) should unroll the following loop */ for(pixelShift= MAX_PIXEL_SHIFT; pixelShift> 0; pixelShift--) { int nPixels = 1 << pixelShift; /* Note: The ''if'' here is possible since we have dealt with huge polys above */ while(deltaX >= nPixels) { { /* Compute right most values of color interpolation */ int maxR = rValue + (deltaR << pixelShift); int maxG = gValue + (deltaG << pixelShift); int maxB = bValue + (deltaB << pixelShift); /* Clamp those guys */ CLAMP_RGB(maxR, maxG, maxB); /* And compute the actual delta */ deltaR = (maxR - rValue) >> pixelShift; deltaG = (maxG - gValue) >> pixelShift; deltaB = (maxB - bValue) >> pixelShift; } /* Do the inner loop */ { int n = nPixels; while(n--) { pv.redValue = (unsigned char) (rValue >> B3D_FixedToIntShift); pv.greenValue = (unsigned char) (gValue >> B3D_FixedToIntShift); pv.blueValue = (unsigned char) (bValue >> B3D_FixedToIntShift); bits[leftX++] = pv; rValue += deltaR; gValue += deltaG; bValue += deltaB; } } /* Finally, adjust the number of pixels left */ deltaX -= nPixels; } } /* The last pixel is done separately */ if(deltaX) { pv.redValue = (unsigned char) (rValue >> B3D_FixedToIntShift); pv.greenValue = (unsigned char) (gValue >> B3D_FixedToIntShift); pv.blueValue = (unsigned char) (bValue >> B3D_FixedToIntShift); bits[leftX++] = pv; } } void b3dDrawSTWRGB(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { struct b3dPixelColor { B3DPrimitiveColor color; } pv, *bits, *tex00, *tex10, *tex01, *tex11; double sValue, tValue, wValue, sDelta, tDelta, wDelta, oneOverW; int rValue, gValue, bValue; int deltaR, deltaG, deltaB; int tr, tg, tb, ta; int fixedLeftS, fixedRightS, fixedLeftT, fixedRightT, fixedDeltaS, fixedDeltaT; int deltaX, pixelShift; B3DTexture *texture = face->texture; INIT_MULTBL; if(!!texture || 0) { /* If no texture simply draw RGB */ b3dDrawRGB(leftX, rightX, yValue, face); return; } if(texture->depth < 16 && (texture->cmSize < (1 << texture->depth))) return; /* Colormap not installed */ { B3DPrimitiveAttribute *attr = face->attributes; /* See above */ double floatX = leftX; double floatY = yValue+0.5; if(b3dDebug) if(!!attr) b3dAbort("face has no RGB attributes"); SETUP_RGB; SETUP_STW; } tr = tg = tb = ta = 255; bits = (struct b3dPixelColor *) currentState->spanBuffer; pv.alphaValue = 255; /* VERY Experimental: Reduce the overhead of clamping as well as division by W by precomputing the deltas for each power of two step */ deltaX = rightX - leftX + 1; if(wValue) oneOverW = 1.0 / wValue; else oneOverW = 0.0; fixedLeftS = (int) (sValue * oneOverW * (texture->width << B3D_IntToFixedShift)); fixedLeftT = (int) (tValue * oneOverW * (texture->height << B3D_IntToFixedShift)); for(pixelShift = MAX_PIXEL_SHIFT; pixelShift > 0; pixelShift--) { int nPixels = 1 << pixelShift; while(deltaX >= nPixels) { { /* Compute right most values of color interpolation */ int maxR = rValue + (deltaR << pixelShift); int maxG = gValue + (deltaG << pixelShift); int maxB = bValue + (deltaB << pixelShift); /* Clamp those guys */ CLAMP_RGB(maxR, maxG, maxB); /* And compute the actual delta */ deltaR = (maxR - rValue) >> pixelShift; deltaG = (maxG - gValue) >> pixelShift; deltaB = (maxB - bValue) >> pixelShift; } /* Compute the RIGHT s/t values (the left ones are kept from the last loop) */ wValue += wDelta * nPixels; sValue += sDelta * nPixels; tValue += tDelta * nPixels; if(wValue) oneOverW = 1.0 / wValue; else oneOverW = 0.0; fixedRightS = (int) (sValue * oneOverW * (texture->width << B3D_IntToFixedShift)); fixedDeltaS = (fixedRightS - fixedLeftS) >> pixelShift; fixedRightT = (int) (tValue * oneOverW * (texture->height << B3D_IntToFixedShift)); fixedDeltaT = (fixedRightT - fixedLeftT) >> pixelShift; /* Do the inner loop */ { int n = nPixels; while(n--) { /* Do the texture load ... hmm ... there should be a way to avoid loading the texture on each pixel... On the other hand, the texture load does not seem too expensive if compared with the texture interpolation. */ LOAD_4_RGB_TEXEL_32(fixedLeftS, fixedLeftT, texture); /* Do the interpolation based on tex00, tex01, tex10, tex11. THIS seems to be one of the real bottlenecks here... */ INTERPOLATE_RGB_TEXEL(fixedLeftS, fixedLeftT); #if USE_MULTBL pv.redValue = (unsigned char) (MULTBL[rValue >> (B3D_FixedToIntShift+4)][tr]); pv.greenValue = (unsigned char) (MULTBL[gValue >> (B3D_FixedToIntShift+4)][tg]); pv.blueValue = (unsigned char) (MULTBL[bValue >> (B3D_FixedToIntShift+4)][tb]); #else pv.redValue = (unsigned char) ((tr * rValue) >> (B3D_FixedToIntShift + 8)); pv.greenValue = (unsigned char) ((tg * gValue) >> (B3D_FixedToIntShift + 8)); pv.blueValue = (unsigned char) ((tb * bValue) >> (B3D_FixedToIntShift + 8)); #endif bits[leftX++] = pv; rValue += deltaR; gValue += deltaG; bValue += deltaB; fixedLeftS += fixedDeltaS; fixedLeftT += fixedDeltaT; } } /* Finally, adjust the number of pixels left and update s/t */ deltaX -= nPixels; fixedLeftS = fixedRightS; fixedLeftT = fixedRightT; } } /* The last pixel is done separately */ if(deltaX) { /* Do the texture load */ LOAD_4_RGB_TEXEL_32(fixedLeftS, fixedLeftT, texture); /* Do the interpolation */ INTERPOLATE_RGB_TEXEL(fixedLeftS, fixedLeftT); #if USE_MULTBL pv.redValue = (unsigned char) (MULTBL[rValue >> (B3D_FixedToIntShift+4)][tr]); pv.greenValue = (unsigned char) (MULTBL[gValue >> (B3D_FixedToIntShift+4)][tg]); pv.blueValue = (unsigned char) (MULTBL[bValue >> (B3D_FixedToIntShift+4)][tb]); #else pv.redValue = (unsigned char) ((tr * rValue) >> (B3D_FixedToIntShift + 8)); pv.greenValue = (unsigned char) ((tg * gValue) >> (B3D_FixedToIntShift + 8)); pv.blueValue = (unsigned char) ((tb * bValue) >> (B3D_FixedToIntShift + 8)); #endif bits[leftX++] = pv; } } void b3dDrawSTWARGB(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { /* not yet implemented */ } void b3dDrawRGBA(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { /* not yet implemented */ } void b3dDrawSTW(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { /* not yet implemented */ } void b3dDrawSTWA(int leftX, int rightX, int yValue, B3DPrimitiveFace *face) { /* not yet implemented */ } '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/18/1999 18:27'! b3dHeaderH ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3d.h * CONTENT: Main include file * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #ifndef __B3D_H #define __B3D_H #ifdef DEBUG #define b3dDebug 1 #else #define b3dDebug 0 #endif #define b3dDoStats 1 /* primary include file */ #include "b3dTypes.h" #include "b3dAlloc.h" typedef int (*b3dDrawBufferFunction) (int leftX, int rightX, int yValue); typedef struct B3DRasterizerState { /* The three sources for allocating temporary rasterizer objects */ B3DFaceAllocList *faceAlloc; B3DEdgeAllocList *edgeAlloc; B3DAttrAllocList *attrAlloc; /* The active edge table */ B3DActiveEdgeTable *aet; /* The list for newly added edges */ B3DPrimitiveEdgeList *addedEdges; /* The fill list */ B3DFillList *fillList; /* The input objects for the rasterizer */ int nObjects; B3DPrimitiveObject **objects; /* The input textures for the rasterizer */ int nTextures; B3DTexture *textures; /* Length and location of span buffer to use */ int spanSize; unsigned int *spanBuffer; /* Function to call on drawing the output buffer */ b3dDrawBufferFunction spanDrawer; } B3DRasterizerState; extern B3DRasterizerState *currentState; /* from b3dInit.c */ int b3dInitializeEdgeAllocator(void* base, int length); int b3dInitializeFaceAllocator(void* base, int length); int b3dInitializeAttrAllocator(void* base, int length); int b3dInitializeAET(void* base, int length); int b3dInitializeEdgeList(void* base, int length); int b3dInitializeFillList(void* base, int length); void b3dSetupObjects(B3DRasterizerState *state); int b3dAddPolygonObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DPrimitiveViewport *vp); int b3dAddIndexedQuadObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DInputQuad *quadPtr, int nQuads, B3DPrimitiveViewport *vp); int b3dAddIndexedTriangleObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DInputFace *facePtr, int nFaces, B3DPrimitiveViewport *vp); int b3dLoadTexture(B3DTexture *texture, int width, int height, int depth, unsigned int *bits, int cmSize, unsigned int *colormap); /* from b3dRemap.c */ int b3dValidateAndRemapState(B3DRasterizerState *state); /* from b3dDraw.c */ typedef void (*b3dPixelDrawer) (int leftX, int rightX, int yValue, B3DPrimitiveFace *face); extern b3dPixelDrawer B3D_FILL_FUNCTIONS[]; /* from b3dMain.c */ void b3dAbort(char *msg); int b3dMainLoop(B3DRasterizerState *state, int stopReason); #endif '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 10/30/2000 20:48'! b3dInitC ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dInit.c * CONTENT: Initialization functions for the B3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #include #include "b3d.h" #define b3dCompensateWindowPos 1 /* helpers */ #define rasterPosX rasterPos[0] #define rasterPosY rasterPos[1] #define rasterPosZ rasterPos[2] #define rasterPosW rasterPos[3] #define windowPosX windowPos[0] #define windowPosY windowPos[1] #define texCoordS texCoord[0] #define texCoordT texCoord[1] /*************************************************************/ /*************************************************************/ /*************************************************************/ int b3dInitializeEdgeAllocator(void* base, int length) { B3DEdgeAllocList *list = (B3DEdgeAllocList*) base; if(length < sizeof(B3DEdgeAllocList)) return B3D_GENERIC_ERROR; list->magic = B3D_EDGE_ALLOC_MAGIC; list->This = base; list->max = (length - sizeof(B3DEdgeAllocList)) / sizeof(B3DPrimitiveEdge) + 1; list->size = 0; list->nFree = list->max; list->firstFree = NULL; return B3D_NO_ERROR; } int b3dInitializeFaceAllocator(void* base, int length) { B3DFaceAllocList *list = (B3DFaceAllocList*) base; if(length < sizeof(B3DFaceAllocList)) return B3D_GENERIC_ERROR; list->magic = B3D_FACE_ALLOC_MAGIC; list->This = base; list->max = (length - sizeof(B3DFaceAllocList)) / sizeof(B3DPrimitiveFace) + 1; list->size = 0; list->nFree = list->max; list->firstFree = NULL; return B3D_NO_ERROR; } int b3dInitializeAttrAllocator(void* base, int length) { B3DAttrAllocList *list = (B3DAttrAllocList*) base; if(length < sizeof(B3DAttrAllocList)) return B3D_GENERIC_ERROR; list->magic = B3D_ATTR_ALLOC_MAGIC; list->This = base; list->max = (length - sizeof(B3DAttrAllocList)) / sizeof(B3DPrimitiveAttribute) + 1; list->size = 0; list->nFree = list->max; list->firstFree = NULL; return B3D_NO_ERROR; } int b3dInitializeEdgeList(void* base, int length) { B3DPrimitiveEdgeList *list = (B3DPrimitiveEdgeList*) base; if(length < sizeof(B3DPrimitiveEdgeList)) return B3D_GENERIC_ERROR; list->magic = B3D_EDGE_LIST_MAGIC; list->This = base; list->max = (length - sizeof(B3DPrimitiveEdgeList)) / sizeof(B3DPrimitiveEdge*) + 1; list->size = 0; return B3D_NO_ERROR; } int b3dInitializeAET(void* base, int length) { B3DActiveEdgeTable *aet = (B3DActiveEdgeTable *) base; if(length < sizeof(B3DActiveEdgeTable)) return B3D_GENERIC_ERROR; aet->magic = B3D_AET_MAGIC; aet->This = base; aet->max = (length - sizeof(B3DActiveEdgeTable)) / sizeof(B3DPrimitiveEdge*) + 1; aet->size = 0; aet->leftEdge = aet->rightEdge = NULL; aet->lastIntersection = &aet->tempEdge0; aet->nextIntersection = &aet->tempEdge1; return B3D_NO_ERROR; } int b3dInitializeFillList(void* base, int length) { B3DFillList *list = (B3DFillList*) base; if(length < sizeof(B3DFillList)) return B3D_GENERIC_ERROR; list->magic = B3D_FILL_LIST_MAGIC; list->This = base; list->firstFace = list->lastFace = NULL; return B3D_NO_ERROR; } /*************************************************************/ /*************************************************************/ /*************************************************************/ /* b3dMapObjectVertices: Map all the vertices of the given object into the designated viewport. */ void b3dMapObjectVertices(B3DPrimitiveObject *obj, B3DPrimitiveViewport *vp) { double xScale, yScale, xOfs, yOfs; int minX, minY, maxX, maxY; double minZ, maxZ; B3DPrimitiveVertex *vtx; int i; xOfs = (vp->x0 + vp->x1) * 0.5 - 0.5; yOfs = (vp->y0 + vp->y1) * 0.5 - 0.5; xScale = (vp->x1 - vp->x0) * 0.5; yScale = (vp->y1 - vp->y0) * -0.5; minX = minY = maxX = maxY = 0x7FFFFFFF; minZ = maxZ = 0.0; vtx = obj->vertices + 1; for(i=1; i < obj->nVertices; i++, vtx++) { double x,y,z,w; int scaledX, scaledY; w = vtx->rasterPosW; if(w) w = 1.0 / w; x = vtx->rasterPosX * w * xScale + xOfs; y = vtx->rasterPosY * w * yScale + yOfs; z = vtx->rasterPosZ * w; if(!!b3dCompensateWindowPos) { vtx->rasterPosX = (float)x; vtx->rasterPosY = (float)y; } vtx->rasterPosZ = (float)z; vtx->rasterPosW = (float)w; scaledX = (int) (x * B3D_FloatToFixed); scaledY = (int) (y * B3D_FloatToFixed); vtx->windowPosX = scaledX; vtx->windowPosY = scaledY; if(b3dCompensateWindowPos) { vtx->rasterPosX = (float) (scaledX * B3D_FixedToFloat); vtx->rasterPosY = (float) (scaledY * B3D_FixedToFloat); } /* Update min/max */ if(i == 1) { minX = maxX = scaledX; minY = maxY = scaledY; minZ = maxZ = z; } else { if(scaledX < minX) minX = scaledX; else if(scaledX > maxX) maxX = scaledX; if(scaledY < minY) minY = scaledY; else if(scaledY > maxY) maxY = scaledY; if(z < minZ) minZ = z; else if(z > maxZ) maxZ = z; } } obj->minX = minX >> B3D_FixedToIntShift; obj->maxX = maxX >> B3D_FixedToIntShift; obj->minY = minY >> B3D_FixedToIntShift; obj->maxY = maxY >> B3D_FixedToIntShift; obj->minZ = (float)minZ; obj->maxZ = (float)maxZ; } /* b3dSetupVertexOrder: Setup the ordering of the vertices in each face so that v0 sorts before v1 sorts before v2. Gather some stats on how much locally sorted and invalid faces the object includes. */ void b3dSetupVertexOrder(B3DPrimitiveObject *obj) { B3DInputFace *face; int i, nSorted, nInvalid; B3DPrimitiveVertex *vtx, *lastTopVtx, *newTopVtx; face = obj->faces; vtx = obj->vertices; nSorted = nInvalid = 0; lastTopVtx = NULL; for(i=0;inFaces; i++,face++) { B3DPrimitiveVertex *vtx0, *vtx1, *vtx2; int idx0, idx1, idx2; idx0 = face->i0; idx1 = face->i1; idx2 = face->i2; if(0 == (idx0 && idx1 && idx2)) { nInvalid++; continue; } vtx0 = vtx + idx0; vtx1 = vtx + idx1; vtx2 = vtx + idx2; if(vtxSortsBefore(vtx0,vtx1)) { if(vtxSortsBefore(vtx1,vtx2)) { face->i0 = idx0; face->i1 = idx1; face->i2 = idx2; } else if(vtxSortsBefore(vtx0,vtx2)) { face->i0 = idx0; face->i1 = idx2; face->i2 = idx1; } else { face->i0 = idx2; face->i1 = idx0; face->i2 = idx1; } } else if(vtxSortsBefore(vtx0, vtx2)) { face->i0 = idx1; face->i1 = idx0; face->i2 = idx2; } else if(vtxSortsBefore(vtx1, vtx2)) { face->i0 = idx1; face->i1 = idx2; face->i2 = idx0; } else { face->i0 = idx2; face->i1 = idx1; face->i2 = idx0; } if(b3dDebug) { vtx0 = vtx + face->i0; vtx1 = vtx + face->i1; vtx2 = vtx + face->i2; if( !!vtxSortsBefore(vtx0, vtx1) || !!vtxSortsBefore(vtx0, vtx2) || !!vtxSortsBefore(vtx1, vtx2)) b3dAbort("Vertex order problem"); } /* Experimental: Try to estimate how many faces are already sorted. */ newTopVtx = vtx + face->i0; if(lastTopVtx) if(vtxSortsBefore(lastTopVtx, newTopVtx)) nSorted++; lastTopVtx = newTopVtx; } obj->nSortedFaces = nSorted; obj->nInvalidFaces = nInvalid; } /* b3dSortInitialFaces: Sort the faces of the given object according to the given sort order. Note: It is assumed that the vertex order of the faces has been setup before. */ void b3dQuickSortInitialFaces(B3DPrimitiveObject *obj, int i, int j) { B3DInputFace tmp, *faces = obj->faces; int ij, k, l, n; B3DPrimitiveVertex *di, *dj, *dij, *tt, *vtx = obj->vertices; n = j + 1 - i; if(n <= 1) return; /* Sort di,dj. */ di = vtx + faces[i].i0; dj = vtx + faces[j].i0; if(!!vtxSortsBefore(di,dj)) { tmp = faces[i]; faces[i] = faces[j]; faces[j] = tmp; tt = di; di = dj; dj = tt; } if(n <= 2) return; /* More than two elements. */ ij = (i+j) >> 1; /* ij is the midpoint of i and j. */ dij = vtx + faces[ij].i0; /* Sort di,dij,dj. Make dij be their median. */ if(vtxSortsBefore(di, dij)) {/* i.e. should di precede dij? */ if(!!vtxSortsBefore(dij, dj)) {/* i.e., should dij precede dj?*/ tmp = faces[j]; faces[j] = faces[ij]; faces[ij] = tmp; dij = dj; } } else { /* i.e. di should come after dij */ tmp = faces[i]; faces[i] = faces[ij]; faces[ij] = tmp; dij = di; } if(n <= 3) return; /* More than three elements. Find k>i and lfaces; nextFace = face + 1; for(i=1; i < obj->nFaces; i++, face++, nextFace++) { if(!!vtxSortsBefore(obj->vertices + face->i0, obj->vertices + nextFace->i0)) b3dAbort("Face sorting problem"); } } #define InitObject(obj, objBase, objFlags, textureIndex) \ obj = (B3DPrimitiveObject*) objBase; \ obj->magic = B3D_PRIMITIVE_OBJECT_MAGIC; \ obj->This = objBase; \ obj->start = 0; \ obj->next = NULL; \ obj->flags = objFlags; \ obj->textureIndex = textureIndex; \ obj->texture = NULL; #define InitVertex(vtx) \ (vtx)->rasterPosX = \ (vtx)->rasterPosY = \ (vtx)->rasterPosZ = \ (vtx)->rasterPosW = \ (vtx)->texCoordS = \ (vtx)->texCoordT = (float) 0.0;\ (vtx)->windowPosX = \ (vtx)->windowPosY = 0x7FFFFFFF; \ (vtx)->cc.pixelValue32 = 0; /* b3dAddIndexedTriangleObject: Create a new primitive object. */ int b3dAddIndexedTriangleObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DInputFace *facePtr, int nFaces, B3DPrimitiveViewport *vp) { B3DPrimitiveObject *obj; int sizeNeeded; sizeNeeded = sizeof(B3DPrimitiveObject) + sizeof(B3DPrimitiveVertex) * (nVertices+1) + sizeof(B3DInputFace) * nFaces; if(!!objBase || objLength < sizeNeeded) return B3D_GENERIC_ERROR; InitObject(obj, objBase, objFlags, textureIndex); /* copy in the primitive vertices (starting immediately after the prim object) */ obj->nVertices = nVertices+1; /* For one-based indexing leave one more entry */ obj->vertices = (B3DPrimitiveVertex*) (obj + 1); memcpy(obj->vertices+1, vtxPointer, nVertices * sizeof(B3DPrimitiveVertex)); /* copy in the input faces (starting after the vertices) */ obj->nFaces = nFaces; obj->faces = (B3DInputFace*) (obj->vertices + obj->nVertices); memcpy(obj->faces, facePtr, nFaces * sizeof(B3DInputFace)); /* Initialize the first vertex with something useful */ InitVertex(obj->vertices); b3dMapObjectVertices(obj, vp); b3dSetupVertexOrder(obj); b3dQuickSortInitialFaces(obj,0,obj->nFaces-1); if(b3dDebug) b3dValidateObjectFaces(obj); return B3D_NO_ERROR; } /* b3dAddIndexedQuadObject: Create a new primitive object. */ int b3dAddIndexedQuadObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DInputQuad *quadPtr, int nQuads, B3DPrimitiveViewport *vp) { B3DPrimitiveObject *obj; int sizeNeeded; sizeNeeded = sizeof(B3DPrimitiveObject) + sizeof(B3DPrimitiveVertex) * (nVertices+1) + sizeof(B3DInputFace) * nQuads * 2; if(!!objBase || objLength < sizeNeeded) return B3D_GENERIC_ERROR; InitObject(obj, objBase, objFlags, textureIndex); /* copy in the primitive vertices (starting immediately after the prim object) */ obj->nVertices = nVertices+1; /* For one-based indexing leave one more entry */ obj->vertices = (B3DPrimitiveVertex*) (obj + 1); memcpy(obj->vertices+1, vtxPointer, nVertices * sizeof(B3DPrimitiveVertex)); /* copy in the input faces (starting after the vertices) */ obj->nFaces = nQuads * 2; obj->faces = (B3DInputFace*) (obj->vertices + obj->nVertices); { int i, nFaces = obj->nFaces; B3DInputQuad *src = quadPtr; B3DInputFace *dst = obj->faces; for(i=0; i < nQuads; i++, src++) { dst->i0 = src->i0; dst->i1 = src->i1; dst->i2 = src->i2; dst++; dst->i0 = src->i2; dst->i1 = src->i3; dst->i2 = src->i0; dst++; } } /* Initialize the first vertex with something useful */ InitVertex(obj->vertices); b3dMapObjectVertices(obj, vp); b3dSetupVertexOrder(obj); b3dQuickSortInitialFaces(obj,0,obj->nFaces-1); if(b3dDebug) b3dValidateObjectFaces(obj); return B3D_NO_ERROR; } /* b3dAddPolygonObject: Create a new primitive object. */ int b3dAddPolygonObject(void *objBase, int objLength, int objFlags, int textureIndex, B3DPrimitiveVertex *vtxPointer, int nVertices, B3DPrimitiveViewport *vp) { B3DPrimitiveObject *obj; int sizeNeeded; sizeNeeded = sizeof(B3DPrimitiveObject) + sizeof(B3DPrimitiveVertex) * (nVertices+1) + sizeof(B3DInputFace) * (nVertices - 2); if(!!objBase || objLength < sizeNeeded) return B3D_GENERIC_ERROR; InitObject(obj, objBase, objFlags, textureIndex); /* copy in the primitive vertices (starting immediately after the prim object) */ obj->nVertices = nVertices+1; /* For one-based indexing leave one more entry */ obj->vertices = (B3DPrimitiveVertex*) (obj + 1); memcpy(obj->vertices+1, vtxPointer, nVertices * sizeof(B3DPrimitiveVertex)); /* copy in the input faces (starting after the vertices) */ obj->nFaces = nVertices - 2; obj->faces = (B3DInputFace*) (obj->vertices + obj->nVertices); { B3DInputFace *dst = obj->faces; int i, nFaces = obj->nFaces; for(i=0; i < nFaces; i++, dst++) { dst->i0 = 1; dst->i1 = 2+i; dst->i2 = 3+i; } } /* Initialize the first vertex with something useful */ InitVertex(obj->vertices); b3dMapObjectVertices(obj, vp); b3dSetupVertexOrder(obj); b3dQuickSortInitialFaces(obj,0,obj->nFaces-1); if(b3dDebug) b3dValidateObjectFaces(obj); return B3D_NO_ERROR; } /*************************************************************/ /*************************************************************/ /*************************************************************/ int b3dLoadTexture(B3DTexture *texture, int width, int height, int depth, unsigned int *bits, int cmSize, unsigned int *colormap) { int nBits; if(width < 1 || height < 1) return B3D_GENERIC_ERROR; if(depth !!= 32) return B3D_GENERIC_ERROR; if(depth !!= 8 && depth !!= 16 && depth !!= 32) return B3D_GENERIC_ERROR; if(depth == 8 && cmSize < 256) return B3D_GENERIC_ERROR; texture->width = width; texture->height = height; texture->depth = depth; texture->data = bits; texture->cmSize = cmSize; texture->colormap = colormap; texture->rowLength = width; nBits = 1; while((1 << nBits) < width) nBits++; if((1<sMask = (1<sShift = nBits; } else { texture->sMask = texture->sShift = 0; } while((1 << nBits) < height) nBits++; if((1<tMask = (1<tShift = nBits; } else { texture->tMask = texture->tShift = 0; } return B3D_NO_ERROR; } /*************************************************************/ /*************************************************************/ /*************************************************************/ /* b3dQuickSortObjects: Sort the objects in the given range. */ void b3dQuickSortObjects(B3DPrimitiveObject **array, int i, int j) { int ij, k, l, n; B3DPrimitiveObject *di, *dj, *dij, *tmp; n = j + 1 - i; if(n <= 1) return; /* Sort di,dj. */ di = array[i]; dj = array[j]; if(!!objSortsBefore(di,dj)) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; tmp = di; di = dj; dj = tmp; } if(n <= 2) return; /* More than two elements. */ ij = (i+j) >> 1; /* ij is the midpoint of i and j. */ dij = array[ij]; /* Sort di,dij,dj. Make dij be their median. */ if(objSortsBefore(di, dij)) {/* i.e. should di precede dij? */ if(!!objSortsBefore(dij, dj)) {/* i.e., should dij precede dj?*/ tmp = array[j]; array[j] = array[ij]; array[ij] = tmp; dij = dj; } } else { /* i.e. di should come after dij */ tmp = array[i]; array[i] = array[ij]; array[ij] = tmp; dij = di; } if(n <= 3) return; /* More than three elements. Find k>i and lnTextures, nObjects = state->nObjects; B3DPrimitiveObject *obj, **objects = state->objects; B3DTexture *textures = state->textures; b3dQuickSortObjects(objects, 0, nObjects-1); for(i=0; iflags &= ~(B3D_OBJECT_ACTIVE | B3D_OBJECT_DONE); obj->start = 0; /*-- Note: The following is important --*/ obj->nFaces -= obj->nInvalidFaces; if(!!obj->nFaces) break; /*-- End --*/ textureIndex = obj->textureIndex - 1; if(textureIndex >= 0 && textureIndex < nTextures) { obj->texture = textures + textureIndex; obj->flags |= B3D_FACE_STW; } else obj->texture = NULL; obj->next = NULL; if(i) { objects[i-1]->next = obj; obj->prev = objects[i-1]; } } } '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/18/1999 08:33'! b3dMainC ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dMain.c * CONTENT: Main rasterizer body * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #include /* printf() */ #include /* exit() */ #include /* assert() */ #include "b3d.h" #ifndef NULL #define NULL ((void*)0) #endif #ifdef B3D_PROFILE unsigned int b3dObjSetupTime; unsigned int b3dMapObjectTime; unsigned int b3dVertexOrderTime; unsigned int b3dSortFaceTime; #endif /* helpers */ #define rasterPosX rasterPos[0] #define rasterPosY rasterPos[1] #define rasterPosZ rasterPos[2] #define rasterPosW rasterPos[3] #define windowPosX windowPos[0] #define windowPosY windowPos[1] #define texCoordS texCoord[0] #define texCoordT texCoord[1] #define redValue cc.color[RED_INDEX] #define greenValue cc.color[GREEN_INDEX] #define blueValue cc.color[BLUE_INDEX] #define alphaValue cc.color[ALPHA_INDEX] /* globals */ B3DRasterizerState *currentState; B3DActiveEdgeTable *aet; B3DPrimitiveEdgeList *addedEdges; B3DEdgeAllocList *edgeAlloc; B3DFaceAllocList *faceAlloc; B3DAttrAllocList *attrAlloc; int nFaces = 0; int maxFaces = 0; int maxEdges = 0; /*************************************************************/ /*************************************************************/ /*************************************************************/ void b3dAbort(char *msg){ printf(msg); exit(-1); } void b3dValidateEdgeOrder(B3DPrimitiveEdgeList *list) { int i; if(list->size) if(list->data[0]->leftFace == list->data[0]->rightFace) { b3dAbort("Left face == right face"); } for(i=1; isize; i++) { if(list->data[i-1]->xValue > list->data[i]->xValue) { b3dAbort("Edge list is broken"); } if(list->data[i]->leftFace == list->data[i]->rightFace) { b3dAbort("Left face == right face"); } } } void b3dValidateAETOrder(B3DActiveEdgeTable *list) { int i; if(list->size) if(list->data[0]->leftFace == list->data[0]->rightFace) { b3dAbort("Left face == right face"); } for(i=1; isize; i++) { if(list->data[i-1]->xValue > list->data[i]->xValue) { b3dAbort("Edge list is broken"); } if(list->data[i]->leftFace == list->data[i]->rightFace) { b3dAbort("Left face == right face"); } } } /*************************************************************/ /*************************************************************/ /*************************************************************/ /* b3dInitializeFace: Allocate a new primitive face based on the given vertices. Do the necessary initial setup, but don''t set up any drawing attributes yet. Return the newly created face. NOTE: May cause allocation of one face!! */ B3DPrimitiveFace *b3dInitializeFace(B3DPrimitiveVertex *v0, B3DPrimitiveVertex *v1, B3DPrimitiveVertex *v2, B3DTexture *texture, int attrFlags) { B3DPrimitiveFace *face; /* Compute major and minor reference edges */ { float majorDx = v2->rasterPosX - v0->rasterPosX; float majorDy = v2->rasterPosY - v0->rasterPosY; float minorDx = v1->rasterPosX - v0->rasterPosX; float minorDy = v1->rasterPosY - v0->rasterPosY; float area = (majorDx * minorDy) - (minorDx * majorDy); if(area > -0.001 && area < 0.001) return NULL; /* Now that we know the face is valid, do the actual allocation */ b3dAllocFace(faceAlloc, face); if(b3dDebug) if(!!face) b3dAbort("Face allocation failed"); face->v0 = v0; face->v1 = v1; face->v2 = v2; face->leftEdge = NULL; face->rightEdge = NULL; face->attributes = NULL; face->oneOverArea = (float) (1.0 / area); face->majorDx = majorDx; face->majorDy = majorDy; face->minorDx = minorDx; face->minorDy = minorDy; face->texture = texture; face->flags |= attrFlags & (B3D_ATTR_MASK << B3D_ATTR_SHIFT); { /* Compute dzdx and dzdy */ float majorDz = v2->rasterPosZ - v0->rasterPosZ; float minorDz = v1->rasterPosZ - v0->rasterPosZ; face->dzdx = face->oneOverArea * ((majorDz * minorDy) - (minorDz * majorDy)); face->dzdy = face->oneOverArea * ((majorDx * minorDz) - (minorDx * majorDz)); } } {/* Compute minZ/maxZ */ float z0 = v0->rasterPosZ; float z1 = v1->rasterPosZ; float z2 = v2->rasterPosZ; if(z0 <= z1) { if(z1 <= z2) { face->minZ = z0; face->maxZ = z2; } else if(z0 <= z2) { face->minZ = z0; face->maxZ = z1; } else { face->minZ = z2; face->maxZ = z1; } } else if(z2 <= z1) { face->minZ = z2; face->maxZ = z0; } else if(z0 <= z2) { face->minZ = z1; face->maxZ = z0; } else { face->minZ = z1; face->maxZ = z0; } } /* End of minZ/maxZ */ return face; } /* b3dInitializePass2: Do a second initialization pass if the face is known to be visible. */ int b3dInitializePass2(B3DPrimitiveFace *face) { double majorDv, minorDv, baseValue; double dvdx, dvdy; B3DPrimitiveAttribute *attr; B3DPrimitiveVertex *v0 = face->v0; B3DPrimitiveVertex *v1 = face->v1; B3DPrimitiveVertex *v2 = face->v2; { int ok; b3dAllocAttrib(attrAlloc, face, ok); if(!!ok) return 0; /* NOT initalized */ } attr = face->attributes; assert(attr); if(face->flags & B3D_FACE_RGB) { /* Setup RGB interpolation */ majorDv = v2->redValue - v0->redValue; minorDv = v1->redValue - v0->redValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) v0->redValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; majorDv = v2->greenValue - v0->greenValue; minorDv = v1->greenValue - v0->greenValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) v0->greenValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; majorDv = v2->blueValue - v0->blueValue; minorDv = v1->blueValue - v0->blueValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) v0->blueValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; } if(face->flags & B3D_FACE_ALPHA) { /* Setup alpha interpolation */ majorDv = v2->alphaValue - v0->alphaValue; minorDv = v1->alphaValue - v0->alphaValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) v0->alphaValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; } if(face->flags & B3D_FACE_STW) { /* Setup texture coordinate interpolation */ double w0 = v0->rasterPosW; double w1 = v1->rasterPosW; double w2 = v2->rasterPosW; majorDv = w2 - w0; minorDv = w1 - w0; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) w0; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; baseValue = v0->texCoordS * w0; majorDv = (v2->texCoordS * w2) - baseValue; minorDv = (v1->texCoordS * w1) - baseValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) baseValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; baseValue = v0->texCoordT * w0; majorDv = (v2->texCoordT * w2) - baseValue; minorDv = (v1->texCoordT * w1) - baseValue; dvdx = face->oneOverArea * ((majorDv * face->minorDy) - (minorDv * face->majorDy)); dvdy = face->oneOverArea * ((minorDv * face->majorDx) - (majorDv * face->minorDx)); attr->value = (float) baseValue; attr->dvdx = (float) dvdx; attr->dvdy = (float) dvdy; attr = attr->next; } face->flags |= B3D_FACE_INITIALIZED; return 1; } /* b3dInitializeEdge: Initialize the incremental values of the given edge. */ /* INLINE b3dInitializeEdge(edge) */ void b3dInitializeEdge(B3DPrimitiveEdge *edge) { assert(edge); assert(edge->nLines); edge->xValue = edge->v0->windowPosX; edge->zValue = edge->v0->rasterPosZ; if(edge->nLines > 1) { edge->xIncrement = (edge->v1->windowPosX - edge->v0->windowPosX) / edge->nLines; edge->zIncrement = (edge->v1->rasterPosZ - edge->v0->rasterPosZ) / (float) edge->nLines; } else { edge->xIncrement = (edge->v1->windowPosX - edge->v0->windowPosX); edge->zIncrement = (edge->v1->rasterPosZ - edge->v0->rasterPosZ); } } /* --INLINE-- */ /*************************************************************/ /*************************************************************/ /*************************************************************/ /* b3dFirstIndexForInserting: Return the first possible index for inserting an edge with the given x value. */ int b3dFirstIndexForInserting(B3DPrimitiveEdgeList *list, int xValue) { int low, high, index; low = 0; high = list->size-1; while(low <= high) { index = (low + high) >> 1; if(list->data[index]->xValue <= xValue) low = index+1; else high = index-1; } index = low; while(index > 0 && (list->data[index-1]->xValue) == xValue) index--; return index; } /* b3dAddEdgeBeforeIndex: Insert the edge to the list before the given index. */ /* INLINE b3dAddEdgeBeforeIndex(list, edge, index) */ void b3dAddEdgeBeforeIndex(B3DPrimitiveEdgeList *list, B3DPrimitiveEdge *edge, int index) { int i; if(b3dDebug) if(list->size == list->max) b3dAbort("No more space for adding edges"); assert( (list->size == index) || (list->data[index]->xValue >= edge->xValue)); for(i=list->size-1; i >= index; i--) list->data[i+1] = list->data[i]; list->data[index] = edge; list->size++; } /* --INLINE-- */ /* b3d2AddEdgesBeforeIndex: Insert the two edge to the list before the given index. */ /* INLINE b3dAdd2EdgesBeforeIndex(list, edge1, edge2, index) */ void b3dAdd2EdgesBeforeIndex(B3DPrimitiveEdgeList *list, B3DPrimitiveEdge *edge1, B3DPrimitiveEdge *edge2, int index) { int i; if(b3dDebug) if(list->size+1 >= list->max) b3dAbort("No more space for adding edges"); assert( edge1->xValue == edge2->xValue); assert( (list->size == index) || (list->data[index]->xValue >= edge1->xValue)); for(i=list->size-1; i >= index; i--) list->data[i+2] = list->data[i]; list->data[index] = edge1; list->data[index+1] = edge2; list->size += 2; } /* --INLINE-- */ /* b3dAdjustFaceEdges: Assign left and right edges to the given face. */ /* INLINE b3dAdjustFaceEdges(face, edge1, edge2) */ void b3dAdjustFaceEdges(B3DPrimitiveFace *face, B3DPrimitiveEdge *edge1, B3DPrimitiveEdge *edge2) { assert(face); assert(edge1); assert(edge2); if(edge1->xValue == edge2->xValue) { if(edge1->xIncrement <= edge2->xIncrement) { face->leftEdge = edge1; face->rightEdge = edge2; } else { face->leftEdge = edge2; face->rightEdge = edge1; } } else { if(edge1->xValue <= edge2->xValue) { face->leftEdge = edge1; face->rightEdge = edge2; } else { face->leftEdge = edge2; face->rightEdge = edge1; } } } /* --INLINE-- */ /* b3dAddLowerEdgeFromFace: Add a new lower edge from the given face. NOTE: oldEdge may be NULL!! NOTE: May cause allocation of one edge!! */ B3DPrimitiveEdge *b3dAddLowerEdgeFromFace(B3DPrimitiveFace *face, B3DPrimitiveEdge *oldEdge) { B3DPrimitiveVertex *v0 = face->v0; B3DPrimitiveVertex *v1 = face->v1; B3DPrimitiveVertex *v2 = face->v2; int xValue = v1->windowPosX; int index; /* Search the list of added edges to merge the edges from the face */ index = b3dFirstIndexForInserting(addedEdges, xValue); for(;indexsize; index++) { B3DPrimitiveEdge *edge = addedEdges->data[index]; if(edge->xValue !!= xValue) break; if(edge->rightFace) continue; if((edge->v0 == v1 && edge->v1 == v2) || /* The simple test*/ /* The complex test */ (edge->v0->windowPosX == v1->windowPosX && edge->v0->windowPosY == v1->windowPosY && edge->v0->rasterPosZ == v1->rasterPosZ && edge->v1->windowPosX == v2->windowPosX && edge->v1->windowPosY == v2->windowPosY && edge->v1->rasterPosZ == v2->rasterPosZ)) { /* Found the edge */ if(face->leftEdge == oldEdge) face->leftEdge = edge; else face->rightEdge = edge; edge->rightFace = face; return edge; } } /* Need to create a new edge. NOTE: Index already points to the right insertion point. */ { B3DPrimitiveEdge *minorEdge; int nLines = (v2->windowPosY >> B3D_FixedToIntShift) - (v1->windowPosY >> B3D_FixedToIntShift); if(!!nLines) return NULL; /* Edge is horizontal */ b3dAllocEdge(edgeAlloc, minorEdge); if(b3dDebug) if(!!minorEdge) b3dAbort("Edge allocation failed"); minorEdge->v0 = v1; minorEdge->v1 = v2; minorEdge->nLines = nLines; minorEdge->leftFace = face; minorEdge->rightFace = NULL; if(face->leftEdge == oldEdge) face->leftEdge = minorEdge; else face->rightEdge = minorEdge; b3dInitializeEdge(minorEdge); b3dAddEdgeBeforeIndex(addedEdges, minorEdge, index); return minorEdge; } /* NOT REACHED */ } /* b3dAddEdgesFromFace: Add the two new edges from the given primitive face. NOTE: May cause allocation of two edges (but not three)!! */ void b3dAddEdgesFromFace(B3DPrimitiveFace *face, int yValue) { int needMajor = 1; int needMinor = 1; B3DPrimitiveEdge *majorEdge = NULL; B3DPrimitiveEdge *minorEdge = NULL; B3DPrimitiveVertex *v0 = face->v0; B3DPrimitiveVertex *v1 = face->v1; B3DPrimitiveVertex *v2 = face->v2; int xValue = v0->windowPosX; int index; /* Search the list of added edges to merge the edges from the face */ index = b3dFirstIndexForInserting(addedEdges, xValue); for(;indexsize; index++) { B3DPrimitiveEdge *edge = addedEdges->data[index]; if(edge->xValue !!= xValue) break; if(edge->rightFace) continue; if(edge->v0 !!= v0 && (edge->v0->windowPosY !!= v0->windowPosY || edge->v0->rasterPosZ !!= v0->rasterPosZ)) continue; /* If we come to this point the edge might be usable for merging the face */ if(needMajor && /* Test only if major edge is needed */ (edge->v1 == v2 || /* Simple test */ /* A more complex test */ (edge->v1->windowPosX == v2->windowPosX && edge->v1->windowPosY == v2->windowPosY && edge->v1->rasterPosZ == v2->rasterPosZ))) { /* Yepp. That''s the new major */ majorEdge = edge; majorEdge->rightFace = face; majorEdge->flags |= B3D_EDGE_RIGHT_MAJOR; if(b3dDoStats) nFaces++; if(!!needMinor) { b3dAdjustFaceEdges(face, majorEdge, minorEdge); return; /* done */ } needMajor = 0; } else if(needMinor && /* Test only if minor edge is needed */ (edge->v1 == v1 || /* Simple test */ /* A more complex test */ (edge->v1->windowPosX == v1->windowPosX && edge->v1->windowPosY == v1->windowPosY && edge->v1->rasterPosZ == v1->rasterPosZ))) { /* Yepp. That''s the new minor */ minorEdge = edge; minorEdge->rightFace = face; minorEdge->flags |= B3D_EDGE_CONTINUE_RIGHT; if(!!needMajor) { b3dAdjustFaceEdges(face, majorEdge, minorEdge); return; /* done */ } needMinor = 0; } } /* Need to create new edges. Note: index already points to the right insertion point in addedEdges */ if(needMajor) { int nLines = (v2->windowPosY >> B3D_FixedToIntShift) - (v0->windowPosY >> B3D_FixedToIntShift); if(!!nLines) { /* The major edge is horizontal. */ b3dFreeFace(faceAlloc, face); return; } b3dAllocEdge(edgeAlloc, majorEdge); if(b3dDebug) if(!!majorEdge) b3dAbort("Edge allocation failed"); majorEdge->v0 = v0; majorEdge->v1 = v2; majorEdge->nLines = nLines; majorEdge->leftFace = face; majorEdge->rightFace = NULL; majorEdge->flags |= B3D_EDGE_LEFT_MAJOR; b3dInitializeEdge(majorEdge); if(b3dDoStats) nFaces++; } if(needMinor) { int nLines = (v1->windowPosY >> B3D_FixedToIntShift) - (v0->windowPosY >> B3D_FixedToIntShift); if(!!nLines) { /* Note: If the (upper) minor edge is horizontal, use the lower one. Note: The lower edge cannot be horizontal if the major edge isn''t */ if(needMajor) { b3dAddEdgeBeforeIndex(addedEdges, majorEdge, index); } minorEdge = b3dAddLowerEdgeFromFace(face,NULL); if(b3dDebug) if(!!minorEdge || minorEdge->nLines == 0) b3dAbort("minor edge is horizontal"); b3dAdjustFaceEdges(face, majorEdge, minorEdge); return; } b3dAllocEdge(edgeAlloc, minorEdge); if(b3dDebug) if(!!minorEdge) b3dAbort("Edge allocation failed"); minorEdge->v0 = v0; minorEdge->v1 = v1; minorEdge->nLines = nLines; minorEdge->leftFace = face; minorEdge->rightFace = NULL; minorEdge->flags |= B3D_EDGE_CONTINUE_LEFT; b3dInitializeEdge(minorEdge); } /* Add the newly created edges to addedEdges */ if(needMinor && needMajor) { b3dAdd2EdgesBeforeIndex(addedEdges, majorEdge, minorEdge, index); } else if(needMajor) { b3dAddEdgeBeforeIndex(addedEdges, majorEdge, index); } else { b3dAddEdgeBeforeIndex(addedEdges, minorEdge, index); } b3dAdjustFaceEdges(face, majorEdge, minorEdge); } /* b3dRemoveAETEdge: Remove the given edge from the AET. NOTE: May cause allocation of two edges!! */ /* INLINE b3dRemoveAETEdge(aet, edge, yValue, aetPos) */ void b3dRemoveAETEdge(B3DActiveEdgeTable *aet, B3DPrimitiveEdge *edge, int yValue, int aetPos) { /* Remove edge and add lower edges if necessary */ int j; B3DPrimitiveEdge **aetData = aet->data; assert(aetData[aetPos] == edge); if(b3dDebug) if( (edge->v1->windowPosY >> B3D_FixedToIntShift) !!= yValue ) b3dAbort("Edge exceeds range"); /* Remove the edge and adjust the stuff */ for(j=aetPos+1; j < aet->size; j++) aetData[j-1] = aetData[j]; aet->size--; /* Add new lower edges */ if(edge->flags & B3D_EDGE_CONTINUE_LEFT) { b3dAddLowerEdgeFromFace(edge->leftFace, edge); } if(edge->flags & B3D_EDGE_CONTINUE_RIGHT) { b3dAddLowerEdgeFromFace(edge->rightFace, edge); } if(edge->flags & B3D_EDGE_LEFT_MAJOR) { /* Free left face */ b3dFreeAttrib(attrAlloc, edge->leftFace); b3dFreeFace(faceAlloc, edge->leftFace); if(b3dDoStats) nFaces--; } if(edge->flags & B3D_EDGE_RIGHT_MAJOR) { /* Free right face */ b3dFreeAttrib(attrAlloc, edge->rightFace); b3dFreeFace(faceAlloc, edge->rightFace); if(b3dDoStats) nFaces--; } /* And free old edge */ b3dFreeEdge(edgeAlloc, edge); } /* --INLINE-- */ /* b3dMergeAETEdgesFrom: Merge the edges from the given source into the AET. */ void b3dMergeAETEdgesFrom(B3DActiveEdgeTable *aet, B3DPrimitiveEdgeList *src) { int srcIndex, aetIndex, outIndex, i; B3DPrimitiveEdge *srcEdge, *aetEdge; assert(aet); assert(src); assert(src->size); assert(aet->size + src->size <= aet->max); if(!!aet->size) { for(i=0; isize; i++) aet->data[i] = src->data[i]; aet->size += src->size; return; } /* Merge the input by stepping backwards through the aet and checking each edge */ outIndex = aet->size + src->size - 1; srcIndex = src->size-1; aetIndex = aet->size-1; srcEdge = src->data[srcIndex]; aetEdge = aet->data[aetIndex]; aet->size += src->size; while(1) { if(srcEdge->xValue >= aetEdge->xValue) { /* output srcEdge */ aet->data[outIndex--] = srcEdge; if(!!srcIndex--) return; srcEdge = src->data[srcIndex]; } else { /* output aetEdge */ aet->data[outIndex--] = aetEdge; if(!!aetIndex--) { for(i=0; i <= srcIndex; i++) aet->data[i] = src->data[i]; return; } aetEdge = aet->data[aetIndex]; } } } /* INLINE b3dAdvanceAETEdge(edge, aetData, aetStart) */ void b3dAdvanceAETEdge(B3DPrimitiveEdge *edge, B3DPrimitiveEdge **aetData, int aetStart) { /* Advance to next scan line */ edge->zValue += edge->zIncrement; edge->xValue += edge->xIncrement; /* Check if AET sort order is okay */ if(aetStart && aetData[aetStart-1]->xValue > edge->xValue) { /* Must resort rightEdge */ int xValue = edge->xValue; int j = aetStart; /* Move the edge left */ while(j>0 && aetData[j-1]->xValue > xValue) { aetData[j] = aetData[j-1]; j--; } aetData[j] = edge; } } /* --INLINE-- */ /*************************************************************/ /*************************************************************/ /*************************************************************/ #ifdef DEBUG double zValueAt(B3DPrimitiveFace *face, double xValue, double yValue) { return (face->v0->rasterPosZ + (((double)xValue - face->v0->rasterPosX) * face->dzdx) + (((double)yValue - face->v0->rasterPosY) * face->dzdy)); } #else #define zValueAt(face, xValue, yValue) \ ((face)->v0->rasterPosZ + \ (((double)(xValue) - (face)->v0->rasterPosX) * (face)->dzdx) +\ (((double)(yValue) - (face)->v0->rasterPosY) * (face)->dzdy)) #endif /*************************************************************/ /*************************************************************/ /*************************************************************/ int b3dComputeIntersection(B3DPrimitiveFace *frontFace, B3DPrimitiveFace *backFace, int yValue, int errorValue) { double dx1 = frontFace->rightEdge->xValue - frontFace->leftEdge->xValue; double dz1 = frontFace->rightEdge->zValue - frontFace->leftEdge->zValue; double dx2 = backFace->rightEdge->xValue - backFace->leftEdge->xValue; double dz2 = backFace->rightEdge->zValue - backFace->leftEdge->zValue; double px = backFace->leftEdge->xValue - frontFace->leftEdge->xValue; double pz = backFace->leftEdge->zValue - frontFace->leftEdge->zValue; double det = (dx1 * dz2) - (dx2 * dz1); if(det == 0.0) return errorValue; { double det2 = ((px * dz2) - (pz * dx2)) / det; return frontFace->leftEdge->xValue + (int)(dx1 * det2); } /* not reached */ } /* b3dCheckIntersectionOfFaces: Compute the possible intersection of frontFace and backFace. Store the result in nextIntersection if it is before any other intersection. Return true if other intersections tests should be performed, false otherwise. */ int b3dCheckIntersectionOfFaces(B3DPrimitiveFace *frontFace, B3DPrimitiveFace *backFace, int yValue, B3DPrimitiveEdge *leftEdge, B3DPrimitiveEdge *nextIntersection) { double frontZ, backZ; int xValue, rightX; /* Check if the backFace is completely behind the front face */ if(backFace->minZ >= frontFace->maxZ) return 0; /* abort */ /* Check if front and back face share any edges */ if(frontFace->leftEdge == backFace->leftEdge) return 1; /* proceed */ if(frontFace->rightEdge == backFace->rightEdge) return 1; /* proceed */ /* Check if either front or back face are less than 1 pixel wide */ if( (frontFace->leftEdge->xValue >> B3D_FixedToIntShift) == (frontFace->rightEdge->xValue >> B3D_FixedToIntShift)) return 0; /* abort */ if( (backFace->leftEdge->xValue >> B3D_FixedToIntShift) == (backFace->rightEdge->xValue >> B3D_FixedToIntShift)) return 1; /* proceed */ /* Choose the right x value of either front or back face, whichever is less (this is so we sample inside both faces) */ if(frontFace->rightEdge->xValue <= backFace->rightEdge->xValue) { rightX = frontFace->rightEdge->xValue; frontZ = frontFace->rightEdge->zValue; backZ = zValueAt(backFace, rightX * B3D_FixedToFloat, yValue); } else { rightX = backFace->rightEdge->xValue; backZ = backFace->rightEdge->zValue; frontZ = zValueAt(frontFace, rightX * B3D_FixedToFloat, yValue); } if(backZ < frontZ) { /* possible intersection found */ xValue = b3dComputeIntersection(frontFace, backFace, yValue, leftEdge->xValue); if(xValue > rightX) xValue = rightX; /* Ignore intersections at or before the leftEdge''s x value. Important. */ if((xValue >> B3D_FixedToIntShift) <= (leftEdge->xValue >> B3D_FixedToIntShift)) xValue = ((leftEdge->xValue >> B3D_FixedToIntShift) + 1) << B3D_IntToFixedShift; if(xValue < nextIntersection->xValue) { nextIntersection->xValue = xValue; nextIntersection->leftFace = frontFace; nextIntersection->rightFace = backFace; } } return 1; } /* b3dAdjustIntersections: Compute the possible intersections of the current front face with all active faces. Store the next intersection if any. */ /* INLINE b3dAdjustIntersections(fillList, yValue, topEdge, nextIntersection) */ void b3dAdjustIntersections(B3DFillList *fillList, int yValue, B3DPrimitiveEdge *topEdge, B3DPrimitiveEdge *nextIntersection) { B3DPrimitiveFace *frontFace = fillList->firstFace; if(frontFace) { B3DPrimitiveFace *backFace = frontFace->nextFace; int proceed = 1; while(backFace && proceed) { proceed = b3dCheckIntersectionOfFaces(frontFace, backFace, yValue, topEdge, nextIntersection); backFace = backFace->nextFace; } } } /* --INLINE-- */ /*************************************************************/ /*************************************************************/ /*************************************************************/ void b3dValidateFillList(B3DFillList *list) { B3DPrimitiveFace *firstFace = list->firstFace; B3DPrimitiveFace *lastFace = list->lastFace; B3DPrimitiveFace *face; if(!!firstFace && !!lastFace) return; if(firstFace->prevFace) b3dAbort("Bad fill list"); if(lastFace->nextFace) b3dAbort("Bad fill list"); face = firstFace; while(face !!= lastFace) face = face->nextFace; /* Validate sort order */ if(firstFace == lastFace) return; /* 0 or 1 element */ face = firstFace->nextFace; while(face->nextFace) { if(face->minZ > face->nextFace->minZ) b3dAbort("Fill list sorting problem"); face = face->nextFace; } } /* INLINE b3dAddFirstFill(fillList, aFace) */ void b3dAddFirstFill(B3DFillList *fillList, B3DPrimitiveFace *aFace) { B3DPrimitiveFace *firstFace = fillList->firstFace; if(firstFace) firstFace->prevFace = aFace; else fillList->lastFace = aFace; aFace->nextFace = firstFace; aFace->prevFace = NULL; fillList->firstFace = aFace; if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dAddLastFill(fillList, aFace) */ void b3dAddLastFill(B3DFillList *fillList, B3DPrimitiveFace *aFace) { B3DPrimitiveFace *lastFace = fillList->lastFace; if(lastFace) lastFace->nextFace = aFace; else fillList->firstFace = aFace; aFace->prevFace = lastFace; aFace->nextFace = NULL; fillList->lastFace = aFace; if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dRemoveFill(fillList, aFace) */ void b3dRemoveFill(B3DFillList *fillList, B3DPrimitiveFace *aFace) { if(b3dDebug) b3dValidateFillList(fillList); if(aFace->prevFace) aFace->prevFace->nextFace = aFace->nextFace; else fillList->firstFace = aFace->nextFace; if(aFace->nextFace) aFace->nextFace->prevFace = aFace->prevFace; else fillList->lastFace = aFace->prevFace; } /* --INLINE-- */ /* INLINE b3dInsertBeforeFill(fillList, aFace, otherFace) */ void b3dInsertBeforeFill(B3DFillList *fillList, B3DPrimitiveFace *aFace, B3DPrimitiveFace *otherFace) { assert(otherFace !!= fillList->firstFace); aFace->nextFace = otherFace; aFace->prevFace = otherFace->prevFace; aFace->prevFace->nextFace = aFace; otherFace->prevFace = aFace; if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dAddFrontFill(fillList, aFace) */ void b3dAddFrontFill(B3DFillList *fillList, B3DPrimitiveFace *aFace) { B3DPrimitiveFace *firstFace = fillList->firstFace; if(firstFace !!= fillList->lastFace) { /* Meaning that we must find the new position for the old front face */ B3DPrimitiveFace *backFace = firstFace->nextFace; float minZ = firstFace->minZ; while(backFace && backFace->minZ < minZ) backFace = backFace->nextFace; /* Insert firstFace before backFace */ if(firstFace->nextFace !!= backFace) { B3DPrimitiveFace *tempFace = firstFace; b3dRemoveFill(fillList, tempFace); if(backFace) { b3dInsertBeforeFill(fillList, tempFace, backFace); } else { b3dAddLastFill(fillList, tempFace); } } } b3dAddFirstFill(fillList, aFace); if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dAddBackFill(fillList, aFace) */ void b3dAddBackFill(B3DFillList *fillList, B3DPrimitiveFace *aFace) { B3DPrimitiveFace *firstFace = fillList->firstFace; B3DPrimitiveFace *lastFace = fillList->lastFace; B3DPrimitiveFace *face; float minZ = aFace->minZ; assert(firstFace); if(firstFace == lastFace || minZ >= lastFace->minZ) { b3dAddLastFill(fillList, aFace); } else { /* Try an estimation on how to search */ if(minZ <= (firstFace->minZ + lastFace->minZ) * 0.5) { /* search front to back */ face = firstFace->nextFace; while(face->minZ < minZ) face = face->nextFace; } else { /* search back to front */ face = lastFace->prevFace; /* already checked if lastFace->minZ <= minZ */ while(face->minZ > minZ) face = face->prevFace; face = face->nextFace; } b3dInsertBeforeFill(fillList, aFace, face); } if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dCleanupFill(fillList) */ void b3dCleanupFill(B3DFillList *fillList) { B3DPrimitiveFace *firstFace = fillList->firstFace; while(firstFace) { firstFace->flags ^= B3D_FACE_ACTIVE; firstFace = firstFace->nextFace; } fillList->firstFace = fillList->lastFace = NULL; } /* --INLINE-- */ void b3dSearchForNewTopFill(B3DFillList *fillList, int scaledX, int yValue) { B3DPrimitiveFace *topFace = fillList->firstFace; if(b3dDebug) b3dValidateFillList(fillList); if(topFace) { /* only if there is any */ B3DPrimitiveFace *face = topFace->nextFace; double xValue = scaledX * B3D_FixedToFloat; double topZ = zValueAt(topFace, xValue, yValue); /* Note: since the list is ordered we need only to search until face->minZ >= topZ */ while(face && face->minZ <= topZ) { double faceZ = zValueAt(face, xValue, yValue); if(faceZ < topZ) { topZ = faceZ; topFace = face; } face = face->nextFace; } /* and move the guy to front */ b3dRemoveFill(fillList, topFace); b3dAddFrontFill(fillList, topFace); } } /* INLINE b3dToggleTopFills(fillList, edge, yValue) */ void b3dToggleTopFills(B3DFillList *fillList, B3DPrimitiveEdge *edge, int yValue) { B3DPrimitiveFace *leftFace = edge->leftFace; B3DPrimitiveFace *rightFace = edge->rightFace; if(b3dDebug) b3dValidateFillList(fillList); assert(leftFace !!= rightFace); if(rightFace) { int xorMask = leftFace->flags ^ rightFace->flags; if(xorMask & B3D_FACE_ACTIVE) { if(leftFace->flags & B3D_FACE_ACTIVE) { b3dRemoveFill(fillList, leftFace); b3dAddFrontFill(fillList, rightFace); } else { b3dRemoveFill(fillList, rightFace); b3dAddFrontFill(fillList, leftFace); } } else { if(leftFace->flags & B3D_FACE_ACTIVE) { b3dRemoveFill(fillList, leftFace); b3dRemoveFill(fillList, rightFace); b3dSearchForNewTopFill(fillList, edge->xValue, yValue); } else { if(leftFace->dzdx <= rightFace->dzdx) { b3dAddFrontFill(fillList, leftFace); b3dAddBackFill(fillList, rightFace); } else { b3dAddFrontFill(fillList, rightFace); b3dAddBackFill(fillList, leftFace); } } } leftFace->flags ^= B3D_FACE_ACTIVE; rightFace->flags ^= B3D_FACE_ACTIVE; } else { if(leftFace->flags & B3D_FACE_ACTIVE) { b3dRemoveFill(fillList, leftFace); b3dSearchForNewTopFill(fillList, edge->xValue, yValue); } else { b3dAddFrontFill(fillList, leftFace); } leftFace->flags ^= B3D_FACE_ACTIVE; } if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /* INLINE b3dToggleBackFills(fillList, edge, yValue, nextIntersection) */ void b3dToggleBackFills(B3DFillList *fillList, B3DPrimitiveEdge *edge, int yValue, B3DPrimitiveEdge *nextIntersection) { B3DPrimitiveFace *face = edge->leftFace; if(b3dDebug) b3dValidateFillList(fillList); if(face->flags & B3D_FACE_ACTIVE) { b3dRemoveFill(fillList, face); } else { b3dAddBackFill(fillList, face); b3dCheckIntersectionOfFaces(fillList->firstFace, face, yValue, edge, nextIntersection); } face->flags ^= B3D_FACE_ACTIVE; face = edge->rightFace; if(face) { if(face->flags & B3D_FACE_ACTIVE) { b3dRemoveFill(fillList, face); } else { b3dAddBackFill(fillList, face); b3dCheckIntersectionOfFaces(fillList->firstFace, face, yValue, edge, nextIntersection); } face->flags ^= B3D_FACE_ACTIVE; } if(b3dDebug) b3dValidateFillList(fillList); } /* --INLINE-- */ /*************************************************************/ /*************************************************************/ /*************************************************************/ /* INLINE b3dClearSpanBuffer(aet) */ void b3dClearSpanBuffer(B3DActiveEdgeTable *aet) { int i, leftX, rightX; unsigned int *buffer = currentState->spanBuffer; if(aet->size && buffer) { leftX = aet->data[0]->xValue >> B3D_FixedToIntShift; rightX = aet->data[aet->size-1]->xValue >> B3D_FixedToIntShift; if(leftX < 0) leftX = 0; if(rightX >= currentState->spanSize) rightX = currentState->spanSize-1; for(i=leftX;i<=rightX;i++) buffer[i] = 0; } } /* --INLINE-- */ /* INLINE b3dDrawSpanBuffer(aet, yValue) */ void b3dDrawSpanBuffer(B3DActiveEdgeTable *aet, int yValue) { int leftX, rightX; if(aet->size && currentState->spanDrawer) { leftX = aet->data[0]->xValue >> B3D_FixedToIntShift; rightX = aet->data[aet->size-1]->xValue >> B3D_FixedToIntShift; if(leftX < 0) leftX = 0; if(rightX > currentState->spanSize) rightX = currentState->spanSize; currentState->spanDrawer(leftX, rightX, yValue); } } /* --INLINE-- */ /*************************************************************/ /*************************************************************/ /*************************************************************/ /* General failure */ #define FAIL(reason,resume) { aet->yValue = yValue; return reason | resume; } #define PROCEED { yValue = aet->yValue; } /* Failure adding objects */ #define FAIL_ADDING(reason) { obj->start = objStart; FAIL(reason, B3D_RESUME_ADDING) } #define PROCEED_ADDING { objStart = obj->start; PROCEED } /* Failure merging objects */ #define FAIL_MERGING(reason) { FAIL(reason, B3D_RESUME_MERGING); } #define PROCEED_MERGING { PROCEED } /* Failure during paint */ #define FAIL_PAINTING(reason) { aet->start = aetStart; aet->leftEdge = leftEdge; aet->rightEdge = rightEdge; FAIL(reason, B3D_RESUME_PAINTING) } #define PROCEED_PAINTING(reason) { aetStart = aet->start; leftEdge = aet->leftEdge; rightEdge = aet->rightEdge; PROCEED } #define FAIL_UPDATING(reason) int b3dMainLoop(B3DRasterizerState *state, int stopReason) { B3DPrimitiveObject *activeStart, *passiveStart; int yValue, nextObjY, nextEdgeY; B3DFillList *fillList; B3DPrimitiveEdge *lastIntersection, *nextIntersection; if(!!state) return B3D_GENERIC_ERROR; if(!!state->nObjects) return B3D_NO_ERROR; if(b3dValidateAndRemapState(state) !!= B3D_NO_ERROR) return B3D_GENERIC_ERROR; if(stopReason == B3D_NO_ERROR) b3dSetupObjects(state); if(b3dDebug) { /* check the sort order of objects */ int i; for(i=2; inObjects;i++) if(!!objSortsBefore(state->objects[i-1], state->objects[i])) b3dAbort("Objects not sorted"); } currentState = state; faceAlloc = state->faceAlloc; edgeAlloc = state->edgeAlloc; attrAlloc = state->attrAlloc; addedEdges = state->addedEdges; fillList = state->fillList; aet = state->aet; nextIntersection = aet->nextIntersection; lastIntersection = aet->lastIntersection; if(b3dDoStats) nFaces = 0; if(stopReason == B3D_NO_ERROR) { activeStart = passiveStart = state->objects[0]; yValue = nextEdgeY = nextObjY = passiveStart->minY; } else { int resumeCode; resumeCode = stopReason & B3D_RESUME_MASK; if(resumeCode == B3D_RESUME_ADDING ) goto RESUME_ADDING; if(resumeCode == B3D_RESUME_MERGING ) goto RESUME_MERGING; if(resumeCode == B3D_RESUME_PAINTING) goto RESUME_PAINTING; if(resumeCode == B3D_RESUME_UPDATING) goto RESUME_UPDATING; return B3D_GENERIC_ERROR; } /**** BEGIN MAINLOOP ****/ while(activeStart || passiveStart || aet->size) { RESUME_ADDING: /* STEP 1: Add new objects if necessary */ if(yValue == nextObjY) { nextEdgeY = nextObjY; while(passiveStart && passiveStart->minY == nextObjY) { passiveStart->flags |= B3D_OBJECT_ACTIVE; passiveStart = passiveStart->next; } if(passiveStart) nextObjY = passiveStart->minY; else nextObjY = 99999; } /* End of adding objects */ /* STEP 2: Add new edges if necessary */ if(yValue == nextEdgeY) { B3DPrimitiveObject *obj = activeStart; int scaledY = (yValue+1) << B3D_IntToFixedShift; nextEdgeY = nextObjY << B3D_IntToFixedShift; while(obj !!= passiveStart) { B3DInputFace *objFaces = obj->faces; B3DPrimitiveVertex *objVtx = obj->vertices; int objStart = obj->start; int objSize = obj->nFaces; int tempY; assert(obj->flags & B3D_OBJECT_ACTIVE); while(objStart < objSize && ((tempY = objVtx[objFaces[objStart].i0].windowPosY) < scaledY)) { /* add edges from face at objFaces[objStart] */ B3DInputFace *inputFace = objFaces + objStart; B3DPrimitiveFace *face; /* NOTE: If any of the following fails, we can re-enter the main loop later on. */ if(faceAlloc->nFree == 0) FAIL_ADDING(B3D_NO_MORE_FACES); if(edgeAlloc->nFree < 2) FAIL_ADDING(B3D_NO_MORE_EDGES); if(addedEdges->size+2 > addedEdges->max) FAIL_ADDING(B3D_NO_MORE_ADDED); /* Allocate a new face and do the initial setup */ face = b3dInitializeFace(objVtx + inputFace->i0, objVtx + inputFace->i1, objVtx + inputFace->i2, obj->texture, obj->flags); if(face) { b3dAddEdgesFromFace(face, yValue); } objStart++; } obj->start = objStart; if(objStart !!= objSize) { if(tempY < nextEdgeY) nextEdgeY = tempY; } else { /* Unlink obj from activeStart list */ obj->flags |= B3D_OBJECT_DONE; if(obj == activeStart) { activeStart = obj->next; } else { obj->prev->next = obj->next; } } obj = obj->next; } nextEdgeY >>= B3D_FixedToIntShift; } /* End of adding edges */ /* STEP 3: Merge all newly added edges from addedList into the AET */ if(addedEdges->size) { RESUME_MERGING: if(b3dDebug) b3dValidateEdgeOrder(addedEdges); /* NOTE: If the following fails, we can re-enter the main loop later on. */ if(aet->size + addedEdges->size > aet->max) FAIL_MERGING(B3D_NO_MORE_AET); b3dMergeAETEdgesFrom(aet, addedEdges); if(b3dDebug) { b3dValidateAETOrder(aet); } addedEdges->size = 0; /* reset added */ } /* End of merging edges */ /********** THIS IS THE CORE LOOP ********/ /* while(yValue < nextEdgeY && !!addedEdges->size && aet->size) { */ if(b3dDoStats) { /* Gather stats */ if(aet->size > maxEdges) maxEdges = aet->size; if(nFaces > maxFaces) maxFaces = nFaces; } /* STEP 4: Draw the current span */ /* STEP 4a: Clear the span buffer */ b3dClearSpanBuffer(aet); /* STEP 4b: Scan out the AET */ if(aet->size) { B3DPrimitiveEdge *leftEdge; B3DPrimitiveEdge *rightEdge; B3DPrimitiveEdge **aetData = aet->data; int aetStart = 1; int aetSize = aet->size; /* clean up old fills if any */ b3dCleanupFill(fillList); nextIntersection->xValue = B3D_MAX_X; leftEdge = aetData[0]; while(aetStart < aetSize) { /*-- Toggle the faces of the top edge (the left edge is always on top) --*/ if(leftEdge == lastIntersection) { /* Special case if this is a intersection edge */ assert(fillList->firstFace == leftEdge->leftFace); b3dRemoveFill(fillList, leftEdge->rightFace); b3dAddFrontFill(fillList, leftEdge->rightFace); } else { b3dToggleTopFills(fillList, leftEdge, yValue); } /*-- end of toggling top edge faces --*/ /* after getting a new top fill we must adjust intersections */ b3dAdjustIntersections(fillList, yValue, leftEdge, nextIntersection); /*-- search for the next top edge which will be the right edge --*/ assert(aetStart < aetSize); if(!!fillList->firstFace) rightEdge = aetData[aetStart++]; /* If no current top fill just use the next edge */ else while(aetStart < aetSize) { /* Search for the next top edge in the AET */ rightEdge = aetData[aetStart]; /* If we have an intersection use the intersection edge */ if(nextIntersection->xValue <= rightEdge->xValue) { rightEdge = nextIntersection; break; } aetStart++; /* Check if this edge is on top */ assert(fillList->firstFace); { double xValue = rightEdge->xValue * B3D_FixedToFloat; B3DPrimitiveFace *topFace = fillList->firstFace; if( rightEdge->leftFace == topFace || rightEdge->rightFace == topFace || rightEdge->zValue < zValueAt(topFace, xValue, yValue)) break; /* rightEdge is on top */ } /* If the edge is not on top toggle its (back) fills */ b3dToggleBackFills(fillList, rightEdge, yValue, nextIntersection); rightEdge = NULL; } /*-- end of search for next top edge --*/ /*-- Now do the drawing from leftEdge to rightEdge --*/ assert(rightEdge); if(fillList->firstFace) { /* Note: We fill *including* leftX and rightX */ int leftX = (leftEdge->xValue >> B3D_FixedToIntShift) + 1; int rightX = (rightEdge->xValue >> B3D_FixedToIntShift); B3DPrimitiveFace *topFace = fillList->firstFace; if(leftX < 0) leftX = 0; if(rightX >= currentState->spanSize) rightX = currentState->spanSize-1; if(leftX <= rightX) { /* Since we know now that some serious filling operation will happen, initialize the attributes of the face if this hasn''t been done before. */ RESUME_PAINTING: if( (topFace->flags & B3D_FACE_INITIALIZED) == 0) { assert(topFace->attributes == NULL); if(!!b3dInitializePass2(topFace)) FAIL_PAINTING(B3D_NO_MORE_ATTRS); } /* And dispatch on the actual pixel drawers */ (*B3D_FILL_FUNCTIONS[(topFace->flags >> B3D_ATTR_SHIFT) & B3D_ATTR_MASK]) (leftX, rightX, yValue, topFace); } } /*-- End of drawing -- */ /* prepare for new top edge */ leftEdge = rightEdge; /* use a new intersection if necessary */ if(leftEdge == nextIntersection) { nextIntersection = lastIntersection; lastIntersection = leftEdge; } nextIntersection->xValue = B3D_MAX_X; } /* clean up old fills if any */ b3dCleanupFill(fillList); } /* STEP 4c: Display the pixels from the span buffer */ b3dDrawSpanBuffer(aet, yValue); /* STEP 5: Go to next y value and update AET entries */ yValue++; if(aet->size) { int aetStart = 0; int aetSize = aet->size; B3DPrimitiveEdge **aetData = aet->data; aetStart = 0; while(aetStart < aetSize) { B3DPrimitiveEdge *edge = aetData[aetStart]; if(--(edge->nLines)) { /* Advance to next scan line and resort edge */ b3dAdvanceAETEdge(edge, aetData, aetStart); aetStart++; } else { /* Remove edge and add lower edges if necessary */ RESUME_UPDATING: if(edgeAlloc->nFree < 2) FAIL_UPDATING(B3D_NO_MORE_EDGES); if(addedEdges->size + 2 > addedEdges->max) FAIL_UPDATING(B3D_NO_MORE_ADDED); b3dRemoveAETEdge(aet, edge, yValue, aetStart); aetSize = aet->size; /* Do NOT advance aetStart here */ } } } /* End of AET update */ if(b3dDebug) { b3dValidateAETOrder(aet); } /*}*/ /******** END OF CORE LOOP ********/ } /**** END MAINLOOP ****/ return B3D_NO_ERROR; } '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'ar 4/18/1999 08:33'! b3dRemapC ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dRemap.c * CONTENT: Remapping functions for the B3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #include "b3d.h" /* b3dRemapFaces: Remap all allocated faces using the given offsets */ /* INLINE b3dRemapFaces(list, attrOffset, edgeOffset) */ void b3dRemapFaces(B3DFaceAllocList *list, int attrOffset, int edgeOffset) { int i; for(i=0; isize;i++) { B3DPrimitiveFace *face = list->data + i; if(face->flags & B3D_ALLOC_FLAG) { if(face->attributes) (char*)face->attributes += attrOffset; if(face->leftEdge) (char*)face->leftEdge += edgeOffset; if(face->rightEdge) (char*)face->rightEdge += edgeOffset; } } } /* --INLINE-- */ /* b3dRemapEdges: Remap all allocated edges using the given offset */ /* INLINE b3dRemapEdges(list, faceOffset) */ void b3dRemapEdges(B3DEdgeAllocList *list, int faceOffset) { int i; for(i=0; isize;i++) { B3DPrimitiveEdge *edge = list->data + i; if(edge->flags & B3D_ALLOC_FLAG) { if(edge->leftFace) (char*)edge->leftFace += faceOffset; if(edge->rightFace) (char*)edge->rightFace += faceOffset; } } } /* --INLINE-- */ /* b3dRemapFills: Remap the fill list using the given offset */ /* INLINE b3dRemapFills(fillList, offset) */ void b3dRemapFills(B3DFillList *fillList, int offset) { B3DPrimitiveFace *temp; if(fillList->firstFace) (char*)fillList->firstFace += offset; if(fillList->lastFace) (char*)fillList->lastFace += offset; temp = fillList->firstFace; while(temp) { if(temp->nextFace) (char*)temp->nextFace += offset; if(temp->prevFace) (char*)temp->prevFace += offset; temp = temp->nextFace; } } /* --INLINE-- */ /* b3dRemapEdgeList: Remap all edge pointers using the given offset */ /* INLINE b3dRemapEdgeList(list, edgeOffset) */ void b3dRemapEdgeList(B3DPrimitiveEdgeList *list, int edgeOffset) { int i; for(i=0; isize;i++) { (char*) list->data[i] += edgeOffset; } } /* --INLINE-- */ /* b3dRemapAET: Remap all edge pointers using the given offset */ /* INLINE b3dRemapAET(list, edgeOffset, aetOffset, firstEdge, lastEdge) */ void b3dRemapAET(B3DActiveEdgeTable *list, int edgeOffset, int aetOffset, void *firstEdge, void *lastEdge) { int i; if(edgeOffset) for(i=0; isize;i++) (char*) list->data[i] += edgeOffset; if((void*)list->leftEdge >= firstEdge && (void*)list->leftEdge < lastEdge) (char*) list->leftEdge += edgeOffset; else if(list->leftEdge) (char*) list->leftEdge += aetOffset; if((void*)list->rightEdge >= firstEdge && (void*)list->rightEdge < lastEdge) (char*) list->rightEdge += edgeOffset; else if(list->rightEdge) (char*) list->rightEdge += aetOffset; if(aetOffset) { (char*) list->nextIntersection += aetOffset; (char*) list->lastIntersection += aetOffset; } } /* --INLINE-- */ /* b3dRemapEdgeVertices: Remap all vertices in the specified range using the given offset */ /* INLINE b3dRemapEdgeVertices(list, vtxOffset, firstVtx, lastVtx) */ void b3dRemapEdgeVertices(B3DEdgeAllocList *list, int vtxOffset, void *firstVtx, void *lastVtx) { int i; for(i=0; isize; i++) { B3DPrimitiveEdge *edge = list->data + i; if((edge->flags & B3D_ALLOC_FLAG) && ((void*)edge->v0 >= (void*)firstVtx) && ((void*)edge->v0 < (void*)lastVtx)) { (char*) edge->v0 += vtxOffset; (char*) edge->v1 += vtxOffset; } } } /* --INLINE-- */ /* b3dRemapFaceVertices: Remap all vertices in the specified range using the given offset */ /* INLINE b3dRemapFaceVertices(list, vtxOffset, firstVtx, lastVtx) */ void b3dRemapFaceVertices(B3DFaceAllocList *list, int vtxOffset, void *firstVtx, void *lastVtx) { int i; for(i=0; isize; i++) { B3DPrimitiveFace *face = list->data + i; if((face->flags & B3D_ALLOC_FLAG) && ((void*)face->v0 >= (void*)firstVtx) && ((void*)face->v0 < (void*)lastVtx)) { (char*) face->v0 += vtxOffset; (char*) face->v1 += vtxOffset; (char*) face->v2 += vtxOffset; } } } /* --INLINE-- */ /* b3dRemapFaceFree: Remap all free faces using the given offset */ /* INLINE b3dRemapFaceFree(list, faceOffset) */ void b3dRemapFaceFree(B3DFaceAllocList *list, int faceOffset) { B3DPrimitiveFace *freeObj; if(list->firstFree) { (char*)list->firstFree += faceOffset; freeObj = list->firstFree; while(freeObj->nextFree) { (char*) freeObj->nextFree += faceOffset; freeObj = freeObj->nextFree; } } } /* --INLINE-- */ /* b3dRemapEdgeFree: Remap all free edges using the given offset */ /* INLINE b3dRemapEdgeFree(list, edgeOffset) */ void b3dRemapEdgeFree(B3DEdgeAllocList *list, int edgeOffset) { B3DPrimitiveEdge *freeObj; if(list->firstFree) { (char*)list->firstFree += edgeOffset; freeObj = list->firstFree; while(freeObj->nextFree) { (char*) freeObj->nextFree += edgeOffset; freeObj = freeObj->nextFree; } } } /* --INLINE-- */ /* b3dRemapAttrFree: Remap all free attributes using the given offset */ /* INLINE b3dRemapAttrFree(list, attrOffset) */ void b3dRemapAttributes(B3DAttrAllocList *list, int attrOffset) { int i; for(i=0; i < list->size; i++) { B3DPrimitiveAttribute *attr = list->data + i; if(attr->next) (char*) attr->next += attrOffset; } } /* --INLINE-- */ /* b3dValidateAndRemapState: Validate the rasterizer state and remap the objects if necessary. */ int b3dValidateAndRemapState(B3DRasterizerState *state) { int faceOffset, edgeOffset, attrOffset, aetOffset, objOffset, i; B3DPrimitiveObject *obj; if(!!state) return B3D_GENERIC_ERROR; /* Check the magic numbers */ if(state->faceAlloc->magic !!= B3D_FACE_ALLOC_MAGIC) return B3D_MAGIC_ERROR; if(state->edgeAlloc->magic !!= B3D_EDGE_ALLOC_MAGIC) return B3D_MAGIC_ERROR; if(state->attrAlloc->magic !!= B3D_ATTR_ALLOC_MAGIC) return B3D_MAGIC_ERROR; if(state->aet->magic !!= B3D_AET_MAGIC) return B3D_MAGIC_ERROR; if(state->addedEdges->magic !!= B3D_EDGE_LIST_MAGIC) return B3D_MAGIC_ERROR; if(state->fillList->magic !!= B3D_FILL_LIST_MAGIC) return B3D_MAGIC_ERROR; /* Check if we need to relocate objects */ faceOffset = (int)state->faceAlloc - (int)state->faceAlloc->This; edgeOffset = (int)state->edgeAlloc - (int)state->edgeAlloc->This; attrOffset = (int)state->attrAlloc - (int)state->attrAlloc->This; aetOffset = (int)state->aet - (int)state->aet->This; /* remap faces */ if(attrOffset || edgeOffset) b3dRemapFaces(state->faceAlloc, attrOffset, edgeOffset); /* remap fills and edges */ if(faceOffset) { b3dRemapFills(state->fillList, faceOffset); b3dRemapEdges(state->edgeAlloc, faceOffset); b3dRemapFaceFree(state->faceAlloc, faceOffset); } /* Remap AET */ if(edgeOffset || aetOffset) { void *firstEdge = state->edgeAlloc->data; void *lastEdge = state->edgeAlloc->data + state->edgeAlloc->size; b3dRemapAET(state->aet, edgeOffset, aetOffset, firstEdge, lastEdge); } /* Remap addedEdges and edge free list*/ if(edgeOffset) { b3dRemapEdgeList(state->addedEdges, edgeOffset); b3dRemapEdgeFree(state->edgeAlloc, edgeOffset); } if(attrOffset) b3dRemapAttributes(state->attrAlloc, attrOffset); state->faceAlloc->This = (void*) state->faceAlloc; state->edgeAlloc->This = (void*) state->edgeAlloc; state->attrAlloc->This = (void*) state->attrAlloc; state->aet->This = (void*) state->aet; /* Remap any vertex pointers */ for(i=0; inObjects; i++) { obj = state->objects[i]; if(obj->magic !!= B3D_PRIMITIVE_OBJECT_MAGIC) return B3D_MAGIC_ERROR; objOffset = (int)obj - (int)obj->This; if(objOffset) { if((obj->flags & B3D_OBJECT_ACTIVE)) { B3DPrimitiveVertex *firstVtx = obj->vertices; B3DPrimitiveVertex *lastVtx = obj->vertices + obj->nVertices; b3dRemapFaceVertices(state->faceAlloc, objOffset, firstVtx, lastVtx); b3dRemapEdgeVertices(state->edgeAlloc, objOffset, firstVtx, lastVtx); } obj->vertices = (B3DPrimitiveVertex*) (obj + 1); obj->faces = (B3DInputFace*) (obj->vertices + obj->nVertices); } obj->This = (void*) obj; } return B3D_NO_ERROR; } '! ! !B3DRasterizerPlugin class methodsFor: 'C source code' stamp: 'di 4/22/1999 09:14'! b3dTypesH ^'/**************************************************************************** * PROJECT: Balloon 3D Graphics Subsystem for Squeak * FILE: b3dTypes.h * CONTENT: Type declarations for the B3D rasterizer * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * * *****************************************************************************/ #ifndef B3D_TYPES_H #define B3D_TYPES_H #ifndef NULL #define NULL ((void*)0) #endif /* Error constants */ #define B3D_NO_ERROR 0 /* Generic error */ #define B3D_GENERIC_ERROR -1 /* Bad magic number */ #define B3D_MAGIC_ERROR -2 /* Note: The error codes that allow resuming must be positive. They''ll be combined with the resume codes */ /* no more space in edge allocation list */ #define B3D_NO_MORE_EDGES 1 /* no more space in face allocation list */ #define B3D_NO_MORE_FACES 2 /* no more space in attribute allocation list */ #define B3D_NO_MORE_ATTRS 3 /* no more space in active edge table */ #define B3D_NO_MORE_AET 4 /* no more space for added edges */ #define B3D_NO_MORE_ADDED 5 /* Resume codes */ #define B3D_RESUME_MASK 0xF0000 /* Resume adding objects/edges */ #define B3D_RESUME_ADDING 0x10000 /* Resume merging added edges */ #define B3D_RESUME_MERGING 0x20000 /* Resume painting faces */ #define B3D_RESUME_PAINTING 0x40000 /* Resume updating the AET */ #define B3D_RESUME_UPDATING 0x80000 /* Factor to convert from float to fixed pt */ #define B3D_FloatToFixed 4096.0 /* Factor to convert from fixed pt to float */ #define B3D_FixedToFloat 0.000244140625 /* Shift value to convert from integer to fixed pt */ #define B3D_IntToFixedShift 12 #define B3D_FixedToIntShift 12 /* 0.5 in fixed pt representation */ #define B3D_FixedHalf 2048 /* Max. possible x value */ #define B3D_MAX_X 0x7FFFFFFF /* Allocation flag: If this flag is not set then the nextFree pointer is valid */ #define B3D_ALLOC_FLAG 1 /************************ PrimitiveColor definition ************************/ typedef unsigned char B3DPrimitiveColor[4]; /* An ugly hack but I can''t find the global defs in CodeWarrior on the Mac */ #ifndef LSB_FIRST #define MSB_FIRST #endif #ifndef MSB_FIRST #define RED_INDEX 0 #define GREEN_INDEX 1 #define BLUE_INDEX 2 #define ALPHA_INDEX 3 #else #define ALPHA_INDEX 0 #define BLUE_INDEX 1 #define GREEN_INDEX 2 #define RED_INDEX 3 #endif /************************ PrimitiveVertex definition ************************/ typedef struct B3DPrimitiveVertex { float position[3]; float normal[3]; float texCoord[2]; float rasterPos[4]; union { int pixelValue32; B3DPrimitiveColor color; } cc; int clipFlags; int windowPos[2]; } B3DPrimitiveVertex; /* sort order for primitive vertices */ #define vtxSortsBefore(vtx1, vtx2) ( (vtx1)->windowPosY == (vtx2)->windowPosY ? (vtx1)->windowPosX <= (vtx2)->windowPosX : (vtx1)->windowPosY <= (vtx2)->windowPosY) /************************ InputFace definition ************************/ /* Note: The following is mainly so that we don''t need these weird int[3] declarations. */ typedef struct B3DInputFace { int i0; int i1; int i2; } B3DInputFace; typedef struct B3DInputQuad { int i0; int i1; int i2; int i3; } B3DInputQuad; /************************ PrimitiveEdge definition ************************/ /* Edge flags: B3D_EDGE_CONTINUE_LEFT - continue with the lower edge of the left face B3D_EDGE_CONTINUE_RIGHT - continue with the lower edge of the right face B3D_EDGE_LEFT_MAJOR - edge is major edge for left face B3D_EDGE_RIGHT_MAJOR - edge is major edge for right face */ #define B3D_EDGE_CONTINUE_LEFT 0x10 #define B3D_EDGE_CONTINUE_RIGHT 0x20 #define B3D_EDGE_LEFT_MAJOR 0x40 #define B3D_EDGE_RIGHT_MAJOR 0x80 typedef struct B3DPrimitiveEdge { int flags; struct B3DPrimitiveEdge *nextFree; /* start/end of edge */ struct B3DPrimitiveVertex *v0; struct B3DPrimitiveVertex *v1; /* left/right face of edge (NOT meant literally) */ struct B3DPrimitiveFace *leftFace; struct B3DPrimitiveFace *rightFace; /* current x/z value */ int xValue; float zValue; /* x/z increment per scan line */ int xIncrement; float zIncrement; /* number of remaining scan lines */ int nLines; } B3DPrimitiveEdge; /* B3DPrimitiveEdgeList: A list of pointers to primitive edges */ #define B3D_EDGE_LIST_MAGIC 0x45553342 typedef struct B3DPrimitiveEdgeList { int magic; void *This; int start; int size; int max; B3DPrimitiveEdge *data[1]; } B3DPrimitiveEdgeList; /* B3DActiveEdgeTable: The active edge table (basically a primitive edge table with few additional entries) */ #define B3D_AET_MAGIC 0x41455420 typedef struct B3DActiveEdgeTable { int magic; void *This; int start; int size; int max; /* Backups for proceeding after failure */ int yValue; B3DPrimitiveEdge *leftEdge; B3DPrimitiveEdge *rightEdge; B3DPrimitiveEdge *lastIntersection; B3DPrimitiveEdge *nextIntersection; /* That''s where lastIntersection and nextIntersection point to */ B3DPrimitiveEdge tempEdge0; B3DPrimitiveEdge tempEdge1; /* Actual data */ B3DPrimitiveEdge *data[1]; } B3DActiveEdgeTable ; /************************ PrimitiveFace definition ************************/ /* Face flags: B3D_FACE_INITIALIZED - have the face attributes been initialized?!! B3D_FACE_ACTIVE - is the face currently in the fill list?!! B3D_FACE_HAS_ALPHA - can the face eventually be transparent?!! B3D_FACE_RGB - R,G,B interpolation values B3D_FACE_ALPHA - Alpha interpolation values B3D_FACE_STW - S,T,W interpolation values */ #define B3D_FACE_INITIALIZED 0x10 #define B3D_FACE_ACTIVE 0x20 #define B3D_FACE_HAS_ALPHA 0x40 #define B3D_FACE_RGB 0x100 #define B3D_FACE_ALPHA 0x200 #define B3D_FACE_STW 0x400 /* # of possible combinations AND maximum (e.g., R+G+B+A+S+T+W) of attribs */ /* NOTE: This is a really ugly hack - I''ll have to fix that */ #define B3D_MAX_ATTRIBUTES 8 /* mask out the face attributes */ #define B3D_ATTR_MASK 0x7 /* shift for getting the attributes */ #define B3D_ATTR_SHIFT 8 typedef struct B3DPrimitiveFace { int flags; struct B3DPrimitiveFace *nextFree; /* The three vertices of the face */ struct B3DPrimitiveVertex *v0; struct B3DPrimitiveVertex *v1; struct B3DPrimitiveVertex *v2; /* The links for the (depth sorted) list of fills */ struct B3DPrimitiveFace *prevFace; struct B3DPrimitiveFace *nextFace; /* The left and right edge of the face (not taken too literally) */ struct B3DPrimitiveEdge *leftEdge; struct B3DPrimitiveEdge *rightEdge; /* The deltas for the major (e.g., v0-v2) and the first minor (e.g., v0-v1) edge */ float majorDx, majorDy; float minorDx, minorDy; /* The inverse area covered by (twice) the triangle */ float oneOverArea; /* Depth attributes are kept here since we almost always need ''em */ float minZ, maxZ; float dzdx, dzdy; /* The pointer to the texture */ struct B3DTexture *texture; /* The pointer to the extended (per face) interpolation values */ struct B3DPrimitiveAttribute *attributes; } B3DPrimitiveFace; /* B3DFillList: A (depth-sorted) list of primitive faces */ #define B3D_FILL_LIST_MAGIC 0x46443342 typedef struct B3DFillList { int magic; void *This; B3DPrimitiveFace *firstFace; B3DPrimitiveFace *lastFace; } B3DFillList; /************************ PrimitiveAttribute definition ************************/ typedef struct B3DPrimitiveAttribute { /* Note: next is either nextFree or or nextUsed */ struct B3DPrimitiveAttribute *next; /* value at the face->v0 */ float value; /* value / dx derivative for face */ float dvdx; /* value / dy derivative for face */ float dvdy; } B3DPrimitiveAttribute; /************************ Texture definition ************************/ #define B3D_TEXTURE_POWER_OF_2 0x10 typedef struct B3DTexture { int width; int height; int depth; int rowLength; /* 32bit words per scan line */ int sMask; /* Nonzero for power of two width */ int sShift; int tMask; /* Nonzero for power of two height */ int tShift; int cmSize; /* length of color map */ unsigned int *colormap; unsigned int *data; } B3DTexture; /************************ PrimitiveViewport definition ************************/ typedef struct B3DPrimitiveViewport { int x0, y0, x1, y1; } B3DPrimitiveViewport; /************************ PrimitiveObject definition ************************/ #define B3D_OBJECT_ACTIVE 0x10 #define B3D_OBJECT_DONE 0x20 #define B3D_PRIMITIVE_OBJECT_MAGIC 0x4F443342 typedef struct B3DPrimitiveObject { int magic; void *This; int __oop__; /* actual ST oop */ struct B3DPrimitiveObject *next; struct B3DPrimitiveObject *prev; int flags; int textureIndex; struct B3DTexture *texture; int minX, maxX, minY, maxY; float minZ, maxZ; int nSortedFaces; int nInvalidFaces; int start; int nFaces; B3DInputFace *faces; int nVertices; B3DPrimitiveVertex *vertices; } B3DPrimitiveObject; /* sort order for primitive objects */ #define objSortsBefore(obj1, obj2) ( (obj1)->minY == (obj2)->minY ? (obj1)->minX <= (obj2)->minX : (obj1)->minY <= (obj2)->minY) #endif /* ifndef B3D_TYPES_H */ '! ! B3DEnginePart subclass: #B3DRenderEngine instanceVariableNames: 'vertexBuffer transformer shader clipper rasterizer properties ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DRenderEngine commentStamp: '' prior: 0! I represent a facade for all Balloon 3D operations. Clients should only interact with me, not with any of the parts of the engine directly. However, clients may configure me to use certain parts in the 3D rendering pipeline. Instance variables: vertexBuffer The vertex buffer passed on through the entire pipeline transformer The part performing transform operations shader The part performing vertex shading operations clipper The part performing view frustum clipping rasterizer The part performing final pixel rasterization ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/14/1999 22:22'! clearDepthBuffer ^rasterizer clearDepthBuffer! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/16/1999 02:17'! clearViewport: aColor ^rasterizer clearViewport: aColor! ! !B3DRenderEngine methodsFor: 'attributes'! color ^vertexBuffer color! ! !B3DRenderEngine methodsFor: 'attributes'! color: aColor ^vertexBuffer color: aColor! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 19:34'! material ^shader material! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 19:34'! material: aMaterial ^shader material: aMaterial! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 16:19'! materialColor ^shader materialColor! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 16:19'! materialColor: aColor ^shader materialColor: aColor! ! !B3DRenderEngine methodsFor: 'attributes'! normal ^vertexBuffer normal! ! !B3DRenderEngine methodsFor: 'attributes'! normal: aVector ^vertexBuffer normal: aVector! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 19:35'! popMaterial ^shader popMaterial.! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/16/1999 03:14'! popTexture ^rasterizer popTexture! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/7/1999 19:34'! pushMaterial ^shader pushMaterial.! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/16/1999 03:14'! pushTexture ^rasterizer pushTexture! ! !B3DRenderEngine methodsFor: 'attributes'! texCoords ^vertexBuffer texCoords! ! !B3DRenderEngine methodsFor: 'attributes'! texCoords: aVector ^vertexBuffer texCoords: aVector! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/16/1999 03:14'! texture ^rasterizer texture! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 6/2/1999 14:00'! texture: anObject "Note: For convenience; the object can be anything that understands #asTexture" ^rasterizer texture: anObject asTexture! ! !B3DRenderEngine methodsFor: 'attributes'! vertex ^vertexBuffer vertex! ! !B3DRenderEngine methodsFor: 'attributes'! vertex: aVector ^vertexBuffer vertex: aVector.! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/4/1999 17:52'! viewport ^rasterizer viewport! ! !B3DRenderEngine methodsFor: 'attributes' stamp: 'ar 2/4/1999 17:52'! viewport: aRect ^rasterizer viewport: aRect! ! !B3DRenderEngine methodsFor: 'draw primitives' stamp: 'ar 11/7/1999 18:12'! drawPolygonAfter: aBlock vertexBuffer reset. vertexBuffer primitive: 3. aBlock value. ^self renderPrimitive.! ! !B3DRenderEngine methodsFor: 'draw primitives' stamp: 'ar 11/7/1999 18:15'! drawPolygonMesh: aB3DPolygonMesh "Draw a generic polygon mesh" | hasVtxNormals hasTexCoords hasVtxColors bounds box | box _ nil. aB3DPolygonMesh polygonsDo:[:poly| hasVtxNormals _ poly hasVertexNormals. hasTexCoords _ poly hasTextureCoords. hasVtxColors _ poly hasVertexColors. "Set the normal of the polygon if we don't have normals per vertex" hasVtxNormals ifFalse:[self normal: poly normal]. bounds _ self drawPolygonAfter:[ poly verticesDo:[:vtx| hasVtxColors ifTrue:[self color: (poly colorOfVertex: vtx)]. hasVtxNormals ifTrue:[self normal: (poly normalOfVertex: vtx)]. hasTexCoords ifTrue:[self texCoord: (poly texCoordOfVertex: vtx)]. self vertex: vtx. ]. ]. box == nil ifTrue:[box _ bounds] ifFalse:[box _ box quickMerge: bounds]. ]. ^box! ! !B3DRenderEngine methodsFor: 'draw primitives' stamp: 'ar 2/4/1999 20:16'! render: anObject anObject renderOn: self.! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 11/7/2000 17:27'! privateClipVB: vb "OBSOLETE. Clip the objects in the vertex buffer." ^clipper processVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 2/2/1999 19:39'! privateNeedsClipVB: visibleFlag "Determine if a vertex buffer with the given visibility flag must be clipped. Return false if either visibleFlag == true (meaning the vertex buffer is completely inside the view frustum) or the rasterizer can clip by itself (it usually can)." ^visibleFlag ~~ true and:[rasterizer needsClip]! ! !B3DRenderEngine methodsFor: 'private-rendering'! privateNeedsShadingVB "Return true if the objects in the vertex buffer needs separate shading. This is determined by checking if a) lighting is enabled b) at least one light exists c) at least one material exists " ^true! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 11/7/2000 17:27'! privatePostClipVB: vb "Clip the objects in the vertex buffer." ^clipper postProcessVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 11/7/2000 17:25'! privatePreClipVB: vb "Clip the objects in the vertex buffer." ^clipper preProcessVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 2/4/1999 04:26'! privateRasterizeVB: vb "Rasterize the current primitive from the vertex buffer." ^rasterizer processVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 2/4/1999 04:26'! privateShadeVB: vb "Shade all the vertices in the vertex buffer using selected materials and lights" ^shader processVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 2/8/1999 21:18'! privateTransformVB: vb "Transform the contents of the vertex buffer. Transforming may include normals (if lighting enabled) and textures (if textures enabled)." ^transformer processVertexBuffer: vb! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 2/2/1999 19:31'! privateVisibleVB: vb "Return the visibility of the objects in the vertex buffer. Return: true - if completely inside view frustum false - if completely outside view frustum nil - if partly inside/outside view frustum "! ! !B3DRenderEngine methodsFor: 'private-rendering' stamp: 'ar 11/7/2000 17:28'! renderPrimitive "This is the main rendering loop for all operations" | visible | "Step 1: Check if the mesh is visible at all" visible _ self privateVisibleVB: vertexBuffer. visible == false ifTrue:[^nil]. "Step 2: Transform vertices, normals, texture coords of the mesh" self privateTransformVB: vertexBuffer. "Step 4a: Pre-clip the mesh if needed so that we can reject invisible meshes before shading" (self privateNeedsClipVB: visible) ifTrue:[visible _ self privatePreClipVB: vertexBuffer] ifFalse:[visible _ true]. "don't bother clipping below" visible == false ifTrue:[^nil]. "Step 3: Light the vertices of the mesh." self privateNeedsShadingVB ifTrue:[self privateShadeVB: vertexBuffer]. "Step 4: Clip the mesh if necessary" (visible == nil) ifTrue:[visible _ self privatePostClipVB: vertexBuffer]. "Step 5: Rasterize the mesh" ^self privateRasterizeVB: vertexBuffer.! ! !B3DRenderEngine methodsFor: 'transforming' stamp: 'ar 2/4/1999 20:18'! loadIdentity ^transformer loadIdentity! ! !B3DRenderEngine methodsFor: 'transforming'! lookFrom: position to: target up: upDirection ^transformer lookFrom: position to: target up: upDirection! ! !B3DRenderEngine methodsFor: 'transforming'! perspective: aPerspective ^transformer perspective: aPerspective! ! !B3DRenderEngine methodsFor: 'transforming' stamp: 'ar 2/5/1999 23:27'! popMatrix ^transformer popMatrix! ! !B3DRenderEngine methodsFor: 'transforming' stamp: 'ar 2/5/1999 23:27'! pushMatrix ^transformer pushMatrix! ! !B3DRenderEngine methodsFor: 'transforming'! rotateBy: aRotation ^transformer rotateBy: aRotation! ! !B3DRenderEngine methodsFor: 'transforming' stamp: 'ar 2/15/1999 02:54'! scaleBy: value ^transformer scaleBy: value! ! !B3DRenderEngine methodsFor: 'transforming'! transformBy: aTransformation ^transformer transformBy: aTransformation! ! !B3DRenderEngine methodsFor: 'transforming' stamp: 'ar 2/4/1999 03:56'! translateBy: aVector ^transformer translateBy: aVector! ! !B3DRenderEngine methodsFor: 'initialize' stamp: 'ar 2/16/1999 01:46'! destroy "Utility - destroy all resources associated with any part of the engine" transformer destroy. shader destroy. clipper destroy. rasterizer destroy.! ! !B3DRenderEngine methodsFor: 'initialize' stamp: 'ar 2/16/1999 01:45'! finish "Flush the pipeline and force changes to the output medium" self flush. rasterizer finish.! ! !B3DRenderEngine methodsFor: 'initialize' stamp: 'ar 2/5/1999 21:34'! flush "Flush the entire pipeline" transformer flush. shader flush. clipper flush. rasterizer flush.! ! !B3DRenderEngine methodsFor: 'initialize' stamp: 'ar 4/18/1999 00:35'! initialize engine _ self. "Obviously ;-)" vertexBuffer _ B3DVertexBuffer new. transformer _ self class transformer engine: self. shader _ self class shader engine: self. clipper _ self class clipper engine: self. rasterizer _ self class rasterizer engine: self. self materialColor: Color white.! ! !B3DRenderEngine methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:51'! reset vertexBuffer reset. transformer reset. shader reset. clipper reset. rasterizer reset. self materialColor: Color white.! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/15/1999 20:24'! addLight: aLightSource "Add the given light source to the engine. Return a handle that can be used to modify the light source later on" ^shader addLight: (aLightSource transformedBy: transformer)! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/15/1999 20:25'! removeLight: lightHandle "Remove the light with the given handle from the engine." ^shader removeLight: lightHandle! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:30'! trackAmbientColor ^vertexBuffer trackAmbientColor! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:30'! trackAmbientColor: aBoolean ^vertexBuffer trackAmbientColor: aBoolean! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:31'! trackDiffuseColor ^vertexBuffer trackDiffuseColor! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:30'! trackDiffuseColor: aBoolean ^vertexBuffer trackDiffuseColor: aBoolean! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:31'! trackEmissionColor ^vertexBuffer trackEmissionColor! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:30'! trackEmissionColor: aBoolean ^vertexBuffer trackEmissionColor: aBoolean! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:31'! trackSpecularColor ^vertexBuffer trackSpecularColor! ! !B3DRenderEngine methodsFor: 'shading' stamp: 'ar 2/8/1999 02:30'! trackSpecularColor: aBoolean ^vertexBuffer trackSpecularColor: aBoolean! ! !B3DRenderEngine methodsFor: 'indexed primitives' stamp: 'ar 11/7/1999 18:12'! drawIndexedLines: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray vertexBuffer reset. vertexBuffer primitive: 4. vertexBuffer loadIndexed: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray. ^self renderPrimitive.! ! !B3DRenderEngine methodsFor: 'indexed primitives' stamp: 'ar 11/7/1999 18:12'! drawIndexedQuads: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray vertexBuffer reset. vertexBuffer primitive: 6. vertexBuffer loadIndexed: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray. ^self renderPrimitive.! ! !B3DRenderEngine methodsFor: 'indexed primitives' stamp: 'ar 11/7/1999 18:12'! drawIndexedTriangles: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray vertexBuffer reset. vertexBuffer primitive: 5. vertexBuffer loadIndexed: indexArray vertices: vertexArray normals: normalArray colors: colorArray texCoords: texCoordArray. ^self renderPrimitive.! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:20'! clipRect "Return the current clipRect" ^rasterizer clipRect! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:20'! clipRect: aRectangle "Set the current clipRect" ^rasterizer clipRect: aRectangle! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 4/17/1999 23:12'! getClipper "Private. Return the clipper used with this engine." ^clipper! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 4/17/1999 23:13'! getRasterizer "Private. Return the rasterizer used with this engine." ^rasterizer! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 4/17/1999 23:12'! getShader "Private. Return the shader used with this engine." ^shader! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 4/17/1999 23:12'! getTransformer "Private. Return the transformer used with this engine." ^transformer! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 4/17/1999 23:11'! getVertexBuffer "Private. Return the vertex buffer used with this engine." ^vertexBuffer! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:19'! target "Return the rendering target" ^rasterizer target! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:19'! target: aForm "Set the rendering target" ^rasterizer target: aForm! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:18'! viewportOffset "Return the offset for the viewport" ^rasterizer viewportOffset! ! !B3DRenderEngine methodsFor: 'private-access' stamp: 'ar 5/26/2000 15:16'! viewportOffset: aPoint "Set the offset for the viewport" ^rasterizer viewportOffset: aPoint! ! !B3DRenderEngine methodsFor: 'picking' stamp: 'ar 4/18/1999 02:28'! asPickerAt: aPoint ^self asPickerAt: aPoint extent: 1@1! ! !B3DRenderEngine methodsFor: 'picking' stamp: 'ar 4/17/1999 23:56'! asPickerAt: aPoint extent: extentPoint | picker | picker _ B3DPickerEngine new. picker loadFrom: self. picker pickAt: aPoint extent: extentPoint. ^picker! ! !B3DRenderEngine methodsFor: 'picking' stamp: 'ar 2/27/2000 20:12'! pickingMatrixAt: aPoint extent: extentPoint "Return a matrix for picking at the given point using the given extent." ^self pickingMatrixFor: self viewport at: aPoint extent: extentPoint! ! !B3DRenderEngine methodsFor: 'picking' stamp: 'ar 2/27/2000 20:10'! pickingMatrixFor: vp at: aPoint extent: extentPoint "Return a matrix for picking at the given point using the given extent." | m scaleX scaleY ofsX ofsY | scaleX _ vp width / extentPoint x. scaleY _ vp height / extentPoint y. ofsX _ (vp width + (2.0 * (vp origin x - aPoint x))) / extentPoint x. ofsY _ (vp height + (2.0 * (aPoint y - vp corner y))) / extentPoint y. m _ B3DMatrix4x4 identity. m a11: scaleX; a22: scaleY. m a14: ofsX; a24: ofsY. ^m! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:23'! hasProperty: propName "Answer whether the receiver has the given property. Deemed to have it only if I have a property dictionary entry for it and that entry is neither nil nor false" self valueOfProperty: propName ifAbsent:[^false]. ^true! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:25'! properties ^properties ifNil:[properties _ IdentityDictionary new].! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:23'! removeProperty: propName self valueOfProperty: propName ifAbsent:[^self]. self properties removeKey: propName.! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:22'! setProperty: propName toValue: aValue aValue ifNil: [^ self removeProperty: propName]. self properties at: propName put: aValue.! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:24'! valueOfProperty: propName ^self valueOfProperty: propName ifAbsent:[nil]! ! !B3DRenderEngine methodsFor: 'properties' stamp: 'ar 11/7/1999 18:36'! valueOfProperty: propName ifAbsent: aBlock properties == nil ifTrue: [^ aBlock value]. ^properties at: propName ifAbsent: aBlock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DRenderEngine class instanceVariableNames: ''! !B3DRenderEngine class methodsFor: 'instance creation' stamp: 'ar 5/26/2000 15:10'! defaultForPlatformOn: aForm "Return the render engine that is most appropriate for the current host platform." (B3DHardwareEngine isAvailableFor: aForm) ifTrue:[^B3DHardwareEngine newOn: aForm]. (B3DPrimitiveEngine isAvailableFor: aForm) ifTrue:[^B3DPrimitiveEngine newOn: aForm]. ^B3DRenderEngine newOn: aForm! ! !B3DRenderEngine class methodsFor: 'instance creation'! new ^super new initialize! ! !B3DRenderEngine class methodsFor: 'instance creation' stamp: 'ar 5/26/2000 15:49'! newOn: aForm ^(self new) target: aForm; yourself! ! !B3DRenderEngine class methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:37'! clipper "Return the transformer to use with this engine" ^B3DVertexClipper! ! !B3DRenderEngine class methodsFor: 'accessing' stamp: 'ar 4/18/1999 05:27'! rasterizer "Return the rasterizer to use with this engine" ^B3DSimulRasterizer! ! !B3DRenderEngine class methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:37'! shader "Return the shader to use with this engine" ^B3DVertexShader! ! !B3DRenderEngine class methodsFor: 'accessing' stamp: 'ar 2/14/1999 01:37'! transformer "Return the transformer to use with this engine" ^B3DVertexTransformer! ! !B3DRenderEngine class methodsFor: 'testing' stamp: 'ar 2/14/1999 01:39'! isAvailable "Return true if this engine is available (e.g., all of its parts are avaiable)" ^(self transformer isAvailable and:[ self shader isAvailable and:[ self clipper isAvailable and:[ self rasterizer isAvailable]]])! ! !B3DRenderEngine class methodsFor: 'testing' stamp: 'ar 2/16/1999 17:34'! isAvailableFor: anOutputMedium "Return true if this engine is available for the given output medium" ^(self transformer isAvailableFor: anOutputMedium) and:[ (self shader isAvailableFor: anOutputMedium) and:[ (self clipper isAvailableFor: anOutputMedium) and:[ (self rasterizer isAvailableFor: anOutputMedium)]]]! ! B3DFloatArray variableWordSubclass: #B3DRotation instanceVariableNames: '' classVariableNames: 'B3DIdentityRotation ' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DRotation commentStamp: '' prior: 0! I represent general 3d rotations by using Unit-Quaternions. Unit-Quaternions are one of the best available representation for rotations in computer graphics because they provide an easy way of doing arithmetic with them and also because they allow us to use spherical linear interpolation (so-called "slerps") of rotations. Indexed Variables: a the real part of the quaternion b the first imaginary part of the quaternion c the second imaginary part of the quaternion d the third imaginary part of the quaternion ! !B3DRotation methodsFor: 'initialize' stamp: 'ar 2/1/1999 22:02'! a: aValue b: bValue c: cValue d: dValue self a: aValue. self b: bValue. self c: cValue. self d: dValue. (aValue < 0.0) ifTrue:[self *= -1.0]. self normalize.! ! !B3DRotation methodsFor: 'initialize' stamp: 'ar 2/1/1999 22:02'! angle: anAngle axis: aVector3 self radiansAngle: anAngle degreesToRadians axis: aVector3 ! ! !B3DRotation methodsFor: 'initialize' stamp: 'ar 2/1/1999 22:02'! from: startVector to: endVector "Create a rotation from startVector to endVector" | axis cos sin | axis := startVector cross: endVector. cos := (startVector dot: endVector) arcCos. sin := axis length. axis safelyNormalize. self a: cos b: axis x * sin c: axis y * sin d: axis z * sin. ! ! !B3DRotation methodsFor: 'initialize' stamp: 'ar 2/1/1999 22:03'! radiansAngle: anAngle axis: aVector3 | angle sin cos | angle := anAngle / 2.0. cos := angle cos. sin := angle sin. self a: cos b: aVector3 x * sin c: aVector3 y * sin d: aVector3 z * sin.! ! !B3DRotation methodsFor: 'initialize'! setIdentity ^self loadFrom: B3DIdentityRotation! ! !B3DRotation methodsFor: 'initialize' stamp: 'ar 2/1/1999 22:03'! x: xValue y: yValue z: zValue a: anAngle | angle sin cos | angle := (anAngle degreesToRadians) / 2.0. cos := angle cos. sin := angle sin. self a: cos b: xValue * sin c: yValue * sin d: zValue * sin! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:59'! a ^self at: 1! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:59'! a: aFloat self at: 1 put: aFloat! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 22:04'! angle ^(self a arcCos * 2.0 radiansToDegrees)! ! !B3DRotation methodsFor: 'accessing'! angle: newAngle self angle: newAngle axis: self axis! ! !B3DRotation methodsFor: 'accessing'! axis | sinAngle | sinAngle := self a arcCos sin. sinAngle isZero ifTrue:[^B3DVector3 zero]. ^B3DVector3 x: (self b / sinAngle) y: (self c / sinAngle) z: (self d / sinAngle)! ! !B3DRotation methodsFor: 'accessing'! axis: newAxis self angle: self angle axis: newAxis! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:59'! b ^self at: 2! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 22:00'! b: aFloat self at: 2 put: aFloat! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:59'! c ^self at: 3! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 22:00'! c: aFloat self at: 3 put: aFloat! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:59'! d ^self at: 4! ! !B3DRotation methodsFor: 'accessing' stamp: 'ar 2/1/1999 22:00'! d: aFloat self at: 4 put: aFloat! ! !B3DRotation methodsFor: 'arithmetic' stamp: 'ar 2/1/1999 22:05'! * aRotation "Multiplying two rotations is the same as concatenating the two rotations." | v1 v2 v3 vv | v1 := self bcd * aRotation a. v2 := aRotation bcd * self a. v3 := aRotation bcd cross: self bcd. vv := v1 + v2 + v3. ^B3DRotation a: (self a * aRotation a) - (self bcd dot: aRotation bcd) b: vv x c: vv y d: vv z! ! !B3DRotation methodsFor: 'arithmetic' stamp: 'ar 2/1/1999 22:06'! negated "Negating a quaternion is the same as reversing the angle of rotation" ^B3DRotation a: self a negated b: self b c: self c d: self d! ! !B3DRotation methodsFor: 'arithmetic' stamp: 'ar 9/17/1999 12:43'! normalize "Normalize the receiver. Note that the actual angle (a) determining the amount of rotation is fixed, since we do not want to modify angles. This leads to: a^2 + b^2 + c^2 + d^2 = 1. b^2 + c^2 + d^2 = 1 - a^2. Note also that the angle (a) can not exceed 1.0 (due its creation by cosine) and if it is 1.0 we have exactly the unit quaternion ( 1, [ 0, 0, 0]). " | oneMinusASquared length | oneMinusASquared := 1.0 - (self a squared). (oneMinusASquared < 1.0e-10) ifTrue:[^self setIdentity]. length := ((self b squared + self c squared + self d squared) / oneMinusASquared) sqrt. length = 0.0 ifTrue:[^self setIdentity]. self b: self b / length. self c: self c / length. self d: self d / length. ! ! !B3DRotation methodsFor: 'converting'! asMatrix4x4 "Given a quaternion q = (a, [ b, c , d]) the rotation matrix can be calculated as | 1 - 2(cc+dd), 2(bc-da), 2(db+ca) | m = | 2(bc+da), 1 - 2(bb+dd), 2(cd-ba) | | 2(db-ca), 2(cd+ba), 1 - 2(bb+cc) | " | a b c d m bb cc dd bc cd db ba ca da | a _ self a. b _ self b. c _ self c. d _ self d. bb := (b * b). cc := (c * c). dd := (d * d). bc := (b * c). cd := (c * d). db := (d * b). ba := (b * a). ca := (c * a). da := (d * a). m := self matrixClass identity. m a11: 1.0 - (cc + dd * 2.0);a12: (bc - da * 2.0); a13: (db + ca * 2.0); a21: (bc + da * 2.0); a22: 1.0 - (bb + dd * 2.0);a23: (cd - ba * 2.0); a31: (db - ca * 2.0); a32: (cd + ba * 2.0); a33: 1.0 - (bb + cc * 2.0). ^m ! ! !B3DRotation methodsFor: 'converting' stamp: 'ar 2/1/1999 22:08'! normalized ^self copy normalize! ! !B3DRotation methodsFor: 'interpolating' stamp: 'jsp 2/25/1999 15:57'! interpolateTo: aRotation at: t "Spherical linear interpolation (slerp) from the receiver to aQuaternion" ^self slerpTo: aRotation at: t extraSpins: 0! ! !B3DRotation methodsFor: 'interpolating' stamp: 'ar 2/1/1999 22:08'! slerpTo: aRotation at: t "Spherical linear interpolation (slerp) from the receiver to aQuaternion" ^self slerpTo: aRotation at: t extraSpins: 0! ! !B3DRotation methodsFor: 'interpolating' stamp: 'ar 3/24/1999 14:58'! slerpTo: aRotation at: t extraSpins: spin "Sperical Linear Interpolation (slerp). Calculate the new quaternion when applying slerp from the receiver (t = 0.0) to aRotation (t = 1.0). spin indicates the number of extra rotations to be added. The code shown below is from Graphics Gems III" | cosT alpha beta flip theta phi sinT | alpha := t. flip := false. "calculate the cosine of the two quaternions on the 4d sphere" cosT := self dot: aRotation. "if aQuaternion is on the opposite hemisphere reverse the direction (note that in quaternion space two points describe the same rotation)" cosT < 0.0 ifTrue:[ flip := true. cosT := cosT negated]. "If the aQuaternion is nearly the same as I am use linear interpolation" cosT > 0.99999 ifTrue:[ "Linear Interpolation" beta := 1.0 - alpha ] ifFalse:[ "Spherical Interpolation" theta := cosT arcCos. phi := (spin * Float pi) + theta. sinT := theta sin. beta := (theta - (alpha * phi)) sin / sinT. alpha := (alpha * phi) sin / sinT]. flip ifTrue:[alpha := alpha negated]. ^B3DRotation a: (alpha * aRotation a) + (beta * self a) b: (alpha * aRotation b) + (beta * self b) c: (alpha * aRotation c) + (beta * self c) d: (alpha * aRotation d) + (beta * self d)! ! !B3DRotation methodsFor: 'printing' stamp: 'ar 2/1/1999 22:09'! printOn: aStream aStream nextPutAll: self class name; nextPut:$(; print: self angle; nextPut: Character space; print: self axis; nextPut:$).! ! !B3DRotation methodsFor: 'private'! bcd ^B3DVector3 x: self b y: self c z: self d! ! !B3DRotation methodsFor: 'private' stamp: 'ar 2/1/1999 22:10'! matrixClass ^B3DMatrix4x4! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DRotation class instanceVariableNames: ''! !B3DRotation class methodsFor: 'instance creation'! a: aValue b: bValue c: cValue d: dValue ^self new a: aValue b: bValue c: cValue d: dValue! ! !B3DRotation class methodsFor: 'instance creation'! angle: anAngle axis: aVector3 ^self new angle: anAngle axis: aVector3! ! !B3DRotation class methodsFor: 'instance creation'! axis: aVector3 angle: anAngle ^self angle: anAngle axis: aVector3! ! !B3DRotation class methodsFor: 'instance creation'! from: startVector to: endVector ^self new from: startVector to: endVector! ! !B3DRotation class methodsFor: 'instance creation'! identity ^self new setIdentity! ! !B3DRotation class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:32'! numElements ^4! ! !B3DRotation class methodsFor: 'instance creation'! radiansAngle: anAngle axis: aVector3 ^self new radiansAngle: anAngle axis: aVector3! ! !B3DRotation class methodsFor: 'instance creation'! x: xValue y: yValue z: zValue a: anAngle ^self new x: xValue y: yValue z: zValue a: anAngle! ! !B3DRotation class methodsFor: 'class initialization'! initialize "B3DRotation initialize" B3DIdentityRotation _ self new. B3DIdentityRotation floatAt: 1 put: 1.0.! ! B3DInplaceArray variableWordSubclass: #B3DRotationArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DRotationArray class instanceVariableNames: ''! !B3DRotationArray class methodsFor: 'instance creation' stamp: 'ar 5/4/2000 15:45'! contentsClass ^B3DRotation! ! B3DIndexedTriangleMesh subclass: #B3DSTriangleMesh instanceVariableNames: 'edgeFlags smoothFlags ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DSTriangleMesh commentStamp: '' prior: 0! I represent a mesh from Autodesk 3D Studio.! !B3DSTriangleMesh methodsFor: 'initialize' stamp: 'ar 2/7/1999 20:57'! from3DS: aDictionary | triList triSpec triSize tri flags | aDictionary isEmpty ifTrue:[^nil]. vertices _ aDictionary at: #vertexList. "matrix _ aDictionary at: #matrix ifAbsent:[nil]. matrix ifNotNil:[matrix quickTransformV3ArrayFrom: vertices to: vertices]." vtxTexCoords _ aDictionary at: #textureVertices ifAbsent:[nil]. triList _ aDictionary at: #triList. triSpec _ triList first. triSize _ triSpec size. faces _ B3DIndexedTriangleArray new: triSize. edgeFlags _ ByteArray new: triSize. 1 to: triSize do:[:i| tri _ (triSpec at: i) key. flags _ (triSpec at: i) value. faces at: i put: (B3DIndexedTriangle with: tri first with: tri second with: tri third). edgeFlags at: i put: flags]. triList second ifNotNil:[ smoothFlags _ WordArray new: triSize. triList second doWithIndex:[:smoothFlag :index| smoothFlags at: index put: smoothFlag]]. ! ! !B3DSTriangleMesh methodsFor: 'private' stamp: 'ar 2/15/1999 06:44'! collectSplitVertices: aSet "Collect the non smooth vertices into a Dictionary vertex index -> Dictionary smoothing group -> list of face indexes. " | face flag vtxIndex groups groupDict | groupDict _ Dictionary new: aSet size * 2. 1 to: faces size do:[:faceIndex| face _ faces at: faceIndex. flag _ smoothFlags at: faceIndex. 1 to: 3 do:[:j| vtxIndex _ face at: j. (aSet includes: vtxIndex) ifTrue:[ groups _ groupDict at: vtxIndex ifAbsentPut:[Dictionary new]. (groups at: flag ifAbsentPut:[OrderedCollection new]) add: faceIndex. ]. ]. ]. ^groupDict! ! !B3DSTriangleMesh methodsFor: 'private' stamp: 'ar 2/17/1999 15:59'! computeFunkyVertexNormals "Compute the vertex normals for the receiver. Don't split the faces so we'll get some funky lighting effects." vtxNormals _ super computeVertexNormals ! ! !B3DSTriangleMesh methodsFor: 'private' stamp: 'ar 2/17/1999 15:57'! computeVertexNormals "Compute the vertex normals for the receiver. Note: This is a multi pass process here - we may have to split up vertices" | set dict | set _ self detectNonSmoothVertices. set isEmpty ifFalse:[ "Collect the dictionary of vertices to split" dict _ self collectSplitVertices: set. "And actually split them" self splitVerticesFrom: dict. ]. "Now do the actual computation" ^super computeVertexNormals! ! !B3DSTriangleMesh methodsFor: 'private' stamp: 'ar 2/15/1999 06:54'! detectNonSmoothVertices "Detect all the vertices in the receiver that cannot be easily smoothed" | mask face flag vtxIndex out newMask | smoothFlags ifNil:[^#()]. mask _ WordArray new: vertices size. mask atAllPut: 16rFFFFFFFF. out _ Set new: 1000. "Leave us enough space to avoid collisions" 1 to: faces size do:[:i| face _ faces at: i. flag _ smoothFlags at: i. 1 to: 3 do:[:j| vtxIndex _ face at: j. newMask _ ((mask at: vtxIndex) bitAnd: flag). newMask = 0 ifTrue:[out add: vtxIndex]. mask at: vtxIndex put: newMask. ]. ]. ^out! ! !B3DSTriangleMesh methodsFor: 'private' stamp: 'ar 2/15/1999 06:49'! splitVerticesFrom: aDictionary "Split the non smooth vertices from the Dictionary vertex index -> Dictionary smoothing group -> list of face indexes. " | newVertices newColors newTexCoords nextIndex vtxIndex nValues skipAssoc faceList iFace | newVertices _ WriteStream with: vertices. vtxColors ifNotNil:[newColors _ WriteStream with: vtxColors]. vtxTexCoords ifNotNil:[newTexCoords _ WriteStream with: vtxTexCoords]. nextIndex _ vertices size. aDictionary associationsDo:[:vertexAssoc| vtxIndex _ vertexAssoc key. nValues _ vertexAssoc value size - 1. "We have to copy n values" newVertices next: nValues put: (vertices at: vtxIndex). newColors ifNotNil:[newColors next: nValues put: (vtxColors at: vtxIndex)]. newTexCoords ifNotNil:[newTexCoords next: nValues put: (vtxTexCoords at: vtxIndex)]. skipAssoc _ true. "Skip the first association - we can reuse the original vertex" vertexAssoc value associationsDo:[:smoothAssoc| skipAssoc ifFalse:[ faceList _ smoothAssoc value. nextIndex _ nextIndex + 1. faceList do:[:faceIndex| iFace _ faces at: faceIndex. 1 to: 3 do:[:i| (iFace at: i) = vtxIndex ifTrue:[iFace at: i put: nextIndex]]. faces at: faceIndex put: iFace. ]. ]. skipAssoc _ false. ]. ]. "Cleanup" vtxNormals _ nil. "Must be recomputed" vertices _ newVertices contents. newColors ifNotNil:[vtxColors _ newColors contents]. newTexCoords ifNotNil:[vtxTexCoords _ newTexCoords contents].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DSTriangleMesh class instanceVariableNames: ''! !B3DSTriangleMesh class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 21:26'! from3DS: aDictionary ^self new from3DS: aDictionary! ! Object subclass: #B3DScanner instanceVariableNames: 'aet fillList added lastIntersection nextIntersection objects spanBuffer bitBlt nFaces maxFaces maxEdges ' classVariableNames: 'DebugMode FlagContinueLeftEdge FlagContinueRightEdge FlagEdgeLeftMajor FlagEdgeRightMajor FlagFaceActive FlagFaceInitialized ' poolDictionaries: '' category: 'VMConstruction-B3DSimulator'! !B3DScanner methodsFor: 'initialize' stamp: 'ar 4/18/1999 07:59'! initialize aet _ B3DActiveEdgeTable new. fillList _ B3DFillList new. added _ B3DPrimitiveEdgeList new. lastIntersection _ B3DPrimitiveEdge new. nextIntersection _ B3DPrimitiveEdge new. objects _ OrderedCollection new.! ! !B3DScanner methodsFor: 'initialize' stamp: 'ar 4/18/1999 05:21'! setupObjects "Set up the list of objects (e.g., triangle inputs) by creating a linked list of objects which is sorted by the initial yValue of the tris." | lastObj | objects _ objects sortBy: [:obj1 :obj2| obj1 bounds origin sortsBefore: obj2 bounds origin]. lastObj _ nil. objects do:[:nextObj| nextObj reset. nextObj prevObj: lastObj. lastObj == nil ifFalse:[lastObj nextObj: nextObj]. lastObj _ nextObj. ]. lastObj == nil ifFalse:[lastObj nextObj: nil]. ! ! !B3DScanner methodsFor: 'public' stamp: 'ar 4/18/1999 05:15'! addObject: primObj objects add: primObj.! ! !B3DScanner methodsFor: 'public' stamp: 'kfr 6/26/2000 14:50'! bitBlt ^bitBlt! ! !B3DScanner methodsFor: 'public' stamp: 'ar 4/18/1999 05:29'! bitBlt: aBitBlt bitBlt _ aBitBlt.! ! !B3DScanner methodsFor: 'public' stamp: 'ar 4/18/1999 07:55'! mainLoop | yValue nextObjY nextEdgeY obj activeStart passiveStart scaledY | objects size = 0 ifTrue:[^self]. "No input" self setupObjects. "Sort objects and create linked list" nFaces _ maxFaces _ maxEdges _ 0. "Pre-fetch first object to start from" activeStart _ passiveStart _ objects at: 1. yValue _ nextEdgeY _ nextObjY _ passiveStart bounds origin y. [activeStart == nil and:[passiveStart == nil and:[aet isEmpty]]] whileFalse:[ "Add new objects if necessary" yValue = nextObjY ifTrue:[ "Make sure we add edges from newly created objects" nextEdgeY _ nextObjY. "Add new objects" [passiveStart notNil and:[passiveStart bounds origin y = nextObjY]] whileTrue:[passiveStart _ passiveStart nextObj]. passiveStart == nil ifTrue:[nextObjY _ 99999]"Some large value" ifFalse:[nextObjY _ passiveStart bounds origin y]. ]. "End of adding new objects" "Add new edges if necessary" yValue = nextEdgeY ifTrue:[ nextEdgeY _ nextObjY bitShift: 12. "Some VERY large value" scaledY _ (yValue+1) bitShift: 12. obj _ activeStart. [obj == passiveStart] whileFalse:[ [obj atEnd not and:[obj peekY < scaledY]] whileTrue:[self addEdgesFromFace: obj next at: yValue]. obj atEnd ifTrue:[ obj == activeStart ifTrue:[activeStart _ obj nextObj] ifFalse:[obj prevObj nextObj: obj nextObj]. ] ifFalse:[obj peekY < nextEdgeY ifTrue:[nextEdgeY _ obj peekY]]. obj _ obj nextObj. ]. nextEdgeY _ (nextEdgeY bitShift: -12). ]. added isEmpty ifFalse:[ "Merge new edges into AET" "Note: These may be lower half edges." B3DScanner doDebug ifTrue:[self validateAETOrder]. aet mergeEdgesFrom: added. B3DScanner doDebug ifTrue:[ self validateAETOrder. self validateEdgesFrom: aet]. added reset. "Clean up the list" ]. "This is the core loop." "[yValue < nextEdgeY and:[added isEmpty and:[aet isEmpty not]]] whileTrue:[" B3DScanner doDebug ifTrue:[yValue printString displayAt: 0@0]. "gather stats" maxEdges _ maxEdges max: aet size. maxFaces _ maxFaces max: nFaces. "Scan out the AET" aet isEmpty ifFalse:[ self clearSpanBufferAt: yValue. self scanAETAt: yValue. self drawSpanBufferAt: yValue. "Advance to next y and update AET" ]. yValue _ yValue + 1. aet isEmpty ifFalse:[self updateAETAt: yValue]. "]." ]. nFaces = 0 ifFalse:[self error: nFaces printString,' remaining faces'].! ! !B3DScanner methodsFor: 'public' stamp: 'ar 4/18/1999 05:31'! resetObjects objects _ OrderedCollection new.! ! !B3DScanner methodsFor: 'public' stamp: 'ar 4/18/1999 05:28'! spanBuffer: aBitmap spanBuffer _ aBitmap.! ! !B3DScanner methodsFor: 'aet adding' stamp: 'ar 4/18/1999 08:14'! addEdgesFromFace: face at: yValue "Add the two top edges from the given face to the aet. The top edges are (v0-v1) and (v0-v2) where (v0-v1) is the 'upper' half-edge of the triangle" | xValue index needMajor needMinor majorEdge minorEdge | face oneOverArea = 0.0 ifTrue:[^self]. needMinor _ needMajor _ true. "We need both edges" majorEdge _ minorEdge _ nil. xValue _ face vertex0 windowPosX. "Search the insertion list to merge the edges of the face" index _ added firstIndexForInserting: xValue. index _ added xValue: xValue from: index do:[:edge| (edge rightFace == nil and:[ "Note: edge vertex0 == face vertex0 should be the case for most meshes. But since it is advantegous for the scanner to have two faces per edge we're also checking for the actual vertex values." edge vertex0 == face vertex0 or:[ edge vertex0 rasterPos = face vertex0 rasterPos]]) ifTrue:[ "This edge is a possible candidate for adding the face" (needMajor and:["See above comment" edge vertex1 == face vertex2 or:[ edge vertex1 rasterPos = face vertex2 rasterPos]]) ifTrue:[ majorEdge _ edge. edge rightFace: face. edge flags: (edge flags bitOr: FlagEdgeRightMajor). nFaces _ nFaces + 1. needMinor ifFalse:[ ^self adjustFace: face major: majorEdge minor: minorEdge]. "Done." needMajor _ false. ] ifFalse:[ (needMinor and:["See above comment" edge vertex1 == face vertex1 or:[ edge vertex1 rasterPos = face vertex1 rasterPos]]) ifTrue:[ minorEdge _ edge. edge rightFace: face. edge flags: (edge flags bitOr: FlagContinueRightEdge). needMajor ifFalse:[ ^self adjustFace: face major: majorEdge minor: minorEdge]. "Done." needMinor _ false. ]. ]. ]. ]. "Need to add new edges. NOTE: index already points to the right point for insertion." needMajor ifTrue:[ majorEdge _ B3DPrimitiveEdge new. majorEdge v0: face vertex0 v1: face vertex2. majorEdge nLines = 0 ifTrue:[^self]. "Horizontal edge" majorEdge leftFace: face. majorEdge initializePass1. majorEdge flags: (majorEdge flags bitOr: FlagEdgeLeftMajor). nFaces _ nFaces + 1. ]. needMinor ifTrue:[ minorEdge _ B3DPrimitiveEdge new. minorEdge v0: face vertex0 v1: face vertex1. minorEdge leftFace: face. minorEdge flags: FlagContinueLeftEdge. "Note: If the (upper) minor edge is horizontal, use the lower one. Note: The lower minor edge cannot be horizontal if the major one isn't" minorEdge nLines = 0 ifTrue:[ needMajor ifTrue:[added add: majorEdge beforeIndex: index]. minorEdge _ self addLowerEdge: minorEdge fromFace: face. minorEdge nLines = 0 ifTrue:[self error:'Minor edge is horizontal']. ^self adjustFace: face major: majorEdge minor: minorEdge]. minorEdge flags: FlagContinueLeftEdge. minorEdge initializePass1. minorEdge xValue = xValue ifFalse:[self error:'Problem with minor edge']. minorEdge nLines = 0 ifTrue:[self error:'Minor edge is horizontal']. ]. needMajor & needMinor ifTrue:[ added add: majorEdge and: minorEdge beforeIndex: index. ] ifFalse:[ needMajor ifTrue:[added add: majorEdge beforeIndex: index] ifFalse:[added add: minorEdge beforeIndex: index]. ]. ^self adjustFace: face major: majorEdge minor: minorEdge.! ! !B3DScanner methodsFor: 'aet adding' stamp: 'ar 4/18/1999 05:56'! addLowerEdge: oldEdge fromFace: face "Add the lower edge (v1-v2) from the given face. Return the newly created edge." | index minorEdge xValue | xValue _ face vertex1 windowPosX. index _ added firstIndexForInserting: xValue. index _ added xValue: xValue from: index do:[:edge| (edge rightFace == nil and:[ "See the comment in #addEdgesFromFace:at:" (edge vertex0 == face vertex1 and:[edge vertex1 == face vertex2]) or:[ edge vertex0 rasterPos = face vertex1 rasterPos and:[ edge vertex1 rasterPos = face vertex2 rasterPos]]]) ifTrue:[ "Adjust the left or right edge of the face" face leftEdge == oldEdge ifTrue:[face leftEdge: edge] ifFalse:[face rightEdge: edge]. edge rightFace: face. ^edge ]. ]. "Need to add new edge. NOTE: index already points to the right point for insertion." minorEdge _ B3DPrimitiveEdge new. minorEdge v0: face vertex1 v1: face vertex2. minorEdge nLines = 0 ifTrue:[^self]. "Horizontal" "Adjust left/right edge of the face" face leftEdge == oldEdge ifTrue:[face leftEdge: minorEdge] ifFalse:[face rightEdge: minorEdge]. minorEdge leftFace: face. minorEdge initializePass1. added add: minorEdge beforeIndex: index. ^minorEdge! ! !B3DScanner methodsFor: 'aet adding' stamp: 'ar 4/8/1999 03:02'! adjustFace: face major: majorEdge minor: minorEdge "Set the left/right edge of the face to the appropriate edges" (majorEdge == nil or:[minorEdge == nil]) ifTrue:[^self error:'Edges must be non-nil']. majorEdge xValue = minorEdge xValue ifTrue:[ "Most likely case. Both edges start at the same point. Use dx/dy slope for determining which one is left and which one is right. NOTE: We have this already computed during face>>initializePass1. The value to use is the x increment at each scan line. NOTE2: There is also a border case when minorEdge is actually the lower edge of the triangle. If both xValues are equal, then the triangle is degenerate (e.g., it's area is zero) in which case the meaning of 'left' or 'right' does not matter at all (and can thus be handled by this simple test)." majorEdge xIncrement <= minorEdge xIncrement ifTrue:[ face leftEdge: majorEdge. face rightEdge: minorEdge] ifFalse:[ face leftEdge: minorEdge. face rightEdge: majorEdge]. ] ifFalse:[ "If the x values are not equal, simply use the edge with the smaller x value as 'left' edge" majorEdge xValue < minorEdge xValue ifTrue:[ face leftEdge: majorEdge. face rightEdge: minorEdge] ifFalse:[ face leftEdge: minorEdge. face rightEdge: majorEdge]. ].! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/18/1999 05:57'! adjustIntersectionsAt: yValue from: topEdge "The top face has changed. Adjust for possible intersections in the same scan line." | frontFace backFace | frontFace _ fillList first. "If frontFace is nil then the fillList is empty. If frontFace nextFace is nil then there is only one face in the list." (frontFace == nil or:[frontFace nextFace == nil]) ifTrue:[^self]. "Now, search the fill list until we reach the first face with minZ > face maxZ. Note that we have a linked list and can thus start from frontFace nextFace until we reach the end of the face list (nil)." backFace _ frontFace nextFace. [backFace == nil] whileFalse:[ (self checkIntersectionOf: frontFace with: backFace at: yValue edge: topEdge) ifFalse:[^self]. "Aborted." backFace _ backFace nextFace. ].! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/13/1999 01:00'! checkIntersectionOf: frontFace with: backFace at: yValue edge: leftEdge "Compute the possible intersection of frontFace and backFace at the given y value. Store the earliest intersection in nextIntersection. Return false if the face enumeration should be aborted, true otherwise. leftEdge is the edge defining the left-most boundary for possible intersections (e.g., all intersections have to be >= leftEdge xValue)" | floatX floatY frontZ backZ xValue rightX | backFace minZ >= frontFace maxZ ifTrue:[^false]. "Abort. Everything behind will be further away." "Check for shared edge of faces" frontFace leftEdge == backFace leftEdge ifTrue:[^true]. "Proceed." frontFace rightEdge == backFace rightEdge ifTrue:[^true]. "Proceed." "Check for newly created front face" (frontFace leftEdge xValue bitShift: -12) = (frontFace rightEdge xValue bitShift: -12) ifTrue:[^false]. "Abort" "Check for newly created back face" (backFace leftEdge xValue bitShift: -12) = (backFace rightEdge xValue bitShift: -12) ifTrue:[^true]. "Proceed" "Compute the z value of either frontFace or backFace depending on whose right edge x value is less (so we test a point that is inside both faces)" floatY _ yValue. frontFace rightEdge xValue <= backFace rightEdge xValue ifTrue:[ "Use frontFace rightEdge as reference value" frontZ _ frontFace rightEdge zValue. rightX _ frontFace rightEdge xValue. floatX _ rightX / 4096.0. backZ _ backFace zValueAtX: floatX y: floatY. ] ifFalse:[ "Use backFace rightEdge as reference value" backZ _ backFace rightEdge zValue. rightX _ backFace rightEdge xValue. floatX _ rightX / 4096.0. frontZ _ frontFace zValueAtX: floatX y: floatY. ]. backZ < frontZ ifTrue:[ "Found a possible intersection." xValue _ self computeIntersectionOf: frontFace with: backFace at: yValue ifError: leftEdge xValue. "The following tests for numerical inaccuracies" xValue > rightX ifTrue:[xValue _ rightX]. xValue < leftEdge xValue ifTrue:[ "In theory, this cannot happen. We may, however, have slight numerical inaccuracies here, too. Conceptually, we treat these intersections as if they occured immediately at the same fractional pixel in the scan line." xValue _ leftEdge xValue]. (xValue bitShift: -12) = (leftEdge xValue bitShift: -12) ifTrue:[ "Intersections at the same pixel are ignored. Process it at the next pixel. NOTE: This step is incredibly important!! It is by ignoring intersections at the same pixel that we can never run in an endless repetition of intersections at the same pixel value." xValue _ (leftEdge xValue bitShift: -12) + 1 bitShift: 12. ]. xValue < nextIntersection xValue ifTrue:[ nextIntersection xValue: xValue. nextIntersection leftFace: frontFace. nextIntersection rightFace: backFace. ]. ]. ^true "proceed"! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/8/1999 03:14'! computeIntersectionOf: frontFace with: backFace at: yValue ifError: errorValue "Compute the z intersection at the given y value" | dx1 dz1 dx2 dz2 px pz det det2 | dx1 _ frontFace rightEdge xValue - frontFace leftEdge xValue. dz1 _ frontFace rightEdge zValue - frontFace leftEdge zValue. dx2 _ backFace rightEdge xValue - backFace leftEdge xValue. dz2 _ backFace rightEdge zValue - backFace leftEdge zValue. px _ backFace leftEdge xValue - frontFace leftEdge xValue. pz _ backFace leftEdge zValue - frontFace leftEdge zValue. "Solve the linear equation using cramers rule" det _ (dx1 * dz2) - (dx2 * dz1). det = 0.0 ifTrue:[^errorValue]. "det1 _ (dx1 * pz) - (px * dz1)." det2 _ (px * dz2) - (pz * dx2). "det1 _ det1 / det." det2 _ det2 / det. ^frontFace leftEdge xValue + (dx1 * det2) truncated! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/8/1999 03:15'! isOnTop: edge at: yValue "Return true if the edge is on top of the current front face" | topFace floatX floatY | topFace _ fillList first. topFace == nil ifTrue:[^true]. "Note: It is important to return true if the edge is shared by the top face" (edge leftFace == topFace or:[edge rightFace == topFace]) ifTrue:[^true]. floatX _ edge xValue / 4096.0. floatY _ yValue. ^edge zValue < (fillList first zValueAtX: floatX y: floatY).! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/18/1999 07:23'! scanAETAt: yValue "Scan out and draw the active edge table" | leftEdge rightEdge tmp | aet reset. aet atEnd ifTrue:[^nil]. "Note the following is debug code that allows restarting this method without getting confused by the face flags. In release mode, having faces in the fillList here would be either an error or due to clipping at the right boundary." fillList do:[:face| face flags: (face flags bitXor: FlagFaceActive)]. fillList reset. nextIntersection xValue: 16r3FFFFFFF. "Out of reach" leftEdge _ aet next. "No do the AET scan" [aet atEnd] whileFalse:[ "The left edge here is always a top edge. Toggle its fills." self toggleTopFillsOf: leftEdge at: yValue. "After we got a new top face we have to adjust possible intersections." self adjustIntersectionsAt: yValue from: leftEdge. "Search for the next top edge, which will be the right boundary." rightEdge _ self searchForNewTopEdgeFrom: leftEdge at: yValue. "And fill the stuff" self fillFrom: leftEdge to: rightEdge at: yValue. leftEdge _ rightEdge. "Use a new intersection edge if necessary" leftEdge == nextIntersection ifTrue:[ tmp _ nextIntersection. nextIntersection _ lastIntersection. lastIntersection _ tmp]. nextIntersection xValue: 16r3FFFFFFF "Must be waaaay off to the right ;-)" ]. self toggleBackFillsOf: leftEdge at: yValue validate: false. fillList isEmpty ifFalse:[self error:'FillList not empty'].! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/18/1999 05:59'! searchForNewTopEdgeFrom: leftEdge at: yValue "Find the next top edge in the AET. Note: We have to make sure that intersection edges are returned appropriately." | edge topFace | topFace _ fillList first. topFace == nil ifTrue:[^aet next]. "Next edge must be top" [aet atEnd] whileFalse:[ "Check if we have an intersection first." nextIntersection xValue <= aet peek xValue ifTrue:[^nextIntersection]. edge _ aet next. "Check if the edge is on top" (self isOnTop: edge at: yValue) ifTrue:[^edge]. "If the edge is not on top, toggle the (back) fills of it" self toggleBackFillsOf: edge at: yValue validate: true. ]. ^nil! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/7/1999 04:40'! toggleBackFillsOf: edge at: yValue validate: aBool "Toggle the faces of the (back) edge" | face | face _ edge leftFace. (face flags anyMask: FlagFaceActive) ifTrue:[ (aBool and:[face == fillList first]) ifTrue:[self error:'Not a back face']. fillList remove: face] ifFalse:[ fillList addBack: face. "Check for possible intersections of back and front face" self checkIntersectionOf: fillList first with: face at: yValue edge: edge]. face flags: (face flags bitXor: FlagFaceActive). face _ edge rightFace. face == nil ifTrue:[^self]. (face flags anyMask: FlagFaceActive) ifTrue:[ (aBool and:[face == fillList first]) ifTrue:[self error:'Not a back face']. fillList remove: face] ifFalse:[ fillList addBack: face. "Check for possible intersections of back and front face" self checkIntersectionOf: fillList first with: face at: yValue edge: edge]. face flags: (face flags bitXor: FlagFaceActive). ! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/5/1999 23:44'! toggleIntersectionEdge: edge "Toggle the faces of the given intersection edge. This is a *very* special case." fillList first == edge leftFace ifFalse:[^self error:'Left face of intersection edge not top face']. fillList remove: edge rightFace. fillList addFront: edge rightFace. ! ! !B3DScanner methodsFor: 'aet scanning' stamp: 'ar 4/18/1999 06:01'! toggleTopFillsOf: edge at: yValue "Toggle the faces of the (new top) edge. We must carefully treat each of the following cases: 1) rightFace notNil (e.g., two faces) a) rightFace active ~= leftFace active => simply swap leftFace and rightFace in the face list b) rightFace active not & leftFace active not => edge defines new boundary entry; check for minimal dxdz and insert in order c) rightFace active & leftFace active => edge defines boundary exit; search all faces for minimal z value 2) rightFace isNil (e.g., single face) a) leftFace active => edge defines boundary exit; see 1c) b) leftFace active not => edge defines boundary entry; simply put it on top. " | leftFace rightFace xorMask noTest | edge == lastIntersection ifTrue:[^self toggleIntersectionEdge: edge]. noTest _ true. leftFace _ edge leftFace. rightFace _ edge rightFace. rightFace == nil ifTrue:[ (leftFace flags anyMask: FlagFaceActive) ifTrue:[ leftFace == fillList first | noTest ifFalse:[self error:'Oops']. fillList remove: leftFace. fillList searchForNewTopAtX: edge xValue y: yValue] ifFalse:[ fillList addFront: leftFace]. leftFace flags: (leftFace flags bitXor: FlagFaceActive). ^self]. "rightFace notNil" xorMask _ leftFace flags bitXor: rightFace flags. (xorMask anyMask: FlagFaceActive) ifTrue:[ "Simply swap" (leftFace flags anyMask: FlagFaceActive) ifTrue:[ leftFace == fillList first | noTest ifFalse:[self error:'Oops']. fillList remove: leftFace. fillList addFront: rightFace] ifFalse:[ rightFace == fillList first | noTest ifFalse:[self error:'Oops']. fillList remove: rightFace. fillList addFront: leftFace]. ] ifFalse:["rightFace active = leftFace active" (leftFace flags anyMask: FlagFaceActive) ifTrue:[ (leftFace == fillList or:[rightFace == fillList first]) | noTest ifFalse:[self error:'Oops']. fillList remove: leftFace. fillList remove: rightFace. fillList searchForNewTopAtX: edge xValue y: yValue. ] ifFalse:[ leftFace dzdx <= rightFace dzdx ifTrue:[ fillList addFront: leftFace. fillList addBack: rightFace] ifFalse:[ fillList addFront: rightFace. fillList addBack: leftFace]. ]. ]. leftFace flags: (leftFace flags bitXor: FlagFaceActive). rightFace flags: (rightFace flags bitXor: FlagFaceActive). ! ! !B3DScanner methodsFor: 'aet updating' stamp: 'ar 4/18/1999 06:02'! updateAETAt: yValue "Advance all entries in the AET by one scan line step" | edge count | aet reset. [aet atEnd] whileFalse:[ edge _ aet next. count _ edge nLines - 1. count = 0 ifTrue:[ "Remove the edge from the AET. If the continuation flag is set, create new (lower) edge(s)." (edge vertex1 windowPosY bitShift: -12) = yValue ifFalse:[self error:'Edge exceeds range']. aet removeFirst. (edge flags anyMask: FlagContinueLeftEdge) ifTrue:[self addLowerEdge: edge fromFace: edge leftFace]. (edge flags anyMask: FlagContinueRightEdge) ifTrue:[self addLowerEdge: edge fromFace: edge rightFace]. (edge flags anyMask: FlagEdgeLeftMajor) ifTrue:[nFaces _ nFaces - 1]. (edge flags anyMask: FlagEdgeRightMajor) ifTrue:[nFaces _ nFaces - 1]. ] ifFalse:[ "Edge continues. Adjust the number of scan lines remaining and update the incremental values. Make sure that the sorting order of the AET is not getting confused." edge nLines: count. "# of scan lines" edge stepToNextLine. "update incremental values" aet resortFirst. "make sure edge is sorted right" ]. ]. ! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 4/18/1999 05:45'! clearSpanBufferAt: yValue spanBuffer primFill: 0.! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 4/18/1999 06:46'! drawSpanBufferAt: yValue | leftX rightX | leftX _ aet first xValue bitShift: -12. rightX _ aet last xValue bitShift: -12. bitBlt copyBitsFrom: leftX to: rightX at: yValue.! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 4/18/1999 06:55'! fillFrom: leftEdge to: rightEdge at: yValue | face | leftEdge xValue >= rightEdge xValue ifTrue:[^self]. "Nothing to do" face _ fillList first. face == nil ifTrue:[^self]. face texture == nil ifTrue:[self rgbFill: face from: leftEdge to: rightEdge at: yValue] ifFalse:[self rgbstwFill: face from: leftEdge to: rightEdge at: yValue]! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 4/18/1999 06:48'! rgbFill: face from: leftEdge to: rightEdge at: yValue "Using only RGB (no alpha no textures)" | leftX rightX floatY floatX rValue gValue bValue pv rAttr gAttr bAttr | "Note: We always sample at pixel centers. If the edges do not include this pixel center, do nothing. Otherwise fill from leftX to rightX, including both pixels." leftX _ (leftEdge xValue bitShift: -12) + 1. rightX _ rightEdge xValue bitShift: -12. leftX < 0 ifTrue:[leftX _ 0]. rightX >= spanBuffer size ifTrue:[rightX _ spanBuffer size-1]. leftX > rightX ifTrue:[^self]. B3DScanner doDebug ifTrue:[ "Sanity check." (face leftEdge xValue > leftEdge xValue) ifTrue:[ (face rightEdge xValue < rightEdge xValue) ifTrue:[self error:'Filling outside face'] ifFalse:[self error:'Filling left of face']. ] ifFalse:[(face rightEdge xValue < rightEdge xValue) ifTrue:[self error:'Filling right of face']]. ]. (face flags anyMask: FlagFaceInitialized) ifFalse:[ face initializePass2. face flags: (face flags bitOr: FlagFaceInitialized)]. "@@: Sampling problem!!" floatY _ yValue + 0.5. floatX _ leftX. rAttr _ face attributes. gAttr _ rAttr nextAttr. bAttr _ gAttr nextAttr. rValue _ (face attrValue: rAttr atX: floatX y: floatY). gValue _ (face attrValue: gAttr atX: floatX y: floatY). bValue _ (face attrValue: bAttr atX: floatX y: floatY). [leftX <= rightX] whileTrue:[ rValue _ rValue min: 255.0 max: 0.0. gValue _ gValue min: 255.0 max: 0.0. bValue _ bValue min: 255.0 max: 0.0. pv _ (bValue truncated) + (gValue truncated bitShift: 8) + (rValue truncated bitShift: 16). spanBuffer at: (leftX _ leftX+1) put: (pv bitOr: 4278190080). rValue _ rValue + rAttr dvdx. gValue _ gValue + gAttr dvdx. bValue _ bValue + bAttr dvdx]. ! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 4/18/1999 07:22'! rgbstwFill: face from: leftEdge to: rightEdge at: yValue "Using only RGB & STW (no alpha)" | leftX rightX floatY floatX rValue gValue bValue pv rAttr gAttr bAttr aAttr wAttr sAttr tAttr wValue sValue tValue texColor | "Note: We always sample at pixel centers. If the edges do not include this pixel center, do nothing. Otherwise fill from leftX to rightX, including both pixels." leftX _ (leftEdge xValue bitShift: -12) + 1. rightX _ rightEdge xValue bitShift: -12. leftX < 0 ifTrue:[leftX _ 0]. rightX >= spanBuffer size ifTrue:[rightX _ spanBuffer size-1]. leftX > rightX ifTrue:[^self]. B3DScanner doDebug ifTrue:[ "Sanity check." (face leftEdge xValue > leftEdge xValue) ifTrue:[ (face rightEdge xValue < rightEdge xValue) ifTrue:[self error:'Filling outside face'] ifFalse:[self error:'Filling left of face']. ] ifFalse:[(face rightEdge xValue < rightEdge xValue) ifTrue:[self error:'Filling right of face']]. ]. (face flags anyMask: FlagFaceInitialized) ifFalse:[ face initializePass2. face flags: (face flags bitOr: FlagFaceInitialized)]. "@@: Sampling problem!!" floatY _ yValue + 0.5. floatX _ leftX. rAttr _ face attributes. gAttr _ rAttr nextAttr. bAttr _ gAttr nextAttr. aAttr _ bAttr nextAttr. wAttr _ aAttr nextAttr. sAttr _ wAttr nextAttr. tAttr _ sAttr nextAttr. rValue _ (face attrValue: rAttr atX: floatX y: floatY). gValue _ (face attrValue: gAttr atX: floatX y: floatY). bValue _ (face attrValue: bAttr atX: floatX y: floatY). wValue _ (face attrValue: wAttr atX: floatX y: floatY). sValue _ (face attrValue: sAttr atX: floatX y: floatY). tValue _ (face attrValue: tAttr atX: floatX y: floatY). [leftX <= rightX] whileTrue:[ rValue _ rValue min: 255.0 max: 0.0. gValue _ gValue min: 255.0 max: 0.0. bValue _ bValue min: 255.0 max: 0.0. texColor _ self textureColor: face texture atS: (sValue / wValue) atT: (tValue / wValue). pv _ (bValue * texColor blue) truncated + ((gValue * texColor green) truncated bitShift: 8) + ((rValue * texColor red) truncated bitShift: 16). spanBuffer at: (leftX _ leftX+1) put: (pv bitOr: 4278190080). rValue _ rValue + rAttr dvdx. gValue _ gValue + gAttr dvdx. bValue _ bValue + bAttr dvdx. wValue _ wValue + wAttr dvdx. sValue _ sValue + sAttr dvdx. tValue _ tValue + tAttr dvdx].! ! !B3DScanner methodsFor: 'span drawing' stamp: 'ar 5/28/2000 12:19'! textureColor: aTexture atS: sValue atT: tValue "Return the interpolated color of the given texture at s/t" | w h fragS fragT sIndex tIndex peeker tex00 tex01 tex10 tex11 sFrac tFrac mixed | w _ aTexture width. h _ aTexture height. fragS _ w * sValue. fragT _ h * tValue. sIndex _ fragS truncated. tIndex _ fragT truncated. peeker _ BitBlt current bitPeekerFromForm: aTexture. tex00 _ (peeker pixelAt: (sIndex \\ w)@(tIndex \\ h)) asColorOfDepth: aTexture depth. tex01 _ (peeker pixelAt: (sIndex+1 \\ w)@(tIndex \\ h)) asColorOfDepth: aTexture depth. tex10 _ (peeker pixelAt: (sIndex \\ w)@(tIndex+1 \\ h)) asColorOfDepth: aTexture depth. tex11 _ (peeker pixelAt: (sIndex+1 \\ w)@(tIndex+1 \\ h)) asColorOfDepth: aTexture depth. sFrac _ fragS \\ 1.0. tFrac _ fragT \\ 1.0. mixed _ ((1.0 - tFrac) * (((1.0 - sFrac) * tex00 asB3DColor) + (sFrac * tex01 asB3DColor))) + (tFrac * (((1.0 - sFrac) * tex10 asB3DColor) + (sFrac * tex11 asB3DColor))). ^mixed! ! !B3DScanner methodsFor: 'misc' stamp: 'ar 4/6/1999 03:49'! validateAETOrder | last next | aet isEmpty ifTrue:[^self]. aet reset. last _ aet next. [aet atEnd] whileFalse:[ next _ aet next. last xValue <= next xValue ifFalse:[^self error:'AET is broken']. last _ next].! ! !B3DScanner methodsFor: 'misc' stamp: 'ar 4/7/1999 05:20'! validateEdgesFrom: aCollection "aCollection must contain two entries for each face." | faceNum face faces | faceNum _ 0. aCollection do:[:edge| edge leftFace ifNil:[self error:'Bad edge'] ifNotNil:[faceNum _ faceNum + 1]. edge rightFace ifNotNil:[faceNum _ faceNum + 1]. ]. faceNum \\ 2 = 0 ifTrue:[^self]. faces _ Bag new. aCollection do:[:edge| face _ edge leftFace. faces add: face. (aet indexOf: face leftEdge) = 0 ifTrue:[self error:'Left edge not in AET']. (aet indexOf: face rightEdge) = 0 ifTrue:[self error:'Right edge not in AET']. face _ edge rightFace. face == nil ifFalse:[ faces add: face. (aet indexOf: face leftEdge) = 0 ifTrue:[self error:'Left edge not in AET']. (aet indexOf: face rightEdge) = 0 ifTrue:[self error:'Right edge not in AET']. ]. ]. self error:'Something *IS* wrong here'.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DScanner class instanceVariableNames: ''! !B3DScanner class methodsFor: 'class initialization' stamp: 'ar 4/8/1999 18:30'! initialize "B3DScanner initialize" FlagContinueLeftEdge _ 1. FlagContinueRightEdge _ 2. FlagEdgeLeftMajor _ 4. FlagEdgeRightMajor _ 8. FlagFaceActive _ 1. FlagFaceInitialized _ 2.! ! !B3DScanner class methodsFor: 'instance creation' stamp: 'ar 4/4/1999 04:27'! new ^super new initialize! ! !B3DScanner class methodsFor: 'accessing' stamp: 'ar 4/18/1999 07:24'! doDebug ^DebugMode == true! ! !B3DScanner class methodsFor: 'accessing' stamp: 'ar 4/18/1999 07:25'! doDebug: aBool "B3DScanner doDebug: true" "B3DScanner doDebug: false" DebugMode _ aBool.! ! Object subclass: #B3DScene instanceVariableNames: 'box objects cameras lights materials defaultCamera clearColor ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Objects'! !B3DScene methodsFor: 'initialize' stamp: 'ti 3/28/2000 13:14'! from3DS: aDictionary "Remove the globals from the scene - the remaining objects are name->sceneObject " | globals constants ambient texture funkyNormals r1 | globals _ aDictionary at: #globals. constants _ globals at: #constants ifAbsent: [Dictionary new]. aDictionary removeKey: #globals. "Collect the scene objects and assign the names" objects _ OrderedCollection new. aDictionary associationsDo: [:assoc | objects add: ((B3DSceneObject from3DS: assoc value) name: assoc key)]. "Fetch the cameras and set a default camera" cameras _ globals at: #cameras. cameras isEmpty ifTrue: [defaultCamera _ B3DCamera new position: 0 @ 0 @ 0] ifFalse: [defaultCamera _ cameras at: cameras keys asSortedCollection first]. "Fetch the lights" lights _ globals at: #lights. "Add the ambient light if possible. Note: The name $AMBIENT$ is used in the keyframe section of the 3DS file. " ambient _ constants at: 'ambientColor' ifAbsent: [B3DColor4 r: 0.0 g: 0.0 b: 0.0 a: 0.0]. ambient ifNotNil: [lights at: '$AMBIENT$' put: (B3DAmbientLight color: ambient)]. "Fetch the background color" clearColor _ constants at: 'backgroundColor' ifAbsent: [Color white]. "Fetch the materials and replace names in sceneObjects by actual materials " materials _ globals at: #materials. "Compute the per vertex normals" funkyNormals _ self confirm: 'Do you want funky normals instead of accurate normals? (It will give the model a somewhat strange, but interesting look)'. 'Computing vertex normals' displayProgressAt: Sensor cursorPoint from: 0 to: objects size during: [:bar | objects doWithIndex: [:obj :index | bar value: index. obj material ifNotNil: [obj material: (materials at: obj material ifAbsent: [])]. funkyNormals ifTrue: [obj geometry computeFunkyVertexNormals] ifFalse: [obj geometry vertexNormals]]]. (self confirm: 'Do you want to use a texture with the model?') ifTrue: [Utilities informUser: 'Choose a rectangle with interesting stuff' during: [r1 _ Rectangle originFromUser: 128 @ 128. Sensor waitNoButton]. texture _ B3DTexture fromDisplay: r1. texture wrap: true. texture interpolate: false. texture envMode: 0]. objects do: [:obj | obj texture ifNotNil: [obj texture: texture]]! ! !B3DScene methodsFor: 'initialize' stamp: 'ar 2/17/1999 05:09'! initialize objects _ OrderedCollection new. cameras _ OrderedCollection new. lights _ OrderedCollection new. materials _OrderedCollection new. ! ! !B3DScene methodsFor: 'initialize' stamp: 'ti 3/28/2000 13:11'! withoutQuestionsFrom3DS: aDictionary "Remove the globals from the scene - the remaining objects are name->sceneObject " | globals constants ambient texture funkyNormals | globals _ aDictionary at: #globals. constants _ globals at: #constants ifAbsent: [Dictionary new]. aDictionary removeKey: #globals. "Collect the scene objects and assign the names" objects _ OrderedCollection new. aDictionary associationsDo: [:assoc | objects add: ((B3DSceneObject from3DS: assoc value) name: assoc key)]. "Fetch the cameras and set a default camera" cameras _ globals at: #cameras. cameras isEmpty ifTrue: [defaultCamera _ B3DCamera new position: 0 @ 0 @ 0] ifFalse: [defaultCamera _ cameras at: cameras keys asSortedCollection first]. "Fetch the lights" lights _ globals at: #lights. "Add the ambient light if possible. Note: The name $AMBIENT$ is used in the keyframe section of the 3DS file. " ambient _ constants at: 'ambientColor' ifAbsent: [B3DColor4 r: 0.0 g: 0.0 b: 0.0 a: 0.0]. ambient ifNotNil: [lights at: '$AMBIENT$' put: (B3DAmbientLight color: ambient)]. "Fetch the background color" clearColor _ constants at: 'backgroundColor' ifAbsent: [Color white]. "Fetch the materials and replace names in sceneObjects by actual materials " materials _ globals at: #materials. "Compute the per vertex normals" funkyNormals _ false. 'Computing vertex normals' displayProgressAt: Sensor cursorPoint from: 0 to: objects size during: [:bar | objects doWithIndex: [:obj :index | bar value: index. obj material ifNotNil: [obj material: (materials at: obj material ifAbsent: [])]. funkyNormals ifTrue: [obj geometry computeFunkyVertexNormals] ifFalse: [obj geometry vertexNormals]]]. objects do: [:obj | obj texture ifNotNil: [obj texture: texture]]! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/15/1999 01:01'! boundingBox |bBox| box ifNotNil:[^box]. bBox _ nil. objects do:[:obj| bBox _ bBox ifNil:[obj boundingBox] ifNotNil:[bBox merge: obj boundingBox] ]. ^box _ bBox! ! !B3DScene methodsFor: 'accessing' stamp: 'ti 3/21/2000 11:57'! cameras ^cameras! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:44'! clearColor ^clearColor! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:44'! clearColor: aColor clearColor _ aColor! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/15/1999 05:29'! defaultCamera ^defaultCamera! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/17/1999 05:08'! defaultCamera: aCamera defaultCamera _ aCamera.! ! !B3DScene methodsFor: 'accessing' stamp: 'jsp 3/1/1999 10:46'! lights ^lights! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/17/1999 05:14'! objects ^objects! ! !B3DScene methodsFor: 'accessing' stamp: 'ar 2/17/1999 05:14'! objects: aCollection objects _ aCollection! ! !B3DScene methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:24'! render | b3d | b3d _ (B3DRenderEngine defaultForPlatformOn: Display). b3d viewport: (0@0 extent: 600@600). clearColor ifNotNil:[b3d clearViewport: clearColor]. b3d clearDepthBuffer. "b3d addLight: (B3DAmbientLight color: Color white)." self renderOn: b3d. b3d finish. b3d destroy.! ! !B3DScene methodsFor: 'displaying' stamp: 'ar 2/16/1999 05:58'! renderOn: aRenderer defaultCamera ifNotNil:[ defaultCamera setClippingPlanesFrom: self. defaultCamera aspectRatio: aRenderer viewport aspectRatio. defaultCamera renderOn: aRenderer]. lights do:[:light| aRenderer addLight: light]. objects do:[:obj| obj renderOn: aRenderer].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DScene class instanceVariableNames: ''! !B3DScene class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 23:59'! from3DS: aDictionary ^self new from3DS: aDictionary! ! !B3DScene class methodsFor: 'instance creation' stamp: 'ar 2/17/1999 05:14'! new ^super new initialize! ! !B3DScene class methodsFor: 'instance creation' stamp: 'ti 3/21/2000 15:05'! withoutQuestionsFrom3DS: aDictionary ^self new withoutQuestionsFrom3DS: aDictionary! ! BorderedMorph subclass: #B3DSceneExplorerMorph instanceVariableNames: 'wheels frameWidth b3DSceneMorph ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Viewing'! !B3DSceneExplorerMorph commentStamp: '' prior: 0! Main comment stating the purpose of this class and relevant relationship to other classes. Possible useful expressions for doIt or printIt. Structure: instVar1 type -- comment about the purpose of instVar1 instVar2 type -- comment about the purpose of instVar2 Any further useful comments about the general approach of this implementation.! !B3DSceneExplorerMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 17:16'! scene ^b3DSceneMorph scene! ! !B3DSceneExplorerMorph methodsFor: 'accessing' stamp: 'ti 3/24/2000 17:04'! scene: aScene b3DSceneMorph scene: aScene.! ! !B3DSceneExplorerMorph methodsFor: 'actions'! openThreeDSFile | menu result newFileString myScene | menu := StandardFileMenu oldFileMenu: (FileDirectory default) withPattern: '*.3ds'. result := menu startUpWithCaption: 'Select 3DS model file ...'. result ifNotNil: [ newFileString := (result directory pathName),(result directory pathNameDelimiter asString),(result name). myScene := (B3DScene withoutQuestionsFrom3DS: (ThreeDSParser parseFileNamed: newFileString)). self scene: myScene].! ! !B3DSceneExplorerMorph methodsFor: 'actions'! selectNewCamera | menu sel | ((self scene cameras isNil) or: [self scene cameras size = 0]) ifTrue: [ (SelectionMenu selections: #('OK')) startUpWithCaption: 'No cameras defined!!'. ^self]. menu _ SelectionMenu selections: self scene cameras keys asArray. sel := menu startUp. sel ifNotNil: [ self scene defaultCamera: (self scene cameras at: sel) copy. b3DSceneMorph updateUpVectorForCamera: self scene defaultCamera. self changed.]! ! !B3DSceneExplorerMorph methodsFor: 'actions' stamp: 'ti 3/21/2000 14:28'! selectNewCamera: aCameraString aCameraString ifNotNil: [ self scene defaultCamera: (self scene cameras at: aCameraString) copy. self updateUpVectorForCamera: self scene defaultCamera. self changed.]! ! !B3DSceneExplorerMorph methodsFor: 'actions'! switchHeadLightStatus b3DSceneMorph switchHeadLightStatus! ! !B3DSceneExplorerMorph methodsFor: 'actions' stamp: 'ti 3/24/2000 17:04'! switchRotationStatus b3DSceneMorph switchRotationStatus! ! !B3DSceneExplorerMorph methodsFor: 'change reporting' stamp: 'ti 3/24/2000 17:11'! layoutChanged | ctrl | super layoutChanged. b3DSceneMorph ifNil: [^self]. b3DSceneMorph extent: (self extent - ((frameWidth * 2)@(frameWidth * 2))). b3DSceneMorph position: (self bounds origin + ((frameWidth)@(frameWidth))). wheels ifNil: [^self]. wheels isEmpty ifTrue: [^self]. ctrl := wheels at: #fov ifAbsent: [nil]. ctrl ifNotNil: [ ctrl position: self bounds corner - ctrl extent - (frameWidth@((frameWidth - ctrl extent y) / 2) rounded)]. ctrl := wheels at: #dolly ifAbsent: [nil]. ctrl ifNotNil: [ ctrl position: self bounds corner - ctrl extent - ((((frameWidth - ctrl extent x) / 2) rounded)@frameWidth)]. ctrl := wheels at: #rotX ifAbsent: [nil]. ctrl ifNotNil: [ ctrl position: (self bounds origin x + (((frameWidth - ctrl extent x) / 2) rounded))@(self bounds corner y - ctrl extent y - frameWidth)]. ctrl := wheels at: #rotY ifAbsent: [nil]. ctrl ifNotNil: [ ctrl position: (self bounds origin x + frameWidth)@(self bounds corner y - ctrl extent y - (((frameWidth - ctrl extent y) / 2) rounded))]. ctrl := wheels at: #rotZ ifAbsent: [nil]. ctrl ifNotNil: [ ctrl position: self bounds origin + ((((frameWidth - ctrl extent x) / 2) rounded)@frameWidth)].! ! !B3DSceneExplorerMorph methodsFor: 'drawing' stamp: 'ti 3/24/2000 17:27'! drawOn: aCanvas super drawOn: aCanvas. aCanvas fillRectangle: (self bounds insetBy: frameWidth) color: Color black.! ! !B3DSceneExplorerMorph methodsFor: 'event handling' stamp: 'ti 3/24/2000 17:13'! handlesMouseDown: evt ^evt yellowButtonPressed ! ! !B3DSceneExplorerMorph methodsFor: 'event handling' stamp: 'ti 3/24/2000 17:14'! mouseDown: evt evt yellowButtonPressed ifTrue: [ self yellowButtonMenu. ^super mouseDown: evt].! ! !B3DSceneExplorerMorph methodsFor: 'initialization' stamp: 'ti 3/24/2000 17:23'! initialize | ctrl | super initialize. self extent: 300@300. self borderRaised. color := Color gray: 0.8. frameWidth := 25. b3DSceneMorph := AdvancedB3DSceneMorph new. self addMorphFront: b3DSceneMorph. wheels := Dictionary new. ctrl := WheelMorph new. ctrl target: b3DSceneMorph. ctrl actionSelector: #addFovAngle:. ctrl factor: -0.07. ctrl setBalloonText: 'FOV'. self addMorphFront: ctrl. wheels at: #fov put: ctrl. ctrl := WheelMorph new. ctrl target: b3DSceneMorph. ctrl actionSelector: #addDolly:. ctrl factor: 0.005. ctrl beVertical. ctrl setBalloonText: 'Dolly'. self addMorphFront: ctrl. wheels at: #dolly put: ctrl. ctrl := WheelMorph new. ctrl target: b3DSceneMorph. ctrl actionSelector: #rotateZ:. ctrl beVertical. ctrl setBalloonText: 'z Axis'. self addMorphFront: ctrl. wheels at: #rotZ put: ctrl. ctrl := WheelMorph new. ctrl target: b3DSceneMorph. ctrl actionSelector: #rotateY:. ctrl setBalloonText: 'y Axis'. self addMorphFront: ctrl. wheels at: #rotY put: ctrl. ctrl := WheelMorph new. ctrl target: b3DSceneMorph. ctrl actionSelector: #rotateX:. ctrl beVertical. ctrl setBalloonText: 'x Axis'. self addMorphFront: ctrl. wheels at: #rotX put: ctrl.! ! !B3DSceneExplorerMorph methodsFor: 'menus'! addCustomMenuItems: aCustomMenu (aCustomMenu isKindOf: MenuMorph) ifTrue: [aCustomMenu addUpdating: #rotationString action: #switchRotationStatus] ifFalse: [aCustomMenu add: 'swich rotation status' action: #switchRotationStatus]. (aCustomMenu isKindOf: MenuMorph) ifTrue: [aCustomMenu addUpdating: #headLightString action: #switchHeadLightStatus] ifFalse: [aCustomMenu add: 'swich headlight' action: #switchHeadLightStatus]. aCustomMenu add: 'open 3DS file' action: #openThreeDSFile. aCustomMenu add: 'select new camera' action: #selectNewCamera.! ! !B3DSceneExplorerMorph methodsFor: 'menus' stamp: 'ti 3/22/2000 18:51'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. self addCustomMenuItems: aCustomMenu.! ! !B3DSceneExplorerMorph methodsFor: 'menus'! headLightString ^b3DSceneMorph headLightIsOn ifTrue: ['swich headlight off'] ifFalse: ['swich headlight on']! ! !B3DSceneExplorerMorph methodsFor: 'menus' stamp: 'ti 3/24/2000 17:04'! rotationString ^b3DSceneMorph isRotating ifTrue: ['stop rotating'] ifFalse: ['start rotating']! ! !B3DSceneExplorerMorph methodsFor: 'menus' stamp: 'ti 3/22/2000 18:57'! yellowButtonMenu | menu sel | menu _ CustomMenu new. menu title: self class name. self addCustomMenuItems: menu. sel := menu startUp. sel ifNotNil: [self perform: sel]! ! !B3DSceneExplorerMorph methodsFor: 'visual properties' stamp: 'ti 3/21/2000 14:45'! defaultColor ^Color gray! ! Morph subclass: #B3DSceneMorph instanceVariableNames: 'scene ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Demo Morphs'! !B3DSceneMorph methodsFor: 'drawing' stamp: 'ar 5/25/2000 17:57'! debugDraw self fullDrawOn: (Display getCanvas). Display forceToScreen: bounds.! ! !B3DSceneMorph methodsFor: 'drawing' stamp: 'ar 2/17/1999 05:05'! drawOn: aCanvas aCanvas asBalloonCanvas render: self. ! ! !B3DSceneMorph methodsFor: 'drawing' stamp: 'ar 2/17/1999 05:34'! renderOn: aRenderer aRenderer viewport: (self bounds insetBy: 1@1). aRenderer clearDepthBuffer. aRenderer loadIdentity. scene renderOn: aRenderer.! ! !B3DSceneMorph methodsFor: 'initialize' stamp: 'ar 2/17/1999 05:27'! createDefaultScene | sceneObj camera | sceneObj _ B3DSceneObject named: 'Sample Cube'. sceneObj geometry: (B3DBox from: (-0.7@-0.7@-0.7) to: (0.7@0.7@0.7)). camera _ B3DCamera new. camera position: 0@0@-1.5. self extent: 100@100. scene _ B3DScene new. scene defaultCamera: camera. scene objects add: sceneObj.! ! !B3DSceneMorph methodsFor: 'initialize' stamp: 'ar 2/17/1999 05:24'! initialize super initialize. self createDefaultScene.! ! !B3DSceneMorph methodsFor: 'accessing' stamp: 'ar 2/17/1999 05:34'! scene ^scene! ! !B3DSceneMorph methodsFor: 'accessing' stamp: 'ar 2/17/1999 05:34'! scene: aScene scene _ aScene! ! !B3DSceneMorph methodsFor: 'stepping' stamp: 'ar 2/17/1999 05:31'! step scene defaultCamera rotateBy: 15. self changed.! ! !B3DSceneMorph methodsFor: 'stepping' stamp: 'ar 2/17/1999 05:31'! stepTime ^1! ! !B3DSceneMorph methodsFor: 'stepping' stamp: 'ar 2/17/1999 05:30'! wantsSteps ^true! ! Object subclass: #B3DSceneObject instanceVariableNames: 'name matrix material texture geometry children ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Objects'! !B3DSceneObject methodsFor: 'initialize' stamp: 'ar 2/16/1999 03:09'! from3DS: aDictionary aDictionary isEmpty ifTrue:[^nil]. geometry _ B3DSTriangleMesh from3DS: aDictionary. material _ (aDictionary at: #triList) last.! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/14/1999 22:37'! boundingBox | bBox | bBox _ geometry ifNotNil:[geometry boundingBox]. children ifNil:[^bBox]. children do:[:obj| bBox _ bBox ifNil:[obj boundingBox] ifNotNil:[bBox merge: obj boundingBox] ]. ^bBox! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:29'! geometry ^geometry! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:30'! geometry: aGeometry geometry _ aGeometry.! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:30'! material ^material! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:10'! material: aMaterial material _ aMaterial. material class == Association ifTrue:[ texture _ material key. material _ material value. ].! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:30'! matrix ^matrix! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:30'! matrix: aMatrix matrix _ aMatrix! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:06'! name ^name! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:06'! name: aString name _ aString.! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:01'! texture ^texture! ! !B3DSceneObject methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:01'! texture: aTexture texture _ aTexture! ! !B3DSceneObject methodsFor: 'displaying' stamp: 'ar 2/16/1999 03:13'! renderOn: aRenderer material ifNotNil:[ aRenderer pushMaterial. aRenderer material: material]. texture ifNotNil:[ aRenderer pushTexture. aRenderer texture: texture]. matrix ifNotNil:[ aRenderer pushMatrix. aRenderer transformBy: matrix]. geometry ifNotNil:[geometry renderOn: aRenderer]. children ifNotNil:[children do:[:child| child renderOn: aRenderer]]. matrix ifNotNil:[aRenderer popMatrix]. texture ifNotNil:[aRenderer popTexture]. material ifNotNil:[aRenderer popMaterial].! ! !B3DSceneObject methodsFor: 'printing' stamp: 'ar 2/8/1999 01:15'! printOn: aStream super printOn: aStream. aStream nextPut:$(; print: self name; nextPut: $).! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DSceneObject class instanceVariableNames: ''! !B3DSceneObject class methodsFor: 'instance creation' stamp: 'ar 2/8/1999 01:06'! from3DS: aDictionary ^self new from3DS: aDictionary! ! !B3DSceneObject class methodsFor: 'instance creation' stamp: 'ar 2/7/1999 20:06'! named: aString ^self new name: aString! ! B3DEnginePlugin subclass: #B3DShaderPlugin instanceVariableNames: 'litVertex primLight primMaterial l2vDirection l2vDistance l2vSpecDir lightFlags vbFlags lightScale vtxInColor vtxOutColor ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DShaderPlugin methodsFor: 'primitives' stamp: 'ar 2/17/1999 19:40'! b3dShadeVertexBuffer "Primitive. Shade all the vertices in the vertex buffer using the given array of primitive light sources. Return true on success." | lightArray vtxCount vtxArray lightCount | self export: true. self inline: false. self var: #vtxArray declareC:'float *vtxArray'. vbFlags _ interpreterProxy stackIntegerValue: 0. primMaterial _ self stackMaterialValue: 1. lightArray _ self stackLightArrayValue: 2. vtxCount _ interpreterProxy stackIntegerValue: 3. vtxArray _ self stackPrimitiveVertexArray: 4 ofSize: vtxCount. (vtxArray = nil or:[primMaterial = nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. "Setup" litVertex _ vtxArray. lightCount _ interpreterProxy slotSizeOf: lightArray. "Go over all vertices" 1 to: vtxCount do:[:i| "Load the primitive vertex" self loadPrimitiveVertex. "Load initial color (e.g., emissive part of vertex and/or material)" (vbFlags anyMask: VBTrackEmission) ifTrue:[ "Load color from vertex" vtxOutColor at: 0 put: (vtxInColor at: 0) + (primMaterial at: EmissionRed). vtxOutColor at: 1 put: (vtxInColor at: 1) + (primMaterial at: EmissionGreen). vtxOutColor at: 2 put: (vtxInColor at: 2) + (primMaterial at: EmissionBlue). vtxOutColor at: 3 put: (vtxInColor at: 3) + (primMaterial at: EmissionAlpha). ] ifFalse:[ vtxOutColor at: 0 put: (primMaterial at: EmissionRed). vtxOutColor at: 1 put: (primMaterial at: EmissionGreen). vtxOutColor at: 2 put: (primMaterial at: EmissionBlue). vtxOutColor at: 3 put: (primMaterial at: EmissionAlpha). ]. "For each enabled light source" 0 to: lightCount-1 do:[:j| "Fetch the light source" primLight _ self fetchLightSource: j ofObject: lightArray. "Setup values" self loadPrimitiveLightSource. "Compute the color from the light source" self shadeVertex. ]. "Store the computed color back" self storePrimitiveVertex. "And step on to the next vertex" litVertex _ litVertex + PrimVertexSize. ]. "Clean up stack" interpreterProxy pop: 6. "Pop args+rcvr" interpreterProxy pushBool: true.! ! !B3DShaderPlugin methodsFor: 'primitives' stamp: 'ar 2/17/1999 04:32'! b3dShaderVersion "Return the current shader version." self export: true. self inline: false. interpreterProxy pop: 1. interpreterProxy pushInteger: 1. "Version 1"! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 2/17/1999 19:40'! addPart: lightPart from: materialPart trackFlag: vbTrackFlag scale: scale "Add the given light part to the output color, scaled by the given scale factor. If the given flag is set in vbFlags then load the part from the primitive vertex" | rPart gPart bPart aPart | self var: #lightPart declareC:'float *lightPart'. self var: #materialPart declareC:'float *materialPart'. self var: #scale declareC:'double scale'. self var: #rPart declareC:'double rPart'. self var: #gPart declareC:'double gPart'. self var: #bPart declareC:'double bPart'. self var: #aPart declareC:'double aPart'. self inline: true. (vbFlags anyMask: vbTrackFlag) ifTrue:[ rPart _ (vtxInColor at: 0) * (lightPart at: 0) * scale. gPart _ (vtxInColor at: 1) * (lightPart at: 1) * scale. bPart _ (vtxInColor at: 2) * (lightPart at: 2) * scale. aPart _ (vtxInColor at: 3) * (lightPart at: 3) * scale. ] ifFalse:[ "Note: This should be pre-computed." rPart _ (materialPart at: 0) * (lightPart at: 0) * scale. gPart _ (materialPart at: 1) * (lightPart at: 1) * scale. bPart _ (materialPart at: 2) * (lightPart at: 2) * scale. aPart _ (materialPart at: 3) * (lightPart at: 3) * scale. ]. vtxOutColor at: 0 put: (vtxOutColor at: 0) + rPart. vtxOutColor at: 1 put: (vtxOutColor at: 1) + gPart. vtxOutColor at: 2 put: (vtxOutColor at: 2) + bPart. vtxOutColor at: 3 put: (vtxOutColor at: 3) + aPart.! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 2/17/1999 19:39'! computeAttenuation "Compute the attenuation for the current light and vertex" lightScale _ 1.0. (lightFlags anyMask: FlagAttenuated) ifTrue:[ lightScale _ 1.0 / ((primLight at: PrimLightAttenuationConstant) + (l2vDistance * ((primLight at: PrimLightAttenuationLinear) + (l2vDistance * (primLight at: PrimLightAttenuationSquared)))))]. ! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:31'! computeDirection "Compute the direction for the current light and vertex" | scale | self inline: true. self var: #scale declareC:'double scale'. (lightFlags anyMask: FlagPositional) ifTrue:[ "Must compute the direction for this vertex" l2vDirection at: 0 put: (litVertex at: PrimVtxPositionX) - (primLight at: PrimLightPositionX). l2vDirection at: 1 put: (litVertex at: PrimVtxPositionY) - (primLight at: PrimLightPositionY). l2vDirection at: 2 put: (litVertex at: PrimVtxPositionZ) - (primLight at: PrimLightPositionZ). "l2vDistance _ self dotProductOf: l2vDirection with: l2vDirection." l2vDistance _ ((l2vDirection at: 0) * (l2vDirection at: 0)) + ((l2vDirection at: 1) * (l2vDirection at: 1)) + ((l2vDirection at: 2) * (l2vDirection at: 2)). (l2vDistance = 0.0 or:[l2vDistance = 1.0]) ifFalse:[ l2vDistance _ l2vDistance sqrt. scale _ -1.0/l2vDistance]. l2vDirection at: 0 put: (l2vDirection at: 0) * scale. l2vDirection at: 1 put: (l2vDirection at: 1) * scale. l2vDirection at: 2 put: (l2vDirection at: 2) * scale. ] ifFalse:[ (lightFlags anyMask: FlagDirectional) ifTrue:[ l2vDirection at: 0 put: (primLight at: PrimLightDirectionX). l2vDirection at: 1 put: (primLight at: PrimLightDirectionY). l2vDirection at: 2 put: (primLight at: PrimLightDirectionZ). ]. ]. ! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:39'! computeSpecularDirection "Computes l2vSpecDir _ l2vSpecDir - vtx position safelyNormalized. " | scale | self var: #scale declareC:'double scale'. scale _ self inverseLengthOfFloat: litVertex + PrimVtxPosition. l2vSpecDir at: 0 put: (l2vSpecDir at: 0) - ((litVertex at: PrimVtxPositionX) * scale). l2vSpecDir at: 1 put: (l2vSpecDir at: 1) - ((litVertex at: PrimVtxPositionY) * scale). l2vSpecDir at: 2 put: (l2vSpecDir at: 2) - ((litVertex at: PrimVtxPositionZ) * scale). ! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:39'! computeSpotFactor "Compute the spot factor for a spot light" | cosAngle minCos deltaCos | self returnTypeC:'double'. self var: #cosAngle declareC:'double cosAngle'. self var: #minCos declareC:'double minCos'. self var: #deltaCos declareC:'double deltaCos'. "Compute cos angle between direction of the spot light and direction to vertex" cosAngle _ self dotProductOfFloat: primLight + PrimLightDirection withDouble: l2vDirection. cosAngle _ 0.0 - cosAngle. minCos _ primLight at: SpotLightMinCos. cosAngle < minCos ifTrue:[^0.0]. deltaCos _ primLight at: SpotLightDeltaCos. deltaCos <= 0.00001 ifTrue:[ "No delta -- a sharp boundary between on and off. Since off has already been determined above, we are on" ^1.0]. "Scale the angle to 0/1 range" cosAngle _ (cosAngle - minCos) / deltaCos. ^cosAngle raisedTo: (primLight at: SpotLightExponent) ! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:39'! dotProductOfFloat: v1 withDouble: v2 self var: #v1 declareC:'float * v1'. self var: #v2 declareC:'double *v2'. self returnTypeC:'double'. ^((v1 at: 0) * (v2 at: 0)) + ((v1 at: 1) * (v2 at: 1)) + ((v1 at: 2) * (v2 at: 2)). ! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:38'! inverseLengthOfDouble: aVector | scale | self returnTypeC:'double'. self var: #aVector declareC:'double * aVector'. self var: #scale declareC:'double scale'. "scale _ self dotProductOf: aVector with: aVector." scale _ ((aVector at: 0) * (aVector at: 0)) + ((aVector at: 1) * (aVector at: 1)) + ((aVector at: 2) * (aVector at: 2)). (scale = 0.0 or:[scale = 1.0]) ifTrue:[^scale]. ^1.0 / scale sqrt! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:38'! inverseLengthOfFloat: aVector | scale | self returnTypeC:'double'. self var: #aVector declareC:'float * aVector'. self var: #scale declareC:'double scale'. "scale _ self dotProductOf: aVector with: aVector." scale _ ((aVector at: 0) * (aVector at: 0)) + ((aVector at: 1) * (aVector at: 1)) + ((aVector at: 2) * (aVector at: 2)). (scale = 0.0 or:[scale = 1.0]) ifTrue:[^scale]. ^1.0 / scale sqrt! ! !B3DShaderPlugin methodsFor: 'shading' stamp: 'ar 4/17/1999 22:40'! shadeVertex | cosAngle specularFactor | self var: #cosAngle declareC:'double cosAngle'. self var: #specularFactor declareC:'double specularFactor'. self computeDirection. self computeAttenuation. (lightFlags anyMask: FlagHasSpot) ifTrue:[ lightScale _ lightScale * self computeSpotFactor. ]. "Compute ambient and diffuse part only if lightScale is non-zero." (lightScale > 0.001) ifTrue:[ "Compute the ambient part" (lightFlags anyMask: FlagAmbientPart) ifTrue:[ self addPart: (primLight + AmbientPart) from: primMaterial + AmbientPart trackFlag: VBTrackAmbient scale: lightScale. ]. "Compute the diffuse part" (lightFlags anyMask: FlagDiffusePart) ifTrue:[ "Compute angle from light->vertex to vertex normal" cosAngle _ self dotProductOfFloat: (litVertex + PrimVtxNormal) withDouble: l2vDirection. "For one-sided lighting negate cosAngle if necessary" ((vbFlags bitAnd: VBTwoSidedLighting) = 0 and:[cosAngle < 0.0]) ifTrue:[cosAngle _ 0.0 - cosAngle]. "For two-sided lighting check if cosAngle > 0.0 meaning that it is a front face" cosAngle > 0.0 ifTrue:[ self addPart: primLight + DiffusePart from: primMaterial + DiffusePart trackFlag: VBTrackDiffuse scale: lightScale * cosAngle. ]. ]. ]. "lightScale > 0.001" "Compute the specular part" ((lightFlags anyMask: FlagSpecularPart) and:[ (primMaterial at: MaterialShininess) > 0.0]) ifTrue:[ "Compute specular part" l2vSpecDir at: 0 put: (l2vDirection at: 0). l2vSpecDir at: 1 put: (l2vDirection at: 1). l2vSpecDir at: 2 put: (l2vDirection at: 2). (vbFlags anyMask: VBUseLocalViewer) ifTrue:[self computeSpecularDirection] ifFalse:[l2vSpecDir at: 2 put: (l2vSpecDir at: 2) - 1.0]. cosAngle _ self dotProductOfFloat: (litVertex + PrimVtxNormal) withDouble: l2vSpecDir. cosAngle > 0.0 ifTrue:[ "Normalize the angle" cosAngle _ cosAngle * (self inverseLengthOfDouble: l2vSpecDir). "cosAngle should be somewhere between 0 and 1. If not, then the vertex normal was not normalized" cosAngle > 1.0 ifTrue:[ specularFactor _ cosAngle raisedTo: (primMaterial at: MaterialShininess). ] ifFalse:[ cosAngle = 0.0 ifTrue:[specularFactor _ 1.0] ifFalse:[specularFactor _ cosAngle raisedTo: (primMaterial at: MaterialShininess)]. ]. self addPart: primLight + SpecularPart from: primMaterial + SpecularPart trackFlag: VBTrackSpecular scale: specularFactor. ]. ].! ! !B3DShaderPlugin methodsFor: 'primitive support' stamp: 'ar 2/15/1999 22:33'! fetchLightSource: index ofObject: anArray "Fetch the primitive light source from the given array. Note: No checks are done within here - that happened in stackLightArrayValue:" | lightOop | self inline: true. self returnTypeC:'void*'. lightOop _ interpreterProxy fetchPointer: index ofObject: anArray. ^interpreterProxy firstIndexableField: lightOop! ! !B3DShaderPlugin methodsFor: 'primitive support' stamp: 'ar 2/15/1999 22:29'! stackLightArrayValue: stackIndex "Load an Array of B3DPrimitiveLights from the given stack index" | oop array arraySize | self inline: false. array _ interpreterProxy stackObjectValue: stackIndex. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: array) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. arraySize _ interpreterProxy slotSizeOf: array. 0 to: arraySize-1 do:[:i| oop _ interpreterProxy fetchPointer: i ofObject: array. (interpreterProxy isIntegerObject: oop) ifTrue:[^interpreterProxy primitiveFail]. ((interpreterProxy isWords: oop) and:[(interpreterProxy slotSizeOf: oop) = PrimLightSize]) ifFalse:[^interpreterProxy primitiveFail]. ]. ^array! ! !B3DShaderPlugin methodsFor: 'primitive support' stamp: 'ar 2/15/1999 19:22'! stackMaterialValue: stackIndex "Load a B3DMaterial from the given stack index" | oop | self inline: false. self returnTypeC:'void *'. oop _ interpreterProxy stackObjectValue: stackIndex. interpreterProxy failed ifTrue:[^nil]. ((interpreterProxy isWords: oop) and:[(interpreterProxy slotSizeOf: oop) = MaterialSize]) ifTrue:[^interpreterProxy firstIndexableField: oop]. ^nil! ! !B3DShaderPlugin methodsFor: 'other' stamp: 'ar 2/17/1999 19:35'! loadPrimitiveLightSource self inline: true. lightFlags _ (self cCoerce: primLight to: 'int*') at: PrimLightFlags.! ! !B3DShaderPlugin methodsFor: 'other' stamp: 'ar 2/17/1999 19:40'! loadPrimitiveVertex "Load the necessary values from the current primitive vertex" | rgba | self inline: true. rgba _ (self cCoerce: litVertex to:'int*') at: PrimVtxColor32. vtxInColor at: 2 put: (rgba bitAnd: 255) * (1.0 / 255.0). rgba _ rgba >> 8. vtxInColor at: 1 put: (rgba bitAnd: 255) * (1.0 / 255.0). rgba _ rgba >> 8. vtxInColor at: 0 put: (rgba bitAnd: 255) * (1.0 / 255.0). rgba _ rgba >> 8. vtxInColor at: 3 put: (rgba bitAnd: 255) * (1.0 / 255.0). ! ! !B3DShaderPlugin methodsFor: 'other' stamp: 'ar 2/17/1999 19:41'! storePrimitiveVertex "Store the computed output color back into the current primitive vertex. Clamp the r,g,b,a part to be in the range 0-255." | r g b a | self inline: true. r _ ((vtxOutColor at: 0) * 255) asInteger. r _ (r min: 255) max: 0. g _ ((vtxOutColor at: 1) * 255) asInteger. g _ (g min: 255) max: 0. b _ ((vtxOutColor at: 2) * 255) asInteger. b _ (b min: 255) max: 0. a _ ((vtxOutColor at: 3) * 255) asInteger. a _ (a min: 255) max: 0. "The following is equal to b + (g << 8) + (r << 16) + (a << 24)" (self cCoerce: litVertex to:'int*') at: PrimVtxColor32 put: b + (g + (r + (a << 8) << 8) << 8). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DShaderPlugin class instanceVariableNames: ''! !B3DShaderPlugin class methodsFor: 'translation' stamp: 'ar 5/15/2000 23:13'! declareCVarsIn: cg cg var: #litVertex type: #'float*'. cg var: #primLight type: #'float*'. cg var: #primMaterial type: #'float*'. cg var: #l2vDirection declareC: 'double l2vDirection[3]'. cg var: #l2vSpecDir declareC: 'double l2vSpecDir[3]'. cg var: #vtxInColor declareC: 'double vtxInColor[4]'. cg var: #vtxOutColor declareC: 'double vtxOutColor[4]'. cg var: #l2vDistance type: #'double'. cg var: #lightScale type: #'double'! ! B3DGeometry variableSubclass: #B3DSimpleMesh instanceVariableNames: 'bBox ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:54'! boundingBox ^bBox ifNil:[bBox _ self computeBoundingBox]! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:53'! colorOfVertex: vtx ^vtx color! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:55'! computeBoundingBox | min max | min _ max _ nil. self vertexPositionsDo:[:vtx| min ifNil:[min _ vtx] ifNotNil:[min _ min min: vtx]. max ifNil:[max _ vtx] ifNotNil:[max _ max max: vtx]. ]. ^Rectangle origin: min corner: max! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:54'! faces ^self! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:54'! faces: aCollection ^self shouldNotImplement! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:53'! normalOfVertex: vtx ^vtx normal! ! !B3DSimpleMesh methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:54'! texCoordOfVertex: vtx ^vtx texCoord! ! !B3DSimpleMesh methodsFor: 'testing' stamp: 'ar 9/14/1999 22:53'! hasTextureCoords 1 to: self size do:[:i| (self at: i) hasTextureCoords ifFalse:[^false]]. ^true! ! !B3DSimpleMesh methodsFor: 'testing' stamp: 'ar 9/14/1999 22:52'! hasVertexColors 1 to: self size do:[:i| (self at: i) hasVertexColors ifFalse:[^false]]. ^true! ! !B3DSimpleMesh methodsFor: 'enumerating' stamp: 'ar 9/17/1999 12:38'! do: aBlock 1 to: self size do:[:i| aBlock value: (self at: i)]! ! !B3DSimpleMesh methodsFor: 'enumerating' stamp: 'ar 9/14/1999 22:02'! trianglesDo: aBlock 1 to: self size do:[:i| (self at: i) trianglesDo: aBlock. ].! ! !B3DSimpleMesh methodsFor: 'enumerating' stamp: 'ar 9/14/1999 21:56'! vertexPositionsDo: aBlock 1 to: self size do:[:i| (self at: i) vertexPositionsDo: aBlock. ]! ! !B3DSimpleMesh methodsFor: 'converting' stamp: 'ar 9/14/1999 22:26'! asIndexedMesh "Convert the receiver into (the more compact) indexed representation" ^self asIndexedTriangleMesh! ! !B3DSimpleMesh methodsFor: 'converting' stamp: 'ar 9/14/1999 22:17'! asIndexedTriangleMesh "Convert the receiver into (the more compact) indexed triangle representation" | map faces face vtx nrm tex col mesh | map _ Dictionary new: (self size * 4). "Need some space for the vertices" faces _ WriteStream on: (B3DIndexedTriangleArray new: self size). self trianglesDo:[:tri| tri assureVertexNormals. face _ B3DIndexedTriangle with: (map at: tri first ifAbsentPut:[map size + 1]) with: (map at: tri second ifAbsentPut:[map size + 1]) with: (map at: tri third ifAbsentPut:[map size + 1]). faces nextPut: face]. faces _ faces contents. vtx _ B3DVector3Array new: map size. nrm _ B3DVector3Array new: map size. self hasTextureCoords ifTrue:[tex _ B3DTexture2Array new: map size]. self hasVertexColors ifTrue:[col _ B3DColor4Array new: map size]. map keysAndValuesDo:[:vertex :idx| vtx at: idx put: vertex position. nrm at: idx put: vertex normal. tex == nil ifFalse:[tex at: idx put: vertex texCoord]. col == nil ifFalse:[col at: idx put: vertex color]. ]. mesh _ B3DIndexedTriangleMesh new. mesh faces: faces. mesh vertices: vtx. mesh texCoords: tex. mesh vertexColors: col. mesh vertexNormals: nrm. ^mesh! ! !B3DSimpleMesh methodsFor: 'converting' stamp: 'ar 9/14/1999 22:05'! asSimpleMesh ^self! ! !B3DSimpleMesh methodsFor: 'converting' stamp: 'ar 9/17/1999 12:31'! transformedBy: aMatrix "Return a copy of the receiver with its vertices transformed by the given matrix" | newFaces| newFaces _ Array new: self size. 1 to: self size do:[:i| newFaces at: i put: ((self at: i) transformedBy: aMatrix)]. ^self class withAll: newFaces! ! !B3DSimpleMesh methodsFor: 'rendering' stamp: 'ar 11/7/1999 18:15'! renderOn: aRenderer | box bounds | box _ nil. 1 to: self size do:[:i| bounds _ (self at: i) renderOn: aRenderer. box == nil ifTrue:[box _ bounds] ifFalse:[box _ box quickMerge: bounds]. ]. ^box! ! !B3DSimpleMesh methodsFor: 'private' stamp: 'ar 9/14/1999 23:01'! withAll: aCollection 1 to: self size do:[:i| self at: i put: (aCollection at: i). ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DSimpleMesh class instanceVariableNames: ''! !B3DSimpleMesh class methodsFor: 'instance creation' stamp: 'ar 9/14/1999 23:00'! withAll: aCollection ^(self new: aCollection size) withAll: aCollection! ! B3DGeometry variableSubclass: #B3DSimpleMeshFace instanceVariableNames: 'normal ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:50'! first ^self at: 1! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:50'! fourth ^self at: 4! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:51'! normal ^normal ifNil:[normal _ self computeFaceNormal].! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:53'! normal: aB3DVector3 normal _ aB3DVector3! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:50'! second ^self at: 2! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 22:50'! third ^self at: 3! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:53'! vertices ^self! ! !B3DSimpleMeshFace methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:53'! vertices: aCollection ^self shouldNotImplement.! ! !B3DSimpleMeshFace methodsFor: 'testing' stamp: 'ar 9/14/1999 23:05'! hasTextureCoords 1 to: self size do:[:i| (self at: i) hasTextureCoords ifFalse:[^false]]. ^true! ! !B3DSimpleMeshFace methodsFor: 'testing' stamp: 'ar 9/14/1999 23:05'! hasVertexColors 1 to: self size do:[:i| (self at: i) hasVertexColors ifFalse:[^false]]. ^true! ! !B3DSimpleMeshFace methodsFor: 'enumerating' stamp: 'ar 9/17/1999 12:38'! do: aBlock 1 to: self size do:[:i| aBlock value: (self at: i)]! ! !B3DSimpleMeshFace methodsFor: 'enumerating' stamp: 'ar 9/14/1999 22:01'! trianglesDo: aBlock "Evaluate aBlock with triangular faces" | face | self size = 3 ifTrue:[^aBlock value: self]. 3 to: self size do:[:i| face _ self class with: (self at: 1) with: (self at: i-1) with: (self at: i). aBlock value: face].! ! !B3DSimpleMeshFace methodsFor: 'enumerating' stamp: 'ar 9/14/1999 21:56'! vertexPositionsDo: aBlock 1 to: self size do:[:i| (self at: i) vertexPositionsDo: aBlock. ]! ! !B3DSimpleMeshFace methodsFor: 'rendering' stamp: 'ar 11/7/1999 18:14'! renderOn: aRenderer ^aRenderer drawPolygonAfter:[ aRenderer normal: self normal. 1 to: self size do:[:i| (self at: i) renderOn: aRenderer]. ].! ! !B3DSimpleMeshFace methodsFor: 'private' stamp: 'ar 9/14/1999 23:09'! computeFaceNormal | d1 d2 nrml | self size < 3 ifTrue:[^B3DVector3 zero]. d1 _ (self at: 1) position - (self at: 2) position. d2 _ (self at: 3) position - (self at: 2) position. d1 safelyNormalize. d2 safelyNormalize. nrml _ d1 cross: d2. ^nrml safelyNormalize! ! !B3DSimpleMeshFace methodsFor: 'private' stamp: 'ar 9/14/1999 22:04'! with: v1 with: v2 with: v3 self at: 1 put: v1; at: 2 put: v2; at: 3 put: v3! ! !B3DSimpleMeshFace methodsFor: 'private' stamp: 'ar 9/14/1999 22:04'! with: v1 with: v2 with: v3 with: v4 self at: 1 put: v1; at: 2 put: v2; at: 3 put: v3; at: 4 put: v4! ! !B3DSimpleMeshFace methodsFor: 'private' stamp: 'ar 9/14/1999 22:05'! withAll: aCollection 1 to: self size do:[:i| self at: i put: (aCollection at: i). ].! ! !B3DSimpleMeshFace methodsFor: 'misc' stamp: 'ar 9/14/1999 22:51'! assureVertexNormals | vtx | 1 to: self size do:[:i| vtx _ self at: i. vtx normal == nil ifTrue:[ vtx _ vtx copy. vtx normal: self normal. self at: i put: vtx]].! ! !B3DSimpleMeshFace methodsFor: 'converting' stamp: 'ar 9/17/1999 12:31'! transformedBy: aMatrix "Return a copy of the receiver with its vertices transformed by the given matrix" | newVtx | newVtx _ Array new: self size. 1 to: self size do:[:i| newVtx at: i put: ((self at: i) transformedBy: aMatrix)]. ^self class withAll: newVtx! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DSimpleMeshFace class instanceVariableNames: ''! !B3DSimpleMeshFace class methodsFor: 'instance creation' stamp: 'ar 9/14/1999 22:03'! with: v0 with: v1 with: v2 ^(self new: 3) with: v0 with: v1 with: v2! ! !B3DSimpleMeshFace class methodsFor: 'instance creation' stamp: 'ar 9/14/1999 22:03'! with: v0 with: v1 with: v2 with: v3 ^(self new: 4) with: v0 with: v1 with: v2 with: v3! ! !B3DSimpleMeshFace class methodsFor: 'instance creation' stamp: 'ar 9/14/1999 22:03'! withAll: aCollection ^(self new: aCollection size) withAll: aCollection! ! Object subclass: #B3DSimpleMeshVertex instanceVariableNames: 'position normal color texCoord ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Meshes'! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:46'! color ^color! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:46'! color: aB3DColor4 color _ aB3DColor4! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:45'! normal ^normal! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:46'! normal: aB3DVector3 normal _ aB3DVector3! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:45'! position ^position! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:45'! position: aB3DVector3 position _ aB3DVector3! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:46'! texCoord ^texCoord! ! !B3DSimpleMeshVertex methodsFor: 'accessing' stamp: 'ar 9/14/1999 21:46'! texCoord: aB3DVector2 texCoord _ aB3DVector2! ! !B3DSimpleMeshVertex methodsFor: 'testing' stamp: 'ar 9/14/1999 23:06'! hasTextureCoords ^texCoord notNil! ! !B3DSimpleMeshVertex methodsFor: 'testing' stamp: 'ar 9/14/1999 23:06'! hasVertexColors ^color notNil! ! !B3DSimpleMeshVertex methodsFor: 'comparing' stamp: 'ar 9/14/1999 21:48'! = aVertex ^self class == aVertex class and:[self position = aVertex position and:[self normal = aVertex normal and:[self color = aVertex color and:[self texCoord = aVertex texCoord]]]]! ! !B3DSimpleMeshVertex methodsFor: 'comparing' stamp: 'ar 9/14/1999 21:49'! hash "Hash is re-implemented because #= is re-implemented" ^(position hash bitXor: texCoord hash) bitXor: (normal hash bitXor: color hash)! ! !B3DSimpleMeshVertex methodsFor: 'enumerating' stamp: 'ar 9/14/1999 21:56'! vertexPositionsDo: aBlock position vertexPositionsDo: aBlock.! ! !B3DSimpleMeshVertex methodsFor: 'rendering' stamp: 'ar 9/14/1999 21:59'! renderOn: aRenderer color == nil ifFalse:[aRenderer color: color]. texCoord == nil ifFalse:[aRenderer texCoord: texCoord]. normal == nil ifFalse:[aRenderer normal: normal]. aRenderer vertex: position.! ! !B3DSimpleMeshVertex methodsFor: 'printing' stamp: 'ar 9/16/1999 22:48'! printOn: aStream aStream nextPutAll:'['; print: position; nextPutAll:']'.! ! !B3DSimpleMeshVertex methodsFor: 'converting' stamp: 'ar 9/17/1999 13:30'! transformedBy: aMatrix "Return a copy of the receiver with its vertices transformed by the given matrix" | transformer copy | transformer _ B3DVertexTransformer new. transformer loadIdentity. transformer transformBy: aMatrix. copy _ self copy. copy position: (transformer transformPosition: position). normal == nil ifFalse:[copy normal: (transformer transformDirection: normal) safelyNormalize]. ^copy! ! B3DVertexRasterizer subclass: #B3DSimulRasterizer instanceVariableNames: 'canvas scanner ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DSimulRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:45'! clipRect: aRectangle super clipRect: aRectangle. scanner bitBlt clipRect: aRectangle.! ! !B3DSimulRasterizer methodsFor: 'initialize' stamp: 'ar 4/18/1999 04:35'! flush self mainLoop.! ! !B3DSimulRasterizer methodsFor: 'initialize' stamp: 'ar 4/18/1999 04:36'! initialize super initialize. scanner _ B3DScanner new.! ! !B3DSimulRasterizer methodsFor: 'initialize' stamp: 'ar 4/18/1999 04:36'! reset super reset. scanner _ B3DScanner new.! ! !B3DSimulRasterizer methodsFor: 'initialize' stamp: 'ar 5/28/2000 12:18'! target: destForm | bb span sourceForm | super target: destForm. span _ Bitmap new: 2048. sourceForm _ Form extent: span size@1 depth: 32 bits: span. bb _ BitBlt current toForm: destForm. bb sourceForm: sourceForm. bb isFXBlt ifTrue:[ bb colorMap: (sourceForm colormapIfNeededFor: destForm). bb combinationRule: 34 "Form paint". "Later we'll change this to 34 for alpha blending" ] ifFalse:[ bb colorMap: (sourceForm colormapIfNeededForDepth: destForm depth). bb combinationRule: 34 "Form paint". "Later we'll change this to 34 for alpha blending" ]. bb destX: 0; destY: 0; sourceX: 0; sourceY: 0; width: 1; height: 1. scanner spanBuffer: span. scanner bitBlt: bb.! ! !B3DSimulRasterizer methodsFor: 'testing' stamp: 'ar 4/18/1999 04:36'! needsClip ^true! ! !B3DSimulRasterizer methodsFor: 'processing' stamp: 'ar 4/18/1999 04:48'! loadVerticesFrom: vb | out vtxArray | vtxArray _ vb vertexArray. out _ Array new: vb vertexCount. 1 to: vb vertexCount do:[:i| out at: i put: (vtxArray at: i). ]. ^out! ! !B3DSimulRasterizer methodsFor: 'processing' stamp: 'ar 4/18/1999 05:31'! mainLoop scanner mainLoop. scanner resetObjects.! ! !B3DSimulRasterizer methodsFor: 'processing' stamp: 'ar 4/18/1999 07:34'! processIndexedQuads: vb | vtxArray out idx1 idxArray idx2 idx3 face obj idx4 | vtxArray _ self loadVerticesFrom: vb. idxArray _ vb indexArray. out _ WriteStream on: (B3DIndexedTriangleArray new: vb indexCount // 3 * 2). 1 to: vb indexCount by: 4 do:[:i| idx1 _ idxArray at: i. idx2 _ idxArray at: i+1. idx3 _ idxArray at: i+2. idx4 _ idxArray at: i+3. idx1 = 0 ifFalse:[ face _ B3DIndexedTriangle with: idx1 with: idx2 with: idx3. out nextPut: face. face _ B3DIndexedTriangle with: idx3 with: idx4 with: idx1. out nextPut: face]. ]. obj _ B3DPrimitiveObject new. obj faces: out contents. obj vertices: vtxArray. obj texture: texture. obj mapVertices: viewport. obj setupVertexOrder. obj sortInitialFaces. scanner addObject: obj.! ! !B3DSimulRasterizer methodsFor: 'processing' stamp: 'ar 4/18/1999 06:55'! processIndexedTriangles: vb | vtxArray out idx1 idxArray idx2 idx3 face obj | vtxArray _ self loadVerticesFrom: vb. idxArray _ vb indexArray. out _ WriteStream on: (B3DIndexedTriangleArray new: vb indexCount // 3). 1 to: vb indexCount by: 3 do:[:i| idx1 _ idxArray at: i. idx2 _ idxArray at: i+1. idx3 _ idxArray at: i+2. idx1 = 0 ifFalse:[ face _ B3DIndexedTriangle with: idx1 with: idx2 with: idx3. out nextPut: face]. ]. obj _ B3DPrimitiveObject new. obj faces: out contents. obj vertices: vtxArray. obj texture: texture. obj mapVertices: viewport. obj setupVertexOrder. obj sortInitialFaces. scanner addObject: obj.! ! !B3DSimulRasterizer methodsFor: 'processing' stamp: 'ar 4/18/1999 07:56'! processPolygon: vb | vtxArray out face obj | vtxArray _ self loadVerticesFrom: vb. out _ WriteStream on: (B3DIndexedTriangleArray new: vtxArray size - 2). 3 to: vb vertexCount do:[:i| face _ B3DIndexedTriangle with: 1 with: i-1 with: i. out nextPut: face. ]. obj _ B3DPrimitiveObject new. obj faces: out contents. obj vertices: vtxArray. obj texture: texture. obj mapVertices: viewport. obj setupVertexOrder. obj sortInitialFaces. scanner addObject: obj.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DSimulRasterizer class instanceVariableNames: ''! !B3DSimulRasterizer class methodsFor: 'testing'! isAvailable ^true "Always"! ! B3DPositionalLight subclass: #B3DSpotLight instanceVariableNames: 'target minCos maxCos deltaCos direction ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DSpotLight methodsFor: 'initialize' stamp: 'ar 2/7/1999 18:44'! from3DS: aDictionary "Initialize the receiver from a 3DS point light" | spotValues hotSpot fallOff | super from3DS: aDictionary. spotValues _ aDictionary at: #spot. target _ spotValues at: #target. hotSpot _ spotValues at: #hotspotAngle. self minAngle: hotSpot. fallOff _ spotValues at: #falloffAngle. self maxAngle: hotSpot + fallOff.! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:18'! direction ^direction ifNil:[direction _ (target - position) safelyNormalize].! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/8/1999 01:40'! direction: aVector direction _ aVector! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:47'! hotSpotDeltaCosine ^deltaCos! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:45'! hotSpotMaxCosine ^maxCos! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:45'! hotSpotMinCosine ^minCos! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:46'! maxAngle ^maxCos arcCos radiansToDegrees! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:25'! maxAngle: angle minCos _ angle degreesToRadians cos. maxCos ifNotNil:[deltaCos _ maxCos - minCos].! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 18:46'! minAngle ^minCos arcCos radiansToDegrees! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:25'! minAngle: angle maxCos _ angle degreesToRadians cos. minCos ifNotNil:[deltaCos _ maxCos - minCos].! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:16'! target ^target! ! !B3DSpotLight methodsFor: 'accessing' stamp: 'ar 2/7/1999 20:16'! target: aVector target _ aVector! ! !B3DSpotLight methodsFor: 'testing' stamp: 'ar 2/15/1999 02:18'! hasSpot ^true! ! !B3DSpotLight methodsFor: 'converting' stamp: 'ar 2/15/1999 22:01'! asPrimitiveLight "Convert the receiver into a B3DPrimitiveLight" | primLight | primLight _ super asPrimitiveLight. primLight flags: (primLight flags bitOr: FlagHasSpot). primLight spotMinCos: minCos. primLight spotMaxCos: maxCos. primLight spotDeltaCos: deltaCos. primLight spotExponent: self spotExponent. primLight direction: (target - position) safelyNormalize. ^primLight! ! !B3DSpotLight methodsFor: 'converting' stamp: 'ar 2/8/1999 01:39'! transformedBy: aTransformer ^(super transformedBy: aTransformer) target: (aTransformer transformPosition: target); direction: nil! ! Form subclass: #B3DTexture instanceVariableNames: 'wrap interpolate envMode ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Lights'! !B3DTexture commentStamp: '' prior: 0! I represent a simple 2D texture. Instance variables: wrap If true, wrap the texture - otherwise clamp it. interpolate If true, interpolate the pixels of the texture. envMode How we combine colors with the texture. Possible values: 0 - OpenGL style modulate texture 1 - OpenGL style decal texture! !B3DTexture methodsFor: 'accessing' stamp: 'ar 6/9/2000 19:16'! contentsOfArea: aRect "Return a new form which derives from the portion of the original form delineated by aRect." ^self contentsOfArea: aRect into: ((self class extent: aRect extent depth: depth) wrap: self wrap; envMode: self envMode; interpolate: self interpolate; yourself)! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:36'! envMode ^envMode! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:36'! envMode: aNumber envMode _ aNumber.! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:33'! interpolate ^interpolate! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:34'! interpolate: aBool interpolate _ aBool! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:34'! wrap ^wrap! ! !B3DTexture methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:33'! wrap: aBool wrap _ aBool! ! !B3DTexture methodsFor: 'flipping' stamp: 'jsp 3/15/1999 14:20'! flipVertically "Flip the texture vertically" | temp h w row | h _ self height. w _ self width. 0 to: ((h // 2) - 1) do: [:i | row _ h - i - 1. 1 to: w do: [:j | temp _ bits at: ((i * w) + j). bits at: ((i * w) + j) put: (bits at: ((row * w) + j)). bits at: ((row * w) + j) put: temp. ]. ]. ! ! !B3DTexture methodsFor: 'converting' stamp: 'ar 5/27/1999 17:49'! asTexture ^self! ! B3DInplaceArray variableWordSubclass: #B3DTexture2Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! !B3DTexture2Array methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:30'! at: index put: value value isPoint ifTrue:[super at: index put: (B3DVector2 u: value x v: value y)] ifFalse:[super at: index put: value]. ^value! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DTexture2Array class instanceVariableNames: ''! !B3DTexture2Array class methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:31'! contentsClass ^B3DVector2! ! B3DEnginePlugin subclass: #B3DTransformerPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 5/22/2000 17:12'! b3dInplaceHouseHolderInvert "Primitive. Perform an inplace house holder matrix inversion" | rcvr d x sigma beta sum s m | self export: true. self var: #rcvr declareC:'float *rcvr'. self var: #m declareC:'double m[4][4]'. self var: #x declareC:'double x[4][4] = { {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1} }'. self var: #d declareC:'double d[4][4]'. self var: #sigma declareC:'double sigma'. self var: #beta declareC:'double beta'. self var: #sum declareC:'double sum'. self var: #s declareC:'double s'. self cCode:'' inSmalltalk:[ m _ CArrayAccessor on: ((1 to: 4) collect:[:i| CArrayAccessor on: (Array new: 4)]). x _ CArrayAccessor on: (Array with: (CArrayAccessor on: #(1.0 0.0 0.0 0.0) copy) with: (CArrayAccessor on: #(0.0 1.0 0.0 0.0) copy) with: (CArrayAccessor on: #(0.0 0.0 1.0 0.0) copy) with: (CArrayAccessor on: #(0.0 0.0 0.0 1.0) copy)). d _ CArrayAccessor on: ((1 to: 4) collect:[:i| CArrayAccessor on: (Array new: 4)]). ]. rcvr _ self stackMatrix: 0. 0 to: 3 do:[:i| 0 to: 3 do:[:j| (m at: i) at: j put: (rcvr at: i*4+j)]]. 0 to: 3 do:[:j| sigma := 0.0. j to: 3 do:[:i| sigma := sigma + (((m at: i) at: j) * ((m at: i) at: j))]. sigma < 1.0e-10 ifTrue:[^interpreterProxy primitiveFail]. "matrix is singular" (((m at: j) at: j) < 0.0) ifTrue:[ s:= sigma sqrt] ifFalse:[ s:= 0.0 - sigma sqrt]. 0 to: 3 do:[:r| (d at: j) at: r put: s]. beta := 1.0 / ( s * ((m at: j) at: j) - sigma). (m at: j) at: j put: (((m at: j) at: j) - s). "update remaining columns" j+1 to: 3 do:[:k| sum := 0.0. j to: 3 do:[:i| sum := sum + (((m at: i) at: j) * ((m at: i) at: k))]. sum := sum * beta. j to: 3 do:[:i| (m at: i) at: k put: (((m at: i) at: k) + (((m at: i) at: j) * sum))]]. "update vector" 0 to: 3 do:[:r| sum := 0.0. j to: 3 do:[:i| sum _ sum + (((x at: i) at: r) * ((m at: i) at: j))]. sum := sum * beta. j to: 3 do:[:i| (x at: i) at: r put:(((x at: i) at: r) + (sum * ((m at: i) at: j)))]. ]. ]. "Now calculate result" 0 to: 3 do:[:r| 3 to: 0 by: -1 do:[:i| i+1 to: 3 do:[:j| (x at: i) at: r put: (((x at: i) at: r) - (((x at: j) at: r) * ((m at: i) at: j))) ]. (x at: i) at: r put: (((x at: i) at: r) / ((d at: i) at: r))]. ]. 0 to: 3 do:[:i| 0 to: 3 do:[:j| rcvr at: i*4+j put: (self cCoerce: ((x at: i) at: j) to:'float')]]. "Return receiver"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 00:02'! b3dTransformMatrixWithInto "Transform two matrices into the third" | m1 m2 m3 | self export: true. self inline: false. self var: #m1 declareC:'float *m1'. self var: #m2 declareC:'float *m2'. self var: #m3 declareC:'float *m3'. m3 _ self stackMatrix: 0. m2 _ self stackMatrix: 1. m1 _ self stackMatrix: 2. (m1 = nil) | (m2 = nil) | (m3 = nil) ifTrue:[^interpreterProxy primitiveFail]. m2 == m3 ifTrue:[^interpreterProxy primitiveFail]. self transformMatrix: m1 with: m2 into: m3. interpreterProxy pop: 3. "Leave rcvr on stack"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 00:03'! b3dTransformPrimitiveNormal "Transform the normal of the given primitive vertex using the argument matrix and rescale the normal if necessary." | pVertex matrix rescale | self export: true. self inline: false. self var: #matrix declareC:'float *matrix'. self var: #pVertex declareC:'float *pVertex'. rescale _ interpreterProxy stackValue: 0. rescale == interpreterProxy nilObject ifFalse:[rescale _ interpreterProxy booleanValueOf: rescale]. matrix _ self stackMatrix: 1. pVertex _ self stackPrimitiveVertex: 2. (matrix = nil) | (pVertex = nil) ifTrue:[^interpreterProxy primitiveFail]. (rescale ~~ true and:[rescale ~~ false]) ifTrue:[rescale _ self analyzeMatrix3x3Length: matrix]. self transformPrimitiveNormal: pVertex by: matrix rescale: rescale. interpreterProxy pop: 3. "Leave rcvr on stack"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 00:03'! b3dTransformPrimitivePosition "Transform the position of the given primitive vertex the given matrix and store the result back inplace." | pVertex matrix | self export: true. self inline: false. self var: #matrix declareC:'float *matrix'. self var: #pVertex declareC:'float *pVertex'. matrix _ self stackMatrix: 0. pVertex _ self stackPrimitiveVertex: 1. (matrix = nil) | (pVertex = nil) ifTrue:[^interpreterProxy primitiveFail]. self transformPrimitivePosition: pVertex by: matrix. interpreterProxy pop: 2. "Leave rcvr on stack"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 00:03'! b3dTransformPrimitiveRasterPosition "Transform the position of the given primitive vertex the given matrix and store the result in homogenous coordinates at rasterPos." | pVertex matrix | self export: true. self inline: false. self var: #matrix declareC:'float *matrix'. self var: #pVertex declareC:'float *pVertex'. matrix _ self stackMatrix: 0. pVertex _ self stackPrimitiveVertex: 1. (matrix = nil) | (pVertex = nil) ifTrue:[^interpreterProxy primitiveFail]. self transformPrimitiveRasterPosition: pVertex by: matrix. interpreterProxy pop: 2. "Leave rcvr on stack"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 00:05'! b3dTransformVertexBuffer "Transform an entire vertex buffer using the supplied modelview and projection matrix." | flags projectionMatrix modelViewMatrix vtxCount vtxArray | self export: true. self inline: false. self var: #projectionMatrix declareC:'float *projectionMatrix'. self var: #modelViewMatrix declareC:'float *modelViewMatrix'. self var: #vtxArray declareC:'float *vtxArray'. flags _ interpreterProxy stackIntegerValue: 0. projectionMatrix _ self stackMatrix: 1. modelViewMatrix _ self stackMatrix: 2. vtxCount _ interpreterProxy stackIntegerValue: 3. vtxArray _ self stackPrimitiveVertexArray: 4 ofSize: vtxCount. (projectionMatrix = nil) | (modelViewMatrix = nil) | (vtxArray = nil) ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifTrue:[^nil]. self transformVB: vtxArray count: vtxCount by: modelViewMatrix and: projectionMatrix flags: flags. interpreterProxy pop: 5. "Leave rcvr on stack"! ! !B3DTransformerPlugin methodsFor: 'primitives' stamp: 'ar 2/17/1999 04:31'! b3dTransformerVersion "Return the current version of the transformer" self export: true. self inline: false. interpreterProxy pop: 1. interpreterProxy pushInteger: 1. "Version 1"! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 2/13/1999 23:45'! analyzeMatrix3x3Length: m "Check if the matrix scales normals to non-unit length." | det | self var: #m declareC:'float *m'. self var: #det declareC:'double det'. det _ ((m at: 0) * (m at: 5) * (m at: 10)) - ((m at: 2) * (m at: 5) * (m at: 8)) + ((m at: 4) * (m at: 9) * (m at: 2)) - ((m at: 6) * (m at: 9) * (m at: 0)) + ((m at: 8) * (m at: 1) * (m at: 6)) - ((m at: 10) * (m at: 1) * (m at: 4)). ^det < 0.99 or:[det > 1.01]! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 2/13/1999 23:45'! analyzeMatrix: m "Analyze the matrix and return the appropriate flags" | flags | self var: #m declareC:'float *m'. "Check the perspective" flags _ 0. ((m at: 12) = 0.0 and:[(m at: 13) = 0.0 and:[(m at: 14) = 0.0 and:[(m at: 15) = 1.0]]]) ifTrue:[ flags _ flags bitOr: FlagM44NoPerspective. "Check translation" ((m at: 3) = 0.0 and:[(m at: 7) = 0.0 and:[(m at: 11) = 0.0]]) ifTrue:[ flags _ flags bitOr: FlagM44NoTranslation. "Check for identity" ((m at: 0) = 1.0 and:[(m at: 5) = 1.0 and:[(m at: 10) = 1.0 and:[ (m at: 1) = 0.0 and:[(m at: 2) = 0.0 and:[ (m at: 4) = 0.0 and:[(m at: 6) = 0.0 and:[ (m at: 8) = 0.0 and:[(m at: 9) = 0.0]]]]]]]]) ifTrue:[ flags _ flags bitOr: FlagM44Identity. ]. ]. ]. ^flags! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 2/13/1999 23:45'! transformMatrix: src with: arg into: dst "Transform src with arg into dst. It is allowed that src == dst but not arg == dst" | m1 m2 m3 c1 c2 c3 c4 | self var: #src declareC:'float *src'. self var: #arg declareC:'float *arg'. self var: #dst declareC:'float *dst'. self var: #m1 declareC:'float *m1'. self var: #m2 declareC:'float *m2'. self var: #m3 declareC:'float *m3'. self var: #c1 declareC:'float c1'. self var: #c2 declareC:'float c2'. self var: #c3 declareC:'float c3'. self var: #c4 declareC:'float c4'. m1 _ self cCoerce: src to:'float *'. m2 _ self cCoerce: arg to: 'float *'. m3 _ self cCoerce: dst to: 'float *'. 0 to: 3 do:[:i| "Compute next row" c1 _ ((m1 at: 0) * (m2 at: 0)) + ((m1 at: 1) * (m2 at: 4)) + ((m1 at: 2) * (m2 at: 8)) + ((m1 at: 3) * (m2 at: 12)). c2 _ ((m1 at: 0) * (m2 at: 1)) + ((m1 at: 1) * (m2 at: 5)) + ((m1 at: 2) * (m2 at: 9)) + ((m1 at: 3) * (m2 at: 13)). c3 _ ((m1 at: 0) * (m2 at: 2)) + ((m1 at: 1) * (m2 at: 6)) + ((m1 at: 2) * (m2 at: 10)) + ((m1 at: 3) * (m2 at: 14)). c4 _ ((m1 at: 0) * (m2 at: 3)) + ((m1 at: 1) * (m2 at: 7)) + ((m1 at: 2) * (m2 at: 11)) + ((m1 at: 3) * (m2 at: 15)). "Store result" m3 at: 0 put: c1. m3 at: 1 put: c2. m3 at: 2 put: c3. m3 at: 3 put: c4. "Skip src and dst to next row" m1 _ m1 + 4. m3 _ m3 + 4. ]. ! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 4/17/1999 22:22'! transformPrimitiveNormal: pVertex by: matrix rescale: rescale "Transform the normal of the given primitive vertex" | x y z rx ry rz dot | self var: #pVertex declareC:'float *pVertex'. self var: #matrix declareC:'float *matrix'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #rx declareC:'double rx'. self var: #ry declareC:'double ry'. self var: #rz declareC:'double rz'. self var: #dot declareC:'double dot'. x _ pVertex at: PrimVtxNormalX. y _ pVertex at: PrimVtxNormalY. z _ pVertex at: PrimVtxNormalZ. rx _ (x * (matrix at: 0)) + (y * (matrix at: 1)) + (z * (matrix at: 2)). ry _ (x * (matrix at: 4)) + (y * (matrix at: 5)) + (z * (matrix at: 6)). rz _ (x * (matrix at: 8)) + (y * (matrix at: 9)) + (z * (matrix at: 10)). rescale ifTrue:[ dot _ (rx * rx) + (ry * ry) + (rz * rz). dot < 1.0e-20 ifTrue:[rx _ ry _ rz _ 0.0] ifFalse:[dot = 1.0 ifFalse:[dot _ 1.0 / dot sqrt. rx _ rx * dot. ry _ ry * dot. rz _ rz * dot]]]. pVertex at: PrimVtxNormalX put: (self cCoerce: rx to:'float'). pVertex at: PrimVtxNormalY put: (self cCoerce: ry to:'float'). pVertex at: PrimVtxNormalZ put: (self cCoerce: rz to:'float'). ! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 4/17/1999 22:24'! transformPrimitivePosition: pVertex by: matrix "Transform the normal of the given primitive vertex" | x y z rx ry rz rw | self var: #pVertex declareC:'float *pVertex'. self var: #matrix declareC:'float *matrix'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #rx declareC:'double rx'. self var: #ry declareC:'double ry'. self var: #rz declareC:'double rz'. self var: #rw declareC:'double rw'. x _ pVertex at: PrimVtxPositionX. y _ pVertex at: PrimVtxPositionY. z _ pVertex at: PrimVtxPositionZ. rx _ (x * (matrix at: 0)) + (y * (matrix at: 1)) + (z * (matrix at: 2)) + (matrix at: 3). ry _ (x * (matrix at: 4)) + (y * (matrix at: 5)) + (z * (matrix at: 6)) + (matrix at: 7). rz _ (x * (matrix at: 8)) + (y * (matrix at: 9)) + (z * (matrix at: 10)) + (matrix at: 11). rw _ (x * (matrix at: 12)) + (y * (matrix at: 13)) + (z * (matrix at: 14)) + (matrix at: 15). rw = 1.0 ifTrue:[ pVertex at: PrimVtxPositionX put: (self cCoerce: rx to: 'float'). pVertex at: PrimVtxPositionY put: (self cCoerce: ry to:'float'). pVertex at: PrimVtxPositionZ put: (self cCoerce: rz to: 'float'). ] ifFalse:[ rw = 0.0 ifTrue:[rw _ 0.0] ifFalse:[rw _ 1.0 / rw]. pVertex at: PrimVtxPositionX put: (self cCoerce: rx*rw to:'float'). pVertex at: PrimVtxPositionY put: (self cCoerce: ry*rw to:'float'). pVertex at: PrimVtxPositionZ put: (self cCoerce: rz*rw to: 'float'). ]. ! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 4/17/1999 22:24'! transformPrimitivePositionFast: pVertex by: matrix "Transform the position of the given primitive vertex assuming that matrix a41 = a42 = a43 = 0.0 and a44 = 1.0" | x y z rx ry rz | self var: #pVertex declareC:'float *pVertex'. self var: #matrix declareC:'float *matrix'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #rx declareC:'double rx'. self var: #ry declareC:'double ry'. self var: #rz declareC:'double rz'. x _ pVertex at: PrimVtxPositionX. y _ pVertex at: PrimVtxPositionY. z _ pVertex at: PrimVtxPositionZ. rx _ (x * (matrix at: 0)) + (y * (matrix at: 1)) + (z * (matrix at: 2)) + (matrix at: 3). ry _ (x * (matrix at: 4)) + (y * (matrix at: 5)) + (z * (matrix at: 6)) + (matrix at: 7). rz _ (x * (matrix at: 8)) + (y * (matrix at: 9)) + (z * (matrix at: 10)) + (matrix at: 11). pVertex at: PrimVtxPositionX put: (self cCoerce: rx to: 'float'). pVertex at: PrimVtxPositionY put: (self cCoerce: ry to: 'float'). pVertex at: PrimVtxPositionZ put: (self cCoerce: rz to: 'float').! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 4/17/1999 22:25'! transformPrimitivePositionFaster: pVertex by: matrix "Transform the position of the given primitive vertex assuming that matrix a14 = a24 = a34 = a41 = a42 = a43 = 0.0 and a44 = 1.0" | x y z rx ry rz | self var: #pVertex declareC:'float *pVertex'. self var: #matrix declareC:'float *matrix'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #rx declareC:'double rx'. self var: #ry declareC:'double ry'. self var: #rz declareC:'double rz'. x _ pVertex at: PrimVtxPositionX. y _ pVertex at: PrimVtxPositionY. z _ pVertex at: PrimVtxPositionZ. rx _ (x * (matrix at: 0)) + (y * (matrix at: 1)) + (z * (matrix at: 2)). ry _ (x * (matrix at: 4)) + (y * (matrix at: 5)) + (z * (matrix at: 6)). rz _ (x * (matrix at: 8)) + (y * (matrix at: 9)) + (z * (matrix at: 10)). pVertex at: PrimVtxPositionX put: (self cCoerce: rx to:'float'). pVertex at: PrimVtxPositionY put: (self cCoerce: ry to:'float'). pVertex at: PrimVtxPositionZ put: (self cCoerce: rz to: 'float').! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 4/17/1999 22:26'! transformPrimitiveRasterPosition: pVertex by: matrix "Transform the normal of the given primitive vertex" | x y z rx ry rz rw | self var: #pVertex declareC:'float *pVertex'. self var: #matrix declareC:'float *matrix'. self var: #x declareC:'double x'. self var: #y declareC:'double y'. self var: #z declareC:'double z'. self var: #rx declareC:'double rx'. self var: #ry declareC:'double ry'. self var: #rz declareC:'double rz'. self var: #rw declareC:'double rw'. x _ pVertex at: PrimVtxPositionX. y _ pVertex at: PrimVtxPositionY. z _ pVertex at: PrimVtxPositionZ. rx _ (x * (matrix at: 0)) + (y * (matrix at: 1)) + (z * (matrix at: 2)) + (matrix at: 3). ry _ (x * (matrix at: 4)) + (y * (matrix at: 5)) + (z * (matrix at: 6)) + (matrix at: 7). rz _ (x * (matrix at: 8)) + (y * (matrix at: 9)) + (z * (matrix at: 10)) + (matrix at: 11). rw _ (x * (matrix at: 12)) + (y * (matrix at: 13)) + (z * (matrix at: 14)) + (matrix at: 15). pVertex at: PrimVtxRasterPosX put: (self cCoerce: rx to:'float'). pVertex at: PrimVtxRasterPosY put: (self cCoerce: ry to:'float'). pVertex at: PrimVtxRasterPosZ put: (self cCoerce: rz to:'float'). pVertex at: PrimVtxRasterPosW put: (self cCoerce: rw to:'float'). ! ! !B3DTransformerPlugin methodsFor: 'transforming' stamp: 'ar 2/13/1999 23:47'! transformVB: vtxArray count: vtxCount by: modelViewMatrix and: projectionMatrix flags: flags "Transform the entire vertex array by the given matrices" "TODO: Check the actual trade-offs between vtxCount and analyzing" | mvFlags prFlags pVertex hasNormals rescale | self var: #projectionMatrix declareC:'float *projectionMatrix'. self var: #modelViewMatrix declareC:'float *modelViewMatrix'. self var: #vtxArray declareC:'float *vtxArray'. self var: #pVertex declareC:'float *pVertex'. "Analyze the matrices for better performance" mvFlags _ self analyzeMatrix: modelViewMatrix. prFlags _ self analyzeMatrix: projectionMatrix. pVertex _ self cCoerce: vtxArray to: 'float *'. hasNormals _ flags anyMask: VBVtxHasNormals. "Check if we have to rescale the normals" hasNormals ifTrue:[ (mvFlags anyMask: FlagM44Identity) ifTrue:[rescale _ false] ifFalse:[rescale _ self analyzeMatrix3x3Length: modelViewMatrix]]. "<---- NOTE: The most likely case goes first ---->" ((mvFlags anyMask: FlagM44NoPerspective) and:[prFlags = 0]) ifTrue:[ "Modelview matrix has no perspective part and projection is not optimized" (mvFlags = FlagM44NoTranslation) = 0 ifTrue:[ "Modelview matrix with translation" 1 to: vtxCount do:[:i| hasNormals ifTrue:[self transformPrimitiveNormal: pVertex by: modelViewMatrix rescale: rescale]. self transformPrimitivePositionFast: pVertex by: modelViewMatrix. self transformPrimitiveRasterPosition: pVertex by: projectionMatrix. pVertex _ pVertex + PrimVertexSize]. ] ifFalse:[ "Modelview matrix without translation" 1 to: vtxCount do:[:i| hasNormals ifTrue:[self transformPrimitiveNormal: pVertex by: modelViewMatrix rescale: rescale]. self transformPrimitivePositionFaster: pVertex by: modelViewMatrix. self transformPrimitiveRasterPosition: pVertex by: projectionMatrix. pVertex _ pVertex + PrimVertexSize]. ]. ^nil]. "done" "<---- End of most likely case ---->" ((mvFlags bitAnd: prFlags) anyMask: FlagM44Identity) ifTrue:[ "If both are identity matrices just copy entries" 1 to: vtxCount do:[:i| pVertex at: PrimVtxRasterPosX put: (pVertex at: PrimVtxPositionX). pVertex at: PrimVtxRasterPosY put: (pVertex at: PrimVtxPositionY). pVertex at: PrimVtxRasterPosZ put: (pVertex at: PrimVtxPositionZ). pVertex at: PrimVtxRasterPosW put: 1.0. pVertex _ pVertex + PrimVertexSize]. ^nil]."done" (mvFlags anyMask: FlagM44Identity) ifTrue:[ "If model view matrix is identity just perform projection" 1 to: vtxCount do:[:i| self transformPrimitiveRasterPosition: pVertex by: projectionMatrix. pVertex _ pVertex + PrimVertexSize]. ^nil]. "done" "<--- modelview matrix not identity --->" (prFlags anyMask: FlagM44Identity) ifTrue:[ "If projection matrix is identity just transform and copy. Note: This case is not very likely so it's not been unrolled." 1 to: vtxCount do:[:i| hasNormals ifTrue:[self transformPrimitiveNormal: pVertex by: modelViewMatrix rescale: rescale]. mvFlags = (FlagM44NoPerspective + FlagM44NoPerspective) ifTrue:[ self transformPrimitivePositionFaster: pVertex by: modelViewMatrix. ] ifFalse:[mvFlags = FlagM44NoPerspective ifTrue:[ self transformPrimitivePositionFast: pVertex by: modelViewMatrix. ] ifFalse:[ self transformPrimitivePosition: pVertex by: modelViewMatrix. ]]. pVertex at: PrimVtxRasterPosX put: (pVertex at: PrimVtxPositionX). pVertex at: PrimVtxRasterPosY put: (pVertex at: PrimVtxPositionY). pVertex at: PrimVtxRasterPosZ put: (pVertex at: PrimVtxPositionZ). pVertex at: PrimVtxRasterPosW put: 1.0. pVertex _ pVertex + PrimVertexSize]. ^nil]. "done" "<----- None of the matrices is identity ---->" "Generic transformation" 1 to: vtxCount do:[:i| hasNormals ifTrue:[self transformPrimitiveNormal: pVertex by: modelViewMatrix rescale: rescale]. self transformPrimitivePosition: pVertex by: modelViewMatrix. self transformPrimitiveRasterPosition: pVertex by: projectionMatrix. pVertex _ pVertex + PrimVertexSize].! ! B3DFloatArray variableWordSubclass: #B3DVector2 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DVector2 commentStamp: '' prior: 0! I represent simple 2D coordinates in the Balloon 3D framework. I may be used to represent both, 2D points and 2D texture coordinates. ! !B3DVector2 methodsFor: 'initialize' stamp: 'ar 2/6/1999 23:30'! u: uValue v: vValue self u: uValue. self v: vValue.! ! !B3DVector2 methodsFor: 'initialize' stamp: 'ar 5/4/2000 15:50'! x: uValue y: vValue self x: uValue. self y: vValue.! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:26'! u ^self floatAt: 1! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:27'! u: aFloat self floatAt: 1 put: aFloat! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:27'! v ^self floatAt: 2! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/6/1999 23:27'! v: aFloat self floatAt: 2 put: aFloat! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:58'! x ^self at: 1! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 5/4/2000 16:00'! x: aFloat self floatAt: 1 put: aFloat! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:58'! y ^self at: 2! ! !B3DVector2 methodsFor: 'accessing' stamp: 'ar 5/4/2000 16:00'! y: aFloat self floatAt: 2 put: aFloat! ! !B3DVector2 methodsFor: 'converting' stamp: 'ar 2/13/1999 20:03'! asPoint ^self x @ self y! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVector2 class instanceVariableNames: ''! !B3DVector2 class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 23:31'! numElements ^2! ! !B3DVector2 class methodsFor: 'instance creation' stamp: 'ar 2/6/1999 23:31'! u: uValue v: vValue ^self new u: uValue v: vValue! ! !B3DVector2 class methodsFor: 'instance creation' stamp: 'ar 5/4/2000 15:49'! x: uValue y: vValue ^self new x: uValue y: vValue! ! B3DInplaceArray variableWordSubclass: #B3DVector2Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVector2Array class instanceVariableNames: ''! !B3DVector2Array class methodsFor: 'instance creation' stamp: 'ar 5/4/2000 15:59'! contentsClass ^B3DVector2! ! B3DFloatArray variableWordSubclass: #B3DVector3 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DVector3 commentStamp: '' prior: 0! I represent simple 3D coordinates, used throughout the entire Balloon 3D engine.! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! x ^self at: 1! ! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! x: aFloat self at: 1 put: aFloat! ! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! y ^self at: 2! ! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! y: aFloat self at: 2 put: aFloat! ! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! z ^self at: 3! ! !B3DVector3 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:24'! z: aFloat self at: 3 put: aFloat! ! !B3DVector3 methodsFor: 'vector functions'! cross: aVector "calculate the cross product from the receiver with aVector" ^self species x: self y * aVector z - (aVector y * self z) y: self z * aVector x - (aVector z * self x) z: self x * aVector y - (aVector x * self y)! ! !B3DVector3 methodsFor: 'vector functions'! length: newLength self safelyNormalize *= newLength! ! !B3DVector3 methodsFor: 'vector functions' stamp: 'ar 2/6/1999 00:32'! max: aVector ^B3DVector3 x: (self x max: aVector x) y: (self y max: aVector y) z: (self z max: aVector z)! ! !B3DVector3 methodsFor: 'vector functions' stamp: 'ar 2/6/1999 00:31'! min: aVector ^B3DVector3 x: (self x min: aVector x) y: (self y min: aVector y) z: (self z min: aVector z)! ! !B3DVector3 methodsFor: 'vector functions'! normalize self /= self length! ! !B3DVector3 methodsFor: 'vector functions'! normalized ^self / self length! ! !B3DVector3 methodsFor: 'vector functions' stamp: 'ar 2/7/1999 00:43'! safelyNormalize "Safely normalize the receiver, e.g. check if the length is non-zero" | length | length _ self length. length = 1.0 ifTrue:[^self]. length = 0.0 ifFalse:[self /= length].! ! !B3DVector3 methodsFor: 'vector functions'! safelyNormalized "Safely normalize the receiver, e.g. check if the length is non-zero" ^self copy safelyNormalize! ! !B3DVector3 methodsFor: 'vector functions'! squaredLength: newLength self length: newLength sqrt! ! !B3DVector3 methodsFor: 'private'! privateLoadFrom: srcObject self x: srcObject x y: srcObject y z: srcObject z.! ! !B3DVector3 methodsFor: 'initialize'! x: x y: y z: z self x: x. self y: y. self z: z.! ! !B3DVector3 methodsFor: 'converting' stamp: 'ar 2/6/1999 00:06'! asB3DVector3 ^self! ! !B3DVector3 methodsFor: 'converting' stamp: 'ar 2/6/1999 00:07'! asB3DVector4 ^B3DVector4 x: self x y: self y z: self z w: 1.0! ! !B3DVector3 methodsFor: 'interpolating' stamp: 'jsp 2/9/1999 11:17'! interpolateTo: end at: amountDone "Interpolates a new vector based on the instance vector, the end state vector, and the amount already done (between 0 and 1)." | tX tY tZ | tX _ self x. tY _ self y. tZ _ self z. ^ (B3DVector3 x: (tX + (((end x) - tX) * amountDone)) y: (tY + (((end y) - tY) * amountDone)) z: (tZ + (((end z) - tZ) * amountDone))). ! ! !B3DVector3 methodsFor: 'testing' stamp: 'laza 3/16/2000 16:30'! isZero ^self = B3DVector3 zero! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVector3 class instanceVariableNames: ''! !B3DVector3 class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:23'! numElements ^3! ! !B3DVector3 class methodsFor: 'instance creation' stamp: 'ar 2/15/1999 02:56'! value: aFloat ^self x: aFloat y: aFloat z: aFloat! ! !B3DVector3 class methodsFor: 'instance creation'! x: x y: y z: z ^self new x: x y: y z: z! ! !B3DVector3 class methodsFor: 'instance creation'! zero ^self new! ! B3DInplaceArray variableWordSubclass: #B3DVector3Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Arrays'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVector3Array class instanceVariableNames: ''! !B3DVector3Array class methodsFor: 'instance creation' stamp: 'ar 2/5/1999 22:51'! contentsClass ^B3DVector3! ! B3DFloatArray variableWordSubclass: #B3DVector4 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Vectors'! !B3DVector4 commentStamp: '' prior: 0! I represent 3D points in homogenous coordinates.! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:16'! w ^self at: 4! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:17'! w: aFloat self at: 4 put: aFloat! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:16'! x ^self at: 1! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:17'! x: aFloat self at: 1 put: aFloat! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:16'! y ^self at: 2! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:17'! y: aFloat self at: 2 put: aFloat! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:16'! z ^self at: 3! ! !B3DVector4 methodsFor: 'accessing' stamp: 'ar 2/1/1999 21:17'! z: aFloat self at: 3 put: aFloat! ! !B3DVector4 methodsFor: 'private'! privateLoadFrom: srcObject self x: srcObject x y: srcObject y z: srcObject z w: srcObject w.! ! !B3DVector4 methodsFor: 'initialize'! x: x y: y z: z w: w self x: x. self y: y. self z: z. self w: w.! ! !B3DVector4 methodsFor: 'converting' stamp: 'ar 2/6/1999 00:08'! asB3DVector3 | wValue | wValue _ self w. wValue = 0.0 ifTrue:[^B3DVector3 zero]. ^B3DVector3 x: self x / wValue y: self y / wValue z: self z / wValue! ! !B3DVector4 methodsFor: 'converting' stamp: 'ar 2/6/1999 00:07'! asB3DVector4 ^self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVector4 class instanceVariableNames: ''! !B3DVector4 class methodsFor: 'instance creation' stamp: 'ar 2/1/1999 21:21'! numElements ^4! ! !B3DVector4 class methodsFor: 'instance creation'! x: x y: y z: z ^self x: x y: y z: z w: 1.0! ! !B3DVector4 class methodsFor: 'instance creation'! x: x y: y z: z w: w ^self new x: x y: y z: z w: w! ! !B3DVector4 class methodsFor: 'instance creation'! zero ^self new! ! Object subclass: #B3DVertexBuffer instanceVariableNames: 'current vertexArray vertexCount indexArray indexCount primitive clipFlags flags ' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Engine'! !B3DVertexBuffer commentStamp: '' prior: 0! I represent the vertex buffer passed on throughout the entire Balloon 3D rendering pipeline. I store all information that may be needed by either part of the pipeline. Instance variables: current Tracking the current attributes of vertices vertexArray Container for all primitive vertices vertexCount The number of vertices in the vertex array indexArray Stores the indexes for indexed primitives indexCount Number of indexes in the index array primitive The type of primitive currently in the buffer clipFlags The clip mask of the vertices in the buffer flags Various state flags ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:44'! clipFlags ^clipFlags! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:44'! clipFlags: aNumber clipFlags _ aNumber! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/8/1999 17:39'! flags ^flags! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/8/1999 17:40'! flags: newFlags "Note: should be used with CARE!!" flags _ newFlags! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:13'! indexArray ^indexArray! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:13'! indexArray: aWordArray indexArray _ aWordArray! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:13'! indexCount ^indexCount! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/7/1999 02:14'! indexCount: aNumber indexCount _ aNumber! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:48'! primitive ^primitive! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:49'! primitive: aNumber primitive _ aNumber.! ! !B3DVertexBuffer methodsFor: 'accessing'! vertexArray ^vertexArray! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:44'! vertexArray: aB3DVertexArray vertexArray _ aB3DVertexArray! ! !B3DVertexBuffer methodsFor: 'accessing'! vertexCount ^vertexCount! ! !B3DVertexBuffer methodsFor: 'accessing' stamp: 'ar 2/5/1999 18:44'! vertexCount: aNumber vertexCount _ aNumber! ! !B3DVertexBuffer methodsFor: 'initialize' stamp: 'ar 2/13/1999 20:24'! initialize vertexArray _ B3DPrimitiveVertexArray new: 100. vertexCount _ 0. indexArray _ WordArray new: 100. indexCount _ 0. current _ B3DPrimitiveVertex new. flags _ 0. primitive _ nil.! ! !B3DVertexBuffer methodsFor: 'initialize' stamp: 'ar 2/13/1999 20:24'! reset vertexCount _ 0. indexCount _ 0.! ! !B3DVertexBuffer methodsFor: 'attributes'! color ^current color! ! !B3DVertexBuffer methodsFor: 'attributes'! color: aColor current color: aColor! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/15/1999 00:09'! loadIndexed: idxArray vertices: vertices normals: normals colors: colors texCoords: texCoords | vtxSize idxSize maxVtx maxIdx | "Check the size of the vertex array" vtxSize _ vertices size. vertexCount + vtxSize >= vertexArray size ifTrue:[ self growVertexArray: (vtxSize + vertexArray size + 100). ]. "Check the size of the index array" idxSize _ idxArray basicSize. indexCount + idxSize >= indexArray size ifTrue:[ self growIndexArray: (idxSize + indexArray size + 100). ]. "Check the sizes of normals, colors, and texCoords" (normals notNil and:[vtxSize ~= normals size]) ifTrue:[^self errorSizeMismatch]. (colors notNil and:[vtxSize ~= colors size]) ifTrue:[^self errorSizeMismatch]. (texCoords notNil and:[vtxSize ~= texCoords size]) ifTrue:[^self errorSizeMismatch]. "Turn off the appropriate flags if no attributes are given. Default to having vertex normals and texture coords." flags _ flags bitOr: (VBVtxHasNormals + VBVtxHasTexCoords). "Turn off tracking flags if no colors are given" colors ifNil:[flags _ flags bitAnd: VBNoTrackMask]. normals ifNil:[flags _ flags bitAnd: VBVtxHasNormals bitInvert32]. texCoords ifNil:[flags _ flags bitAnd: VBVtxHasTexCoords bitInvert32]. "Load the vertices" maxVtx _ self primLoadVB: vertexArray startingAt: vertexCount vertices: vertices normals: normals colors: colors texCoords: texCoords count: vtxSize default: current. "Load the indexes" maxIdx _ self primLoadIndexArrayInto: indexArray startingAt: indexCount from: idxArray count: idxSize max: maxVtx offset: vertexCount. "Adjust the size of the vertex array and the index array" vertexCount _ vertexCount + maxVtx. indexCount _ indexCount + maxIdx.! ! !B3DVertexBuffer methodsFor: 'attributes'! normal ^current normal! ! !B3DVertexBuffer methodsFor: 'attributes'! normal: aVector current normal: aVector! ! !B3DVertexBuffer methodsFor: 'attributes'! texCoords ^current texCoords! ! !B3DVertexBuffer methodsFor: 'attributes'! texCoords: aVector current texCoords: aVector! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/8/1999 17:37'! trackAmbientColor: aBool aBool ifTrue:[flags _ flags bitOr: VBTrackAmbient] ifFalse:[flags _ flags bitAnd: VBTrackAmbient bitInvert32]! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/8/1999 17:37'! trackDiffuseColor: aBool aBool ifTrue:[flags _ flags bitOr: VBTrackDiffuse] ifFalse:[flags _ flags bitAnd: VBTrackDiffuse bitInvert32]! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/8/1999 17:37'! trackEmissionColor: aBool aBool ifTrue:[flags _ flags bitOr: VBTrackEmission] ifFalse:[flags _ flags bitAnd: VBTrackEmission bitInvert32]! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/8/1999 17:37'! trackSpecularColor: aBool aBool ifTrue:[flags _ flags bitOr: VBTrackSpecular] ifFalse:[flags _ flags bitAnd: VBTrackSpecular bitInvert32]! ! !B3DVertexBuffer methodsFor: 'attributes'! vertex ^current position! ! !B3DVertexBuffer methodsFor: 'attributes' stamp: 'ar 2/7/1999 04:05'! vertex: aVector current position: aVector. self addPrimitiveVertex: current.! ! !B3DVertexBuffer methodsFor: 'private' stamp: 'ar 2/7/1999 02:31'! errorSizeMismatch ^self error:'Vertex size mismatch'! ! !B3DVertexBuffer methodsFor: 'private' stamp: 'ar 2/7/1999 02:41'! growIndexArray: newSize | newIdxArray | newIdxArray _ indexArray species new: newSize. newIdxArray replaceFrom: 1 to: indexArray size with: indexArray startingAt: 1. indexArray _ newIdxArray.! ! !B3DVertexBuffer methodsFor: 'private' stamp: 'ar 4/14/1999 02:35'! growVertexArray: newSize | newVtxArray | newVtxArray _ vertexArray species new: newSize. newVtxArray privateReplaceFrom: 1 to: vertexArray basicSize with: vertexArray startingAt: 1. vertexArray _ newVtxArray.! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:32'! hasVertexNormals ^flags anyMask: VBVtxHasNormals! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:32'! hasVertexTexCoords ^flags anyMask: VBVtxHasTexCoords! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:32'! trackAmbientColor "Return true if the vertex colors override the ambient part of material entries." ^flags anyMask: VBTrackAmbient! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:32'! trackDiffuseColor "Return true if the vertex colors override the diffuse part of material entries." ^flags anyMask: VBTrackDiffuse! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:32'! trackEmissionColor "Return true if the vertex colors override the emissive part of material entries." ^flags anyMask: VBTrackEmission! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:33'! trackSpecularColor "Return true if the vertex colors override the specular part of material entries." ^flags anyMask: VBTrackSpecular! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:35'! twoSidedLighting "Return true if we shade front and back facing polygons differently" ^flags anyMask: VBTwoSidedLighting! ! !B3DVertexBuffer methodsFor: 'testing' stamp: 'ar 2/8/1999 17:35'! useLocalViewer ^flags anyMask: VBUseLocalViewer! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 4/19/1999 16:16'! addClipIndex: index "Add a primitive index to the list of indexes." indexCount >= indexArray size ifTrue:[self growIndexArray: indexCount + (indexCount // 4) + 10]. indexArray at: (indexCount _ indexCount + 1) put: index.! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 4/19/1999 16:16'! addClipVertex: pVtx "Add a primitive vertex to the list of vertices processed. Return the index of the vertex." vertexCount >= vertexArray size ifTrue:[self growVertexArray: vertexCount + (vertexCount // 4) + 10]. vertexArray at: (vertexCount _ vertexCount + 1) put: pVtx. ^vertexCount! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 23:13'! addPrimitiveIndex: index "Add a primitive index to the list of indexes." indexCount >= indexArray size ifTrue:[self growIndexArray: indexCount * 3 // 2 + 100]. indexArray at: (indexCount _ indexCount + 1) put: index.! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 04:02'! addPrimitiveVertex: pVtx "Add a primitive vertex to the list of vertices processed. Return the index of the vertex." vertexCount >= vertexArray size ifTrue:[self growVertexArray: vertexCount * 3 // 2 + 100]. vertexArray at: (vertexCount _ vertexCount + 1) put: pVtx. ^vertexCount! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 23:16'! growForClip vertexCount*2+100 > vertexArray size ifTrue:[self growVertexArray: vertexCount*2+100]. indexCount*2+100 > indexArray size ifTrue:[self growIndexArray: indexCount*2+100].! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 16:07'! primitiveColorAt: index ^(vertexArray at: index) color! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 04:04'! primitiveIndexAt: index ^indexArray at: index! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 04:05'! primitiveIndexAt: index put: value ^indexArray at: index put: value! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 04:04'! primitiveVertexAt: index ^vertexArray at: index! ! !B3DVertexBuffer methodsFor: 'protected' stamp: 'ar 2/7/1999 04:04'! primitiveVertexAt: index put: aPrimitiveVertex ^vertexArray at: index put: aPrimitiveVertex! ! !B3DVertexBuffer methodsFor: 'primitives' stamp: 'ar 4/5/1999 11:48'! primLoadIndexArrayInto: dstArray startingAt: dstStart from: idxArray count: count max: maxValue offset: vtxOffset "Primitive. Load the given index array into the receiver. NOTE: dstStart is a zero-based index." | idx | "self flag: #b3dDebug. self primitiveFailed." 1 to: count do:[:i| idx _ idxArray basicAt: i. (idx < 1 or:[idx > maxValue]) ifTrue:[^self error:'Index out of range']. dstArray at: dstStart + i put: idx + vtxOffset. ]. ^count! ! !B3DVertexBuffer methodsFor: 'primitives' stamp: 'ar 4/5/1999 11:48'! primLoadVB: dstArray startingAt: dstStart vertices: vertices normals: normals colors: colors texCoords: texCoords count: count default: defaultValues | hasNormals hasColors hasTexCoords pVtx defaultNormal defaultColor defaultTexCoords | "self flag: #b3dDebug. self primitiveFailed." defaultNormal _ defaultValues normal. defaultColor _ defaultValues color. defaultTexCoords _ defaultValues texCoords. hasNormals _ normals notNil. hasColors _ colors notNil. hasTexCoords _ texCoords notNil. 1 to: count do:[:i| pVtx _ dstArray at: dstStart + i. pVtx position: (vertices at: i). pVtx normal: (hasNormals ifTrue:[normals at: i] ifFalse:[defaultNormal]). pVtx color: (hasColors ifTrue:[colors at: i] ifFalse:[defaultColor]). pVtx texCoords: (hasTexCoords ifTrue:[texCoords at: i] ifFalse:[defaultTexCoords]). dstArray at: dstStart + i put: pVtx. ]. ^count! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVertexBuffer class instanceVariableNames: ''! !B3DVertexBuffer class methodsFor: 'instance creation'! new ^super new initialize! ! B3DEnginePlugin subclass: #B3DVertexBufferPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !B3DVertexBufferPlugin methodsFor: 'primitives' stamp: 'ar 2/14/1999 23:32'! b3dLoadIndexArray "Primitive. Load the given index array into the receiver. NOTE: dstStart is a zero-based index." | vtxOffset maxValue count srcArray srcPtr idx dstStart dstArray dstSize dstPtr | self export: true. self inline: false. self var: #dstPtr declareC:'int *dstPtr'. self var: #srcPtr declareC:'int *srcPtr'. "Load the arguments" vtxOffset _ interpreterProxy stackIntegerValue: 0. maxValue _ interpreterProxy stackIntegerValue: 1. count _ interpreterProxy stackIntegerValue: 2. srcArray _ interpreterProxy stackObjectValue: 3. dstStart _ interpreterProxy stackIntegerValue: 4. dstArray _ interpreterProxy stackObjectValue: 5. interpreterProxy failed ifTrue:[^nil]. "Will cover all possible problems above" "Check srcArray" (interpreterProxy isWords: srcArray) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy slotSizeOf: srcArray) < count) ifTrue:[^interpreterProxy primitiveFail]. srcPtr _ self cCoerce: (interpreterProxy firstIndexableField: srcArray) to:'int*'. "Check dstArray" dstSize _ interpreterProxy slotSizeOf: dstArray. "Check if there is enough room left in dstArray" dstStart + count > dstSize ifTrue:[^interpreterProxy primitiveFail]. dstPtr _ self cCoerce: (interpreterProxy firstIndexableField: dstArray) to:'int *'. "Do the actual work" 0 to: count-1 do:[:i| idx _ srcPtr at: i. (idx < 1 or:[idx > maxValue]) ifTrue:[^interpreterProxy primitiveFail]. dstPtr at: dstStart + i put: idx + vtxOffset. ]. "Clean up the stack" interpreterProxy pop: 7. "Pop args+rcvr" interpreterProxy pushInteger: count. ! ! !B3DVertexBufferPlugin methodsFor: 'primitives' stamp: 'ar 11/20/2000 22:48'! b3dLoadVertexBuffer "Primitive. Load the data into the given vertex buffer. NOTE: dstStart is a zero-based index." | defaultVtx defaultNormal defaultTexCoords defaultColor count texPtr colorPtr normalPtr vtxPtr dstStart dstPtr pVtx | self export: true. self inline: false. self var: #defaultVtx declareC:'int *defaultVtx'. self var: #defaultNormal declareC:'int *defaultNormal'. self var: #defaultTexCoords declareC:'int *defaultTexCoords'. self var: #defaultColor declareC:'int *defaultColor'. self var: #texPtr declareC:'int *texPtr'. self var: #colorPtr declareC:'int *colorPtr'. self var: #normalPtr declareC:'int *normalPtr'. self var: #vtxPtr declareC:'int *vtxPtr'. self var: #dstPtr declareC:'int *dstPtr'. self var: #pVtx declareC:'int *pVtx'. defaultVtx _ self stackPrimitiveVertex: 0. count _ interpreterProxy stackIntegerValue: 1. texPtr _ self vbLoadArray: (interpreterProxy stackObjectValue: 2) size: 2*count. colorPtr _ self vbLoadArray: (interpreterProxy stackObjectValue: 3) size: count. normalPtr _ self vbLoadArray: (interpreterProxy stackObjectValue: 4) size: 3*count. vtxPtr _ self vbLoadArray: (interpreterProxy stackObjectValue: 5) size: 3*count. dstStart _ interpreterProxy stackIntegerValue: 6. dstPtr _ self stackPrimitiveVertexArray: 7 ofSize: dstStart + count. "Check for all problems above" (dstPtr = nil or:[defaultVtx == nil or:[interpreterProxy failed]]) ifTrue:[^interpreterProxy primitiveFail]. "Install default values" normalPtr = nil ifTrue:[defaultNormal _ defaultVtx + PrimVtxNormal] ifFalse:[defaultNormal _ normalPtr]. texPtr = nil ifTrue:[defaultTexCoords _ defaultVtx + PrimVtxTexCoords] ifFalse:[defaultTexCoords _ texPtr]. colorPtr = nil ifTrue:[defaultColor _ defaultVtx + PrimVtxColor32] ifFalse:[defaultColor _ colorPtr]. "Do the actual stuff" pVtx _ dstPtr + (dstStart * PrimVertexSize). 0 to: count-1 do:[:i| pVtx at: PrimVtxPositionX put: (vtxPtr at: 0). pVtx at: PrimVtxPositionY put: (vtxPtr at: 1). pVtx at: PrimVtxPositionZ put: (vtxPtr at: 2). pVtx at: PrimVtxNormalX put: (defaultNormal at: 0). pVtx at: PrimVtxNormalY put: (defaultNormal at: 1). pVtx at: PrimVtxNormalZ put: (defaultNormal at: 2). pVtx at: PrimVtxColor32 put: (defaultColor at: 0). pVtx at: PrimVtxTexCoordU put: (defaultTexCoords at: 0). pVtx at: PrimVtxTexCoordV put: (defaultTexCoords at: 1). "And go to the next vertex" pVtx _ pVtx + PrimVertexSize. vtxPtr _ vtxPtr + 3. normalPtr = nil ifFalse:[defaultNormal _ defaultNormal + 3]. colorPtr = nil ifFalse:[defaultColor _ defaultColor + 1]. texPtr = nil ifFalse:[defaultTexCoords _ defaultTexCoords + 2]. ]. "Clean up stack" interpreterProxy pop: 9. "Pop args+rcvr" interpreterProxy pushInteger: count.! ! !B3DVertexBufferPlugin methodsFor: 'private' stamp: 'ar 4/17/1999 22:29'! vbLoadArray: oop size: count "Load the word based array of size count from the given oop" self returnTypeC: 'void*'. self inline: false. oop == nil ifTrue:[interpreterProxy primitiveFail. ^nil]. oop == interpreterProxy nilObject ifTrue:[^nil]. (interpreterProxy isWords: oop) ifFalse:[interpreterProxy primitiveFail. ^nil]. (interpreterProxy slotSizeOf: oop) = count ifFalse:[interpreterProxy primitiveFail. ^nil]. ^interpreterProxy firstIndexableField: oop! ! B3DEnginePart subclass: #B3DVertexClipper instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'B3DEngineConstants ' category: 'Balloon3D-Engine'! !B3DVertexClipper commentStamp: '' prior: 0! I provide clipping capabilities for rasterizers needing explicit clipping.! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 11/7/2000 17:24'! postProcessVertexBuffer: vb "Clip individual items depending on the primitive type" vb growForClip. "Make sure we have enough space during primitive operation" ^super processVertexBuffer: vb.! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 11/7/2000 17:24'! preProcessVertexBuffer: vb "Clip the elements in the vertex buffer. Return true if all vertices are inside. Return false if all vertices are outside. If partial clipping occurs, return nil." | fullMask | fullMask _ self determineClipFlags: vb vertexArray count: vb vertexCount. vb clipFlags: fullMask. "Check if all vertices are inside, so no clipping is necessary" (fullMask allMask: InAllMask) ifTrue:[^true]. "Check if all vertices are outside, so we can get rid of the entire buffer" (fullMask anyMask: OutAllMask) ifTrue:[ "Reset the number of vertices in the vertex buffer to zero to indicate all outside" vb reset. ^false]. ^nil! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 4/17/1999 23:06'! processIndexedLines: vb "Process an indexed line set" ^self error:'Lines are not yet implemented'! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 9/17/1999 20:08'! processIndexedQuads: vb "Clip an indexed quad set" | vtxArray idxArray tempVB idx1 idx2 idx3 maxVtx maxIdx index lastIndex clipFlags vtx returnValue | self flag: #b3dPrimitive. returnValue _ false. "Assume we don't see nothing" tempVB _ B3DVertexBuffer new. vtxArray _ vb vertexArray. idxArray _ vb indexArray. maxVtx _ vb indexCount. maxIdx _ vb indexCount. lastIndex _ -3. "Hack the lastIndex ;-)" [index _ self primNextClippedQuadAfter: lastIndex + 4 vertices: vtxArray count: maxVtx indexes: idxArray count: maxIdx. index = 0] whileFalse:[ "Need a partial clip here, storing the triangulated polygon at the end" tempVB reset. clipFlags _ InAllMask + OutAllMask. "Copy the poly into tempVB" 0 to: 3 do:[:i| vtx _ vtxArray at: (idxArray at: index+i). idxArray at: index+i put: 0. tempVB addClipVertex: vtx. clipFlags _ clipFlags bitAnd: vtx clipFlags]. tempVB clipFlags: clipFlags. self processPolygon: tempVB. tempVB vertexCount > 2 ifTrue:[ returnValue _ nil. "We see some parts and not others" idx1 _ vb addClipVertex: (tempVB vertexArray at: 1). 3 to: tempVB vertexCount do:[:j| idx2 _ vb addClipVertex: (tempVB vertexArray at: j-1). idx3 _ vb addClipVertex: (tempVB vertexArray at: j). vb addClipIndex: idx1. vb addClipIndex: idx2. vb addClipIndex: idx3. vb addClipIndex: idx3. ]. ]. lastIndex _ index. ]. ^returnValue! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 9/17/1999 20:08'! processIndexedTriangles: vb "Clip an indexed triangle set" | vtxArray idxArray tempVB idx1 idx2 idx3 maxVtx maxIdx index lastIndex clipFlags vtx returnValue | self flag: #b3dPrimitive. returnValue _ false. "Assume we don't see nothing" tempVB _ B3DVertexBuffer new. vtxArray _ vb vertexArray. idxArray _ vb indexArray. maxVtx _ vb indexCount. maxIdx _ vb indexCount. lastIndex _ -2. "Hack the lastIndex ;-)" [index _ self primNextClippedTriangleAfter: lastIndex + 3 vertices: vtxArray count: maxVtx indexes: idxArray count: maxIdx. index = 0] whileFalse:[ "Need a partial clip here, storing the triangulated polygon at the end" tempVB reset. clipFlags _ InAllMask + OutAllMask. "Copy the poly into tempVB" 0 to: 2 do:[:i| vtx _ vtxArray at: (idxArray at: index+i). idxArray at: index+i put: 0. tempVB addClipVertex: vtx. clipFlags _ clipFlags bitAnd: vtx clipFlags]. tempVB clipFlags: clipFlags. self processPolygon: tempVB. tempVB vertexCount > 2 ifTrue:[ returnValue _ nil. "We see some parts and not others" idx1 _ vb addClipVertex: (tempVB vertexArray at: 1). 3 to: tempVB vertexCount do:[:j| idx2 _ vb addClipVertex: (tempVB vertexArray at: j-1). idx3 _ vb addClipVertex: (tempVB vertexArray at: j). vb addClipIndex: idx1. vb addClipIndex: idx2. vb addClipIndex: idx3. ]. ]. lastIndex _ index. ]. ^returnValue! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 4/17/1999 23:06'! processLineLoop: vertexBuffer "Process a closed line defined by the vertex buffer" ^self error:'Lines are not yet implemented'! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 4/17/1999 23:06'! processLines: vertexBuffer "Process a series of lines defined by each two points the vertex buffer" ^self error:'Lines are not yet implemented'! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 4/17/1999 23:06'! processPoints: vertexBuffer "Process a series of points defined by the vertex buffer" ^self error:'Points are not yet implemented'! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 9/17/1999 20:10'! processPolygon: vb "Process a polygon from the vertex buffer that requires partial clipping" | outMask vtxArray tempVtxArray count | outMask := vb clipFlags bitAnd: OutAllMask. vtxArray _ vb vertexArray. tempVtxArray _ vtxArray clone. "Note: tempVtxArray has the SAME contents as vtxArray since the data is stored inplace. Thus we can decide from which buffer to start the clipping operation later on." count _ self clipPolygon: vtxArray count: vb vertexCount with: tempVtxArray mask: outMask. vb vertexCount: count. count < 3 ifTrue:[^false]. ^nil! ! !B3DVertexClipper methodsFor: 'processing' stamp: 'ar 11/7/2000 17:25'! processVertexBuffer: vb "Clip the elements in the vertex buffer. Return true if all vertices are inside. Return false if all vertices are outside. If partial clipping occurs, return nil." | result | result _ self preProcessVertexBuffer: vb. result == nil ifFalse:[^result]. ^self postProcessVertexBuffer: vb! ! !B3DVertexClipper methodsFor: 'clip flags' stamp: 'ar 2/16/1999 19:20'! clipFlagsX: x y: y z: z w: w "Determine the clip flags for the given vector. The clip flags are a combination of inside and outside flags that can be used to easily reject an entire buffer if it is completely inside or outside and can also be used to detect the most commen cases in clipping (e.g., intersection with one boundary only)." | w2 flags | w2 _ w negated. flags _ 0. flags _ flags bitOr:(x >= w2 ifTrue:[InLeftBit] ifFalse:[OutLeftBit]). flags _ flags bitOr:(x <= w ifTrue:[InRightBit] ifFalse:[OutRightBit]). flags _ flags bitOr:(y >= w2 ifTrue:[InBottomBit] ifFalse:[OutBottomBit]). flags _ flags bitOr:(y <= w ifTrue:[InTopBit] ifFalse:[OutTopBit]). flags _ flags bitOr:(z >= w2 ifTrue:[InFrontBit] ifFalse:[OutFrontBit]). flags _ flags bitOr:(z <= w ifTrue:[InBackBit] ifFalse:[OutBackBit]). ^flags! ! !B3DVertexClipper methodsFor: 'clip flags' stamp: 'ar 2/16/1999 19:20'! determineClipFlags: vtxArray count: vtxCount "Determine the clip flags for all the vertices in the vertex array" | fullMask flags | self flag: #b3dPrimitive. fullMask _ InAllMask + OutAllMask. vtxArray upTo: vtxCount do:[:vtx| flags _ (self clipFlagsX: vtx rasterPosX y: vtx rasterPosY z: vtx rasterPosZ w: vtx rasterPosW). vtx clipFlags: flags. fullMask _ fullMask bitAnd: flags. ]. ^fullMask! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygon: vtxArray count: vtxCount with: tempVtxArray mask: outMask "Clip the polygon defined by vtxCount vertices in vtxArray. tempVtxArray is a temporary storage area used for copying the vertices back and forth during clipping operation. outMask is the full clip mask of the vertex buffer, allowing some optimizations of the clipping code. NOTE: It is significant here that the contents of vtxArray and tempVtxArray are equal." | count | self flag: #b3dPrimitive. "Check if the polygon is outside one boundary only. If so, just do this single clipping operation avoiding multiple enumeration." outMask = OutLeftBit ifTrue:[^self clipPolygonLeftFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutRightBit ifTrue:[^self clipPolygonRightFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutTopBit ifTrue:[^self clipPolygonTopFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutBottomBit ifTrue:[^self clipPolygonBottomFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutFrontBit ifTrue:[^self clipPolygonFrontFrom: tempVtxArray to: vtxArray count: vtxCount]. outMask = OutBackBit ifTrue:[^self clipPolygonBackFrom: tempVtxArray to: vtxArray count: vtxCount]. "Just do each of the clipping operations" count _ vtxCount. count _ self clipPolygonLeftFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonRightFrom: tempVtxArray to: vtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonTopFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonBottomFrom: tempVtxArray to: vtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonFrontFrom: vtxArray to: tempVtxArray count: count. count = 0 ifTrue:[^0]. count _ self clipPolygonBackFrom: tempVtxArray to: vtxArray count: count. ^count! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygonBackFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InBackBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InBackBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self backClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygonBottomFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InBottomBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InBottomBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self bottomClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygonFrontFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InFrontBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InFrontBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self frontClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 4/16/1999 06:01'! clipPolygonLeftFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InLeftBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InLeftBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self leftClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygonRightFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InRightBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InRightBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self rightClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping polygons' stamp: 'ar 2/16/1999 19:21'! clipPolygonTopFrom: buf1 to: buf2 count: n | last next t outIndex inLast inNext outVtx | outIndex _ 0. last _ buf1 at: n. inLast _ last clipFlags anyMask: InTopBit. 1 to: n do:[:i| next _ buf1 at: i. inNext _ next clipFlags anyMask: InTopBit. inLast = inNext ifFalse:["Passes clip boundary" t _ self topClipValueFrom: last to: next. outVtx _ self interpolateFrom: last to: next at: t. buf2 at: (outIndex _ outIndex+1) put: outVtx]. inNext ifTrue:[buf2 at: (outIndex _ outIndex+1) put: next]. last _ next. inLast _ inNext. ]. ^outIndex! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! backClipValueFrom: last to: next ^(last rasterPosZ - last rasterPosW) / ((next rasterPosW - last rasterPosW) - (next rasterPosZ - last rasterPosZ)).! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! bottomClipValueFrom: last to: next ^(last rasterPosY + last rasterPosW) negated / ((next rasterPosW - last rasterPosW) + (next rasterPosY - last rasterPosY)).! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! frontClipValueFrom: last to: next ^(last rasterPosZ + last rasterPosW) negated / ((next rasterPosW - last rasterPosW) + (next rasterPosZ - last rasterPosZ)).! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 4/16/1999 06:43'! interpolateFrom: last to: next at: t "Interpolate the primitive vertices last/next at the parameter t" | out | out _ next clone. "Interpolate raster position" out rasterPos: ((next rasterPos - last rasterPos) * t) + last rasterPos. out clipFlags: (self clipFlagsX: out rasterPosX y: out rasterPosY z: out rasterPosZ w: out rasterPosW). "Interpolate color" out b3dColor: ((next b3dColor - last b3dColor) * t) + last b3dColor. "Interpolate texture coordinates" out texCoords: ((next texCoords - last texCoords) * t) + last texCoords. ^out! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! leftClipValueFrom: last to: next ^(last rasterPosX + last rasterPosW) negated / ((next rasterPosW - last rasterPosW) + (next rasterPosX - last rasterPosX)).! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! rightClipValueFrom: last to: next ^(last rasterPosX - last rasterPosW) / ((next rasterPosW - last rasterPosW) - (next rasterPosX - last rasterPosX)).! ! !B3DVertexClipper methodsFor: 'clipping utilitites' stamp: 'ar 2/16/1999 19:21'! topClipValueFrom: last to: next ^(last rasterPosY - last rasterPosW) / ((next rasterPosW - last rasterPosW) - (next rasterPosY - last rasterPosY)).! ! !B3DVertexClipper methodsFor: 'private' stamp: 'ar 2/16/1999 19:21'! primNextClippedQuadAfter: firstIndex vertices: vtxArray count: vtxCount indexes: idxArray count: idxCount "Find the next partially clipped quad from the vertex buffer and return its index. If there are no more partially clipped quads return zero." | quadMask | self flag: #b3dPrimitive. firstIndex to: idxCount by: 4 do:[:i| quadMask _ ((vtxArray at: (idxArray at: i)) clipFlags bitAnd: (vtxArray at: (idxArray at: i+1)) clipFlags) bitAnd: ((vtxArray at: (idxArray at: i+2)) clipFlags bitAnd: (vtxArray at: (idxArray at: i+3)) clipFlags). "Check if quad is completely inside" (quadMask allMask: InAllMask) ifFalse:[ "Quad is not completely inside -> needs clipping." (quadMask anyMask: OutAllMask) ifTrue:[ "quad is completely outside. Store all zeros" idxArray at: i put: 0. idxArray at: i+1 put: 0. idxArray at: i+2 put: 0. idxArray at: i+3 put: 0. ] ifFalse:[ "quad must be partially clipped." ^i ]. ]. ]. ^0 "No more entries"! ! !B3DVertexClipper methodsFor: 'private' stamp: 'ar 2/16/1999 19:22'! primNextClippedTriangleAfter: firstIndex vertices: vtxArray count: vtxCount indexes: idxArray count: idxCount "Find the next partially clipped triangle from the vertex buffer and return its index. If there are no more partially clipped triangles return zero." | triMask | self flag: #b3dPrimitive. firstIndex to: idxCount by: 3 do:[:i| triMask _ ((vtxArray at: (idxArray at: i)) clipFlags bitAnd: (vtxArray at: (idxArray at: i+1)) clipFlags) bitAnd: (vtxArray at: (idxArray at: i+2)) clipFlags. "Check if tri is completely inside" (triMask allMask: InAllMask) ifFalse:[ "Tri is not completely inside -> needs clipping." (triMask anyMask: OutAllMask) ifTrue:[ "tri is completely outside. Store all zeros" idxArray at: i put: 0. idxArray at: i+1 put: 0. idxArray at: i+2 put: 0. ] ifFalse:[ "tri must be partially clipped." ^i ]. ]. ]. ^0 "No more entries"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVertexClipper class instanceVariableNames: ''! !B3DVertexClipper class methodsFor: 'class initialization' stamp: 'ar 2/13/1999 20:31'! initialize "B3DClipper initialize" "InLeftBit _ 16r01. OutLeftBit _ 16r02. InRightBit _ 16r04. OutRightBit _ 16r08. InTopBit _ 16r10. OutTopBit _ 16r20. InBottomBit _ 16r40. OutBottomBit _ 16r80. InFrontBit _ 16r100. OutFrontBit _ 16r200. InBackBit _ 16r400. OutBackBit _ 16r800. InAllMask _ 16r555. 1365 OutAllMask _ 16rAAA 2730."! ! !B3DVertexClipper class methodsFor: 'testing' stamp: 'ar 2/14/1999 01:40'! isAvailable "Return true if this part of the engine is available" ^true! ! B3DEnginePart subclass: #B3DVertexRasterizer instanceVariableNames: 'target offset clipRect viewport dirtyRect texture textureStack vbBounds ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DVertexRasterizer commentStamp: '' prior: 0! I am the superclass for all rasterizers in the Balloon 3D engine. Rasterizers perform the final pixel generation of the primitives and are the most time-critical part of the engine. Rasterizers keep a viewport, defining the destination rectangle and a dirtyRect, defining the actual affected 2D region of the rasterization process. Instance variables: viewport the destination rectangle dirtyRect the affected region of all rasterization operations performed! !B3DVertexRasterizer methodsFor: 'initialize' stamp: 'ar 2/16/1999 04:30'! finish "Force everything on the output device"! ! !B3DVertexRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:29'! flush "Flush pending operations."! ! !B3DVertexRasterizer methodsFor: 'initialize' stamp: 'ar 2/16/1999 03:15'! initialize super initialize. textureStack _ OrderedCollection new.! ! !B3DVertexRasterizer methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:53'! reset super reset. textureStack _ OrderedCollection new.! ! !B3DVertexRasterizer methodsFor: 'initialize' stamp: 'ar 5/26/2000 15:12'! target: aForm "Set the target for rendering operations" target _ aForm! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 5/26/2000 15:18'! clipRect "Return the current clipping rectangle" ^clipRect! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 5/26/2000 15:18'! clipRect: aRectangle "Install a clipping rectangle if necessary" clipRect _ aRectangle! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/7/1999 03:38'! dirtyRect "If the dirtyRect is not known (e.g., not implemented by a particular rasterizer) return the full viewport" ^dirtyRect ifNil:[viewport]! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/7/1999 03:35'! dirtyRect: aRectangle dirtyRect _ aRectangle! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:16'! popTexture texture _ textureStack removeLast.! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:15'! pushTexture textureStack addLast: texture! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:15'! texture ^texture! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 2/16/1999 03:15'! texture: aForm texture _ aForm! ! !B3DVertexRasterizer methodsFor: 'accessing'! viewport ^viewport! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 7/11/2000 11:04'! viewport: aRectangle | r | r _ aRectangle. offset ifNotNil:[r _ r translateBy: offset]. viewport _ B3DViewport origin: r origin truncated corner: r corner truncated. viewport toggleYScale.! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 5/26/2000 15:17'! viewportOffset "Return the viewport offset" ^offset! ! !B3DVertexRasterizer methodsFor: 'accessing' stamp: 'ar 5/26/2000 15:17'! viewportOffset: aPoint "Set the viewport offset" offset _ aPoint! ! !B3DVertexRasterizer methodsFor: 'testing'! needsClip "Return true if we need to clip polygons before rasterization. Generally, this should not be the case." ^self subclassResponsibility! ! !B3DVertexRasterizer methodsFor: 'processing' stamp: 'ar 2/16/1999 02:02'! clearDepthBuffer "If the rasterizer uses a depth buffer, clear it."! ! !B3DVertexRasterizer methodsFor: 'processing' stamp: 'ar 5/28/2000 02:25'! clearViewport: aColor "Clear the current viewport using the given color" target ifNotNil:[ target fill: viewport rule: Form over fillColor: aColor asColor ].! ! !B3DVertexRasterizer methodsFor: 'processing' stamp: 'ar 11/7/1999 18:04'! processVertexBuffer: vb vbBounds _ nil. super processVertexBuffer: vb. ^vbBounds! ! B3DEnginePart subclass: #B3DVertexShader instanceVariableNames: 'lights material materialStack ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DVertexShader methodsFor: 'initialize' stamp: 'ar 2/17/1999 04:17'! initialize super initialize. lights _ OrderedCollection new. material _ B3DMaterial new. materialStack _ OrderedCollection new: 10.! ! !B3DVertexShader methodsFor: 'initialize' stamp: 'ar 4/10/1999 22:55'! reset super reset. lights _ OrderedCollection new. material _ B3DMaterial new. materialStack _ OrderedCollection new: 10.! ! !B3DVertexShader methodsFor: 'shading' stamp: 'ar 4/3/1999 20:10'! processVertexBuffer: vb | colors emissionPart | colors _ B3DColor4Array new: vb vertexCount. "Load initial colors (e.g., emission part)" vb trackEmissionColor ifFalse:[ emissionPart _ material emission. 1 to: vb vertexCount do:[:i| colors at: i put: emissionPart]. ] ifTrue:[ 1 to: vb vertexCount do:[:i| colors at: i put: (vb primitiveVertexAt: i) b3dColor]. ]. lights do:[:light| light shadeVertexBuffer: vb with: material into: colors. ]. colors clampAllFrom: 0.0 to: 1.0. vb vertexArray upTo: vb vertexCount doWithIndex:[:vtx :i| vtx color: (colors at: i)]. ! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:14'! addLight: aLightSource lights add: aLightSource. ^lights size! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:36'! material ^material! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:36'! material: aMaterial material _ aMaterial.! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/7/1999 16:19'! materialColor: aColor material ambientPart: aColor. material diffusePart: aColor. material specularPart: aColor.! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:35'! popMaterial material _ materialStack removeLast.! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/7/1999 19:35'! pushMaterial materialStack addLast: material.! ! !B3DVertexShader methodsFor: 'accessing' stamp: 'ar 2/17/1999 04:16'! removeLight: lightIndex "Remove the light with the given index" (lightIndex < 1 or:[lightIndex > lights size]) ifTrue:[^nil]. lights at: lightIndex put: nil. "So we don't change the indexes"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVertexShader class instanceVariableNames: ''! !B3DVertexShader class methodsFor: 'testing' stamp: 'ar 2/14/1999 01:41'! isAvailable "Return true if this part of the engine is available" ^true! ! B3DEnginePart subclass: #B3DVertexTransformer instanceVariableNames: 'modelMatrix viewMatrix textureMatrix currentMatrix needsUpdate matrixStack matrixState ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Engine'! !B3DVertexTransformer methodsFor: 'initialize' stamp: 'ar 4/18/1999 02:23'! initialize super initialize. modelMatrix _ B3DMatrix4x4 identity. viewMatrix _ B3DMatrix4x4 identity. textureMatrix _ B3DMatrix4x4 identity. currentMatrix _ modelMatrix. matrixStack _ OrderedCollection new: 30. matrixStack resetTo: 1. needsUpdate _ false.! ! !B3DVertexTransformer methodsFor: 'initialize' stamp: 'ar 4/16/1999 07:59'! reset super reset. modelMatrix := B3DMatrix4x4 identity. viewMatrix := B3DMatrix4x4 identity. textureMatrix := B3DMatrix4x4 identity. currentMatrix := modelMatrix. matrixStack := OrderedCollection new: 30. matrixStack resetTo: 1. needsUpdate := false.! ! !B3DVertexTransformer methodsFor: 'public' stamp: 'ar 8/19/1999 16:31'! transformDirection: aVector3 | zero one | zero _ B3DVector3 new. one _ zero + aVector3. zero _ self transformPosition: zero. one _ self transformPosition: one. ^one - zero! ! !B3DVertexTransformer methodsFor: 'public' stamp: 'ar 2/8/1999 01:33'! transformPosition: aVector3 | pVtx | pVtx _ B3DPrimitiveVertex new. pVtx position: aVector3. self privateTransformPrimitiveVertex: pVtx byModelView: self modelViewMatrix. ^pVtx position! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:58'! currentMatrix ^currentMatrix! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:58'! matrixMode currentMatrix == modelMatrix ifTrue:[^#modelView]. currentMatrix == viewMatrix ifTrue:[^#projection]. currentMatrix == textureMatrix ifTrue:[^#texture]. self error:'Bad matrix state'.! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:58'! matrixMode: aSymbol aSymbol == #modelView ifTrue:[currentMatrix := modelMatrix. ^self]. aSymbol == #projection ifTrue:[currentMatrix := viewMatrix. ^self]. aSymbol == #texture ifTrue:[currentMatrix := textureMatrix. ^self]. self error:'Bad matrix mode'.! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:58'! modelViewMatrix ^modelMatrix! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:59'! popMatrix "Pop the current matrix from the stack" matrixStack isEmpty ifTrue:[^self error:'Empty matrix stack']. currentMatrix loadFrom: matrixStack removeLast.! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:59'! projectionMatrix ^viewMatrix! ! !B3DVertexTransformer methodsFor: 'accessing' stamp: 'ar 2/2/1999 18:59'! pushMatrix "Push the current matrix" | theMatrix | theMatrix := B3DMatrix4x4 new. theMatrix loadFrom: currentMatrix. matrixStack addLast: theMatrix.! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/2/1999 19:00'! loadIdentity currentMatrix setIdentity. needsUpdate := true.! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/2/1999 19:00'! loadMatrix: aMatrix currentMatrix loadFrom: aMatrix. needsUpdate := true.! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/7/1999 01:39'! lookFrom: position to: target up: upDirection "create a matrix such that we look from eyePoint to centerPoint using upDirection" | xDir yDir zDir m | "calculate z vector" zDir _ target - position. zDir safelyNormalize. "calculate x vector" xDir _ upDirection cross: zDir. xDir safelyNormalize. "recalc y vector" yDir _ zDir cross: xDir. yDir safelyNormalize. m := B3DMatrix4x4 new. m a11: xDir x; a12: xDir y; a13: xDir z; a14: 0.0; a21: yDir x; a22: yDir y; a23: yDir z; a24: 0.0; a31: zDir x; a32: zDir y; a33: zDir z; a34: 0.0; a41: 0.0; a42: 0.0; a43: 0.0; a44: 1.0. self transformBy: m. self translateBy: position negated.! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/2/1999 19:00'! multiplyMatrix: aMatrix "Multiply aMatrix with the current matrix" currentMatrix *= aMatrix! ! !B3DVertexTransformer methodsFor: 'modifying'! rotateBy: aRotation self transformBy: aRotation asMatrix4x4.! ! !B3DVertexTransformer methodsFor: 'modifying'! scaleBy: aVector self transformBy: (B3DMatrix4x4 identity setScale: aVector)! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/2/1999 19:01'! scaleByX: x y: y z: z currentMatrix scaleByX: x y: y z: z. needsUpdate := true.! ! !B3DVertexTransformer methodsFor: 'modifying'! transformBy: aTransformation self privateTransformMatrix: currentMatrix with: aTransformation asMatrix4x4 into: currentMatrix. needsUpdate := true.! ! !B3DVertexTransformer methodsFor: 'modifying'! translateBy: aVector "Add the translation defined by aVector to the current matrix" self transformBy: (B3DMatrix4x4 identity setTranslation: aVector).! ! !B3DVertexTransformer methodsFor: 'modifying' stamp: 'ar 2/2/1999 19:01'! translateByX: x y: y z: z "Add the translation defined by aVector to the current matrix" currentMatrix translateByX: x y: y z: z. needsUpdate := true.! ! !B3DVertexTransformer methodsFor: 'processing' stamp: 'ar 4/18/1999 02:23'! processVertexBuffer: vb ^self processVertexBuffer: vb modelView: self modelViewMatrix projection: self projectionMatrix! ! !B3DVertexTransformer methodsFor: 'processing' stamp: 'ar 4/18/1999 02:22'! processVertexBuffer: vb modelView: modelViewMatrix projection: projectionMatrix ^self privateTransformVB: vb vertexArray count: vb vertexCount modelViewMatrix: modelViewMatrix projectionMatrix: projectionMatrix flags: vb flags! ! !B3DVertexTransformer methodsFor: 'view transformation'! ortho: aFrustum viewMatrix _ aFrustum asFrustum asOrthoMatrix. needsUpdate _ true.! ! !B3DVertexTransformer methodsFor: 'view transformation'! perspective: aPerspectiveOrFrustum viewMatrix _ aPerspectiveOrFrustum asFrustum asPerspectiveMatrix. needsUpdate _ true.! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:19'! privateTransformMatrix: m1 with: m2 into: m3 "Perform a 4x4 matrix multiplication m2 * m1 = m3 being equal to first transforming points by m2 and then by m1. Note that m1 may be identical to m3." | c1 c2 c3 c4 | m2 == m3 ifTrue:[^self error:'Argument and result matrix identical']. c1 _ ((m1 a11 * m2 a11) + (m1 a12 * m2 a21) + (m1 a13 * m2 a31) + (m1 a14 * m2 a41)). c2 _ ((m1 a11 * m2 a12) + (m1 a12 * m2 a22) + (m1 a13 * m2 a32) + (m1 a14 * m2 a42)). c3 _ ((m1 a11 * m2 a13) + (m1 a12 * m2 a23) + (m1 a13 * m2 a33) + (m1 a14 * m2 a43)). c4 _ ((m1 a11 * m2 a14) + (m1 a12 * m2 a24) + (m1 a13 * m2 a34) + (m1 a14 * m2 a44)). m3 a11: c1; a12: c2; a13: c3; a14: c4. c1 _ ((m1 a21 * m2 a11) + (m1 a22 * m2 a21) + (m1 a23 * m2 a31) + (m1 a24 * m2 a41)). c2 _ ((m1 a21 * m2 a12) + (m1 a22 * m2 a22) + (m1 a23 * m2 a32) + (m1 a24 * m2 a42)). c3 _ ((m1 a21 * m2 a13) + (m1 a22 * m2 a23) + (m1 a23 * m2 a33) + (m1 a24 * m2 a43)). c4 _ ((m1 a21 * m2 a14) + (m1 a22 * m2 a24) + (m1 a23 * m2 a34) + (m1 a24 * m2 a44)). m3 a21: c1; a22: c2; a23: c3; a24: c4. c1 _ ((m1 a31 * m2 a11) + (m1 a32 * m2 a21) + (m1 a33 * m2 a31) + (m1 a34 * m2 a41)). c2 _ ((m1 a31 * m2 a12) + (m1 a32 * m2 a22) + (m1 a33 * m2 a32) + (m1 a34 * m2 a42)). c3 _ ((m1 a31 * m2 a13) + (m1 a32 * m2 a23) + (m1 a33 * m2 a33) + (m1 a34 * m2 a43)). c4 _ ((m1 a31 * m2 a14) + (m1 a32 * m2 a24) + (m1 a33 * m2 a34) + (m1 a34 * m2 a44)). m3 a31: c1; a32: c2; a33: c3; a34: c4. c1 _ ((m1 a41 * m2 a11) + (m1 a42 * m2 a21) + (m1 a43 * m2 a31) + (m1 a44 * m2 a41)). c2 _ ((m1 a41 * m2 a12) + (m1 a42 * m2 a22) + (m1 a43 * m2 a32) + (m1 a44 * m2 a42)). c3 _ ((m1 a41 * m2 a13) + (m1 a42 * m2 a23) + (m1 a43 * m2 a33) + (m1 a44 * m2 a43)). c4 _ ((m1 a41 * m2 a14) + (m1 a42 * m2 a24) + (m1 a43 * m2 a34) + (m1 a44 * m2 a44)). m3 a41: c1; a42: c2; a43: c3; a44: c4.! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:20'! privateTransformPrimitiveNormal: primitiveVertex byMatrix: aMatrix rescale: scaleNeeded | x y z rx ry rz dot | x _ primitiveVertex normalX. y _ primitiveVertex normalY. z _ primitiveVertex normalZ. rx := (x * aMatrix a11) + (y * aMatrix a12) + (z * aMatrix a13). ry := (x * aMatrix a21) + (y * aMatrix a22) + (z * aMatrix a23). rz := (x * aMatrix a31) + (y * aMatrix a32) + (z * aMatrix a33). scaleNeeded ifTrue:[ dot _ (rx * rx) + (ry * ry) + (rz * rz). dot < 1.0e-20 ifTrue:[ rx _ ry _ rz _ 0.0. ] ifFalse:[ dot = 1.0 ifFalse:[ dot _ 1.0 / dot sqrt. rx _ rx * dot. ry _ ry * dot. rz _ rz * dot. ]. ]. ]. primitiveVertex normalX: rx. primitiveVertex normalY: ry. primitiveVertex normalZ: rz. ! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:20'! privateTransformPrimitiveVertex: primitiveVertex byModelView: aMatrix | x y z rx ry rz rw oneOverW | x _ primitiveVertex positionX. y _ primitiveVertex positionY. z _ primitiveVertex positionZ. rx := (x * aMatrix a11) + (y * aMatrix a12) + (z * aMatrix a13) + aMatrix a14. ry := (x * aMatrix a21) + (y * aMatrix a22) + (z * aMatrix a23) + aMatrix a24. rz := (x * aMatrix a31) + (y * aMatrix a32) + (z * aMatrix a33) + aMatrix a34. rw := (x * aMatrix a41) + (y * aMatrix a42) + (z * aMatrix a43) + aMatrix a44. rw = 1.0 ifTrue:[ primitiveVertex positionX: rx. primitiveVertex positionY: ry. primitiveVertex positionZ: rz. ] ifFalse:[ rw = 0.0 ifTrue:[oneOverW _ 0.0] ifFalse:[oneOverW _ 1.0 / rw]. primitiveVertex positionX: rx * oneOverW. primitiveVertex positionY: ry * oneOverW. primitiveVertex positionZ: rz * oneOverW. ]. ! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/8/1999 18:19'! privateTransformPrimitiveVertex: primitiveVertex byModelViewWithoutW: aMatrix "Special case of aMatrix a41 = a42 = a43 = 0.0 and a44 = 1.0" | x y z rx ry rz | "Note: This is not supported by primitive level operations." self flag: #b3dPrimitive. x _ primitiveVertex positionX. y _ primitiveVertex positionY. z _ primitiveVertex positionZ. rx := (x * aMatrix a11) + (y * aMatrix a12) + (z * aMatrix a13) + aMatrix a14. ry := (x * aMatrix a21) + (y * aMatrix a22) + (z * aMatrix a23) + aMatrix a24. rz := (x * aMatrix a31) + (y * aMatrix a32) + (z * aMatrix a33) + aMatrix a34. primitiveVertex positionX: rx. primitiveVertex positionY: ry. primitiveVertex positionZ: rz.! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:21'! privateTransformPrimitiveVertex: primitiveVertex byProjection: aMatrix | x y z rx ry rz rw | x _ primitiveVertex positionX. y _ primitiveVertex positionY. z _ primitiveVertex positionZ. rx := (x * aMatrix a11) + (y * aMatrix a12) + (z * aMatrix a13) + aMatrix a14. ry := (x * aMatrix a21) + (y * aMatrix a22) + (z * aMatrix a23) + aMatrix a24. rz := (x * aMatrix a31) + (y * aMatrix a32) + (z * aMatrix a33) + aMatrix a34. rw := (x * aMatrix a41) + (y * aMatrix a42) + (z * aMatrix a43) + aMatrix a44. primitiveVertex rasterPosX: rx. primitiveVertex rasterPosY: ry. primitiveVertex rasterPosZ: rz. primitiveVertex rasterPosW: rw.! ! !B3DVertexTransformer methodsFor: 'private-transforming' stamp: 'ar 2/17/1999 04:22'! privateTransformVB: vertexArray count: vertexCount modelViewMatrix: modelViewMatrix projectionMatrix: projectionMatrix flags: flags | noW | (modelViewMatrix a41 = 0.0 and:[ modelViewMatrix a42 = 0.0 and:[ modelViewMatrix a43 = 0.0 and:[ modelViewMatrix a44 = 1.0]]]) ifTrue:[noW _ true]. noW ifTrue:[ vertexArray upTo: vertexCount do:[:primitiveVertex| self privateTransformPrimitiveVertex: primitiveVertex byModelViewWithoutW: modelViewMatrix. self privateTransformPrimitiveVertex: primitiveVertex byProjection: projectionMatrix. (flags anyMask: VBVtxHasNormals) ifTrue:[self privateTransformPrimitiveNormal: primitiveVertex byMatrix: modelViewMatrix rescale: true]. ]. ] ifFalse:[ vertexArray upTo: vertexCount do:[:primitiveVertex| self privateTransformPrimitiveVertex: primitiveVertex byModelView: modelViewMatrix. self privateTransformPrimitiveVertex: primitiveVertex byProjection: projectionMatrix. (flags anyMask: VBVtxHasNormals) ifTrue:[self privateTransformPrimitiveNormal: primitiveVertex byMatrix: modelViewMatrix rescale: true]. ]. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DVertexTransformer class instanceVariableNames: ''! !B3DVertexTransformer class methodsFor: 'testing' stamp: 'ar 2/14/1999 01:41'! isAvailable "Return true if this part of the engine is available" ^true! ! B3DFloatArray variableWordSubclass: #B3DViewingFrustum instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Viewing'! !B3DViewingFrustum commentStamp: '' prior: 0! I represent a viewing frustum, defined by the following values: typedef struct B3DViewingFrustum { float left; float right; float top; float bottom; float near; float far; } B3DViewingFrustum; The frustum can be converted into either a ortho matrix (having no perspective distortion) or a perspective matrix for use in the Balloon 3D render engine.! !B3DViewingFrustum methodsFor: 'private'! asOrthoMatrixInto: aB3DMatrix4x4 | x y z tx ty tz dx dy dz | (self near <= 0.0 or:[self far <= 0.0]) ifTrue: [^self error:'Clipping plane error']. dx := self right - self left. dy := self top - self bottom. dz := self far - self near. x := dx * 0.5. y := dy * 0.5. z := dz * -0.5. tx := (self left + self right) / dx. ty := (self top + self bottom) / dy. tz := (self near + self far) / dz. aB3DMatrix4x4 a11: x; a12: 0.0; a13: 0.0; a14: tx; a21: 0.0; a22: y; a23: 0.0; a24: ty; a31: 0.0; a32: 0.0; a33: z; a34: tz; a41: 0.0; a42: 0.0; a43: 0.0; a44: 1.0. ^aB3DMatrix4x4! ! !B3DViewingFrustum methodsFor: 'private' stamp: 'ar 2/7/1999 01:30'! asPerspectiveMatrixInto: aB3DMatrix4x4 | x y a b c d dx dy dz z2 | (self near <= 0.0 or:[self far <= 0.0 or:[self near >= self far]]) ifTrue: [^self error:'Clipping plane error']. dx := self right - self left. dy := self top - self bottom. dz := self far - self near. z2 := 2.0 * self near. x := z2 / dx. y := z2 / dy. a := (self left + self right) / dx. b := (self top + self bottom) / dy. c := (self near + self far) "*negated*" / dz. d := (-2.0 * self near * self far) / dz. aB3DMatrix4x4 a11: x; a12: 0.0; a13: a; a14: 0.0; a21: 0.0; a22: y; a23: b; a24: 0.0; a31: 0.0; a32: 0.0; a33: c; a34: d; a41: 0.0; a42: 0.0; a43: "*-1*"1; a44: 0.0. ^aB3DMatrix4x4! ! !B3DViewingFrustum methodsFor: 'private'! computeFromNear: nearDistance far: farDistance fov: fieldOfView aspect: aspectRatio "Compute the viewing frustum from the given values" | top bottom | top := nearDistance * fieldOfView degreesToRadians tan. bottom := top negated. self left: bottom * aspectRatio. self right: top * aspectRatio. self top: top. self bottom: bottom. self near: nearDistance. self far: farDistance.! ! !B3DViewingFrustum methodsFor: 'accessing'! bottom ^self floatAt: 4.! ! !B3DViewingFrustum methodsFor: 'accessing'! bottom: aFloat self floatAt: 4 put: aFloat! ! !B3DViewingFrustum methodsFor: 'accessing'! far ^self floatAt: 6! ! !B3DViewingFrustum methodsFor: 'accessing'! far: aFloat self floatAt: 6 put: aFloat! ! !B3DViewingFrustum methodsFor: 'accessing'! left ^self floatAt: 1! ! !B3DViewingFrustum methodsFor: 'accessing'! left: aFloat self floatAt: 1 put: aFloat! ! !B3DViewingFrustum methodsFor: 'accessing'! near ^self floatAt: 5! ! !B3DViewingFrustum methodsFor: 'accessing'! near: aFloat self floatAt: 5 put: aFloat! ! !B3DViewingFrustum methodsFor: 'accessing'! right ^self floatAt: 2! ! !B3DViewingFrustum methodsFor: 'accessing'! right: aFloat self floatAt: 2 put: aFloat! ! !B3DViewingFrustum methodsFor: 'accessing'! top ^self floatAt: 3! ! !B3DViewingFrustum methodsFor: 'accessing'! top: aFloat self floatAt: 3 put: aFloat! ! !B3DViewingFrustum methodsFor: 'converting'! asFrustum ^self! ! !B3DViewingFrustum methodsFor: 'converting'! asOrthoMatrix ^self asOrthoMatrixInto: B3DMatrix4x4 new! ! !B3DViewingFrustum methodsFor: 'converting'! asPerspectiveMatrix ^self asPerspectiveMatrixInto: B3DMatrix4x4 new! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! B3DViewingFrustum class instanceVariableNames: ''! !B3DViewingFrustum class methodsFor: 'instance creation'! near: nearDistance far: farDistance fov: fieldOfView aspect: aspectRatio ^self new computeFromNear: nearDistance far: farDistance fov: fieldOfView aspect: aspectRatio! ! !B3DViewingFrustum class methodsFor: 'instance creation'! numElements ^6! ! Rectangle subclass: #B3DViewport instanceVariableNames: 'center scale ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Viewing'! !B3DViewport commentStamp: '' prior: 0! I represent a viewport for the Ballon 3D graphics engine. Since all positions are computed in the unit-coordinate system (-1,-1,-1) (1,1,1) after the render pipeline has completed, I am used to map these positions into the physical (pixel) coordinates of the output device before rasterization takes place. Instance variables: center The center of the viewport scale The scale for points! !B3DViewport methodsFor: 'mapping'! asMatrixTransform2x3 ^(MatrixTransform2x3 withScale: scale) offset: center! ! !B3DViewport methodsFor: 'mapping'! mapVertex4: aVector | w x y oneOverW | w _ aVector w. w = 1.0 ifTrue:[ x _ aVector x. y _ aVector y. ] ifFalse:[ w = 0.0 ifTrue:[oneOverW _ 0.0] ifFalse:[oneOverW _ 1.0 / w]. x _ aVector x * oneOverW. y _ aVector y * oneOverW. ]. ^((x@y) * scale + center) truncated! ! !B3DViewport methodsFor: 'private' stamp: 'ar 2/8/1999 21:45'! setOrigin: topLeft corner: bottomRight super setOrigin: topLeft corner: bottomRight. center _ (self origin + self corner) / 2.0. scale _ corner - center + (0.5@-0.5). "Rasterizer offset"! ! !B3DViewport methodsFor: 'private' stamp: 'ar 2/7/1999 01:42'! toggleYScale scale _ scale x @ scale y negated.! ! !B3DViewport methodsFor: 'accessing' stamp: 'ar 2/15/1999 02:53'! aspectRatio ^self width asFloat / self height asFloat! ! !B3DViewport methodsFor: 'accessing' stamp: 'ar 4/3/1999 20:29'! center ^center! ! !B3DViewport methodsFor: 'accessing' stamp: 'ar 4/3/1999 20:29'! scale ^scale! ! TransformationMorph subclass: #BOBTransformationMorph instanceVariableNames: 'worldBoundsToShow useRegularWarpBlt ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/10/2000 14:22'! adjustAfter: changeBlock "Cause this morph to remain cetered where it was before, and choose appropriate smoothing, after a change of scale or rotation." | | "oldRefPos _ self referencePosition." changeBlock value. self chooseSmoothing. "self penUpWhile: [self position: self position + (oldRefPos - self referencePosition)]." self layoutChanged. owner ifNotNil: [owner invalidRect: bounds] ! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/26/2000 19:24'! changeWorldBoundsToShow: aRectangle aRectangle area = 0 ifTrue: [^self]. worldBoundsToShow _ aRectangle. owner myWorldChanged.! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/28/2000 11:45'! drawSubmorphsOn: aCanvas (self innerBounds intersects: aCanvas clipRect) ifFalse: [^self]. useRegularWarpBlt == true ifTrue: [ ^aCanvas transformBy: transform clippingTo: ((self innerBounds intersect: aCanvas clipRect) expandBy: 1) rounded during: [:myCanvas | submorphs reverseDo:[:m | myCanvas fullDrawMorph: m] ] smoothing: smoothing ]. aCanvas transform2By: transform "#transformBy: for pure WarpBlt" clippingTo: ((self innerBounds intersect: aCanvas clipRect) expandBy: 1) truncated during: [:myCanvas | submorphs reverseDo:[:m | myCanvas fullDrawMorph: m] ] smoothing: smoothing ! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/27/2000 12:39'! extent: aPoint | newExtent | newExtent _ aPoint truncated. bounds extent = newExtent ifTrue: [^self]. bounds _ bounds topLeft extent: newExtent. self recomputeExtent. ! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/26/2000 19:23'! extentFromParent: aPoint | newExtent | submorphs isEmpty ifTrue: [^self extent: aPoint]. newExtent _ aPoint truncated. bounds _ bounds topLeft extent: newExtent. newExtent _ self recomputeExtent. newExtent ifNil: [^self]. bounds _ bounds topLeft extent: newExtent. ! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/15/2000 11:57'! layoutChanged | myGuy | "use the version from Morph" fullBounds _ nil. owner ifNotNil: [owner layoutChanged]. submorphs size > 0 ifTrue: [ (myGuy _ self firstSubmorph) isWorldMorph ifFalse: [ worldBoundsToShow = myGuy bounds ifFalse: [ self changeWorldBoundsToShow: (worldBoundsToShow _ myGuy bounds). ]. ]. "submorphs do: [:m | m ownerChanged]" "<< I don't see any reason for this" ]. ! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/27/2000 12:39'! recomputeExtent | scalePt newScale theGreenThingie greenIBE myNewExtent | submorphs isEmpty ifTrue: [^self extent]. worldBoundsToShow ifNil: [worldBoundsToShow _ self firstSubmorph bounds]. worldBoundsToShow area = 0 ifTrue: [^self extent]. scalePt _ owner innerBounds extent / worldBoundsToShow extent. newScale _ scalePt x min: scalePt y. theGreenThingie _ owner. greenIBE _ theGreenThingie innerBounds extent. myNewExtent _ (greenIBE min: worldBoundsToShow extent * newScale) truncated. self scale: newScale; offset: worldBoundsToShow origin * newScale. smoothing _ (newScale < 1.0) ifTrue: [2] ifFalse: [1]. ^myNewExtent! ! !BOBTransformationMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/28/2000 11:26'! useRegularWarpBlt: aBoolean useRegularWarpBlt _ aBoolean! ! Morph subclass: #BackgroundMorph instanceVariableNames: 'image offset delta running ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Widgets'! !BackgroundMorph commentStamp: '' prior: 0! This morph incorporates tiling and regular motion with the intent of supporting, eg, panning of endless (toroidal) backgrounds. The idea is that embedded morphs get displayed at a moving offset relative to my position. Moreover this display is tiled according to the bounding box of the submorphs (subBounds), as much as necesary to fill the rest of my bounds.! !BackgroundMorph methodsFor: 'as yet unclassified' stamp: 'di 11/4/97 09:01'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. running ifTrue: [aCustomMenu add: 'stop' action: #stopRunning] ifFalse: [aCustomMenu add: 'start' action: #startRunning]. ! ! !BackgroundMorph methodsFor: 'as yet unclassified' stamp: 'ar 6/17/1999 01:06'! drawOn: aCanvas "The tiling is solely determined by bounds, subBounds and offset. The extent of display is determined by bounds and the clipRect of the canvas." | start d subBnds | submorphs isEmpty ifTrue: [^ super drawOn: aCanvas]. subBnds _ self subBounds. running ifFalse: [super drawOn: aCanvas. ^ aCanvas fillRectangle: subBnds color: Color lightBlue]. start _ subBnds topLeft + offset - bounds topLeft - (1@1) \\ subBnds extent - subBnds extent + (1@1). d _ subBnds topLeft - bounds topLeft. "Sensor redButtonPressed ifTrue: [self halt]." start x to: bounds width - 1 by: subBnds width do: [:x | start y to: bounds height - 1 by: subBnds height do: [:y | aCanvas translateBy: (x@y) - d clippingTo: bounds during:[:tileCanvas| self drawSubmorphsOn: tileCanvas]]].! ! !BackgroundMorph methodsFor: 'as yet unclassified'! fullBounds ^ self bounds! ! !BackgroundMorph methodsFor: 'as yet unclassified' stamp: 'ar 5/29/1999 08:32'! fullDrawOn: aCanvas running ifFalse: [ ^aCanvas clipBy: (bounds translateBy: aCanvas origin) during:[:clippedCanvas| super fullDrawOn: clippedCanvas]]. aCanvas drawMorph: self. ! ! !BackgroundMorph methodsFor: 'as yet unclassified'! initialize super initialize. offset _ 0@0. delta _ 1@0. running _ true! ! !BackgroundMorph methodsFor: 'as yet unclassified'! layoutChanged "Do nothing, since I clip my submorphs"! ! !BackgroundMorph methodsFor: 'as yet unclassified'! slideBy: inc submorphs isEmpty ifTrue: [^ self]. offset _ offset + inc \\ self subBounds extent. self changed! ! !BackgroundMorph methodsFor: 'as yet unclassified'! startRunning running _ true. self changed! ! !BackgroundMorph methodsFor: 'as yet unclassified'! step "Answer the desired time between steps in milliseconds." running ifTrue: [self slideBy: delta]! ! !BackgroundMorph methodsFor: 'as yet unclassified'! stepTime "Answer the desired time between steps in milliseconds." ^ 20! ! !BackgroundMorph methodsFor: 'as yet unclassified'! stopRunning running _ false. self changed! ! !BackgroundMorph methodsFor: 'as yet unclassified'! subBounds "calculate the submorph bounds" | subBounds | subBounds _ nil. self submorphsDo: [:m | subBounds == nil ifTrue: [subBounds _ m fullBounds] ifFalse: [subBounds _ subBounds merge: m fullBounds]]. ^ subBounds! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BackgroundMorph class instanceVariableNames: ''! !BackgroundMorph class methodsFor: 'as yet unclassified'! test ^ self new image: Form fromUser! ! Collection subclass: #Bag instanceVariableNames: 'contents ' classVariableNames: '' poolDictionaries: '' category: 'Collections-Unordered'! !Bag commentStamp: '' prior: 0! I represent an unordered collection of possibly duplicate elements. I store these elements in a dictionary, tallying up occurrences of equal objects. Because I store an occurrence only once, my clients should beware that objects they store will not necessarily be retrieved such that == is true. If the client cares, a subclass of me should be created.! !Bag methodsFor: 'accessing' stamp: 'sma 5/12/2000 17:23'! at: index self errorNotKeyed! ! !Bag methodsFor: 'accessing' stamp: 'sma 5/12/2000 17:23'! at: index put: anObject self errorNotKeyed! ! !Bag methodsFor: 'accessing' stamp: 'tao 1/5/2000 18:25'! cumulativeCounts "Answer with a collection of cumulative percents covered by elements so far." | s n | s _ self size / 100.0. n _ 0. ^ self sortedCounts asArray collect: [:a | n _ n + a key. (n / s roundTo: 0.1) -> a value]! ! !Bag methodsFor: 'accessing' stamp: 'sma 5/12/2000 11:35'! size "Answer how many elements the receiver contains." | tally | tally _ 0. contents do: [:each | tally _ tally + each]. ^ tally! ! !Bag methodsFor: 'accessing' stamp: 'sma 6/15/2000 17:00'! sortedCounts "Answer with a collection of counts with elements, sorted by decreasing count." | counts | counts _ SortedCollection sortBlock: [:x :y | x >= y]. contents associationsDo: [:assn | counts add: (Association key: assn value value: assn key)]. ^ counts! ! !Bag methodsFor: 'accessing'! sortedElements "Answer with a collection of elements with counts, sorted by element." | elements | elements _ SortedCollection new. contents associationsDo: [:assn | elements add: assn]. ^elements! ! !Bag methodsFor: 'adding' stamp: 'sma 5/12/2000 17:18'! add: newObject "Include newObject as one of the receiver's elements. Answer newObject." ^ self add: newObject withOccurrences: 1! ! !Bag methodsFor: 'adding' stamp: 'sma 5/12/2000 17:20'! add: newObject withOccurrences: anInteger "Add newObject anInteger times to the receiver. Answer newObject." contents at: newObject put: (contents at: newObject ifAbsent: [0]) + anInteger. ^ newObject! ! !Bag methodsFor: 'converting' stamp: 'sma 5/12/2000 14:34'! asBag ^ self! ! !Bag methodsFor: 'converting' stamp: 'sma 5/12/2000 14:30'! asSet "Answer a set with the elements of the receiver." ^ contents keys! ! !Bag methodsFor: 'copying' stamp: 'sma 5/12/2000 14:53'! copy ^ self shallowCopy setContents: contents copy! ! !Bag methodsFor: 'enumerating'! do: aBlock "Refer to the comment in Collection|do:." contents associationsDo: [:assoc | assoc value timesRepeat: [aBlock value: assoc key]]! ! !Bag methodsFor: 'private' stamp: 'sma 5/12/2000 14:49'! setContents: aDictionary contents _ aDictionary! ! !Bag methodsFor: 'removing' stamp: 'sma 5/12/2000 14:32'! remove: oldObject ifAbsent: exceptionBlock "Refer to the comment in Collection|remove:ifAbsent:." | count | count _ contents at: oldObject ifAbsent: [^ exceptionBlock value]. count = 1 ifTrue: [contents removeKey: oldObject] ifFalse: [contents at: oldObject put: count - 1]. ^ oldObject! ! !Bag methodsFor: 'testing'! includes: anObject "Refer to the comment in Collection|includes:." ^contents includesKey: anObject! ! !Bag methodsFor: 'testing'! occurrencesOf: anObject "Refer to the comment in Collection|occurrencesOf:." (self includes: anObject) ifTrue: [^contents at: anObject] ifFalse: [^0]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Bag class instanceVariableNames: ''! !Bag class methodsFor: 'instance creation' stamp: 'sma 5/12/2000 13:31'! new ^ self new: 4! ! !Bag class methodsFor: 'instance creation' stamp: 'sma 5/12/2000 14:49'! new: nElements ^ super new setContents: (Dictionary new: nElements)! ! !Bag class methodsFor: 'instance creation' stamp: 'sma 5/12/2000 17:17'! newFrom: aCollection "Answer an instance of me containing the same elements as aCollection." ^ self withAll: aCollection "Examples: Bag newFrom: {1. 2. 3. 3} {1. 2. 3. 3} as: Bag "! ! Object subclass: #BalloonBezierSimulation instanceVariableNames: 'start end via lastX lastY fwDx fwDy fwDDx fwDDy maxSteps ' classVariableNames: 'HeightSubdivisions LineConversions MonotonSubdivisions OverflowSubdivisions ' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! end ^end! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! end: aPoint end _ aPoint! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 01:57'! inTangent "Return the tangent at the start point" ^via - start! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! initialX ^start y <= end y ifTrue:[start x] ifFalse:[end x]! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! initialY ^start y <= end y ifTrue:[start y] ifFalse:[end y]! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! initialZ ^0 "Assume no depth given"! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 01:57'! outTangent "Return the tangent at the end point" ^end - via! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! start ^start! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! start: aPoint start _ aPoint! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! via ^via! ! !BalloonBezierSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:45'! via: aPoint via _ aPoint! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 20:46'! computeInitialStateFrom: source with: transformation "Compute the initial state in the receiver." start _ (transformation localPointToGlobal: source start) asIntegerPoint. end _ (transformation localPointToGlobal: source end) asIntegerPoint. via _ (transformation localPointToGlobal: source via) asIntegerPoint.! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 02:39'! computeSplitAt: t "Split the receiver at the parametric value t" | left right newVia1 newVia2 newPoint | left _ self clone. right _ self clone. "Compute new intermediate points" newVia1 _ (via - start) * t + start. newVia2 _ (end - via) * t + via. "Compute new point on curve" newPoint _ ((newVia1 - newVia2) * t + newVia2) asIntegerPoint. left via: newVia1 asIntegerPoint. left end: newPoint. right start: newPoint. right via: newVia2 asIntegerPoint. ^Array with: left with: right! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 01:34'! floatStepToFirstScanLineAt: yValue in: edgeTableEntry "Float version of forward differencing" | startX endX startY endY deltaY fwX1 fwX2 fwY1 fwY2 steps scaledStepSize squaredStepSize | (end y) >= (start y) ifTrue:[ startX _ start x. endX _ end x. startY _ start y. endY _ end y. ] ifFalse:[ startX _ end x. endX _ start x. startY _ end y. endY _ start y. ]. deltaY _ endY - startY. "Quickly check if the line is visible at all" (yValue >= endY or:[deltaY = 0]) ifTrue:[ ^edgeTableEntry lines: 0]. fwX1 _ (startX + endX - (2 * via x)) asFloat. fwX2 _ (via x - startX * 2) asFloat. fwY1 _ (startY + endY - (2 * via y)) asFloat. fwY2 _ ((via y - startY) * 2) asFloat. steps _ deltaY asInteger * 2. scaledStepSize _ 1.0 / steps asFloat. squaredStepSize _ scaledStepSize * scaledStepSize. fwDx _ fwX2 * scaledStepSize. fwDDx _ 2.0 * fwX1 * squaredStepSize. fwDy _ fwY2 * scaledStepSize. fwDDy _ 2.0 * fwY1 * squaredStepSize. fwDx _ fwDx + (fwDDx * 0.5). fwDy _ fwDy + (fwDDy * 0.5). lastX _ startX asFloat. lastY _ startY asFloat. "self xDirection: xDir. self yDirection: yDir." edgeTableEntry xValue: startX. edgeTableEntry yValue: startY. edgeTableEntry zValue: 0. edgeTableEntry lines: deltaY. "If not at first scan line then step down to yValue" yValue = startY ifFalse:[ self stepToNextScanLineAt: yValue in: edgeTableEntry. "And adjust remainingLines" edgeTableEntry lines: deltaY - (yValue - startY). ].! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 02:45'! floatStepToNextScanLineAt: yValue in: edgeTableEntry "Float version of forward differencing" [yValue asFloat > lastY] whileTrue:[ (fwDx < -50.0 or:[fwDx > 50.0]) ifTrue:[self halt]. (fwDy < -50.0 or:[fwDy > 50.0]) ifTrue:[self halt]. (fwDDx < -50.0 or:[fwDDx > 50.0]) ifTrue:[self halt]. (fwDDy < -50.0 or:[fwDDy > 50.0]) ifTrue:[self halt]. lastX _ lastX + fwDx. lastY _ lastY + fwDy. fwDx _ fwDx + fwDDx. fwDy _ fwDy + fwDDy. ]. edgeTableEntry xValue: lastX asInteger. edgeTableEntry zValue: 0.! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 16:23'! intStepToFirstScanLineAt: yValue in: edgeTableEntry "Scaled integer version of forward differencing" | startX endX startY endY deltaY fwX1 fwX2 fwY1 fwY2 scaledStepSize squaredStepSize | (end y) >= (start y) ifTrue:[ startX _ start x. endX _ end x. startY _ start y. endY _ end y. ] ifFalse:[ startX _ end x. endX _ start x. startY _ end y. endY _ start y. ]. deltaY _ endY - startY. "Quickly check if the line is visible at all" (yValue >= endY or:[deltaY = 0]) ifTrue:[ ^edgeTableEntry lines: 0]. fwX1 _ (startX + endX - (2 * via x)). fwX2 _ (via x - startX * 2). fwY1 _ (startY + endY - (2 * via y)). fwY2 _ ((via y - startY) * 2). maxSteps _ deltaY asInteger * 2. scaledStepSize _ 16r1000000 // maxSteps. "@@: Okay, we need some fancy 64bit multiplication here" squaredStepSize _ self absoluteSquared8Dot24: scaledStepSize. squaredStepSize = ((scaledStepSize * scaledStepSize) bitShift: -24) ifFalse:[self error:'Bad computation']. fwDx _ fwX2 * scaledStepSize. fwDDx _ 2 * fwX1 * squaredStepSize. fwDy _ fwY2 * scaledStepSize. fwDDy _ 2 * fwY1 * squaredStepSize. fwDx _ fwDx + (fwDDx // 2). fwDy _ fwDy + (fwDDy // 2). self validateIntegerRange. lastX _ startX * 256. lastY _ startY * 256. edgeTableEntry xValue: startX. edgeTableEntry yValue: startY. edgeTableEntry zValue: 0. edgeTableEntry lines: deltaY. "If not at first scan line then step down to yValue" yValue = startY ifFalse:[ self stepToNextScanLineAt: yValue in: edgeTableEntry. "And adjust remainingLines" edgeTableEntry lines: deltaY - (yValue - startY). ].! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 04:02'! intStepToNextScanLineAt: yValue in: edgeTableEntry "Scaled integer version of forward differencing" [maxSteps >= 0 and:[yValue * 256 > lastY]] whileTrue:[ self validateIntegerRange. lastX _ lastX + ((fwDx + 16r8000) // 16r10000). lastY _ lastY + ((fwDy + 16r8000) // 16r10000). fwDx _ fwDx + fwDDx. fwDy _ fwDy + fwDDy. maxSteps _ maxSteps - 1. ]. edgeTableEntry xValue: lastX // 256. edgeTableEntry zValue: 0.! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/29/1998 22:14'! isMonoton "Return true if the receiver is monoton along the y-axis, e.g., check if the tangents have the same sign" ^(via y - start y) * (end y - via y) >= 0! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/31/1998 16:36'! stepToFirstScanLineAt: yValue in: edgeTableEntry "Compute the initial x value for the scan line at yValue" ^self intStepToFirstScanLineAt: yValue in: edgeTableEntry! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 03:40'! stepToNextScanLineAt: yValue in: edgeTableEntry "Compute the next x value for the scan line at yValue. This message is sent during incremental updates. The yValue parameter is passed in here for edges that have more complicated computations," ^self intStepToNextScanLineAt: yValue in: edgeTableEntry! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 11/1/1998 00:31'! subdivide "Subdivide the receiver" | dy dx | "Test 1: If the bezier curve is not monoton in Y, we need a subdivision" self isMonoton ifFalse:[ MonotonSubdivisions _ MonotonSubdivisions + 1. ^self subdivideToBeMonoton]. "Test 2: If the receiver is horizontal, don't do anything" (end y = start y) ifTrue:[^nil]. "Test 3: If the receiver can be represented as a straight line, make a line from the receiver and declare it invalid" ((end - start) crossProduct: (via - start)) = 0 ifTrue:[ LineConversions _ LineConversions + 1. ^self subdivideToBeLine]. "Test 4: If the height of the curve exceeds 256 pixels, subdivide (forward differencing is numerically not very stable)" dy _ end y - start y. dy < 0 ifTrue:[dy _ dy negated]. (dy > 255) ifTrue:[ HeightSubdivisions _ HeightSubdivisions + 1. ^self subdivideAt: 0.5]. "Test 5: Check if the incremental values could possibly overflow the scaled integer range" dx _ end x - start x. dx < 0 ifTrue:[dx _ dx negated]. dy * 32 < dx ifTrue:[ OverflowSubdivisions _ OverflowSubdivisions + 1. ^self subdivideAt: 0.5]. ^nil! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 22:13'! subdivideAt: parameter "Subdivide the receiver at the given parameter" | both | (parameter <= 0.0 or:[parameter >= 1.0]) ifTrue:[self halt]. both _ self computeSplitAt: parameter. "Transcript cr. self quickPrint: self. Transcript space. self quickPrint: both first. Transcript space. self quickPrint: both last. Transcript endEntry." self via: both first via. self end: both first end. ^both last! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 11/11/1998 22:15'! subdivideToBeLine "Not a true subdivision. Just return a line representing the receiver and fake me to be of zero height" | line | line _ BalloonLineSimulation new. line start: start. line end: end. "Make me invalid" end _ start. via _ start. ^line! ! !BalloonBezierSimulation methodsFor: 'computing' stamp: 'ar 10/30/1998 02:24'! subdivideToBeMonoton "Subdivide the receiver at it's extreme point" | v1 v2 t other | v1 _ (via - start). v2 _ (end - via). t _ (v1 y / (v2 y - v1 y)) negated asFloat. other _ self subdivideAt: t. self isMonoton ifFalse:[self halt]. other isMonoton ifFalse:[self halt]. ^other! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 16:37'! absoluteSquared8Dot24: value "Compute the squared value of a 8.24 number with 0.0 <= value < 1.0, e.g., compute (value * value) bitShift: -24" | halfWord1 halfWord2 result | (value >= 0 and:[value < 16r1000000]) ifFalse:[^self error:'Value out of range']. halfWord1 _ value bitAnd: 16rFFFF. halfWord2 _ (value bitShift: -16) bitAnd: 255. result _ (halfWord1 * halfWord1) bitShift: -16. "We don't need the lower 16bits at all" result _ result + ((halfWord1 * halfWord2) * 2). result _ result + ((halfWord2 * halfWord2) bitShift: 16). "word1 _ halfWord1 * halfWord1. word2 _ (halfWord2 * halfWord1) + (word1 bitShift: -16). word1 _ word1 bitAnd: 16rFFFF. word2 _ word2 + (halfWord1 * halfWord2). word2 _ word2 + ((halfWord2 * halfWord2) bitShift: 16)." ^result bitShift: -8! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 5/25/2000 17:57'! debugDraw | entry minY maxY lX lY canvas | entry _ BalloonEdgeData new. canvas _ Display getCanvas. minY _ (start y min: end y) min: via y. maxY _ (start y max: end y) max: via y. entry yValue: minY. self stepToFirstScanLineAt: minY in: entry. lX _ entry xValue. lY _ entry yValue. minY+1 to: maxY do:[:y| self stepToNextScanLineAt: y in: entry. canvas line: lX@lY to: entry xValue @ y width: 2 color: Color black. lX _ entry xValue. lY _ y. ]. ! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 5/25/2000 17:57'! debugDraw2 | canvas last max t next | canvas _ Display getCanvas. max _ 100. last _ nil. 0 to: max do:[:i| t _ i asFloat / max asFloat. next _ self valueAt: t. last ifNotNil:[ canvas line: last to: next rounded width: 2 color: Color blue. ]. last _ next rounded. ].! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 5/25/2000 17:57'! debugDrawWide: n | entry minY maxY canvas curve p1 p2 entry2 y | curve _ self class new. curve start: start + (0@n). curve via: via + (0@n). curve end: end + (0@n). entry _ BalloonEdgeData new. entry2 _ BalloonEdgeData new. canvas _ Display getCanvas. minY _ (start y min: end y) min: via y. maxY _ (start y max: end y) max: via y. entry yValue: minY. entry2 yValue: minY + n. self stepToFirstScanLineAt: minY in: entry. curve stepToFirstScanLineAt: minY+n in: entry2. y _ minY. 1 to: n do:[:i| y _ y + 1. self stepToNextScanLineAt: y in: entry. p1 _ entry xValue @ y. canvas line: p1 to: p1 + (n@0) width: 1 color: Color black. ]. [y < maxY] whileTrue:[ y _ y + 1. self stepToNextScanLineAt: y in: entry. p2 _ (entry xValue + n) @ y. curve stepToNextScanLineAt: y in: entry2. p1 _ entry2 xValue @ y. canvas line: p1 to: p2 width: 1 color: Color black. ]. ! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 00:35'! printOn: aStream aStream nextPutAll: self class name; nextPut:$(; print: start; nextPutAll:' - '; print: via; nextPutAll:' - '; print: end; nextPut:$)! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'MPW 1/1/1901 21:55'! printOnStream: aStream aStream print: self class name; print:'('; write: start; print:' - '; write: via; print:' - '; write: end; print:')'.! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 21:56'! quickPrint: curve Transcript nextPut:$(; print: curve start; space; print: curve via; space; print: curve end; nextPut:$).! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 22:13'! quickPrint: curve first: aBool aBool ifTrue:[Transcript cr]. Transcript nextPut:$(; print: curve start; space; print: curve via; space; print: curve end; nextPut:$). Transcript endEntry.! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 03:53'! stepToFirst | startX endX startY endY deltaY fwX1 fwX2 fwY1 fwY2 steps scaledStepSize squaredStepSize | (end y) >= (start y) ifTrue:[ startX _ start x. endX _ end x. startY _ start y. endY _ end y. ] ifFalse:[ startX _ end x. endX _ start x. startY _ end y. endY _ start y. ]. deltaY _ endY - startY. "Quickly check if the line is visible at all" (deltaY = 0) ifTrue:[^self]. fwX1 _ (startX + endX - (2 * via x)) asFloat. fwX2 _ (via x - startX * 2) asFloat. fwY1 _ (startY + endY - (2 * via y)) asFloat. fwY2 _ ((via y - startY) * 2) asFloat. steps _ deltaY asInteger * 2. scaledStepSize _ 1.0 / steps asFloat. squaredStepSize _ scaledStepSize * scaledStepSize. fwDx _ fwX2 * scaledStepSize. fwDDx _ 2.0 * fwX1 * squaredStepSize. fwDy _ fwY2 * scaledStepSize. fwDDy _ 2.0 * fwY1 * squaredStepSize. fwDx _ fwDx + (fwDDx * 0.5). fwDy _ fwDy + (fwDDy * 0.5). lastX _ startX asFloat. lastY _ startY asFloat. ! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 03:50'! stepToFirstInt "Scaled integer version of forward differencing" | startX endX startY endY deltaY fwX1 fwX2 fwY1 fwY2 scaledStepSize squaredStepSize | self halt. (end y) >= (start y) ifTrue:[ startX _ start x. endX _ end x. startY _ start y. endY _ end y. ] ifFalse:[ startX _ end x. endX _ start x. startY _ end y. endY _ start y. ]. deltaY _ endY - startY. "Quickly check if the line is visible at all" (deltaY = 0) ifTrue:[^nil]. fwX1 _ (startX + endX - (2 * via x)). fwX2 _ (via x - startX * 2). fwY1 _ (startY + endY - (2 * via y)). fwY2 _ ((via y - startY) * 2). maxSteps _ deltaY asInteger * 2. scaledStepSize _ 16r1000000 // maxSteps. "@@: Okay, we need some fancy 64bit multiplication here" squaredStepSize _ (scaledStepSize * scaledStepSize) bitShift: -24. fwDx _ fwX2 * scaledStepSize. fwDDx _ 2 * fwX1 * squaredStepSize. fwDy _ fwY2 * scaledStepSize. fwDDy _ 2 * fwY1 * squaredStepSize. fwDx _ fwDx + (fwDDx // 2). fwDy _ fwDy + (fwDDy // 2). self validateIntegerRange. lastX _ startX * 256. lastY _ startY * 256. ! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 00:26'! stepToNext lastX _ lastX + fwDx. lastY _ lastY + fwDy. fwDx _ fwDx + fwDDx. fwDy _ fwDy + fwDDy.! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 04:01'! stepToNextInt "Scaled integer version of forward differencing" self halt. (maxSteps >= 0) ifTrue:[ self validateIntegerRange. lastX _ lastX + ((fwDx + 16r8000) // 16r10000). lastY _ lastY + ((fwDy + 16r8000) // 16r10000). fwDx _ fwDx + fwDDx. fwDy _ fwDy + fwDDy. maxSteps _ maxSteps - 1. ].! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/30/1998 03:27'! validateIntegerRange fwDx class == SmallInteger ifFalse:[self halt]. fwDy class == SmallInteger ifFalse:[self halt]. fwDDx class == SmallInteger ifFalse:[self halt]. fwDDy class == SmallInteger ifFalse:[self halt]. ! ! !BalloonBezierSimulation methodsFor: 'private' stamp: 'ar 10/29/1998 21:26'! valueAt: parameter "Return the point at the value parameter: p(t) = (1-t)^2 * p1 + 2*t*(1-t) * p2 + t^2 * p3. " | t1 t2 t3 | t1 _ (1.0 - parameter) squared. t2 _ 2 * parameter * (1.0 - parameter). t3 _ parameter squared. ^(start * t1) + (via * t2) + (end * t3)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonBezierSimulation class instanceVariableNames: ''! !BalloonBezierSimulation class methodsFor: 'class initialization' stamp: 'ar 10/30/1998 03:04'! initialize "GraphicsBezierSimulation initialize" HeightSubdivisions _ 0. LineConversions _ 0. MonotonSubdivisions _ 0. OverflowSubdivisions _ 0.! ! Object variableWordSubclass: #BalloonBuffer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Engine'! !BalloonBuffer methodsFor: 'accessing' stamp: 'ar 10/26/1998 21:12'! at: index "For simulation only" | word | word _ self basicAt: index. word < 16r3FFFFFFF ifTrue:[^word]. "Avoid LargeInteger computations" ^word >= 16r80000000 "Negative?!!" ifTrue:["word - 16r100000000" (word bitInvert32 + 1) negated] ifFalse:[word]! ! !BalloonBuffer methodsFor: 'accessing' stamp: 'ar 10/26/1998 21:12'! at: index put: anInteger "For simulation only" | word | anInteger < 0 ifTrue:["word _ 16r100000000 + anInteger" word _ (anInteger + 1) negated bitInvert32] ifFalse:[word _ anInteger]. self basicAt: index put: word. ^anInteger! ! !BalloonBuffer methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index "For simulation only" ^Float fromIEEE32Bit: (self basicAt: index)! ! !BalloonBuffer methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! floatAt: index put: value "For simulation only" value isFloat ifTrue:[self basicAt: index put: value asIEEE32BitWord] ifFalse:[self at: index put: value asFloat]. ^value! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonBuffer class instanceVariableNames: ''! !BalloonBuffer class methodsFor: 'instance creation' stamp: 'ar 10/26/1998 21:11'! mew: n ^self new: (n max: 256)! ! !BalloonBuffer class methodsFor: 'instance creation' stamp: 'ar 10/26/1998 21:11'! new ^self new: 256.! ! FormCanvas subclass: #BalloonCanvas instanceVariableNames: 'transform colorTransform engine aaLevel deferred ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Engine'! !BalloonCanvas methodsFor: 'initialize' stamp: 'ar 11/24/1998 15:28'! flush "Force all pending primitives onscreen" engine ifNotNil:[engine flush].! ! !BalloonCanvas methodsFor: 'initialize' stamp: 'ar 12/30/1998 10:54'! initialize aaLevel _ 1. deferred _ false.! ! !BalloonCanvas methodsFor: 'initialize' stamp: 'ar 11/11/1998 20:25'! resetEngine engine _ nil.! ! !BalloonCanvas methodsFor: 'accessing' stamp: 'ar 11/13/1998 01:02'! aaLevel ^aaLevel! ! !BalloonCanvas methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:53'! aaLevel: newLevel "Only allow changes to aaLevel if we're working on >= 8 bit forms" form depth >= 8 ifFalse:[^self]. aaLevel = newLevel ifTrue:[^self]. self flush. "In case there are pending primitives in the engine" aaLevel _ newLevel. engine ifNotNil:[engine aaLevel: aaLevel].! ! !BalloonCanvas methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:54'! deferred ^deferred! ! !BalloonCanvas methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:55'! deferred: aBoolean deferred == aBoolean ifTrue:[^self]. self flush. "Force pending prims on screen" deferred _ aBoolean. engine ifNotNil:[engine deferred: aBoolean].! ! !BalloonCanvas methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:55'! ensuredEngine engine ifNil:[ true ifTrue:[engine _ BalloonEngine new] ifFalse:[engine _ BalloonDebugEngine new]. engine aaLevel: aaLevel. engine bitBlt: port. engine destOffset: origin. engine clipRect: clipRect. engine deferred: deferred. engine]. engine colorTransform: colorTransform. engine edgeTransform: transform. ^engine! ! !BalloonCanvas methodsFor: 'testing' stamp: 'ar 11/13/1998 13:19'! isBalloonCanvas ^true! ! !BalloonCanvas methodsFor: 'testing' stamp: 'ar 11/12/1998 01:07'! isVisible: aRectangle ^transform ifNil:[super isVisible: aRectangle] ifNotNil:[super isVisible: (transform localBoundsToGlobal: aRectangle)]! ! !BalloonCanvas methodsFor: 'copying' stamp: 'ar 11/24/1998 22:33'! copy self flush. ^super copy resetEngine! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 05:40'! fillColor: c "Note: This always fills, even if the color is transparent." "Note2: To achieve the above we must make sure that c is NOT transparent" self frameAndFillRectangle: form boundingBox fillColor: (c alpha: 1.0) borderWidth: 0 borderColor: nil! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 05:51'! fillOval: r color: c borderWidth: borderWidth borderColor: borderColor "Draw a filled and outlined oval" "Note: The optimization test below should actually read: self ifNoTransformWithIn: (r insetBy: borderWidth // 2) but since borderWidth is assumed to be very small related to r we don't check it." (self ifNoTransformWithIn: r) ifTrue:[^super fillOval: r color: c borderWidth: borderWidth borderColor: borderColor]. ^self drawOval: (r insetBy: borderWidth // 2) color: c borderWidth: borderWidth borderColor: borderColor! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 05:40'! fillRectangle: r color: c "Fill the rectangle with the given color" ^self frameAndFillRectangle: r fillColor: c borderWidth: 0 borderColor: nil! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 06:26'! frameAndFillRectangle: r fillColor: c borderWidth: borderWidth borderColor: borderColor "Draw a filled and outlined rectangle" "Note: The optimization test below should actually read: self ifNoTransformWithIn: (r insetBy: borderWidth // 2) but since borderWidth is assumed to be very small related to r we don't check it." (self ifNoTransformWithIn: r) ifTrue:[^super frameAndFillRectangle: r fillColor: c borderWidth: borderWidth borderColor: borderColor]. ^self drawRectangle: (r insetBy: borderWidth // 2) color: c borderWidth: borderWidth borderColor: borderColor! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 05:52'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth topLeftColor: topLeftColor bottomRightColor: bottomRightColor "Draw a beveled or raised rectangle" | bw | "Note: The optimization test below should actually read: self ifNoTransformWithIn: (r insetBy: borderWidth // 2) but since borderWidth is assumed to be very small related to r we don't check it." (self ifNoTransformWithIn: r) ifTrue:[^super frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth topLeftColor: topLeftColor bottomRightColor: bottomRightColor]. "Fill rectangle and draw top and left border" bw _ borderWidth // 2. self drawRectangle: (r insetBy: bw) color: fillColor borderWidth: borderWidth borderColor: topLeftColor. "Now draw bottom right border." self drawPolygon: (Array with: r topRight + (bw negated@bw) with: r bottomRight - bw asPoint with: r bottomLeft + (bw@bw negated)) color: nil borderWidth: borderWidth borderColor: bottomRightColor.! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/9/1999 05:41'! frameRectangle: r width: w color: c "Draw a frame around the given rectangle" ^self frameAndFillRectangle: r fillColor: nil borderWidth: w borderColor: c! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 2/12/1999 17:45'! line: pt1 to: pt2 width: w color: c "Draw a line from pt1 to: pt2" (self ifNoTransformWithIn:(pt1 rect: pt2)) ifTrue:[^super line: pt1 to: pt2 width: w color: c]. ^self drawPolygon: (Array with: pt1 with: pt2) color: c borderWidth: w borderColor: c! ! !BalloonCanvas methodsFor: 'drawing' stamp: 'ar 11/11/1998 19:39'! point: pt color: c "Is there any use for this?" | myPt | transform ifNil:[myPt _ pt] ifNotNil:[myPt _ transform localPointToGlobal: pt]. ^super point: myPt color: c! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'DSM 10/15/1999 15:14'! drawBezier3Shape: vertices color: c borderWidth: borderWidth borderColor: borderColor self drawBezierShape: (Bezier3Segment convertBezier3ToBezier2: vertices) color: c borderWidth: borderWidth borderColor: borderColor! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 2/17/2000 00:25'! drawBezierShape: vertices color: c borderWidth: borderWidth borderColor: borderColor "Draw a boundary shape that is defined by a list of vertices. Each three subsequent vertices define a quadratic bezier segment. For lines, the control point should be set to either the start or the end of the bezier curve." | fillC borderC | fillC _ self shadowColor ifNil:[c]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawBezierShape: vertices fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 11/24/1998 15:16'! drawCompressedShape: compressedShape "Draw a compressed shape" self ensuredEngine drawCompressedShape: compressedShape transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'DSM 10/15/1999 15:18'! drawGeneralBezier3Shape: contours color: c borderWidth: borderWidth borderColor: borderColor | b2 | b2 _ contours collect: [:b3 | Bezier3Segment convertBezier3ToBezier2: b3 ]. self drawGeneralBezierShape: b2 color: c borderWidth: borderWidth borderColor: borderColor! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 2/17/2000 00:24'! drawGeneralBezierShape: contours color: c borderWidth: borderWidth borderColor: borderColor "Draw a general boundary shape (e.g., possibly containing holes)" | fillC borderC | fillC _ self shadowColor ifNil:[c]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawGeneralBezierShape: contours fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 2/17/2000 00:24'! drawGeneralPolygon: contours color: c borderWidth: borderWidth borderColor: borderColor "Draw a general polygon (e.g., a polygon that can contain holes)" | fillC borderC | fillC _ self shadowColor ifNil:[c]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawGeneralPolygon: contours fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 2/17/2000 00:24'! drawOval: r color: c borderWidth: borderWidth borderColor: borderColor "Draw the oval defined by the given rectangle" | fillC borderC | fillC _ self shadowColor ifNil:[c]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawOval: r fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 2/17/2000 00:24'! drawRectangle: r color: c borderWidth: borderWidth borderColor: borderColor "Draw a rectangle" | fillC borderC | fillC _ self shadowColor ifNil:[c]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawRectangle: r fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! !BalloonCanvas methodsFor: 'balloon drawing' stamp: 'ar 5/28/2000 12:23'! render: anObject | b3d | b3d _ (B3DRenderEngine defaultForPlatformOn: form). "Install the viewport offset" b3d viewportOffset: origin. "Install the clipping rectangle for the target form" b3d clipRect: clipRect. anObject renderOn: b3d. b3d flush.! ! !BalloonCanvas methodsFor: 'TODO' stamp: 'ar 2/9/1999 05:46'! line: point1 to: point2 brushForm: brush "Who's gonna use this?" | pt1 pt2 | self flush. "Sorry, but necessary..." transform ifNil:[pt1 _ point1. pt2 _ point2] ifNotNil:[pt1 _ transform localPointToGlobal: point1. pt2 _ transform localPointToGlobal: point2]. ^super line: pt1 to: pt2 brushForm: brush! ! !BalloonCanvas methodsFor: 'TODO' stamp: 'ar 2/9/1999 05:46'! paragraph: para bounds: bounds color: c (self ifNoTransformWithIn: bounds) ifTrue:[^super paragraph: para bounds: bounds color: c].! ! !BalloonCanvas methodsFor: 'TODO' stamp: 'ar 2/9/1999 05:38'! text: s bounds: boundsRect font: fontOrNil color: c (self ifNoTransformWithIn: boundsRect) ifTrue:[^super text: s bounds: boundsRect font: fontOrNil color: c]! ! !BalloonCanvas methodsFor: 'transforming' stamp: 'ar 11/24/1998 14:45'! colorTransformBy: aColorTransform aColorTransform ifNil:[^self]. colorTransform ifNil:[colorTransform _ aColorTransform] ifNotNil:[colorTransform _ colorTransform composedWithLocal: aColorTransform]! ! !BalloonCanvas methodsFor: 'transforming' stamp: 'ar 12/30/1998 10:47'! preserveStateDuring: aBlock | state result | state _ BalloonState new. state transform: transform. state colorTransform: colorTransform. state aaLevel: self aaLevel. result _ aBlock value: self. transform _ state transform. colorTransform _ state colorTransform. self aaLevel: state aaLevel. ^result! ! !BalloonCanvas methodsFor: 'transforming' stamp: 'ar 11/12/1998 00:32'! transformBy: aTransform aTransform ifNil:[^self]. transform ifNil:[transform _ aTransform] ifNotNil:[transform _ transform composedWithLocal: aTransform]! ! !BalloonCanvas methodsFor: 'transforming' stamp: 'ar 5/29/1999 08:59'! transformBy: aDisplayTransform during: aBlock | myTransform result | myTransform _ transform. self transformBy: aDisplayTransform. result _ aBlock value: self. transform _ myTransform. ^result! ! !BalloonCanvas methodsFor: 'private' stamp: 'ar 2/9/1999 06:29'! ifNoTransformWithIn: box "Return true if the current transformation does not affect the given bounding box" | delta | "false ifFalse:[^false]." transform isNil ifTrue:[^true]. delta _ (transform localPointToGlobal: box origin) - box origin. ^(transform localPointToGlobal: box corner) - box corner = delta! ! !BalloonCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 12:12'! image: aForm at: aPoint sourceRect: sourceRect rule: rule | warp dstRect srcQuad dstOffset center | (self ifNoTransformWithIn: sourceRect) & false ifTrue:[^super image: aForm at: aPoint sourceRect: sourceRect rule: rule]. dstRect _ (transform localBoundsToGlobal: (aForm boundingBox translateBy: aPoint)). dstOffset _ 0@0. "dstRect origin." "dstRect _ 0@0 corner: dstRect extent." center _ 0@0."transform globalPointToLocal: dstRect origin." srcQuad _ transform globalPointsToLocal: (dstRect innerCorners). srcQuad _ srcQuad collect:[:pt| pt - aPoint]. warp _ (WarpBlt current toForm: Display) sourceForm: aForm; cellSize: 2; "installs a new colormap if cellSize > 1" combinationRule: Form over. warp copyQuad: srcQuad toRect: (dstRect translateBy: dstOffset). self frameRectangle: (aForm boundingBox translateBy: aPoint) color: Color green. "... TODO ... create a bitmap fill style from the form and use it for a simple rectangle."! ! !BalloonCanvas methodsFor: 'converting' stamp: 'ar 11/11/1998 22:57'! asBalloonCanvas ^self! ! !BalloonCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 08:48'! fillRectangle: aRectangle fillStyle: aFillStyle "Fill the given rectangle." ^self drawRectangle: aRectangle color: aFillStyle "@@: Name confusion!!!!!!" borderWidth: 0 borderColor: nil ! ! !BalloonCanvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:50'! fillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc "Fill the given rectangle." ^self drawOval: (aRectangle insetBy: bw // 2) color: aFillStyle "@@: Name confusion!!!!!!" borderWidth: bw borderColor: bc ! ! !BalloonCanvas methodsFor: 'drawing-polygons' stamp: 'ar 6/18/1999 09:00'! drawPolygon: vertices color: aColor borderWidth: bw borderColor: bc "Generalize for the BalloonCanvas" ^self drawPolygon: vertices fillStyle: aColor borderWidth: bw borderColor: bc! ! !BalloonCanvas methodsFor: 'drawing-polygons' stamp: 'ar 2/17/2000 00:25'! drawPolygon: vertices fillStyle: aFillStyle borderWidth: borderWidth borderColor: borderColor "Draw a simple polygon defined by the list of vertices." | fillC borderC | fillC _ self shadowColor ifNil:[aFillStyle]. borderC _ self shadowColor ifNil:[borderColor]. self ensuredEngine drawPolygon: (vertices copyWith: vertices first) fill: fillC borderWidth: borderWidth borderColor: borderC transform: transform.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonCanvas class instanceVariableNames: ''! !BalloonCanvas class methodsFor: 'instance creation' stamp: 'ar 11/11/1998 19:14'! new ^super new initialize! ! BalloonEngine subclass: #BalloonDebugEngine instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Engine'! !BalloonDebugEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:30'! initialize super initialize. deferred _ true.! ! !BalloonDebugEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 01:45'! reset workBuffer _ BalloonBuffer new: 400000. super reset.! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:04'! primClipRectInto: rect ^BalloonEnginePlugin doPrimitive:'gePrimitiveGetClipRect'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/25/1998 22:29'! primFlushNeeded "Return true if there are no more entries in AET and GET and the last scan line has been displayed" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveNeedsFlush'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:04'! primGetAALevel "Set the AA level" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetAALevel'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:04'! primGetBezierStats: statsArray ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetBezierStats'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:04'! primGetClipRect: rect ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetClipRect'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primGetCounts: statsArray ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetCounts'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:55'! primGetDepth "Set the AA level" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetDepth'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primGetFailureReason ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetFailureReason'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primGetOffset ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetOffset'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primGetTimes: statsArray ^BalloonEnginePlugin doPrimitive: 'gePrimitiveGetTimes'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/25/1998 22:20'! primNeedsFlush "Return true if there are no more entries in AET and GET and the last scan line has been displayed" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveNeedsFlush'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primSetAALevel: level "Set the AA level" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetAALevel'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:05'! primSetClipRect: rect ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetClipRect'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:06'! primSetColorTransform: transform ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetColorTransform'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:06'! primSetDepth: depth ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetDepth'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:06'! primSetEdgeTransform: transform ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetEdgeTransform'! ! !BalloonDebugEngine methodsFor: 'primitives-access' stamp: 'ar 11/24/1998 21:06'! primSetOffset: point ^BalloonEnginePlugin doPrimitive: 'gePrimitiveSetOffset'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:11'! primAddBezierFrom: start to: end via: via leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddBezier'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:01'! primAddBezierShape: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddBezierShape'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/27/1998 14:27'! primAddBitmapFill: form colormap: cmap tile: tileFlag from: origin along: direction normal: normal xIndex: xIndex ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddBitmapFill'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:01'! primAddCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddCompressedShape'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:02'! primAddExternalEdge: index initialX: initialX initialY: initialY initialZ: initialZ leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ^BalloonEnginePlugin doPrimitive: 'gePrimitiveRegisterExternalEdge'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:02'! primAddExternalFill: index ^BalloonEnginePlugin doPrimitive: 'gePrimitiveRegisterExternalFill'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:02'! primAddGradientFill: colorRamp from: origin along: direction normal: normal radial: isRadial ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddGradientFill'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:16'! primAddLineFrom: start to: end leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddLine'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:03'! primAddOvalFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddOval'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:03'! primAddPolygon: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddPolygon'! ! !BalloonDebugEngine methodsFor: 'primitives-adding' stamp: 'ar 11/24/1998 21:20'! primAddRectFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddRect'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 20:59'! primAddActiveEdgeTableEntryFrom: edgeEntry "Add edge entry to the AET." ^BalloonEnginePlugin doPrimitive: 'gePrimitiveAddActiveEdgeEntry'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 20:59'! primChangeActiveEdgeTableEntryFrom: edgeEntry "Change the entry in the active edge table from edgeEntry" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveChangedActiveEdgeEntry'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 20:59'! primDisplaySpanBuffer "Display the current scan line if necessary" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveDisplaySpanBuffer'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 20:59'! primFinishedProcessing "Return true if there are no more entries in AET and GET and the last scan line has been displayed" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveFinishedProcessing'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primInitializeProcessing "Initialize processing in the GE. Create the active edge table and sort it." ^BalloonEnginePlugin doPrimitive: 'gePrimitiveInitializeProcessing'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primMergeFill: fillBitmap from: fill "Merge the filled bitmap into the current output buffer." ^BalloonEnginePlugin doPrimitive: 'gePrimitiveMergeFillFrom'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primNextActiveEdgeEntryInto: edgeEntry "Store the next entry of the AET at the current y-value in edgeEntry. Return false if there is no entry, true otherwise." ^BalloonEnginePlugin doPrimitive: 'gePrimitiveNextActiveEdgeEntry'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primNextFillEntryInto: fillEntry "Store the next fill entry of the active edge table in fillEntry. Return false if there is no such entry, true otherwise" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveNextFillEntry'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primNextGlobalEdgeEntryInto: edgeEntry "Store the next entry of the GET at the current y-value in edgeEntry. Return false if there is no entry, true otherwise." ^BalloonEnginePlugin doPrimitive: 'gePrimitiveNextGlobalEdgeEntry'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primRenderImage: edge with: fill "Start/Proceed rendering the current scan line" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveRenderImage'! ! !BalloonDebugEngine methodsFor: 'primitives-incremental' stamp: 'ar 11/24/1998 21:00'! primRenderScanline: edge with: fill "Start/Proceed rendering the current scan line" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveRenderScanline'! ! !BalloonDebugEngine methodsFor: 'primitives-misc' stamp: 'ar 11/24/1998 20:59'! primCopyBufferFrom: oldBuffer to: newBuffer "Copy the contents of oldBuffer into the (larger) newBuffer" ^BalloonEnginePlugin doPrimitive: 'gePrimitiveCopyBuffer'! ! !BalloonDebugEngine methodsFor: 'primitives-misc' stamp: 'ar 11/24/1998 20:59'! primInitializeBuffer: buffer ^BalloonEnginePlugin doPrimitive: 'gePrimitiveInitializeBuffer'! ! Object subclass: #BalloonEdgeData instanceVariableNames: 'index xValue yValue zValue lines source ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonEdgeData commentStamp: '' prior: 0! BalloonEdgeData defines an entry in the internal edge table of the Balloon engine. Instance Variables: index The index into the external objects array of the associated graphics engine xValue The computed x-value of the requested operation yValue The y-value for the requested operation height The (remaining) height of the edge source The object from the external objects array! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:34'! index ^index! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:35'! index: anInteger index _ anInteger! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:13'! lines ^lines! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:13'! lines: anInteger ^lines _ anInteger! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:34'! source ^source! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 21:39'! source: anObject source _ anObject! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:34'! xValue ^xValue! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:35'! xValue: anInteger xValue _ anInteger! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:34'! yValue ^yValue! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:35'! yValue: anInteger yValue _ anInteger! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 19:56'! zValue ^zValue! ! !BalloonEdgeData methodsFor: 'accessing' stamp: 'ar 10/27/1998 19:56'! zValue: anInteger zValue _ anInteger! ! !BalloonEdgeData methodsFor: 'computing' stamp: 'ar 10/27/1998 15:53'! stepToFirstScanLine source stepToFirstScanLineAt: yValue in: self! ! !BalloonEdgeData methodsFor: 'computing' stamp: 'ar 10/27/1998 15:53'! stepToNextScanLine source stepToNextScanLineAt: yValue in: self! ! Object subclass: #BalloonEngine instanceVariableNames: 'workBuffer span bitBlt forms clipRect destOffset externals aaLevel edgeTransform colorTransform deferred postFlushNeeded ' classVariableNames: 'BezierStats BufferCache CacheProtect Counts Debug Times ' poolDictionaries: 'BalloonEngineConstants ' category: 'Balloon-Engine'! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:29'! flush "Force all pending primitives onscreen" workBuffer ifNil:[^self]. self copyBits. self release.! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 12/30/1998 11:24'! initialize externals _ OrderedCollection new: 100. span _ Bitmap new: 2048. bitBlt _ nil. self bitBlt: ((BitBlt toForm: Display) destRect: Display boundingBox; yourself). forms _ #(). deferred _ false.! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:42'! postFlushIfNeeded "Force all pending primitives onscreen" workBuffer ifNil:[^self]. (deferred not or:[postFlushNeeded]) ifTrue:[ self copyBits. self release].! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:43'! preFlushIfNeeded "Force all pending primitives onscreen" workBuffer ifNil:[^self]. self primFlushNeeded ifTrue:[ self copyBits. self reset].! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/11/1998 22:52'! release self class recycleBuffer: workBuffer. workBuffer _ nil.! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:34'! reset workBuffer ifNil:[workBuffer _ self class allocateOrRecycleBuffer: 10000]. self primInitializeBuffer: workBuffer. self primSetAALevel: self aaLevel. self primSetOffset: destOffset. self primSetClipRect: clipRect. self primSetEdgeTransform: edgeTransform. self primSetColorTransform: colorTransform. forms _ #().! ! !BalloonEngine methodsFor: 'initialize' stamp: 'ar 11/25/1998 22:39'! resetIfNeeded workBuffer ifNil:[self reset]. self primSetEdgeTransform: edgeTransform. self primSetColorTransform: colorTransform. self primSetDepth: self primGetDepth + 1. postFlushNeeded _ false.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 10/11/1999 16:49'! drawBezierShape: points fill: fillStyle borderWidth: borderWidth borderColor: borderFill transform: aTransform | fills | self edgeTransform: aTransform. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderFill. self primAddBezierShape: points segments: (points size) // 3 fill: (fills at: 1) lineWidth: borderWidth lineFill: (fills at: 2). self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 11/26/1998 19:44'! drawCompressedShape: shape transform: aTransform | fillIndexList | self edgeTransform: aTransform. self resetIfNeeded. fillIndexList _ self registerFills: shape fillStyles. self primAddCompressedShape: shape points segments: shape numSegments leftFills: shape leftFills rightFills: shape rightFills lineWidths: shape lineWidths lineFills: shape lineFills fillIndexList: fillIndexList. self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 1/15/1999 03:02'! drawGeneralBezierShape: contours fill: fillStyle borderWidth: borderWidth borderColor: borderFill transform: aTransform | fills | self edgeTransform: aTransform. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderFill. contours do:[:points| self primAddBezierShape: points segments: (points size // 3) fill: (fills at: 1) lineWidth: borderWidth lineFill: (fills at: 2). "Note: To avoid premature flushing of the pipeline we need to reset the flush bit within the engine." self primFlushNeeded: false. ]. "And set the flush bit afterwards" self primFlushNeeded: true. self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 1/15/1999 03:02'! drawGeneralPolygon: contours fill: fillStyle borderWidth: borderWidth borderColor: borderFill transform: aTransform | fills | self edgeTransform: aTransform. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderFill. contours do:[:points| self primAddPolygon: points segments: points size fill: (fills at: 1) lineWidth: borderWidth lineFill: (fills at: 2). "Note: To avoid premature flushing of the pipeline we need to reset the flush bit within the engine." self primFlushNeeded: false. ]. "And set the flush bit afterwards" self primFlushNeeded: true. self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 11/26/1998 19:45'! drawOval: rect fill: fillStyle borderWidth: borderWidth borderColor: borderColor transform: aMatrix | fills | self edgeTransform: aMatrix. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderColor. self primAddOvalFrom: rect origin to: rect corner fillIndex: (fills at: 1) borderWidth: borderWidth borderColor: (fills at: 2). self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 11/26/1998 19:45'! drawPolygon: points fill: fillStyle borderWidth: borderWidth borderColor: borderFill transform: aTransform | fills | self edgeTransform: aTransform. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderFill. self primAddPolygon: points segments: points size fill: (fills at: 1) lineWidth: borderWidth lineFill: (fills at: 2). self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 11/26/1998 19:45'! drawRectangle: rect fill: fillStyle borderWidth: borderWidth borderColor: borderColor transform: aMatrix | fills | self edgeTransform: aMatrix. self resetIfNeeded. fills _ self registerFill: fillStyle and: borderColor. self primAddRectFrom: rect origin to: rect corner fillIndex: (fills at: 1) borderWidth: borderWidth borderColor: (fills at: 2). self postFlushIfNeeded.! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 1/14/1999 15:24'! registerFill: aFillStyle "Register the given fill style." | theForm | aFillStyle ifNil:[^0]. aFillStyle isSolidFill ifTrue:[^aFillStyle scaledPixelValue32]. aFillStyle isGradientFill ifTrue:[ ^self primAddGradientFill: aFillStyle pixelRamp from: aFillStyle origin along: aFillStyle direction normal: aFillStyle normal radial: aFillStyle isRadialFill ]. aFillStyle isBitmapFill ifTrue:[ theForm _ aFillStyle form. theForm unhibernate. forms _ forms copyWith: theForm. ^self primAddBitmapFill: theForm colormap: (theForm colormapIfNeededForDepth: 32) tile: aFillStyle isTiled from: aFillStyle origin along: aFillStyle direction normal: aFillStyle normal xIndex: forms size]. ^0! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 11/26/1998 19:45'! registerFill: fill1 and: fill2 ^self registerFills: (Array with: fill1 with: fill2)! ! !BalloonEngine methodsFor: 'drawing' stamp: 'ar 1/14/1999 15:24'! registerFill: aFillStyle transform: aTransform aFillStyle ifNil:[^0]. aFillStyle isSolidFill ifTrue:[^aFillStyle scaledPixelValue32]. aFillStyle isGradientFill ifTrue:[ ^self primAddGradientFill: aFillStyle pixelRamp from: aFillStyle origin along: aFillStyle direction normal: aFillStyle normal radial: aFillStyle isRadialFill matrix: aTransform. ]. ^0! ! !BalloonEngine methodsFor: 'drawing' stamp: 'di 11/21/1999 20:15'! registerFills: fills | fillIndexList index fillIndex | ((colorTransform notNil and:[colorTransform isAlphaTransform]) or:[ fills anySatisfy: [:any| any notNil and:[any isTranslucent]]]) ifTrue:[ self flush. self reset. postFlushNeeded _ true]. fillIndexList _ WordArray new: fills size. index _ 1. [index <= fills size] whileTrue:[ fillIndex _ self registerFill: (fills at: index). fillIndex == nil ifTrue:[index _ 1] "Need to start over" ifFalse:[fillIndexList at: index put: fillIndex. index _ index+1] ]. ^fillIndexList! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/25/1998 00:45'! canProceedAfter: failureReason "Check if we can proceed after the failureReason indicated." | newBuffer | failureReason = GErrorNeedFlush ifTrue:[ "Need to flush engine before proceeding" self copyBits. self reset. ^true]. failureReason = GErrorNoMoreSpace ifTrue:[ "Work buffer is too small" newBuffer _ workBuffer species new: workBuffer size * 2. self primCopyBufferFrom: workBuffer to: newBuffer. workBuffer _ newBuffer. ^true]. "Not handled" ^false! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/29/1998 18:22'! copyBits self copyLoopFaster.! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/14/1998 19:32'! copyLoop "This is the basic rendering loop using as little primitive support as possible." | finished edge fill | edge _ BalloonEdgeData new. fill _ BalloonFillData new. self primInitializeProcessing. "Initialize the GE for processing" [self primFinishedProcessing] whileFalse:[ "Step 1: Process the edges in the global edge table that will be added in this step" [finished _ self primNextGlobalEdgeEntryInto: edge. finished] whileFalse:[ edge source: (externals at: edge index). edge stepToFirstScanLine. self primAddActiveEdgeTableEntryFrom: edge]. "Step 2: Scan the active edge table" [finished _ self primNextFillEntryInto: fill. finished] whileFalse:[ fill source: (externals at: fill index). "Compute the new fill" fill computeFill. "And mix it in the out buffer" self primMergeFill: fill destForm bits from: fill]. "Step 3: Display the current span buffer if necessary" self primDisplaySpanBuffer. "Step 4: Advance and resort the active edge table" [finished _ self primNextActiveEdgeEntryInto: edge. finished] whileFalse:[ "If the index is zero then the edge has been handled by the GE" edge source: (externals at: edge index). edge stepToNextScanLine. self primChangeActiveEdgeTableEntryFrom: edge]. ]. self primGetTimes: Times. self primGetCounts: Counts. self primGetBezierStats: BezierStats.! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/14/1998 19:32'! copyLoopFaster "This is a copy loop drawing one scan line at a time" | edge fill reason | edge _ BalloonEdgeData new. fill _ BalloonFillData new. [self primFinishedProcessing] whileFalse:[ reason _ self primRenderScanline: edge with: fill. "reason ~= 0 means there has been a problem" reason = 0 ifFalse:[ self processStopReason: reason edge: edge fill: fill. ]. ]. self primGetTimes: Times. self primGetCounts: Counts. self primGetBezierStats: BezierStats.! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/14/1998 19:33'! copyLoopFastest "This is a copy loop drawing the entire image" | edge fill reason | edge _ BalloonEdgeData new. fill _ BalloonFillData new. [self primFinishedProcessing] whileFalse:[ reason _ self primRenderImage: edge with: fill. "reason ~= 0 means there has been a problem" reason = 0 ifFalse:[ self processStopReason: reason edge: edge fill: fill. ]. ]. self primGetTimes: Times. self primGetCounts: Counts. self primGetBezierStats: BezierStats.! ! !BalloonEngine methodsFor: 'copying' stamp: 'ar 11/11/1998 21:19'! processStopReason: reason edge: edge fill: fill "The engine has stopped because of some reason. Try to figure out how to respond and do the necessary actions." "Note: The order of operations below can affect the speed" "Process unknown fills first" reason = GErrorFillEntry ifTrue:[ fill source: (externals at: fill index). "Compute the new fill" fill computeFill. "And mix it in the out buffer" ^self primMergeFill: fill destForm bits from: fill]. "Process unknown steppings in the AET second" reason = GErrorAETEntry ifTrue:[ edge source: (externals at: edge index). edge stepToNextScanLine. ^self primChangeActiveEdgeTableEntryFrom: edge]. "Process unknown entries in the GET third" reason = GErrorGETEntry ifTrue:[ edge source: (externals at: edge index). edge stepToFirstScanLine. ^self primAddActiveEdgeTableEntryFrom: edge]. "Process generic problems last" (self canProceedAfter: reason) ifTrue:[^self]. "Okay." ^self error:'Unkown stop reason in graphics engine' ! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/11/1998 23:04'! aaLevel ^aaLevel ifNil:[1]! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/11/1998 23:04'! aaLevel: anInteger aaLevel _ (anInteger min: 4) max: 1.! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 10/29/1998 01:51'! aaTransform "Return a transformation for the current anti-aliasing level" | matrix | matrix _ MatrixTransform2x3 withScale: (self aaLevel) asFloat asPoint. matrix offset: (self aaLevel // 2) asFloat asPoint. ^matrix composedWith:(MatrixTransform2x3 withOffset: destOffset asFloatPoint)! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 10/13/1998 03:04'! bitBlt ^bitBlt! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 5/28/2000 15:02'! bitBlt: aBitBlt bitBlt _ aBitBlt. bitBlt isNil ifTrue:[^self]. self class primitiveSetBitBltPlugin: bitBlt getPluginName. self clipRect: bitBlt clipRect. bitBlt sourceForm: (Form extent: span size @ 1 depth: 32 bits: span); sourceRect: (0@0 extent: 1@span size); colorMap: (Color colorMapIfNeededFrom: 32 to: bitBlt destForm depth); combinationRule: (bitBlt destForm depth >= 8 ifTrue:[34] ifFalse:[Form paint]).! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/1/1998 02:57'! clipRect ^clipRect! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 10/13/1998 02:44'! clipRect: aRect clipRect _ aRect truncated! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/24/1998 15:04'! colorTransform ^colorTransform! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/24/1998 15:04'! colorTransform: aColorTransform colorTransform _ aColorTransform! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 12/30/1998 11:24'! deferred ^deferred! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 12/30/1998 11:24'! deferred: aBoolean deferred _ aBoolean.! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/1/1998 02:56'! destOffset ^destOffset! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/12/1998 00:22'! destOffset: aPoint destOffset _ aPoint asIntegerPoint. bitBlt destX: aPoint x; destY: aPoint y.! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/25/1998 22:34'! edgeTransform ^edgeTransform! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 11/25/1998 22:34'! edgeTransform: aTransform edgeTransform _ aTransform.! ! !BalloonEngine methodsFor: 'accessing' stamp: 'ar 10/29/1998 01:51'! fullTransformFrom: aMatrix | m | m _ self aaTransform composedWith: aMatrix. "m offset: m offset + destOffset." ^m! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:48'! primClipRectInto: rect ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primFlushNeeded ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primFlushNeeded: aBoolean ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetAALevel "Set the AA level" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetBezierStats: statsArray ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetClipRect: rect ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetCounts: statsArray ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primGetDepth ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetFailureReason ^0! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetOffset ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primGetTimes: statsArray ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primSetAALevel: level "Set the AA level" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primSetClipRect: rect ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primSetColorTransform: transform ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primSetDepth: depth ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:47'! primSetEdgeTransform: transform ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-access' stamp: 'ar 2/2/2001 15:49'! primSetOffset: point ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddBezierFrom: start to: end via: via leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddBezierFrom: start to: end via: via leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddBezierShape: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddBezierShape: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddBitmapFill: form colormap: cmap tile: tileFlag from: origin along: direction normal: normal xIndex: xIndex (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddBitmapFill: form colormap: cmap tile: tileFlag from: origin along: direction normal: normal xIndex: xIndex ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddExternalEdge: index initialX: initialX initialY: initialY initialZ: initialZ leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddExternalEdge: index initialX: initialX initialY: initialY initialZ: initialZ leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddExternalFill: index (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddExternalFill: index ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddGradientFill: colorRamp from: origin along: direction normal: normal radial: isRadial (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddGradientFill: colorRamp from: origin along: direction normal: normal radial: isRadial ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddLineFrom: start to: end leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddLineFrom: start to: end leftFillIndex: leftFillIndex rightFillIndex: rightFillIndex ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddOvalFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddOvalFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddPolygon: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddPolygon: points segments: nSegments fill: fillStyle lineWidth: lineWidth lineFill: lineFill ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-adding' stamp: 'ar 2/2/2001 15:47'! primAddRectFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddRectFrom: start to: end fillIndex: fillIndex borderWidth: width borderColor: pixelValue32 ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:48'! primAddActiveEdgeTableEntryFrom: edgeEntry "Add edge entry to the AET." (self canProceedAfter: self primGetFailureReason) ifTrue:[ ^self primAddActiveEdgeTableEntryFrom: edgeEntry ]. ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:48'! primChangeActiveEdgeTableEntryFrom: edgeEntry "Change the entry in the active edge table from edgeEntry" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:48'! primDisplaySpanBuffer "Display the current scan line if necessary" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primFinishedProcessing "Return true if there are no more entries in AET and GET and the last scan line has been displayed" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primInitializeProcessing "Initialize processing in the GE. Create the active edge table and sort it." ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primMergeFill: fillBitmap from: fill "Merge the filled bitmap into the current output buffer." ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primNextActiveEdgeEntryInto: edgeEntry "Store the next entry of the AET at the current y-value in edgeEntry. Return false if there is no entry, true otherwise." ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primNextFillEntryInto: fillEntry "Store the next fill entry of the active edge table in fillEntry. Return false if there is no such entry, true otherwise" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primNextGlobalEdgeEntryInto: edgeEntry "Store the next entry of the GET at the current y-value in edgeEntry. Return false if there is no entry, true otherwise." ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primRenderImage: edge with: fill "Start/Proceed rendering the current scan line" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-incremental' stamp: 'ar 2/2/2001 15:49'! primRenderScanline: edge with: fill "Start/Proceed rendering the current scan line" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-misc' stamp: 'ar 2/2/2001 15:48'! primCopyBufferFrom: oldBuffer to: newBuffer "Copy the contents of oldBuffer into the (larger) newBuffer" ^self primitiveFailed! ! !BalloonEngine methodsFor: 'primitives-misc' stamp: 'ar 2/2/2001 15:49'! primInitializeBuffer: buffer ^self primitiveFailed! ! !BalloonEngine methodsFor: 'experimental' stamp: 'ar 11/12/1998 19:53'! registerBezier: aCurve transformation: aMatrix self primAddBezierFrom: aCurve start to: aCurve end via: aCurve via leftFillIndex: (self registerFill: aCurve leftFill transform: aMatrix) rightFillIndex: (self registerFill: aCurve rightFill transform: aMatrix) matrix: aMatrix! ! !BalloonEngine methodsFor: 'experimental' stamp: 'ar 11/11/1998 21:15'! registerBoundary: boundaryObject transformation: aMatrix | external | external _ boundaryObject asEdgeRepresentation: (self fullTransformFrom: aMatrix). self subdivideExternalEdge: external from: boundaryObject. ! ! !BalloonEngine methodsFor: 'experimental' stamp: 'ar 11/12/1998 19:54'! registerExternalEdge: externalEdge from: boundaryObject externals addLast: externalEdge. self primAddExternalEdge: externals size initialX: externalEdge initialX initialY: externalEdge initialY initialZ: externalEdge initialZ leftFillIndex: (self registerFill: boundaryObject leftFill transform: nil) rightFillIndex: (self registerFill: boundaryObject rightFill transform: nil)! ! !BalloonEngine methodsFor: 'experimental' stamp: 'ar 11/12/1998 19:54'! registerLine: aLine transformation: aMatrix self primAddLineFrom: aLine start to: aLine end leftFillIndex: (self registerFill: aLine leftFill transform: aMatrix) rightFillIndex: (self registerFill: aLine rightFill transform: aMatrix) matrix: aMatrix! ! !BalloonEngine methodsFor: 'experimental' stamp: 'ar 11/11/1998 21:15'! subdivideExternalEdge: external from: boundaryObject | external2 | external2 _ external subdivide. external2 notNil ifTrue:[ self subdivideExternalEdge: external from: boundaryObject. self subdivideExternalEdge: external2 from: boundaryObject. ] ifFalse:[ self registerExternalEdge: external from: boundaryObject. ].! ! !BalloonEngine methodsFor: 'profiling' stamp: 'ar 11/11/1998 21:16'! doAddCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList matrix: aMatrix "Note: This method is for profiling the overhead of loading a compressed shape into the engine." ^self primAddCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList matrix: aMatrix! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonEngine class instanceVariableNames: ''! !BalloonEngine class methodsFor: 'instance creation' stamp: 'ar 10/9/1998 21:44'! new ^super new initialize! ! !BalloonEngine class methodsFor: 'class initialization' stamp: 'ar 11/11/1998 22:49'! initialize "BalloonEngine initialize" BufferCache _ WeakArray new: 1. Smalltalk garbageCollect. "Make the cache old" CacheProtect _ Semaphore forMutualExclusion. Times _ WordArray new: 10. Counts _ WordArray new: 10. BezierStats _ WordArray new: 4. Debug ifNil:[Debug _ false].! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 10/25/1998 17:37'! debug: aBoolean "BalloonEngine debug: true" "BalloonEngine debug: false" Debug _ aBoolean! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! doProfileStats: aBool "Note: On Macintosh systems turning on profiling can significantly degrade the performance of Balloon since we're using the high accuracy timer for measuring." "BalloonEngine doProfileStats: true" "BalloonEngine doProfileStats: false" ^false! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 10/30/1998 23:57'! printBezierStats "BalloonEngine printBezierStats" "BalloonEngine resetBezierStats" Transcript cr; nextPutAll:'Bezier statistics:'; crtab; print: (BezierStats at: 1); tab; nextPutAll:' non-monoton curves splitted'; crtab; print: (BezierStats at: 2); tab; nextPutAll:' curves splitted for numerical accuracy'; crtab; print: (BezierStats at: 3); tab; nextPutAll:' curves splitted to avoid integer overflow'; crtab; print: (BezierStats at: 4); tab; nextPutAll:' curves internally converted to lines'; endEntry.! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 10/28/1998 23:59'! printStat: time count: n string: aString Transcript cr; print: time; tab; nextPutAll:' mSecs -- '; print: n; tab; nextPutAll:' ops -- '; print: ((time asFloat / (n max: 1) asFloat) roundTo: 0.01); tab; nextPutAll: ' avg. mSecs/op -- '; nextPutAll: aString.! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 1/12/1999 10:52'! printStats "BalloonEngine doProfileStats: true" "BalloonEngine printStats" "BalloonEngine resetStats" Transcript cr; nextPutAll:'/************** BalloonEngine statistics ****************/'. self printStat: (Times at: 1) count: (Counts at: 1) string: 'Initialization'. self printStat: (Times at: 2) count: (Counts at: 2) string: 'Finish test'. self printStat: (Times at: 3) count: (Counts at: 3) string: 'Fetching/Adding GET entries'. self printStat: (Times at: 4) count: (Counts at: 4) string: 'Adding AET entries'. self printStat: (Times at: 5) count: (Counts at: 5) string: 'Fetching/Computing fills'. self printStat: (Times at: 6) count: (Counts at: 6) string: 'Merging fills'. self printStat: (Times at: 7) count: (Counts at: 7) string: 'Displaying span buffer'. self printStat: (Times at: 8) count: (Counts at: 8) string: 'Fetching/Updating AET entries'. self printStat: (Times at: 9) count: (Counts at: 9) string: 'Changing AET entries'. Transcript cr; print: Times sum; nextPutAll:' mSecs for all operations'. Transcript cr; print: Counts sum; nextPutAll: ' overall operations'. Transcript endEntry.! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 10/30/1998 23:57'! resetBezierStats BezierStats _ WordArray new: 4.! ! !BalloonEngine class methodsFor: 'accessing' stamp: 'ar 10/28/1998 23:38'! resetStats Times _ WordArray new: 10. Counts _ WordArray new: 10.! ! !BalloonEngine class methodsFor: 'private' stamp: 'ar 11/11/1998 22:50'! allocateOrRecycleBuffer: initialSize "Try to recycly a buffer. If this is not possibly, create a new one." | buffer | CacheProtect critical:[ buffer _ BufferCache at: 1. BufferCache at: 1 put: nil. ]. ^buffer ifNil:[BalloonBuffer new: initialSize]! ! !BalloonEngine class methodsFor: 'private' stamp: 'ar 5/28/2000 22:17'! primitiveSetBitBltPlugin: pluginName ^nil! ! !BalloonEngine class methodsFor: 'private' stamp: 'ar 11/11/1998 22:51'! recycleBuffer: balloonBuffer "Try to keep the buffer for later drawing operations." | buffer | CacheProtect critical:[ buffer _ BufferCache at: 1. (buffer isNil or:[buffer size < balloonBuffer size] ) ifTrue:[BufferCache at: 1 put: balloonBuffer]. ].! ! InterpreterPlugin subclass: #BalloonEngineBase instanceVariableNames: 'workBuffer objBuffer getBuffer aetBuffer spanBuffer engine formArray engineStopped geProfileTime dispatchedValue dispatchReturnValue objUsed doProfileStats copyBitsFn loadBBFn bbPluginName ' classVariableNames: 'EdgeInitTable EdgeStepTable FillTable WideLineFillTable WideLineWidthTable ' poolDictionaries: 'BalloonEngineConstants ' category: 'VMConstruction-Plugins'! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:34'! aaColorMaskGet ^workBuffer at: GWAAColorMask! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:34'! aaColorMaskPut: value ^workBuffer at: GWAAColorMask put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:34'! aaColorShiftGet ^workBuffer at: GWAAColorShift! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:34'! aaColorShiftPut: value ^workBuffer at: GWAAColorShift put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:34'! aaHalfPixelGet ^workBuffer at: GWAAHalfPixel! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaHalfPixelPut: value ^workBuffer at: GWAAHalfPixel put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaLevelGet ^workBuffer at: GWAALevel! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaLevelPut: value ^workBuffer at: GWAALevel put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaScanMaskGet ^workBuffer at: GWAAScanMask! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaScanMaskPut: value ^workBuffer at: GWAAScanMask put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:35'! aaShiftGet ^workBuffer at: GWAAShift! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 00:36'! aaShiftPut: value ^workBuffer at: GWAAShift put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! aetStartGet ^workBuffer at: GWAETStart! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:29'! aetStartPut: value ^workBuffer at: GWAETStart put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! aetUsedGet ^workBuffer at: GWAETUsed! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:29'! aetUsedPut: value ^workBuffer at: GWAETUsed put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 00:43'! clearSpanBufferGet ^workBuffer at: GWClearSpanBuffer! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 00:44'! clearSpanBufferPut: value ^workBuffer at: GWClearSpanBuffer put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:46'! clipMaxXGet ^workBuffer at: GWClipMaxX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:46'! clipMaxXPut: value ^workBuffer at: GWClipMaxX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:46'! clipMaxYGet ^workBuffer at: GWClipMaxY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:46'! clipMaxYPut: value ^workBuffer at: GWClipMaxY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:46'! clipMinXGet ^workBuffer at: GWClipMinX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:47'! clipMinXPut: value ^workBuffer at: GWClipMinX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:47'! clipMinYGet ^workBuffer at: GWClipMinY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 18:47'! clipMinYPut: value ^workBuffer at: GWClipMinY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:36'! colorTransform self returnTypeC:'float *'. ^self cCoerce: workBuffer + GWColorTransform to:'float *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! currentYGet ^workBuffer at: GWCurrentY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 21:27'! currentYPut: value ^workBuffer at: GWCurrentY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 20:28'! currentZGet ^workBuffer at: GWCurrentZ! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 20:29'! currentZPut: value ^workBuffer at: GWCurrentZ put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 16:23'! destOffsetXGet ^workBuffer at: GWDestOffsetX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 16:24'! destOffsetXPut: value ^workBuffer at: GWDestOffsetX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 16:23'! destOffsetYGet ^workBuffer at: GWDestOffsetY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 16:24'! destOffsetYPut: value ^workBuffer at: GWDestOffsetY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:36'! edgeTransform self returnTypeC:'float *'. ^self cCoerce: workBuffer + GWEdgeTransform to:'float *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillMaxXGet ^workBuffer at: GWFillMaxX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:29'! fillMaxXPut: value ^workBuffer at: GWFillMaxX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillMaxYGet ^workBuffer at: GWFillMaxY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! fillMaxYPut: value ^workBuffer at: GWFillMaxY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillMinXGet ^workBuffer at: GWFillMinX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! fillMinXPut: value ^workBuffer at: GWFillMinX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillMinYGet ^workBuffer at: GWFillMinY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! fillMinYPut: value ^workBuffer at: GWFillMinY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillOffsetXGet ^workBuffer at: GWFillOffsetX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! fillOffsetXPut: value ^workBuffer at: GWFillOffsetX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! fillOffsetYGet ^workBuffer at: GWFillOffsetY! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! fillOffsetYPut: value ^workBuffer at: GWFillOffsetY put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 17:08'! firstPointListGet ^workBuffer at: GWPointListFirst! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/29/1998 17:08'! firstPointListPut: value ^workBuffer at: GWPointListFirst put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! getStartGet ^workBuffer at: GWGETStart! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:30'! getStartPut: value ^workBuffer at: GWGETStart put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:27'! getUsedGet ^workBuffer at: GWGETUsed! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! getUsedPut: value ^workBuffer at: GWGETUsed put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:36'! hasColorTransformGet ^workBuffer at: GWHasColorTransform! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:36'! hasColorTransformPut: value ^workBuffer at: GWHasColorTransform put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:36'! hasEdgeTransformGet ^workBuffer at: GWHasEdgeTransform! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/24/1998 21:35'! hasEdgeTransformPut: value ^workBuffer at: GWHasEdgeTransform put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/9/1998 15:36'! incrementStat: statIndex by: value ^workBuffer at: statIndex put: (workBuffer at: statIndex) + value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! lastExportedEdgeGet ^workBuffer at: GWLastExportedEdge! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 20:11'! lastExportedEdgePut: value ^workBuffer at: GWLastExportedEdge put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 14:24'! lastExportedFillGet ^workBuffer at: GWLastExportedFill! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 14:24'! lastExportedFillPut: value ^workBuffer at: GWLastExportedFill put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:51'! lastExportedLeftXGet ^workBuffer at: GWLastExportedLeftX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:51'! lastExportedLeftXPut: value ^workBuffer at: GWLastExportedLeftX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:51'! lastExportedRightXGet ^workBuffer at: GWLastExportedRightX! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:51'! lastExportedRightXPut: value ^workBuffer at: GWLastExportedRightX put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! magicNumberGet ^workBuffer at: GWMagicIndex! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:39'! magicNumberPut: value ^workBuffer at: GWMagicIndex put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/25/1998 00:20'! needsFlushGet ^workBuffer at: GWNeedsFlush! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/25/1998 00:20'! needsFlushPut: value ^workBuffer at: GWNeedsFlush put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! objStartGet ^workBuffer at: GWObjStart! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:32'! objStartPut: value ^workBuffer at: GWObjStart put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! objUsedGet ^workBuffer at: GWObjUsed! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:32'! objUsedPut: value ^workBuffer at: GWObjUsed put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:33'! point1Get self returnTypeC:'int *'. ^self cCoerce: workBuffer + GWPoint1 to:'int *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:34'! point2Get self returnTypeC:'int *'. ^self cCoerce: workBuffer + GWPoint2 to:'int *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/28/1998 16:34'! point3Get self returnTypeC:'int *'. ^self cCoerce: workBuffer + GWPoint3 to:'int *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 11/1/1998 03:13'! point4Get self returnTypeC:'int *'. ^self cCoerce: workBuffer + GWPoint4 to:'int *'! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! spanEndAAGet ^workBuffer at: GWSpanEndAA! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! spanEndAAPut: value ^workBuffer at: GWSpanEndAA put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! spanEndGet ^workBuffer at: GWSpanEnd! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! spanEndPut: value ^workBuffer at: GWSpanEnd put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! spanSizeGet ^workBuffer at: GWSpanSize! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! spanSizePut: value ^workBuffer at: GWSpanSize put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! spanStartGet ^workBuffer at: GWSpanStart! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! spanStartPut: value ^workBuffer at: GWSpanStart put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! stateGet ^workBuffer at: GWState! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! statePut: value ^workBuffer at: GWState put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! stopReasonGet ^workBuffer at: GWStopReason! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:31'! stopReasonPut: value ^workBuffer at: GWStopReason put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:28'! wbSizeGet ^workBuffer at: GWSize! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:37'! wbSizePut: value ^workBuffer at: GWSize put: value! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 23:29'! wbTopGet ^workBuffer at: GWBufferTop! ! !BalloonEngineBase methodsFor: 'accessing state' stamp: 'ar 10/27/1998 18:32'! wbTopPut: value ^workBuffer at: GWBufferTop put: value! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:22'! obj: object at: index ^objBuffer at: object + index! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:22'! obj: object at: index put: value ^objBuffer at: object + index put: value! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:02'! objectHeaderOf: obj ^self makeUnsignedFrom:(self obj: obj at: GEObjectType)! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectIndexOf: obj ^self obj: obj at: GEObjectIndex! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectIndexOf: obj put: value ^self obj: obj at: GEObjectIndex put: value! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectLengthOf: obj ^self obj: obj at: GEObjectLength! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectLengthOf: obj put: value ^self obj: obj at: GEObjectLength put: value! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectTypeOf: obj ^(self makeUnsignedFrom:(self obj: obj at: GEObjectType)) bitAnd: GEPrimitiveTypeMask! ! !BalloonEngineBase methodsFor: 'accessing objects' stamp: 'ar 11/24/1998 22:03'! objectTypeOf: obj put: value ^self obj: obj at: GEObjectType put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/9/1998 15:35'! edgeFillsInvalidate: edge ^self objectTypeOf: edge put: ((self objectTypeOf: edge) bitOr: GEEdgeFillsInvalid)! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/9/1998 15:35'! edgeFillsValidate: edge ^self objectTypeOf: edge put: ((self objectTypeOf: edge) bitAnd: GEEdgeFillsInvalid bitInvert32)! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:04'! edgeLeftFillOf: edge ^self obj: edge at: GEFillIndexLeft! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:04'! edgeLeftFillOf: edge put: value ^self obj: edge at: GEFillIndexLeft put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:04'! edgeNumLinesOf: edge ^self obj: edge at: GENumLines! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:04'! edgeNumLinesOf: edge put: value ^self obj: edge at: GENumLines put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:05'! edgeRightFillOf: edge ^self obj: edge at: GEFillIndexRight! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:05'! edgeRightFillOf: edge put: value ^self obj: edge at: GEFillIndexRight put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/9/1998 15:35'! edgeTypeOf: edge "Return the edge type (e.g., witout the wide edge flag)" ^(self objectTypeOf: edge) >> 1! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:05'! edgeXValueOf: edge ^self obj: edge at: GEXValue! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:05'! edgeXValueOf: edge put: value ^self obj: edge at: GEXValue put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:05'! edgeYValueOf: edge ^self obj: edge at: GEYValue! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:06'! edgeYValueOf: edge put: value ^self obj: edge at: GEYValue put: value! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:06'! edgeZValueOf: edge ^self obj: edge at: GEZValue! ! !BalloonEngineBase methodsFor: 'accessing edges' stamp: 'ar 11/24/1998 22:06'! edgeZValueOf: edge put: value ^self obj: edge at: GEZValue put: value! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 10/31/1998 00:43'! wbStackClear self wbTopPut: self wbSizeGet.! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 11/9/1998 15:34'! wbStackPop: nItems self wbTopPut: self wbTopGet + nItems.! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 10/30/1998 17:16'! wbStackPush: nItems (self allocateStackEntry: nItems) ifFalse:[^false]. self wbTopPut: self wbTopGet - nItems. ^true! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 10/30/1998 17:17'! wbStackSize ^self wbSizeGet - self wbTopGet! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 11/9/1998 15:34'! wbStackValue: index ^workBuffer at: self wbTopGet + index! ! !BalloonEngineBase methodsFor: 'accessing stack' stamp: 'ar 11/9/1998 15:34'! wbStackValue: index put: value ^workBuffer at: self wbTopGet + index put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/7/1998 22:25'! fillTypeOf: fill ^((self objectTypeOf: fill) bitAnd: GEPrimitiveFillMask) >> 8! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:08'! stackFillDepth: index ^self wbStackValue: index+1! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:08'! stackFillDepth: index put: value ^self wbStackValue: index+1 put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 14:31'! stackFillEntryLength ^3! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 14:35'! stackFillRightX: index ^self wbStackValue: index+2! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 14:35'! stackFillRightX: index put: value ^self wbStackValue: index+2 put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:10'! stackFillSize ^self wbStackSize! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:09'! stackFillValue: index ^self wbStackValue: index! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:09'! stackFillValue: index put: value ^self wbStackValue: index put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:49'! topDepth self stackFillSize = 0 ifTrue:[^-1] ifFalse:[^self topFillDepth].! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:28'! topFill self stackFillSize = 0 ifTrue:[^0] ifFalse:[^self topFillValue].! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:27'! topFillDepth ^self stackFillDepth: self stackFillSize - self stackFillEntryLength! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:27'! topFillDepthPut: value ^self stackFillDepth: self stackFillSize - self stackFillEntryLength put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 14:36'! topFillRightX ^self stackFillRightX: self stackFillSize - self stackFillEntryLength! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 14:36'! topFillRightXPut: value ^self stackFillRightX: self stackFillSize - self stackFillEntryLength put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:27'! topFillValue ^self stackFillValue: self stackFillSize - self stackFillEntryLength! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 10/30/1998 19:27'! topFillValuePut: value ^self stackFillValue: self stackFillSize - self stackFillEntryLength put: value! ! !BalloonEngineBase methodsFor: 'accessing fills' stamp: 'ar 11/25/1998 15:19'! topRightX self stackFillSize = 0 ifTrue:[^999999999] ifFalse:[^self topFillRightX].! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/24/1998 20:05'! loadArrayTransformFrom: transformOop into: destPtr length: n "Load a transformation from the given array." | value | self inline: false. self var: #destPtr declareC:'float *destPtr'. 0 to: n-1 do:[:i| value _ interpreterProxy fetchPointer: i ofObject: transformOop. ((interpreterProxy isIntegerObject: value) or:[interpreterProxy isFloatObject: value]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isIntegerObject: value) ifTrue:[destPtr at: i put: (self cCoerce: (interpreterProxy integerValueOf: value) asFloat to:'float')] ifFalse:[destPtr at: i put: (self cCoerce: (interpreterProxy floatValueOf: value) to: 'float')]. ].! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/25/1998 21:04'! loadColorTransformFrom: transformOop "Load a 2x3 transformation matrix from the given oop. Return true if the matrix is not nil, false otherwise" | okay transform | self var: #transform declareC:'float *transform'. transform _ self colorTransform. self hasColorTransformPut: 0. okay _ self loadTransformFrom: transformOop into: transform length: 8. okay ifFalse:[^false]. self hasColorTransformPut: 1. "Scale transform to be in 0-256 range" transform at: 1 put: (transform at: 1) * (self cCoerce: 256.0 to:'float'). transform at: 3 put: (transform at: 3) * (self cCoerce: 256.0 to:'float'). transform at: 5 put: (transform at: 5) * (self cCoerce: 256.0 to:'float'). transform at: 7 put: (transform at: 7) * (self cCoerce: 256.0 to:'float'). ^okay! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/11/1998 22:21'! loadEdgeStateFrom: edgeOop | edge | self inline: false. edge _ self lastExportedEdgeGet. (interpreterProxy slotSizeOf: edgeOop) < ETBalloonEdgeDataSize ifTrue:[^interpreterProxy primitiveFail]. self edgeXValueOf: edge put: (interpreterProxy fetchInteger: ETXValueIndex ofObject: edgeOop). self edgeYValueOf: edge put: (interpreterProxy fetchInteger: ETYValueIndex ofObject: edgeOop). self edgeZValueOf: edge put: (interpreterProxy fetchInteger: ETZValueIndex ofObject: edgeOop). self edgeNumLinesOf: edge put: (interpreterProxy fetchInteger: ETLinesIndex ofObject: edgeOop). ^edge! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/24/1998 21:33'! loadEdgeTransformFrom: transformOop "Load a 2x3 transformation matrix from the given oop. Return true if the matrix is not nil, false otherwise" | transform okay | self inline: false. self var: #transform declareC:'float *transform'. self hasEdgeTransformPut: 0. transform _ self edgeTransform. okay _ self loadTransformFrom: transformOop into: transform length: 6. interpreterProxy failed ifTrue:[^nil]. okay ifFalse:[^false]. self hasEdgeTransformPut: 1. "Add the fill offset to the matrix" transform at: 2 put: (self cCoerce: (transform at: 2) + self destOffsetXGet asFloat to:'float'). transform at: 5 put: (self cCoerce: (transform at: 5) + self destOffsetYGet asFloat to:'float'). ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/25/1998 17:26'! loadFormsFrom: arrayOop "Check all the forms from arrayOop." | formOop bmBits bmBitsSize bmWidth bmHeight bmDepth ppw bmRaster | (interpreterProxy fetchClassOf: arrayOop) == interpreterProxy classArray ifFalse:[^false]. formArray _ arrayOop. 0 to: (interpreterProxy slotSizeOf: formArray) - 1 do:[:i| formOop _ interpreterProxy fetchPointer: i ofObject: formArray. (interpreterProxy isIntegerObject: formOop) ifTrue:[^false]. (interpreterProxy isPointers: formOop) ifFalse:[^false]. (interpreterProxy slotSizeOf: formOop) < 5 ifTrue:[^false]. bmBits _ interpreterProxy fetchPointer: 0 ofObject: formOop. (interpreterProxy fetchClassOf: bmBits) == interpreterProxy classBitmap ifFalse:[^false]. bmBitsSize _ interpreterProxy slotSizeOf: bmBits. bmWidth _ interpreterProxy fetchInteger: 1 ofObject: formOop. bmHeight _ interpreterProxy fetchInteger: 2 ofObject: formOop. bmDepth _ interpreterProxy fetchInteger: 3 ofObject: formOop. interpreterProxy failed ifTrue:[^false]. (bmWidth >= 0 and:[bmHeight >= 0]) ifFalse:[^false]. ppw _ 32 // bmDepth. bmRaster _ bmWidth + (ppw-1) // ppw. bmBitsSize = (bmRaster * bmHeight) ifFalse:[^false]. ]. ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 10/27/1998 21:24'! loadPoint: pointArray from: pointOop "Load the contents of pointOop into pointArray" | value | self inline: false. self var: #pointArray declareC:'int *pointArray'. (interpreterProxy fetchClassOf: pointOop) = interpreterProxy classPoint ifFalse:[^interpreterProxy primitiveFail]. value _ interpreterProxy fetchPointer: 0 ofObject: pointOop. ((interpreterProxy isIntegerObject: value) or:[interpreterProxy isFloatObject: value]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isIntegerObject: value) ifTrue:[pointArray at: 0 put: (interpreterProxy integerValueOf: value)] ifFalse:[pointArray at: 0 put: (interpreterProxy floatValueOf: value) asInteger]. value _ interpreterProxy fetchPointer: 1 ofObject: pointOop. ((interpreterProxy isIntegerObject: value) or:[interpreterProxy isFloatObject: value]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isIntegerObject: value) ifTrue:[pointArray at: 1 put: (interpreterProxy integerValueOf: value)] ifFalse:[pointArray at: 1 put: (interpreterProxy floatValueOf: value) asInteger]. ! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 10/28/1998 00:46'! loadSpanBufferFrom: spanOop "Load the span buffer from the given oop." self inline: false. (interpreterProxy fetchClassOf: spanOop) = (interpreterProxy classBitmap) ifFalse:[^false]. spanBuffer _ interpreterProxy firstIndexableField: spanOop. "Leave last entry unused to avoid complications" self spanSizePut: (interpreterProxy slotSizeOf: spanOop) - 1. ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/25/1998 23:22'! loadTransformFrom: transformOop into: destPtr length: n "Load a transformation from transformOop into the float array defined by destPtr. The transformation is assumed to be either an array or a FloatArray of length n." self inline: false. self var: #destPtr declareC:'float *destPtr'. transformOop = interpreterProxy nilObject ifTrue:[^false]. (interpreterProxy isIntegerObject: transformOop) ifTrue:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: transformOop) = n ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: transformOop) ifTrue:[self loadWordTransformFrom: transformOop into: destPtr length: n] ifFalse:[self loadArrayTransformFrom: transformOop into: destPtr length: n]. ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/24/1998 20:03'! loadWordTransformFrom: transformOop into: destPtr length: n "Load a float array transformation from the given oop" | srcPtr | self inline: false. self var: #srcPtr declareC:'float *srcPtr'. self var: #destPtr declareC:'float *destPtr'. srcPtr _ self cCoerce: (interpreterProxy firstIndexableField: transformOop) to: 'float *'. 0 to: n-1 do:[:i| destPtr at: i put: (srcPtr at: i)].! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 10/28/1998 19:37'! loadWorkBufferFrom: wbOop "Load the working buffer from the given oop" self inline: false. (interpreterProxy isIntegerObject: wbOop) ifTrue:[^false]. (interpreterProxy isWords: wbOop) ifFalse:[^false]. (interpreterProxy slotSizeOf: wbOop) < GWMinimalSize ifTrue:[^false]. workBuffer _ interpreterProxy firstIndexableField: wbOop. self magicNumberGet = GWMagicNumber ifFalse:[^false]. "Sanity checks" (self wbSizeGet = (interpreterProxy slotSizeOf: wbOop)) ifFalse:[^false]. self objStartGet = GWHeaderSize ifFalse:[^false]. "Load buffers" objBuffer _ workBuffer + self objStartGet. getBuffer _ objBuffer + self objUsedGet. aetBuffer _ getBuffer + self getUsedGet. "Make sure we don't exceed the work buffer" GWHeaderSize + self objUsedGet + self getUsedGet + self aetUsedGet > self wbSizeGet ifTrue:[^false]. ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 11/25/1998 00:36'! quickLoadEngineFrom: engineOop "Load the minimal required state from the engineOop, e.g., just the work buffer." self inline: false. interpreterProxy failed ifTrue:[^false]. (interpreterProxy isIntegerObject: engineOop) ifTrue:[^false]. (interpreterProxy isPointers: engineOop) ifFalse:[^false]. (interpreterProxy slotSizeOf: engineOop) < BEBalloonEngineSize ifTrue:[^false]. engine _ engineOop. (self loadWorkBufferFrom: (interpreterProxy fetchPointer: BEWorkBufferIndex ofObject: engineOop)) ifFalse:[^false]. self stopReasonPut: 0. objUsed _ self objUsedGet. engineStopped _ false. ^true! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 10/28/1998 21:06'! quickLoadEngineFrom: oop requiredState: requiredState self inline: false. (self quickLoadEngineFrom: oop) ifFalse:[^false]. self stateGet = requiredState ifTrue:[^true]. self stopReasonPut: GErrorBadState. ^false! ! !BalloonEngineBase methodsFor: 'loading state' stamp: 'ar 10/31/1998 17:23'! quickLoadEngineFrom: oop requiredState: requiredState or: alternativeState self inline: false. (self quickLoadEngineFrom: oop) ifFalse:[^false]. self stateGet = requiredState ifTrue:[^true]. self stateGet = alternativeState ifTrue:[^true]. self stopReasonPut: GErrorBadState. ^false! ! !BalloonEngineBase methodsFor: 'storing state' stamp: 'ar 11/11/1998 22:21'! storeEdgeStateFrom: edge into: edgeOop self inline: false. (interpreterProxy slotSizeOf: edgeOop) < ETBalloonEdgeDataSize ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy storeInteger: ETIndexIndex ofObject: edgeOop withValue: (self objectIndexOf: edge). interpreterProxy storeInteger: ETXValueIndex ofObject: edgeOop withValue: (self edgeXValueOf: edge). interpreterProxy storeInteger: ETYValueIndex ofObject: edgeOop withValue: (self currentYGet). interpreterProxy storeInteger: ETZValueIndex ofObject: edgeOop withValue: (self edgeZValueOf: edge). interpreterProxy storeInteger: ETLinesIndex ofObject: edgeOop withValue: (self edgeNumLinesOf: edge). self lastExportedEdgePut: edge.! ! !BalloonEngineBase methodsFor: 'storing state' stamp: 'ar 11/25/1998 00:36'! storeEngineStateInto: oop self objUsedPut: objUsed.! ! !BalloonEngineBase methodsFor: 'storing state' stamp: 'ar 11/11/1998 22:24'! storeFillStateInto: fillOop | fillIndex leftX rightX | self inline: false. fillIndex _ self lastExportedFillGet. leftX _ self lastExportedLeftXGet. rightX _ self lastExportedRightXGet. (interpreterProxy slotSizeOf: fillOop) < FTBalloonFillDataSize ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy storeInteger: FTIndexIndex ofObject: fillOop withValue: (self objectIndexOf: fillIndex). interpreterProxy storeInteger: FTMinXIndex ofObject: fillOop withValue: leftX. interpreterProxy storeInteger: FTMaxXIndex ofObject: fillOop withValue: rightX. interpreterProxy storeInteger: FTYValueIndex ofObject: fillOop withValue: self currentYGet.! ! !BalloonEngineBase methodsFor: 'storing state' stamp: 'ar 11/9/1998 15:34'! storeStopStateIntoEdge: edgeOop fill: fillOop | reason edge | reason _ self stopReasonGet. reason = GErrorGETEntry ifTrue:[ edge _ getBuffer at: self getStartGet. self storeEdgeStateFrom: edge into: edgeOop. self getStartPut: self getStartGet + 1. ]. reason = GErrorFillEntry ifTrue:[ self storeFillStateInto: fillOop. ]. reason = GErrorAETEntry ifTrue:[ edge _ aetBuffer at: self aetStartGet. self storeEdgeStateFrom: edge into: edgeOop. "Do not advance to the next aet entry yet" "self aetStartPut: self aetStartGet + 1." ].! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/9/1998 15:34'! areEdgeFillsValid: edge ^((self objectHeaderOf: edge) bitAnd: GEEdgeFillsInvalid) = 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 10/31/1998 17:06'! finishedProcessing "Return true if processing is finished" ^self stateGet = GEStateCompleted! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/24/1998 19:39'! hasColorTransform ^self hasColorTransformGet ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/24/1998 19:38'! hasEdgeTransform ^self hasEdgeTransformGet ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 10/29/1998 19:36'! isEdge: edge | type | type _ self objectTypeOf: edge. type > GEPrimitiveEdgeMask ifTrue:[^false]. ^((self objectTypeOf: edge) bitAnd: GEPrimitiveEdgeMask) ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/7/1998 21:28'! isFill: fill ^(self isFillColor: fill) or:[self isRealFill: fill]! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 10/29/1998 19:31'! isFillColor: fill ^((self makeUnsignedFrom: fill) bitAnd: 16rFF000000) ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/25/1998 00:43'! isObject: obj ^obj >= 0 and:[obj < objUsed]! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/7/1998 21:28'! isRealFill: fill ^((self objectTypeOf: fill) bitAnd: GEPrimitiveFillMask) ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 10/31/1998 23:12'! isStackEntry: entry ^entry >= self wbTopGet and:[entry < self wbSizeGet]! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 10/30/1998 17:38'! isStackIndex: index ^index >= 0 and:[index < self wbStackSize]! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/9/1998 15:36'! isWide: object ^((self objectTypeOf: object) bitAnd: GEPrimitiveWide) ~= 0! ! !BalloonEngineBase methodsFor: 'testing' stamp: 'ar 11/25/1998 00:21'! needsFlush ^self needsFlushGet ~= 0! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:08'! primitiveGetAALevel self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. interpreterProxy pushInteger: self aaLevelGet.! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:10'! primitiveGetClipRect | rectOop pointOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. rectOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isPointers: rectOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: rectOop) < 2 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pushRemappableOop: rectOop. pointOop _ interpreterProxy makePointwithxValue: self clipMinXGet yValue: self clipMinYGet. rectOop _ interpreterProxy popRemappableOop. interpreterProxy storePointer: 0 ofObject: rectOop withValue: pointOop. interpreterProxy pushRemappableOop: rectOop. pointOop _ interpreterProxy makePointwithxValue: self clipMaxXGet yValue: self clipMaxYGet. rectOop _ interpreterProxy popRemappableOop. interpreterProxy storePointer: 1 ofObject: rectOop withValue: pointOop. interpreterProxy pop: 2. interpreterProxy push: rectOop.! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:13'! primitiveGetCounts | statOop stats | self export: true. self inline: false. self var: #stats declareC:'int *stats'. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. statOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: statOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: statOop) < 9 ifTrue:[^interpreterProxy primitiveFail]. stats _ interpreterProxy firstIndexableField: statOop. stats at: 0 put: (stats at: 0) + (workBuffer at: GWCountInitializing). stats at: 1 put: (stats at: 1) + (workBuffer at: GWCountFinishTest). stats at: 2 put: (stats at: 2) + (workBuffer at: GWCountNextGETEntry). stats at: 3 put: (stats at: 3) + (workBuffer at: GWCountAddAETEntry). stats at: 4 put: (stats at: 4) + (workBuffer at: GWCountNextFillEntry). stats at: 5 put: (stats at: 5) + (workBuffer at: GWCountMergeFill). stats at: 6 put: (stats at: 6) + (workBuffer at: GWCountDisplaySpan). stats at: 7 put: (stats at: 7) + (workBuffer at: GWCountNextAETEntry). stats at: 8 put: (stats at: 8) + (workBuffer at: GWCountChangeAETEntry). interpreterProxy pop: 1. "Leave rcvr on stack"! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:14'! primitiveGetDepth self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. interpreterProxy pushInteger: self currentZGet.! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:10'! primitiveGetFailureReason "Return the reason why the last operation failed." self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. "Note -- don't call loadEngineFrom here because this will override the stopReason with Zero" (interpreterProxy isIntegerObject: engine) ifTrue:[^false]. (interpreterProxy isPointers: engine) ifFalse:[^false]. (interpreterProxy slotSizeOf: engine) < BEBalloonEngineSize ifTrue:[^false]. (self loadWorkBufferFrom: (interpreterProxy fetchPointer: BEWorkBufferIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. interpreterProxy pushInteger: self stopReasonGet.! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:11'! primitiveGetOffset | pointOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. pointOop _ interpreterProxy makePointwithxValue: self destOffsetXGet yValue: self destOffsetYGet. interpreterProxy pop: 1. interpreterProxy push: pointOop.! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:07'! primitiveGetTimes | statOop stats | self export: true. self inline: false. self var: #stats declareC:'int *stats'. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. statOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: statOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: statOop) < 9 ifTrue:[^interpreterProxy primitiveFail]. stats _ interpreterProxy firstIndexableField: statOop. stats at: 0 put: (stats at: 0) + (workBuffer at: GWTimeInitializing). stats at: 1 put: (stats at: 1) + (workBuffer at: GWTimeFinishTest). stats at: 2 put: (stats at: 2) + (workBuffer at: GWTimeNextGETEntry). stats at: 3 put: (stats at: 3) + (workBuffer at: GWTimeAddAETEntry). stats at: 4 put: (stats at: 4) + (workBuffer at: GWTimeNextFillEntry). stats at: 5 put: (stats at: 5) + (workBuffer at: GWTimeMergeFill). stats at: 6 put: (stats at: 6) + (workBuffer at: GWTimeDisplaySpan). stats at: 7 put: (stats at: 7) + (workBuffer at: GWTimeNextAETEntry). stats at: 8 put: (stats at: 8) + (workBuffer at: GWTimeChangeAETEntry). interpreterProxy pop: 1. "Leave rcvr on stack"! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:12'! primitiveNeedsFlush | needFlush | self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. needFlush _ self needsFlush. self storeEngineStateInto: engine. interpreterProxy pop: 1. interpreterProxy pushBool: needFlush. ! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:14'! primitiveNeedsFlushPut | needFlush | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. needFlush _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. needFlush _ interpreterProxy booleanValueOf: needFlush. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. needFlush == true ifTrue:[self needsFlushPut: 1] ifFalse:[self needsFlushPut: 0]. self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:12'! primitiveSetAALevel | level | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. level _ interpreterProxy stackIntegerValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self setAALevel: level. self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leace rcvr on stack"! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/16/2000 20:03'! primitiveSetBitBltPlugin "Primitive. Set the BitBlt plugin to use." | pluginName length ptr needReload | self export: true. self var: #ptr declareC:'char *ptr'. pluginName _ interpreterProxy stackValue: 0. "Must be string to work" (interpreterProxy isBytes: pluginName) ifFalse:[^interpreterProxy primitiveFail]. length _ interpreterProxy byteSizeOf: pluginName. length >= 256 ifTrue:[^interpreterProxy primitiveFail]. ptr _ interpreterProxy firstIndexableField: pluginName. needReload _ false. 0 to: length-1 do:[:i| "Compare and store the plugin to be used" (bbPluginName at: i) = (ptr at: i) ifFalse:[ bbPluginName at: i put: (ptr at: i). needReload _ true]]. (bbPluginName at: length) = 0 ifFalse:[ bbPluginName at: length put: 0. needReload _ true]. needReload ifTrue:[ self initialiseModule ifFalse:[^interpreterProxy primitiveFail]]. interpreterProxy pop: 1. "Return receiver"! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:05'! primitiveSetClipRect | rectOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. rectOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isPointers: rectOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: rectOop) < 2 ifTrue:[^interpreterProxy primitiveFail]. self loadPoint: self point1Get from: (interpreterProxy fetchPointer: 0 ofObject: rectOop). self loadPoint: self point2Get from: (interpreterProxy fetchPointer: 1 ofObject: rectOop). interpreterProxy failed ifFalse:[ self clipMinXPut: (self point1Get at: 0). self clipMinYPut: (self point1Get at: 1). self clipMaxXPut: (self point2Get at: 0). self clipMaxYPut: (self point2Get at: 1). self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:11'! primitiveSetColorTransform | transformOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. transformOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self loadColorTransformFrom: transformOop. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:06'! primitiveSetDepth | depth | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. depth _ interpreterProxy stackIntegerValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self currentZPut: depth. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:14'! primitiveSetEdgeTransform | transformOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. transformOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self loadEdgeTransformFrom: transformOop. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-access' stamp: 'ar 5/11/2000 23:13'! primitiveSetOffset | pointOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. pointOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy fetchClassOf: pointOop) = interpreterProxy classPoint ifFalse:[^interpreterProxy primitiveFail]. self loadPoint: self point1Get from: pointOop. interpreterProxy failed ifFalse:[ self destOffsetXPut: (self point1Get at: 0). self destOffsetYPut: (self point1Get at: 1). self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:58'! primitiveAddActiveEdgeEntry "Note: No need to load either bitBlt or spanBuffer" | edgeOop edge | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. edgeOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateWaitingForEdge) ifFalse:[^interpreterProxy primitiveFail]. edge _ self loadEdgeStateFrom: edgeOop. interpreterProxy failed ifTrue:[^nil]. (self needAvailableSpace: 1) ifFalse:[^interpreterProxy primitiveFail]. (self edgeNumLinesOf: edge) > 0 ifTrue:[ self insertEdgeIntoAET: edge. ]. engineStopped ifTrue:[^interpreterProxy primitiveFail]. self statePut: GEStateAddingFromGET. "Back to adding edges from GET" self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" doProfileStats ifTrue:[ self incrementStat: GWCountAddAETEntry by: 1. self incrementStat: GWTimeAddAETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveChangedActiveEdgeEntry "Note: No need to load either bitBlt or spanBuffer" | edgeOop edge | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. edgeOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateWaitingChange) ifFalse:[^interpreterProxy primitiveFail]. edge _ self loadEdgeStateFrom: edgeOop. interpreterProxy failed ifTrue:[^nil]. (self edgeNumLinesOf: edge) = 0 ifTrue:[ self removeFirstAETEntry] ifFalse:[ self resortFirstAETEntry. self aetStartPut: self aetStartGet + 1]. self statePut: GEStateUpdateEdges. "Back to updating edges" self storeEngineStateInto: engine. interpreterProxy pop: 1. "Leave rcvr on stack" doProfileStats ifTrue:[ self incrementStat: GWCountChangeAETEntry by: 1. self incrementStat: GWTimeChangeAETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveDisplaySpanBuffer "Note: Must load bitBlt and spanBuffer" self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateBlitBuffer) ifFalse:[^interpreterProxy primitiveFail]. "Load span buffer and bitBlt" (self loadSpanBufferFrom: (interpreterProxy fetchPointer: BESpanIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. (self loadBitBltFrom: (interpreterProxy fetchPointer: BEBitBltIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. (self currentYGet bitAnd: self aaScanMaskGet) = self aaScanMaskGet ifTrue:[ self displaySpanBufferAt: self currentYGet. self postDisplayAction. ]. self finishedProcessing ifFalse:[ self aetStartPut: 0. self currentYPut: self currentYGet + 1. self statePut: GEStateUpdateEdges]. self storeEngineStateInto: engine. doProfileStats ifTrue:[ self incrementStat: GWCountDisplaySpan by: 1. self incrementStat: GWTimeDisplaySpan by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveInitializeProcessing "Note: No need to load bitBlt but must load spanBuffer" self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Load span buffer for clear operation" (self loadSpanBufferFrom: (interpreterProxy fetchPointer: BESpanIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. self initializeGETProcessing. engineStopped ifTrue:[^interpreterProxy primitiveFail]. self statePut: GEStateAddingFromGET. "Initialized" interpreterProxy failed ifFalse:[self storeEngineStateInto: engine]. doProfileStats ifTrue:[ self incrementStat: GWCountInitializing by: 1. self incrementStat: GWTimeInitializing by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveMergeFillFrom "Note: No need to load bitBlt but must load spanBuffer" | fillOop bitsOop value | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. fillOop _ interpreterProxy stackObjectValue: 0. bitsOop _ interpreterProxy stackObjectValue: 1. engine _ interpreterProxy stackObjectValue: 2. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateWaitingForFill) ifFalse:[^interpreterProxy primitiveFail]. "Load span buffer for merging the fill" (self loadSpanBufferFrom: (interpreterProxy fetchPointer: BESpanIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. "Check bitmap" (interpreterProxy fetchClassOf: bitsOop) = interpreterProxy classBitmap ifFalse:[^interpreterProxy primitiveFail]. "Check fillOop" (interpreterProxy slotSizeOf: fillOop) < FTBalloonFillDataSize ifTrue:[^interpreterProxy primitiveFail]. "Check if this was the fill we have exported" value _ interpreterProxy fetchInteger: FTIndexIndex ofObject: fillOop. (self objectIndexOf: self lastExportedFillGet) = value ifFalse:[^interpreterProxy primitiveFail]. value _ interpreterProxy fetchInteger: FTMinXIndex ofObject: fillOop. self lastExportedLeftXGet = value ifFalse:[^interpreterProxy primitiveFail]. value _ interpreterProxy fetchInteger: FTMaxXIndex ofObject: fillOop. self lastExportedRightXGet = value ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: bitsOop) < (self lastExportedRightXGet - self lastExportedLeftXGet) ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifTrue:[^nil]. self fillBitmapSpan: (interpreterProxy firstIndexableField: bitsOop) from: self lastExportedLeftXGet to: self lastExportedRightXGet. self statePut: GEStateScanningAET. "Back to scanning AET" self storeEngineStateInto: engine. interpreterProxy pop: 2. "Leave rcvr on stack" doProfileStats ifTrue:[ self incrementStat: GWCountMergeFill by: 1. self incrementStat: GWTimeMergeFill by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveNextActiveEdgeEntry "Note: No need to load either bitBlt or spanBuffer" | edgeOop hasEdge edge | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. edgeOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUpdateEdges or: GEStateCompleted) ifFalse:[^interpreterProxy primitiveFail]. hasEdge _ false. self stateGet = GEStateCompleted ifFalse:[ hasEdge _ self findNextExternalUpdateFromAET. hasEdge ifTrue:[ edge _ aetBuffer at: self aetStartGet. self storeEdgeStateFrom: edge into: edgeOop. "Do not advance to the next aet entry yet" "self aetStartPut: self aetStartGet + 1." self statePut: GEStateWaitingChange. "Wait for changed edge" ] ifFalse:[self statePut: GEStateAddingFromGET]. "Start over" ]. interpreterProxy failed ifTrue:[^nil]. self storeEngineStateInto: engine. interpreterProxy pop: 2. interpreterProxy pushBool: hasEdge not. doProfileStats ifTrue:[ self incrementStat: GWCountNextAETEntry by: 1. self incrementStat: GWTimeNextAETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 14:59'! primitiveNextFillEntry "Note: No need to load bitBlt but must load spanBuffer" | fillOop hasFill | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. fillOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateScanningAET) ifFalse:[^interpreterProxy primitiveFail]. "Load span buffer for internal handling of fills" (self loadSpanBufferFrom: (interpreterProxy fetchPointer: BESpanIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. (self loadFormsFrom: (interpreterProxy fetchPointer: BEFormsIndex ofObject: engine)) ifFalse:[^interpreterProxy primitiveFail]. "Check if we have to clear the span buffer before proceeding" (self clearSpanBufferGet = 0) ifFalse:[ (self currentYGet bitAnd: self aaScanMaskGet) = 0 ifTrue:[self clearSpanBuffer]. self clearSpanBufferPut: 0]. hasFill _ self findNextExternalFillFromAET. engineStopped ifTrue:[^interpreterProxy primitiveFail]. hasFill ifTrue:[self storeFillStateInto: fillOop]. interpreterProxy failed ifFalse:[ hasFill ifTrue:[ self statePut: GEStateWaitingForFill] ifFalse:[ self wbStackClear. self spanEndAAPut: 0. self statePut: GEStateBlitBuffer]. self storeEngineStateInto: engine. interpreterProxy pop: 2. interpreterProxy pushBool: hasFill not. doProfileStats ifTrue:[ self incrementStat: GWCountNextFillEntry by: 1. self incrementStat: GWTimeNextFillEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ].! ! !BalloonEngineBase methodsFor: 'primitives-incremental' stamp: 'ar 5/13/2000 15:00'! primitiveNextGlobalEdgeEntry "Note: No need to load either bitBlt or spanBuffer" | edgeOop hasEdge edge | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. edgeOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateAddingFromGET) ifFalse:[^interpreterProxy primitiveFail]. hasEdge _ self findNextExternalEntryFromGET. hasEdge ifTrue:[ edge _ getBuffer at: self getStartGet. self storeEdgeStateFrom: edge into: edgeOop. self getStartPut: self getStartGet + 1]. interpreterProxy failed ifTrue:[^nil]. hasEdge ifTrue:[ self statePut: GEStateWaitingForEdge] "Wait for adding edges" ifFalse:[ "Start scanning the AET" self statePut: GEStateScanningAET. self clearSpanBufferPut: 1. "Clear span buffer at next entry" self aetStartPut: 0. self wbStackClear]. self storeEngineStateInto: engine. interpreterProxy pop: 2. interpreterProxy pushBool: hasEdge not. doProfileStats ifTrue:[ self incrementStat: GWCountNextGETEntry by: 1. self incrementStat: GWTimeNextGETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 5/12/2000 16:40'! loadRenderingState "Load the entire state from the interpreter for the rendering primitives" | edgeOop fillOop state | self inline: false. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. fillOop _ interpreterProxy stackObjectValue: 0. edgeOop _ interpreterProxy stackObjectValue: 1. engine _ interpreterProxy stackObjectValue: 2. interpreterProxy failed ifTrue:[^false]. (self quickLoadEngineFrom: engine) ifFalse:[^false]. "Load span buffer and bitBlt" (self loadSpanBufferFrom: (interpreterProxy fetchPointer: BESpanIndex ofObject: engine)) ifFalse:[^false]. (self loadBitBltFrom: (interpreterProxy fetchPointer: BEBitBltIndex ofObject: engine)) ifFalse:[^false]. (self loadFormsFrom: (interpreterProxy fetchPointer: BEFormsIndex ofObject: engine)) ifFalse:[^false]. "Check edgeOop and fillOop" (interpreterProxy slotSizeOf: edgeOop) < ETBalloonEdgeDataSize ifTrue:[^false]. (interpreterProxy slotSizeOf: fillOop) < FTBalloonFillDataSize ifTrue:[^false]. "Note: Rendering can only take place if we're not in one of the intermediate (e.g., external) states." state _ self stateGet. (state = GEStateWaitingForEdge or:[ state = GEStateWaitingForFill or:[ state = GEStateWaitingChange]]) ifTrue:[^false]. ^true! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 5/11/2000 23:08'! primitiveRenderImage "Start/Proceed rendering the entire image" self export: true. self inline: false. self loadRenderingState ifFalse:[^interpreterProxy primitiveFail]. self proceedRenderingScanline. "Finish this scan line" engineStopped ifTrue:[^self storeRenderingState]. self proceedRenderingImage. "And go on as usual" self storeRenderingState.! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 5/11/2000 23:07'! primitiveRenderScanline "Start rendering the entire image" self export: true. self inline: false. self loadRenderingState ifFalse:[^interpreterProxy primitiveFail]. self proceedRenderingScanline. "Finish the current scan line" self storeRenderingState.! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 5/13/2000 15:00'! proceedRenderingImage "This is the main rendering entry" | external | self inline: false. [self finishedProcessing] whileFalse:[ doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. external _ self findNextExternalEntryFromGET. doProfileStats ifTrue:[ self incrementStat: GWCountNextGETEntry by: 1. self incrementStat: GWTimeNextGETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateAddingFromGET]. external ifTrue:[ self statePut: GEStateWaitingForEdge. ^self stopBecauseOf: GErrorGETEntry. ]. self aetStartPut: 0. self wbStackClear. self clearSpanBufferPut: 1. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. (self clearSpanBufferGet ~= 0 and:[(self currentYGet bitAnd: self aaScanMaskGet) = 0]) ifTrue:[self clearSpanBuffer]. self clearSpanBufferPut: 0. external _ self findNextExternalFillFromAET. doProfileStats ifTrue:[ self incrementStat: GWCountNextFillEntry by: 1. self incrementStat: GWTimeNextFillEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateScanningAET]. external ifTrue:[ self statePut: GEStateWaitingForFill. ^self stopBecauseOf: GErrorFillEntry. ]. self wbStackClear. self spanEndAAPut: 0. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. (self currentYGet bitAnd: self aaScanMaskGet) = self aaScanMaskGet ifTrue:[ self displaySpanBufferAt: self currentYGet. self postDisplayAction. ]. doProfileStats ifTrue:[ self incrementStat: GWCountDisplaySpan by: 1. self incrementStat: GWTimeDisplaySpan by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateBlitBuffer]. self finishedProcessing ifTrue:[^0]. self aetStartPut: 0. self currentYPut: self currentYGet + 1. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. external _ self findNextExternalUpdateFromAET. doProfileStats ifTrue:[ self incrementStat: GWCountNextAETEntry by: 1. self incrementStat: GWTimeNextAETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateUpdateEdges]. external ifTrue:[ self statePut: GEStateWaitingChange. ^self stopBecauseOf: GErrorAETEntry. ]. ].! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 5/13/2000 15:00'! proceedRenderingScanline "Proceed rendering the current scan line. This method may be called after some Smalltalk code has been executed inbetween." "This is the main rendering entry" | external state | self inline: false. state _ self stateGet. state = GEStateUnlocked ifTrue:[ self initializeGETProcessing. engineStopped ifTrue:[^0]. state _ GEStateAddingFromGET. ]. state = GEStateAddingFromGET ifTrue:[ doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. external _ self findNextExternalEntryFromGET. doProfileStats ifTrue:[ self incrementStat: GWCountNextGETEntry by: 1. self incrementStat: GWTimeNextGETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateAddingFromGET]. external ifTrue:[ self statePut: GEStateWaitingForEdge. ^self stopBecauseOf: GErrorGETEntry. ]. self aetStartPut: 0. self wbStackClear. self clearSpanBufferPut: 1. state _ GEStateScanningAET. ]. state = GEStateScanningAET ifTrue:[ doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. (self clearSpanBufferGet ~= 0 and:[(self currentYGet bitAnd: self aaScanMaskGet) = 0]) ifTrue:[self clearSpanBuffer]. self clearSpanBufferPut: 0. external _ self findNextExternalFillFromAET. doProfileStats ifTrue:[ self incrementStat: GWCountNextFillEntry by: 1. self incrementStat: GWTimeNextFillEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateScanningAET]. external ifTrue:[ self statePut: GEStateWaitingForFill. ^self stopBecauseOf: GErrorFillEntry. ]. state _ GEStateBlitBuffer. self wbStackClear. self spanEndAAPut: 0. ]. state = GEStateBlitBuffer ifTrue:[ doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. (self currentYGet bitAnd: self aaScanMaskGet) = self aaScanMaskGet ifTrue:[ self displaySpanBufferAt: self currentYGet. self postDisplayAction. ]. doProfileStats ifTrue:[ self incrementStat: GWCountDisplaySpan by: 1. self incrementStat: GWTimeDisplaySpan by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateBlitBuffer]. self finishedProcessing ifTrue:[^0]. state _ GEStateUpdateEdges. self aetStartPut: 0. self currentYPut: self currentYGet + 1. ]. state = GEStateUpdateEdges ifTrue:[ doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. external _ self findNextExternalUpdateFromAET. doProfileStats ifTrue:[ self incrementStat: GWCountNextAETEntry by: 1. self incrementStat: GWTimeNextAETEntry by: (interpreterProxy ioMicroMSecs - geProfileTime)]. engineStopped ifTrue:[^self statePut: GEStateUpdateEdges]. external ifTrue:[ self statePut: GEStateWaitingChange. ^self stopBecauseOf: GErrorAETEntry. ]. self statePut: GEStateAddingFromGET. ].! ! !BalloonEngineBase methodsFor: 'primitives-rendering' stamp: 'ar 10/31/1998 23:54'! storeRenderingState self inline: false. interpreterProxy failed ifTrue:[^nil]. engineStopped ifTrue:[ "Check the stop reason and store the required information" self storeStopStateIntoEdge: (interpreterProxy stackObjectValue: 1) fill: (interpreterProxy stackObjectValue: 0). ]. self storeEngineStateInto: engine. interpreterProxy pop: 3. interpreterProxy pushInteger: self stopReasonGet.! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:06'! primitiveAbortProcessing self export: true. self inline: false. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. self statePut: GEStateCompleted. self storeEngineStateInto: engine.! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:12'! primitiveCopyBuffer | buf1 buf2 diff src dst | self export: true. self inline: false. self var: #src declareC:'int * src'. self var: #dst declareC:'int * dst'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. buf2 _ interpreterProxy stackObjectValue: 0. buf1 _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. "Make sure the old buffer is properly initialized" (self loadWorkBufferFrom: buf1) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the buffers are of the same type" (interpreterProxy fetchClassOf: buf1) = (interpreterProxy fetchClassOf: buf2) ifFalse:[^interpreterProxy primitiveFail]. "Make sure buf2 is at least of the size of buf1" diff _ (interpreterProxy slotSizeOf: buf2) - (interpreterProxy slotSizeOf: buf1). diff < 0 ifTrue:[^interpreterProxy primitiveFail]. "Okay - ready for copying. First of all just copy the contents up to wbTop" src _ workBuffer. dst _ interpreterProxy firstIndexableField: buf2. 0 to: self wbTopGet-1 do:[:i| dst at: i put: (src at: i). ]. "Adjust wbSize and wbTop in the new buffer" dst at: GWBufferTop put: self wbTopGet + diff. dst at: GWSize put: self wbSizeGet + diff. "Now copy the entries from wbTop to wbSize" src _ src + self wbTopGet. dst _ dst + self wbTopGet + diff. 0 to: (self wbSizeGet - self wbTopGet - 1) do:[:i| dst at: i put: (src at: i). ]. "Okay, done. Check the new buffer by loading the state from it" (self loadWorkBufferFrom: buf2) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 2. "Leave rcvr on stack" ! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:05'! primitiveDoProfileStats "Turn on/off profiling. Return the old value of the flag." | oldValue newValue | self inline: false. self export: true. oldValue _ doProfileStats. newValue _ interpreterProxy stackObjectValue: 0. newValue _ interpreterProxy booleanValueOf: newValue. interpreterProxy failed ifFalse:[ doProfileStats _ newValue. interpreterProxy pop: 2. "Pop rcvr, arg" interpreterProxy pushBool: oldValue. ].! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/13/2000 14:59'! primitiveFinishedProcessing | finished | self export: true. self inline: false. doProfileStats ifTrue:[geProfileTime _ interpreterProxy ioMicroMSecs]. interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. engine _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. finished _ self finishedProcessing. self storeEngineStateInto: engine. interpreterProxy pop: 1. interpreterProxy pushBool: finished. doProfileStats ifTrue:[ self incrementStat: GWCountFinishTest by: 1. self incrementStat: GWTimeFinishTest by: (interpreterProxy ioMicroMSecs - geProfileTime)]. ! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:09'! primitiveInitializeBuffer | wbOop size | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. wbOop _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy isWords: wbOop) ifFalse:[^interpreterProxy primitiveFail]. (size _ interpreterProxy slotSizeOf: wbOop) < GWMinimalSize ifTrue:[^interpreterProxy primitiveFail]. workBuffer _ interpreterProxy firstIndexableField: wbOop. objBuffer _ workBuffer + GWHeaderSize. self magicNumberPut: GWMagicNumber. self wbSizePut: size. self wbTopPut: size. self statePut: GEStateUnlocked. self objStartPut: GWHeaderSize. self objUsedPut: 4. "Dummy fill object" self objectTypeOf: 0 put: GEPrimitiveFill. self objectLengthOf: 0 put: 4. self objectIndexOf: 0 put: 0. self getStartPut: 0. self getUsedPut: 0. self aetStartPut: 0. self aetUsedPut: 0. self stopReasonPut: 0. self needsFlushPut: 0. self clipMinXPut: 0. self clipMaxXPut: 0. self clipMinYPut: 0. self clipMaxYPut: 0. self currentZPut: 0. self resetGraphicsEngineStats. self initEdgeTransform. self initColorTransform. interpreterProxy pop: 2. interpreterProxy push: wbOop.! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:08'! primitiveRegisterExternalEdge | rightFillIndex leftFillIndex initialZ initialY initialX index edge | self export: true. self inline: false. interpreterProxy methodArgumentCount = 6 ifFalse:[^interpreterProxy primitiveFail]. rightFillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). leftFillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 1). initialZ _ interpreterProxy stackIntegerValue: 2. initialY _ interpreterProxy stackIntegerValue: 3. initialX _ interpreterProxy stackIntegerValue: 4. index _ interpreterProxy stackIntegerValue: 5. engine _ interpreterProxy stackObjectValue: 6. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. (self allocateObjEntry: GEBaseEdgeSize) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the fills are okay" (self isFillOkay: leftFillIndex) ifFalse:[^interpreterProxy primitiveFail]. (self isFillOkay: rightFillIndex) ifFalse:[^interpreterProxy primitiveFail]. edge _ objUsed. objUsed _ edge + GEBaseEdgeSize. "Install type and length" self objectTypeOf: edge put: GEPrimitiveEdge. self objectLengthOf: edge put: GEBaseEdgeSize. self objectIndexOf: edge put: index. "Install remaining stuff" self edgeXValueOf: edge put: initialX. self edgeYValueOf: edge put: initialY. self edgeZValueOf: edge put: initialZ. self edgeLeftFillOf: edge put: (self transformColor: leftFillIndex). self edgeRightFillOf: edge put: (self transformColor: rightFillIndex). engineStopped ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 6. "Leave rcvr on stack" ].! ! !BalloonEngineBase methodsFor: 'primitives-other' stamp: 'ar 5/11/2000 23:14'! primitiveRegisterExternalFill | index fill | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. index _ interpreterProxy stackIntegerValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Note: We *must* not allocate any fill with index 0" fill _ 0. [fill = 0] whileTrue:[ (self allocateObjEntry: GEBaseEdgeSize) ifFalse:[^interpreterProxy primitiveFail]. fill _ objUsed. objUsed _ fill + GEBaseFillSize. "Install type and length" self objectTypeOf: fill put: GEPrimitiveFill. self objectLengthOf: fill put: GEBaseFillSize. self objectIndexOf: fill put: index. ]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 2. interpreterProxy pushInteger: fill. ].! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/29/1998 18:37'! allocateAETEntry: nSlots "Allocate n slots in the active edge table" ^self needAvailableSpace: nSlots! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/28/1998 21:06'! allocateGETEntry: nSlots "Allocate n slots in the global edge table" | srcIndex dstIndex | self inline: false. "First allocate nSlots in the AET" (self allocateAETEntry: nSlots) ifFalse:[^false]. self aetUsedGet = 0 ifFalse:["Then move the AET upwards" srcIndex _ self aetUsedGet. dstIndex _ self aetUsedGet + nSlots. 1 to: self aetUsedGet do:[:i| aetBuffer at: (dstIndex _ dstIndex - 1) put: (aetBuffer at: (srcIndex _ srcIndex - 1))]. ]. aetBuffer _ aetBuffer + nSlots. ^true! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/28/1998 21:16'! allocateObjEntry: nSlots "Allocate n slots in the object buffer" | srcIndex dstIndex | self inline: false. "First allocate nSlots in the GET" (self allocateGETEntry: nSlots) ifFalse:[^false]. self getUsedGet = 0 ifFalse:["Then move the GET upwards" srcIndex _ self getUsedGet. dstIndex _ self getUsedGet + nSlots. 1 to: self getUsedGet do:[:i| getBuffer at: (dstIndex _ dstIndex - 1) put: (getBuffer at: (srcIndex _ srcIndex - 1))]. ]. getBuffer _ getBuffer + nSlots. ^true! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/29/1998 18:37'! allocateStackEntry: nSlots "AET and Stack allocation are symmetric" ^self needAvailableSpace: nSlots! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/30/1998 19:24'! allocateStackFillEntry ^self wbStackPush: self stackFillEntryLength! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 10/30/1998 19:24'! freeStackFillEntry self wbStackPop: self stackFillEntryLength.! ! !BalloonEngineBase methodsFor: 'allocating' stamp: 'ar 11/25/1998 02:19'! needAvailableSpace: nSlots "Check if we have n slots available" GWHeaderSize + objUsed + self getUsedGet + self aetUsedGet + nSlots > self wbTopGet ifTrue:[ self stopBecauseOf: GErrorNoMoreSpace. ^false ]. ^true! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 11/1/1998 01:07'! addEdgeToGET: edge self inline: false. (self allocateGETEntry: 1) ifFalse:[^0]. "Install edge in the GET" getBuffer at: self getUsedGet put: edge. self getUsedPut: self getUsedGet + 1.! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 11/25/1998 00:41'! createGlobalEdgeTable "Create the global edge table" | object end | self inline: false. object _ 0. end _ objUsed. [object < end] whileTrue:[ "Note: addEdgeToGET: may fail on insufficient space but that's not a problem here" (self isEdge: object) ifTrue:[ "Check if the edge starts below fillMaxY." (self edgeYValueOf: object) >= self fillMaxYGet ifFalse:[ self checkedAddEdgeToGET: object. ]. ]. object _ object + (self objectLengthOf: object). ].! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 11/9/1998 15:36'! findNextExternalEntryFromGET "Check the global edge table for any entries that cannot be handled by the engine itself. If there are any, return true. Otherwise, initialize the the edge and add it to the AET" | yValue edge type | yValue _ self currentYGet. "As long as we have entries in the GET" [self getStartGet < self getUsedGet] whileTrue:[ edge _ getBuffer at: self getStartGet. (self edgeYValueOf: edge) > yValue ifTrue:[^false]. "No more edges to add" type _ self objectTypeOf: edge. (type bitAnd: GEPrimitiveWideMask) = GEPrimitiveEdge ifTrue:[^true]. "This is an external edge" "Note: We must make sure not to do anything with the edge if there is not enough room in the AET" (self needAvailableSpace: 1) ifFalse:[^false]. "No more room" "Process the edge in the engine itself" self dispatchOn: type in: EdgeInitTable. "Insert the edge into the AET" self insertEdgeIntoAET: edge. self getStartPut: self getStartGet + 1. ]. "No entries in GET" ^false! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 10/28/1998 21:07'! getSorts: edge1 before: edge2 "Return true if the edge at index i should sort before the edge at index j." | diff | self inline: false. edge1 = edge2 ifTrue:[^true]. "First, sort by Y" diff _ (self edgeYValueOf: edge1) - (self edgeYValueOf: edge2). diff = 0 ifFalse:[^diff < 0]. "Then, by X" diff _ (self edgeXValueOf: edge1) - (self edgeXValueOf: edge2). ^diff < 0! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 11/25/1998 00:41'! initializeGETProcessing "Initialization stuff that needs to be done before any processing can take place." self inline: false. "Make sure aaLevel is initialized" self setAALevel: self aaLevelGet. self clipMinXGet < 0 ifTrue:[self clipMinXPut: 0]. self clipMaxXGet > self spanSizeGet ifTrue:[self clipMaxXPut: self spanSizeGet]. "Convert clipRect to aaLevel" self fillMinXPut: self clipMinXGet << self aaShiftGet. self fillMinYPut: self clipMinYGet << self aaShiftGet. self fillMaxXPut: self clipMaxXGet << self aaShiftGet. self fillMaxYPut: self clipMaxYGet << self aaShiftGet. "Reset GET and AET" self getUsedPut: 0. self aetUsedPut: 0. getBuffer _ aetBuffer _ objBuffer + objUsed. "Create the global edge table" self createGlobalEdgeTable. engineStopped ifTrue:[^nil]. self getUsedGet = 0 ifTrue:[ "Nothing to do" self currentYPut: self fillMaxYGet. ^0]. "Sort entries in the GET" self sortGlobalEdgeTable. "Find the first y value to be processed" self currentYPut: (self edgeYValueOf: (getBuffer at: 0)). self currentYGet < self fillMinYGet ifTrue:[self currentYPut: self fillMinYGet]. "Load and clear the span buffer" self spanStartPut: 0. self spanEndPut: (self spanSizeGet << self aaShiftGet) - 1. self clearSpanBuffer. "@@: Is this really necessary?!!"! ! !BalloonEngineBase methodsFor: 'GET processing' stamp: 'ar 10/27/1998 17:55'! quickSortGlobalEdgeTable: array from: i to: j "Sort elements i through j of self to be nondescending according to sortBlock." "Note: The original loop has been heavily re-written for C translation" | di dij dj tt ij k l n tmp again before | self var: #array declareC:'int *array'. self inline: false. "The prefix d means the data at that index." (n _ j + 1 - i) <= 1 ifTrue: [^0]. "Nothing to sort." "Sort di,dj." di _ array at: i. dj _ array at: j. before _ self getSorts: di before: dj. "i.e., should di precede dj?" before ifFalse:[ tmp _ array at: i. array at: i put: (array at: j). array at: j put: tmp. tt _ di. di _ dj. dj _ tt]. n <= 2 ifTrue:[^0]. "More than two elements." ij _ (i + j) // 2. "ij is the midpoint of i and j." dij _ array at: ij. "Sort di,dij,dj. Make dij be their median." before _ (self getSorts: di before: dij). "i.e. should di precede dij?" before ifTrue:[ before _ (self getSorts: dij before: dj). "i.e., should dij precede dj?" before ifFalse:["i.e., should dij precede dj?" tmp _ array at: j. array at: j put: (array at: ij). array at: ij put: tmp. dij _ dj] ] ifFalse:[ "i.e. di should come after dij" tmp _ array at: i. array at: i put: (array at: ij). array at: ij put: tmp. dij _ di]. n <= 3 ifTrue:[^0]. "More than three elements." "Find k>i and l= depth ifTrue:[^rightEdge]. self aetStartPut: self aetStartGet + 1. ]. ^nil! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 11/25/1998 23:21'! findNextExternalFillFromAET "Scan the active edge table. If there is any fill that cannot be handled by the engine itself, return true. Otherwise handle the fills and return false." | leftEdge rightEdge leftX rightX | "self currentYGet >= 680 ifTrue:[ self printAET. self halt. ]." self inline: false. leftX _ rightX _ self fillMaxXGet. [self aetStartGet < self aetUsedGet] whileTrue:[ leftEdge _ rightEdge _ aetBuffer at: self aetStartGet. "TODO: We should check if leftX from last operation is greater than leftX from next edge. Currently, we rely here on spanEndAA from the span buffer fill." leftX _ rightX _ self edgeXValueOf: leftEdge. leftX >= self fillMaxXGet ifTrue:[^false]. "Nothing more visible" self quickRemoveInvalidFillsAt: leftX. "Check if we need to draw the edge" (self isWide: leftEdge) ifTrue:[ self toggleWideFillOf: leftEdge. "leftX _ rightX _ self drawWideEdge: leftEdge from: leftX." ]. (self areEdgeFillsValid: leftEdge) ifTrue:[ self toggleFillsOf: leftEdge. "Adjust the fills" engineStopped ifTrue:[^false]. ]. self aetStartPut: self aetStartGet + 1. self aetStartGet < self aetUsedGet ifTrue:[ rightEdge _ aetBuffer at: self aetStartGet. rightX _ self edgeXValueOf: rightEdge. rightX >= self fillMinXGet ifTrue:["This is the visible portion" self fillAllFrom: leftX to: rightX. "Fetch the currently active fill" "fill _ self makeUnsignedFrom: self topFill. fill = 0 ifFalse:[self fillSpan: fill from: leftX to: rightX max: self topRightX]" ]. ]. ]. "Note: Due to pre-clipping we may have to draw remaining stuff with the last fill" rightX < self fillMaxXGet ifTrue:[ self fillAllFrom: rightX to: self fillMaxXGet. "fill _ self makeUnsignedFrom: self topFill. fill = 0 ifFalse:[self fillSpan: fill from: rightX to: self fillMaxXGet max: self topRightX]." ]. ^false! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 11/9/1998 15:36'! findNextExternalUpdateFromAET "Check the active edge table for any entries that cannot be handled by the engine itself. If there are any, return true. Otherwise, step the the edge to the next y value." | edge count type | self inline: false. [self aetStartGet < self aetUsedGet] whileTrue:[ edge _ aetBuffer at: self aetStartGet. count _ (self edgeNumLinesOf: edge) - 1. count = 0 ifTrue:[ "Edge at end -- remove it" self removeFirstAETEntry ] ifFalse:[ "Store remaining lines back" self edgeNumLinesOf: edge put: count. type _ self objectTypeOf: edge. (type bitAnd: GEPrimitiveWideMask) = GEPrimitiveEdge ifTrue:[^true]. "This is an external edge" self dispatchOn: type in: EdgeStepTable. self resortFirstAETEntry. self aetStartPut: self aetStartGet+1. ]. ]. ^false! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 21:07'! indexForInsertingIntoAET: edge "Find insertion point for the given edge in the AET" | initialX index | self inline: false. initialX _ self edgeXValueOf: edge. index _ 0. [index < self aetUsedGet and:[ (self edgeXValueOf: (aetBuffer at: index)) < initialX]] whileTrue:[index _ index + 1]. [index < self aetUsedGet and:[ (self edgeXValueOf: (aetBuffer at: index)) = initialX and:[ (self getSorts: (aetBuffer at: index) before: edge)]]] whileTrue:[index _ index + 1]. ^index! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 19:52'! insertEdgeIntoAET: edge "Insert the edge with the given index from the global edge table into the active edge table. The edge has already been stepped to the initial yValue -- thus remainingLines and rasterX are both set." | index | self inline: false. "Check for the number of lines remaining" (self edgeNumLinesOf: edge) <= 0 ifTrue:[^nil]. "Nothing to do" "Find insertion point" index _ self indexForInsertingIntoAET: edge. "And insert edge" self insertToAET: edge beforeIndex: index.! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 21:07'! insertToAET: edge beforeIndex: index "Insert the given edge into the AET." | i | self inline: false. "Make sure we have space in the AET" (self allocateAETEntry: 1) ifFalse:[^nil]. "Insufficient space in AET" i _ self aetUsedGet-1. [i < index] whileFalse:[ aetBuffer at: i+1 put: (aetBuffer at: i). i _ i - 1. ]. aetBuffer at: index put: edge. self aetUsedPut: self aetUsedGet + 1.! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 01:39'! moveAETEntryFrom: index edge: edge x: xValue "The entry at index is not in the right position of the AET. Move it to the left until the position is okay." | newIndex | self inline: false. newIndex _ index. [newIndex > 0 and:[(self edgeXValueOf: (aetBuffer at: newIndex-1)) > xValue]] whileTrue:[ aetBuffer at: newIndex put: (aetBuffer at: newIndex-1). newIndex _ newIndex - 1]. aetBuffer at: newIndex put: edge.! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 21:07'! removeFirstAETEntry | index | self inline: false. index _ self aetStartGet. self aetUsedPut: self aetUsedGet - 1. [index < self aetUsedGet] whileTrue:[ aetBuffer at: index put: (aetBuffer at: index + 1). index _ index + 1. ].! ! !BalloonEngineBase methodsFor: 'AET processing' stamp: 'ar 10/28/1998 21:07'! resortFirstAETEntry | edge xValue leftEdge | self inline: false. self aetStartGet = 0 ifTrue:[^nil]. "Nothing to resort" edge _ aetBuffer at: self aetStartGet. xValue _ self edgeXValueOf: edge. leftEdge _ aetBuffer at: (self aetStartGet - 1). (self edgeXValueOf: leftEdge) <= xValue ifTrue:[^nil]. "Okay" self moveAETEntryFrom: self aetStartGet edge: edge x: xValue.! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/24/1998 22:42'! fillSorts: fillEntry1 before: fillEntry2 "Return true if fillEntry1 should be drawn before fillEntry2" | diff | self inline: false. "First check the depth value" diff _ (self stackFillDepth: fillEntry1) - (self stackFillDepth: fillEntry2). diff = 0 ifFalse:[^diff > 0]. "See the class comment for aetScanningProblems" ^(self cCoerce: (self makeUnsignedFrom: (self stackFillValue: fillEntry1)) to:'unsigned') < (self cCoerce: (self makeUnsignedFrom: (self stackFillValue: fillEntry2)) to: 'unsigned')! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 15:47'! findStackFill: fillIndex depth: depth | index | index _ 0. [index < self stackFillSize and:[ (self stackFillValue: index) ~= fillIndex or:[ (self stackFillDepth: index) ~= depth]]] whileTrue:[index _ index + self stackFillEntryLength]. index >= self stackFillSize ifTrue:[^-1] ifFalse:[^index]. ! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 15:48'! hideFill: fillIndex depth: depth "Make the fill style with the given index invisible" | index newTopIndex newTop newDepth newRightX | self inline: false. index _ self findStackFill: fillIndex depth: depth. index = -1 ifTrue:[^false]. index = 0 ifTrue:[ self freeStackFillEntry. ^true]. "Fill is visible - replace it with the last entry on the stack" self stackFillValue: index put: (self stackFillValue: 0). self stackFillDepth: index put: (self stackFillDepth: 0). self stackFillRightX: index put: (self stackFillRightX: 0). self freeStackFillEntry. (self stackFillSize <= self stackFillEntryLength) ifTrue:[^true]. "Done" "Find the new top fill" newTopIndex _ 0. index _ self stackFillEntryLength. [index < self stackFillSize] whileTrue:[ (self fillSorts: index before: newTopIndex) ifTrue:[newTopIndex _ index]. index _ index + self stackFillEntryLength. ]. (newTopIndex + self stackFillEntryLength = self stackFillSize) ifTrue:[^true]. "Top fill not changed" newTop _ self stackFillValue: newTopIndex. self stackFillValue: newTopIndex put: self topFillValue. self topFillValuePut: newTop. newDepth _ self stackFillDepth: newTopIndex. self stackFillDepth: newTopIndex put: self topFillDepth. self topFillDepthPut: newDepth. newRightX _ self stackFillRightX: newTopIndex. self stackFillRightX: newTopIndex put: self topFillRightX. self topFillRightXPut: newRightX. ^true! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 15:16'! quickRemoveInvalidFillsAt: leftX "Remove any top fills if they have become invalid." self stackFillSize = 0 ifTrue:[^nil]. [self topRightX <= leftX] whileTrue:[ self hideFill: self topFill depth: self topDepth. self stackFillSize = 0 ifTrue:[^nil]. ].! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 14:38'! showFill: fillIndex depth: depth rightX: rightX self inline: false. (self allocateStackFillEntry) ifFalse:[^nil]. "Insufficient space" self stackFillValue: 0 put: fillIndex. self stackFillDepth: 0 put: depth. self stackFillRightX: 0 put: rightX. self stackFillSize = self stackFillEntryLength ifTrue:[^nil]. "No need to update" (self fillSorts: 0 before: self stackFillSize - self stackFillEntryLength) ifTrue:[ "New top fill" self stackFillValue: 0 put: self topFillValue. self stackFillDepth: 0 put: self topFillDepth. self stackFillRightX: 0 put: self topFillRightX. self topFillValuePut: fillIndex. self topFillDepthPut: depth. self topFillRightXPut: rightX. ].! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 14:38'! toggleFill: fillIndex depth: depth rightX: rightX "Make the fill style with the given index either visible or invisible" | hidden | self inline: false. self stackFillSize = 0 ifTrue:[ (self allocateStackFillEntry) ifTrue:[ self topFillValuePut: fillIndex. self topFillDepthPut: depth. self topFillRightXPut: rightX. ]. ] ifFalse:[ hidden _ self hideFill: fillIndex depth: depth. hidden ifFalse:[self showFill: fillIndex depth: depth rightX: rightX]. ].! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 15:19'! toggleFillsOf: edge | depth fillIndex | self inline: false. (self needAvailableSpace: self stackFillEntryLength * 2) ifFalse:[^nil]. "Make sure we have enough space left" depth _ (self edgeZValueOf: edge) << 1. fillIndex _ self edgeLeftFillOf: edge. fillIndex = 0 ifFalse:[self toggleFill: fillIndex depth: depth rightX: 999999999]. fillIndex _ self edgeRightFillOf: edge. fillIndex = 0 ifFalse:[self toggleFill: fillIndex depth: depth rightX: 999999999]. self quickRemoveInvalidFillsAt: (self edgeXValueOf: edge).! ! !BalloonEngineBase methodsFor: 'FILL processing' stamp: 'ar 11/25/1998 15:50'! toggleWideFillOf: edge | fill type lineWidth depth rightX index | self inline: false. type _ self edgeTypeOf: edge. dispatchedValue _ edge. self dispatchOn: type in: WideLineWidthTable. lineWidth _ dispatchReturnValue. self dispatchOn: type in: WideLineFillTable. fill _ dispatchReturnValue. fill = 0 ifTrue:[^nil]. (self needAvailableSpace: self stackFillEntryLength) ifFalse:[^nil]. "Make sure we have enough space left" depth _ (self edgeZValueOf: edge) << 1 + 1. "So lines sort before interior fills" rightX _ (self edgeXValueOf: edge) + lineWidth. index _ self findStackFill: fill depth: depth. index = -1 ifTrue:[ self showFill: fill depth: depth rightX: rightX. ] ifFalse:[ (self stackFillRightX: index) < rightX ifTrue:[self stackFillRightX: index put: rightX]. ]. self quickRemoveInvalidFillsAt: (self edgeXValueOf: edge).! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/9/1998 00:53'! aaFirstPixelFrom: leftX to: rightX "Common function to compute the first full pixel for AA drawing" | firstPixel | self inline: true. firstPixel _ (leftX + self aaLevelGet - 1) bitAnd: (self aaLevelGet - 1) bitInvert32. firstPixel > rightX ifTrue:[^rightX] ifFalse:[^firstPixel]! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/9/1998 00:53'! aaLastPixelFrom: leftX to: rightX "Common function to compute the last full pixel for AA drawing" self inline: true. ^(rightX - 1) bitAnd: (self aaLevelGet - 1) bitInvert32.! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/9/1998 00:50'! adjustAALevel "NOTE: This method is (hopefully) obsolete due to unrolling the fill loops to deal with full pixels." "Adjust the span buffers values by the appropriate color offset for anti-aliasing. We do this by replicating the top bits of each color in the lower bits. The idea is that we can scale each color value uniquely from 0 to 255 and thus fill the entire range of colors." | adjustShift adjustMask x0 x1 pixelValue | self inline: false. adjustShift _ 8 - self aaColorShiftGet. adjustMask _ self aaColorMaskGet bitInvert32. x0 _ self spanStartGet >> self aaShiftGet. x1 _ self spanEndGet >> self aaShiftGet. [x0 < x1] whileTrue:[ pixelValue _ spanBuffer at: x0. spanBuffer at: x0 put: (pixelValue bitOr: (pixelValue >> adjustShift bitAnd: adjustMask)). x0 _ x0 + 1].! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/14/1998 19:31'! clearSpanBuffer "Clear the current span buffer. The span buffer is only cleared in the area that has been used by the previous scan line." | x0 x1 | self inline: false. x0 _ self spanStartGet >> self aaShiftGet. x1 _ self spanEndGet >> self aaShiftGet + 1. x0 < 0 ifTrue:[x0 _ 0]. x1 > self spanSizeGet ifTrue:[x1 _ self spanSizeGet]. [x0 < x1] whileTrue:[ spanBuffer at: x0 put: 0. x0 _ x0 + 1]. self spanStartPut: self spanSizeGet. self spanEndPut: 0.! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 5/12/2000 16:42'! displaySpanBufferAt: y "Display the span buffer at the current scan line." | targetX0 targetX1 targetY | self inline: false. "self aaLevelGet > 1 ifTrue:[self adjustAALevel]." targetX0 _ self spanStartGet >> self aaShiftGet. targetX0 < self clipMinXGet ifTrue:[targetX0 _ self clipMinXGet]. targetX1 _ (self spanEndGet + self aaLevelGet - 1) >> self aaShiftGet. targetX1 > self clipMaxXGet ifTrue:[targetX1 _ self clipMaxXGet]. targetY _ y >> self aaShiftGet. (targetY < self clipMinYGet or:[targetY >= self clipMaxYGet or:[ targetX1 < self clipMinXGet or:[targetX0 >= self clipMaxXGet]]]) ifTrue:[^0]. self copyBitsFrom: targetX0 to: targetX1 at: targetY.! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/25/1998 02:34'! drawWideEdge: edge from: leftX "Draw the given edge starting from leftX with the edge's fill. Return the end value of the drawing operation." | rightX fill type lineWidth | self inline: false. "Not for the moment" type _ self edgeTypeOf: edge. dispatchedValue _ edge. self dispatchOn: type in: WideLineWidthTable. lineWidth _ dispatchReturnValue. self dispatchOn: type in: WideLineFillTable. fill _ self makeUnsignedFrom: dispatchReturnValue. fill = 0 ifTrue:[^leftX]. "Check if this line is only partially visible" "self assert:(self isFillColor: fill)." rightX _ leftX + lineWidth. self fillSpan: fill from: leftX to: rightX. ^rightX! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/25/1998 15:12'! fillAllFrom: leftX to: rightX "Fill the span buffer from leftX to rightX with the given fill." | fill startX stopX | self inline: true. fill _ self topFill. startX _ leftX. stopX _ self topRightX. [stopX < rightX] whileTrue:[ fill _ self makeUnsignedFrom: self topFill. fill = 0 ifFalse:[ (self fillSpan: fill from: startX to: stopX) ifTrue:[^true]]. self quickRemoveInvalidFillsAt: stopX. startX _ stopX. stopX _ self topRightX]. fill _ self makeUnsignedFrom: self topFill. fill = 0 ifFalse:[^self fillSpan: fill from: startX to: rightX]. ^false! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/9/1998 16:10'! fillBitmapSpan: bits from: leftX to: rightX "Fill the span buffer between leftEdge and rightEdge using the given bits. Note: We always start from zero - this avoids using huge bitmap buffers if the bitmap is to be displayed at the very far right hand side and also gives us a chance of using certain bitmaps (e.g., those with depth 32) directly." | x0 x1 x bitX colorMask colorShift baseShift fillValue | self inline: false. self var: #bits declareC:'int *bits'. x0 _ leftX. x1 _ rightX. bitX _ -1. "Hack for pre-increment" self aaLevelGet = 1 ifTrue:["Speedy version for no anti-aliasing" [x0 < x1] whileTrue:[ fillValue _ (self cCoerce: bits to: 'int *') at: (bitX _ bitX + 1). spanBuffer at: x0 put: fillValue. x0 _ x0 + 1. ]. ] ifFalse:["Generic version with anti-aliasing" colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. baseShift _ self aaShiftGet. [x0 < x1] whileTrue:[ x _ x0 >> baseShift. fillValue _ (self cCoerce: bits to: 'int *') at: (bitX _ bitX + 1). fillValue _ (fillValue bitAnd: colorMask) >> colorShift. spanBuffer at: x put: (spanBuffer at: x) + fillValue. x0 _ x0 + 1. ]. ]. x1 > self spanEndGet ifTrue:[self spanEndPut: x1]. x1 > self spanEndAAGet ifTrue:[self spanEndAAPut: x1].! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/8/1998 03:30'! fillColorSpan: pixelValue32 from: leftX to: rightX "Fill the span buffer between leftEdge and rightEdge with the given pixel value." | x0 x1 | self inline: true. "Use a unrolled version for anti-aliased fills..." self aaLevelGet = 1 ifFalse:[^self fillColorSpanAA: pixelValue32 x0: leftX x1: rightX]. x0 _ leftX. x1 _ rightX. "Unroll the inner loop four times, since we're only storing data." [x0 + 4 < x1] whileTrue:[ spanBuffer at: x0 put: pixelValue32. spanBuffer at: x0+1 put: pixelValue32. spanBuffer at: x0+2 put: pixelValue32. spanBuffer at: x0+3 put: pixelValue32. x0 _ x0+4. ]. [x0 < x1] whileTrue:[ spanBuffer at: x0 put: pixelValue32. x0 _ x0 + 1. ].! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/9/1998 00:52'! fillColorSpanAA: pixelValue32 x0: leftX x1: rightX "This is the inner loop for solid color fills with anti-aliasing. This loop has been unrolled for speed and quality into three parts: a) copy all pixels that fall into the first full pixel. b) copy aaLevel pixels between the first and the last full pixel c) copy all pixels that fall in the last full pixel" | colorMask baseShift x idx firstPixel lastPixel aaLevel pv32 | self inline: false. "Not now -- maybe later" "Compute the pixel boundaries." firstPixel _ self aaFirstPixelFrom: leftX to: rightX. lastPixel _ self aaLastPixelFrom: leftX to: rightX. aaLevel _ self aaLevelGet. baseShift _ self aaShiftGet. x _ leftX. "Part a: Deal with the first n sub-pixels" x < firstPixel ifTrue:[ pv32 _ (pixelValue32 bitAnd: self aaColorMaskGet) >> self aaColorShiftGet. [x < firstPixel] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + pv32. x _ x + 1. ]. ]. "Part b: Deal with the full pixels" x < lastPixel ifTrue:[ colorMask _ (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0. pv32 _ (pixelValue32 bitAnd: colorMask) >> self aaShiftGet. [x < lastPixel] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + pv32. x _ x + aaLevel. ]. ]. "Part c: Deal with the last n sub-pixels" x < rightX ifTrue:[ pv32 _ (pixelValue32 bitAnd: self aaColorMaskGet) >> self aaColorShiftGet. [x < rightX] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + pv32. x _ x + 1. ]. ].! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/15/1998 02:04'! fillSpan: fill from: leftX to: rightX "Fill the span buffer from leftX to rightX with the given fill. Clip before performing any operations. Return true if the fill must be handled by some Smalltalk code." | x0 x1 type | self inline: false. fill = 0 ifTrue:[^false]. "Nothing to do" "Start from spEnd - we must not paint pixels twice at a scan line" leftX < self spanEndAAGet ifTrue:[x0 _ self spanEndAAGet] ifFalse:[x0 _ leftX]. rightX > (self spanSizeGet << self aaShiftGet) ifTrue:[x1 _ (self spanSizeGet << self aaShiftGet)] ifFalse:[x1 _ rightX]. "Clip left and right values" x0 < self fillMinXGet ifTrue:[x0 _ self fillMinXGet]. x1 > self fillMaxXGet ifTrue:[x1 _ self fillMaxXGet]. "Adjust start and end values of span" x0 < self spanStartGet ifTrue:[self spanStartPut: x0]. x1 > self spanEndGet ifTrue:[self spanEndPut: x1]. x1 > self spanEndAAGet ifTrue:[self spanEndAAPut: x1]. x0 >= x1 ifTrue:[^false]. "Nothing to do" (self isFillColor: fill) ifTrue:[ self fillColorSpan: fill from: x0 to: x1. ] ifFalse:[ "Store the values for the dispatch" self lastExportedFillPut: fill. self lastExportedLeftXPut: x0. self lastExportedRightXPut: x1. type _ self fillTypeOf: fill. type <= 1 ifTrue:[^true]. self dispatchOn: type in: FillTable. ]. ^false! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/25/1998 14:57'! fillSpan: fill from: leftX to: rightX max: maxRightX "Fill the span buffer from leftX to rightX with the given fill. Clip before performing any operations. Return true if the fill must be handled by some Smalltalk code." | x0 x1 type | self inline: false. fill = 0 ifTrue:[^false]. "Nothing to do" "Start from spEnd - we must not paint pixels twice at a scan line" leftX < self spanEndAAGet ifTrue:[x0 _ self spanEndAAGet] ifFalse:[x0 _ leftX]. rightX > (self spanSizeGet << self aaShiftGet) ifTrue:[x1 _ (self spanSizeGet << self aaShiftGet)] ifFalse:[x1 _ rightX]. maxRightX < x1 ifTrue:[x1 _ maxRightX]. "Clip left and right values" x0 < self fillMinXGet ifTrue:[x0 _ self fillMinXGet]. x1 > self fillMaxXGet ifTrue:[x1 _ self fillMaxXGet]. "Adjust start and end values of span" x0 < self spanStartGet ifTrue:[self spanStartPut: x0]. x1 > self spanEndGet ifTrue:[self spanEndPut: x1]. x1 > self spanEndAAGet ifTrue:[self spanEndAAPut: x1]. x0 >= x1 ifTrue:[^false]. "Nothing to do" (self isFillColor: fill) ifTrue:[ self fillColorSpan: fill from: x0 to: x1. ] ifFalse:[ "Store the values for the dispatch" self lastExportedFillPut: fill. self lastExportedLeftXPut: x0. self lastExportedRightXPut: x1. type _ self fillTypeOf: fill. type <= 1 ifTrue:[^true]. self dispatchOn: type in: FillTable. ]. ^false! ! !BalloonEngineBase methodsFor: 'displaying' stamp: 'ar 11/8/1998 15:13'! postDisplayAction "We have just blitted a scan line to the screen. Do whatever seems to be a good idea here." "Note: In the future we may check the time needed for this scan line and interrupt processing to give the Smalltalk code a chance to run at a certain time." self inline: false. "Check if there is any more work to do." (self getStartGet >= self getUsedGet and:[self aetUsedGet = 0]) ifTrue:[ "No more entries to process" self statePut: GEStateCompleted. ]. (self currentYGet >= self fillMaxYGet) ifTrue:[ "Out of clipping range" self statePut: GEStateCompleted. ].! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/8/1998 14:26'! incrementPoint: point by: delta self var: #point declareC:'int *point'. point at: 0 put: (point at: 0) + delta. point at: 1 put: (point at: 1) + delta.! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 9/5/1999 14:13'! transformColor: fillIndex | r g b a transform alphaScale | self var: #transform declareC:'float *transform'. self var: #alphaScale declareC:'double alphaScale'. (fillIndex = 0 or:[self isFillColor: fillIndex]) ifFalse:[^fillIndex]. b _ fillIndex bitAnd: 255. g _ (fillIndex >> 8) bitAnd: 255. r _ (fillIndex >> 16) bitAnd: 255. a _ (fillIndex >> 24) bitAnd: 255. (self hasColorTransform) ifTrue:[ transform _ self colorTransform. alphaScale _ (a * (transform at: 6) + (transform at: 7)) / a. r _ (r * (transform at: 0) + (transform at: 1) * alphaScale) asInteger. g _ (g * (transform at: 2) + (transform at: 3) * alphaScale) asInteger. b _ (b * (transform at: 4) + (transform at: 5) * alphaScale) asInteger. a _ a * alphaScale. r _ r max: 0. r _ r min: 255. g _ g max: 0. g _ g min: 255. b _ b max: 0. b _ b min: 255. a _ a max: 0. a _ a min: 255. ]. a < 1 ifTrue:[^0]."ALWAYS return zero for transparent fills" "If alpha is not 255 (or close thereto) then we need to flush the engine before proceeding" (a < 255 and:[self needsFlush]) ifTrue:[self stopBecauseOf: GErrorNeedFlush]. ^b + (g << 8) + (r << 16) + (a << 24)! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/24/1998 19:47'! transformPoint: point "Transform the given point. If haveMatrix is true then use the current transformation." self var:#point declareC:'int *point'. self hasEdgeTransform ifFalse:[ "Multiply each component by aaLevel and add a half pixel" point at: 0 put: (point at: 0) + self destOffsetXGet * self aaLevelGet. point at: 1 put: (point at: 1) + self destOffsetYGet * self aaLevelGet. ] ifTrue:[ "Note: AA adjustment is done in #transformPoint: for higher accuracy" self transformPoint: point into: point. ].! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/1/1998 16:59'! transformPoint: srcPoint into: dstPoint "Transform srcPoint into dstPoint by using the currently loaded matrix" "Note: This method has been rewritten so that inlining works (e.g., removing the declarations and adding argument coercions at the appropriate points)" self inline: true. self transformPointX: ((self cCoerce: srcPoint to: 'int *') at: 0) asFloat y: ((self cCoerce: srcPoint to:'int *') at: 1) asFloat into: (self cCoerce: dstPoint to: 'int *')! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/24/1998 19:25'! transformPointX: xValue y: yValue into: dstPoint "Transform srcPoint into dstPoint by using the currently loaded matrix" "Note: This should be rewritten so that inlining works (e.g., removing the declarations and adding argument coercions at the appropriate points)" | x y transform | self inline: true. "Won't help at the moment ;-(" self var: #dstPoint declareC:'int *dstPoint'. self var: #xValue declareC: 'double xValue'. self var: #yValue declareC: 'double yValue'. self var: #transform declareC:'float *transform'. transform _ self edgeTransform. x _ ((((transform at: 0) * xValue) + ((transform at: 1) * yValue) + (transform at: 2)) * self aaLevelGet asFloat) asInteger. y _ ((((transform at: 3) * xValue) + ((transform at: 4) * yValue) + (transform at: 5)) * self aaLevelGet asFloat) asInteger. dstPoint at: 0 put: x. dstPoint at: 1 put: y.! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/24/1998 19:48'! transformPoints: n "Transform n (n=1,2,3) points. If haveMatrix is true then the matrix contains the actual transformation." self inline: true. n > 0 ifTrue:[self transformPoint: self point1Get]. n > 1 ifTrue:[self transformPoint: self point2Get]. n > 2 ifTrue:[self transformPoint: self point3Get]. n > 3 ifTrue:[self transformPoint: self point4Get].! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 10/25/1999 00:57'! transformWidth: w "Transform the given width" | deltaX deltaY dstWidth dstWidth2 | self inline: false. self var: #deltaX declareC:'double deltaX'. self var: #deltaY declareC:'double deltaY'. w = 0 ifTrue:[^0]. self point1Get at: 0 put: 0. self point1Get at: 1 put: 0. self point2Get at: 0 put: w * 256. self point2Get at: 1 put: 0. self point3Get at: 0 put: 0. self point3Get at: 1 put: w * 256. self transformPoints: 3. deltaX _ ((self point2Get at: 0) - (self point1Get at: 0)) asFloat. deltaY _ ((self point2Get at: 1) - (self point1Get at: 1)) asFloat. dstWidth _ (((deltaX * deltaX) + (deltaY * deltaY)) sqrt asInteger + 128) // 256. deltaX _ ((self point3Get at: 0) - (self point1Get at: 0)) asFloat. deltaY _ ((self point3Get at: 1) - (self point1Get at: 1)) asFloat. dstWidth2 _ (((deltaX * deltaX) + (deltaY * deltaY)) sqrt asInteger + 128) // 256. dstWidth2 < dstWidth ifTrue:[dstWidth _ dstWidth2]. dstWidth = 0 ifTrue:[^1] ifFalse:[^dstWidth]! ! !BalloonEngineBase methodsFor: 'transforming' stamp: 'ar 11/25/1998 21:33'! uncheckedTransformColor: fillIndex | r g b a transform | self var: #transform declareC:'float *transform'. (self hasColorTransform) ifFalse:[^fillIndex]. b _ fillIndex bitAnd: 255. g _ (fillIndex >> 8) bitAnd: 255. r _ (fillIndex >> 16) bitAnd: 255. a _ (fillIndex >> 24) bitAnd: 255. transform _ self colorTransform. r _ (r * (transform at: 0) + (transform at: 1)) asInteger. g _ (g * (transform at: 2) + (transform at: 3)) asInteger. b _ (b * (transform at: 4) + (transform at: 5)) asInteger. a _ (a * (transform at: 6) + (transform at: 7)) asInteger. r _ r max: 0. r _ r min: 255. g _ g max: 0. g _ g min: 255. b _ b max: 0. b _ b min: 255. a _ a max: 0. a _ a min: 255. a < 16 ifTrue:[^0]."ALWAYS return zero for transparent fills" ^b + (g << 8) + (r << 16) + (a << 24)! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/9/1998 02:06'! accurateLengthOf: deltaX with: deltaY "Return the accurate length of the vector described by deltaX and deltaY" | length2 | deltaX = 0 ifTrue:[deltaY < 0 ifTrue:[^0-deltaY] ifFalse:[^deltaY]]. deltaY = 0 ifTrue:[deltaX < 0 ifTrue:[^0-deltaX] ifFalse:[^deltaX]]. length2 _ (deltaX * deltaX) + (deltaY * deltaY). ^self computeSqrt: length2! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/7/1998 14:26'! computeSqrt: length2 length2 < 32 ifTrue:[^self smallSqrtTable at: length2] ifFalse:[^(length2 asFloat sqrt + 0.5) asInteger]! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/8/1998 14:33'! estimatedLengthOf: deltaX with: deltaY "Estimate the length of the vector described by deltaX and deltaY. This method may be extremely inaccurate - use it only if you know exactly that this doesn't matter. Otherwise use #accurateLengthOf:width:" | absDx absDy | deltaX >= 0 ifTrue:[absDx _ deltaX] ifFalse:[absDx _ 0 - deltaX]. deltaY >= 0 ifTrue:[absDy _ deltaY] ifFalse:[absDy _ 0 - deltaY]. absDx > absDy ifTrue:[^absDx + (absDy // 2)] ifFalse:[^absDy + (absDx // 2)] ! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/24/1998 19:45'! initColorTransform | transform | self inline: false. self var: #transform declareC:'float *transform'. transform _ self colorTransform. transform at: 0 put: (self cCoerce: 1.0 to: 'float'). transform at: 1 put: (self cCoerce: 0.0 to: 'float'). transform at: 2 put: (self cCoerce: 1.0 to: 'float'). transform at: 3 put: (self cCoerce: 0.0 to: 'float'). transform at: 4 put: (self cCoerce: 1.0 to: 'float'). transform at: 5 put: (self cCoerce: 0.0 to: 'float'). transform at: 6 put: (self cCoerce: 1.0 to: 'float'). transform at: 7 put: (self cCoerce: 0.0 to: 'float'). self hasColorTransformPut: 0.! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/24/1998 19:45'! initEdgeTransform | transform | self inline: false. self var: #transform declareC:'float *transform'. transform _ self edgeTransform. transform at: 0 put: (self cCoerce: 1.0 to: 'float'). transform at: 1 put: (self cCoerce: 0.0 to: 'float'). transform at: 2 put: (self cCoerce: 0.0 to: 'float'). transform at: 3 put: (self cCoerce: 0.0 to: 'float'). transform at: 4 put: (self cCoerce: 1.0 to: 'float'). transform at: 5 put: (self cCoerce: 0.0 to: 'float'). self hasEdgeTransformPut: 0.! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/7/1998 14:26'! resetGraphicsEngineStats self inline: false. workBuffer at: GWTimeInitializing put: 0. workBuffer at: GWTimeFinishTest put: 0. workBuffer at: GWTimeNextGETEntry put: 0. workBuffer at: GWTimeAddAETEntry put: 0. workBuffer at: GWTimeNextFillEntry put: 0. workBuffer at: GWTimeMergeFill put: 0. workBuffer at: GWTimeDisplaySpan put: 0. workBuffer at: GWTimeNextAETEntry put: 0. workBuffer at: GWTimeChangeAETEntry put: 0. workBuffer at: GWCountInitializing put: 0. workBuffer at: GWCountFinishTest put: 0. workBuffer at: GWCountNextGETEntry put: 0. workBuffer at: GWCountAddAETEntry put: 0. workBuffer at: GWCountNextFillEntry put: 0. workBuffer at: GWCountMergeFill put: 0. workBuffer at: GWCountDisplaySpan put: 0. workBuffer at: GWCountNextAETEntry put: 0. workBuffer at: GWCountChangeAETEntry put: 0. workBuffer at: GWBezierMonotonSubdivisions put: 0. workBuffer at: GWBezierHeightSubdivisions put: 0. workBuffer at: GWBezierOverflowSubdivisions put: 0. workBuffer at: GWBezierLineConversions put: 0. ! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/7/1998 14:26'! setAALevel: level "Set the anti-aliasing level. Three levels are supported: 1 - No antialiasing 2 - 2x2 unweighted anti-aliasing 4 - 4x4 unweighted anti-aliasing. " | aaLevel | self inline: false. level >= 4 ifTrue:[aaLevel _ 4]. (level >= 2) & (level < 4) ifTrue:[aaLevel _ 2]. level < 2 ifTrue:[aaLevel _ 1]. self aaLevelPut: aaLevel. aaLevel = 1 ifTrue:[ self aaShiftPut: 0. self aaColorMaskPut: 16rFFFFFFFF. self aaScanMaskPut: 0. ]. aaLevel = 2 ifTrue:[ self aaShiftPut: 1. self aaColorMaskPut: 16rFCFCFCFC. self aaScanMaskPut: 1. ]. aaLevel = 4 ifTrue:[ self aaShiftPut: 2. self aaColorMaskPut: 16rF0F0F0F0. self aaScanMaskPut: 3. ]. self aaColorShiftPut: self aaShiftGet * 2. self aaHalfPixelPut: self aaShiftGet. ! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/8/1998 15:25'! smallSqrtTable | theTable | self inline: false. self returnTypeC:'int *'. self var: #theTable declareC:'static int theTable[32] = {0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6}'. ^theTable! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/8/1998 20:57'! squaredLengthOf: deltaX with: deltaY ^(deltaX * deltaX) + (deltaY * deltaY)! ! !BalloonEngineBase methodsFor: 'other' stamp: 'ar 11/25/1998 02:22'! stopBecauseOf: stopReason self stopReasonPut: stopReason. engineStopped _ true.! ! !BalloonEngineBase methodsFor: 'private' stamp: 'ar 5/16/2000 17:09'! copyBitsFrom: x0 to: x1 at: yValue copyBitsFn = 0 ifTrue:[ "We need copyBits here so try to load it implicitly" self initialiseModule ifFalse:[^false]. ]. ^self cCode:' ((int (*) (int, int, int)) copyBitsFn)(x0, x1, yValue)'! ! !BalloonEngineBase methodsFor: 'private' stamp: 'ar 5/13/2000 14:55'! errorWrongIndex "Ignore dispatch errors when translating to C (since we have no entry point for #error in the VM proxy)" self cCode:'' inSmalltalk:[self error:'BalloonEngine: Fatal dispatch error']! ! !BalloonEngineBase methodsFor: 'private' stamp: 'ar 5/16/2000 17:08'! loadBitBltFrom: bbObj loadBBFn = 0 ifTrue:[ "We need copyBits here so try to load it implicitly" self initialiseModule ifFalse:[^false]. ]. ^self cCode: '((int (*) (int))loadBBFn)(bbObj)'! ! !BalloonEngineBase methodsFor: 'private' stamp: 'ar 10/28/1998 20:58'! makeUnsignedFrom: someIntegerValue ^someIntegerValue! ! !BalloonEngineBase methodsFor: 'initialize-release' stamp: 'ar 5/16/2000 19:56'! initialiseModule self export: true. loadBBFn _ interpreterProxy ioLoadFunction: 'loadBitBltFrom' From: bbPluginName. copyBitsFn _ interpreterProxy ioLoadFunction: 'copyBitsFromtoat' From: bbPluginName. ^(loadBBFn ~= 0 and:[copyBitsFn ~= 0])! ! !BalloonEngineBase methodsFor: 'initialize-release' stamp: 'ar 5/16/2000 19:57'! moduleUnloaded: aModuleName "The module with the given name was just unloaded. Make sure we have no dangling references." self export: true. self var: #aModuleName type: 'char *'. (aModuleName strcmp: bbPluginName) = 0 ifTrue:[ "BitBlt just shut down. How nasty." loadBBFn _ 0. copyBitsFn _ 0. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonEngineBase class instanceVariableNames: ''! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 5/16/2000 20:03'! declareCVarsIn: cg "Buffers" cg var: #workBuffer type: #'int*'. cg var: #objBuffer type: #'int*'. cg var: #getBuffer type: #'int*'. cg var: #aetBuffer type: #'int*'. cg var: #spanBuffer type: #'unsigned int*'. cg var: #edgeTransform declareC: 'float edgeTransform[6]'. cg var: #doProfileStats declareC: 'int doProfileStats = 0'. cg var: 'bbPluginName' declareC:'char bbPluginName[256] = "BitBltPlugin"'. ! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/7/1998 22:26'! initialize "BalloonEngineBase initialize" "BalloonEnginePlugin translateDoInlining: true." EdgeInitTable _ self initializeEdgeInitTable. EdgeStepTable _ self initializeEdgeStepTable. WideLineWidthTable _ self initializeWideLineWidthTable. WideLineFillTable _ self initializeWideLineFillTable. FillTable _ self initializeFillTable.! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/4/1998 21:52'! initializeEdgeInitTable "BalloonEngineBase initialize" ^#( errorWrongIndex errorWrongIndex errorWrongIndex errorWrongIndex stepToFirstLine stepToFirstWideLine stepToFirstBezier stepToFirstWideBezier )! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/4/1998 21:52'! initializeEdgeStepTable "BalloonEngineBase initialize" ^#( errorWrongIndex errorWrongIndex errorWrongIndex errorWrongIndex stepToNextLine stepToNextWideLine stepToNextBezier stepToNextWideBezier )! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/25/1998 19:46'! initializeFillTable "BalloonEngineBase initialize" ^#( errorWrongIndex "Type zero - undefined" errorWrongIndex "Type one - external fill" fillLinearGradient "Linear gradient fill" fillRadialGradient "Radial gradient fill" fillBitmapSpan "Clipped bitmap fill" fillBitmapSpan "Repeated bitmap fill" )! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/4/1998 23:03'! initializeWideLineFillTable "BalloonEngineBase initialize" ^#( errorWrongIndex errorWrongIndex returnWideLineFill returnWideBezierFill )! ! !BalloonEngineBase class methodsFor: 'class initialization' stamp: 'ar 11/4/1998 23:03'! initializeWideLineWidthTable "BalloonEngineBase initialize" ^#( errorWrongIndex errorWrongIndex returnWideLineWidth returnWideBezierWidth )! ! !BalloonEngineBase class methodsFor: 'accessing' stamp: 'ar 5/11/2000 23:48'! moduleName ^'B2DPlugin'! ! !BalloonEngineBase class methodsFor: 'accessing' stamp: 'ar 11/11/1998 21:56'! simulatorClass ^BalloonEngineSimulation! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/7/1998 03:33'! a1EngineOutline "The following is a brief outline on how the engine works. In general, we're using a pretty straight-forward active edge approach, e.g., we classify all edges into three different states: a) Waiting for processing b) Active (e.g., being processed) c) Finished Before the engine starts all edges are sorted by their y-value in a so-called 'global edge table' (furthermore referred to as GET) and processed in top to bottom order (the edges are also sorted by x-value but this is only for simplifying the insertion when adding edges). Then, we start at the first visible scan line and execute the following steps: 1) Move all edges starting at the current scan line from state a) to state b) This step requires the GET to be sorted so that we only need to check the first edges of the GET. After the initial state of the edge (e.g., it's current pixel value and data required for incremental updates) the edges are then inserted in the 'active edge table' (called AET). The sort order in the AET is defined by the pixel position of each edge at the current scan line and thus edges are kept in increasing x-order. This step does occur for every edge only once and is therefore not the most time-critical part of the approach. 2) Draw the current scan line This step includes two sub-parts. In the first part, the scan line is assembled. This involves walking through the AET and drawing the pixels between each two neighbour edges. Since each edge can have two associated fills (a 'left' and a 'right' fill) we need to make sure that edges falling on the same pixel position do not affect the painted image. This issue is discussed in the aetScanningProblems documentation. Wide edges (e.g., edges having an associated width) are also handled during this step. Wide edges are always preferred over interior fills - this ensures that the outline of an object cannot be overdrawn by any interior fill of a shape that ends very close to the edge (for more information see wideEdges documentation). After the scan is assembled it is blitted to the screen. This only happens all 'aaLevel' scan lines (for further information see the antiAliasing documentation). This second step is done at each scan line in the image, and is usually the most time-critical part. 3) Update all currently active edges Updating the active edges basically means either to remove the edge from the AET (if it is at the end y value) or incrementally computing the pixel value for the next scan line. Based on the information gathered in the first step, this part should be executed as fast as possible - it happens for each edge in the AET at each scan line and may be the bottleneck if many edges are involved in the drawing operations (see the TODO list; part of it probably deals with the issue). " ^self error:'Comment only'! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/7/1998 03:55'! a2AntiAliasing "The engine currently used a very simple, but efficient anti-aliasing scheme. It is based on a square unweighted filter of size 1, 2, or 4 resulting in three levels of anti-aliasing: * No anti-aliasing (filter size 1) This simply draws each pixel 'as is' on the screen * Slight anti-aliasing (filter size 2) Doubles the rasterization size in each direction and assembles the pixel value as the medium of the four sub-pixels falling into the full pixel * Full anti-aliasing (filter size 4) Quadruples the rasterization in each direction and assembles the pixel value as the medium of the sixteen sub-pixels falling into the full pixel The reason for using these three AA levels is simply efficiency of computing. Since the above filters (1x1, 2x2, 4x4) have all power of two elements (1, 4, and 16) we can compute the weighted sum of the final pixel by computing destColor _ destColor + (srcColor // subPixels) And, since we're only working on 32bit destination buffer we do not need to compute the components of each color separately but can neatly put the entire color into a single formula: destPixel32 _ destPixel32 + ((srcPixel32 bitAnd: aaMask) >> aaShift). with aaMask = 16rFFFFFFFF for aaLevel = 1, aaMask = 16rFCFCFCFC for aaLevel = 2, aaMask = 16rF0F0F0F0 for aaLevel = 4 and aaShift = 0, 2, or 4 for the different levels. However, while the above is efficient to compute, it also drops accuracy. So, for the 4x4 anti-aliasing we're effectively only using the high 4 bits of each color component. While is generally not a problem (we add 16 sub-pixels into this value) there is a simple arithmetic difficulty because the above cannot fill the entire range of values, e.g., 16 * (255 // 16) = 16 * 15 = 240 and not 255 as expected. We solve this problem by replicating the top n (n=0, 2, 4) bits of each component as the low bits in an adjustment step before blitting to scan line to the screen. This has the nice effect that a zero pixel value (e.g., transparent) will remain zero, a white pixel (as computed above) will result in a value of 255 for each component (defining opaque white) and each color inbetween linearly mapped between 0 and 255. " ^self error:'Comment only'! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/7/1998 03:35'! a3RasterizationRules ^self error:'Comment only'! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/7/1998 03:35'! a4WideEdges! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/7/1998 03:36'! a5AETScanningProblems "Due to having two fill entries (one left and one right) there can be problems while scanning the active edge table. In general, the AET should look like the following (ri - regions, ei - edges, fi - fills): | \ | r1 | r2 \ r3 | r4 | \ | e1 e2 e3 with: f(r1) = fLeft(e1) = 0 (empty fill, denoted -) f(r2) = fRight(e1) = fLeft(e2) (denoted x) f(r3) = fRight(e2) = fLeft(e3) (denoted o) f(r4) = fRight(e3) = 0 However, due to integer arithmetic used during computations the AET may look like the following: X \| | | \ | | \ | r1 | r2 \ r3 | r4 | \ | e1 e2 e3 In this case, the starting point of e1 and e2 have the same x value at the first scan line but e2 has been sorted before e1 (Note: This can happen in *many* cases - the above is just a very simple example). Given the above outlined fill relations we have a problem. So, for instance, using the left/right fills as defined by the edges would lead to the effect that in the first scan line region r3 is actually filled with the right fill of e1 while it should actually be filled with the right fill of e2. This leads to noticable artifacts in the image and increasing resolution does not help. What we do here is defining an arbitrary sort order between fills (you can think of it as a depth value but the only thing that matters is that you can order the fills by this number and that the empty fill is always sorted at the end), and toggle the fills between an 'active' and an 'inactive' state at each edge. This is done as follows: For each edge ei in the AET do: * if fLeft(ei) isActive then removeActive(fLeft(ei)) else addActive(fLeft(ei)) * if fRight(ei) isActive then removeActive(fRight(ei)) else addActive(fRight(ei)) * draw the span from ei to ei+1 with currentActive where addActive adds the fill to the list of currently active fills, removeActive() removes the fill from the active list and currentActive returns the fill AS DEFINED BY THE SORT ORDER from the list of active fills. Note that this does not change anything in the first example above because the list will only contain one entry (besides the empty fill). In the second case however, it will lead to the following sequence: * toggle fLeft(e2) = f(r2) = 'x' - makes fLeft(e2) active - activeList = 'x' * toggle fRight(e2) = f(r3) = 'o' - makes fRight(e2) active - activeList = 'xo' * draw span from e2 to e1 Depending on the sort order between 'x' and 'o' the region will be drawn with either one of the fills. It is significant to note here that the occurence of such a problem is generally only *very* few pixels large (in the above example zero pixels) and will therefore not be visually noticable. In any case, there is a unique decision for the fill to use here and that is what we need if the problem did not happen accidentally (e.g., someone has manually changed one fill of an edge but not the fill of the opposite edge). * toggle fLeft(e1) = f(r1) = '-' - makes fLeft(r1) visible - activeList = 'xo-' [Note: empty fills are a special case. They can be ignored since they sort last and the activeList can return the empty fill if it is itself empty]. * toggle fRight(e1) = f(r2) = 'x' - makes fRight(e1) invisible - activeList = 'o-' * draw span from e2 to e3 Since the active list contains (besides the empty fill) only one fill value this will be used. Fortunately, this is the correct fill because it is the fill we had initially defined for the region r2. An interesting side effect of the above is that there is no such notion as a 'left' or 'right' fill anymore. Another (not-so-nice) side effect is that the entire AET has to be scanned from the beginning even if only the last few edges actually affect the visible region. PS. I need to find a way of clipping the edges for this. More on it later... " ^self error:'Comment only'! ! !BalloonEngineBase class methodsFor: 'documentation' stamp: 'ar 11/8/1998 00:06'! a6StuffTODO "This is an unordered list of things to do: BalloonEnginePlugin>>stepToFirstBezierIn:at: 1) Check if reducing maxSteps from 2*deltaY to deltaY brings a *significant* performance improvement. In theory this should make for double step performance but will cost in quality. Might be that the AA stuff will compensate for this - but I'm not really sure. BalloonEngineBase>>dispatchOn:in: 1) Check what dispatches cost most and must be inlined by an #inlinedDispatchOn:in: Probably this will be stepping and eventually wide line stuff but we'll see. BalloonEngineBase 1) Check which variables should become inst vars, if any. This will remove an indirection during memory access and might allow a couple of optimizations by the C compiler. Anti-Aliasing: 1) Check if we can use a weighted 3x3 filter function of the form 1 2 1 2 4 2 1 2 1 Which should be *extremely* nice for fonts (it's sharpening edges). The good thing about the above is that it sums up to 16 (as in the 4x4 case) but I don't know how to keep a history without needing two extra scan lines. 2) Check if we can - somehow - integrate more general filters. 3) Unroll the loops during AA so we can copy and mask aaLevel pixels in each step between start and end. This should speed up filling by a factor of 2-4 (in particular for difficult stuff like radial gradients). Clipping 1) Find a way of clipping edges left of the clip rectangle or at least ignoring most of them after the first scan line. The AET scanning problems discuss the issue but it should be possible to keep the color list between spans (if not empty) and speed up drawing at the very right (such as in the Winnie Pooh example where a lot of stuff is between the left border and the clipping rect. 2) Check if we can determine empty states of the color list and an edge that is longer than anything left of it. This should work in theory but might be relatively expensive to compute. " ^self error:'Comment only'! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/24/1998 23:54'! initEdgeConstants: dict "Initialize the edge constants" self initFromSpecArray: #( "Edge primitive types" (GEPrimitiveEdge 2) "External edge - not handled by the GE" (GEPrimitiveWideEdge 3) "Wide external edge" (GEPrimitiveLine 4) "Straight line" (GEPrimitiveWideLine 5) "Wide line" (GEPrimitiveBezier 6) "Quadratic bezier curve" (GEPrimitiveWideBezier 7) "Wide bezier curve" "Special flags" (GEPrimitiveWide 16r01) "Flag determining a wide primitive" (GEPrimitiveWideMask 16rFE) "Mask for clearing the wide flag" (GEEdgeFillsInvalid 16r10000) "Flag determining if left/right fills of an edge are invalid" (GEEdgeClipFlag 16r20000) "Flag determining if this is a clip edge" "General edge state constants" (GEXValue 4) "Current raster x" (GEYValue 5) "Current raster y" (GEZValue 6) "Current raster z" (GENumLines 7) "Number of scan lines remaining" (GEFillIndexLeft 8) "Left fill index" (GEFillIndexRight 9) "Right fill index" (GEBaseEdgeSize 10) "Basic size of each edge" "General fill state constants" (GEBaseFillSize 4) "Basic size of each fill" "General Line state constants" (GLXDirection 10) "Direction of edge (1: left-to-right; -1: right-to-left)" (GLYDirection 11) "Direction of edge (1: top-to-bottom; -1: bottom-to-top)" (GLXIncrement 12) "Increment at each scan line" (GLError 13) "Current error" (GLErrorAdjUp 14) "Error to add at each scan line" (GLErrorAdjDown 15) "Error to subtract on roll-over" "Note: The following entries are only needed before the incremental state is computed. They are therefore aliased to the error values above" (GLEndX 14) "End X of line" (GLEndY 15) "End Y of line" (GLBaseSize 16) "Basic size of each line" "Additional stuff for wide lines" (GLWideFill 16) "Current fill of line" (GLWideWidth 17) "Current width of line" (GLWideEntry 18) "Initial steps" (GLWideExit 19) "Final steps" (GLWideExtent 20) "Target width" (GLWideSize 21) "Size of wide lines" "General Bezier state constants" (GBUpdateData 10) "Incremental update data for beziers" (GBUpdateX 0) "Last computed X value (24.8)" (GBUpdateY 1) "Last computed Y value (24.8)" (GBUpdateDX 2) "Delta X forward difference step (8.24)" (GBUpdateDY 3) "Delta Y forward difference step (8.24)" (GBUpdateDDX 4) "Delta DX forward difference step (8.24)" (GBUpdateDDY 5) "Delta DY forward difference step (8.24)" "Note: The following four entries are only needed before the incremental state is computed. They are therefore aliased to the incremental values above" (GBViaX 12) "via x" (GBViaY 13) "via y" (GBEndX 14) "end x" (GBEndY 15) "end y" (GBBaseSize 16) "Basic size of each bezier. Note: MUST be greater or equal to the size of lines" "Additional stuff for wide beziers" (GBWideFill 16) "Current fill of line" (GBWideWidth 17) "Current width of line" (GBWideEntry 18) "Initial steps" (GBWideExit 19) "Final steps" (GBWideExtent 20) "Target extent" (GBFinalX 21) "Final X value" (GBWideUpdateData 22) "Update data for second curve" (GBWideSize 28) "Size of wide beziers" ) in: dict.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/27/1998 14:19'! initFillConstants: dict "Initialize the fill constants" "BalloonEngineBase initPool" self initFromSpecArray: #( "Fill primitive types" (GEPrimitiveFill 16r100) (GEPrimitiveLinearGradientFill 16r200) (GEPrimitiveRadialGradientFill 16r300) (GEPrimitiveClippedBitmapFill 16r400) (GEPrimitiveRepeatedBitmapFill 16r500) "General fill state constants" (GEBaseFillSize 4) "Basic size of each fill" "Oriented fill constants" (GFOriginX 4) "X origin of fill" (GFOriginY 5) "Y origin of fill" (GFDirectionX 6) "X direction of fill" (GFDirectionY 7) "Y direction of fill" (GFNormalX 8) "X normal of fill" (GFNormalY 9) "Y normal of fill" "Gradient fill constants" (GFRampLength 10) "Length of following color ramp" (GFRampOffset 12) "Offset of first ramp entry" (GGBaseSize 12) "Bitmap fill constants" (GBBitmapWidth 10) "Width of bitmap" (GBBitmapHeight 11) "Height of bitmap" (GBBitmapDepth 12) "Depth of bitmap" (GBBitmapSize 13) "Size of bitmap words" (GBBitmapRaster 14) "Size of raster line" (GBColormapSize 15) "Size of colormap, if any" (GBTileFlag 16) "True if the bitmap is tiled" (GBColormapOffset 18) "Offset of colormap, if any" (GBMBaseSize 18) "Basic size of bitmap fill" ) in: dict.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:26'! initFromSpecArray: specArray in: aDictionary specArray do:[:spec| self initPoolVariable: spec first value: spec last in: aDictionary. ]! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initPool "BalloonEngineBase initPool" (Smalltalk includesKey: #BalloonEngineConstants) ifFalse:[ Smalltalk declare: #BalloonEngineConstants from: Undeclared. ]. (Smalltalk at: #BalloonEngineConstants) isNil ifTrue:[ (Smalltalk associationAt: #BalloonEngineConstants) value: Dictionary new. ]. self initPool: (Smalltalk at: #BalloonEngineConstants).! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initPool: aDictionary self initStateConstants: aDictionary. self initWorkBufferConstants: aDictionary. self initPrimitiveConstants: aDictionary. self initEdgeConstants: aDictionary. self initFillConstants: aDictionary. self initializeInstVarNames: BalloonEngine in: aDictionary prefixedBy: 'BE'. self initializeInstVarNames: BalloonEdgeData in: aDictionary prefixedBy: 'ET'. self initializeInstVarNames: BalloonFillData in: aDictionary prefixedBy: 'FT'.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initPoolFull "BalloonEngineBase initPoolFull" "Move old stuff to Undeclared and re-initialize the receiver" BalloonEngineConstants associationsDo:[:assoc| Undeclared declare: assoc key from: BalloonEngineConstants. ]. self initPool. Undeclared removeUnreferencedKeys.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initPoolVariable: token value: value in: aDictionary aDictionary declare: token from: Undeclared. (aDictionary associationAt: token) value: value.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initPrimitiveConstants: dict "Initialize the primitive constants" self initFromSpecArray: #( "Primitive type constants" (GEPrimitiveUnknown 0) (GEPrimitiveEdgeMask 16rFF) (GEPrimitiveFillMask 16rFF00) (GEPrimitiveTypeMask 16rFFFF) "General state constants (Note: could be compressed later)" (GEObjectType 0) "Type of object" (GEObjectLength 1) "Length of object" (GEObjectIndex 2) "Index into external objects" (GEObjectUnused 3) "Currently unused" ) in: dict.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/25/1998 00:25'! initStateConstants: dict "Initialize the state Constants" "BalloonEngineBase initPool" self initFromSpecArray: #( (GEStateUnlocked 0) "Buffer is unlocked and can be modified as wanted" (GEStateAddingFromGET 1) "Adding edges from the GET" (GEStateWaitingForEdge 2) "Waiting for edges added to GET" (GEStateScanningAET 3) "Scanning the active edge table" (GEStateWaitingForFill 4) "Waiting for a fill to mix in during AET scan" (GEStateBlitBuffer 5) "Blt the current scan line" (GEStateUpdateEdges 6) "Update edges to next scan line" (GEStateWaitingChange 7) "Waiting for a changed edge" (GEStateCompleted 8) "Rendering completed" "Error constants" (GErrorNoMoreSpace 1) "No more space in collection" (GErrorBadState 2) "Tried to call a primitive while engine in bad state" (GErrorNeedFlush 3) "Tried to call a primitive that requires flushing before" "Incremental error constants" (GErrorGETEntry 4) "Unknown entry in GET" (GErrorFillEntry 5) "Unknown FILL encountered" (GErrorAETEntry 6) "Unknown entry in AET" ) in: dict.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/25/1998 00:20'! initWorkBufferConstants: dict "Initialize the work buffer constants" "BalloonEngineBase initPool" self initFromSpecArray: #( "General work buffer constants" (GWMagicNumber 16r416E6469) "Magic number" (GWHeaderSize 128) "Size of header" (GWMinimalSize 256) "Minimal size of work buffer" "Header entries" (GWMagicIndex 0) "Index of magic number" (GWSize 1) "Size of full buffer" (GWState 2) "Current state (e.g., locked or not)" "Buffer entries" (GWObjStart 8) "objStart" (GWObjUsed 9) "objUsed" (GWBufferTop 10) "wbTop" (GWGETStart 11) "getStart" (GWGETUsed 12) "getUsed" (GWAETStart 13) "aetStart" (GWAETUsed 14) "aetUsed" "Transform entries" (GWHasEdgeTransform 16) "True if we have an edge transformation" (GWHasColorTransform 17) "True if we have a color transformation" (GWEdgeTransform 18) "2x3 edge transformation" (GWColorTransform 24) "8 word RGBA color transformation" "Span entries" (GWSpanStart 32) "spStart" (GWSpanSize 33) "spSize" (GWSpanEnd 34) "spEnd" (GWSpanEndAA 35) "spEndAA" "Bounds entries" (GWFillMinX 36) "fillMinX" (GWFillMaxX 37) "fillMaxX" (GWFillMinY 38) "fillMinY" (GWFillMaxY 39) "fillMaxY" (GWFillOffsetX 40) "fillOffsetX" (GWFillOffsetY 41) "fillOffsetY" (GWClipMinX 42) (GWClipMaxX 43) (GWClipMinY 44) (GWClipMaxY 45) (GWDestOffsetX 46) (GWDestOffsetY 47) "AA entries" (GWAALevel 48) "aaLevel" (GWAAShift 49) "aaShift" (GWAAColorShift 50) "aaColorShift" (GWAAColorMask 51) "aaColorMask" (GWAAScanMask 52) "aaScanMask" (GWAAHalfPixel 53) "aaHalfPixel" "Misc entries" (GWNeedsFlush 63) "True if the engine may need a flush" (GWStopReason 64) "stopReason" (GWLastExportedEdge 65) "last exported edge" (GWLastExportedFill 66) "last exported fill" (GWLastExportedLeftX 67) "last exported leftX" (GWLastExportedRightX 68) "last exported rightX" (GWClearSpanBuffer 69) "Do we have to clear the span buffer?" (GWPointListFirst 70) "First point list in buffer" (GWPoint1 80) (GWPoint2 82) (GWPoint3 84) (GWPoint4 86) (GWCurrentY 88) "Profile stats" (GWTimeInitializing 90) (GWCountInitializing 91) (GWTimeFinishTest 92) (GWCountFinishTest 93) (GWTimeNextGETEntry 94) (GWCountNextGETEntry 95) (GWTimeAddAETEntry 96) (GWCountAddAETEntry 97) (GWTimeNextFillEntry 98) (GWCountNextFillEntry 99) (GWTimeMergeFill 100) (GWCountMergeFill 101) (GWTimeDisplaySpan 102) (GWCountDisplaySpan 103) (GWTimeNextAETEntry 104) (GWCountNextAETEntry 105) (GWTimeChangeAETEntry 106) (GWCountChangeAETEntry 107) "Bezier stats" (GWBezierMonotonSubdivisions 108) "# of subdivision due to non-monoton beziers" (GWBezierHeightSubdivisions 109) "# of subdivisions due to excessive height" (GWBezierOverflowSubdivisions 110) "# of subdivisions due to possible int overflow" (GWBezierLineConversions 111) "# of beziers converted to lines" (GWHasClipShapes 112) "True if the engine contains clip shapes" (GWCurrentZ 113) "Current z value of primitives" ) in: dict.! ! !BalloonEngineBase class methodsFor: 'pool initialization' stamp: 'ar 11/11/1998 22:27'! initializeInstVarNames: aClass in: aDictionary prefixedBy: aString | token value | aClass instVarNames doWithIndex:[:instVarName :index| token _ (aString, instVarName first asUppercase asString, (instVarName copyFrom: 2 to: instVarName size),'Index') asSymbol. value _ index - 1. aDictionary declare: token from: Undeclared. (aDictionary associationAt: token) value: value. ]. token _ (aString, aClass name,'Size') asSymbol. aDictionary declare: token from: Undeclared. (aDictionary associationAt: token) value: aClass instSize.! ! BalloonEngineBase subclass: #BalloonEnginePlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:07'! primitiveAddBezier | leftFill rightFill viaOop endOop startOop nSegments | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. rightFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). leftFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 1). viaOop _ interpreterProxy stackObjectValue: 2. endOop _ interpreterProxy stackObjectValue: 3. startOop _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the fills are okay" ((self isFillOkay: leftFill) and:[self isFillOkay: rightFill]) ifFalse:[^interpreterProxy primitiveFail]. "Do a quick check if the fillIndices are equal - if so, just ignore it" leftFill = rightFill & false ifTrue:[ ^interpreterProxy pop: 6. "Leave rcvr on stack" ]. self loadPoint: self point1Get from: startOop. self loadPoint: self point2Get from: viaOop. self loadPoint: self point3Get from: endOop. interpreterProxy failed ifTrue:[^0]. self transformPoints: 3. nSegments _ self loadAndSubdivideBezierFrom: self point1Get via: self point2Get to: self point3Get isWide: false. self needAvailableSpace: nSegments * GBBaseSize. engineStopped ifFalse:[ leftFill _ self transformColor: leftFill. rightFill _ self transformColor: rightFill]. engineStopped ifFalse:[ self loadWideBezier: 0 lineFill: 0 leftFill: leftFill rightFill: rightFill n: nSegments. ]. engineStopped ifTrue:[ "Make sure the stack is okay" self wbStackClear. ^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 5. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:08'! primitiveAddBezierShape | points lineFill lineWidth fillIndex length isArray segSize nSegments | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. lineFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). lineWidth _ interpreterProxy stackIntegerValue: 1. fillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 2). nSegments _ interpreterProxy stackIntegerValue: 3. points _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "First, do a check if the points look okay" length _ interpreterProxy slotSizeOf: points. (interpreterProxy isWords: points) ifTrue:[ isArray _ false. "Either PointArray or ShortPointArray" (length = (nSegments * 3) or:[length = (nSegments * 6)]) ifFalse:[^interpreterProxy primitiveFail]. ] ifFalse:["Must be Array of points" (interpreterProxy fetchClassOf: points) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. length = (nSegments * 3) ifFalse:[^interpreterProxy primitiveFail]. isArray _ true. ]. "Now check that we have some hope to have enough free space. Do this by assuming nPoints boundaries of maximum size, hoping that most of the fills will be colors and many boundaries will be line segments" (lineWidth = 0 or:[lineFill = 0]) ifTrue:[segSize _ GLBaseSize] ifFalse:[segSize _ GLWideSize]. (self needAvailableSpace: segSize * nSegments) ifFalse:[^interpreterProxy primitiveFail]. "Check the fills" ((self isFillOkay: lineFill) and:[self isFillOkay: fillIndex]) ifFalse:[^interpreterProxy primitiveFail]. "Transform colors" lineFill _ self transformColor: lineFill. fillIndex _ self transformColor: fillIndex. engineStopped ifTrue:[^interpreterProxy primitiveFail]. "Check if have anything at all to do" ((lineFill = 0 or:[lineWidth = 0]) and:[fillIndex = 0]) ifTrue:[^interpreterProxy pop: 5]. "Transform the lineWidth" lineWidth = 0 ifFalse:[ lineWidth _ self transformWidth: lineWidth. lineWidth < 1 ifTrue:[lineWidth _ 1]]. "And load the actual shape" isArray ifTrue:[ self loadArrayShape: points nSegments: nSegments fill: fillIndex lineWidth: lineWidth lineFill: lineFill. ] ifFalse:[ self loadShape: (interpreterProxy firstIndexableField: points) nSegments: nSegments fill: fillIndex lineWidth: lineWidth lineFill: lineFill pointsShort: (nSegments * 3 = length)]. engineStopped ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self needsFlushPut: 1. self storeEngineStateInto: engine. interpreterProxy pop: 5. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:10'! primitiveAddBitmapFill | nrmOop dirOop originOop tileFlag fill xIndex cmOop formOop | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 7 ifFalse:[^interpreterProxy primitiveFail]. xIndex _ interpreterProxy stackIntegerValue: 0. xIndex <= 0 ifTrue:[^interpreterProxy primitiveFail]. nrmOop _ interpreterProxy stackObjectValue: 1. dirOop _ interpreterProxy stackObjectValue: 2. originOop _ interpreterProxy stackObjectValue: 3. tileFlag _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 4). tileFlag ifTrue:[tileFlag _ 1] ifFalse:[tileFlag _ 0]. cmOop _ interpreterProxy stackObjectValue: 5. formOop _ interpreterProxy stackObjectValue: 6. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 7) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self loadPoint: self point1Get from: originOop. self loadPoint: self point2Get from: dirOop. self loadPoint: self point3Get from: nrmOop. interpreterProxy failed ifTrue:[^0]. fill _ self loadBitmapFill: formOop colormap: cmOop tile: tileFlag from: self point1Get along: self point2Get normal: self point3Get xIndex: xIndex-1. engineStopped ifTrue:[ "Make sure the stack is okay" ^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 8. interpreterProxy push: (interpreterProxy positive32BitIntegerFor: fill). ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:06'! primitiveAddCompressedShape | fillIndexList lineFills lineWidths rightFills leftFills nSegments points pointsShort | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 7 ifFalse:[^interpreterProxy primitiveFail]. fillIndexList _ interpreterProxy stackObjectValue: 0. lineFills _ interpreterProxy stackObjectValue: 1. lineWidths _ interpreterProxy stackObjectValue: 2. rightFills _ interpreterProxy stackObjectValue: 3. leftFills _ interpreterProxy stackObjectValue: 4. nSegments _ interpreterProxy stackIntegerValue: 5. points _ interpreterProxy stackObjectValue: 6. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 7) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "First, do a check if the compressed shape is okay" (self checkCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList) ifFalse:[^interpreterProxy primitiveFail]. "Now check that we have some hope to have enough free space. Do this by assuming nSegments boundaries of maximum size, hoping that most of the fills will be colors and many boundaries will be line segments" (self needAvailableSpace: (GBBaseSize max: GLBaseSize) * nSegments) ifFalse:[^interpreterProxy primitiveFail]. "Check if the points are short" pointsShort _ (interpreterProxy slotSizeOf: points) = (nSegments * 3). "Then actually load the compressed shape" self loadCompressedShape: (interpreterProxy firstIndexableField: points) segments: nSegments leftFills: (interpreterProxy firstIndexableField: leftFills) rightFills: (interpreterProxy firstIndexableField: rightFills) lineWidths: (interpreterProxy firstIndexableField: lineWidths) lineFills: (interpreterProxy firstIndexableField: lineFills) fillIndexList: (interpreterProxy firstIndexableField: fillIndexList) pointShort: pointsShort. engineStopped ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self needsFlushPut: 1. self storeEngineStateInto: engine. interpreterProxy pop: 7. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:13'! primitiveAddGradientFill | isRadial nrmOop dirOop originOop rampOop fill | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. isRadial _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). nrmOop _ interpreterProxy stackValue: 1. dirOop _ interpreterProxy stackValue: 2. originOop _ interpreterProxy stackValue: 3. rampOop _ interpreterProxy stackValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. self loadPoint: self point1Get from: originOop. self loadPoint: self point2Get from: dirOop. self loadPoint: self point3Get from: nrmOop. interpreterProxy failed ifTrue:[^0]. fill _ self loadGradientFill: rampOop from: self point1Get along: self point2Get normal: self point3Get isRadial: isRadial. engineStopped ifTrue:[ "Make sure the stack is okay" ^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 6. interpreterProxy push: (interpreterProxy positive32BitIntegerFor: fill). ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:08'! primitiveAddLine | leftFill rightFill endOop startOop | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 4 ifFalse:[^interpreterProxy primitiveFail]. rightFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). leftFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 1). endOop _ interpreterProxy stackObjectValue: 2. startOop _ interpreterProxy stackObjectValue: 3. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 4) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the fills are okay" ((self isFillOkay: leftFill) and:[self isFillOkay: rightFill]) ifFalse:[^interpreterProxy primitiveFail]. "Load the points" self loadPoint: self point1Get from: startOop. self loadPoint: self point2Get from: endOop. interpreterProxy failed ifTrue:[^0]. "Transform points" self transformPoints: 2. "Transform colors" leftFill _ self transformColor: leftFill. rightFill _ self transformColor: rightFill. engineStopped ifTrue:[^interpreterProxy primitiveFail]. "Load line" self loadWideLine: 0 from: self point1Get to: self point2Get lineFill: 0 leftFill: leftFill rightFill: rightFill. engineStopped ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self storeEngineStateInto: engine. interpreterProxy pop: 4. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:12'! primitiveAddOval | fillIndex borderWidth borderIndex endOop startOop | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. borderIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). borderWidth _ interpreterProxy stackIntegerValue: 1. fillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 2). endOop _ interpreterProxy stackObjectValue: 3. startOop _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the fills are okay" ((self isFillOkay: borderIndex) and:[self isFillOkay: fillIndex]) ifFalse:[^interpreterProxy primitiveFail]. "Transform colors" fillIndex _ self transformColor: fillIndex. borderIndex _ self transformColor: borderIndex. engineStopped ifTrue:[^interpreterProxy primitiveFail]. "Check if we have anything at all to do" (fillIndex = 0 and:[borderIndex = 0 or:[borderWidth <= 0]]) ifTrue:[ ^interpreterProxy pop: 5. "Leave rcvr on stack" ]. "Make sure we have some space" (self needAvailableSpace: (16 * GBBaseSize)) ifFalse:[^interpreterProxy primitiveFail]. "Check if we need a border" (borderWidth > 0 and:[borderIndex ~= 0]) ifTrue:[borderWidth _ self transformWidth: borderWidth] ifFalse:[borderWidth _ 0]. "Load the rectangle points" self loadPoint: self point1Get from: startOop. self loadPoint: self point2Get from: endOop. interpreterProxy failed ifTrue:[^0]. self loadOval: borderWidth lineFill: borderIndex leftFill: 0 rightFill: fillIndex. engineStopped ifTrue:[ self wbStackClear. ^interpreterProxy primitiveFail. ]. interpreterProxy failed ifFalse:[ self needsFlushPut: 1. self storeEngineStateInto: engine. interpreterProxy pop: 5. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:11'! primitiveAddPolygon | points lineFill lineWidth fillIndex nPoints length isArray segSize | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. lineFill _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). lineWidth _ interpreterProxy stackIntegerValue: 1. fillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 2). nPoints _ interpreterProxy stackIntegerValue: 3. points _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "First, do a check if the points look okay" length _ interpreterProxy slotSizeOf: points. (interpreterProxy isWords: points) ifTrue:[ isArray _ false. "Either PointArray or ShortPointArray" (length = nPoints or:[nPoints * 2 = length]) ifFalse:[^interpreterProxy primitiveFail]. ] ifFalse:["Must be Array of points" (interpreterProxy fetchClassOf: points) = interpreterProxy classArray ifFalse:[^interpreterProxy primitiveFail]. length = nPoints ifFalse:[^interpreterProxy primitiveFail]. isArray _ true. ]. "Now check that we have some hope to have enough free space. Do this by assuming nPoints boundaries of maximum size, hoping that most of the fills will be colors and many boundaries will be line segments" (lineWidth = 0 or:[lineFill = 0]) ifTrue:[segSize _ GLBaseSize] ifFalse:[segSize _ GLWideSize]. (self needAvailableSpace: segSize * nPoints) ifFalse:[^interpreterProxy primitiveFail]. "Check the fills" ((self isFillOkay: lineFill) and:[self isFillOkay: fillIndex]) ifFalse:[^interpreterProxy primitiveFail]. "Transform colors" lineFill _ self transformColor: lineFill. fillIndex _ self transformColor: fillIndex. engineStopped ifTrue:[^interpreterProxy primitiveFail]. "Check if have anything at all to do" ((lineFill = 0 or:[lineWidth = 0]) and:[fillIndex = 0]) ifTrue:[^interpreterProxy pop: 6]. "Transform the lineWidth" lineWidth = 0 ifFalse:[lineWidth _ self transformWidth: lineWidth]. "And load the actual polygon" isArray ifTrue:[ self loadArrayPolygon: points nPoints: nPoints fill: fillIndex lineWidth: lineWidth lineFill: lineFill ] ifFalse:[ self loadPolygon: (interpreterProxy firstIndexableField: points) nPoints: nPoints fill: fillIndex lineWidth: lineWidth lineFill: lineFill pointsShort: (nPoints = length)]. engineStopped ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self needsFlushPut: 1. self storeEngineStateInto: engine. interpreterProxy pop: 5. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:09'! primitiveAddRect | fillIndex borderWidth borderIndex endOop startOop | self export: true. self inline: false. "Fail if we have the wrong number of arguments" interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. borderIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). borderWidth _ interpreterProxy stackIntegerValue: 1. fillIndex _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 2). endOop _ interpreterProxy stackObjectValue: 3. startOop _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: (interpreterProxy stackObjectValue: 5) requiredState: GEStateUnlocked) ifFalse:[^interpreterProxy primitiveFail]. "Make sure the fills are okay" ((self isFillOkay: borderIndex) and:[self isFillOkay: fillIndex]) ifFalse:[^interpreterProxy primitiveFail]. "Transform colors" borderIndex _ self transformColor: borderIndex. fillIndex _ self transformColor: fillIndex. engineStopped ifTrue:[^interpreterProxy primitiveFail]. "Check if we have anything at all to do" (fillIndex = 0 and:[borderIndex = 0 or:[borderWidth = 0]]) ifTrue:[ ^interpreterProxy pop: 5. "Leave rcvr on stack" ]. "Make sure we have some space" (self needAvailableSpace: (4 * GLBaseSize)) ifFalse:[^interpreterProxy primitiveFail]. "Check if we need a border" (borderWidth > 0 and:[borderIndex ~= 0]) ifTrue:[borderWidth _ self transformWidth: borderWidth] ifFalse:[borderWidth _ 0]. "Load the rectangle" self loadPoint: self point1Get from: startOop. self loadPoint: self point3Get from: endOop. interpreterProxy failed ifTrue:[^nil]. self point2Get at: 0 put: (self point3Get at: 0). self point2Get at: 1 put: (self point1Get at: 1). self point4Get at: 0 put: (self point1Get at: 0). self point4Get at: 1 put: (self point3Get at: 1). "Transform the points" self transformPoints: 4. self loadRectangle: borderWidth lineFill: borderIndex leftFill: 0 rightFill: fillIndex. interpreterProxy failed ifFalse:[ self needsFlushPut: 1. self storeEngineStateInto: engine. interpreterProxy pop: 5. "Leave rcvr on stack" ].! ! !BalloonEnginePlugin methodsFor: 'primitives' stamp: 'ar 5/11/2000 23:06'! primitiveGetBezierStats | statOop stats | self export: true. self inline: false. self var: #stats declareC:'int *stats'. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. statOop _ interpreterProxy stackObjectValue: 0. engine _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (self quickLoadEngineFrom: engine) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: statOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: statOop) < 4 ifTrue:[^interpreterProxy primitiveFail]. stats _ interpreterProxy firstIndexableField: statOop. stats at: 0 put: (stats at: 0) + (workBuffer at: GWBezierMonotonSubdivisions). stats at: 1 put: (stats at: 1) + (workBuffer at: GWBezierHeightSubdivisions). stats at: 2 put: (stats at: 2) + (workBuffer at: GWBezierOverflowSubdivisions). stats at: 3 put: (stats at: 3) + (workBuffer at: GWBezierLineConversions). interpreterProxy pop: 1. "Leave rcvr on stack"! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineEndXOf: line ^self obj: line at: GLEndX! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineEndXOf: line put: value ^self obj: line at: GLEndX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineEndYOf: line ^self obj: line at: GLEndY! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineEndYOf: line put: value ^self obj: line at: GLEndY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineErrorAdjDownOf: line ^self obj: line at: GLErrorAdjDown! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:07'! lineErrorAdjDownOf: line put: value ^self obj: line at: GLErrorAdjDown put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:08'! lineErrorAdjUpOf: line ^self obj: line at: GLErrorAdjUp! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:08'! lineErrorAdjUpOf: line put: value ^self obj: line at: GLErrorAdjUp put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:08'! lineErrorOf: line ^self obj: line at: GLError! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:08'! lineErrorOf: line put: value ^self obj: line at: GLError put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:08'! lineXDirectionOf: line ^self obj: line at: GLXDirection! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! lineXDirectionOf: line put: value ^self obj: line at: GLXDirection put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! lineXIncrementOf: line ^self obj: line at: GLXIncrement! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! lineXIncrementOf: line put: value ^self obj: line at: GLXIncrement put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! lineYDirectionOf: line ^self obj: line at: GLYDirection! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! lineYDirectionOf: line put: value ^self obj: line at: GLYDirection put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:09'! wideLineEntryOf: line ^self obj: line at: GLWideEntry! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineEntryOf: line put: value ^self obj: line at: GLWideEntry put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineExitOf: line ^self obj: line at: GLWideExit! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineExitOf: line put: value ^self obj: line at: GLWideExit put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineExtentOf: line ^self obj: line at: GLWideExtent! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineExtentOf: line put: value ^self obj: line at: GLWideExtent put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:10'! wideLineFillOf: line ^self obj: line at: GLWideFill! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:11'! wideLineFillOf: line put: value ^self obj: line at: GLWideFill put: value! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:11'! wideLineWidthOf: line ^self obj: line at: GLWideWidth! ! !BalloonEnginePlugin methodsFor: 'accessing lines' stamp: 'ar 11/24/1998 22:11'! wideLineWidthOf: line put: value ^self obj: line at: GLWideWidth put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! bezierEndXOf: bezier ^self obj: bezier at: GBEndX! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:17'! bezierEndXOf: bezier put: value ^self obj: bezier at: GBEndX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:17'! bezierEndYOf: bezier ^self obj: bezier at: GBEndY! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! bezierEndYOf: bezier put: value ^self obj: bezier at: GBEndY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:19'! bezierFinalXOf: bezier ^self obj: bezier at: GBFinalX! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! bezierFinalXOf: bezier put: value ^self obj: bezier at: GBFinalX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:24'! bezierUpdateDataOf: bezier self returnTypeC: 'int *'. ^objBuffer + bezier + GBUpdateData! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! bezierViaXOf: bezier ^self obj: bezier at: GBViaX! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:17'! bezierViaXOf: bezier put: value ^self obj: bezier at: GBViaX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:17'! bezierViaYOf: bezier ^self obj: bezier at: GBViaY! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! bezierViaYOf: bezier put: value ^self obj: bezier at: GBViaY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzEndX: index ^self wbStackValue: self wbStackSize - index + 4! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzEndX: index put: value ^self wbStackValue: self wbStackSize - index + 4 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzEndY: index ^self wbStackValue: self wbStackSize - index + 5! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzEndY: index put: value ^self wbStackValue: self wbStackSize - index + 5 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:54'! bzStartX: index ^self wbStackValue: self wbStackSize - index + 0! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:54'! bzStartX: index put: value ^self wbStackValue: self wbStackSize - index + 0 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzStartY: index ^self wbStackValue: self wbStackSize - index + 1! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:54'! bzStartY: index put: value ^self wbStackValue: self wbStackSize - index + 1 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzViaX: index ^self wbStackValue: self wbStackSize - index + 2! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:54'! bzViaX: index put: value ^self wbStackValue: self wbStackSize - index + 2 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:53'! bzViaY: index ^self wbStackValue: self wbStackSize - index + 3! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 10/30/1998 20:54'! bzViaY: index put: value ^self wbStackValue: self wbStackSize - index + 3 put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! wideBezierEntryOf: line ^self obj: line at: GBWideEntry! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! wideBezierEntryOf: line put: value ^self obj: line at: GBWideEntry put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! wideBezierExitOf: line ^self obj: line at: GBWideExit! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:18'! wideBezierExitOf: line put: value ^self obj: line at: GBWideExit put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! wideBezierExtentOf: bezier ^self obj: bezier at: GBWideExtent! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! wideBezierExtentOf: bezier put: value ^self obj: bezier at: GBWideExtent put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:19'! wideBezierFillOf: bezier ^self obj: bezier at: GBWideFill! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:17'! wideBezierFillOf: bezier put: value ^self obj: bezier at: GBWideFill put: value! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:25'! wideBezierUpdateDataOf: bezier self returnTypeC: 'int *'. ^objBuffer + bezier + GBWideUpdateData! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! wideBezierWidthOf: line ^self obj: line at: GBWideWidth! ! !BalloonEnginePlugin methodsFor: 'accessing beziers' stamp: 'ar 11/24/1998 22:20'! wideBezierWidthOf: line put: value ^self obj: line at: GBWideWidth put: value! ! !BalloonEnginePlugin methodsFor: 'accessing gradients' stamp: 'ar 11/24/1998 22:18'! gradientRampLengthOf: fill ^self obj: fill at: GFRampLength! ! !BalloonEnginePlugin methodsFor: 'accessing gradients' stamp: 'ar 11/24/1998 22:17'! gradientRampLengthOf: fill put: value ^self obj: fill at: GFRampLength put: value! ! !BalloonEnginePlugin methodsFor: 'accessing gradients' stamp: 'ar 11/24/1998 22:25'! gradientRampOf: fill self returnTypeC:'int *'. ^objBuffer + fill + GFRampOffset! ! !BalloonEnginePlugin methodsFor: 'testing' stamp: 'ar 11/4/1998 21:46'! isBezier: bezier ^((self objectTypeOf: bezier) bitAnd: GEPrimitiveWideMask) = GEPrimitiveBezier! ! !BalloonEnginePlugin methodsFor: 'testing' stamp: 'ar 11/8/1998 15:14'! isFillOkay: fill self inline: false. ^(fill = 0 or:[(self isFillColor: fill) or:[((self isObject: fill) and:[self isFill: fill])]]) ! ! !BalloonEnginePlugin methodsFor: 'testing' stamp: 'ar 11/4/1998 21:46'! isLine: line ^((self objectTypeOf: line) bitAnd: GEPrimitiveWideMask) = GEPrimitiveLine! ! !BalloonEnginePlugin methodsFor: 'testing' stamp: 'ar 11/6/1998 01:53'! isWideBezier: bezier ^(self isBezier: bezier) and:[self isWide: bezier]! ! !BalloonEnginePlugin methodsFor: 'testing' stamp: 'ar 11/4/1998 22:08'! isWideLine: line ^(self isLine: line) and:[self isWide: line]! ! !BalloonEnginePlugin methodsFor: 'lines-simple' stamp: 'ar 11/4/1998 21:52'! stepToFirstLine "Initialize the current entry in the GET by stepping to the current scan line" self inline: true. ^self stepToFirstLineIn: (getBuffer at: self getStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'lines-simple' stamp: 'ar 11/9/1998 15:38'! stepToFirstLineIn: line at: yValue "Initialize the line at yValue" | deltaX deltaY xDir widthX error xInc errorAdjUp startY | self inline: false. "Do a quick check if there is anything at all to do" ((self isWide: line) not and:[yValue >= (self lineEndYOf: line)]) ifTrue:[^self edgeNumLinesOf: line put: 0]. deltaX _ (self lineEndXOf: line) - (self edgeXValueOf: line). deltaY _ (self lineEndYOf: line) - (self edgeYValueOf: line). "Check if edge goes left to right" deltaX >= 0 ifTrue:[ xDir _ 1. widthX _ deltaX. error _ 0] ifFalse:[ xDir _ -1. widthX _ 0 - deltaX. error _ 1 - deltaY]. "Check if deltaY is zero. Note: We could actually get out here immediately but wide lines rely on an accurate setup in this case" deltaY = 0 ifTrue:[ error _ 0. "No error for horizontal edges" xInc _ deltaX. "Encodes width and direction" errorAdjUp _ 0] ifFalse:["Check if edge is y-major" deltaY > widthX "Note: The '>' instead of '>=' could be important here..." ifTrue:[ xInc _ 0. errorAdjUp _ widthX] ifFalse:[ xInc _ (widthX // deltaY) * xDir. errorAdjUp _ widthX \\ deltaY]]. "Store the values" self edgeNumLinesOf: line put: deltaY. self lineXDirectionOf: line put: xDir. "self lineYDirectionOf: line put: yDir." "<-- Already set" self lineXIncrementOf: line put: xInc. self lineErrorOf: line put: error. self lineErrorAdjUpOf: line put: errorAdjUp. self lineErrorAdjDownOf: line put: deltaY. "And step to the first scan line" (startY _ self edgeYValueOf: line) = yValue ifFalse:[ startY to: yValue-1 do:[:i| self stepToNextLineIn: line at: i]. "Adjust number of lines remaining" self edgeNumLinesOf: line put: deltaY - (yValue - startY). ].! ! !BalloonEnginePlugin methodsFor: 'lines-simple' stamp: 'ar 11/4/1998 21:53'! stepToNextLine "Process the current entry in the AET by stepping to the next scan line" self inline: true. ^self stepToNextLineIn: (aetBuffer at: self aetStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'lines-simple' stamp: 'ar 11/9/1998 15:39'! stepToNextLineIn: line at: yValue "Incrementally step to the next scan line in the given line" | x err | self inline: true. x _ (self edgeXValueOf: line) + (self lineXIncrementOf: line). err _ (self lineErrorOf: line) + (self lineErrorAdjUpOf: line). err > 0 ifTrue:[ x _ x + (self lineXDirectionOf: line). err _ err - (self lineErrorAdjDownOf: line). ]. self lineErrorOf: line put: err. self edgeXValueOf: line put: x.! ! !BalloonEnginePlugin methodsFor: 'lines-loading' stamp: 'ar 11/24/1998 23:15'! loadLine: line from: point1 to: point2 offset: yOffset leftFill: leftFill rightFill: rightFill "Load the line defined by point1 and point2." | p1 p2 yDir | self var: #point1 declareC:'int *point1'. self var: #point2 declareC:'int *point2'. self var: #p1 declareC:'int *p1'. self var: #p2 declareC:'int *p2'. (point1 at: 1) <= (point2 at: 1) ifTrue:[ p1 _ point1. p2 _ point2. yDir _ 1] ifFalse:[ p1 _ point2. p2 _ point1. yDir _ -1]. self edgeXValueOf: line put: (p1 at: 0). self edgeYValueOf: line put: (p1 at: 1) - yOffset. self edgeZValueOf: line put: self currentZGet. self edgeLeftFillOf: line put: leftFill. self edgeRightFillOf: line put: rightFill. self lineEndXOf: line put: (p2 at: 0). self lineEndYOf: line put: (p2 at: 1) - yOffset. self lineYDirectionOf: line put: yDir.! ! !BalloonEnginePlugin methodsFor: 'lines-loading' stamp: 'ar 11/6/1998 17:07'! loadRectangle: lineWidth lineFill: lineFill leftFill: leftFill rightFill: rightFill "Load a rectangle currently defined by point1-point4" self loadWideLine: lineWidth from: self point1Get to: self point2Get lineFill: lineFill leftFill: leftFill rightFill: rightFill. self loadWideLine: lineWidth from: self point2Get to: self point3Get lineFill: lineFill leftFill: leftFill rightFill: rightFill. self loadWideLine: lineWidth from: self point3Get to: self point4Get lineFill: lineFill leftFill: leftFill rightFill: rightFill. self loadWideLine: lineWidth from: self point4Get to: self point1Get lineFill: lineFill leftFill: leftFill rightFill: rightFill. ! ! !BalloonEnginePlugin methodsFor: 'lines-loading' stamp: 'ar 11/8/1998 19:24'! loadWideLine: lineWidth from: p1 to: p2 lineFill: lineFill leftFill: leftFill rightFill: rightFill "Load a (possibly wide) line defined by the points p1 and p2" | line offset | self var: #p1 declareC:'int *p1'. self var: #p2 declareC:'int *p2'. (lineWidth = 0 or:[lineFill = 0]) ifTrue:[ line _ self allocateLine. offset _ 0] ifFalse:[ line _ self allocateWideLine. offset _ self offsetFromWidth: lineWidth]. engineStopped ifTrue:[^0]. self loadLine: line from: p1 to: p2 offset: offset leftFill: leftFill rightFill: rightFill. (self isWide: line) ifTrue:[ self wideLineFillOf: line put: lineFill. self wideLineWidthOf: line put: lineWidth. self wideLineExtentOf: line put: lineWidth].! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/9/1998 15:34'! adjustWideLine: line afterSteppingFrom: lastX to: nextX "Adjust the wide line after it has been stepped from lastX to nextX. Special adjustments of line width and start position are made here to simulate a rectangular brush" | yEntry yExit lineWidth lineOffset deltaX xDir baseWidth | self inline: false. "Don't inline this" "Fetch the values the adjustment decisions are based on" yEntry _ (self wideLineEntryOf: line). yExit _ (self wideLineExitOf: line). baseWidth _ self wideLineExtentOf: line. lineOffset _ self offsetFromWidth: baseWidth. lineWidth _ self wideLineWidthOf: line. xDir _ self lineXDirectionOf: line. deltaX _ nextX - lastX. "Adjust the start of the line to fill an entire rectangle" yEntry < baseWidth ifTrue:[ xDir < 0 ifTrue:[ lineWidth _ lineWidth - deltaX] "effectively adding" ifFalse:[ lineWidth _ lineWidth + deltaX. self edgeXValueOf: line put: lastX]. ]. "Adjust the end of x-major lines" ((yExit + lineOffset) = 0) ifTrue:[ xDir > 0 ifTrue:[lineWidth _ lineWidth - (self lineXIncrementOf: line)] ifFalse:[lineWidth _ lineWidth + (self lineXIncrementOf: line). "effectively subtracting" self edgeXValueOf: line put: lastX]. ]. "Adjust the end of the line to fill an entire rectangle" (yExit + lineOffset) > 0 ifTrue:[ xDir < 0 ifTrue:[ lineWidth _ lineWidth + deltaX. "effectively subtracting" self edgeXValueOf: line put: lastX] ifFalse:[ lineWidth _ lineWidth - deltaX] ]. "Store the manipulated line width back" self wideLineWidthOf: line put: lineWidth.! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/6/1998 17:08'! returnWideLineFill "Return the fill of the (wide) line - this method is called from a case." ^(dispatchReturnValue _ self wideLineFillOf: dispatchedValue).! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/6/1998 17:08'! returnWideLineWidth "Return the width of the (wide) line - this method is called from a case." ^(dispatchReturnValue _ self wideLineWidthOf: dispatchedValue).! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/4/1998 21:54'! stepToFirstWideLine "Initialize the current entry in the GET by stepping to the current scan line" self inline: true. ^self stepToFirstWideLineIn: (getBuffer at: self getStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/9/1998 15:38'! stepToFirstWideLineIn: line at: yValue "Initialize the wide line at yValue." | startY yEntry yExit lineWidth nLines lineOffset startX xDir | self inline: false. "Get some values" lineWidth _ self wideLineExtentOf: line. lineOffset _ self offsetFromWidth: lineWidth. "Compute the incremental values of the line" startX _ self edgeXValueOf: line. startY _ self edgeYValueOf: line. self stepToFirstLineIn: line at: startY. nLines _ (self edgeNumLinesOf: line). xDir _ self lineXDirectionOf: line. "Adjust the line to start at the correct X position" self edgeXValueOf: line put: startX - lineOffset. "Adjust the number of lines to include the lineWidth" self edgeNumLinesOf: line put: nLines + lineWidth. "Adjust the values for x-major lines" xDir > 0 ifTrue:[ self wideLineWidthOf: line put: (self lineXIncrementOf: line) + lineWidth. ] ifFalse:[ self wideLineWidthOf: line put: lineWidth - (self lineXIncrementOf: line). "adding" self edgeXValueOf: line put: (self edgeXValueOf: line) + (self lineXIncrementOf: line). ]. "Compute the points where we have to turn on/off the fills" yEntry _ 0. "turned on at lineOffset" yExit _ 0 - nLines - lineOffset. "turned off at zero" self wideLineEntryOf: line put: yEntry. self wideLineExitOf: line put: yExit. "Turn the fills on/off as necessary" (yEntry >= lineOffset and:[yExit < 0]) ifTrue:[self edgeFillsValidate: line] ifFalse:[self edgeFillsInvalidate: line]. "And step to the first scan line" startY = yValue ifFalse:[ startY to: yValue-1 do:[:i| self stepToNextWideLineIn: line at: i]. "Adjust number of lines remaining" self edgeNumLinesOf: line put: (self edgeNumLinesOf: line) - (yValue - startY). ]. ! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/4/1998 21:55'! stepToNextWideLine "Process the current entry in the AET by stepping to the next scan line" self inline: true. ^self stepToNextWideLineIn: (aetBuffer at: self aetStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'lines-wide' stamp: 'ar 11/9/1998 15:39'! stepToNextWideLineIn: line at: yValue "Incrementally step to the next scan line in the given wide line" | yEntry yExit lineWidth lineOffset lastX nextX | self inline: true. "Adjust entry/exit values" yEntry _ (self wideLineEntryOf: line) + 1. yExit _ (self wideLineExitOf: line) + 1. self wideLineEntryOf: line put: yEntry. self wideLineExitOf: line put: yExit. "Turn fills on/off" lineWidth _ self wideLineExtentOf: line. lineOffset _ self offsetFromWidth: lineWidth. yEntry >= lineOffset ifTrue:[self edgeFillsValidate: line]. yExit >= 0 ifTrue:[self edgeFillsInvalidate: line]. "Step to the next scan line" lastX _ self edgeXValueOf: line. self stepToNextLineIn: line at: yValue. nextX _ self edgeXValueOf: line. "Check for special start/end adjustments" (yEntry <= lineWidth or:[yExit+lineOffset >= 0]) ifTrue:[ "Yes, need an update" self adjustWideLine: line afterSteppingFrom: lastX to: nextX. ].! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/9/1998 01:56'! assureValue: val1 between: val2 and: val3 "Make sure that val1 is between val2 and val3." self inline: true. val2 > val3 ifTrue:[ val1 > val2 ifTrue:[^val2]. val1 < val3 ifTrue:[^val3]. ] ifFalse:[ val1 < val2 ifTrue:[^val2]. val1 > val3 ifTrue:[^val3]. ]. ^val1 ! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/9/1998 01:57'! computeBezier: index splitAt: param "Split the bezier curve at the given parametric value. Note: Since this method is only invoked to make non-monoton beziers monoton we must check for the resulting y values to be *really* between the start and end value." | startX startY viaX viaY endX endY newIndex leftViaX leftViaY rightViaX rightViaY sharedX sharedY | self inline: false. self var: #param declareC:'double param'. leftViaX _ startX _ self bzStartX: index. leftViaY _ startY _ self bzStartY: index. rightViaX _ viaX _ self bzViaX: index. rightViaY _ viaY _ self bzViaY: index. endX _ self bzEndX: index. endY _ self bzEndY: index. "Compute intermediate points" sharedX _ leftViaX _ leftViaX + ((viaX - startX) asFloat * param) asInteger. sharedY _ leftViaY _ leftViaY + ((viaY - startY) asFloat * param) asInteger. rightViaX _ rightViaX + ((endX - viaX) asFloat * param) asInteger. rightViaY _ rightViaY + ((endY - viaY) asFloat * param) asInteger. "Compute new shared point" sharedX _ sharedX + ((rightViaX - leftViaX) asFloat * param) asInteger. sharedY _ sharedY + ((rightViaY - leftViaY) asFloat * param) asInteger. "Check the new via points" leftViaY _ self assureValue: leftViaY between: startY and: sharedY. rightViaY _ self assureValue: rightViaY between: sharedY and: endY. newIndex _ self allocateBezierStackEntry. engineStopped ifTrue:[^0]. "Something went wrong" "Store the first part back" self bzViaX: index put: leftViaX. self bzViaY: index put: leftViaY. self bzEndX: index put: sharedX. self bzEndY: index put: sharedY. "Store the second point back" self bzStartX: newIndex put: sharedX. self bzStartY: newIndex put: sharedY. self bzViaX: newIndex put: rightViaX. self bzViaY: newIndex put: rightViaY. self bzEndX: newIndex put: endX. self bzEndY: newIndex put: endY. ^newIndex! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/6/1998 01:26'! computeBezierSplitAtHalf: index "Split the bezier curve at 0.5." | startX startY viaX viaY endX endY newIndex leftViaX leftViaY rightViaX rightViaY sharedX sharedY | self inline: false. newIndex _ self allocateBezierStackEntry. engineStopped ifTrue:[^0]. "Something went wrong" leftViaX _ startX _ self bzStartX: index. leftViaY _ startY _ self bzStartY: index. rightViaX _ viaX _ self bzViaX: index. rightViaY _ viaY _ self bzViaY: index. endX _ self bzEndX: index. endY _ self bzEndY: index. "Compute intermediate points" leftViaX _ leftViaX + ((viaX - startX) // 2). leftViaY _ leftViaY + ((viaY - startY) // 2). sharedX _ rightViaX _ rightViaX + ((endX - viaX) // 2). sharedY _ rightViaY _ rightViaY + ((endY - viaY) // 2). "Compute new shared point" sharedX _ sharedX + ((leftViaX - rightViaX) // 2). sharedY _ sharedY + ((leftViaY - rightViaY) // 2). "Store the first part back" self bzViaX: index put: leftViaX. self bzViaY: index put: leftViaY. self bzEndX: index put: sharedX. self bzEndY: index put: sharedY. "Store the second point back" self bzStartX: newIndex put: sharedX. self bzStartY: newIndex put: sharedY. self bzViaX: newIndex put: rightViaX. self bzViaY: newIndex put: rightViaY. self bzEndX: newIndex put: endX. self bzEndY: newIndex put: endY. ^newIndex! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 20:15'! loadAndSubdivideBezierFrom: point1 via: point2 to: point3 isWide: wideFlag "Load and subdivide the bezier curve from point1/point2/point3. If wideFlag is set then make sure the curve is monoton in X." | bz1 bz2 index2 index1 | self inline: false. self var: #point1 declareC:'int *point1'. self var: #point2 declareC:'int *point2'. self var: #point3 declareC:'int *point3'. bz1 _ self allocateBezierStackEntry. engineStopped ifTrue:[^0]. "Load point1/point2/point3 on the top of the stack" self bzStartX: bz1 put: (point1 at: 0). self bzStartY: bz1 put: (point1 at: 1). self bzViaX: bz1 put: (point2 at: 0). self bzViaY: bz1 put: (point2 at: 1). self bzEndX: bz1 put: (point3 at: 0). self bzEndY: bz1 put: (point3 at: 1). "Now check if the bezier curve is monoton. If not, subdivide it." index2 _ bz2 _ self subdivideToBeMonoton: bz1 inX: wideFlag. bz1 to: bz2 by: 6 do:[:index| index1 _ self subdivideBezierFrom: index. index1 > index2 ifTrue:[index2 _ index1]. engineStopped ifTrue:[^0]. "Something went wrong" ]. "Return the number of segments" ^index2 // 6! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/24/1998 23:15'! loadBezier: bezier segment: index leftFill: leftFillIndex rightFill: rightFillIndex offset: yOffset "Initialize the bezier segment stored on the stack" self inline: false. (self bzEndY: index) >= (self bzStartY: index) ifTrue:[ "Top to bottom" self edgeXValueOf: bezier put: (self bzStartX: index). self edgeYValueOf: bezier put: (self bzStartY: index) - yOffset. self bezierViaXOf: bezier put: (self bzViaX: index). self bezierViaYOf: bezier put: (self bzViaY: index) - yOffset. self bezierEndXOf: bezier put: (self bzEndX: index). self bezierEndYOf: bezier put: (self bzEndY: index) - yOffset. ] ifFalse:[ self edgeXValueOf: bezier put: (self bzEndX: index). self edgeYValueOf: bezier put: (self bzEndY: index) - yOffset. self bezierViaXOf: bezier put: (self bzViaX: index). self bezierViaYOf: bezier put: (self bzViaY: index) - yOffset. self bezierEndXOf: bezier put: (self bzStartX: index). self bezierEndYOf: bezier put: (self bzStartY: index) - yOffset. ]. self edgeZValueOf: bezier put: self currentZGet. self edgeLeftFillOf: bezier put: leftFillIndex. self edgeRightFillOf: bezier put: rightFillIndex. "self debugDrawBezier: bezier."! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/25/1998 23:21'! loadOval: lineWidth lineFill: lineFill leftFill: leftFill rightFill: rightFill "Load a rectangular oval currently defined by point1/point2" | w h cx cy nSegments | self inline: false. w _ ((self point2Get at: 0) - (self point1Get at: 0)) // 2. h _ ((self point2Get at: 1) - (self point1Get at: 1)) // 2. cx _ ((self point2Get at: 0) + (self point1Get at: 0)) // 2. cy _ ((self point2Get at: 1) + (self point1Get at: 1)) // 2. 0 to: 15 do:[:i| self loadOvalSegment: i w: w h: h cx: cx cy: cy. self transformPoints: 3. nSegments _ self loadAndSubdivideBezierFrom: self point1Get via: self point2Get to: self point3Get isWide: (lineWidth ~= 0 and:[lineFill ~= 0]). engineStopped ifTrue:[^nil]. self loadWideBezier: lineWidth lineFill: lineFill leftFill: leftFill rightFill: rightFill n: nSegments. engineStopped ifTrue:[^nil]. ].! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 15:17'! loadOvalSegment: seg w: w h: h cx: cx cy: cy | x0 y0 x2 y2 x1 y1 | self inline: false. "Load start point of segment" x0 _ ((self circleCosTable at: seg * 2 + 0) * w asFloat + cx) asInteger. y0 _ ((self circleSinTable at: seg * 2 + 0) * h asFloat + cy) asInteger. self point1Get at: 0 put: x0. self point1Get at: 1 put: y0. "Load end point of segment" x2 _ ((self circleCosTable at: seg * 2 + 2) * w asFloat + cx) asInteger. y2 _ ((self circleSinTable at: seg * 2 + 2) * h asFloat + cy) asInteger. self point3Get at: 0 put: x2. self point3Get at: 1 put: y2. "Load intermediate point of segment" x1 _ ((self circleCosTable at: seg * 2 + 1) * w asFloat + cx) asInteger. y1 _ ((self circleSinTable at: seg * 2 + 1) * h asFloat + cy) asInteger. "NOTE: The intermediate point is the point ON the curve and not yet the control point (which is OFF the curve)" x1 _ (x1 * 2) - (x0 + x2 // 2). y1 _ (y1 * 2) - (y0 + y2 // 2). self point2Get at: 0 put: x1. self point2Get at: 1 put: y1.! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 03:41'! loadWideBezier: lineWidth lineFill: lineFill leftFill: leftFill rightFill: rightFill n: nSegments "Load the (possibly wide) bezier from the segments currently on the bezier stack." | index bezier wide offset | self inline: false. (lineWidth = 0 or:[lineFill = 0]) ifTrue:[wide _ false. offset _ 0] ifFalse:[wide _ true. offset _ self offsetFromWidth: lineWidth]. index _ nSegments * 6. [index > 0] whileTrue:[ wide ifTrue:[bezier _ self allocateWideBezier] ifFalse:[bezier _ self allocateBezier]. engineStopped ifTrue:[^0]. self loadBezier: bezier segment: index leftFill: leftFill rightFill: rightFill offset: offset. wide ifTrue:[ self wideBezierFillOf: bezier put: lineFill. self wideBezierWidthOf: bezier put: lineWidth. self wideBezierExtentOf: bezier put: lineWidth. ]. index _ index - 6. ]. self wbStackClear.! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 14:36'! subdivideBezier: index "Subdivide the given bezier curve if necessary" | startX startY endX endY deltaX deltaY | self inline: false. startY _ self bzStartY: index. endY _ self bzEndY: index. "If the receiver is horizontal, don't do anything" (endY = startY) ifTrue:[^index]. "TODO: If the curve can be represented as a line, then do so" "If the height of the curve exceeds 256 pixels, subdivide (forward differencing is numerically not very stable)" deltaY _ endY - startY. deltaY < 0 ifTrue:[deltaY _ 0 - deltaY]. (deltaY > 255) ifTrue:[ self incrementStat: GWBezierHeightSubdivisions by: 1. ^self computeBezierSplitAtHalf: index]. "Check if the incremental values could possibly overflow the scaled integer range" startX _ self bzStartX: index. endX _ self bzEndX: index. deltaX _ endX - startX. deltaX < 0 ifTrue:[deltaX _ 0 - deltaX]. deltaY * 32 < deltaX ifTrue:[ self incrementStat: GWBezierOverflowSubdivisions by: 1. ^self computeBezierSplitAtHalf: index]. ^index ! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 03:43'! subdivideBezierFrom: index "Recursively subdivide the curve on the bezier stack." | otherIndex index1 index2 | self inline: false. otherIndex _ self subdivideBezier: index. otherIndex = index ifFalse:[ index1 _ self subdivideBezierFrom: index. engineStopped ifTrue:[^0]. index2 _ self subdivideBezierFrom: otherIndex. engineStopped ifTrue:[^0]. index1 >= index2 ifTrue:[^index1] ifFalse:[^index2] ]. ^index! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/8/1998 15:17'! subdivideToBeMonoton: base inX: doTestX "Check if the given bezier curve is monoton in Y, and, if desired in X. If not, subdivide it" | index1 index2 base2 | self inline: false. base2 _ index1 _ index2 _ self subdivideToBeMonotonInY: base. doTestX ifTrue:[index1 _ self subdivideToBeMonotonInX: base]. index1 > index2 ifTrue:[index2 _ index1]. (base ~= base2 and:[doTestX]) ifTrue:[index1 _ self subdivideToBeMonotonInX: base2]. index1 > index2 ifTrue:[index2 _ index1]. ^index2! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/7/1998 19:42'! subdivideToBeMonotonInX: index "Check if the given bezier curve is monoton in X. If not, subdivide it" | denom num startX viaX endX dx1 dx2 | self inline: false. startX _ self bzStartX: index. viaX _ self bzViaX: index. endX _ self bzEndX: index. dx1 _ viaX - startX. dx2 _ endX - viaX. (dx1 * dx2) >= 0 ifTrue:[^index]. "Bezier is monoton" self incrementStat: GWBezierMonotonSubdivisions by: 1. "Compute split value" denom _ dx2 - dx1. num _ dx1. num < 0 ifTrue:[num _ 0 - num]. denom < 0 ifTrue:[denom _ 0 - denom]. ^self computeBezier: index splitAt: (num asFloat / denom asFloat).! ! !BalloonEnginePlugin methodsFor: 'bezier-loading' stamp: 'ar 11/7/1998 19:42'! subdivideToBeMonotonInY: index "Check if the given bezier curve is monoton in Y. If not, subdivide it" | startY viaY endY dy1 dy2 denom num | self inline: false. startY _ self bzStartY: index. viaY _ self bzViaY: index. endY _ self bzEndY: index. dy1 _ viaY - startY. dy2 _ endY - viaY. (dy1 * dy2) >= 0 ifTrue:[^index]. "Bezier is monoton" self incrementStat: GWBezierMonotonSubdivisions by: 1. "Compute split value" denom _ dy2 - dy1. num _ dy1. num < 0 ifTrue:[num _ 0 - num]. denom < 0 ifTrue:[denom _ 0 - denom]. ^self computeBezier: index splitAt: (num asFloat / denom asFloat).! ! !BalloonEnginePlugin methodsFor: 'beziers-simple' stamp: 'ar 11/6/1998 00:07'! stepToFirstBezier "Initialize the current entry in the GET by stepping to the current scan line" self inline: true. ^self stepToFirstBezierIn: (getBuffer at: self getStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'beziers-simple' stamp: 'ar 11/9/1998 15:38'! stepToFirstBezierIn: bezier at: yValue "Initialize the bezier at yValue. TODO: Check if reducing maxSteps from 2*deltaY to deltaY brings a *significant* performance improvement. In theory this should make for double step performance but will cost in quality. Might be that the AA stuff will compensate for this - but I'm not really sure." | updateData deltaY maxSteps scaledStepSize squaredStepSize startX startY viaX viaY endX endY fwX1 fwX2 fwY1 fwY2 fwDx fwDDx fwDy fwDDy | self inline: false. "Too many temps for useful inlining" self var: #updateData declareC:'int *updateData'. "Do a quick check if there is anything at all to do" ((self isWide: bezier) not and:[yValue >= (self bezierEndYOf: bezier)]) ifTrue:[^self edgeNumLinesOf: bezier put: 0]. "Now really initialize bezier" startX _ self edgeXValueOf: bezier. startY _ self edgeYValueOf: bezier. viaX _ self bezierViaXOf: bezier. viaY _ self bezierViaYOf: bezier. endX _ self bezierEndXOf: bezier. endY _ self bezierEndYOf: bezier. deltaY _ endY - startY. "Initialize integer forward differencing" fwX1 _ (viaX - startX) * 2. fwX2 _ startX + endX - (viaX * 2). fwY1 _ (viaY - startY) * 2. fwY2 _ startY + endY - (viaY * 2). maxSteps _ deltaY * 2. maxSteps < 2 ifTrue:[maxSteps _ 2]. scaledStepSize _ 16r1000000 // maxSteps. squaredStepSize _ self absoluteSquared8Dot24: scaledStepSize. fwDx _ fwX1 * scaledStepSize. fwDDx _ fwX2 * squaredStepSize * 2. fwDx _ fwDx + (fwDDx // 2). fwDy _ fwY1 * scaledStepSize. fwDDy _ fwY2 * squaredStepSize * 2. fwDy _ fwDy + (fwDDy // 2). "Store the values" self edgeNumLinesOf: bezier put: deltaY. updateData _ self bezierUpdateDataOf: bezier. updateData at: GBUpdateX put: (startX * 256). updateData at: GBUpdateY put: (startY * 256). updateData at: GBUpdateDX put: fwDx. updateData at: GBUpdateDY put: fwDy. updateData at: GBUpdateDDX put: fwDDx. updateData at: GBUpdateDDY put: fwDDy. "And step to the first scan line" (startY _ self edgeYValueOf: bezier) = yValue ifFalse:[ self stepToNextBezierIn: bezier at: yValue. "Adjust number of lines remaining" self edgeNumLinesOf: bezier put: deltaY - (yValue - startY). ].! ! !BalloonEnginePlugin methodsFor: 'beziers-simple' stamp: 'ar 11/6/1998 00:08'! stepToNextBezier "Process the current entry in the AET by stepping to the next scan line" self inline: true. ^self stepToNextBezierIn: (aetBuffer at: self aetStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'beziers-simple' stamp: 'ar 11/9/1998 01:49'! stepToNextBezierForward: updateData at: yValue "Incrementally step to the next scan line in the given bezier update data. Note: This method has been written so that inlining works, e.g., not declaring updateData as 'int*' but casting it on every use." | minY lastX lastY fwDx fwDy | self inline: true. lastX _ (self cCoerce: updateData to: 'int*') at: GBUpdateX. lastY _ (self cCoerce: updateData to: 'int*') at: GBUpdateY. fwDx _ (self cCoerce: updateData to: 'int*') at: GBUpdateDX. fwDy _ (self cCoerce: updateData to: 'int*') at: GBUpdateDY. minY _ yValue * 256. "Step as long as we haven't yet reached minY and also as long as fwDy is greater than zero thus stepping down. Note: The test for fwDy should not be necessary in theory but is a good insurance in practice." [minY > lastY and:[fwDy >= 0]] whileTrue:[ lastX _ lastX + ((fwDx + 16r8000) // 16r10000). lastY _ lastY + ((fwDy + 16r8000) // 16r10000). fwDx _ fwDx + ((self cCoerce: updateData to: 'int*') at: GBUpdateDDX). fwDy _ fwDy + ((self cCoerce: updateData to: 'int*') at: GBUpdateDDY). ]. (self cCoerce: updateData to: 'int*') at: GBUpdateX put: lastX. (self cCoerce: updateData to: 'int*') at: GBUpdateY put: lastY. (self cCoerce: updateData to: 'int*') at: GBUpdateDX put: fwDx. (self cCoerce: updateData to: 'int*') at: GBUpdateDY put: fwDy. ^lastX // 256 ! ! !BalloonEnginePlugin methodsFor: 'beziers-simple' stamp: 'ar 11/9/1998 15:39'! stepToNextBezierIn: bezier at: yValue "Incrementally step to the next scan line in the given bezier" | xValue | self inline: true. xValue _ self stepToNextBezierForward: (self bezierUpdateDataOf: bezier) at: yValue. self edgeXValueOf: bezier put: xValue.! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/8/1998 15:18'! adjustWideBezierLeft: bezier width: lineWidth offset: lineOffset endX: endX "Adjust the wide bezier curve (dx < 0) to start/end at the right point" | lastX lastY | self inline: false. (self bezierUpdateDataOf: bezier) at: GBUpdateX put: (((self bezierUpdateDataOf: bezier) at: GBUpdateX) - (lineOffset * 256)). "Set the lastX/Y value of the second curve lineWidth pixels right/down" lastX _ (self wideBezierUpdateDataOf: bezier) at: GBUpdateX. (self wideBezierUpdateDataOf: bezier) at: GBUpdateX put: lastX + (lineWidth - lineOffset * 256). "Set lineWidth pixels down" lastY _ (self wideBezierUpdateDataOf: bezier) at: GBUpdateY. (self wideBezierUpdateDataOf: bezier) at: GBUpdateY put: lastY + (lineWidth * 256). "Record the last X value" self bezierFinalXOf: bezier put: endX - lineOffset. ! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/8/1998 15:18'! adjustWideBezierRight: bezier width: lineWidth offset: lineOffset endX: endX "Adjust the wide bezier curve (dx >= 0) to start/end at the right point" | lastX lastY | self inline: false. (self bezierUpdateDataOf: bezier) at: GBUpdateX put: (((self bezierUpdateDataOf: bezier) at: GBUpdateX) + (lineOffset * 256)). "Set the lastX/Y value of the second curve lineWidth pixels right/down" "Set lineWidth-lineOffset pixels left" lastX _ (self wideBezierUpdateDataOf: bezier) at: GBUpdateX. (self wideBezierUpdateDataOf: bezier) at: GBUpdateX put: lastX - (lineWidth - lineOffset * 256). lastY _ (self wideBezierUpdateDataOf: bezier) at: GBUpdateY. "Set lineWidth pixels down" (self wideBezierUpdateDataOf: bezier) at: GBUpdateY put: lastY + (lineWidth * 256). "Record the last X value" self bezierFinalXOf: bezier put: endX - lineOffset + lineWidth.! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/8/1998 03:44'! computeFinalWideBezierValues: bezier width: lineWidth "Get both values from the two boundaries of the given bezier and compute the actual position/width of the line" | leftX rightX temp | leftX _ ((self bezierUpdateDataOf: bezier) at: GBUpdateX) // 256. rightX _ ((self wideBezierUpdateDataOf: bezier) at: GBUpdateX) // 256. leftX > rightX ifTrue:[temp _ leftX. leftX _ rightX. rightX _ temp]. self edgeXValueOf: bezier put: leftX. (rightX - leftX) > lineWidth ifTrue:[ self wideBezierWidthOf: bezier put: (rightX - leftX). ] ifFalse:[ self wideBezierWidthOf: bezier put: lineWidth. ].! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/6/1998 01:54'! returnWideBezierFill ^(dispatchReturnValue _ self wideBezierFillOf: dispatchedValue).! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/6/1998 01:54'! returnWideBezierWidth ^(dispatchReturnValue _ self wideBezierWidthOf: dispatchedValue).! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/6/1998 02:00'! stepToFirstWideBezier "Initialize the current entry in the GET by stepping to the current scan line" self inline: true. ^self stepToFirstWideBezierIn: (getBuffer at: self getStartGet) at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/9/1998 15:38'! stepToFirstWideBezierIn: bezier at: yValue "Initialize the bezier at yValue" | lineWidth startY nLines yEntry yExit lineOffset endX xDir | self inline: false. "Get some values" lineWidth _ self wideBezierExtentOf: bezier. lineOffset _ self offsetFromWidth: lineWidth. "Compute the incremental values of the bezier" endX _ self bezierEndXOf: bezier. startY _ self edgeYValueOf: bezier. self stepToFirstBezierIn: bezier at: startY. nLines _ (self edgeNumLinesOf: bezier). "Copy the incremental update data" 0 to: 5 do:[:i| (self wideBezierUpdateDataOf: bezier) at: i put: ((self bezierUpdateDataOf: bezier) at: i). ]. "Compute primary x direction of curve (e.g., 1: left to right; -1: right to left)." xDir _ ((self bezierUpdateDataOf: bezier) at: GBUpdateDX). xDir = 0 ifTrue:[((self bezierUpdateDataOf: bezier) at: GBUpdateDDX)]. xDir >= 0 ifTrue:[xDir _ 1] ifFalse:[xDir _ -1]. "Adjust the curve to start/end at the right position" xDir < 0 ifTrue:[self adjustWideBezierLeft: bezier width: lineWidth offset: lineOffset endX: endX] ifFalse:[self adjustWideBezierRight: bezier width: lineWidth offset: lineOffset endX: endX]. "Adjust the last value for horizontal lines" nLines = 0 ifTrue:[(self bezierUpdateDataOf: bezier) at: GBUpdateX put: (self bezierFinalXOf: bezier) * 256]. "Adjust the number of lines to include the lineWidth" self edgeNumLinesOf: bezier put: nLines + lineWidth. "Compute the points where we have to turn on/off the fills" yEntry _ 0. "turned on at lineOffset" yExit _ 0 - nLines - lineOffset. "turned off at zero" self wideBezierEntryOf: bezier put: yEntry. self wideBezierExitOf: bezier put: yExit. "Turn the fills on/off as necessary" (yEntry >= lineOffset and:[yExit < 0]) ifTrue:[self edgeFillsValidate: bezier] ifFalse:[self edgeFillsInvalidate: bezier]. self computeFinalWideBezierValues: bezier width: lineWidth. "And step to the first scan line" startY = yValue ifFalse:[ "Note: Must single step here so that entry/exit works" startY to: yValue-1 do:[:i| self stepToNextWideBezierIn: bezier at: i]. "Adjust number of lines remaining" self edgeNumLinesOf: bezier put: (self edgeNumLinesOf: bezier) - (yValue - startY). ].! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/6/1998 02:34'! stepToNextWideBezier "Initialize the current entry in the GET by stepping to the current scan line" self inline: true. self stepToNextWideBezierIn: (aetBuffer at: self aetStartGet) at: self currentYGet.! ! !BalloonEnginePlugin methodsFor: 'beziers-wide' stamp: 'ar 11/9/1998 15:39'! stepToNextWideBezierIn: bezier at: yValue "Incrementally step to the next scan line in the given wide bezier" | yEntry yExit lineWidth lineOffset | self inline: false. "Don't inline this" lineWidth _ self wideBezierExtentOf: bezier. lineOffset _ self offsetFromWidth: lineWidth. yEntry _ (self wideBezierEntryOf: bezier) + 1. yExit _ (self wideBezierExitOf: bezier) + 1. self wideBezierEntryOf: bezier put: yEntry. self wideBezierExitOf: bezier put: yExit. yEntry >= lineOffset ifTrue:[self edgeFillsValidate: bezier]. yExit >= 0 ifTrue:[self edgeFillsInvalidate: bezier]. "Check if we have to step the upper curve" (yExit + lineOffset < 0) ifTrue:[ self stepToNextBezierForward: (self bezierUpdateDataOf: bezier) at: yValue. ] ifFalse:[ "Adjust the last x value to the final x recorded previously" (self bezierUpdateDataOf: bezier) at: GBUpdateX put: (self bezierFinalXOf: bezier) * 256. ]. "Step the lower curve" self stepToNextBezierForward: (self wideBezierUpdateDataOf: bezier) at: yValue. self computeFinalWideBezierValues: bezier width: lineWidth.! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/9/1998 16:07'! checkCompressedFillIndexList: fillList max: maxIndex segments: nSegs "Check the fill indexes in the run-length encoded fillList" | length runLength runValue nFills fillPtr | self inline: false. self var: #fillPtr declareC:'int *fillPtr'. length _ interpreterProxy slotSizeOf: fillList. fillPtr _ interpreterProxy firstIndexableField: fillList. nFills _ 0. 0 to: length-1 do:[:i| runLength _ self shortRunLengthAt: i from: fillPtr. runValue _ self shortRunValueAt: 0 from: fillPtr. (runValue >= 0 and:[runValue <= maxIndex]) ifFalse:[^false]. nFills _ nFills + runLength. ]. ^nFills = nSegs! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/25/1998 00:42'! checkCompressedFills: indexList "Check if the indexList (containing fill handles) is okay." | fillPtr length fillIndex | self inline: false. self var: #fillPtr declareC:'int *fillPtr'. "First check if the oops have the right format" (interpreterProxy isWords: indexList) ifFalse:[^false]. "Then check the fill entries" length _ interpreterProxy slotSizeOf: indexList. fillPtr _ interpreterProxy firstIndexableField: indexList. 1 to: length do:[:i| fillIndex _ fillPtr at: 0. "Make sure the fill is okay" (self isFillOkay: fillIndex) ifFalse:[^false]. fillPtr _ fillPtr + 1]. ^true! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/9/1998 16:07'! checkCompressedLineWidths: lineWidthList segments: nSegments "Check the run-length encoded lineWidthList matches nSegments" | length runLength nItems ptr | self inline: false. self var: #ptr declareC:'int *ptr'. length _ interpreterProxy slotSizeOf: lineWidthList. ptr _ interpreterProxy firstIndexableField: lineWidthList. nItems _ 0. 0 to: length-1 do:[:i| runLength _ self shortRunLengthAt: i from: ptr. nItems _ nItems + runLength. ]. ^nItems = nSegments! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/8/1998 15:19'! checkCompressedPoints: points segments: nSegments "Check if the given point array can be handled by the engine." | pSize | self inline: false. (interpreterProxy isWords: points) ifFalse:[^false]. pSize _ interpreterProxy slotSizeOf: points. "The points must be either in PointArray format or ShortPointArray format. Also, we currently handle only quadratic segments (e.g., 3 points each) and thus either pSize = nSegments * 3, for ShortPointArrays or, pSize = nSegments * 6, for PointArrays" (pSize = (nSegments * 3) or:[pSize = (nSegments * 6)]) ifFalse:[^false]. "Can't handle this" ^true! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/12/1998 21:22'! checkCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList "Check if the given shape can be handled by the engine. Since there are a number of requirements this is an extra method." | maxFillIndex | self inline: false. (self checkCompressedPoints: points segments: nSegments) ifFalse:[^false]. (self checkCompressedFills: fillIndexList) ifFalse:[^false]. maxFillIndex _ interpreterProxy slotSizeOf: fillIndexList. (self checkCompressedFillIndexList: leftFills max: maxFillIndex segments: nSegments) ifFalse:[^false]. (self checkCompressedFillIndexList: rightFills max: maxFillIndex segments: nSegments) ifFalse:[^false]. (self checkCompressedFillIndexList: lineFills max: maxFillIndex segments: nSegments) ifFalse:[^false]. (self checkCompressedLineWidths: lineWidths segments: nSegments) ifFalse:[^false]. ^true! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/24/1998 21:13'! loadCompressedSegment: segmentIndex from: points short: pointsShort leftFill: leftFill rightFill: rightFill lineWidth: lineWidth lineColor: lineFill "Load the compressed segment identified by segment index" | x0 y0 x1 y1 x2 y2 index segs | self inline: true. "Check if have anything to do at all" (leftFill = rightFill and:[lineWidth = 0 or:[lineFill = 0]]) ifTrue:[^nil]. "Nothing to do" index _ segmentIndex * 6. "3 points with x/y each" pointsShort ifTrue:["Load short points" x0 _ self loadPointShortAt: (index+0) from: points. y0 _ self loadPointShortAt: (index+1) from: points. x1 _ self loadPointShortAt: (index+2) from: points. y1 _ self loadPointShortAt: (index+3) from: points. x2 _ self loadPointShortAt: (index+4) from: points. y2 _ self loadPointShortAt: (index+5) from: points. ] ifFalse:[ x0 _ self loadPointIntAt: (index+0) from: points. y0 _ self loadPointIntAt: (index+1) from: points. x1 _ self loadPointIntAt: (index+2) from: points. y1 _ self loadPointIntAt: (index+3) from: points. x2 _ self loadPointIntAt: (index+4) from: points. y2 _ self loadPointIntAt: (index+5) from: points. ]. "Briefly check if can represent the bezier as a line" ((x0 = x1 and:[y0 = y1]) or:[x1 = x2 and:[y1 = y2]]) ifTrue:[ "We can use a line from x0/y0 to x2/y2" (x0 = x2 and:[y0 = y2]) ifTrue:[^nil]. "Nothing to do" "Load and transform points" self point1Get at: 0 put: x0. self point1Get at: 1 put: y0. self point2Get at: 0 put: x2. self point2Get at: 1 put: y2. self transformPoints: 2. ^self loadWideLine: lineWidth from: self point1Get to: self point2Get lineFill: lineFill leftFill: leftFill rightFill: rightFill. ]. "Need bezier curve" "Load and transform points" self point1Get at: 0 put: x0. self point1Get at: 1 put: y0. self point2Get at: 0 put: x1. self point2Get at: 1 put: y1. self point3Get at: 0 put: x2. self point3Get at: 1 put: y2. self transformPoints: 3. segs _ self loadAndSubdivideBezierFrom: self point1Get via: self point2Get to: self point3Get isWide: (lineWidth ~= 0 and:[lineFill ~= 0]). engineStopped ifTrue:[^nil]. self loadWideBezier: lineWidth lineFill: lineFill leftFill: leftFill rightFill: rightFill n: segs. ! ! !BalloonEnginePlugin methodsFor: 'shapes-compressed' stamp: 'ar 11/25/1998 00:28'! loadCompressedShape: points segments: nSegments leftFills: leftFills rightFills: rightFills lineWidths: lineWidths lineFills: lineFills fillIndexList: fillIndexList pointShort: pointsShort "Load a compressed shape into the engine. WARNING: THIS METHOD NEEDS THE FULL FRAME SIZE!!!!!!!! " | leftRun rightRun widthRun lineFillRun leftLength rightLength widthLength lineFillLength leftValue rightValue widthValue lineFillValue | self inline: false. "Don't you!!!!!!!!" self var: #points declareC:'int *points'. self var: #leftFills declareC:'int *leftFills'. self var: #rightFills declareC:'int *rightFills'. self var: #lineWidths declareC:'int *lineWidths'. self var: #lineFills declareC:'int *lineFills'. self var: #fillIndexList declareC:'int *fillIndexList'. nSegments = 0 ifTrue:[^0]. "Initialize run length encodings" leftRun _ rightRun _ widthRun _ lineFillRun _ -1. leftLength _ rightLength _ widthLength _ lineFillLength _ 1. leftValue _ rightValue _ widthValue _ lineFillValue _ 0. 1 to: nSegments do:[:i| "Decrement current run length and load new stuff" (leftLength _ leftLength - 1) <= 0 ifTrue:[ leftRun _ leftRun + 1. leftLength _ self shortRunLengthAt: leftRun from: leftFills. leftValue _ self shortRunValueAt: leftRun from: leftFills. leftValue = 0 ifFalse:[ leftValue _ fillIndexList at: leftValue-1. leftValue _ self transformColor: leftValue. engineStopped ifTrue:[^nil]]]. (rightLength _ rightLength - 1) <= 0 ifTrue:[ rightRun _ rightRun + 1. rightLength _ self shortRunLengthAt: rightRun from: rightFills. rightValue _ self shortRunValueAt: rightRun from: rightFills. rightValue = 0 ifFalse:[ rightValue _ fillIndexList at: rightValue-1. rightValue _ self transformColor: rightValue]]. (widthLength _ widthLength - 1) <= 0 ifTrue:[ widthRun _ widthRun + 1. widthLength _ self shortRunLengthAt: widthRun from: lineWidths. widthValue _ self shortRunValueAt: widthRun from: lineWidths. widthValue = 0 ifFalse:[widthValue _ self transformWidth: widthValue]]. (lineFillLength _ lineFillLength - 1) <= 0 ifTrue:[ lineFillRun _ lineFillRun + 1. lineFillLength _ self shortRunLengthAt: lineFillRun from: lineFills. lineFillValue _ self shortRunValueAt: lineFillRun from: lineFills. lineFillValue = 0 ifFalse:[lineFillValue _ fillIndexList at: lineFillValue-1]]. self loadCompressedSegment: i - 1 from: points short: pointsShort leftFill: leftValue rightFill: rightValue lineWidth: widthValue lineColor: lineFillValue. engineStopped ifTrue:[^nil]. ].! ! !BalloonEnginePlugin methodsFor: 'shapes-polygons' stamp: 'ar 11/24/1998 23:09'! loadArrayPolygon: points nPoints: nPoints fill: fillIndex lineWidth: lineWidth lineFill: lineFill | x0 y0 x1 y1 | self loadPoint: self point1Get from: (interpreterProxy fetchPointer: 0 ofObject: points). interpreterProxy failed ifTrue:[^nil]. x0 _ self point1Get at: 0. y0 _ self point1Get at: 1. 1 to: nPoints-1 do:[:i| self loadPoint: self point1Get from: (interpreterProxy fetchPointer: i ofObject: points). interpreterProxy failed ifTrue:[^nil]. x1 _ self point1Get at: 0. y1 _ self point1Get at: 1. self point1Get at: 0 put: x0. self point1Get at: 1 put: y0. self point2Get at: 0 put: x1. self point2Get at: 1 put: y1. self transformPoints: 2. self loadWideLine: lineWidth from: self point1Get to: self point2Get lineFill: lineFill leftFill: fillIndex rightFill: 0. engineStopped ifTrue:[^nil]. x0 _ x1. y0 _ y1. ].! ! !BalloonEnginePlugin methodsFor: 'shapes-polygons' stamp: 'ar 11/24/1998 23:14'! loadArrayShape: points nSegments: nSegments fill: fillIndex lineWidth: lineWidth lineFill: lineFill | pointOop x0 y0 x1 y1 x2 y2 segs | self inline: false. 0 to: nSegments-1 do:[:i| pointOop _ interpreterProxy fetchPointer: (i * 3) ofObject: points. self loadPoint: self point1Get from: pointOop. pointOop _ interpreterProxy fetchPointer: (i * 3 + 1) ofObject: points. self loadPoint: self point2Get from: pointOop. pointOop _ interpreterProxy fetchPointer: (i * 3 + 2) ofObject: points. self loadPoint: self point3Get from: pointOop. interpreterProxy failed ifTrue:[^nil]. self transformPoints: 3. x0 _ self point1Get at: 0. y0 _ self point1Get at: 1. x1 _ self point2Get at: 0. y1 _ self point2Get at: 1. x2 _ self point3Get at: 0. y2 _ self point3Get at: 1. "Check if we can use a line" ((x0 = y0 and:[x1 = y1]) or:[x1 = x2 and:[y1 = y2]]) ifTrue:[ self loadWideLine: lineWidth from: self point1Get to: self point3Get lineFill: lineFill leftFill: fillIndex rightFill: 0. ] ifFalse:["Need bezier" segs _ self loadAndSubdivideBezierFrom: self point1Get via: self point2Get to: self point3Get isWide: (lineWidth ~= 0 and:[lineFill ~= 0]). engineStopped ifTrue:[^nil]. self loadWideBezier: lineWidth lineFill: lineFill leftFill: fillIndex rightFill: 0 n: segs. ]. engineStopped ifTrue:[^nil]. ].! ! !BalloonEnginePlugin methodsFor: 'shapes-polygons' stamp: 'ar 11/24/1998 23:10'! loadPolygon: points nPoints: nPoints fill: fillIndex lineWidth: lineWidth lineFill: lineFill pointsShort: isShort | x0 y0 x1 y1 | self var:#points declareC:'int *points'. isShort ifTrue:[ x0 _ self loadPointShortAt: 0 from: points. y0 _ self loadPointShortAt: 1 from: points. ] ifFalse:[ x0 _ self loadPointIntAt: 0 from: points. y0 _ self loadPointIntAt: 1 from: points. ]. 1 to: nPoints-1 do:[:i| isShort ifTrue:[ x1 _ self loadPointShortAt: i*2 from: points. y1 _ self loadPointShortAt: i*2+1 from: points. ] ifFalse:[ x1 _ self loadPointIntAt: i*2 from: points. y1 _ self loadPointIntAt: i*2+1 from: points. ]. self point1Get at: 0 put: x0. self point1Get at: 1 put: y0. self point2Get at: 0 put: x1. self point2Get at: 1 put: y1. self transformPoints: 2. self loadWideLine: lineWidth from: self point1Get to: self point2Get lineFill: lineFill leftFill: fillIndex rightFill: 0. engineStopped ifTrue:[^nil]. x0 _ x1. y0 _ y1. ].! ! !BalloonEnginePlugin methodsFor: 'shapes-polygons' stamp: 'ar 11/24/1998 21:14'! loadShape: points nSegments: nSegments fill: fillIndex lineWidth: lineWidth lineFill: lineFill pointsShort: pointsShort self inline: false. self var:#points declareC:'int *points'. 1 to: nSegments do:[:i| self loadCompressedSegment: i-1 from: points short: pointsShort leftFill: fillIndex rightFill: 0 lineWidth: lineWidth lineColor: lineFill. engineStopped ifTrue:[^nil]. ].! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/8/1998 15:20'! fillLinearGradient self inline: true. ^self fillLinearGradient: self lastExportedFillGet from: self lastExportedLeftXGet to: self lastExportedRightXGet at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/24/1998 21:50'! fillLinearGradient: fill from: leftX to: rightX at: yValue "Draw a linear gradient fill." | x0 x1 ramp rampSize dsX ds x rampIndex | self inline: false. self var: #ramp declareC:'int *ramp'. ramp _ self gradientRampOf: fill. rampSize _ self gradientRampLengthOf: fill. dsX _ self fillDirectionXOf: fill. ds _ ((leftX - (self fillOriginXOf: fill)) * dsX) + ((yValue - (self fillOriginYOf: fill)) * (self fillDirectionYOf: fill)). x _ x0 _ leftX. x1 _ rightX. "Note: The inner loop has been divided into three parts for speed" "Part one: Fill everything outside the left boundary" [(rampIndex _ ds // 16r10000) < 0 and:[x < x1]] whileTrue:[ x _ x + 1. ds _ ds + dsX]. x > x0 ifTrue:[self fillColorSpan: (self makeUnsignedFrom: (ramp at: 0)) from: x0 to: x]. "Part two: Fill everything inside the boundaries" self aaLevelGet = 1 ifTrue:[ "Fast version w/o anti-aliasing" [((rampIndex _ ds // 16r10000) < rampSize and:[rampIndex >= 0]) and:[x < x1]] whileTrue:[ spanBuffer at: x put: (self makeUnsignedFrom: (ramp at: rampIndex)). x _ x + 1. ds _ ds + dsX. ]. ] ifFalse:[x _ self fillLinearGradientAA: fill ramp: ramp ds: ds dsX: dsX from: x to: rightX]. "Part three fill everything outside right boundary" x < x1 ifTrue:[ rampIndex < 0 ifTrue:[rampIndex _ 0]. rampIndex >= rampSize ifTrue:[rampIndex _ rampSize-1]. self fillColorSpan: (self makeUnsignedFrom: (ramp at: rampIndex)) from: x to: x1]. ! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/10/1998 17:18'! fillLinearGradientAA: fill ramp: ramp ds: deltaS dsX: dsX from: leftX to: rightX "This is the AA version of linear gradient filling." | colorMask colorShift baseShift rampIndex ds rampSize x idx rampValue aaLevel firstPixel lastPixel | self inline: false. self var: #ramp declareC:'int *ramp'. aaLevel _ self aaLevelGet. baseShift _ self aaShiftGet. rampSize _ self gradientRampLengthOf: fill. ds _ deltaS. x _ leftX. rampIndex _ ds // 16r10000. firstPixel _ self aaFirstPixelFrom: leftX to: rightX. lastPixel _ self aaLastPixelFrom: leftX to: rightX. "Deal with the first n sub-pixels" colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. [x < firstPixel and:[rampIndex < rampSize and:[rampIndex >= 0]]] whileTrue:[ rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. "Copy as many pixels as possible" [x < firstPixel and:[(ds//16r10000) = rampIndex]] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + rampValue. x _ x + 1. ds _ ds + dsX]. rampIndex _ ds // 16r10000. ]. "Deal with the full pixels" colorMask _ (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0. colorShift _ self aaShiftGet. [x < lastPixel and:[rampIndex < rampSize and:[rampIndex >= 0]]] whileTrue:[ rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. "Copy as many pixels as possible" [x < lastPixel and:[(ds//16r10000) = rampIndex]] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + rampValue. x _ x + aaLevel. ds _ ds + (dsX << colorShift)]. rampIndex _ ds // 16r10000. ]. "Deal with the last n sub-pixels" colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. [x < rightX and:[rampIndex < rampSize and:[rampIndex>=0]]] whileTrue:[ rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. "Copy as many pixels as possible" [x < rightX and:[(ds//16r10000) = rampIndex]] whileTrue:[ idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + rampValue. x _ x + 1. ds _ ds + dsX]. rampIndex _ ds // 16r10000. ]. ^x! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/24/1998 19:02'! fillRadialDecreasing: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: leftX to: rightX "Part 2a) Compute the decreasing part of the ramp" | ds dt rampIndex rampValue length2 x x1 nextLength | self inline: true. ds _ (self cCoerce: deltaST to:'int*') at: 0. dt _ (self cCoerce: deltaST to:'int*') at: 1. rampIndex _ self accurateLengthOf: ds // 16r10000 with: dt // 16r10000. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). length2 _ (rampIndex-1) * (rampIndex-1). x _ leftX. x1 _ rightX. x1 > (self fillOriginXOf: fill) ifTrue:[x1 _ self fillOriginXOf: fill]. [x < x1] whileTrue:[ "Try to copy the current value more than just once" [x < x1 and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) >= length2]] whileTrue:[ spanBuffer at: x put: rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. "Step to next ramp value" nextLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [nextLength < length2] whileTrue:[ rampIndex _ rampIndex - 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). length2 _ (rampIndex-1) * (rampIndex-1). ]. ]. (self cCoerce: deltaST to: 'int *') at: 0 put: ds. (self cCoerce: deltaST to: 'int *') at: 1 put: dt. ^x! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/24/1998 19:02'! fillRadialDecreasingAA: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: leftX to: rightX "Part 2a) Compute the decreasing part of the ramp" | ds dt rampIndex rampValue length2 x nextLength x1 aaLevel colorMask colorShift baseShift index firstPixel lastPixel | self inline: false. self var: #ramp declareC:'int *ramp'. self var: #deltaST declareC:' int *deltaST'. ds _ (self cCoerce: deltaST to:'int*') at: 0. dt _ (self cCoerce: deltaST to:'int*') at: 1. aaLevel _ self aaLevelGet. baseShift _ self aaShiftGet. rampIndex _ self accurateLengthOf: ds // 16r10000 with: dt // 16r10000. length2 _ (rampIndex-1) * (rampIndex-1). x _ leftX. x1 _ self fillOriginXOf: fill. x1 > rightX ifTrue:[x1 _ rightX]. firstPixel _ self aaFirstPixelFrom: leftX to: x1. lastPixel _ self aaLastPixelFrom: leftX to: x1. "Deal with the first n sub-pixels" (x < firstPixel) ifTrue:[ colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < firstPixel] whileTrue:[ "Try to copy the current value more than just once" [x < firstPixel and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) >= length2]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. "Step to next ramp value" nextLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [nextLength < length2] whileTrue:[ rampIndex _ rampIndex - 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. length2 _ (rampIndex-1) * (rampIndex-1). ]. ]. ]. "Deal with the full pixels" (x < lastPixel) ifTrue:[ colorMask _ (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0. colorShift _ self aaShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < lastPixel] whileTrue:[ "Try to copy the current value more than just once" [x < lastPixel and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) >= length2]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + aaLevel. ds _ ds + (dsX << colorShift). dt _ dt + (dtX << colorShift)]. "Step to next ramp value" nextLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [nextLength < length2] whileTrue:[ rampIndex _ rampIndex - 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. length2 _ (rampIndex-1) * (rampIndex-1). ]. ]. ]. "Deal with the last n sub-pixels" (x < x1) ifTrue:[ colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < x1] whileTrue:[ "Try to copy the current value more than just once" [x < x1 and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) >= length2]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. "Step to next ramp value" nextLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [nextLength < length2] whileTrue:[ rampIndex _ rampIndex - 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. length2 _ (rampIndex-1) * (rampIndex-1). ]. ]. ]. "Done -- store stuff back" (self cCoerce: deltaST to: 'int *') at: 0 put: ds. (self cCoerce: deltaST to: 'int *') at: 1 put: dt. ^x! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/8/1998 15:20'! fillRadialGradient self inline: true. ^self fillRadialGradient: self lastExportedFillGet from: self lastExportedLeftXGet to: self lastExportedRightXGet at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/24/1998 19:02'! fillRadialGradient: fill from: leftX to: rightX at: yValue "Draw a radial gradient fill." | x x1 ramp rampSize dsX ds dtX dt length2 deltaX deltaY deltaST | self inline: false. self var: #ramp declareC:'int *ramp'. self var: #deltaST declareC:'int *deltaST'. ramp _ self gradientRampOf: fill. rampSize _ self gradientRampLengthOf: fill. deltaX _ leftX - (self fillOriginXOf: fill). deltaY _ yValue - (self fillOriginYOf: fill). dsX _ self fillDirectionXOf: fill. dtX _ self fillNormalXOf: fill. ds _ (deltaX * dsX) + (deltaY * (self fillDirectionYOf: fill)). dt _ (deltaX * dtX) + (deltaY * (self fillNormalYOf: fill)). x _ leftX. x1 _ rightX. "Note: The inner loop has been divided into three parts for speed" "Part one: Fill everything outside the left boundary" length2 _ (rampSize-1) * (rampSize-1). "This is the upper bound" [(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) >= length2 and:[x < x1]] whileTrue:[ x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. x > leftX ifTrue:[self fillColorSpan: (self makeUnsignedFrom: (ramp at: rampSize-1)) from: leftX to: x]. "Part two: Fill everything inside the boundaries" deltaST _ self point1Get. deltaST at: 0 put: ds. deltaST at: 1 put: dt. (x < (self fillOriginXOf: fill)) ifTrue:[ "Draw the decreasing part" self aaLevelGet = 1 ifTrue:[x _ self fillRadialDecreasing: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: x to: x1] ifFalse:[x _ self fillRadialDecreasingAA: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: x to: x1]. ]. x < x1 ifTrue:[ "Draw the increasing part" self aaLevelGet = 1 ifTrue:[x _ self fillRadialIncreasing: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: x to: x1] ifFalse:[x _ self fillRadialIncreasingAA: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: x to: x1]. ]. "Part three fill everything outside right boundary" x < rightX ifTrue:[self fillColorSpan: (self makeUnsignedFrom: (ramp at: rampSize-1)) from: x to: rightX]. ! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/9/1998 01:21'! fillRadialIncreasing: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: leftX to: rightX "Part 2b) Compute the increasing part of the ramp" | ds dt rampIndex rampValue length2 x x1 nextLength rampSize lastLength | self inline: true. ds _ (self cCoerce: deltaST to:'int*') at: 0. dt _ (self cCoerce: deltaST to:'int*') at: 1. rampIndex _ self accurateLengthOf: ds // 16r10000 with: dt // 16r10000. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampSize _ self gradientRampLengthOf: fill. length2 _ (rampSize-1) * (rampSize-1). "This is the upper bound" nextLength _ (rampIndex+1) * (rampIndex+1). lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. x _ leftX. x1 _ rightX. [x < x1 and:[lastLength < length2]] whileTrue:[ "Try to copy the current value more than once" [x < x1 and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) <= nextLength]] whileTrue:[ spanBuffer at: x put: rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [lastLength > nextLength] whileTrue:[ rampIndex _ rampIndex + 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). nextLength _ (rampIndex+1) * (rampIndex+1). ]. ]. (self cCoerce: deltaST to: 'int *') at: 0 put: ds. (self cCoerce: deltaST to: 'int *') at: 1 put: dt. ^x! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/9/1998 16:09'! fillRadialIncreasingAA: fill ramp: ramp deltaST: deltaST dsX: dsX dtX: dtX from: leftX to: rightX "Part 2b) Compute the increasing part of the ramp" | ds dt rampIndex rampValue length2 x nextLength rampSize lastLength aaLevel colorMask colorShift baseShift index firstPixel lastPixel | self inline: false. self var: #ramp declareC:'int *ramp'. self var: #deltaST declareC:' int *deltaST'. ds _ (self cCoerce: deltaST to:'int*') at: 0. dt _ (self cCoerce: deltaST to:'int*') at: 1. aaLevel _ self aaLevelGet. baseShift _ self aaShiftGet. rampIndex _ self accurateLengthOf: ds // 16r10000 with: dt // 16r10000. rampSize _ self gradientRampLengthOf: fill. length2 _ (rampSize-1) * (rampSize-1). "This is the upper bound" nextLength _ (rampIndex+1) * (rampIndex+1). lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. x _ leftX. firstPixel _ self aaFirstPixelFrom: leftX to: rightX. lastPixel _ self aaLastPixelFrom: leftX to: rightX. "Deal with the first n subPixels" (x < firstPixel and:[lastLength < length2]) ifTrue:[ colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < firstPixel and:[lastLength < length2]] whileTrue:[ "Try to copy the current value more than once" [x < firstPixel and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) <= nextLength]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [lastLength > nextLength] whileTrue:[ rampIndex _ rampIndex + 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. nextLength _ (rampIndex+1) * (rampIndex+1). ]. ]. ]. "Deal with the full pixels" (x < lastPixel and:[lastLength < length2]) ifTrue:[ colorMask _ (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0. colorShift _ self aaShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < lastPixel and:[lastLength < length2]] whileTrue:[ "Try to copy the current value more than once" [x < lastPixel and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) <= nextLength]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + aaLevel. ds _ ds + (dsX << colorShift). dt _ dt + (dtX << colorShift)]. lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [lastLength > nextLength] whileTrue:[ rampIndex _ rampIndex + 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. nextLength _ (rampIndex+1) * (rampIndex+1). ]. ]. ]. "Deal with last n sub-pixels" (x < rightX and:[lastLength < length2]) ifTrue:[ colorMask _ self aaColorMaskGet. colorShift _ self aaColorShiftGet. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. [x < rightX and:[lastLength < length2]] whileTrue:[ "Try to copy the current value more than once" [x < rightX and:[(self squaredLengthOf: ds // 16r10000 with: dt // 16r10000) <= nextLength]] whileTrue:[ index _ x >> baseShift. spanBuffer at: index put: (spanBuffer at: index) + rampValue. x _ x + 1. ds _ ds + dsX. dt _ dt + dtX]. lastLength _ self squaredLengthOf: ds // 16r10000 with: dt // 16r10000. [lastLength > nextLength] whileTrue:[ rampIndex _ rampIndex + 1. rampValue _ self makeUnsignedFrom: ((self cCoerce: ramp to:'int *') at: rampIndex). rampValue _ (rampValue bitAnd: colorMask) >> colorShift. nextLength _ (rampIndex+1) * (rampIndex+1). ]. ]. ]. "Done -- store stuff back" (self cCoerce: deltaST to: 'int *') at: 0 put: ds. (self cCoerce: deltaST to: 'int *') at: 1 put: dt. ^x! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/27/1998 13:36'! loadFillOrientation: fill from: point1 along: point2 normal: point3 width: fillWidth height: fillHeight "Transform the points" | dirX dirY nrmX nrmY dsLength2 dsX dsY dtLength2 dtX dtY | self var: #point1 declareC:'int *point1'. self var: #point2 declareC:'int *point2'. self var: #point3 declareC:'int *point3'. point2 at: 0 put: (point2 at: 0) + (point1 at: 0). point2 at: 1 put: (point2 at: 1) + (point1 at: 1). point3 at: 0 put: (point3 at: 0) + (point1 at: 0). point3 at: 1 put: (point3 at: 1) + (point1 at: 1). self transformPoint: point1. self transformPoint: point2. self transformPoint: point3. dirX _ (point2 at: 0) - (point1 at: 0). dirY _ (point2 at: 1) - (point1 at: 1). nrmX _ (point3 at: 0) - (point1 at: 0). nrmY _ (point3 at: 1) - (point1 at: 1). "Compute the scale from direction/normal into ramp size" dsLength2 _ (dirX * dirX) + (dirY * dirY). dsLength2 > 0 ifTrue:[ dsX _ (dirX asFloat * fillWidth asFloat * 65536.0 / dsLength2 asFloat) asInteger. dsY _ (dirY asFloat * fillWidth asFloat * 65536.0 / dsLength2 asFloat) asInteger. ] ifFalse:[ dsX _ 0. dsY _ 0]. dtLength2 _ (nrmX * nrmX) + (nrmY * nrmY). dtLength2 > 0 ifTrue:[ dtX _ (nrmX asFloat * fillHeight asFloat * 65536.0 / dtLength2 asFloat) asInteger. dtY _ (nrmY asFloat * fillHeight asFloat * 65536.0 / dtLength2 asFloat) asInteger. ] ifFalse:[dtX _ 0. dtY _ 0]. self fillOriginXOf: fill put: (point1 at: 0). self fillOriginYOf: fill put: (point1 at: 1). self fillDirectionXOf: fill put: dsX. self fillDirectionYOf: fill put: dsY. self fillNormalXOf: fill put: dtX. self fillNormalYOf: fill put: dtY. ! ! !BalloonEnginePlugin methodsFor: 'fills-gradient' stamp: 'ar 11/25/1998 16:44'! loadGradientFill: rampOop from: point1 along: point2 normal: point3 isRadial: isRadial "Load the gradient fill as defined by the color ramp." | rampWidth fill | self inline: false. self var: #point1 declareC:'int *point1'. self var: #point2 declareC:'int *point2'. self var: #point3 declareC:'int *point3'. (interpreterProxy fetchClassOf: rampOop) = interpreterProxy classBitmap ifFalse:[^interpreterProxy primitiveFail]. rampWidth _ interpreterProxy slotSizeOf: rampOop. fill _ self allocateGradientFill: (interpreterProxy firstIndexableField: rampOop) rampWidth: rampWidth isRadial: isRadial. engineStopped ifTrue:[^nil]. self loadFillOrientation: fill from: point1 along: point2 normal: point3 width: rampWidth height: rampWidth. ^fill! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 00:38'! allocateBezier | bezier | (self allocateObjEntry: GBBaseSize) ifFalse:[^0]. bezier _ objUsed. objUsed _ bezier + GBBaseSize. self objectTypeOf: bezier put: GEPrimitiveBezier. self objectIndexOf: bezier put: 0. self objectLengthOf: bezier put: GBBaseSize. ^bezier! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 10/30/1998 20:52'! allocateBezierStackEntry self wbStackPush: 6. ^self wbStackSize! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 16:40'! allocateBitmapFill: cmSize colormap: cmBits | fill fillSize cm | self var:#cm declareC:'int *cm'. self var:#cmBits declareC:'int *cmBits'. fillSize _ GBMBaseSize + cmSize. (self allocateObjEntry: fillSize) ifFalse:[^0]. fill _ objUsed. objUsed _ fill + fillSize. self objectTypeOf: fill put: GEPrimitiveClippedBitmapFill. self objectIndexOf: fill put: 0. self objectLengthOf: fill put: fillSize. cm _ self colormapOf: fill. self hasColorTransform ifTrue:[ 0 to: cmSize-1 do:[:i| cm at: i put: (self transformColor: (cmBits at: i))]. ] ifFalse:[ 0 to: cmSize-1 do:[:i| cm at: i put: (cmBits at: i)]. ]. self bitmapCmSizeOf: fill put: cmSize. ^fill! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 00:38'! allocateGradientFill: ramp rampWidth: rampWidth isRadial: isRadial | fill fillSize rampPtr | self var:#ramp declareC:'int *ramp'. self var:#rampPtr declareC:'int *rampPtr'. fillSize _ GGBaseSize + rampWidth. (self allocateObjEntry: fillSize) ifFalse:[^0]. fill _ objUsed. objUsed _ fill + fillSize. isRadial ifTrue:[self objectTypeOf: fill put: GEPrimitiveRadialGradientFill] ifFalse:[self objectTypeOf: fill put: GEPrimitiveLinearGradientFill]. self objectIndexOf: fill put: 0. self objectLengthOf: fill put: fillSize. rampPtr _ self gradientRampOf: fill. self hasColorTransform ifTrue:[ 0 to: rampWidth-1 do:[:i| rampPtr at: i put: (self transformColor: (ramp at: i))]. ] ifFalse:[ 0 to: rampWidth-1 do:[:i| rampPtr at: i put: (ramp at: i)]. ]. self gradientRampLengthOf: fill put: rampWidth. ^fill! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 00:39'! allocateLine | line | (self allocateObjEntry: GLBaseSize) ifFalse:[^0]. line _ objUsed. objUsed _ line + GLBaseSize. self objectTypeOf: line put: GEPrimitiveLine. self objectIndexOf: line put: 0. self objectLengthOf: line put: GLBaseSize. ^line! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 00:39'! allocateWideBezier | bezier | (self allocateObjEntry: GBWideSize) ifFalse:[^0]. bezier _ objUsed. objUsed _ bezier + GBWideSize. self objectTypeOf: bezier put: GEPrimitiveWideBezier. self objectIndexOf: bezier put: 0. self objectLengthOf: bezier put: GBWideSize. ^bezier! ! !BalloonEnginePlugin methodsFor: 'allocation' stamp: 'ar 11/25/1998 00:39'! allocateWideLine | line | (self allocateObjEntry: GLWideSize) ifFalse:[^0]. line _ objUsed. objUsed _ line + GLWideSize. self objectTypeOf: line put: GEPrimitiveWideLine. self objectIndexOf: line put: 0. self objectLengthOf: line put: GLWideSize. ^line! ! !BalloonEnginePlugin methodsFor: 'GET processing' stamp: 'ar 11/9/1998 15:37'! checkedAddBezierToGET: bezier "Add the bezier to the global edge table if it intersects the clipping region" | lineWidth | self inline: true. (self isWide: bezier) ifTrue:[lineWidth _ (self wideBezierExtentOf: bezier)] ifFalse:[lineWidth _ 0]. (self bezierEndYOf: bezier) + lineWidth < (self fillMinYGet) ifTrue:[^0]. "Overlaps in Y but may still be entirely right of clip region" ((self edgeXValueOf: bezier) - lineWidth >= self fillMaxXGet and:[ (self bezierEndXOf: bezier) - lineWidth >= self fillMaxXGet]) ifTrue:[^0]. self addEdgeToGET: bezier. ! ! !BalloonEnginePlugin methodsFor: 'GET processing' stamp: 'ar 11/9/1998 15:37'! checkedAddEdgeToGET: edge "Add the edge to the global edge table. For known edge types, check if the edge intersects the visible region" self inline: true. (self isLine: edge) ifTrue:[^self checkedAddLineToGET: edge]. (self isBezier: edge) ifTrue:[^self checkedAddBezierToGET: edge]. self addEdgeToGET: edge. ! ! !BalloonEnginePlugin methodsFor: 'GET processing' stamp: 'ar 11/9/1998 15:37'! checkedAddLineToGET: line "Add the line to the global edge table if it intersects the clipping region" | lineWidth | self inline: true. (self isWide: line) ifTrue:[lineWidth _ (self wideLineExtentOf: line)] ifFalse:[lineWidth _ 0]. (self lineEndYOf: line) + lineWidth < (self fillMinYGet) ifTrue:[^0]. "Overlaps in Y but may still be entirely right of clip region" ((self edgeXValueOf: line) - lineWidth >= self fillMaxXGet and:[ (self lineEndXOf: line) - lineWidth >= self fillMaxXGet]) ifTrue:[^0]. self addEdgeToGET: line. ! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 10/30/1998 20:02'! absoluteSquared8Dot24: value "Compute the squared value of a 8.24 number with 0.0 <= value < 1.0, e.g., compute (value * value) bitShift: -24" | word1 word2 | self inline: true. word1 _ value bitAnd: 16rFFFF. word2 _ (value bitShift: -16) bitAnd: 255. ^(( (self cCoerce: (word1 * word1) to:'unsigned') bitShift: -16) + ((word1 * word2) * 2) + ((word2 * word2) bitShift: 16)) bitShift: -8! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/1/1998 17:06'! circleCosTable | theTable | self returnTypeC:'double *'. self inline: false. "Don't you inline this!!!!!!" self var:#theTable declareC:'static double theTable[33] = {1.0, 0.98078528040323, 0.923879532511287, 0.831469612302545, 0.7071067811865475, 0.555570233019602, 0.38268343236509, 0.1950903220161286, 0.0, -0.1950903220161283, -0.3826834323650896, -0.555570233019602, -0.707106781186547, -0.831469612302545, -0.9238795325112865, -0.98078528040323, -1.0, -0.98078528040323, -0.923879532511287, -0.831469612302545, -0.707106781186548, -0.555570233019602, -0.3826834323650903, -0.1950903220161287, 0.0, 0.1950903220161282, 0.38268343236509, 0.555570233019602, 0.707106781186547, 0.831469612302545, 0.9238795325112865, 0.98078528040323, 1.0 }'. ^theTable! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/1/1998 17:06'! circleSinTable | theTable | self returnTypeC:'double *'. self inline: false. "Don't you inline this!!!!!!" self var:#theTable declareC:'static double theTable[33] = {0.0, 0.1950903220161282, 0.3826834323650897, 0.555570233019602, 0.707106781186547, 0.831469612302545, 0.923879532511287, 0.98078528040323, 1.0, 0.98078528040323, 0.923879532511287, 0.831469612302545, 0.7071067811865475, 0.555570233019602, 0.38268343236509, 0.1950903220161286, 0.0, -0.1950903220161283, -0.3826834323650896, -0.555570233019602, -0.707106781186547, -0.831469612302545, -0.9238795325112865, -0.98078528040323, -1.0, -0.98078528040323, -0.923879532511287, -0.831469612302545, -0.707106781186548, -0.555570233019602, -0.3826834323650903, -0.1950903220161287, 0.0 }'. ^theTable! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/3/1998 23:24'! loadPointIntAt: index from: intArray "Load the int value from the given index in intArray" ^(self cCoerce: intArray to: 'int *') at: index! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/3/1998 23:23'! loadPointShortAt: index from: shortArray "Load the short value from the given index in shortArray" self returnTypeC:'short'. ^(self cCoerce: shortArray to: 'short *') at: index! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/1/1998 03:16'! makeRectFromPoints self point2Get at: 0 put: (self point3Get at: 0). self point2Get at: 1 put: (self point1Get at: 1). self point4Get at: 0 put: (self point1Get at: 0). self point4Get at: 1 put: (self point3Get at: 1).! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/6/1998 17:55'! offsetFromWidth: lineWidth "Common function so that we don't compute that wrong in any place and can easily find all the places where we deal with one-pixel offsets." self inline: true. ^lineWidth // 2! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/25/1998 19:27'! rShiftTable | theTable | self returnTypeC:'int *'. self inline: false. "Don't you inline this!!!!!!" self var:#theTable declareC:'static int theTable[17] = {0, 5, 4, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1}'. ^theTable! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/3/1998 22:55'! shortRunLengthAt: i from: runArray "Return the run-length value from the given ShortRunArray." ^((self cCoerce: runArray to:'int *') at: i) bitShift: - 16! ! !BalloonEnginePlugin methodsFor: 'private' stamp: 'ar 11/3/1998 22:54'! shortRunValueAt: i from: runArray "Return the run-length value from the given ShortRunArray. Note: We don't need any coercion to short/int here, since we deal basically only with unsigned values." ^(((self cCoerce: runArray to:'int *') at: i) bitAnd: 16rFFFF)! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillDirectionXOf: fill ^self obj: fill at: GFDirectionX! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillDirectionXOf: fill put: value ^self obj: fill at: GFDirectionX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:19'! fillDirectionYOf: fill ^self obj: fill at: GFDirectionY! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillDirectionYOf: fill put: value ^self obj: fill at: GFDirectionY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillNormalXOf: fill ^self obj: fill at: GFNormalX! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillNormalXOf: fill put: value ^self obj: fill at: GFNormalX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillNormalYOf: fill ^self obj: fill at: GFNormalY! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:16'! fillNormalYOf: fill put: value ^self obj: fill at: GFNormalY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:17'! fillOriginXOf: fill ^self obj: fill at: GFOriginX! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:17'! fillOriginXOf: fill put: value ^self obj: fill at: GFOriginX put: value! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:17'! fillOriginYOf: fill ^self obj: fill at: GFOriginY! ! !BalloonEnginePlugin methodsFor: 'accessing fills' stamp: 'ar 11/24/1998 22:18'! fillOriginYOf: fill put: value ^self obj: fill at: GFOriginY put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:20'! bitmapCmSizeOf: bmFill ^self obj: bmFill at: GBColormapSize! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:19'! bitmapCmSizeOf: bmFill put: value ^self obj: bmFill at: GBColormapSize put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:17'! bitmapDepthOf: bmFill ^self obj: bmFill at: GBBitmapDepth! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:20'! bitmapDepthOf: bmFill put: value ^self obj: bmFill at: GBBitmapDepth put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:17'! bitmapHeightOf: bmFill ^self obj: bmFill at: GBBitmapHeight! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:17'! bitmapHeightOf: bmFill put: value ^self obj: bmFill at: GBBitmapHeight put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:18'! bitmapRasterOf: bmFill ^self obj: bmFill at: GBBitmapRaster! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:18'! bitmapRasterOf: bmFill put: value ^self obj: bmFill at: GBBitmapRaster put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:19'! bitmapSizeOf: bmFill ^self obj: bmFill at: GBBitmapSize! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:18'! bitmapSizeOf: bmFill put: value ^self obj: bmFill at: GBBitmapSize put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/27/1998 14:20'! bitmapTileFlagOf: bmFill ^self obj: bmFill at: GBTileFlag! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/27/1998 14:20'! bitmapTileFlagOf: bmFill put: value ^self obj: bmFill at: GBTileFlag put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:18'! bitmapWidthOf: bmFill ^self obj: bmFill at: GBBitmapWidth! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/24/1998 22:17'! bitmapWidthOf: bmFill put: value ^self obj: bmFill at: GBBitmapWidth put: value! ! !BalloonEnginePlugin methodsFor: 'accessing bitmaps' stamp: 'ar 11/25/1998 16:39'! colormapOf: bmFill self returnTypeC:'int *'. ^objBuffer + bmFill + GBColormapOffset! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/25/1998 21:33'! bitmapValue: bmFill bits: bits atX: xp y: yp | bmDepth bmRaster value rShift cMask r g b a | self inline: true. bmDepth _ self bitmapDepthOf: bmFill. bmRaster _ self bitmapRasterOf: bmFill. bmDepth = 32 ifTrue:[ value _ (self cCoerce: bits to:'int*') at: (bmRaster * yp) + xp. (value ~= 0 and:[(value bitAnd: 16rFF000000) = 0]) ifTrue:[value _ value bitOr: 16rFF000000]. ^self uncheckedTransformColor: value]. "rShift - shift value to convert from pixel to word index" rShift _ self rShiftTable at: bmDepth. value _ self makeUnsignedFrom: ((self cCoerce: bits to:'int*') at: (bmRaster * yp) + (xp >> rShift)). "cMask - mask out the pixel from the word" cMask _ (1 << bmDepth) - 1. "rShift - shift value to move the pixel in the word to the lowest bit position" rShift _ 32 - bmDepth - ((xp bitAnd: (1 << rShift - 1)) * bmDepth). value _ (value >> rShift) bitAnd: cMask. bmDepth = 16 ifTrue:[ "Must convert by expanding bits" value = 0 ifFalse:[ b _ (value bitAnd: 31) << 3. b _ b + (b >> 5). g _ (value >> 5 bitAnd: 31) << 3. g _ g + (g >> 5). r _ (value >> 10 bitAnd: 31) << 3. r _ r + (r >> 5). a _ 255. value _ b + (g << 8) + (r << 16) + (a << 24)]. ] ifFalse:[ "Must convert by using color map" (self bitmapCmSizeOf: bmFill) = 0 ifTrue:[value _ 0] ifFalse:[value _ self makeUnsignedFrom: ((self colormapOf: bmFill) at: value)]. ]. ^self uncheckedTransformColor: value.! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/27/1998 14:19'! clampValue: value max: maxValue self inline: true. value < 0 ifTrue:[^0] ifFalse:[value >= maxValue ifTrue:[^maxValue-1] ifFalse:[^value]]! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/25/1998 19:46'! fillBitmapSpan self inline: true. ^self fillBitmapSpan: self lastExportedFillGet from: self lastExportedLeftXGet to: self lastExportedRightXGet at: self currentYGet! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/27/1998 14:23'! fillBitmapSpan: bmFill from: leftX to: rightX at: yValue | x x1 dsX ds dtX dt deltaX deltaY bits xp yp bmWidth bmHeight fillValue tileFlag | self inline: false. self var: #bits declareC:'int *bits'. self aaLevelGet = 1 ifFalse:[^self fillBitmapSpanAA: bmFill from: leftX to: rightX at: yValue]. bits _ self loadBitsFrom: bmFill. bits == nil ifTrue:[^nil]. bmWidth _ self bitmapWidthOf: bmFill. bmHeight _ self bitmapHeightOf: bmFill. tileFlag _ (self bitmapTileFlagOf: bmFill) = 1. deltaX _ leftX - (self fillOriginXOf: bmFill). deltaY _ yValue - (self fillOriginYOf: bmFill). dsX _ self fillDirectionXOf: bmFill. dtX _ self fillNormalXOf: bmFill. ds _ (deltaX * dsX) + (deltaY * (self fillDirectionYOf: bmFill)). dt _ (deltaX * dtX) + (deltaY * (self fillNormalYOf: bmFill)). x _ leftX. x1 _ rightX. [x < x1] whileTrue:[ tileFlag ifTrue:[ ds _ self repeatValue: ds max: bmWidth << 16. dt _ self repeatValue: dt max: bmHeight << 16]. xp _ ds // 16r10000. yp _ dt // 16r10000. tileFlag ifFalse:[ xp _ self clampValue: xp max: bmWidth. yp _ self clampValue: yp max: bmHeight]. (xp >= 0 and:[yp >= 0 and:[xp < bmWidth and:[yp < bmHeight]]]) ifTrue:[ fillValue _ self bitmapValue: bmFill bits: bits atX: xp y: yp. spanBuffer at: x put: fillValue. ]. ds _ ds + dsX. dt _ dt + dtX. x _ x + 1. ].! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/27/1998 14:23'! fillBitmapSpanAA: bmFill from: leftX to: rightX at: yValue | x dsX ds dtX dt deltaX deltaY bits xp yp bmWidth bmHeight fillValue baseShift cMask cShift idx aaLevel firstPixel lastPixel tileFlag | self inline: false. self var: #bits declareC:'int *bits'. bits _ self loadBitsFrom: bmFill. bits == nil ifTrue:[^nil]. bmWidth _ self bitmapWidthOf: bmFill. bmHeight _ self bitmapHeightOf: bmFill. tileFlag _ (self bitmapTileFlagOf: bmFill) = 1. deltaX _ leftX - (self fillOriginXOf: bmFill). deltaY _ yValue - (self fillOriginYOf: bmFill). dsX _ self fillDirectionXOf: bmFill. dtX _ self fillNormalXOf: bmFill. ds _ (deltaX * dsX) + (deltaY * (self fillDirectionYOf: bmFill)). dt _ (deltaX * dtX) + (deltaY * (self fillNormalYOf: bmFill)). aaLevel _ self aaLevelGet. firstPixel _ self aaFirstPixelFrom: leftX to: rightX. lastPixel _ self aaLastPixelFrom: leftX to: rightX. baseShift _ self aaShiftGet. cMask _ self aaColorMaskGet. cShift _ self aaColorShiftGet. x _ leftX. [x < firstPixel] whileTrue:[ tileFlag ifTrue:[ ds _ self repeatValue: ds max: bmWidth << 16. dt _ self repeatValue: dt max: bmHeight << 16]. xp _ ds // 16r10000. yp _ dt // 16r10000. tileFlag ifFalse:[ xp _ self clampValue: xp max: bmWidth. yp _ self clampValue: yp max: bmHeight]. (xp >= 0 and:[yp >= 0 and:[xp < bmWidth and:[yp < bmHeight]]]) ifTrue:[ fillValue _ self bitmapValue: bmFill bits: bits atX: xp y: yp. fillValue _ (fillValue bitAnd: cMask) >> cShift. idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + fillValue. ]. ds _ ds + dsX. dt _ dt + dtX. x _ x + 1. ]. cMask _ (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0. cShift _ self aaShiftGet. [x < lastPixel] whileTrue:[ tileFlag ifTrue:[ ds _ self repeatValue: ds max: bmWidth << 16. dt _ self repeatValue: dt max: bmHeight << 16]. xp _ ds // 16r10000. yp _ dt // 16r10000. tileFlag ifFalse:[ xp _ self clampValue: xp max: bmWidth. yp _ self clampValue: yp max: bmHeight]. (xp >= 0 and:[yp >= 0 and:[xp < bmWidth and:[yp < bmHeight]]]) ifTrue:[ fillValue _ self bitmapValue: bmFill bits: bits atX: xp y: yp. fillValue _ (fillValue bitAnd: cMask) >> cShift. idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + fillValue. ]. ds _ ds + (dsX << cShift). dt _ dt + (dtX << cShift). x _ x + aaLevel. ]. cMask _ self aaColorMaskGet. cShift _ self aaColorShiftGet. [x < rightX] whileTrue:[ tileFlag ifTrue:[ ds _ self repeatValue: ds max: bmWidth << 16. dt _ self repeatValue: dt max: bmHeight << 16]. xp _ ds // 16r10000. yp _ dt // 16r10000. tileFlag ifFalse:[ xp _ self clampValue: xp max: bmWidth. yp _ self clampValue: yp max: bmHeight]. (xp >= 0 and:[yp >= 0 and:[xp < bmWidth and:[yp < bmHeight]]]) ifTrue:[ fillValue _ self bitmapValue: bmFill bits: bits atX: xp y: yp. fillValue _ (fillValue bitAnd: cMask) >> cShift. idx _ x >> baseShift. spanBuffer at: idx put: (spanBuffer at: idx) + fillValue. ]. ds _ ds + dsX. dt _ dt + dtX. x _ x + 1. ]. ! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/27/1998 14:24'! loadBitmapFill: formOop colormap: cmOop tile: tileFlag from: point1 along: point2 normal: point3 xIndex: xIndex "Load the bitmap fill." | bmFill cmSize cmBits bmBits bmBitsSize bmWidth bmHeight bmDepth ppw bmRaster | self var: #cmBits declareC:'int *cmBits'. self var: #point1 declareC:'int *point1'. self var: #point2 declareC:'int *point2'. self var: #point3 declareC:'int *point3'. cmOop == interpreterProxy nilObject ifTrue:[ cmSize _ 0. cmBits _ nil. ] ifFalse:[ (interpreterProxy fetchClassOf: cmOop) == interpreterProxy classBitmap ifFalse:[^interpreterProxy primitiveFail]. cmSize _ interpreterProxy slotSizeOf: cmOop. cmBits _ interpreterProxy firstIndexableField: cmOop. ]. (interpreterProxy isIntegerObject: formOop) ifTrue:[^interpreterProxy primitiveFail]. (interpreterProxy isPointers: formOop) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: formOop) < 5 ifTrue:[^interpreterProxy primitiveFail]. bmBits _ interpreterProxy fetchPointer: 0 ofObject: formOop. (interpreterProxy fetchClassOf: bmBits) == interpreterProxy classBitmap ifFalse:[^interpreterProxy primitiveFail]. bmBitsSize _ interpreterProxy slotSizeOf: bmBits. bmWidth _ interpreterProxy fetchInteger: 1 ofObject: formOop. bmHeight _ interpreterProxy fetchInteger: 2 ofObject: formOop. bmDepth _ interpreterProxy fetchInteger: 3 ofObject: formOop. interpreterProxy failed ifTrue:[^nil]. (bmWidth >= 0 and:[bmHeight >= 0]) ifFalse:[^interpreterProxy primitiveFail]. (bmDepth = 32) | (bmDepth = 8) | (bmDepth = 16) | (bmDepth = 1) | (bmDepth = 2) | (bmDepth = 4) ifFalse:[^interpreterProxy primitiveFail]. (cmSize = 0 or:[cmSize = (1 << bmDepth)]) ifFalse:[^interpreterProxy primitiveFail]. ppw _ 32 // bmDepth. bmRaster _ bmWidth + (ppw-1) // ppw. bmBitsSize = (bmRaster * bmHeight) ifFalse:[^interpreterProxy primitiveFail]. bmFill _ self allocateBitmapFill: cmSize colormap: cmBits. engineStopped ifTrue:[^nil]. self bitmapWidthOf: bmFill put: bmWidth. self bitmapHeightOf: bmFill put: bmHeight. self bitmapDepthOf: bmFill put: bmDepth. self bitmapRasterOf: bmFill put: bmRaster. self bitmapSizeOf: bmFill put: bmBitsSize. self bitmapTileFlagOf: bmFill put: tileFlag. self objectIndexOf: bmFill put: xIndex. self loadFillOrientation: bmFill from: point1 along: point2 normal: point3 width: bmWidth height: bmHeight. ^bmFill! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/25/1998 17:25'! loadBitsFrom: bmFill "Note: Assumes that the contents of formArray has been checked before" | xIndex formOop bitsOop bitsLen | self returnTypeC:'int *'. xIndex _ self objectIndexOf: bmFill. xIndex > (interpreterProxy slotSizeOf: formArray) ifTrue:[^nil]. formOop _ interpreterProxy fetchPointer: xIndex ofObject: formArray. bitsOop _ interpreterProxy fetchPointer: 0 ofObject: formOop. bitsLen _ interpreterProxy slotSizeOf: bitsOop. bitsLen = (self bitmapSizeOf: bmFill) ifFalse:[^nil]. ^interpreterProxy firstIndexableField: bitsOop! ! !BalloonEnginePlugin methodsFor: 'fills-bitmaps' stamp: 'ar 11/27/1998 14:14'! repeatValue: delta max: maxValue | newDelta | self inline: true. newDelta _ delta. [newDelta < 0] whileTrue:[newDelta _ newDelta + maxValue]. [newDelta >= maxValue] whileTrue:[newDelta _ newDelta - maxValue]. ^newDelta! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonEnginePlugin class instanceVariableNames: ''! !BalloonEnginePlugin class methodsFor: 'class initialization' stamp: 'ar 11/11/1998 22:01'! declareCVarsIn: cg "Nothing to declare"! ! BalloonEnginePlugin subclass: #BalloonEngineSimulation instanceVariableNames: 'bbObj ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/28/1998 20:46'! assert: bool bool ifFalse:[^self error:'Assertion failed'].! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/9/1998 01:23'! circleCosTable ^CArrayAccessor on: #(1.0 0.98078528040323 0.923879532511287 0.831469612302545 0.7071067811865475 0.555570233019602 0.38268343236509 0.1950903220161286 0.0 -0.1950903220161283 -0.3826834323650896 -0.555570233019602 -0.707106781186547 -0.831469612302545 -0.9238795325112865 -0.98078528040323 -1.0 -0.98078528040323 -0.923879532511287 -0.831469612302545 -0.707106781186548 -0.555570233019602 -0.3826834323650903 -0.1950903220161287 0.0 0.1950903220161282 0.38268343236509 0.555570233019602 0.707106781186547 0.831469612302545 0.9238795325112865 0.98078528040323 1.0 )! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/9/1998 01:23'! circleSinTable ^CArrayAccessor on: #(0.0 0.1950903220161282 0.3826834323650897 0.555570233019602 0.707106781186547 0.831469612302545 0.923879532511287 0.98078528040323 1.0 0.98078528040323 0.923879532511287 0.831469612302545 0.7071067811865475 0.555570233019602 0.38268343236509 0.1950903220161286 0.0 -0.1950903220161283 -0.3826834323650896 -0.555570233019602 -0.707106781186547 -0.831469612302545 -0.9238795325112865 -0.98078528040323 -1.0 -0.98078528040323 -0.923879532511287 -0.831469612302545 -0.707106781186548 -0.555570233019602 -0.3826834323650903 -0.1950903220161287 0.0 )! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/24/1998 20:50'! colorTransform ^super colorTransform asPluggableAccessor: (Array with:[:obj :index| obj floatAt: index] with:[:obj :index :value| obj floatAt: index put: value])! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/28/1998 13:32'! copyBitsFrom: x0 to: x1 at: y bbObj copyBitsFrom: x0 to: x1 at: y.! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/29/1998 18:44'! dispatchOn: anInteger in: selectorArray "Simulate a case statement via selector table lookup. The given integer must be between 0 and selectorArray size-1, inclusive. For speed, no range test is done, since it is done by the at: operation." self perform: (selectorArray at: (anInteger + 1)).! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/24/1998 20:50'! edgeTransform ^super edgeTransform asPluggableAccessor: (Array with:[:obj :index| obj floatAt: index] with:[:obj :index :value| obj floatAt: index put: value])! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/29/1998 19:19'! ioMicroMSecs ^Time millisecondClockValue! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/28/1998 01:05'! loadBitBltFrom: oop bbObj _ oop. ^true! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/4/1998 14:05'! loadPointIntAt: index from: intArray "Load the int value from the given index in intArray" ^(index bitAnd: 1) = 0 ifTrue:[(intArray getObject at: (index // 2) + 1) x] ifFalse:[(intArray getObject at: (index // 2) + 1) y]! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/4/1998 14:04'! loadPointShortAt: index from: intArray "Load the int value from the given index in intArray" ^(index bitAnd: 1) = 0 ifTrue:[(intArray getObject at: (index // 2) + 1) x] ifFalse:[(intArray getObject at: (index // 2) + 1) y]! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/28/1998 01:05'! makeUnsignedFrom: integer integer < 0 ifTrue:[^(0 - integer - 1) bitInvert32] ifFalse:[^integer]! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/25/1998 19:24'! rShiftTable ^CArrayAccessor on: #(0 5 4 0 3 0 0 0 2 0 0 0 0 0 0 0 1).! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/4/1998 13:54'! shortRunLengthAt: i from: runArray ^runArray getObject lengthAtRun: i+1! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/4/1998 13:55'! shortRunValueAt: i from: runArray ^runArray getObject valueAtRun: i+1! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 10/31/1998 23:07'! showDisplayBits "Do nothing."! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/4/1998 19:51'! smallSqrtTable "Return a lookup table for rounded integer square root values from 0 to 31" ^CArrayAccessor on:#(0 1 1 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 )! ! !BalloonEngineSimulation methodsFor: 'simulation' stamp: 'ar 11/25/1998 02:23'! stopBecauseOf: stopReason "Don't stop because of need to flush." stopReason = GErrorNeedFlush ifFalse:[ ^super stopBecauseOf: stopReason. ].! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawBezier: line | canvas p1 p2 p3 | self assert:(self isBezier: line). p1 _ (self edgeXValueOf: line) @ (self edgeYValueOf: line) // self aaLevelGet. p2 _ (self bezierViaXOf: line) @ (self bezierViaYOf: line) // self aaLevelGet. p3 _ (self bezierEndXOf: line) @ (self bezierEndYOf: line) // self aaLevelGet. canvas _ Display getCanvas. canvas line: p1 to: p2 width: 2 color: Color blue; line: p2 to: p3 width: 2 color: Color blue.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 11/1/1998 01:16'! debugDrawEdge: edge self assert: (self isEdge: edge). (self isLine: edge) ifTrue:[^self debugDrawLine: edge]. (self isBezier: edge) ifTrue:[^self debugDrawBezier: edge]. self halt.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawHLine: yValue | canvas | canvas _ Display getCanvas. canvas line: 0 @ (yValue // self aaLevelGet) to: Display extent x @ (yValue // self aaLevelGet) width: 2 color: Color green.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawLine: line | canvas | self assert: (self isLine: line). canvas _ Display getCanvas. canvas line: (self edgeXValueOf: line) @ (self edgeYValueOf: line) // self aaLevelGet to: (self lineEndXOf: line) @ (self lineEndYOf: line) // self aaLevelGet width: 2 color: Color red.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawLineFrom: pt1 to: pt2 | canvas | canvas _ Display getCanvas. canvas line: (pt1 at: 0) @ (pt1 at: 1) // self aaLevelGet to: (pt2 at: 0) @ (pt2 at: 1) // self aaLevelGet width: 1 color: Color red.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawPt: pt | canvas | canvas _ Display getCanvas. canvas fillRectangle:((pt-2) corner: pt+2) color: Color red! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 5/25/2000 17:58'! debugDrawPtLineFrom: pt1 to: pt2 | canvas | canvas _ Display getCanvas. canvas line: pt1 to: pt2 width: 1 color: Color red.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 11/25/1998 00:43'! debugPrintObjects | object end | self inline: false. object _ 0. end _ objUsed. [object < end] whileTrue:[ Transcript cr; nextPut:$#; print: object; space; print: (self objectHeaderOf: object); space. (self isEdge: object) ifTrue:[Transcript nextPutAll:'(edge) ']. (self isFill:object) ifTrue:[Transcript nextPutAll:'(fill) ']. Transcript print: (self objectLengthOf: object); space. Transcript endEntry. object _ object + (self objectLengthOf: object). ].! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 11/1/1998 17:21'! debugPrintPoints: n Transcript cr. n > 0 ifTrue:[ Transcript print: (self point1Get at: 0) @ (self point1Get at: 1); space. ]. n > 1 ifTrue:[ Transcript print: (self point2Get at: 0) @ (self point2Get at: 1); space. ]. n > 2 ifTrue:[ Transcript print: (self point3Get at: 0) @ (self point3Get at: 1); space. ]. n > 3 ifTrue:[ Transcript print: (self point4Get at: 0) @ (self point4Get at: 1); space. ]. Transcript endEntry.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 11/5/1998 21:15'! printAET | edge | Transcript cr; show:'************* ActiveEdgeTable **************'. 0 to: self aetUsedGet - 1 do:[:i| edge _ aetBuffer at: i. Transcript cr; print: i; space; nextPutAll:'edge #';print: edge; space; nextPutAll:'x: '; print: (self edgeXValueOf: edge); space; nextPutAll:'y: '; print: (self edgeYValueOf: edge); space; nextPutAll:'z: '; print: (self edgeZValueOf: edge); space; nextPutAll:'fill0: '; print: (self edgeLeftFillOf: edge); space; nextPutAll:'fill1: '; print: (self edgeRightFillOf: edge); space; nextPutAll:'lines: '; print: (self edgeNumLinesOf: edge); space. (self areEdgeFillsValid: edge) ifFalse:[Transcript nextPutAll:' disabled']. Transcript endEntry. ].! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 11/5/1998 21:14'! printGET | edge | Transcript cr; show:'************* GlobalEdgeTable **************'. 0 to: self getUsedGet - 1 do:[:i| edge _ getBuffer at: i. Transcript cr; print: i; space; nextPutAll:'edge #';print: edge; space; nextPutAll:'x: '; print: (self edgeXValueOf: edge); space; nextPutAll:'y: '; print: (self edgeYValueOf: edge); space; nextPutAll:'z: '; print: (self edgeZValueOf: edge); space; nextPutAll:'fill0: '; print: (self edgeLeftFillOf: edge); space; nextPutAll:'fill1: '; print: (self edgeRightFillOf: edge); space; nextPutAll:'lines: '; print: (self edgeNumLinesOf: edge); space. (self areEdgeFillsValid: edge) ifFalse:[Transcript nextPutAll:' disabled']. Transcript endEntry. ].! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 10/30/1998 21:57'! quickPrint: curve Transcript nextPut:$(; print: curve start; space; print: curve via; space; print: curve end; nextPut:$).! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 10/30/1998 22:18'! quickPrintBezier: bezier Transcript cr. Transcript nextPut:$(; print: (self edgeXValueOf: bezier)@(self edgeYValueOf: bezier); space; print: (self bezierViaXOf: bezier)@(self bezierViaYOf: bezier); space; print: (self bezierEndXOf: bezier)@(self bezierEndYOf: bezier); nextPut:$). Transcript endEntry.! ! !BalloonEngineSimulation methodsFor: 'debug support' stamp: 'ar 10/30/1998 22:00'! quickPrintBezier: index first: aBool aBool ifTrue:[Transcript cr]. Transcript nextPut:$(; print: (self bzStartX: index)@(self bzStartY: index); space; print: (self bzViaX: index)@(self bzViaY: index); space; print: (self bzEndX: index)@(self bzEndY: index); nextPut:$). Transcript endEntry.! ! !BalloonEngineSimulation methodsFor: 'initialize' stamp: 'ar 1/12/1999 10:38'! initialize doProfileStats _ false.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonEngineSimulation class instanceVariableNames: ''! !BalloonEngineSimulation class methodsFor: 'instance creation' stamp: 'ar 10/29/1998 19:18'! new ^super new initialize! ! Object subclass: #BalloonFillData instanceVariableNames: 'index minX maxX yValue source destForm ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:56'! destForm ^destForm! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:56'! destForm: aForm destForm _ aForm! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! index ^index! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! index: anInteger index _ anInteger! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! maxX ^maxX! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! maxX: anInteger maxX _ anInteger! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! minX ^minX! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! minX: anInteger minX _ anInteger! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:56'! source ^source! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:56'! source: anObject source _ anObject! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/28/1998 16:35'! width ^maxX - minX! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! yValue ^yValue! ! !BalloonFillData methodsFor: 'accessing' stamp: 'ar 10/27/1998 15:55'! yValue: anInteger yValue _ anInteger! ! !BalloonFillData methodsFor: 'computing' stamp: 'ar 11/14/1998 19:32'! computeFill (destForm isNil or:[destForm width < self width]) ifTrue:[ destForm _ Form extent: (self width + 10) @ 1 depth: 32. ]. source computeFillFrom: minX to: maxX at: yValue in: destForm! ! Object subclass: #BalloonLineSimulation instanceVariableNames: 'start end xIncrement xDirection error errorAdjUp errorAdjDown ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 03:02'! end ^end! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 03:02'! end: aPoint end _ aPoint! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:31'! initialX ^start y <= end y ifTrue:[start x] ifFalse:[end x]! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:31'! initialY ^start y <= end y ifTrue:[start y] ifFalse:[end y]! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/27/1998 20:31'! initialZ ^0 "Assume no depth given"! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 03:02'! start ^start! ! !BalloonLineSimulation methodsFor: 'accessing' stamp: 'ar 10/30/1998 03:02'! start: aPoint start _ aPoint! ! !BalloonLineSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 20:52'! computeInitialStateFrom: source with: aTransformation "Compute the initial state in the receiver." start _ (aTransformation localPointToGlobal: source start) asIntegerPoint. end _ (aTransformation localPointToGlobal: source end) asIntegerPoint.! ! !BalloonLineSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 23:22'! stepToFirstScanLineAt: yValue in: edgeTableEntry "Compute the initial x value for the scan line at yValue" | startX endX startY endY yDir deltaY deltaX widthX | (start y) <= (end y) ifTrue:[ startX _ start x. endX _ end x. startY _ start y. endY _ end y. yDir _ 1. ] ifFalse:[ startX _ end x. endX _ start x. startY _ end y. endY _ start y. yDir _ -1. ]. deltaY _ endY - startY. deltaX _ endX - startX. "Quickly check if the line is visible at all" (yValue >= endY or:[deltaY = 0]) ifTrue:[^edgeTableEntry lines: 0]. "Check if edge goes left to right" deltaX >= 0 ifTrue:[ xDirection _ 1. widthX _ deltaX. error _ 0. ] ifFalse:[ xDirection _ -1. widthX _ 0 - deltaX. error _ 1 - deltaY. ]. "Check if edge is horizontal" deltaY = 0 ifTrue:[ xIncrement _ 0. errorAdjUp _ 0] ifFalse:["Check if edge is y-major" deltaY > widthX ifTrue:[ xIncrement _ 0. errorAdjUp _ widthX] ifFalse:[ xIncrement _ (widthX // deltaY) * xDirection. errorAdjUp _ widthX \\ deltaY]]. errorAdjDown _ deltaY. edgeTableEntry xValue: startX. edgeTableEntry lines: deltaY. "If not at first scan line then step down to yValue" yValue = startY ifFalse:[ startY to: yValue do:[:y| self stepToNextScanLineAt: y in: edgeTableEntry]. "And adjust remainingLines" edgeTableEntry lines: deltaY - (yValue - startY). ].! ! !BalloonLineSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 20:39'! stepToNextScanLineAt: yValue in: edgeTableEntry "Compute the next x value for the scan line at yValue. This message is sent during incremental updates. The yValue parameter is passed in here for edges that have more complicated computations," | x | x _ edgeTableEntry xValue + xIncrement. error _ error + errorAdjUp. error > 0 ifTrue:[ x _ x + xDirection. error _ error - errorAdjDown. ]. edgeTableEntry xValue: x.! ! !BalloonLineSimulation methodsFor: 'computing' stamp: 'ar 10/29/1998 23:42'! subdivide ^nil! ! !BalloonLineSimulation methodsFor: 'printing' stamp: 'ar 10/27/1998 23:20'! printOn: aStream aStream nextPutAll: self class name; nextPut:$(; print: start; nextPutAll:' - '; print: end; nextPut:$)! ! !BalloonLineSimulation methodsFor: 'printing' stamp: 'MPW 1/1/1901 21:57'! printOnStream: aStream aStream print: self class name; print:'('; write: start; print:' - '; write: end; print:')'.! ! PolygonMorph subclass: #BalloonMorph instanceVariableNames: 'target offsetFromTarget balloonOwner ' classVariableNames: 'BalloonColor BalloonFont ' poolDictionaries: '' category: 'Morphic-Widgets'! !BalloonMorph commentStamp: '' prior: 0! A balloon with text used for the display of explanatory information. Balloon help is integrated into Morphic as follows: If a Morph has the property #balloonText, then it will respond to #showBalloon by adding a text balloon to the world, and to #deleteBalloon by removing the balloon. Moreover, if mouseOverEnabled is true (see class msg), then the Hand will arrange to cause display of the balloon after the mouse has lingered over the morph for a while, and removal of the balloon when the mouse leaves the bounds of that morph. In any case, the Hand will attempt to remove any such balloons before handling mouseDown events, or displaying other balloons. Balloons should not be duplicated with veryDeepCopy unless their target is also duplicated at the same time.! !BalloonMorph methodsFor: 'initialization' stamp: 'sma 11/11/2000 16:08'! initialize super initialize. self beSmoothCurve. color _ self class balloonColor. borderColor _ Color black. borderWidth _ 1. offsetFromTarget _ 0@0! ! !BalloonMorph methodsFor: 'initialization' stamp: 'ar 10/4/2000 10:13'! popUpFor: aMorph hand: aHand "Pop up the receiver as balloon help for the given hand" balloonOwner _ aMorph. self popUpForHand: aHand.! ! !BalloonMorph methodsFor: 'initialization' stamp: 'ar 12/14/2000 23:59'! popUpForHand: aHand "Pop up the receiver as balloon help for the given hand" | worldBounds | self lock. self fullBounds. "force layout" aHand world addMorphFront: self. "So that if the translation below makes it overlap the receiver, it won't interfere with the rootMorphsAt: logic and hence cause flashing. Without this, flashing happens, believe me!!" ((worldBounds _ aHand world bounds) containsRect: self bounds) ifFalse: [self bounds: (self bounds translatedToBeWithin: worldBounds)]. aHand balloonHelp: self.! ! !BalloonMorph methodsFor: 'stepping' stamp: 'sma 12/23/1999 14:05'! step "Move with target." target ifNotNil: [self position: target position + offsetFromTarget]. ! ! !BalloonMorph methodsFor: 'stepping' stamp: 'di 9/18/97 10:10'! stepTime ^ 0 "every cycle"! ! !BalloonMorph methodsFor: 'private' stamp: 'sw 2/7/2000 01:49'! adjustedCenter "This horizontal adjustment is needed because we want the interior TextMorph to be centered within the visual balloon rather than simply within the BalloonMorph's bounding box. Without this, balloon-help text would be a bit off-center" ^ self center + (offsetFromTarget x sign * (5 @ 0))! ! !BalloonMorph methodsFor: 'private' stamp: 'sma 12/23/1999 14:06'! setTarget: aMorph (target _ aMorph) ifNotNil: [offsetFromTarget _ self position - target position]! ! !BalloonMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/27/2000 18:07'! morphicLayerNumber "helpful for insuring some morphs always appear in front of or behind others. smaller numbers are in front" ^5 "Balloons are very front-like things"! ! !BalloonMorph methodsFor: 'accessing' stamp: 'ar 10/3/2000 17:19'! balloonOwner ^balloonOwner! ! !BalloonMorph methodsFor: 'testing' stamp: 'ar 9/15/2000 17:56'! isBalloonHelp ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonMorph class instanceVariableNames: ''! !BalloonMorph class methodsFor: 'instance creation' stamp: 'sma 12/23/1999 20:05'! string: str for: morph ^ self string: str for: morph corner: #bottomLeft! ! !BalloonMorph class methodsFor: 'instance creation' stamp: 'sma 12/23/1999 20:04'! string: str for: morph corner: cornerName "Make up and return a balloon for morph. Find the quadrant that clips the text the least, using cornerName as a tie-breaker. tk 9/12/97" | tm vertices | tm _ self getTextMorph: str. vertices _ self getVertices: tm bounds. vertices _ self getBestLocation: vertices for: morph corner: cornerName. ^ self new color: morph balloonColor; setVertices: vertices; addMorph: tm; setTarget: morph! ! !BalloonMorph class methodsFor: 'private' stamp: 'sw 10/26/2000 09:44'! getBestLocation: vertices for: morph corner: cornerName "Try four rel locations of the balloon for greatest unclipped area. 12/99 sma" | rect maxArea verts rectCorner morphPoint mbc a mp dir bestVerts result usableArea | rect _ vertices first rect: (vertices at: 5). maxArea _ -1. verts _ vertices. usableArea _ (morph world ifNil: [self currentWorld]) viewBox. 1 to: 4 do: [:i | dir _ #(vertical horizontal) atWrap: i. verts _ verts collect: [:p | p flipBy: dir centerAt: rect center]. rectCorner _ #(bottomLeft bottomRight topRight topLeft) at: i. morphPoint _ #(topCenter topCenter bottomCenter bottomCenter) at: i. a _ ((rect align: (rect perform: rectCorner) with: (mbc _ morph boundsForBalloon perform: morphPoint)) intersect: usableArea) area. (a > maxArea or: [a = rect area and: [rectCorner = cornerName]]) ifTrue: [maxArea _ a. bestVerts _ verts. mp _ mbc]]. result _ bestVerts collect: [:p | p + (mp - bestVerts first)] "Inlined align:with:". ^ result! ! !BalloonMorph class methodsFor: 'private' stamp: 'sw 2/2/2000 22:13'! getTextMorph: aStringOrMorph "Construct text morph." | m text | aStringOrMorph isMorph ifTrue: [m _ aStringOrMorph] ifFalse: [BalloonFont ifNil: [text _ aStringOrMorph] ifNotNil: [text _ Text string: aStringOrMorph attribute: (TextFontReference toFont: BalloonFont)]. m _ (TextMorph new contents: text) centered]. m setToAdhereToEdge: #adjustedCenter. ^ m! ! !BalloonMorph class methodsFor: 'private' stamp: 'sma 12/23/1999 15:34'! getVertices: bounds "Construct vertices for a balloon up and to left of anchor" | corners | corners _ bounds corners atAll: #(1 4 3 2). ^ (Array with: corners first + (0 - bounds width // 3 @ 0) with: corners first + (0 - bounds width // 6 @ (bounds height // 2))) , corners! ! !BalloonMorph class methodsFor: 'utility' stamp: 'sma 11/11/2000 14:59'! balloonColor ^ BalloonColor! ! !BalloonMorph class methodsFor: 'utility' stamp: 'sw 1/31/2000 15:43'! balloonFont ^ BalloonFont! ! !BalloonMorph class methodsFor: 'utility' stamp: 'sw 1/31/2000 15:39'! chooseBalloonFont "BalloonMorph chooseBalloonFont" Preferences chooseFontWithPrompt: 'Select the font to be used for balloon help' andSendTo: self withSelector: #setBalloonFontTo:! ! !BalloonMorph class methodsFor: 'utility' stamp: 'sma 11/11/2000 14:59'! setBalloonColorTo: aColor aColor ifNotNil: [BalloonColor _ aColor]! ! !BalloonMorph class methodsFor: 'utility' stamp: 'sw 1/31/2000 15:40'! setBalloonFontTo: aFont aFont ifNotNil: [BalloonFont _ aFont]! ! RectangleMorph subclass: #BalloonRectangleMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Geometry'! !BalloonRectangleMorph methodsFor: 'initialize' stamp: 'ar 11/15/1998 22:31'! initialize super initialize. color _ GradientFillStyle ramp: {0.0 -> Color green. 0.5 -> Color yellow. 1.0 -> Color red}. color radial: true. borderColor _ GradientFillStyle ramp: {0.0 -> Color black. 1.0 -> Color white}. borderWidth _ 10. self extent: 100@100.! ! !BalloonRectangleMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 22:24'! doesBevels "To return true means that this object can show bevelled borders, and therefore can accept, eg, #raised or #inset as valid borderColors. Must be overridden by subclasses that do not support bevelled borders." ^ false! ! !BalloonRectangleMorph methodsFor: 'flexing' stamp: 'ar 11/15/1998 22:20'! newTransformationMorph ^MatrixTransformMorph new! ! !BalloonRectangleMorph methodsFor: 'drawing' stamp: 'ar 11/15/1998 22:40'! drawOn: aCanvas (color isKindOf: OrientedFillStyle) ifTrue:[ color origin: bounds center. color direction: (bounds extent x * 0.7) @ 0. color normal: 0@(bounds extent y * 0.7). ]. (borderColor isKindOf: OrientedFillStyle) ifTrue:[ borderColor origin: bounds topLeft. borderColor direction: (bounds extent x) @ 0. borderColor normal: 0@(bounds extent y). ]. aCanvas asBalloonCanvas drawRectangle: (bounds insetBy: borderWidth // 2) color: color borderWidth: borderWidth borderColor: borderColor.! ! Object subclass: #BalloonSolidFillSimulation instanceVariableNames: 'color ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonSolidFillSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 23:07'! computeFillFrom: minX to: maxX at: yValue in: form | bb | color isTransparent ifFalse:[ bb _ BitBlt toForm: form. bb fillColor: color. bb destX: 0 destY: 0 width: (maxX - minX) height: 1. bb combinationRule: Form over. bb copyBits].! ! !BalloonSolidFillSimulation methodsFor: 'computing' stamp: 'ar 10/27/1998 23:08'! computeInitialStateFrom: source with: aColorTransform color _ source asColor.! ! Object subclass: #BalloonState instanceVariableNames: 'transform colorTransform aaLevel ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Engine'! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:47'! aaLevel ^aaLevel! ! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:47'! aaLevel: aNumber aaLevel _ aNumber! ! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:42'! colorTransform ^colorTransform! ! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:42'! colorTransform: aColorTransform colorTransform _ aColorTransform! ! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:41'! transform ^transform! ! !BalloonState methodsFor: 'accessing' stamp: 'ar 12/30/1998 10:42'! transform: aMatrixTransform transform _ aMatrixTransform! ! MimeConverter subclass: #Base64MimeConverter instanceVariableNames: 'data ' classVariableNames: 'FromCharTable ToCharTable ' poolDictionaries: '' category: 'Collections-Streams'! !Base64MimeConverter commentStamp: '' prior: 0! This class encodes and decodes data in Base64 format. This is MIME encoding. We translate a whole stream at once, taking a Stream as input and giving one as output. Returns a whole stream for the caller to use. 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y Outbound: bytes are broken into 6 bit chunks, and the 0-63 value is converted to a character. 3 data bytes go into 4 characters. Inbound: Characters are translated in to 0-63 values and shifted into 8 bit bytes. (See: N. Borenstein, Bellcore, N. Freed, Innosoft, Network Working Group, Request for Comments: RFC 1521, September 1993, MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies. Sec 6.2) By Ted Kaehler, based on Tim Olson's Base64Filter.! !Base64MimeConverter methodsFor: 'conversion' stamp: 'tk 12/9/97 13:34'! mimeDecode "Convert a stream in base 64 with only a-z,A-Z,0-9,+,/ to a full byte stream of characters. Reutrn a whole stream for the user to read." | nibA nibB nibC nibD | [mimeStream atEnd] whileFalse: [ (nibA _ self nextValue) ifNil: [^ dataStream]. (nibB _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibA bitShift: 2) + (nibB bitShift: -4)) asCharacter. nibB _ nibB bitAnd: 16rF. (nibC _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibB bitShift: 4) + (nibC bitShift: -2)) asCharacter. nibC _ nibC bitAnd: 16r3. (nibD _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibC bitShift: 6) + nibD) asCharacter. ]. ^ dataStream! ! !Base64MimeConverter methodsFor: 'conversion' stamp: 'tk 12/9/97 13:39'! mimeDecodeToByteArray "Convert a stream in base 64 with only a-z,A-Z,0-9,+,/ to a full ByteArray of 0-255 values. Reutrn a whole stream for the user to read." | nibA nibB nibC nibD | [mimeStream atEnd] whileFalse: [ (nibA _ self nextValue) ifNil: [^ dataStream]. (nibB _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibA bitShift: 2) + (nibB bitShift: -4)). nibB _ nibB bitAnd: 16rF. (nibC _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibB bitShift: 4) + (nibC bitShift: -2)). nibC _ nibC bitAnd: 16r3. (nibD _ self nextValue) ifNil: [^ dataStream]. dataStream nextPut: ((nibC bitShift: 6) + nibD). ]. ^ dataStream! ! !Base64MimeConverter methodsFor: 'conversion' stamp: 'tk 12/9/97 12:57'! mimeEncode "Convert from data to 6 bit characters." | phase1 phase2 raw nib | phase1 _ phase2 _ false. [dataStream atEnd] whileFalse: [ data _ raw _ dataStream next asInteger. nib _ (data bitAnd: 16rFC) bitShift: -2. mimeStream nextPut: (ToCharTable at: nib+1). (raw _ dataStream next) ifNil: [raw _ 0. phase1 _ true]. data _ ((data bitAnd: 3) bitShift: 8) + raw asInteger. nib _ (data bitAnd: 16r3F0) bitShift: -4. mimeStream nextPut: (ToCharTable at: nib+1). (raw _ dataStream next) ifNil: [raw _ 0. phase2 _ true]. data _ ((data bitAnd: 16rF) bitShift: 8) + (raw asInteger). nib _ (data bitAnd: 16rFC0) bitShift: -6. mimeStream nextPut: (ToCharTable at: nib+1). nib _ (data bitAnd: 16r3F). mimeStream nextPut: (ToCharTable at: nib+1)]. phase1 ifTrue: [mimeStream skip: -2; nextPut: $=; nextPut: $=. ^ mimeStream]. phase2 ifTrue: [mimeStream skip: -1; nextPut: $=. ^ mimeStream]. ! ! !Base64MimeConverter methodsFor: 'conversion' stamp: 'tk 12/9/97 13:21'! nextValue "The next six bits of data char from the mimeStream, or nil. Skip all other chars" | raw num | [raw _ mimeStream next. raw ifNil: [^ nil]. "end of stream" raw == $= ifTrue: [^ nil]. num _ FromCharTable at: raw asciiValue + 1. num ifNotNil: [^ num]. "else ignore space, return, tab, ..." true] whileTrue.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Base64MimeConverter class instanceVariableNames: ''! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 2/19/2000 15:53'! decodeInteger: mimeString | bytes sum | "Decode the MIME string into an integer of any length" bytes _ (Base64MimeConverter mimeDecodeToBytes: (ReadStream on: mimeString)) contents. sum _ 0. bytes reverseDo: [:by | sum _ sum * 256 + by]. ^ sum! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 2/21/2000 17:22'! encodeInteger: int | strm | "Encode an integer of any length and return the MIME string" strm _ ReadWriteStream on: (ByteArray new: int digitLength). 1 to: int digitLength do: [:ii | strm nextPut: (int digitAt: ii)]. strm reset. ^ ((self mimeEncode: strm) contents) copyUpTo: $= "remove padding"! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 12/9/97 14:29'! example "Base64MimeConverter example" | ss bb | ss _ ReadWriteStream on: (String new: 10). ss nextPutAll: 'Hi There!!'. bb _ Base64MimeConverter mimeEncode: ss. "bb contents 'SGkgVGhlcmUh'" ^ (Base64MimeConverter mimeDecodeToChars: bb) contents ! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 12/9/97 13:53'! initialize FromCharTable _ Array new: 256. "nils" ToCharTable _ Array new: 64. ($A asciiValue to: $Z asciiValue) doWithIndex: [:val :ind | FromCharTable at: val+1 put: ind-1. ToCharTable at: ind put: val asCharacter]. ($a asciiValue to: $z asciiValue) doWithIndex: [:val :ind | FromCharTable at: val+1 put: ind+25. ToCharTable at: ind+26 put: val asCharacter]. ($0 asciiValue to: $9 asciiValue) doWithIndex: [:val :ind | FromCharTable at: val+1 put: ind+25+26. ToCharTable at: ind+26+26 put: val asCharacter]. FromCharTable at: $+ asciiValue + 1 put: 62. ToCharTable at: 63 put: $+. FromCharTable at: $/ asciiValue + 1 put: 63. ToCharTable at: 64 put: $/. ! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 12/12/97 11:41'! mimeDecodeToBytes: aStream "Return a RWBinaryOrTextStream of the original ByteArray. aStream has only 65 innocuous character values. aStream is not binary. (See class comment). 4 bytes in aStream goes to 3 bytes in output." | me | aStream position: 0. me _ self new mimeStream: aStream. me dataStream: (RWBinaryOrTextStream on: (ByteArray new: aStream size * 3 // 4)). me mimeDecodeToByteArray. me dataStream position: 0. ^ me dataStream! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 12/9/97 13:01'! mimeDecodeToChars: aStream "Return a ReadWriteStream of the original String. aStream has only 65 innocuous character values. It is not binary. (See class comment). 4 bytes in aStream goes to 3 bytes in output." | me | aStream position: 0. me _ self new mimeStream: aStream. me dataStream: (ReadWriteStream on: (String new: aStream size * 3 // 4)). me mimeDecode. me dataStream position: 0. ^ me dataStream! ! !Base64MimeConverter class methodsFor: 'as yet unclassified' stamp: 'tk 12/9/97 12:28'! mimeEncode: aStream "Return a ReadWriteStream of characters. The data of aStream is encoded as 65 innocuous characters. (See class comment). 3 bytes in aStream goes to 4 bytes in output." | me | aStream position: 0. me _ self new dataStream: aStream. me mimeStream: (ReadWriteStream on: (String new: aStream size + 20 * 4 // 3)). me mimeEncode. me mimeStream position: 0. ^ me mimeStream! ! RectangleMorph subclass: #BasicButton instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting'! !BasicButton commentStamp: '' prior: 0! A minimalist button-like object intended for use with the tile-scripting system.! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 6/16/1998 17:02'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'change label...' action: #setLabel.! ! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 11/29/1999 17:36'! initialize super initialize. self borderWidth: 1. self borderColor: Color yellow darker. self useRoundedCorners. self color: Color yellow. self label: 'Button'! ! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 6/16/1998 16:49'! label | s | s _ ''. self allMorphsDo: [:m | (m isKindOf: StringMorph) ifTrue: [s _ m contents]]. ^ s! ! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 12/7/1999 18:14'! label: aString | oldLabel m | (oldLabel _ self findA: StringMorph) ifNotNil: [oldLabel delete]. m _ StringMorph contents: aString font: TextStyle defaultFont. self extent: m extent + (borderWidth + 6). m position: self center - (m extent // 2). self addMorph: m. m lock! ! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 12/10/1999 09:07'! label: aString font: aFontOrNil | oldLabel m aFont | (oldLabel _ self findA: StringMorph) ifNotNil: [oldLabel delete]. aFont _ aFontOrNil ifNil: [Preferences standardButtonFont]. m _ StringMorph contents: aString font: aFont. self extent: (m width + 6) @ (m height + 6). m position: self center - (m extent // 2). self addMorph: m. m lock ! ! !BasicButton methodsFor: 'as yet unclassified' stamp: 'sw 12/10/1999 09:08'! setLabel | newLabel | newLabel _ FillInTheBlank request: 'Enter a new label for this button' initialAnswer: self label. newLabel isEmpty ifFalse: [self label: newLabel font: nil]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BasicButton class instanceVariableNames: ''! !BasicButton class methodsFor: 'as yet unclassified' stamp: 'sw 6/16/1998 16:58'! defaultNameStemForInstances ^ 'button'! ! Object subclass: #Behavior instanceVariableNames: 'superclass methodDict format ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !Behavior commentStamp: '' prior: 0! My instances describe the behavior of other objects. I provide the minimum state necessary for compiling methods, and creating and running instances. Most objects are created as instances of the more fully supported subclass, Class, but I am a good starting point for providing instance-specific behavior (as in Metaclass).! !Behavior methodsFor: 'initialize-release' stamp: 'ar 7/19/1999 23:00'! forgetDoIts "get rid of old DoIt methods" self removeSelectorSimply: #DoIt; removeSelectorSimply: #DoItIn:! ! !Behavior methodsFor: 'initialize-release' stamp: 'di 3/10/2000 08:40'! nonObsoleteClass "Attempt to find and return the current version of this obsolete class" | obsName | obsName _ self name. [obsName beginsWith: 'AnObsolete'] whileTrue: [obsName _ obsName copyFrom: 'AnObsolete' size + 1 to: obsName size]. ^ Smalltalk at: obsName asSymbol! ! !Behavior methodsFor: 'initialize-release' stamp: 'ar 9/10/1999 17:33'! obsolete "Invalidate and recycle local messages, e.g., zap the method dictionary if can be done safely." self canZapMethodDictionary ifTrue:[ methodDict _ MethodDictionary new ].! ! !Behavior methodsFor: 'initialize-release' stamp: 'ar 7/15/1999 16:39'! superclass: aClass methodDictionary: mDict format: fmt "Basic initialization of the receiver. Must only be sent to a new instance; else we would need Object flushCache." superclass _ aClass. format _ fmt. methodDict _ mDict.! ! !Behavior methodsFor: 'accessing'! compilerClass "Answer a compiler class appropriate for source methods of this class." ^Compiler! ! !Behavior methodsFor: 'accessing' stamp: 'sw 3/10/2000 16:55'! confirmRemovalOf: aSelector "Determine if it is okay to remove the given selector. Answer 1 if it should be removed, 2 if it should be removed followed by a senders browse, and 3 if it should not be removed." | count aMenu answer caption allCalls | allCalls _ Smalltalk allCallsOn: aSelector. (count _ allCalls size) == 0 ifTrue: [^ 1]. "no senders -- let the removal happen without warning" count == 1 ifTrue: [MessageSet parse: allCalls first toClassAndSelector: [:aClass :aSel | (aClass == self and: [aSel == aSelector]) ifTrue: [^ 1]]]. "only sender is itself" aMenu _ PopUpMenu labels: 'Remove it Remove, then browse senders Don''t remove, but show me those senders Forget it -- do nothing -- sorry I asked'. caption _ 'This message has ', count printString, ' sender'. count > 1 ifTrue: [caption _ caption copyWith: $s]. answer _ aMenu startUpWithCaption: caption. answer == 3 ifTrue: [Smalltalk browseMessageList: allCalls name: 'Senders of ', aSelector autoSelect: aSelector keywords first]. answer == 0 ifTrue: [answer _ 3]. "If user didn't answer, treat it as cancel" ^ answer min: 3! ! !Behavior methodsFor: 'accessing'! decompilerClass "Answer a decompiler class appropriate for compiled methods of this class." ^Decompiler! ! !Behavior methodsFor: 'accessing' stamp: 'ar 7/11/1999 05:17'! environment "Return the environment in which the receiver is visible" ^Smalltalk! ! !Behavior methodsFor: 'accessing'! evaluatorClass "Answer an evaluator class appropriate for evaluating expressions in the context of this class." ^Compiler! ! !Behavior methodsFor: 'accessing'! format "Answer an Integer that encodes the kinds and numbers of variables of instances of the receiver." ^format! ! !Behavior methodsFor: 'accessing' stamp: 'di 3/27/1999 23:19'! methodDict methodDict == nil ifTrue: [self recoverFromMDFault]. ^ methodDict! ! !Behavior methodsFor: 'accessing' stamp: 'rca 7/26/2000 16:53'! name "Answer a String that is the name of the receiver." ^'a subclass of ', superclass name! ! !Behavior methodsFor: 'accessing'! parserClass "Answer a parser class to use for parsing method headers." ^self compilerClass parserClass! ! !Behavior methodsFor: 'accessing'! sourceCodeTemplate "Answer an expression to be edited and evaluated in order to define methods in this class." ^'message selector and argument names "comment stating purpose of message" | temporary variable names | statements'! ! !Behavior methodsFor: 'accessing'! subclassDefinerClass "Answer an evaluator class appropriate for evaluating definitions of new subclasses of this class." ^Compiler! ! !Behavior methodsFor: 'accessing' stamp: 'ar 7/13/1999 22:00'! typeOfClass "Answer a symbol uniquely describing the type of the receiver" self instSpec = CompiledMethod instSpec ifTrue:[^#compiledMethod]. "Very special!!" self isBytes ifTrue:[^#bytes]. (self isWords and:[self isPointers not]) ifTrue:[^#words]. self isWeak ifTrue:[^#weak]. self isVariable ifTrue:[^#variable]. ^#normal.! ! !Behavior methodsFor: 'testing' stamp: 'ar 7/15/1999 14:03'! autoMutateInstances "Return true if the receiver should automatically mutate its instances to a new class layout on recompilation." ^true! ! !Behavior methodsFor: 'testing' stamp: 'ar 9/10/1999 17:29'! canZapMethodDictionary "Return true if it is safe to zap the method dictionary on #obsolete" ^true! ! !Behavior methodsFor: 'testing'! instSize "Answer the number of named instance variables (as opposed to indexed variables) of the receiver." self flag: #instSizeChange. "Smalltalk browseAllCallsOn: #instSizeChange" " NOTE: This code supports the backward-compatible extension to 8 bits of instSize. When we revise the image format, it should become... ^ ((format bitShift: -1) bitAnd: 16rFF) - 1 Note also that every other method in this category will require 2 bits more of right shift after the change. " ^ ((format bitShift: -10) bitAnd: 16rC0) + ((format bitShift: -1) bitAnd: 16r3F) - 1! ! !Behavior methodsFor: 'testing'! instSpec ^ (format bitShift: -7) bitAnd: 16rF! ! !Behavior methodsFor: 'testing' stamp: 'ar 7/9/1999 18:18'! isBehavior "Return true if the receiver is a behavior" ^true! ! !Behavior methodsFor: 'testing'! isBits "Answer whether the receiver contains just bits (not pointers)." ^ self instSpec >= 6! ! !Behavior methodsFor: 'testing'! isBytes "Answer whether the receiver has 8-bit instance variables." ^ self instSpec >= 8! ! !Behavior methodsFor: 'testing'! isFixed "Answer whether the receiver does not have a variable (indexable) part." ^self isVariable not! ! !Behavior methodsFor: 'testing' stamp: 'ar 7/14/1999 02:38'! isObsolete "Return true if the receiver is obsolete." ^self instanceCount = 0! ! !Behavior methodsFor: 'testing'! isPointers "Answer whether the receiver contains just pointers (not bits)." ^self isBits not! ! !Behavior methodsFor: 'testing'! isVariable "Answer whether the receiver has indexable variables." ^ self instSpec >= 2! ! !Behavior methodsFor: 'testing' stamp: 'ar 3/21/98 02:36'! isWeak "Answer whether the receiver has contains weak references." ^ self instSpec = 4! ! !Behavior methodsFor: 'testing'! isWords "Answer whether the receiver has 16-bit instance variables." ^self isBytes not! ! !Behavior methodsFor: 'testing' stamp: 'ar 7/11/1999 05:36'! shouldNotBeRedefined "Return true if the receiver should not be redefined. The assumption is that compact classes, classes in Smalltalk specialObjects and Behaviors should not be redefined" ^(Smalltalk compactClassesArray includes: self) or:[(Smalltalk specialObjectsArray includes: self) or:[self isKindOf: self]]! ! !Behavior methodsFor: 'copying' stamp: 'di 2/17/2000 22:37'! copy "Answer a copy of the receiver without a list of subclasses." | myCopy | myCopy _ self shallowCopy. ^myCopy methodDictionary: self methodDict copy! ! !Behavior methodsFor: 'copying' stamp: 'di 2/17/2000 22:37'! copyOfMethodDictionary "Return a copy of the receiver's method dictionary" ^ self methodDict copy! ! !Behavior methodsFor: 'copying' stamp: 'tk 4/16/1999 17:30'! deepCopy "Classes should only be shallowCopied or made anew." ^ self shallowCopy! ! !Behavior methodsFor: 'printing' stamp: 'sw 10/13/2000 12:59'! defaultNameStemForInstances "Answer a basis for external names for default instances of the receiver. For classees, the class-name itself is a good one." ^ self name! ! !Behavior methodsFor: 'printing'! literalScannedAs: scannedLiteral notifying: requestor "Postprocesses a literal scanned by Scanner scanToken (esp. xLitQuote). If scannedLiteral is not an association, answer it. Else, if it is of the form: nil->#NameOfMetaclass answer nil->theMetaclass, if any has that name, else report an error. Else, if it is of the form: #NameOfGlobalVariable->anythiEng answer the global, class, or pool association with that nameE, if any, else add it to Undeclared a answer the new Association." | key value | (scannedLiteral isMemberOf: Association) ifFalse: [^ scannedLiteral]. key _ scannedLiteral key. value _ scannedLiteral value. key isNil ifTrue: "###" [self scopeHas: value ifTrue: [:assoc | (assoc value isKindOf: Behavior) ifTrue: [^ nil->assoc value class]]. requestor notify: 'No such metaclass'. ^false]. (key isMemberOf: Symbol) ifTrue: "##" [(self scopeHas: key ifTrue: [:assoc | ^assoc]) ifFalse: [Undeclared at: key put: nil. ^ Undeclared associationAt: key]]. requestor notify: '## must be followed by a non-local variable name'. ^false " Form literalScannedAs: 14 notifying: nil 14 Form literalScannedAs: #OneBitForm notiEfying: nil OneBitForm Form literalScannedAs: ##OneBitForm notifying: nil OneBitForm->a Form Form literalScannedAs: ##Form notifying: nil Form->Form Form literalScannedAs: ###Form notifying: nil nilE->Form class "! ! !Behavior methodsFor: 'printing'! printHierarchy "Answer a description containing the names and instance variable names of all of the subclasses and superclasses of the receiver." | aStream index | index _ 0. aStream _ WriteStream on: (String new: 16). self allSuperclasses reverseDo: [:aClass | aStream crtab: index. index _ index + 1. aStream nextPutAll: aClass name. aStream space. aStream print: aClass instVarNames]. aStream cr. self printSubclassesOn: aStream level: index. ^aStream contents! ! !Behavior methodsFor: 'printing'! printOn: aStream "Refer to the comment in Object|printOn:." aStream nextPutAll: 'a descendent of '. superclass printOn: aStream! ! !Behavior methodsFor: 'printing' stamp: 'MPW 1/1/1901 21:56'! printOnStream: aStream "Refer to the comment in Object|printOn:." aStream print: 'a descendent of '; write:superclass.! ! !Behavior methodsFor: 'printing'! storeLiteral: aCodeLiteral on: aStream "Store aCodeLiteral on aStream, changing an Association to ##GlobalName or ###MetaclassSoleInstanceName format if appropriate" | key value | (aCodeLiteral isMemberOf: Association) ifFalse: [aCodeLiteral storeOn: aStream. ^self]. key _ aCodeLiteral key. (key isNil and: [(value _ aCodeLiteral value) isMemberOf: Metaclass]) ifTrue: [aStream nextPutAll: '###'; nextPutAll: value soleInstance name. ^self]. ((key isMemberOf: Symbol) and: [self scopeHas: key ifTrue: [:ignore]]) ifTrue: [aStream nextPutAll: '##'; nextPutAll: key. ^self]. aCodeLiteral storeOn: aStream! ! !Behavior methodsFor: 'creating class hierarchy' stamp: 'ar 7/10/1999 12:10'! superclass: aClass "Change the receiver's superclass to be aClass." "Note: Do not use 'aClass isKindOf: Behavior' here in case we recompile from Behavior itself." (aClass == nil or: [aClass isBehavior]) ifTrue: [superclass _ aClass. Object flushCache] ifFalse: [self error: 'superclass must be a class-describing object']! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 2/17/2000 22:41'! addSelector: selector withMethod: compiledMethod "Add the message selector with the corresponding compiled method to the receiver's method dictionary." | oldMethod | oldMethod _ self lookupSelector: selector. self methodDict at: selector put: compiledMethod. "Now flush Squeak's method cache, either by selector or by method" oldMethod == nil ifFalse: [oldMethod flushCache]. selector flushCache! ! !Behavior methodsFor: 'creating method dictionary'! compile: code "Compile the argument, code, as source code in the context of the receiver. Create an error notification if the code can not be compiled. The argument is either a string or an object that converts to a string or a PositionableStream on an object that converts to a string." ^self compile: code notifying: nil! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'tk 12/6/97 21:33'! compile: code notifying: requestor "Compile the argument, code, as source code in the context of the receiver and insEtall the result in the receiver's method dictionary. The second argument, requestor, is to be notified if an error occurs. The argument code is either a string or an object that converts to a string or a PositionableStream. This method also saves the source code." | method selector methodNode | method _ self compile: code "a Text" notifying: requestor trailer: #(0 0 0 0) ifFail: [^nil] elseSetSelectorAndNode: [:sel :parseNode | selector _ sel. methodNode _ parseNode]. method putSource: code "a Text" fromParseNode: methodNode inFile: 2 withPreamble: [:f | f cr; nextPut: $!!; nextChunkPut: 'Behavior method'; cr]. ^selector! ! !Behavior methodsFor: 'creating method dictionary'! compileAll ^ self compileAllFrom: self! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 9/10/1999 15:53'! compileAllFrom: oldClass "Compile all the methods in the receiver's method dictionary. This validates sourceCode and variable references and forces all methods to use the current bytecode set" "ar 7/10/1999: Use oldClass selectors not self selectors" oldClass selectorsDo: [:sel | self recompile: sel from: oldClass]. Smalltalk currentProjectDo: [:proj | proj compileAllIsolated: self from: oldClass].! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 2/17/2000 22:37'! compress "Compact the method dictionary of the receiver." self methodDict rehash! ! !Behavior methodsFor: 'creating method dictionary'! decompile: selector "Find the compiled code associated with the argument, selector, as a message selector in the receiver's method dictionary and decompile it. Answer the resulting source code as a string. Create an error notification if the selector is not in the receiver's method dictionary." ^self decompilerClass new decompile: selector in: self! ! !Behavior methodsFor: 'creating method dictionary'! defaultSelectorForMethod: aMethod "Given a method, invent and answer an appropriate message selector (a Symbol), that is, one that will parse with the correct number of arguments." | aStream | aStream _ WriteStream on: (String new: 16). aStream nextPutAll: 'DoIt'. 1 to: aMethod numArgs do: [:i | aStream nextPutAll: 'with:']. ^aStream contents asSymbol! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'ar 7/11/1999 05:11'! methodDictionary "Convenience" ^self methodDict! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'ar 7/12/1999 07:45'! methodDictionary: aDictionary "Store the argument, aDictionary, as the method dictionary of the receiver." methodDict _ aDictionary.! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 5/24/2000 16:05'! recompile: selector from: oldClass "Compile the method associated with selector in the receiver's method dictionary." "ar 7/10/1999: Use oldClass compiledMethodAt: not self compiledMethodAt:" | method trailer methodNode | method _ oldClass compiledMethodAt: selector. trailer _ (method endPC + 1 to: method size) collect: [:i | method at: i]. methodNode _ self compilerClass new compile: (oldClass sourceCodeAt: selector) in: self notifying: nil ifFail: [^ self]. "Assume OK after proceed from SyntaxError" selector == methodNode selector ifFalse: [self error: 'selector changed!!']. self addSelector: selector withMethod: (methodNode generate: trailer). ! ! !Behavior methodsFor: 'creating method dictionary'! recompileChanges "Compile all the methods that are in the changes file. This validates sourceCode and variable references and forces methods to use the current bytecode set" self selectorsDo: [:sel | (self compiledMethodAt: sel) fileIndex > 1 ifTrue: [self recompile: sel from: self]]! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 4/1/2000 10:11'! recompileNonResidentMethod: method atSelector: selector from: oldClass "Recompile the method supplied in the context of this class." | trailer methodNode | trailer _ (method size - 3 to: method size) collect: [:i | method at: i]. methodNode _ self compilerClass new compile: (method getSourceFor: selector in: oldClass) in: self notifying: nil ifFail: ["We're in deep doo-doo if this fails (syntax error). Presumably the user will correct something and proceed, thus installing the result in this methodDict. We must retrieve that new method, and restore the original (or remove) and then return the method we retrieved." ^ self error: 'see comment']. selector == methodNode selector ifFalse: [self error: 'selector changed!!']. ^ methodNode generate: trailer ! ! !Behavior methodsFor: 'creating method dictionary' stamp: 'di 1/2/1999 15:16'! removeSelector: selector "Assuming that the argument, selector (a Symbol), is a message selector in my method dictionary, remove it and its method." ^ self removeSelectorSimply: selector! ! !Behavior methodsFor: 'instance creation'! basicNew "Primitive. Answer an instance of the receiver (which is a class) with no indexable variables. Fail if the class is indexable. Essential. See Object documentation whatIsAPrimitive." self isVariable ifTrue: [ ^ self basicNew: 0 ]. "space must be low" Smalltalk signalLowSpace. ^ self basicNew "retry if user proceeds" ! ! !Behavior methodsFor: 'instance creation' stamp: 'di 8/18/2000 22:10'! basicNew: sizeRequested "Primitive. Answer an instance of this class with the number of indexable variables specified by the argument, sizeRequested. Fail if this class is not indexable or if the argument is not a positive Integer, or if there is not enough memory available. Essential. See Object documentation whatIsAPrimitive." self isVariable ifFalse: [self error: self printString, ' cannot have variable sized instances']. (sizeRequested isInteger and: [sizeRequested >= 0]) ifTrue: ["arg okay; space must be low." Smalltalk signalLowSpace. ^ self basicNew: sizeRequested "retry if user proceeds"]. self primitiveFailed! ! !Behavior methodsFor: 'instance creation' stamp: 'sw 5/4/2000 20:47'! initializedInstance "Answer an instance of the receiver which in some sense is initialized. In the case of Morphs, this will yield an instance that can be attached to the Hand after having received the same kind of basic initialization that would be obtained from an instance chosen from the 'new morph' menu. Return nil if the receiver is reluctant for some reason to return such a thing" ^ self new! ! !Behavior methodsFor: 'instance creation' stamp: 'di 8/18/2000 20:27'! new "Answer a new instance of the receiver (which is a class) with no indexable variables. Fail if the class is indexable." "This method runs primitively if successful" ^ self basicNew "Exceptional conditions will be handled in basicNew" ! ! !Behavior methodsFor: 'instance creation' stamp: 'di 8/18/2000 20:32'! new: sizeRequested "Answer an instance of this class with the number of indexable variables specified by the argument, sizeRequested." "This method runs primitively if successful" ^ self basicNew: sizeRequested "Exceptional conditions will be handled in basicNew:" ! ! !Behavior methodsFor: 'accessing class hierarchy'! allSubclassesWithLevelDo: classAndLevelBlock startingLevel: level "Walk the tree of subclasses, giving the class and its level" | subclassNames subclass | classAndLevelBlock value: self value: level. self == Class ifTrue: [^ self]. "Don't visit all the metaclasses" "Visit subclasses in alphabetical order" subclassNames _ SortedCollection new. self subclassesDo: [:subC | subclassNames add: subC name]. subclassNames do: [:name | (Smalltalk at: name) allSubclassesWithLevelDo: classAndLevelBlock startingLevel: level+1]! ! !Behavior methodsFor: 'accessing class hierarchy'! allSuperclasses "Answer an OrderedCollection of the receiver's and the receiver's ancestor's superclasses. The first element is the receiver's immediate superclass, followed by its superclass; the last element is Object." | temp | superclass == nil ifTrue: [^OrderedCollection new] ifFalse: [temp _ superclass allSuperclasses. temp addFirst: superclass. ^temp]! ! !Behavior methodsFor: 'accessing class hierarchy'! superclass "Answer the receiver's superclass, a Class." ^superclass! ! !Behavior methodsFor: 'accessing class hierarchy'! withAllSuperclasses "Answer an OrderedCollection of the receiver and the receiver's superclasses. The first element is the receiver, followed by its superclass; the last element is Object." | temp | temp _ self allSuperclasses. temp addFirst: self. ^ temp! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'bf 9/27/1999 17:23'! >> selector "Answer the compiled method associated with the argument, selector (a Symbol), a message selector in the receiver's method dictionary. If the selector is not in the dictionary, create an error notification." ^self compiledMethodAt: selector ! ! !Behavior methodsFor: 'accessing method dictionary'! allSelectors "Answer a Set of all the message selectors that instances of the receiver can understand." | temp | superclass == nil ifTrue: [^self selectors] ifFalse: [temp _ superclass allSelectors. temp addAll: self selectors. ^temp] "Point allSelectors"! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 10/19/1999 15:12'! changeRecordsAt: selector "Return a list of ChangeRecords for all versions of the method at selector. Source code can be retrieved by sending string to any one. Return nil if the method is absent." "(Pen changeRecordsAt: #go:) collect: [:cRec | cRec string]" | aList | aList _ VersionsBrowser new scanVersionsOf: (self compiledMethodAt: selector ifAbsent: [^ nil]) class: self meta: self isMeta category: (self whichCategoryIncludesSelector: selector) selector: selector. ^ aList ifNotNil: [aList changeList]! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:37'! compiledMethodAt: selector "Answer the compiled method associated with the argument, selector (a Symbol), a message selector in the receiver's method dictionary. If the selector is not in the dictionary, create an error notification." ^ self methodDict at: selector! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:41'! compiledMethodAt: selector ifAbsent: aBlock "Answer the compiled method associated with the argument, selector (a Symbol), a message selector in the receiver's method dictionary. If the selector is not in the dictionary, return the value of aBlock" ^ self methodDict at: selector ifAbsent: [aBlock value]! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'tk 1/7/98 10:31'! compressedSourceCodeAt: selector "(Paragraph compressedSourceCodeAt: #displayLines:affectedRectangle:) size 721 1921 Paragraph selectors inject: 0 into: [:tot :sel | tot + (Paragraph compressedSourceCodeAt: sel) size] 13606 31450" | rawText parse | rawText _ (self sourceCodeAt: selector) asString. parse _ self compilerClass new parse: rawText in: self notifying: nil. ^ rawText compressWithTable: ((selector keywords , parse tempNames , self instVarNames , #(self super ifTrue: ifFalse:) , ((0 to: 7) collect: [:i | String streamContents: [:s | s cr. i timesRepeat: [s tab]]]) , (self compiledMethodAt: selector) literalStrings) asSortedCollection: [:a :b | a size > b size])! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 12/1/2000 20:12'! firstCommentAt: selector "Answer a string representing the first comment in the method associated with selector. Return an empty string if the relevant source file is not available, or if the method's source code does not contain a comment. Not smart enough to bypass quotes in string constants, but does map doubled quote into a single quote." | sourceString commentStart pos nextQuotePos | sourceString _ (self sourceCodeAt: selector) asString. sourceString size == 0 ifTrue: [^ '']. commentStart _ sourceString findString: '"' startingAt: 1. commentStart == 0 ifTrue: [^ '']. pos _ commentStart + 1. [(nextQuotePos _ sourceString findString: '"' startingAt: pos) == (sourceString findString: '""' startingAt: pos)] whileTrue: [pos _ nextQuotePos + 2]. commentStart == nextQuotePos ifTrue: [^ '']. "Must have been a quote in string literal" ^ (sourceString copyFrom: commentStart + 1 to: nextQuotePos - 1) copyReplaceAll: '""' with: '"' "Behavior firstCommentAt: #firstCommentAt:"! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 12/5/2000 08:53'! firstPrecodeCommentFor: selector "If there is a comment in the source code at the given selector that preceeds the body of the method, return it here, else return nil" | parser lastHeaderChar firstCommentPosition | "Behavior firstPrecodeCommentFor: #firstPrecodeCommentFor:" (parser _ self parserClass new) parseSelector: (self sourceCodeAt: selector). lastHeaderChar _ parser endOfLastToken. firstCommentPosition _ self positionOfFirstCommentAt: selector. ^ (firstCommentPosition == nil or: [firstCommentPosition <= (lastHeaderChar + 4)]) ifFalse: [nil] ifTrue: [self firstCommentAt: selector]! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'tk 4/26/1999 07:28'! formalParametersAt: aSelector "Return the names of the arguments used in this method." | source parser message list params | source _ self sourceCodeAt: aSelector ifAbsent: [^ #()]. "for now" (parser _ self parserClass new) parseSelector: source. message _ source copyFrom: 1 to: (parser endOfLastToken min: source size). list _ message string findTokens: Character separators. params _ OrderedCollection new. list withIndexDo: [:token :ind | ind even ifTrue: [params addLast: token]]. ^ params! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 1/2/1999 15:45'! lookupSelector: selector "Look up the given selector in my methodDictionary. Return the corresponding method if found. Otherwise chase the superclass chain and try again. Return nil if no method is found." | lookupClass | lookupClass _ self. [lookupClass == nil] whileFalse: [(lookupClass includesSelector: selector) ifTrue: [^ lookupClass compiledMethodAt: selector]. lookupClass _ lookupClass superclass]. ^ nil! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 12/1/2000 20:25'! methodHeaderFor: selector "Answer the string corresponding to the method header for the given selector" | sourceString parser | sourceString _ self ultimateSourceCodeAt: selector ifAbsent: [self error: 'not found']. (parser _ self parserClass new) parseSelector: sourceString. ^ sourceString asString copyFrom: 1 to: (parser endOfLastToken min: sourceString size) "Behavior methodHeaderFor: #methodHeaderFor: " ! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:41'! methodsDo: aBlock "Evaluate aBlock for all the compiled methods in my method dictionary." ^ self methodDict valuesDo: aBlock! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 11/30/2000 10:29'! positionOfFirstCommentAt: aSelector "Answer the position in the source string associated with aSelector of the first comment therein, or an empty string if none" | sourceString commentStart | sourceString _ (self sourceCodeAt: aSelector) asString. sourceString size == 0 ifTrue: [^ 0]. commentStart _ sourceString findString: '"' startingAt: 1. ^ commentStart! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 12/5/2000 11:06'! precodeCommentOrInheritedCommentFor: selector "Answer a string representing the first comment in the method associated with selector, considering however only comments that occur before the beginning of the actual code. If the version recorded in the receiver is uncommented, look up the inheritance chain. Return an empty string if none found. Not smart enough to bypass quotes in string constants, but does map doubled quote into a single quote." | aSuper aComment | ^ (aComment _ self firstPrecodeCommentFor: selector) isEmptyOrNil ifFalse: [aComment] ifTrue: [(self == Behavior or: [superclass == nil or: [(aSuper _ superclass classThatUnderstands: selector) == nil]]) ifTrue: [''] ifFalse: [aSuper precodeCommentOrInheritedCommentFor: selector]] "Utilities class precodeCommentOrInheritedCommentFor: #testingComment"! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 3/27/1999 13:02'! rootStubInImageSegment: imageSegment ^ ImageSegmentRootStub new xxSuperclass: superclass format: format segment: imageSegment! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 3/27/1999 23:21'! selectorAtMethod: method setClass: classResultBlock "Answer both the message selector associated with the compiled method and the class in which that selector is defined." | sel | sel _ self methodDict keyAtIdentityValue: method ifAbsent: [superclass == nil ifTrue: [classResultBlock value: self. ^self defaultSelectorForMethod: method]. sel _ superclass selectorAtMethod: method setClass: classResultBlock. "Set class to be self, rather than that returned from superclass. " sel == (self defaultSelectorForMethod: method) ifTrue: [classResultBlock value: self]. ^sel]. classResultBlock value: self. ^sel! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:38'! selectors "Answer a Set of all the message selectors specified in the receiver's method dictionary." ^ self methodDict keys "Point selectors."! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:41'! selectorsAndMethodsDo: aBlock "Evaluate selectorBlock for all the message selectors in my method dictionary." ^ self methodDict keysAndValuesDo: aBlock! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:38'! selectorsDo: selectorBlock "Evaluate selectorBlock for all the message selectors in my method dictionary." ^ self methodDict keysDo: selectorBlock! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'tk 3/24/1999 07:44'! selectorsWithArgs: numberOfArgs "Return all selectors defined in this class that take this number of arguments. Could use String.keywords. Could see how compiler does this." | list num | list _ OrderedCollection new. self selectorsDo: [:aSel | num _ aSel count: [:char | char == $:]. num = 0 ifTrue: [aSel last isLetter ifFalse: [num _ 1]]. num = numberOfArgs ifTrue: [list add: aSel]]. ^ list! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:40'! sourceCodeAt: selector ^ (self methodDict at: selector) getSourceFor: selector in: self! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'di 2/17/2000 22:40'! sourceCodeAt: selector ifAbsent: aBlock ^ (self methodDict at: selector ifAbsent: [^ aBlock value]) getSourceFor: selector in: self! ! !Behavior methodsFor: 'accessing method dictionary'! sourceMethodAt: selector "Answer the paragraph corresponding to the source code for the argument." ^(self sourceCodeAt: selector) asText makeSelectorBoldIn: self! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sw 11/3/97 00:10'! sourceMethodAt: selector ifAbsent: aBlock "Answer the paragraph corresponding to the source code for the argument." ^ (self sourceCodeAt: selector ifAbsent: [^ aBlock value]) asText makeSelectorBoldIn: self! ! !Behavior methodsFor: 'accessing instances and variables'! allClassVarNames "Answer a Set of the names of the receiver's and the receiver's ancestor's class variables." ^superclass allClassVarNames! ! !Behavior methodsFor: 'accessing instances and variables'! allInstVarNames "Answer an Array of the names of the receiver's instance variables. The Array ordering is the order in which the variables are stored and accessed by the interpreter." | vars | superclass == nil ifTrue: [vars _ self instVarNames copy] "Guarantee a copy is answered." ifFalse: [vars _ superclass allInstVarNames , self instVarNames]. ^vars! ! !Behavior methodsFor: 'accessing instances and variables' stamp: 'jm 5/20/1998 15:53'! allInstances "Answer a collection of all current instances of the receiver." | all | all _ OrderedCollection new. self allInstancesDo: [:x | x == all ifFalse: [all add: x]]. ^ all asArray ! ! !Behavior methodsFor: 'accessing instances and variables'! allSharedPools "Answer a Set of the names of the pools (Dictionaries) that the receiver and the receiver's ancestors share." ^superclass allSharedPools! ! !Behavior methodsFor: 'accessing instances and variables' stamp: 'di 6/20/97 10:51'! allSubInstances "Answer a list of all current instances of the receiver and all of its subclasses." | aCollection | aCollection _ OrderedCollection new. self allSubInstancesDo: [:x | x == aCollection ifFalse: [aCollection add: x]]. ^ aCollection! ! !Behavior methodsFor: 'accessing instances and variables'! classVarNames "Answer a Set of the receiver's class variable names." ^Set new! ! !Behavior methodsFor: 'accessing instances and variables'! inspectAllInstances "Inpsect all instances of the receiver. 1/26/96 sw" | all allSize prefix | all _ self allInstances. (allSize _ all size) == 0 ifTrue: [^ self notify: 'There are no instances of ', self name]. prefix _ allSize == 1 ifTrue: ['The lone instance'] ifFalse: ['The ', allSize printString, ' instances']. all asArray inspectWithLabel: (prefix, ' of ', self name)! ! !Behavior methodsFor: 'accessing instances and variables'! inspectSubInstances "Inspect all instances of the receiver and all its subclasses. CAUTION - don't do this for something as generic as Object!! 1/26/96 sw" | all allSize prefix | all _ self allSubInstances. (allSize _ all size) == 0 ifTrue: [^ self notify: 'There are no instances of ', self name, ' or any of its subclasses']. prefix _ allSize == 1 ifTrue: ['The lone instance'] ifFalse: ['The ', allSize printString, ' instances']. all asArray inspectWithLabel: (prefix, ' of ', self name, ' & its subclasses')! ! !Behavior methodsFor: 'accessing instances and variables'! instVarNames "Answer an Array of the instance variable names. Behaviors must make up fake local instance variable names because Behaviors have instance variables for the purpose of compiling methods, but these are not named instance variables." | mySize superSize | mySize _ self instSize. superSize _ superclass == nil ifTrue: [0] ifFalse: [superclass instSize]. mySize = superSize ifTrue: [^#()]. ^(superSize + 1 to: mySize) collect: [:i | 'inst' , i printString]! ! !Behavior methodsFor: 'accessing instances and variables'! instanceCount "Answer the number of instances of the receiver that are currently in use." | count | count _ 0. self allInstancesDo: [:x | count _ count + 1]. ^count! ! !Behavior methodsFor: 'accessing instances and variables'! sharedPools "Answer a Set of the names of the pools (Dictionaries) that the receiver shares. 9/12/96 tk sharedPools have an order now" ^ OrderedCollection new! ! !Behavior methodsFor: 'accessing instances and variables'! someInstance "Primitive. Answer the first instance in the enumeration of all instances of the receiver. Fails if there are none. Essential. See Object documentation whatIsAPrimitive." ^nil! ! !Behavior methodsFor: 'accessing instances and variables'! subclassInstVarNames "Answer a Set of the names of the receiver's subclasses' instance variables." | vars | vars _ Set new. self allSubclasses do: [:aSubclass | vars addAll: aSubclass instVarNames]. ^vars! ! !Behavior methodsFor: 'testing class hierarchy' stamp: 'ar 3/12/98 12:36'! includesBehavior: aClass ^self == aClass or:[self inheritsFrom: aClass]! ! !Behavior methodsFor: 'testing class hierarchy'! inheritsFrom: aClass "Answer whether the argument, aClass, is on the receiver's superclass chain." | aSuperclass | aSuperclass _ superclass. [aSuperclass == nil] whileFalse: [aSuperclass == aClass ifTrue: [^true]. aSuperclass _ aSuperclass superclass]. ^false! ! !Behavior methodsFor: 'testing class hierarchy' stamp: 'sma 11/11/2000 14:09'! kindOfSubclass "Answer a String that is the keyword that describes the receiver's kind of subclass, either a regular subclass, a variableSubclass, a variableByteSubclass, a variableWordSubclass, or a weakSubclass." self isWeak ifTrue: [^' weakSubclass: ']. self isVariable ifTrue: [self isBits ifTrue: [self isBytes ifTrue: [^' variableByteSubclass: '] ifFalse: [^' variableWordSubclass: ']] ifFalse: [^' variableSubclass: ']] ifFalse: [^' subclass: ']! ! !Behavior methodsFor: 'testing method dictionary'! allUnsentMessages "Answer an array of all the messages defined by the receiver that are not sent anywhere in the system. 5/8/96 sw" ^ Smalltalk allUnSentMessagesIn: self selectors! ! !Behavior methodsFor: 'testing method dictionary'! canUnderstand: selector "Answer whether the receiver can respond to the message whose selector is the argument. The selector can be in the method dictionary of the receiver's class or any of its superclasses." (self includesSelector: selector) ifTrue: [^true]. superclass == nil ifTrue: [^false]. ^superclass canUnderstand: selector! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'tk 9/13/97 09:53'! classThatUnderstands: selector "Answer the class that can respond to the message whose selector is the argument. The selector can be in the method dictionary of the receiver's class or any of its superclasses." (self includesSelector: selector) ifTrue: [^ self]. superclass == nil ifTrue: [^ nil]. ^ superclass classThatUnderstands: selector! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'di 2/17/2000 22:40'! hasMethods "Answer whether the receiver has any methods in its method dictionary." ^ self methodDict size > 0! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'di 3/27/1999 23:20'! includesSelector: aSymbol "Answer whether the message whose selector is the argument is in the method dictionary of the receiver's class." ^ self methodDict includesKey: aSymbol! ! !Behavior methodsFor: 'testing method dictionary'! scopeHas: name ifTrue: assocBlock "If the argument name is a variable known to the receiver, then evaluate the second argument, assocBlock." ^superclass scopeHas: name ifTrue: assocBlock! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'sma 6/3/2000 22:03'! thoroughWhichSelectorsReferTo: literal special: specialFlag byte: specialByte "Answer a set of selectors whose methods access the argument as a literal. Dives into the compact literal notation, making it slow but thorough " | who | who _ Set new. self selectorsAndMethodsDo: [:sel :method | ((method hasLiteralThorough: literal) or: [specialFlag and: [method scanFor: specialByte]]) ifTrue: [((literal isMemberOf: Association) not or: [method sendsToSuper not or: [method literals allButLast includes: literal]]) ifTrue: [who add: sel]]]. ^ who! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'di 2/17/2000 22:39'! whichClassIncludesSelector: aSymbol "Answer the class on the receiver's superclass chain where the argument, aSymbol (a message selector), will be found. Answer nil if none found." (self methodDict includesKey: aSymbol) ifTrue: [^ self]. superclass == nil ifTrue: [^ nil]. ^ superclass whichClassIncludesSelector: aSymbol "Rectangle whichClassIncludesSelector: #inspect."! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'di 2/17/2000 22:40'! whichSelectorsAccess: instVarName "Answer a Set of selectors whose methods access the argument, instVarName, as a named instance variable." | instVarIndex | instVarIndex _ self allInstVarNames indexOf: instVarName ifAbsent: [^Set new]. ^ self methodDict keys select: [:sel | ((self methodDict at: sel) readsField: instVarIndex) or: [(self methodDict at: sel) writesField: instVarIndex]] "Point whichSelectorsAccess: 'x'."! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'ls 10/10/1999 13:22'! whichSelectorsReferTo: literal "Answer a Set of selectors whose methods access the argument as a literal." | special byte | special _ Smalltalk hasSpecialSelector: literal ifTrueSetByte: [:b | byte _ b]. ^self whichSelectorsReferTo: literal special: special byte: byte "Rectangle whichSelectorsReferTo: #+."! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'sma 6/3/2000 22:01'! whichSelectorsReferTo: literal special: specialFlag byte: specialByte "Answer a set of selectors whose methods access the argument as a literal." | who | who _ Set new. self selectorsAndMethodsDo: [:sel :method | ((method hasLiteral: literal) or: [specialFlag and: [method scanFor: specialByte]]) ifTrue: [((literal isMemberOf: Association) not or: [method sendsToSuper not or: [method literals allButLast includes: literal]]) ifTrue: [who add: sel]]]. ^ who! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'di 2/17/2000 22:39'! whichSelectorsStoreInto: instVarName "Answer a Set of selectors whose methods access the argument, instVarName, as a named instance variable." | instVarIndex | instVarIndex _ self allInstVarNames indexOf: instVarName ifAbsent: [^Set new]. ^ self methodDict keys select: [:sel | (self methodDict at: sel) writesField: instVarIndex] "Point whichSelectorsStoreInto: 'x'."! ! !Behavior methodsFor: 'enumerating'! allInstancesDo: aBlock "Evaluate the argument, aBlock, for each of the current instances of the receiver." | inst next | self == UndefinedObject ifTrue: [^ aBlock value: nil]. inst _ self someInstance. [inst == nil] whileFalse: [aBlock value: inst. inst _ inst nextInstance]! ! !Behavior methodsFor: 'enumerating' stamp: 'tk 11/12/1999 11:36'! allInstancesEverywhereDo: aBlock "Evaluate the argument, aBlock, for each of the current instances of the receiver. Including those in ImageSegments that are out on the disk. Bring each in briefly." self == UndefinedObject ifTrue: [^ aBlock value: nil]. self allInstancesDo: aBlock. "Now iterate over instances in segments that are out on the disk." ImageSegment allSubInstancesDo: [:seg | seg allInstancesOf: self do: aBlock]. ! ! !Behavior methodsFor: 'enumerating' stamp: 'di 6/20/97 10:50'! allSubInstancesDo: aBlock "Evaluate the argument, aBlock, for each of the current instances of the receiver and all its subclasses." self allInstancesDo: aBlock. self allSubclassesDo: [:sub | sub allInstancesDo: aBlock]! ! !Behavior methodsFor: 'enumerating'! allSubclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's subclasses." self subclassesDo: [:cl | aBlock value: cl. cl allSubclassesDo: aBlock]! ! !Behavior methodsFor: 'enumerating' stamp: 'tk 8/18/1999 17:38'! allSubclassesDoGently: aBlock "Evaluate the argument, aBlock, for each of the receiver's subclasses." self subclassesDoGently: [:cl | cl isInMemory ifTrue: [ aBlock value: cl. cl allSubclassesDoGently: aBlock]]! ! !Behavior methodsFor: 'enumerating'! allSuperclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's superclasses." superclass == nil ifFalse: [aBlock value: superclass. superclass allSuperclassesDo: aBlock]! ! !Behavior methodsFor: 'enumerating'! selectSubclasses: aBlock "Evaluate the argument, aBlock, with each of the receiver's (next level) subclasses as its argument. Collect into a Set only those subclasses for which aBlock evaluates to true. In addition, evaluate aBlock for the subclasses of each of these successful subclasses and collect into the set those for which aBlock evaluates true. Answer the resulting set." | aSet | aSet _ Set new. self allSubclasses do: [:aSubclass | (aBlock value: aSubclass) ifTrue: [aSet add: aSubclass]]. ^aSet! ! !Behavior methodsFor: 'enumerating'! selectSuperclasses: aBlock "Evaluate the argument, aBlock, with the receiver's superclasses as the argument. Collect into an OrderedCollection only those superclasses for which aBlock evaluates to true. In addition, evaluate aBlock for the superclasses of each of these successful superclasses and collect into the OrderedCollection ones for which aBlock evaluates to true. Answer the resulting OrderedCollection." | aSet | aSet _ Set new. self allSuperclasses do: [:aSuperclass | (aBlock value: aSuperclass) ifTrue: [aSet add: aSuperclass]]. ^aSet! ! !Behavior methodsFor: 'enumerating'! withAllSubclassesDo: aBlock "Evaluate the argument, aBlock, for the receiver and each of its subclasses." aBlock value: self. self allSubclassesDo: aBlock! ! !Behavior methodsFor: 'enumerating' stamp: 'ar 7/11/1999 04:21'! withAllSuperclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's superclasses." aBlock value: self. superclass == nil ifFalse: [superclass withAllSuperclassesDo: aBlock]! ! !Behavior methodsFor: 'user interface' stamp: 'ls 10/10/1999 13:22'! allCallsOn: aSymbol "Answer a SortedCollection of all the methods that call on aSymbol." | aSortedCollection special byte | aSortedCollection _ SortedCollection new. special _ Smalltalk hasSpecialSelector: aSymbol ifTrueSetByte: [:b | byte _ b ]. self withAllSubclassesDo: [:class | (class whichSelectorsReferTo: aSymbol special: special byte: byte) do: [:sel | sel ~~ #DoIt ifTrue: [aSortedCollection add: class name , ' ' , sel]]]. ^aSortedCollection! ! !Behavior methodsFor: 'user interface' stamp: 'sw 4/4/2000 11:22'! allUnreferencedInstanceVariables "Return a list of the instance variables known to the receiver which are not referenced in the receiver or any of its subclasses OR superclasses" | any definingClass | ^ self allInstVarNames copy reject: [:ivn | any _ false. definingClass _ self classThatDefinesInstanceVariable: ivn. definingClass withAllSubclasses do: [:class | any ifFalse: [(class whichSelectorsAccess: ivn asSymbol) do: [:sel | sel ~~ #DoIt ifTrue: [any _ true]]]]. any]! ! !Behavior methodsFor: 'user interface' stamp: 'sw 2/23/98 00:48'! browse Browser newOnClass: self! ! !Behavior methodsFor: 'user interface'! browseAllAccessesTo: instVarName "Collection browseAllAccessesTo: 'contents'." "Create and schedule a Message Set browser for all the receiver's methods or any methods of a subclass that refer to the instance variable name." | coll | coll _ OrderedCollection new. Cursor wait showWhile: [self withAllSubclasses do: [:class | (class whichSelectorsAccess: instVarName) do: [:sel | sel ~~ #DoIt ifTrue: [coll add: class name , ' ' , sel]]]. self allSuperclasses do: [:class | (class whichSelectorsAccess: instVarName) do: [:sel | sel ~~ #DoIt ifTrue: [coll add: class name , ' ' , sel]]]]. ^ Smalltalk browseMessageList: coll name: 'Accesses to ' , instVarName autoSelect: instVarName! ! !Behavior methodsFor: 'user interface'! browseAllCallsOn: aSymbol "Create and schedule a Message Set browser for all the methods that call on aSymbol." | key label | (aSymbol isKindOf: LookupKey) ifTrue: [label _ 'Users of ' , (key _ aSymbol key)] ifFalse: [label _ 'Senders of ' , (key _ aSymbol)]. ^ Smalltalk browseMessageList: (self allCallsOn: aSymbol) asSortedCollection name: label autoSelect: key "Number browseAllCallsOn: #/."! ! !Behavior methodsFor: 'user interface'! browseAllStoresInto: instVarName "Collection browseAllStoresInto: 'contents'." "Create and schedule a Message Set browser for all the receiver's methods or any methods of a subclass that refer to the instance variable name." | coll | coll _ OrderedCollection new. Cursor wait showWhile: [self withAllSubclasses do: [:class | (class whichSelectorsStoreInto: instVarName) do: [:sel | sel ~~ #DoIt ifTrue: [coll add: class name , ' ' , sel]]]. self allSuperclasses do: [:class | (class whichSelectorsStoreInto: instVarName) do: [:sel | sel ~~ #DoIt ifTrue: [coll add: class name , ' ' , sel]]]]. ^ Smalltalk browseMessageList: coll name: 'Stores into ' , instVarName autoSelect: instVarName! ! !Behavior methodsFor: 'user interface'! crossReference "Answer an Array of arrays of size 2 whose first element is a message selector in the receiver's method dictionary and whose second element is a set of all message selectors in the method dictionary whose methods send a message with that selector. Subclasses are not included." ^self selectors asSortedCollection asArray collect: [:x | Array with: (String with: Character cr), x with: (self whichSelectorsReferTo: x)] "Point crossReference."! ! !Behavior methodsFor: 'user interface'! unreferencedInstanceVariables "Return a list of the instance variables defined in the receiver which are not referenced in the receiver or any of its subclasses. 2/26/96 sw" | any | ^ self instVarNames copy reject: [:ivn | any _ false. self withAllSubclasses do: [:class | (class whichSelectorsAccess: ivn) do: [:sel | sel ~~ #DoIt ifTrue: [any _ true]]]. any] "Ob unreferencedInstanceVariables"! ! !Behavior methodsFor: 'private' stamp: 'tk 12/29/1999 22:04'! becomeCompact "Here are the restrictions on compact classes in order for export segments to work: A compact class index may not be reused. If a class was compact in a release of Squeak, no other class may use that index. The class might not be compact later, and there should be nil in its place in the array." | cct index | self isWeak ifTrue:[^ self halt: 'You must not make a weak class compact']. cct _ Smalltalk compactClassesArray. (self indexIfCompact > 0 or: [cct includes: self]) ifTrue: [^ self halt: self name , 'is already compact']. index _ cct indexOf: nil ifAbsent: [^ self halt: 'compact class table is full']. "Install this class in the compact class table" cct at: index put: self. "Update instspec so future instances will be compact" format _ format + (index bitShift: 11). "Make up new instances and become old ones into them" self updateInstancesFrom: self. "Purge any old instances" Smalltalk garbageCollect.! ! !Behavior methodsFor: 'private' stamp: 'tk 1/10/2000 14:50'! becomeCompactSimplyAt: index "Make me compact, but don't update the instances. For importing segments." "Here are the restrictions on compact classes in order for export segments to work: A compact class index may not be reused. If a class was compact in a release of Squeak, no other class may use that index. The class might not be compact later, and there should be nil in its place in the array." | cct | self isWeak ifTrue:[^ self halt: 'You must not make a weak class compact']. cct _ Smalltalk compactClassesArray. (self indexIfCompact > 0 or: [cct includes: self]) ifTrue: [^ self halt: self name , 'is already compact']. (cct at: index) ifNotNil: [^ self halt: 'compact table slot already in use']. "Install this class in the compact class table" cct at: index put: self. "Update instspec so future instances will be compact" format _ format + (index bitShift: 11). "Caller must convert the instances" ! ! !Behavior methodsFor: 'private'! becomeUncompact | cct index | cct _ Smalltalk compactClassesArray. (index _ self indexIfCompact) = 0 ifTrue: [^ self]. (cct includes: self) ifFalse: [^ self halt "inconsistent state"]. "Update instspec so future instances will not be compact" format _ format - (index bitShift: 11). "Make up new instances and become old ones into them" self updateInstancesFrom: self. "Make sure there are no compact ones left around" Smalltalk garbageCollect. "Remove this class from the compact class table" cct at: index put: nil. ! ! !Behavior methodsFor: 'private'! flushCache "Tell the interpreter to remove the contents of its method lookup cache, if it has one. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !Behavior methodsFor: 'private'! indexIfCompact "If these 5 bits are non-zero, then instances of this class will be compact. It is crucial that there be an entry in Smalltalk compactClassesArray for any class so optimized. See the msgs becomeCompact and becomeUncompact." ^ (format bitShift: -11) bitAnd: 16r1F " Smalltalk compactClassesArray doWithIndex: [:c :i | c == nil ifFalse: [c indexIfCompact = i ifFalse: [self halt]]] "! ! !Behavior methodsFor: 'private' stamp: 'di 2/17/2000 22:38'! removeSelectorSimply: selector "Assuming that the argument, selector (a Symbol), is a message selector in my method dictionary, remove it and its method." | oldMethod | oldMethod _ self methodDict at: selector ifAbsent: [^ self]. self methodDict removeKey: selector. "Now flush Squeak's method cache, either by selector or by method" oldMethod flushCache. selector flushCache.! ! !Behavior methodsFor: 'system startup' stamp: 'ar 11/16/1999 20:15'! shutDown "This message is sent on system shutdown to registered classes" ! ! !Behavior methodsFor: 'system startup' stamp: 'ar 11/16/1999 20:15'! shutDown: quitting "This message is sent on system shutdown to registered classes" ^self shutDown.! ! !Behavior methodsFor: 'system startup' stamp: 'ar 11/16/1999 20:15'! startUp "This message is sent to registered classes when the system is coming up." ! ! !Behavior methodsFor: 'system startup' stamp: 'ar 11/16/1999 20:15'! startUp: resuming "This message is sent to registered classes when the system is coming up." ^self startUp! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Behavior class instanceVariableNames: ''! !Behavior class methodsFor: 'testing' stamp: 'ar 9/10/1999 17:28'! canZapMethodDictionary "Return false since zapping the method dictionary of Behavior class or its subclasses will cause the system to fail." ^false! ! LineSegment subclass: #Bezier2Segment instanceVariableNames: 'via ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Geometry'! !Bezier2Segment commentStamp: '' prior: 0! This class represents a quadratic bezier segment between two points Instance variables: via The additional control point (OFF the curve)! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 11/2/1998 12:13'! from: startPoint to: endPoint "Initialize the receiver as straight line" start _ startPoint. end _ endPoint. via _ (start + end) // 2.! ! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 11/2/1998 12:13'! from: startPoint to: endPoint via: viaPoint "Initialize the receiver" start _ startPoint. end _ endPoint. via _ viaPoint.! ! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 11/2/1998 12:14'! from: startPoint to: endPoint withMidPoint: pointOnCurve "Initialize the receiver with the pointOnCurve assumed at the parametric value 0.5" start _ startPoint. end _ endPoint. "Compute via" via _ (pointOnCurve * 2) - ((start+end) // 2).! ! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 11/2/1998 12:14'! from: startPoint to: endPoint withMidPoint: pointOnCurve at: parameter "Initialize the receiver with the pointOnCurve at the given parametric value" | t1 t2 t3 | start _ startPoint. end _ endPoint. "Compute via" t1 _ (1.0 - parameter) squared. t2 _ 2 * parameter * (1.0 - parameter). t3 _ parameter squared. via _ (pointOnCurve * t2) - (start * t1) - (end * t3)! ! !Bezier2Segment methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:14'! bounds "Return the bounds containing the receiver" ^super bounds encompass: via! ! !Bezier2Segment methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:14'! via "Return the control point" ^via! ! !Bezier2Segment methodsFor: 'testing' stamp: 'ar 11/2/1998 12:15'! hasZeroLength "Return true if the receiver has zero length" ^start = end and:[start = via]! ! !Bezier2Segment methodsFor: 'testing' stamp: 'ar 11/2/1998 12:15'! isBezier2Segment "Return true if the receiver is a quadratic bezier segment" ^true! ! !Bezier2Segment methodsFor: 'testing' stamp: 'ar 11/2/1998 12:15'! isStraight "Return true if the receiver represents a straight line" ^(self tangentAtStart crossProduct: self tangentAtEnd) = 0! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/2/1998 12:15'! length "Return the length of the receiver" "Note: Overestimates the length" ^(start dist: via) + (via dist: end)! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/6/1998 23:39'! lineSegmentsDo: aBlock "Evaluate aBlock with the receiver's line segments" "Note: We could use forward differencing here." | steps last deltaStep t next | steps _ 1 max: (self length // 10). "Assume 10 pixels per step" last _ start. deltaStep _ 1.0 / steps asFloat. t _ deltaStep. 1 to: steps do:[:i| next _ self valueAt: t. aBlock value: last value: next. last _ next. t _ t + deltaStep].! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/2/1998 12:16'! tangentAt: parameter "Return the tangent at the given parametric value along the receiver" | in out | in _ self tangentAtStart. out _ self tangentAtEnd. ^in + (out - in * parameter)! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/2/1998 12:16'! tangentAtEnd "Return the tangent for the last point" ^end - via! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/2/1998 12:16'! tangentAtStart "Return the tangent for the first point" ^via - start! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 11/2/1998 12:17'! valueAt: parameter "Evaluate the receiver at the given parametric value" "Return the point at the parametric value t: p(t) = (1-t)^2 * p1 + 2*t*(1-t) * p2 + t^2 * p3. " | t1 t2 t3 | t1 _ (1.0 - parameter) squared. t2 _ 2 * parameter * (1.0 - parameter). t3 _ parameter squared. ^(start * t1) + (via * t2) + (end * t3)! ! !Bezier2Segment methodsFor: 'converting' stamp: 'ar 11/2/1998 12:17'! asBezier2Segment "Represent the receiver as quadratic bezier segment" ^self! ! !Bezier2Segment methodsFor: 'converting' stamp: 'ar 11/2/1998 12:18'! asIntegerSegment "Convert the receiver into integer representation" ^self species from: start asIntegerPoint to: end asIntegerPoint via: via asIntegerPoint! ! !Bezier2Segment methodsFor: 'printing' stamp: 'ar 11/2/1998 12:18'! printOn: aStream "Print the receiver on aStream" aStream nextPutAll: self class name; nextPutAll:' from: '; print: start; nextPutAll: ' via: '; print: via; nextPutAll: ' to: '; print: end; space.! ! !Bezier2Segment methodsFor: 'printing' stamp: 'MPW 1/1/1901 21:59'! printOnStream: aStream aStream print: self class name; print:'from: '; write: start; print:'via: '; write: via; print:'to: '; write: end; print:' '.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Bezier2Segment class instanceVariableNames: ''! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:14'! from: startPoint to: endPoint via: viaPoint ^self new from: startPoint to: endPoint via: viaPoint! ! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:30'! from: startPoint to: endPoint withMidPoint: pointOnCurve ^self new from: startPoint to: endPoint withMidPoint: pointOnCurve! ! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:32'! from: startPoint to: endPoint withMidPoint: pointOnCurve at: parameter ^self new from: startPoint to: endPoint withMidPoint: pointOnCurve at: parameter! ! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:30'! from: startPoint via: viaPoint to: endPoint ^self new from: startPoint to: endPoint via: viaPoint! ! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:32'! from: startPoint withMidPoint: pointOnCurve at: parameter to: endPoint ^self new from: startPoint to: endPoint withMidPoint: pointOnCurve at: parameter! ! !Bezier2Segment class methodsFor: 'instance creation' stamp: 'ar 11/1/1998 21:30'! from: startPoint withMidPoint: pointOnCurve to: endPoint ^self new from: startPoint to: endPoint withMidPoint: pointOnCurve! ! LineSegment subclass: #Bezier3Segment instanceVariableNames: 'via1 via2 ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Geometry'! !Bezier3Segment commentStamp: '' prior: 0! This class represents a cubic bezier segment between two points Instance variables: via1, via2 The additional control points (OFF the curve)! !Bezier3Segment methodsFor: 'initialization' stamp: 'DSM 10/14/1999 15:33'! from: aPoint1 via: aPoint2 and: aPoint3 to: aPoint4 start _ aPoint1. via1 _ aPoint2. via2 _ aPoint3. end _ aPoint4! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/15/1999 15:20'! bounds ^ ((super bounds encompassing: via1) encompassing: via2)! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/15/1999 15:01'! valueAt: t | a b c d | "| p1 p2 p3 | p1 _ start interpolateTo: via1 at: t. p2 _ via1 interpolateTo: via2 at: t. p3 _ via2 interpolateTo: end at: t. p1 _ p1 interpolateTo: p2 at: t. p2 _ p2 interpolateTo: p3 at: t. ^ p1 interpolateTo: p2 at: t" a _ (start negated) + (3 * via1) - (3 * via2) + (end). b _ (3 * start) - (6 * via1) + (3 * via2). c _ (3 * start negated) + (3 * via1). d _ start. ^ ((a * t + b) * t + c) * t + d ! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/14/1999 15:31'! via1: aPoint via1 _ aPoint! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/14/1999 15:31'! via2: aPoint via2 _ aPoint! ! !Bezier3Segment methodsFor: 'converting' stamp: 'DSM 10/15/1999 15:55'! asBezierShape "Demote a cubic bezier to a set of approximating quadratic beziers. Should convert to forward differencing someday" | curves pts step prev index a b f | curves _ self bezier2SegmentCount: 0.5. pts _ PointArray new: curves * 3. step _ 1.0 / (curves * 2). prev _ start. 1 to: curves do: [ :c | index _ 3*c. a _ pts at: index-2 put: prev. b _ (self valueAt: (c*2-1)*step). f _ pts at: index put: (self valueAt: (c*2)*step). pts at: index-1 put: (4 * b - a - f) / 2. prev _ pts at: index. ]. ^ pts. ! ! !Bezier3Segment methodsFor: 'converting' stamp: 'DSM 10/15/1999 15:45'! asPointArray | p | p _ PointArray new: 4. p at: 1 put: start. p at: 2 put: via1. p at: 3 put: via2. p at: 4 put: end. ^ p! ! !Bezier3Segment methodsFor: 'converting' stamp: 'DSM 3/10/2000 12:10'! bezier2SegmentCount: pixelError "Compute the number of quadratic bezier segments needed to approximate this cubic with no more than a specified error" | a | a _ (start x negated @ start y negated) + (3 * via1) - (3 * via2) + (end). ^ (((a r / (20.0 * pixelError)) raisedTo: 0.333333) ceiling) max: 1. ! ! !Bezier3Segment methodsFor: 'private' stamp: 'DSM 10/14/1999 16:25'! bezier2SegmentCount "Compute the number of quadratic bezier segments needed to approximate this cubic with less than a 1-pixel error" ^ self bezier2SegmentCount: 1.0! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Bezier3Segment class instanceVariableNames: ''! !Bezier3Segment class methodsFor: 'instance creation' stamp: 'DSM 10/15/1999 15:23'! from: p1 to: p2 ^ self new from: p1 via: (p1 interpolateTo: p2 at: 0.3333) and: (p1 interpolateTo: p2 at: 0.66667) to: p2! ! !Bezier3Segment class methodsFor: 'instance creation' stamp: 'DSM 10/15/1999 15:24'! from: p1 via: p2 and: p3 to: p4 ^ self new from: p1 via: p2 and: p3 to: p4! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'DSM 10/15/1999 16:06'! convertBezier3ToBezier2: vertices | pa pts index c | pts _ OrderedCollection new. 1 to: vertices size // 4 do: [:i | index _ i * 4 - 3. c _ Bezier3Segment new from: (vertices at: index) via: (vertices at: index + 1) and: (vertices at: index + 2) to: (vertices at: index + 3). pts addAll: c asBezierShape]. pa _ PointArray new: pts size. pts withIndexDo: [:p :i | pa at: i put: p ]. ^ pa! ! !Bezier3Segment class methodsFor: 'examples' stamp: 'DSM 10/15/1999 15:49'! example1 | c | c _ Bezier3Segment new from: 0@0 via: 0@100 and: 100@0 to: 100@100. ^ c asBezierShape! ! !Bezier3Segment class methodsFor: 'examples' stamp: 'DSM 10/15/1999 16:00'! example2 "draws a cubic bezier on the screen" | c canvas | c _ Bezier3Segment new from: 0 @ 0 via: 0 @ 100 and: 100 @ 0 to: 100 @ 100. canvas _ BalloonCanvas on: Display. canvas aaLevel: 4. canvas drawBezier3Shape: c asPointArray color: Color transparent borderWidth: 1 borderColor: Color black! ! Object subclass: #BitBlt instanceVariableNames: 'destForm sourceForm halftoneForm combinationRule destX destY width height sourceX sourceY clipX clipY clipWidth clipHeight colorMap ' classVariableNames: 'CachedFontColorMaps ' poolDictionaries: '' category: 'Graphics-Primitives'! !BitBlt commentStamp: '' prior: 0! I represent a block transfer (BLT) of pixels into a rectangle (destX, destY, width, height) of the destinationForm. The source of pixels may be a similar rectangle (at sourceX, sourceY) in the sourceForm, or a constant color, currently called halftoneForm. If both are specified, their pixel values are combined with a logical AND function prior to transfer. In any case, the pixels from the source are combined with those of the destination by as specified by the combinationRule. The combination rule whose value is 0 through 15 programs the transfer to produce 1 or 0 according to its 4-bit representation as follows: 8: if source is 0 and destination is 0 4: if source is 0 and destination is 1 2: if source is 1 and destination is 0 1: if source is 1 and destination is 1. At each pixel the corresponding bits of the source and destination pixel values determine one of these conditions; if the combination rule has a 1 in the corresponding bit position, then the new destination value will be 1, otherwise it will be zero. Forms may be of different depths, see the comment in class Form. In addition to the original 16 combination rules, this BitBlt supports 16 fails (to simulate paint bits) 17 fails (to simulate erase bits) 18 sourceWord + destinationWord 19 sourceWord - destinationWord 20 rgbAdd: sourceWord with: destinationWord. Sum of color components 21 rgbSub: sourceWord with: destinationWord. Difference of color components 22 OLDrgbDiff: sourceWord with: destinationWord. Sum of abs of differences in components 23 OLDtallyIntoMap: destinationWord. Tallies pixValues into a colorMap these old versions don't do bitwise dest clipping. Use 32 and 33 now. 24 alphaBlend: sourceWord with: destinationWord. 32-bit source and dest only 25 pixPaint: sourceWord with: destinationWord. Wherever the sourceForm is non-zero, it replaces the destination. Can be used with a 1-bit source color mapped to (0, FFFFFFFF), and a fillColor to fill the dest with that color wherever the source is 1. 26 pixMask: sourceWord with: destinationWord. Like pixPaint, but fills with 0. 27 rgbMax: sourceWord with: destinationWord. Max of each color component. 28 rgbMin: sourceWord with: destinationWord. Min of each color component. 29 rgbMin: sourceWord bitInvert32 with: destinationWord. Min with (max-source) 30 alphaBlendConst: sourceWord with: destinationWord. alpha is an arg. works in 16 bits. 31 alphaPaintConst: sourceWord with: destinationWord. alpha is an arg. works in 16 bits. 32 rgbDiff: sourceWord with: destinationWord. Sum of abs of differences in components 33 tallyIntoMap: destinationWord. Tallies pixValues into a colorMap 34 alphaBlendScaled: srcWord with: dstWord. Alpha blend of scaled srcWord and destWord. The color specified by halftoneForm may be either a Color or a Pattern. A Color is converted to a pixelValue for the depth of the destinationForm. If a Pattern, BitBlt will simply interpret its bitmap as an array of Color pixelValues. BitBlt aligns the first element of this array with the top scanline of the destinationForm, the second with the second, and so on, cycling through the color array as necessary. Within each scan line the 32-bit value is repeated from left to right across the form. If the value repeats on pixels boudaries, the effect will be a constant color; if not, it will produce a halftone that repeats on 32-bit boundaries. Any transfer specified is further clipped by the specified rectangle (clipX, clipY, clipWidth, clipHeight), and also by the bounds of the source and destination forms. To make a small Form repeat and fill a big form, use an InfiniteForm as the source. To write on a form and leave with both transparent and opapue areas, use a MaskedForm as the source. Pixels from a source to a destination whose pixels have a different depth are converted based on the optional colorMap. If colorMap is nil, then conversion to more bits is done by filling the new high-order bits with zero, and conversion to fewer bits is done by truncating the lost high-order bits. The colorMap, if specified, must be a word array (ie Bitmap) with 2^n elements, where n is the pixel depth of the source. For every source pixel, BitBlt will then index this array, and select the corresponding pixelValue and mask it to the destination pixel size before storing. When blitting from a 32 or 16 bit deep Form to one 8 bits or less, the default is truncation. This will produce very strange colors, since truncation of the high bits does not produce the nearest encoded color. Supply a 512 long colorMap, and red, green, and blue will be shifted down to 3 bits each, and mapped. The message copybits...stdColors will use the best map to the standard colors for destinations of depths 8, 4, 2 and 1. Two other sized of colorMaps are allowed, 4096 (4 bits per color) and 32786 (five bits per color). Normal blits between 16 and 32 bit forms truncates or pads the colors automatically to provide the best preservation of colors. Colors can be remapped at the same depth. Sometimes a Form is in terms of colors that are not the standard colors for this depth, for example in a GIF file. Convert the Form to a MaskedForm and send colorMap: the list of colors that the picture is in terms of. MaskedForm will use the colorMap when copying to the display or another Form. (Note also that a Form can be copied to itself, and transformed in the process, if a non-nil colorMap is supplied.)! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/17/2000 18:58'! clipHeight ^clipHeight! ! !BitBlt methodsFor: 'accessing'! clipHeight: anInteger "Set the receiver's clipping area height to be the argument, anInteger." clipHeight _ anInteger! ! !BitBlt methodsFor: 'accessing'! clipRect "Answer the receiver's clipping area rectangle." ^clipX @ clipY extent: clipWidth @ clipHeight! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 10/4/2000 16:37'! clipRect: aRectangle "Set the receiver's clipping area rectangle to be the argument, aRectangle." clipX _ aRectangle left truncated. clipY _ aRectangle top truncated. clipWidth _ aRectangle right truncated - clipX. clipHeight _ aRectangle bottom truncated - clipY.! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/17/2000 18:58'! clipWidth ^clipWidth! ! !BitBlt methodsFor: 'accessing'! clipWidth: anInteger "Set the receiver's clipping area width to be the argument, anInteger." clipWidth _ anInteger! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/17/2000 18:58'! clipX ^clipX! ! !BitBlt methodsFor: 'accessing'! clipX: anInteger "Set the receiver's clipping area top left x coordinate to be the argument, anInteger." clipX _ anInteger! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/17/2000 18:58'! clipY ^clipY! ! !BitBlt methodsFor: 'accessing'! clipY: anInteger "Set the receiver's clipping area top left y coordinate to be the argument, anInteger." clipY _ anInteger! ! !BitBlt methodsFor: 'accessing'! colorMap ^ colorMap! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/25/2000 19:43'! colorMap: map "See last part of BitBlt comment. 6/18/96 tk" (map notNil and:[map isColormap]) ifTrue:[colorMap _ map colors] ifFalse:[colorMap _ map]! ! !BitBlt methodsFor: 'accessing'! combinationRule: anInteger "Set the receiver's combination rule to be the argument, anInteger, a number in the range 0-15." combinationRule _ anInteger! ! !BitBlt methodsFor: 'accessing'! destForm ^ destForm! ! !BitBlt methodsFor: 'accessing'! destOrigin: aPoint "Set the receiver's destination top left coordinates to be those of the argument, aPoint." destX _ aPoint x. destY _ aPoint y! ! !BitBlt methodsFor: 'accessing' stamp: 'tk 3/19/97'! destRect "The rectangle we are about to blit to or just blitted to. " ^ destX @ destY extent: width @ height! ! !BitBlt methodsFor: 'accessing'! destRect: aRectangle "Set the receiver's destination form top left coordinates to be the origin of the argument, aRectangle, and set the width and height of the receiver's destination form to be the width and height of aRectangle." destX _ aRectangle left. destY _ aRectangle top. width _ aRectangle width. height _ aRectangle height! ! !BitBlt methodsFor: 'accessing'! destX: anInteger "Set the top left x coordinate of the receiver's destination form to be the argument, anInteger." destX _ anInteger! ! !BitBlt methodsFor: 'accessing'! destX: x destY: y width: w height: h "Combined init message saves 3 sends from DisplayScanner" destX _ x. destY _ y. width _ w. height _ h.! ! !BitBlt methodsFor: 'accessing'! destY: anInteger "Set the top left y coordinate of the receiver's destination form to be the argument, anInteger." destY _ anInteger! ! !BitBlt methodsFor: 'accessing'! fillColor ^ halftoneForm! ! !BitBlt methodsFor: 'accessing'! fillColor: aColorOrPattern "The destForm will be filled with this color or pattern of colors. May be an old Color, a new type Color, a Bitmap (see BitBlt comment), a Pattern, or a Form. 6/18/96 tk" aColorOrPattern == nil ifTrue: [halftoneForm _ nil. ^ self]. destForm == nil ifTrue: [self error: 'Must set destForm first']. halftoneForm _ aColorOrPattern bitPatternForDepth: destForm depth! ! !BitBlt methodsFor: 'accessing'! height: anInteger "Set the receiver's destination form height to be the argument, anInteger." height _ anInteger! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 2/21/2000 22:06'! isFXBlt ^false! ! !BitBlt methodsFor: 'accessing'! sourceForm ^ sourceForm! ! !BitBlt methodsFor: 'accessing'! sourceForm: aForm "Set the receiver's source form to be the argument, aForm." sourceForm _ aForm! ! !BitBlt methodsFor: 'accessing'! sourceOrigin: aPoint "Set the receiver's source form coordinates to be those of the argument, aPoint." sourceX _ aPoint x. sourceY _ aPoint y! ! !BitBlt methodsFor: 'accessing'! sourceRect: aRectangle "Set the receiver's source form top left x and y, width and height to be the top left coordinate and extent of the argument, aRectangle." sourceX _ aRectangle left. sourceY _ aRectangle top. width _ aRectangle width. height _ aRectangle height! ! !BitBlt methodsFor: 'accessing'! sourceX: anInteger "Set the receiver's source form top left x to be the argument, anInteger." sourceX _ anInteger! ! !BitBlt methodsFor: 'accessing'! sourceY: anInteger "Set the receiver's source form top left y to be the argument, anInteger." sourceY _ anInteger! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/25/2000 19:39'! tallyMap "Return the map used for tallying pixels" ^colorMap! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/25/2000 19:39'! tallyMap: aBitmap "Install the map used for tallying pixels" colorMap _ aBitmap! ! !BitBlt methodsFor: 'accessing'! width: anInteger "Set the receiver's destination form width to be the argument, anInteger." width _ anInteger! ! !BitBlt methodsFor: 'copying'! copy: destRectangle from: sourcePt in: srcForm | destOrigin | sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ 3. "store" destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. self copyBits! ! !BitBlt methodsFor: 'copying' stamp: 'di 12/26/1998 15:04'! copy: destRectangle from: sourcePt in: srcForm fillColor: hf rule: rule "Specify a Color to fill, not a Form. 6/18/96 tk" | destOrigin | sourceForm _ srcForm. self fillColor: hf. "sets halftoneForm" combinationRule _ rule. destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. srcForm == nil ifFalse: [colorMap _ srcForm colormapIfNeededForDepth: destForm depth]. ^ self copyBits! ! !BitBlt methodsFor: 'copying'! copy: destRectangle from: sourcePt in: srcForm halftoneForm: hf rule: rule | destOrigin | sourceForm _ srcForm. self fillColor: hf. "sets halftoneForm" combinationRule _ rule. destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. self copyBits! ! !BitBlt methodsFor: 'copying' stamp: 'ar 2/2/2001 15:09'! copyBits "Primitive. Perform the movement of bits from the source form to the destination form. Fail if any variables are not of the right type (Integer, Float, or Form) or if the combination rule is not implemented. In addition to the original 16 combination rules, this BitBlt supports 16 fail (to simulate paint) 17 fail (to simulate mask) 18 sourceWord + destinationWord 19 sourceWord - destinationWord 20 rgbAdd: sourceWord with: destinationWord 21 rgbSub: sourceWord with: destinationWord 22 rgbDiff: sourceWord with: destinationWord 23 tallyIntoMap: destinationWord 24 alphaBlend: sourceWord with: destinationWord 25 pixPaint: sourceWord with: destinationWord 26 pixMask: sourceWord with: destinationWord 27 rgbMax: sourceWord with: destinationWord 28 rgbMin: sourceWord with: destinationWord 29 rgbMin: sourceWord bitInvert32 with: destinationWord " "Check for compressed source, destination or halftone forms" (combinationRule >= 30 and: [combinationRule <= 31]) ifTrue: ["No alpha specified -- re-run with alpha = 1.0" ^ self copyBitsTranslucent: 255]. ((sourceForm isKindOf: Form) and: [sourceForm unhibernate]) ifTrue: [^ self copyBits]. ((destForm isKindOf: Form) and: [destForm unhibernate]) ifTrue: [^ self copyBits]. ((halftoneForm isKindOf: Form) and: [halftoneForm unhibernate]) ifTrue: [^ self copyBits]. "Check for unimplmented rules" combinationRule = Form oldPaint ifTrue: [^ self paintBits]. combinationRule = Form oldErase1bitShape ifTrue: [^ self eraseBits]. self error: 'Bad BitBlt arg (Fraction?); proceed to convert.'. "Convert all numeric parameters to integers and try again." destX _ destX asInteger. destY _ destY asInteger. width _ width asInteger. height _ height asInteger. sourceX _ sourceX asInteger. sourceY _ sourceY asInteger. clipX _ clipX asInteger. clipY _ clipY asInteger. clipWidth _ clipWidth asInteger. clipHeight _ clipHeight asInteger. ^ self copyBitsAgain! ! !BitBlt methodsFor: 'copying' stamp: 'ar 10/27/1999 23:36'! copyBitsSimulated ^BitBltSimulation copyBitsFrom: self.! ! !BitBlt methodsFor: 'copying' stamp: 'ar 2/2/2001 15:09'! copyBitsTranslucent: factor "This entry point to BitBlt supplies an extra argument to specify translucency for operations 30 and 31. The argument must be an integer between 0 and 255." "Check for compressed source, destination or halftone forms" ((sourceForm isKindOf: Form) and: [sourceForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. ((destForm isKindOf: Form) and: [destForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. ((halftoneForm isKindOf: Form) and: [halftoneForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. self primitiveFailed "Later do nicer error recovery -- share copyBits recovery"! ! !BitBlt methodsFor: 'copying' stamp: 'di 7/17/97 10:04'! copyForm: srcForm to: destPt rule: rule ^ self copyForm: srcForm to: destPt rule: rule colorMap: (srcForm colormapIfNeededForDepth: destForm depth)! ! !BitBlt methodsFor: 'copying'! copyForm: srcForm to: destPt rule: rule color: color sourceForm _ srcForm. halftoneForm _ color. combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. self copyBits! ! !BitBlt methodsFor: 'copying' stamp: 'di 7/17/97 10:04'! copyForm: srcForm to: destPt rule: rule colorMap: map sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. colorMap _ map. self copyBits! ! !BitBlt methodsFor: 'copying'! copyForm: srcForm to: destPt rule: rule fillColor: color sourceForm _ srcForm. self fillColor: color. "sets halftoneForm" combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. self copyBits! ! !BitBlt methodsFor: 'copying' stamp: 'di 7/1/97 14:09'! copyFrom: sourceRectangle in: srcForm to: destPt | sourceOrigin | sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ 3. "store" destX _ destPt x. destY _ destPt y. sourceOrigin _ sourceRectangle origin. sourceX _ sourceOrigin x. sourceY _ sourceOrigin y. width _ sourceRectangle width. height _ sourceRectangle height. colorMap _ srcForm colormapIfNeededForDepth: destForm depth. self copyBits! ! !BitBlt methodsFor: 'copying' stamp: 'RAA 9/27/2000 16:48'! displayString: aString from: startIndex to: stopIndex at: aPoint strikeFont: font kern: kernDelta destY _ aPoint y. destX _ aPoint x. "the following are not really needed, but theBitBlt primitive will fail if not set" sourceX ifNil: [sourceX _ 100]. width ifNil: [width _ 100]. ^self primDisplayString: aString from: startIndex to: stopIndex map: font characterToGlyphMap xTable: font xTable kern: kernDelta.! ! !BitBlt methodsFor: 'copying'! fill: destRect fillColor: grayForm rule: rule "Fill with a Color, not a Form. 6/18/96 tk" sourceForm _ nil. self fillColor: grayForm. "sets halftoneForm" combinationRule _ rule. destX _ destRect left. destY _ destRect top. sourceX _ 0. sourceY _ 0. width _ destRect width. height _ destRect height. self copyBits! ! !BitBlt methodsFor: 'copying'! pixelAt: aPoint "Assumes this BitBlt has been set up specially (see the init message, BitBlt bitPeekerFromForm:. Returns the pixel at aPoint." sourceX _ aPoint x. sourceY _ aPoint y. destForm bits at: 1 put: 0. "Just to be sure" self copyBits. ^ destForm bits at: 1! ! !BitBlt methodsFor: 'copying'! pixelAt: aPoint put: pixelValue "Assumes this BitBlt has been set up specially (see the init message, BitBlt bitPokerToForm:. Overwrites the pixel at aPoint." destX _ aPoint x. destY _ aPoint y. sourceForm bits at: 1 put: pixelValue. self copyBits " | bb | bb _ (BitBlt bitPokerToForm: Display). [Sensor anyButtonPressed] whileFalse: [bb pixelAt: Sensor cursorPoint put: 55] "! ! !BitBlt methodsFor: 'line drawing'! drawFrom: startPoint to: stopPoint ^ self drawFrom: startPoint to: stopPoint withFirstPoint: true! ! !BitBlt methodsFor: 'line drawing' stamp: '6/8/97 15:41 di'! drawFrom: startPoint to: stopPoint withFirstPoint: drawFirstPoint "Draw a line whose end points are startPoint and stopPoint. The line is formed by repeatedly calling copyBits at every point along the line. If drawFirstPoint is false, then omit the first point so as not to overstrike at line junctions." | offset point1 point2 forwards | "Always draw down, or at least left-to-right" forwards _ (startPoint y = stopPoint y and: [startPoint x < stopPoint x]) or: [startPoint y < stopPoint y]. forwards ifTrue: [point1 _ startPoint. point2 _ stopPoint] ifFalse: [point1 _ stopPoint. point2 _ startPoint]. sourceForm == nil ifTrue: [destX _ point1 x. destY _ point1 y] ifFalse: [width _ sourceForm width. height _ sourceForm height. offset _ sourceForm offset. destX _ (point1 x + offset x) rounded. destY _ (point1 y + offset y) rounded]. "Note that if not forwards, then the first point is the last and vice versa. We agree to always paint stopPoint, and to optionally paint startPoint." (drawFirstPoint or: [forwards == false "ie this is stopPoint"]) ifTrue: [self copyBits]. self drawLoopX: (point2 x - point1 x) rounded Y: (point2 y - point1 y) rounded. (drawFirstPoint or: [forwards "ie this is stopPoint"]) ifTrue: [self copyBits]. ! ! !BitBlt methodsFor: 'line drawing' stamp: 'ar 2/2/2001 15:09'! drawLoopX: xDelta Y: yDelta "Primitive. Implements the Bresenham plotting algorithm (IBM Systems Journal, Vol. 4 No. 1, 1965). It chooses a principal direction, and maintains a potential, P. When P's sign changes, it is time to move in the minor direction as well. This particular version does not write the first and last points, so that these can be called for as needed in client code. Optional. See Object documentation whatIsAPrimitive." | dx dy px py P | dx _ xDelta sign. dy _ yDelta sign. px _ yDelta abs. py _ xDelta abs. "self copyBits." py > px ifTrue: ["more horizontal" P _ py // 2. 1 to: py do: [:i | destX _ destX + dx. (P _ P - px) < 0 ifTrue: [destY _ destY + dy. P _ P + py]. i < py ifTrue: [self copyBits]]] ifFalse: ["more vertical" P _ px // 2. 1 to: px do: [:i | destY _ destY + dy. (P _ P - py) < 0 ifTrue: [destX _ destX + dx. P _ P + px]. i < px ifTrue: [self copyBits]]]! ! !BitBlt methodsFor: 'private' stamp: 'hg 6/27/2000 12:27'! cachedFontColormapFrom: sourceDepth to: destDepth | srcIndex map | CachedFontColorMaps class == Array ifFalse: [CachedFontColorMaps _ (1 to: 9) collect: [:i | Array new: 32]]. srcIndex _ sourceDepth. sourceDepth > 8 ifTrue: [srcIndex _ 9]. (map _ (CachedFontColorMaps at: srcIndex) at: destDepth) ~~ nil ifTrue: [^ map]. map _ (Color cachedColormapFrom: sourceDepth to: destDepth) copy. (CachedFontColorMaps at: srcIndex) at: destDepth put: map. ^ map ! ! !BitBlt methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! copyBitsAgain "Primitive. See BitBlt|copyBits, also a Primitive. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !BitBlt methodsFor: 'private' stamp: 'ar 10/25/1998 17:30'! copyBitsFrom: x0 to: x1 at: y destX _ x0. destY _ y. sourceX _ x0. width _ (x1 - x0). self copyBits.! ! !BitBlt methodsFor: 'private'! eraseBits "Perform the erase operation, which puts 0's in the destination wherever the source (which is assumed to be just 1 bit deep) has a 1. This requires the colorMap to be set in order to AND all 1's into the destFrom pixels regardless of their size." | oldMask oldMap | oldMask _ halftoneForm. halftoneForm _ nil. oldMap _ colorMap. self colorMap: (Bitmap with: 0 with: 16rFFFFFFFF). combinationRule _ Form erase. self copyBits. "Erase the dest wherever the source is 1" halftoneForm _ oldMask. "already converted to a Bitmap" colorMap _ oldMap! ! !BitBlt methodsFor: 'private' stamp: 'ar 5/26/2000 16:38'! getPluginName "Private. Return the name of the plugin representing BitBlt. Used for dynamically switching between different BB representations only." ^'BitBltPlugin'! ! !BitBlt methodsFor: 'private' stamp: 'hg 6/27/2000 12:28'! installStrikeFont: aStrikeFont foregroundColor: foregroundColor backgroundColor: backgroundColor | lastSourceDepth | sourceForm ifNotNil:[lastSourceDepth _ sourceForm depth]. sourceForm _ aStrikeFont glyphs. (colorMap notNil and:[lastSourceDepth = sourceForm depth]) ifFalse: ["Set up color map for a different source depth (color font)" "Uses caching for reasonable efficiency" colorMap _ self cachedFontColormapFrom: sourceForm depth to: destForm depth. colorMap at: 1 put: (backgroundColor pixelValueForDepth: destForm depth)]. sourceForm depth = 1 ifTrue: [colorMap at: 2 put: (foregroundColor pixelValueForDepth: destForm depth). "Ignore any halftone pattern since we use a color map approach here" halftoneForm _ nil]. sourceY _ 0. height _ aStrikeFont height. ! ! !BitBlt methodsFor: 'private'! paintBits "Perform the paint operation, which requires two calls to BitBlt." | color oldMap saveRule | sourceForm depth = 1 ifFalse: [^ self halt: 'paint operation is only defined for 1-bit deep sourceForms']. saveRule _ combinationRule. color _ halftoneForm. halftoneForm _ nil. oldMap _ colorMap. "Map 1's to ALL ones, not just one" self colorMap: (Bitmap with: 0 with: 16rFFFFFFFF). combinationRule _ Form erase. self copyBits. "Erase the dest wherever the source is 1" halftoneForm _ color. combinationRule _ Form under. self copyBits. "then OR, with whatever color, into the hole" colorMap _ oldMap. combinationRule _ saveRule " | dot | dot _ Form dotOfSize: 32. ((BitBlt destForm: Display sourceForm: dot fillColor: Color lightGray combinationRule: Form paint destOrigin: Sensor cursorPoint sourceOrigin: 0@0 extent: dot extent clipRect: Display boundingBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF)) copyBits"! ! !BitBlt methodsFor: 'private' stamp: 'ar 5/18/2000 21:49'! primDisplayString: aString from: startIndex to: stopIndex map: glyphMap xTable: xTable kern: kernDelta | ascii glyph | startIndex to: stopIndex do:[:charIndex| ascii _ (aString at: charIndex) asciiValue. glyph _ glyphMap at: ascii + 1. sourceX _ xTable at: glyph + 1. width _ (xTable at: glyph + 2) - sourceX. self copyBits. destX _ destX + width + kernDelta. ].! ! !BitBlt methodsFor: 'private'! setDestForm: df | bb | bb _ df boundingBox. destForm _ df. clipX _ bb left. clipY _ bb top. clipWidth _ bb width. clipHeight _ bb height! ! !BitBlt methodsFor: 'private' stamp: 'di 9/11/1998 13:07'! setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect | aPoint | destForm _ df. sourceForm _ sf. self fillColor: hf. "sets halftoneForm" combinationRule _ cr. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourceOrigin x. sourceY _ sourceOrigin y. width _ extent x. height _ extent y. aPoint _ clipRect origin. clipX _ aPoint x. clipY _ aPoint y. aPoint _ clipRect corner. clipWidth _ aPoint x - clipX. clipHeight _ aPoint y - clipY. sourceForm == nil ifFalse: [colorMap _ sourceForm colormapIfNeededForDepth: destForm depth]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitBlt class instanceVariableNames: ''! !BitBlt class methodsFor: 'instance creation' stamp: 'ar 5/28/2000 12:04'! asGrafPort "Return the GrafPort associated with the receiver" ^GrafPort! ! !BitBlt class methodsFor: 'instance creation' stamp: 'di 3/2/98 12:53'! bitPeekerFromForm: sourceForm "Answer an instance to be used extract individual pixels from the given Form. The destination for a 1x1 copyBits will be the low order bits of (bits at: 1)." | pixPerWord | pixPerWord _ 32 // sourceForm depth. sourceForm unhibernate. ^ self destForm: (Form extent: pixPerWord@1 depth: sourceForm depth) sourceForm: sourceForm halftoneForm: nil combinationRule: Form over destOrigin: (pixPerWord - 1)@0 sourceOrigin: 0@0 extent: 1@1 clipRect: (0@0 extent: pixPerWord@1) ! ! !BitBlt class methodsFor: 'instance creation' stamp: 'di 3/2/98 12:53'! bitPokerToForm: destForm "Answer an instance to be used for valueAt: aPoint put: pixValue. The source for a 1x1 copyBits will be the low order of (bits at: 1)" | pixPerWord | pixPerWord _ 32//destForm depth. destForm unhibernate. ^ self destForm: destForm sourceForm: (Form extent: pixPerWord@1 depth: destForm depth) halftoneForm: nil combinationRule: Form over destOrigin: 0@0 sourceOrigin: (pixPerWord-1)@0 extent: 1@1 clipRect: (0@0 extent: destForm extent) ! ! !BitBlt class methodsFor: 'instance creation' stamp: 'ar 5/28/2000 12:00'! current "Return the class currently to be used for BitBlt" ^Display defaultBitBltClass! ! !BitBlt class methodsFor: 'instance creation'! destForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect "Answer an instance of me with values set according to the arguments." ^ self new setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect! ! !BitBlt class methodsFor: 'instance creation'! destForm: df sourceForm: sf halftoneForm: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect "Answer an instance of me with values set according to the arguments." ^ self new setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect! ! !BitBlt class methodsFor: 'instance creation'! toForm: aForm ^ self new setDestForm: aForm! ! !BitBlt class methodsFor: 'examples' stamp: 'di 12/1/97 12:08'! alphaBlendDemo "To run this demo, use... Display restoreAfter: [BitBlt alphaBlendDemo] Displays 10 alphas, then lets you paint. Option-Click to stop painting." "This code exhibits alpha blending in any display depth by performing the blend in an off-screen buffer with 32-bit pixels, and then copying the result back onto the screen with an appropriate color map. - tk 3/10/97" "This version uses a sliding buffer for painting that keeps pixels in 32 bits as long as they are in the buffer, so as not to lose info by converting down to display resolution and back up to 32 bits at each operation. - di 3/15/97" | brush buff dispToBuff buffToDisplay mapDto32 map32toD prevP p brushToBuff theta buffRect buffSize buffToBuff brushRect delta newBuffRect updateRect | "compute color maps if needed" Display depth <= 8 ifTrue: [ mapDto32 _ Color cachedColormapFrom: Display depth to: 32. map32toD _ Color cachedColormapFrom: 32 to: Display depth]. "display 10 different alphas, across top of screen" buff _ Form extent: 500@50 depth: 32. dispToBuff _ BitBlt toForm: buff. dispToBuff colorMap: mapDto32. dispToBuff copyFrom: (50@10 extent: 500@50) in: Display to: 0@0. 1 to: 10 do: [:i | dispToBuff fill: (50*(i-1)@0 extent: 50@50) fillColor: (Color red alpha: i/10) rule: Form blend]. buffToDisplay _ BitBlt toForm: Display. buffToDisplay colorMap: map32toD. buffToDisplay copyFrom: buff boundingBox in: buff to: 50@10. "Create a brush with radially varying alpha" brush _ Form extent: 30@30 depth: 32. 1 to: 5 do: [:i | brush fillShape: (Form dotOfSize: brush width*(6-i)//5) fillColor: (Color red alpha: 0.02 * i - 0.01) at: brush extent // 2]. "Now paint with the brush using alpha blending." buffSize _ 100. buff _ Form extent: brush extent + buffSize depth: 32. "Travelling 32-bit buffer" dispToBuff _ BitBlt toForm: buff. "This is from Display to buff" dispToBuff colorMap: mapDto32. brushToBuff _ BitBlt toForm: buff. "This is from brush to buff" brushToBuff sourceForm: brush; sourceOrigin: 0@0. brushToBuff combinationRule: Form blend. buffToBuff _ BitBlt toForm: buff. "This is for slewing the buffer" [Sensor yellowButtonPressed] whileFalse: [prevP _ nil. buffRect _ Sensor cursorPoint - (buffSize // 2) extent: buff extent. dispToBuff copyFrom: buffRect in: Display to: 0@0. [Sensor redButtonPressed] whileTrue: ["Here is the painting loop" p _ Sensor cursorPoint - (brush extent // 2). (prevP == nil or: [prevP ~= p]) ifTrue: [prevP == nil ifTrue: [prevP _ p]. (p dist: prevP) > buffSize ifTrue: ["Stroke too long to fit in buffer -- clip to buffer, and next time through will do more of it" theta _ (p-prevP) theta. p _ ((theta cos@theta sin) * buffSize asFloat + prevP) truncated]. brushRect _ p extent: brush extent. (buffRect containsRect: brushRect) ifFalse: ["Brush is out of buffer region. Scroll the buffer, and fill vacated regions from the display" delta _ brushRect amountToTranslateWithin: buffRect. buffToBuff copyFrom: buff boundingBox in: buff to: delta. newBuffRect _ buffRect translateBy: delta negated. (newBuffRect areasOutside: buffRect) do: [:r | dispToBuff copyFrom: r in: Display to: r origin - newBuffRect origin]. buffRect _ newBuffRect]. "Interpolate from prevP to p..." brushToBuff drawFrom: prevP - buffRect origin to: p - buffRect origin withFirstPoint: false. "Update (only) the altered pixels of the destination" updateRect _ (p min: prevP) corner: (p max: prevP) + brush extent. buffToDisplay copy: updateRect from: updateRect origin - buffRect origin in: buff. prevP _ p]]]! ! !BitBlt class methodsFor: 'examples' stamp: 'di 12/1/97 12:09'! antiAliasDemo "To run this demo, use... Display restoreAfter: [BitBlt antiAliasDemo] Goes immediately into on-screen paint mode. Option-Click to stop painting." "This code exhibits alpha blending in any display depth by performing the blend in an off-screen buffer with 32-bit pixels, and then copying the result back onto the screen with an appropriate color map. - tk 3/10/97" "This version uses a sliding buffer for painting that keeps pixels in 32 bits as long as they are in the buffer, so as not to lose info by converting down to display resolution and back up to 32 bits at each operation. - di 3/15/97" "This version also uses WarpBlt to paint into twice as large a buffer, and then use smoothing when reducing back down to the display. In fact this same routine will now work for 3x3 soothing as well. Remove the statements 'buff displayAt: 0@0' to hide the buffer. - di 3/19/97" | brush buff dispToBuff buffToDisplay mapDto32 map32toD prevP p brushToBuff theta buffRect buffSize buffToBuff brushRect delta newBuffRect updateRect scale p0 | "compute color maps if needed" Display depth <= 8 ifTrue: [ mapDto32 _ Color cachedColormapFrom: Display depth to: 32. map32toD _ Color cachedColormapFrom: 32 to: Display depth]. "Create a brush with radially varying alpha" brush _ Form extent: 3@3 depth: 32. brush fill: brush boundingBox fillColor: (Color red alpha: 0.05). brush fill: (1@1 extent: 1@1) fillColor: (Color red alpha: 0.2). scale _ 2. "Actual drawing happens at this magnification" "Scale brush up for painting in magnified buffer" brush _ brush magnify: brush boundingBox by: scale. "Now paint with the brush using alpha blending." buffSize _ 100. buff _ Form extent: (brush extent + buffSize) * scale depth: 32. "Travelling 32-bit buffer" dispToBuff _ (WarpBlt toForm: buff) "From Display to buff - magnify by 2" sourceForm: Display; colorMap: mapDto32; combinationRule: Form over. brushToBuff _ (BitBlt toForm: buff) "From brush to buff" sourceForm: brush; sourceOrigin: 0@0; combinationRule: Form blend. buffToDisplay _ (WarpBlt toForm: Display) "From buff to Display - shrink by 2" sourceForm: buff; colorMap: map32toD; cellSize: scale; "...and use smoothing" combinationRule: Form over. buffToBuff _ BitBlt toForm: buff. "This is for slewing the buffer" [Sensor yellowButtonPressed] whileFalse: [prevP _ nil. buffRect _ Sensor cursorPoint - (buff extent // scale // 2) extent: buff extent // scale. p0 _ (buff extent // 2) - (buffRect extent // 2). dispToBuff copyQuad: buffRect innerCorners toRect: buff boundingBox. buff displayAt: 0@0. "** remove to hide sliding buffer **" [Sensor redButtonPressed] whileTrue: ["Here is the painting loop" p _ Sensor cursorPoint - buffRect origin + p0. "p, prevP are rel to buff origin" (prevP == nil or: [prevP ~= p]) ifTrue: [prevP == nil ifTrue: [prevP _ p]. (p dist: prevP) > (buffSize-1) ifTrue: ["Stroke too long to fit in buffer -- clip to buffer, and next time through will do more of it" theta _ (p-prevP) theta. p _ ((theta cos@theta sin) * (buffSize-2) asFloat + prevP) truncated]. brushRect _ p extent: brush extent. ((buff boundingBox insetBy: scale) containsRect: brushRect) ifFalse: ["Brush is out of buffer region. Scroll the buffer, and fill vacated regions from the display" delta _ (brushRect amountToTranslateWithin: (buff boundingBox insetBy: scale)) // scale. buffToBuff copyFrom: buff boundingBox in: buff to: delta*scale. newBuffRect _ buffRect translateBy: delta negated. p _ p translateBy: delta*scale. prevP _ prevP translateBy: delta*scale. (newBuffRect areasOutside: buffRect) do: [:r | dispToBuff copyQuad: r innerCorners toRect: (r origin - newBuffRect origin*scale extent: r extent*scale)]. buffRect _ newBuffRect]. "Interpolate from prevP to p..." brushToBuff drawFrom: prevP to: p withFirstPoint: false. buff displayAt: 0@0. "** remove to hide sliding buffer **" "Update (only) the altered pixels of the destination" updateRect _ (p min: prevP) corner: (p max: prevP) + brush extent. updateRect _ updateRect origin // scale * scale corner: updateRect corner + scale // scale * scale. buffToDisplay copyQuad: updateRect innerCorners toRect: (updateRect origin // scale + buffRect origin extent: updateRect extent // scale). prevP _ p]]]! ! !BitBlt class methodsFor: 'examples'! exampleOne "This tests BitBlt by displaying the result of all sixteen combination rules that BitBlt is capable of using. (Please see the comment in BitBlt for the meaning of the combination rules)." | path | path _ Path new. 0 to: 3 do: [:i | 0 to: 3 do: [:j | path add: j * 100 @ (i * 75)]]. Display fillWhite. path _ path translateBy: 60 @ 40. 1 to: 16 do: [:index | BitBlt exampleAt: (path at: index) rule: index - 1 fillColor: Color black] "BitBlt exampleOne"! ! !BitBlt class methodsFor: 'examples'! exampleTwo "This is to test painting with a gray tone. It also tests that the seaming with gray patterns is correct in the microcode. Lets you paint for a while and then automatically stops." | f aBitBlt | "create a small black Form source as a brush. " f _ Form extent: 20 @ 20. f fillBlack. "create a BitBlt which will OR gray into the display. " aBitBlt _ BitBlt destForm: Display sourceForm: f fillColor: Color gray combinationRule: Form under destOrigin: Sensor cursorPoint sourceOrigin: 0 @ 0 extent: f extent clipRect: Display computeBoundingBox. "paint the gray Form on the screen for a while. " [Sensor anyButtonPressed] whileFalse: [aBitBlt destOrigin: Sensor cursorPoint. aBitBlt copyBits] "BitBlt exampleTwo"! ! !BitBlt class methodsFor: 'private'! exampleAt: originPoint rule: rule fillColor: mask "This builds a source and destination form and copies the source to the destination using the specifed rule and mask. It is called from the method named exampleOne." | s d border aBitBlt | border_Form extent: 32@32. border fillBlack. border fill: (1@1 extent: 30@30) fillColor: Color white. s _ Form extent: 32@32. s fillWhite. s fillBlack: (7@7 corner: 25@25). d _ Form extent: 32@32. d fillWhite. d fillBlack: (0@0 corner: 32@16). s displayOn: Display at: originPoint. border displayOn: Display at: originPoint rule: Form under. d displayOn: Display at: originPoint + (s width @0). border displayOn: Display at: originPoint + (s width @0) rule: Form under. d displayOn: Display at: originPoint + (s extent // (2 @ 1)). aBitBlt _ BitBlt destForm: Display sourceForm: s fillColor: mask combinationRule: rule destOrigin: originPoint + (s extent // (2 @ 1)) sourceOrigin: 0 @ 0 extent: s extent clipRect: Display computeBoundingBox. aBitBlt copyBits. border displayOn: Display at: originPoint + (s extent // (2 @ 1)) rule: Form under. "BitBlt exampleAt: 100@100 rule: Form over fillColor: Display gray"! ! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 10/28/1999 23:38'! benchDiffsFrom: before to: afterwards "Given two outputs of BitBlt>>benchmark show the relative improvements." | old new log oldLine newLine oldVal newVal improvement | log _ WriteStream on: String new. old _ ReadStream on: before. new _ ReadStream on: afterwards. [old atEnd or:[new atEnd]] whileFalse:[ oldLine _ old upTo: Character cr. newLine _ new upTo: Character cr. (oldLine includes: Character tab) ifTrue:[ oldLine _ ReadStream on: oldLine. newLine _ ReadStream on: newLine. Transcript cr; show: (oldLine upTo: Character tab); tab. log cr; nextPutAll: (newLine upTo: Character tab); tab. [oldLine skipSeparators. newLine skipSeparators. oldLine atEnd] whileFalse:[ oldVal _ Integer readFrom: oldLine. newVal _ Integer readFrom: newLine. improvement _ oldVal asFloat / newVal asFloat roundTo: 0.1. Transcript show: improvement printString; tab. log print: improvement; tab]. ] ifFalse:[ Transcript cr; show: oldLine. log cr; nextPutAll: oldLine. ]. ]. ^log contents! ! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 5/25/2000 17:58'! benchmark "BitBlt benchmark" "Run a benchmark on different combinations rules, source/destination depths and BitBlt modes. Note: This benchmark doesn't give you any 'absolute' value - it is intended only for benchmarking improvements in the bitblt code and nothing else. Attention: *this*may*take*a*while*" | bb source dest destRect log t | log _ WriteStream on: String new. destRect _ 0@0 extent: 600@600. "Form paint/Form over - the most common rules" #( 25 3 ) do:[:rule| Transcript cr; show:'---- Combination rule: ', rule printString,' ----'. log cr; nextPutAll:'---- Combination rule: ', rule printString,' ----'. #(1 2 4 8 16 32) do:[:destDepth| dest _ nil. dest _ Form extent: destRect extent depth: destDepth. Transcript cr. log cr. #(1 2 4 8 16 32) do:[:sourceDepth| Transcript cr; show: sourceDepth printString, ' => ', destDepth printString. log cr; nextPutAll: sourceDepth printString, ' => ', destDepth printString. source _ nil. bb _ nil. source _ Form extent: destRect extent depth: sourceDepth. (source getCanvas) fillOval: dest boundingBox color: Color yellow borderWidth: 30 borderColor: Color black. bb _ WarpBlt toForm: dest. bb sourceForm: source. bb sourceRect: source boundingBox. bb destRect: dest boundingBox. bb colorMap: (source colormapIfNeededForDepth: dest depth). bb combinationRule: rule. "Measure speed of copyBits" t _ Time millisecondsToRun:[bb copyBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. bb sourceForm: source destRect: source boundingBox. "Measure speed of 1x1 warpBits" bb cellSize: 1. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. "Measure speed of 2x2 warpBits" bb cellSize: 2. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. "Measure speed of 3x3 warpBits" bb cellSize: 3. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. ]. ]. ]. ^log contents! ! InterpreterPlugin subclass: #BitBltSimulation instanceVariableNames: 'destForm sourceForm halftoneForm combinationRule destX destY width height sourceX sourceY clipX clipY clipWidth clipHeight sourceBits sourcePitch sourcePixSize destBits destPitch destPixSize pixPerWord bitCount skew mask1 mask2 preload nWords destMask hDir vDir sourceIndex sourceDelta destIndex destDelta sx sy dx dy bbW bbH srcWidth srcHeight destWidth destHeight halftoneHeight noSource noHalftone halftoneBase colorMap sourceAlpha cmBitsPerColor srcBitShift dstBitShift scanStart scanStop scanString scanRightX scanStopArray scanDisplayFlag scanXTable stopCode bitBltOop affectedL affectedR affectedT affectedB opTable maskTable ditherMatrix4x4 ditherThresholds16 ditherValues16 hasSurfaceLock warpSrcShift warpSrcMask warpAlignShift warpAlignMask warpBitShiftTable cmDeltaBits cmRedMask cmBlueMask cmGreenMask cmRedShift cmBlueShift cmGreenShift ' classVariableNames: 'AllOnes BBClipHeightIndex BBClipWidthIndex BBClipXIndex BBClipYIndex BBColorMapIndex BBDestFormIndex BBDestXIndex BBDestYIndex BBHalftoneFormIndex BBHeightIndex BBLastIndex BBRuleIndex BBSourceFormIndex BBSourceXIndex BBSourceYIndex BBWarpBase BBWidthIndex BBXTableIndex BinaryPoint CrossedX EndOfRun FixedPt1 FormBitsIndex FormDepthIndex FormHeightIndex FormWidthIndex OpTable OpTableSize ' poolDictionaries: '' category: 'VMConstruction-Interpreter'! !BitBltSimulation commentStamp: '' prior: 0! This class implements BitBlt, much as specified in the Blue Book spec. Performance has been enhanced through the use of pointer variables such as sourceIndex and destIndex, and by separating several special cases of the inner loop. Operation has been extended to color, with support for 1, 2, 4, 8, 16, and 32-bit pixel sizes. Conversion between different pixel sizes is facilitated by accepting an optional color map. In addition to the original 16 combination rules, this BitBlt supports 16 fail (for old paint mode) 17 fail (for old mask mode) 18 sourceWord + destinationWord 19 sourceWord - destinationWord 20 rgbAdd: sourceWord with: destinationWord 21 rgbSub: sourceWord with: destinationWord 22 OLDrgbDiff: sourceWord with: destinationWord 23 OLDtallyIntoMap: destinationWord -- old vers doesn't clip to bit boundary 24 alphaBlend: sourceWord with: destinationWord 25 pixPaint: sourceWord with: destinationWord 26 pixMask: sourceWord with: destinationWord 27 rgbMax: sourceWord with: destinationWord 28 rgbMin: sourceWord with: destinationWord 29 rgbMin: sourceWord bitInvert32 with: destinationWord 30 alphaBlendConst: sourceWord with: destinationWord -- alpha passed as an arg 31 alphaPaintConst: sourceWord with: destinationWord -- alpha passed as an arg 32 rgbDiff: sourceWord with: destinationWord 33 tallyIntoMap: destinationWord 34 alphaBlendScaled: sourceWord with: destinationWord This implementation has also been fitted with an experimental "warp drive" that allows abritrary scaling and rotation (and even limited affine deformations) with all BitBlt storage modes supported. To add a new rule to BitBlt... 1. add the new rule method or methods in the category 'combination rules' of BBSim 2. describe it in the class comment of BBSim and in the class comment for BitBlt 3. add refs to initializeRuleTable in proper positions 4. add refs to initBBOpTable, following the pattern ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 21:27'! drawLoopX: xDelta Y: yDelta "This is the primitive implementation of the line-drawing loop. See the comments in BitBlt>>drawLoopX:Y:" | dx1 dy1 px py P affL affR affT affB | xDelta > 0 ifTrue: [dx1 _ 1] ifFalse: [xDelta = 0 ifTrue: [dx1 _ 0] ifFalse: [dx1 _ -1]]. yDelta > 0 ifTrue: [dy1 _ 1] ifFalse: [yDelta = 0 ifTrue: [dy1 _ 0] ifFalse: [dy1 _ -1]]. px _ yDelta abs. py _ xDelta abs. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999. py > px ifTrue: ["more horizontal" P _ py // 2. 1 to: py do: [:i | destX _ destX + dx1. (P _ P - px) < 0 ifTrue: [destY _ destY + dy1. P _ P + py]. i < py ifTrue: [self copyBits. interpreterProxy failed ifTrue: [^ nil "bail out now on failure -- avoid storing x,y"]. (affectedL < affectedR and: [affectedT < affectedB]) ifTrue: ["Affected rectangle grows along the line" affL _ affL min: affectedL. affR _ affR max: affectedR. affT _ affT min: affectedT. affB _ affB max: affectedB. (affR - affL) * (affB - affT) > 4000 ifTrue: ["If affected rectangle gets large, update it in chunks" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. self showDisplayBits. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999]]. ]]] ifFalse: ["more vertical" P _ px // 2. 1 to: px do: [:i | destY _ destY + dy1. (P _ P - py) < 0 ifTrue: [destX _ destX + dx1. P _ P + px]. i < px ifTrue: [self copyBits. interpreterProxy failed ifTrue: [^ nil "bail out now on failure -- avoid storing x,y"]. (affectedL < affectedR and: [affectedT < affectedB]) ifTrue: ["Affected rectangle grows along the line" affL _ affL min: affectedL. affR _ affR max: affectedR. affT _ affT min: affectedT. affB _ affB max: affectedB. (affR - affL) * (affB - affT) > 4000 ifTrue: ["If affected rectangle gets large, update it in chunks" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. self showDisplayBits. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999]]. ]]]. "Remaining affected rect" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. "store destX, Y back" interpreterProxy storeInteger: BBDestXIndex ofObject: bitBltOop withValue: destX. interpreterProxy storeInteger: BBDestYIndex ofObject: bitBltOop withValue: destY.! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 21:23'! fetchIntOrFloat: fieldIndex ofObject: objectPointer "Return the integer value of the given field of the given object. If the field contains a Float, truncate it and return its integral part. Fail if the given field does not contain a small integer or Float, or if the truncated Float is out of the range of small integers." | fieldOop floatValue | self var: #floatValue declareC:'double floatValue'. fieldOop _ interpreterProxy fetchPointer: fieldIndex ofObject: objectPointer. (interpreterProxy isIntegerObject: fieldOop) ifTrue:[^interpreterProxy integerValueOf: fieldOop]. floatValue _ interpreterProxy floatValueOf: fieldOop. (-2147483648.0 <= floatValue and:[floatValue <= 2147483647.0]) ifFalse:[interpreterProxy primitiveFail. ^0]. ^floatValue asInteger! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 20:51'! fetchIntegerOrTruncFloat: fieldIndex ofObject: objectPointer "Return the integer value of the given field of the given object. If the field contains a Float, truncate it and return its integral part. Fail if the given field does not contain a small integer or Float, or if the truncated Float is out of the range of small integers." | fieldOop floatValue | self var: #floatValue declareC:'double floatValue'. fieldOop _ interpreterProxy fetchPointer: fieldIndex ofObject: objectPointer. (interpreterProxy isIntegerObject: fieldOop) ifTrue:[^interpreterProxy integerValueOf: fieldOop]. floatValue _ interpreterProxy floatValueOf: fieldOop. (-2147483648.0 <= floatValue and:[floatValue <= 2147483647.0]) ifFalse:[interpreterProxy primitiveFail. ^0]. ^floatValue asInteger! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/28/1999 22:21'! loadBitBltDestForm "Load the dest form for BitBlt. Return false if anything is wrong, true otherwise." | destBitsSize | self inline: true. destBits _ interpreterProxy fetchPointer: FormBitsIndex ofObject: destForm. destWidth _ interpreterProxy fetchInteger: FormWidthIndex ofObject: destForm. destHeight _ interpreterProxy fetchInteger: FormHeightIndex ofObject: destForm. (destWidth >= 0 and: [destHeight >= 0]) ifFalse: [^ false]. destPixSize _ interpreterProxy fetchInteger: FormDepthIndex ofObject: destForm. "Ignore an integer bits handle for Display in which case the appropriate values will be obtained by calling ioLockSurfaceBits()." (interpreterProxy isIntegerObject: destBits) ifTrue:[ "Query for actual surface dimensions" (self queryDestSurface: (interpreterProxy integerValueOf: destBits)) ifFalse:[^false]. pixPerWord _ 32 // destPixSize. destBits _ destPitch _ 0. ] ifFalse:[ pixPerWord _ 32 // destPixSize. destPitch _ destWidth + (pixPerWord-1) // pixPerWord * 4. destBitsSize _ interpreterProxy byteSizeOf: destBits. ((interpreterProxy isWordsOrBytes: destBits) and: [destBitsSize = (destPitch * destHeight)]) ifFalse: [^ false]. "Skip header since external bits don't have one" destBits _ self cCoerce: (interpreterProxy firstIndexableField: destBits) to:'int'. ]. ^true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/20/2000 19:42'! loadBitBltFrom: bbObj "Load BitBlt from the oop. This function is exported for the Balloon engine." self export: true. ^self loadBitBltFrom: bbObj warping: false.! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 5/11/2000 20:39'! loadBitBltFrom: bbObj warping: aBool "Load context from BitBlt instance. Return false if anything is amiss" "NOTE this should all be changed to minX/maxX coordinates for simpler clipping -- once it works!!" | ok | self inline: false. bitBltOop _ bbObj. colorMap _ nil. "Assume no color map" combinationRule _ interpreterProxy fetchInteger: BBRuleIndex ofObject: bitBltOop. (interpreterProxy failed or: [combinationRule < 0 or: [combinationRule > (OpTableSize - 2)]]) ifTrue: [^ false "operation out of range"]. (combinationRule >= 16 and: [combinationRule <= 17]) ifTrue: [^ false "fail for old simulated paint, erase modes"]. sourceForm _ interpreterProxy fetchPointer: BBSourceFormIndex ofObject: bitBltOop. noSource _ self ignoreSourceOrHalftone: sourceForm. halftoneForm _ interpreterProxy fetchPointer: BBHalftoneFormIndex ofObject: bitBltOop. noHalftone _ self ignoreSourceOrHalftone: halftoneForm. destForm _ interpreterProxy fetchPointer: BBDestFormIndex ofObject: bbObj. ((interpreterProxy isPointers: destForm) and: [(interpreterProxy slotSizeOf: destForm) >= 4]) ifFalse: [^ false]. ok _ self loadBitBltDestForm. ok ifFalse:[^false]. destX _ self fetchIntOrFloat: BBDestXIndex ofObject: bitBltOop. destY _ self fetchIntOrFloat: BBDestYIndex ofObject: bitBltOop. width _ self fetchIntOrFloat: BBWidthIndex ofObject: bitBltOop. height _ self fetchIntOrFloat: BBHeightIndex ofObject: bitBltOop. interpreterProxy failed ifTrue: [^ false "non-integer value"]. noSource ifTrue: [sourceX _ sourceY _ 0] ifFalse: [((interpreterProxy isPointers: sourceForm) and: [(interpreterProxy slotSizeOf: sourceForm) >= 4]) ifFalse: [^ false]. ok _ self loadBitBltSourceForm. ok ifFalse:[^false]. colorMap _ interpreterProxy fetchPointer: BBColorMapIndex ofObject: bitBltOop. ok _ self loadColorMap: aBool. ok ifFalse:[^false]. self setupColorMasks. sourceX _ self fetchIntOrFloat: BBSourceXIndex ofObject: bitBltOop. sourceY _ self fetchIntOrFloat: BBSourceYIndex ofObject: bitBltOop]. ok _ self loadHalftoneForm. ok ifFalse:[^false]. clipX _ self fetchIntOrFloat: BBClipXIndex ofObject: bitBltOop. clipY _ self fetchIntOrFloat: BBClipYIndex ofObject: bitBltOop. clipWidth _ self fetchIntOrFloat: BBClipWidthIndex ofObject: bitBltOop. clipHeight _ self fetchIntOrFloat: BBClipHeightIndex ofObject: bitBltOop. interpreterProxy failed ifTrue: [^ false "non-integer value"]. clipX < 0 ifTrue: [clipWidth _ clipWidth + clipX. clipX _ 0]. clipY < 0 ifTrue: [clipHeight _ clipHeight + clipY. clipY _ 0]. clipX+clipWidth > destWidth ifTrue: [clipWidth _ destWidth - clipX]. clipY+clipHeight > destHeight ifTrue: [clipHeight _ destHeight - clipY]. ^ true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 21:24'! loadBitBltSourceForm "Load the source form for BitBlt. Return false if anything is wrong, true otherwise." | sourcePixPerWord sourceBitsSize | self inline: true. sourceBits _ interpreterProxy fetchPointer: FormBitsIndex ofObject: sourceForm. srcWidth _ self fetchIntOrFloat: FormWidthIndex ofObject: sourceForm. srcHeight _ self fetchIntOrFloat: FormHeightIndex ofObject: sourceForm. (srcWidth >= 0 and: [srcHeight >= 0]) ifFalse: [^ false]. sourcePixSize _ interpreterProxy fetchInteger: FormDepthIndex ofObject: sourceForm. "Ignore an integer bits handle for Display in which case the appropriate values will be obtained by calling ioLockSurfaceBits()." (interpreterProxy isIntegerObject: sourceBits) ifTrue:[ "Query for actual surface dimensions" (self querySourceSurface: (interpreterProxy integerValueOf: sourceBits)) ifFalse:[^false]. sourcePixPerWord _ 32 // sourcePixSize. sourceBits _ sourcePitch _ 0. ] ifFalse:[ sourcePixPerWord _ 32 // sourcePixSize. sourcePitch _ srcWidth + (sourcePixPerWord-1) // sourcePixPerWord * 4. sourceBitsSize _ interpreterProxy byteSizeOf: sourceBits. ((interpreterProxy isWordsOrBytes: sourceBits) and: [sourceBitsSize = (sourcePitch * srcHeight)]) ifFalse: [^ false]. "Skip header since external bits don't have one" sourceBits _ self cCoerce: (interpreterProxy firstIndexableField: sourceBits) to:'int'. ]. ^true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/28/1999 22:21'! loadColorMap: warping "ColorMap, if not nil, must be longWords, and 2^N long, where N = sourcePixSize for 1, 2, 4, 8 bits, or N = 9, 12, or 15 (3, 4, 5 bits per color) for 16 or 32 bits." | cmSize | self inline: true. cmBitsPerColor _ 0. colorMap = interpreterProxy nilObject ifTrue:[ colorMap _ nil. ] ifFalse:[ (interpreterProxy isWords: colorMap) ifTrue:[ cmSize _ interpreterProxy slotSizeOf: colorMap. cmSize = 512 ifTrue: [cmBitsPerColor _ 3]. cmSize = 4096 ifTrue: [cmBitsPerColor _ 4]. cmSize = 32768 ifTrue: [cmBitsPerColor _ 5]. warping ifFalse:[ "WarpBlt has different checks on the color map" sourcePixSize <= 8 ifTrue: [cmSize = (1 << sourcePixSize) ifFalse: [^ false] ] ifFalse: [cmBitsPerColor = 0 ifTrue: [^ false] ]]. colorMap _ self cCoerce: (interpreterProxy firstIndexableField: colorMap) to: 'int'. self setupColorMasks. ] ifFalse: [^ false]]. ^true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/28/1999 22:22'! loadHalftoneForm "Load the halftone form" | halftoneBits | self inline: true. noHalftone ifTrue:[ halftoneBase _ nil. ^true]. ((interpreterProxy isPointers: halftoneForm) and: [(interpreterProxy slotSizeOf: halftoneForm) >= 4]) ifTrue: ["Old-style 32xN monochrome halftone Forms" halftoneBits _ interpreterProxy fetchPointer: FormBitsIndex ofObject: halftoneForm. halftoneHeight _ interpreterProxy fetchInteger: FormHeightIndex ofObject: halftoneForm. (interpreterProxy isWords: halftoneBits) ifFalse: [noHalftone _ true]] ifFalse: ["New spec accepts, basically, a word array" ((interpreterProxy isPointers: halftoneForm) not and: [interpreterProxy isWords: halftoneForm]) ifFalse: [^ false]. halftoneBits _ halftoneForm. halftoneHeight _ interpreterProxy slotSizeOf: halftoneBits]. halftoneBase _ self cCoerce: (interpreterProxy firstIndexableField: halftoneBits) to:'int'. ^true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 21:24'! loadScannerFrom: bbObj start: start stop: stop string: string rightX: rightX stopArray: stopArray displayFlag: displayFlag self inline: false. "Load arguments and Scanner state" scanStart _ start. scanStop _ stop. scanString _ string. scanRightX _ rightX. scanStopArray _ stopArray. scanDisplayFlag _ displayFlag. interpreterProxy success: ( (interpreterProxy isPointers: scanStopArray) and: [(interpreterProxy slotSizeOf: scanStopArray) >= 1]). scanXTable _ interpreterProxy fetchPointer: BBXTableIndex ofObject: bbObj. interpreterProxy success: ( (interpreterProxy isPointers: scanXTable) and: [(interpreterProxy slotSizeOf: scanXTable) >= 1]). "width and sourceX may not be set..." interpreterProxy storeInteger: BBWidthIndex ofObject: bbObj withValue: 0. interpreterProxy storeInteger: BBSourceXIndex ofObject: bbObj withValue: 0. "Now load BitBlt state if displaying" scanDisplayFlag ifTrue: [interpreterProxy success: (self loadBitBltFrom: bbObj)] ifFalse: [bitBltOop _ bbObj. destX _ self fetchIntOrFloat: BBDestXIndex ofObject: bbObj]. ^interpreterProxy failed not! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/27/1999 16:03'! loadWarpBltFrom: bbObj ^self loadBitBltFrom: bbObj warping: true! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'di 1/4/2000 14:19'! scanCharacters self inline: true. scanDisplayFlag ifTrue: [ self clipRange. (combinationRule = 30) | (combinationRule = 31) ifTrue: [^ interpreterProxy primitiveFail]. self lockSurfaces ifFalse: [^ interpreterProxy primitiveFail]]. self scanCharactersLockedAndClipped. scanDisplayFlag ifTrue:[self unlockSurfaces].! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/28/1999 18:03'! setupColorMasks | bits targetBits | bits _ targetBits _ 0. sourcePixSize <= 8 ifTrue:[^nil]. sourcePixSize = 16 ifTrue:[bits _ 5]. sourcePixSize = 32 ifTrue:[bits _ 8]. colorMap == nil ifTrue:["Convert between RGB values" destPixSize <= 8 ifTrue:[^nil]. destPixSize = 16 ifTrue:[targetBits _ 5]. destPixSize = 32 ifTrue:[targetBits _ 8]] ifFalse:[targetBits _ cmBitsPerColor]. self setupColorMasksFrom: bits to: targetBits! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 10/28/1999 23:50'! setupColorMasksFrom: srcBits to: targetBits "Setup color masks for converting an incoming RGB pixel value from srcBits to targetBits." | delta mask | cmDeltaBits _ targetBits - srcBits. cmDeltaBits <= 0 ifTrue:[ mask _ 1 << targetBits - 1. delta _ srcBits - targetBits. "Mask for extracting a color part of the source" cmRedMask _ mask << (srcBits*2 - cmDeltaBits). cmGreenMask _ mask << (srcBits - cmDeltaBits). cmBlueMask _ mask << (0 - cmDeltaBits)] ifFalse:[ mask _ 1 << srcBits - 1. delta _ targetBits - srcBits. "Mask for extracting a color part of the source" cmRedMask _ mask << (srcBits*2). cmGreenMask _ mask << srcBits. cmBlueMask _ mask]. "Shifts for adjusting each value in a cm RGB value" cmRedShift _ delta * 3. cmGreenShift _ delta * 2. cmBlueShift _ delta.! ! !BitBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 20:46'! showDisplayBits interpreterProxy showDisplayBits: destForm Left: affectedL Top: affectedT Right: affectedR Bottom: affectedB! ! !BitBltSimulation methodsFor: 'accessing'! affectedBottom ^affectedB! ! !BitBltSimulation methodsFor: 'accessing'! affectedLeft ^affectedL! ! !BitBltSimulation methodsFor: 'accessing'! affectedRight ^affectedR! ! !BitBltSimulation methodsFor: 'accessing'! affectedTop ^affectedT! ! !BitBltSimulation methodsFor: 'accessing'! stopReason ^stopCode! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/23/1999 20:33'! checkSourceOverlap "check for possible overlap of source and destination" "ar 10/19/1999: This method requires surfaces to be locked." | t | self inline: true. (sourceForm = destForm and: [dy >= sy]) ifTrue: [dy > sy ifTrue: ["have to start at bottom" vDir _ -1. sy _ sy + bbH - 1. dy _ dy + bbH - 1] ifFalse: [(dy = sy) & (dx > sx) ifTrue: ["y's are equal, but x's are backward" hDir _ -1. sx _ sx + bbW - 1. "start at right" dx _ dx + bbW - 1. "and fix up masks" nWords > 1 ifTrue: [t _ mask1. mask1 _ mask2. mask2 _ t]]]. "Dest inits may be affected by this change" destIndex _ destBits + (dy * destPitch) + ((dx // pixPerWord) *4). destDelta _ (destPitch * vDir) - (4 * (nWords * hDir))]! ! !BitBltSimulation methodsFor: 'setup'! clipRange "clip and adjust source origin and extent appropriately" "first in x" destX >= clipX ifTrue: [sx _ sourceX. dx _ destX. bbW _ width] ifFalse: [sx _ sourceX + (clipX - destX). bbW _ width - (clipX - destX). dx _ clipX]. (dx + bbW) > (clipX + clipWidth) ifTrue: [bbW _ bbW - ((dx + bbW) - (clipX + clipWidth))]. "then in y" destY >= clipY ifTrue: [sy _ sourceY. dy _ destY. bbH _ height] ifFalse: [sy _ sourceY + clipY - destY. bbH _ height - (clipY - destY). dy _ clipY]. (dy + bbH) > (clipY + clipHeight) ifTrue: [bbH _ bbH - ((dy + bbH) - (clipY + clipHeight))]. noSource ifTrue: [^ nil]. sx < 0 ifTrue: [dx _ dx - sx. bbW _ bbW + sx. sx _ 0]. sx + bbW > srcWidth ifTrue: [bbW _ bbW - (sx + bbW - srcWidth)]. sy < 0 ifTrue: [dy _ dy - sy. bbH _ bbH + sy. sy _ 0]. sy + bbH > srcHeight ifTrue: [bbH _ bbH - (sy + bbH - srcHeight)]! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 2/20/2000 19:43'! copyBits "This function is exported for the Balloon engine" self export: true. self inline: true. self clipRange. (bbW <= 0 or: [bbH <= 0]) ifTrue: ["zero width or height; noop" affectedL _ affectedR _ affectedT _ affectedB _ 0. ^ nil]. "Lock the surfaces" self lockSurfaces ifFalse:[^interpreterProxy primitiveFail]. self copyBitsLockedAndClipped. self unlockSurfaces.! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 2/20/2000 19:42'! copyBitsFrom: startX to: stopX at: yValue "Support for the balloon engine." self export: true. destX _ startX. destY _ yValue. sourceX _ startX. width _ (stopX - startX). self copyBits. self showDisplayBits.! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 2/19/2000 20:58'! copyBitsLockedAndClipped "Perform the actual copyBits operation. Assume: Surfaces have been locked and clipping was performed." | done | self inline: true. "Try a shortcut for stuff that should be run as quickly as possible" done _ self tryCopyingBitsQuickly. done ifTrue:[^nil]. (combinationRule = 30) | (combinationRule = 31) ifTrue: ["Check and fetch source alpha parameter for alpha blend" interpreterProxy methodArgumentCount = 1 ifTrue: [sourceAlpha _ interpreterProxy stackIntegerValue: 0. (interpreterProxy failed not and: [(sourceAlpha >= 0) & (sourceAlpha <= 255)]) ifTrue: [interpreterProxy pop: 1] ifFalse: [^ interpreterProxy primitiveFail]] ifFalse: [^ interpreterProxy primitiveFail]]. bitCount _ 0. "Choose and perform the actual copy loop." self performCopyLoop. (combinationRule = 22) | (combinationRule = 32) ifTrue: ["zero width and height; return the count" affectedL _ affectedR _ affectedT _ affectedB _ 0. interpreterProxy pop: 1. ^ interpreterProxy pushInteger: bitCount]. hDir > 0 ifTrue: [affectedL _ dx. affectedR _ dx + bbW] ifFalse: [affectedL _ dx - bbW + 1. affectedR _ dx + 1]. vDir > 0 ifTrue: [affectedT _ dy. affectedB _ dy + bbH] ifFalse: [affectedT _ dy - bbH + 1. affectedB _ dy + 1]! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/23/1999 20:36'! destMaskAndPointerInit "Compute masks for left and right destination words" | startBits pixPerM1 endBits | self inline: true. pixPerM1 _ pixPerWord - 1. "A mask, assuming power of two" "how many pixels in first word" startBits _ pixPerWord - (dx bitAnd: pixPerM1). mask1 _ AllOnes >> (32 - (startBits*destPixSize)). "how many pixels in last word" endBits _ ((dx + bbW - 1) bitAnd: pixPerM1) + 1. mask2 _ AllOnes << (32 - (endBits*destPixSize)). "determine number of words stored per line; merge masks if only 1" bbW < startBits ifTrue: [mask1 _ mask1 bitAnd: mask2. mask2 _ 0. nWords _ 1] ifFalse: [nWords _ (bbW - startBits) + pixPerM1 // pixPerWord + 1]. hDir _ vDir _ 1. "defaults for no overlap with source" "calculate byte addr and delta, based on first word of data" "Note pitch is bytes and nWords is longs, not bytes" destIndex _ destBits + (dy * destPitch) + ((dx // pixPerWord) *4). destDelta _ destPitch * vDir - (4 * (nWords * hDir)). "byte addr delta" ! ! !BitBltSimulation methodsFor: 'setup'! ignoreSourceOrHalftone: formPointer formPointer = interpreterProxy nilObject ifTrue: [ ^true ]. combinationRule = 0 ifTrue: [ ^true ]. combinationRule = 5 ifTrue: [ ^true ]. combinationRule = 10 ifTrue: [ ^true ]. combinationRule = 15 ifTrue: [ ^true ]. ^false! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/25/1999 21:56'! performCopyLoop "Based on the values provided during setup choose and perform the appropriate inner loop function." self inline: true. "Should be inlined into caller for speed" self destMaskAndPointerInit. noSource ifTrue: ["Simple fill loop" self copyLoopNoSource. ] ifFalse: ["Loop using source and dest" self checkSourceOverlap. (sourcePixSize ~= destPixSize or: [colorMap ~= nil]) ifTrue: [ "If we must convert between pixel depths or use color lookups use the general version" self copyLoopPixMap. ] ifFalse: [ "Otherwise we simple copy pixels and can use a faster version" self sourceSkewAndPointerInit. self copyLoop. ] ]. ! ! !BitBltSimulation methodsFor: 'setup'! returnAt: stopIndex lastIndex: lastIndex left: left top: top stopCode _ interpreterProxy stObject: scanStopArray at: stopIndex. interpreterProxy failed ifTrue: [^ nil]. interpreterProxy storeInteger: BBLastIndex ofObject: bitBltOop withValue: lastIndex. scanDisplayFlag ifTrue: [ "Now we know extent of affected rectangle" affectedL _ left. affectedR _ bbW + dx. affectedT _ top. affectedB _ bbH + dy. ].! ! !BitBltSimulation methodsFor: 'setup' stamp: 'di 1/4/2000 14:19'! scanCharactersLockedAndClipped "Perform the actual scanCharacters operation. Assume: Surfaces have been locked and clipping was performed." | left top lastIndex charVal ascii sourceX2 nextDestX | self inline: true. scanDisplayFlag ifTrue: [left _ dx. top _ dy]. lastIndex _ scanStart. [lastIndex <= scanStop] whileTrue: [ charVal _ interpreterProxy stObject: scanString at: lastIndex. ascii _ interpreterProxy integerValueOf: charVal. interpreterProxy failed ifTrue: [^ nil]. stopCode _ interpreterProxy stObject: scanStopArray at: ascii + 1. interpreterProxy failed ifTrue: [^ nil]. stopCode = interpreterProxy nilObject ifFalse: [^ self returnAt: ascii + 1 lastIndex: lastIndex left: left top: top]. sourceX _ interpreterProxy stObject: scanXTable at: ascii + 1. sourceX2 _ interpreterProxy stObject: scanXTable at: ascii + 2. interpreterProxy failed ifTrue: [^ nil]. (interpreterProxy isIntegerObject: sourceX) & (interpreterProxy isIntegerObject: sourceX2) ifTrue: [sourceX _ interpreterProxy integerValueOf: sourceX. sourceX2 _ interpreterProxy integerValueOf: sourceX2] ifFalse: [interpreterProxy primitiveFail. ^ nil]. nextDestX _ destX + (width _ sourceX2 - sourceX). nextDestX > scanRightX ifTrue: [^ self returnAt: CrossedX lastIndex: lastIndex left: left top: top]. (scanDisplayFlag) ifTrue:[ self clipRange. "Must clip again" (bbW > 0 and:[bbH > 0]) ifTrue: [self copyBitsLockedAndClipped]. ]. destX _ nextDestX. interpreterProxy storeInteger: BBDestXIndex ofObject: bitBltOop withValue: destX. lastIndex _ lastIndex + 1]. self returnAt: EndOfRun lastIndex: scanStop left: left top: top! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/23/1999 20:38'! sourceSkewAndPointerInit "This is only used when source and dest are same depth, ie, when the barrel-shift copy loop is used." | dWid sxLowBits dxLowBits pixPerM1 | self inline: true. pixPerM1 _ pixPerWord - 1. "A mask, assuming power of two" sxLowBits _ sx bitAnd: pixPerM1. dxLowBits _ dx bitAnd: pixPerM1. "check if need to preload buffer (i.e., two words of source needed for first word of destination)" hDir > 0 ifTrue: ["n Bits stored in 1st word of dest" dWid _ bbW min: pixPerWord - dxLowBits. preload _ (sxLowBits + dWid) > pixPerM1] ifFalse: [dWid _ bbW min: dxLowBits + 1. preload _ (sxLowBits - dWid + 1) < 0]. "calculate right-shift skew from source to dest" skew _ (sxLowBits - dxLowBits) * destPixSize. " -32..32 " preload ifTrue: [skew < 0 ifTrue: [skew _ skew+32] ifFalse: [skew _ skew-32]]. "Calc byte addr and delta from longWord info" sourceIndex _ sourceBits + (sy * sourcePitch) + ((sx // (32//sourcePixSize)) *4). "calculate increments from end of 1 line to start of next" sourceDelta _ (sourcePitch * vDir) - (4 * (nWords * hDir)). preload ifTrue: ["Compensate for extra source word fetched" sourceDelta _ sourceDelta - (4*hDir)].! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/23/1999 20:40'! tryCopyingBitsQuickly "Shortcut for stuff that's being run from the balloon engine. Since we do this at each scan line we should avoid the expensive setup for source and destination." self inline: true. "We need a source." noSource ifTrue:[^false]. "We handle only combinationRule 34" (combinationRule = 34) ifFalse:[^false]. "We handle only sourcePixSize 32" (sourcePixSize = 32) ifFalse:[^false]. "We don't handle overlaps" (sourceForm = destForm) ifTrue:[^false]. "We need at least 8bit deep dest forms" (destPixSize < 8) ifTrue:[^false]. "If 8bit, then we want a color map" (destPixSize = 8 and:[colorMap = nil]) ifTrue:[^false]. destPixSize = 32 ifTrue:[self alphaSourceBlendBits32]. destPixSize = 16 ifTrue:[self alphaSourceBlendBits16]. destPixSize = 8 ifTrue:[self alphaSourceBlendBits8]. affectedL _ dx. affectedR _ dx + bbW. affectedT _ dy. affectedB _ dy + bbH. ^true! ! !BitBltSimulation methodsFor: 'setup' stamp: 'ar 10/27/1999 17:05'! warpBits | ns | self inline: true. ns _ noSource. noSource _ true. self clipRange. "noSource suppresses sourceRect clipping" noSource _ ns. (noSource or: [bbW <= 0 or: [bbH <= 0]]) ifTrue: ["zero width or height; noop" affectedL _ affectedR _ affectedT _ affectedB _ 0. ^ nil]. self lockSurfaces. self destMaskAndPointerInit. self xWarpLoop. hDir > 0 ifTrue: [affectedL _ dx. affectedR _ dx + bbW] ifFalse: [affectedL _ dx - bbW + 1. affectedR _ dx + 1]. vDir > 0 ifTrue: [affectedT _ dy. affectedB _ dy + bbH] ifFalse: [affectedT _ dy - bbH + 1. affectedB _ dy + 1]. self unlockSurfaces.! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 10/28/1999 19:52'! alphaSourceBlendBits16 "This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 16 sourceForm ~= destForm. " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY dstMask srcShift ditherBase ditherIndex ditherThreshold | self inline: false. "This particular method should be optimized in itself" deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. (dx bitAnd: 1) = 0 ifTrue:[ mask1 _ 16r0000FFFF. srcShift _ 16] ifFalse:[mask1 _ 16rFFFF0000. srcShift _ 0]. "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx // 2 * 4). ditherBase _ (dstY bitAnd: 3) * 4. ditherIndex _ (sx bitAnd: 3) - 1. "For pre-increment" deltaX _ bbW + 1. "So we can pre-decrement" dstMask _ mask1. dstMask = 16rFFFF ifTrue:[srcShift _ 16] ifFalse:[srcShift _ 0]. "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ ditherThreshold _ ditherMatrix4x4 at: ditherBase + (ditherIndex _ ditherIndex + 1 bitAnd: 3). sourceWord _ self srcLongAt: srcIndex. srcAlpha _ sourceWord >> 24. srcAlpha = 255 ifTrue:[ "Dither from 32 to 16 bit" sourceWord _ self dither32To16: sourceWord threshold: ditherThreshold. sourceWord = 0 ifTrue:[sourceWord _ 1]. sourceWord _ sourceWord << srcShift. "Store masked value" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ] ifFalse:[ "srcAlpha ~= 255" srcAlpha = 0 ifTrue:[ ] ifFalse:[ "0 < srcAlpha < 255" "If we have to mix colors then just copy a single word" destWord _ self dstLongAt: dstIndex. destWord _ destWord bitAnd: dstMask bitInvert32. destWord _ destWord >> srcShift. "Expand from 16 to 32 bit by adding zero bits" destWord _ (((destWord bitAnd: 16r7C00) bitShift: 9) bitOr: ((destWord bitAnd: 16r3E0) bitShift: 6)) bitOr: (((destWord bitAnd: 16r1F) bitShift: 3) bitOr: 16rFF000000). "Mix colors" sourceWord _ self alphaBlendScaled: sourceWord with: destWord. "And dither" sourceWord _ self dither32To16: sourceWord threshold: ditherThreshold. sourceWord = 0 ifTrue:[sourceWord _ 1]. sourceWord _ sourceWord << srcShift. "Store back" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ]. ]. srcIndex _ srcIndex + 4. srcShift = 0 ifTrue:[dstIndex _ dstIndex + 4]. srcShift _ srcShift bitXor: 16. "Toggle between 0 and 16" dstMask _ dstMask bitInvert32. "Mask other half word" ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 10/25/1999 19:16'! alphaSourceBlendBits32 "This version assumes combinationRule = 34 sourcePixSize = destPixSize = 32 sourceForm ~= destForm. Note: The inner loop has been optimized for dealing with the special cases of srcAlpha = 0.0 and srcAlpha = 1.0 " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY | self inline: false. "This particular method should be optimized in itself" "Give the compile a couple of hints" self var: #sourceWord declareC:'register int sourceWord'. self var: #deltaX declareC:'register int deltaX'. "The following should be declared as pointers so the compiler will notice that they're used for accessing memory locations (good to know on an Intel architecture) but then the increments would be different between ST code and C code so must hope the compiler notices what happens (MS Visual C does)" self var: #srcIndex declareC:'register int srcIndex'. self var: #dstIndex declareC:'register int dstIndex'. deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx * 4). deltaX _ bbW + 1. "So we can pre-decrement" "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ sourceWord _ self srcLongAt: srcIndex. srcAlpha _ sourceWord >> 24. srcAlpha = 255 ifTrue:[ self dstLongAt: dstIndex put: sourceWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. "Now copy as many words as possible with alpha = 255" [(deltaX _ deltaX - 1) ~= 0 and:[ (sourceWord _ self srcLongAt: srcIndex) >> 24 = 255]] whileTrue:[ self dstLongAt: dstIndex put: sourceWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. "Adjust deltaX" deltaX _ deltaX + 1. ] ifFalse:[ "srcAlpha ~= 255" srcAlpha = 0 ifTrue:[ srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. "Now skip as many words as possible," [(deltaX _ deltaX - 1) ~= 0 and:[ (sourceWord _ self srcLongAt: srcIndex) >> 24 = 0]] whileTrue:[ srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. "Adjust deltaX" deltaX _ deltaX + 1. ] ifFalse:[ "0 < srcAlpha < 255" "If we have to mix colors then just copy a single word" destWord _ self dstLongAt: dstIndex. destWord _ self alphaBlendScaled: sourceWord with: destWord. self dstLongAt: dstIndex put: destWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. ]. ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 10/28/1999 19:53'! alphaSourceBlendBits8 "This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 8 sourceForm ~= destForm. Note: This is not real blending since we don't have the source colors available. " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY dstMask srcShift adjust mappingTable | self inline: false. "This particular method should be optimized in itself" self var: #mappingTable declareC:'unsigned int *mappingTable'. mappingTable _ self default8To32Table. deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. mask1 _ 24 - ((dx bitAnd: 3) * 8). mask2 _ AllOnes bitXor:(16rFF << mask1). (dx bitAnd: 1) = 0 ifTrue:[adjust _ 0] ifFalse:[adjust _ 16r1F1F1F1F]. (dy bitAnd: 1) = 0 ifTrue:[adjust _ adjust bitXor: 16r1F1F1F1F]. "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ adjust _ adjust bitXor: 16r1F1F1F1F. srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx // 4 * 4). deltaX _ bbW + 1. "So we can pre-decrement" srcShift _ mask1. dstMask _ mask2. "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ sourceWord _ ((self srcLongAt: srcIndex) bitAnd: (adjust bitInvert32)) + adjust. srcAlpha _ sourceWord >> 24. srcAlpha > 31 ifTrue:["Everything below 31 is transparent" srcAlpha < 224 ifTrue:["Everything above 224 is opaque" destWord _ self dstLongAt: dstIndex. destWord _ destWord bitAnd: dstMask bitInvert32. destWord _ destWord >> srcShift. destWord _ mappingTable at: destWord. sourceWord _ self alphaBlendScaled: sourceWord with: destWord. ]. sourceWord _ self rgbMap: sourceWord from: 8 to: cmBitsPerColor. sourceWord _ self colormapAt: sourceWord. sourceWord _ sourceWord << srcShift. "Store back" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ]. srcIndex _ srcIndex + 4. srcShift = 0 ifTrue:[ dstIndex _ dstIndex + 4. srcShift _ 24. dstMask _ 16r00FFFFFF. ] ifFalse:[ srcShift _ srcShift - 8. dstMask _ (dstMask >> 8) bitOr: 16rFF000000. ]. adjust _ adjust bitXor: 16r1F1F1F1F. ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'di 3/21/2000 09:11'! copyLoop | prevWord thisWord skewWord halftoneWord mergeWord hInc y unskew skewMask notSkewMask mergeFnwith destWord | "This version of the inner loop assumes noSource = false." self inline: false. self var: #mergeFnwith declareC: 'int (*mergeFnwith)(int, int)'. mergeFnwith _ self cCoerce: (opTable at: combinationRule+1) to: 'int (*)(int, int)'. mergeFnwith. "null ref for compiler" hInc _ hDir*4. "Byte delta" "degenerate skew fixed for Sparc. 10/20/96 ikp" skew == -32 ifTrue: [skew _ unskew _ skewMask _ 0] ifFalse: [skew < 0 ifTrue: [unskew _ skew+32. skewMask _ AllOnes << (0-skew)] ifFalse: [skew = 0 ifTrue: [unskew _ 0. skewMask _ AllOnes] ifFalse: [unskew _ skew-32. skewMask _ AllOnes >> skew]]]. notSkewMask _ skewMask bitInvert32. noHalftone ifTrue: [halftoneWord _ AllOnes. halftoneHeight _ 0] ifFalse: [halftoneWord _ self halftoneAt: 0]. y _ dy. 1 to: bbH do: "here is the vertical loop" [ :i | halftoneHeight > 1 ifTrue: "Otherwise, its always the same" [halftoneWord _ self halftoneAt: y. y _ y + vDir]. preload ifTrue: ["load the 64-bit shifter" prevWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc] ifFalse: [prevWord _ 0]. "Note: the horizontal loop has been expanded into three parts for speed:" "This first section requires masking of the destination store..." destMask _ mask1. thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: destWord. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + hInc. "This central horizontal loop requires no store masking" destMask _ AllOnes. combinationRule = 3 ifTrue: [(skew = 0) & (halftoneWord = AllOnes) ifTrue: ["Very special inner loop for STORE mode with no skew -- just move words" 2 to: nWords-1 do: [ :word | "Note loop starts with prevWord loaded (due to preload)" self dstLongAt: destIndex put: prevWord. destIndex _ destIndex + hInc. prevWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc]] ifFalse: ["Special inner loop for STORE mode -- no need to call merge" 2 to: nWords-1 do: [ :word | thisWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. self dstLongAt: destIndex put: (skewWord bitAnd: halftoneWord). destIndex _ destIndex + hInc]] ] ifFalse: [2 to: nWords-1 do: "Normal inner loop does merge:" [ :word | thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: (self dstLongAt: destIndex). self dstLongAt: destIndex put: mergeWord. destIndex _ destIndex + hInc] ]. "This last section, if used, requires masking of the destination store..." nWords > 1 ifTrue: [destMask _ mask2. thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: destWord. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + hInc]. sourceIndex _ sourceIndex + sourceDelta. destIndex _ destIndex + destDelta]! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 12/7/1999 21:37'! copyLoopNoSource | halftoneWord mergeWord mergeFnwith destWord | "Faster copyLoop when source not used. hDir and vDir are both positive, and perload and skew are unused" self inline: false. self var: #mergeFnwith declareC: 'int (*mergeFnwith)(int, int)'. mergeFnwith _ self cCoerce: (opTable at: combinationRule+1) to: 'int (*)(int, int)'. mergeFnwith. "null ref for compiler" 1 to: bbH do: "here is the vertical loop" [ :i | noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. "Note: the horizontal loop has been expanded into three parts for speed:" "This first section requires masking of the destination store..." destMask _ mask1. destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: halftoneWord with: destWord. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4. "This central horizontal loop requires no store masking" destMask _ AllOnes. combinationRule = 3 ifTrue: ["Special inner loop for STORE" destWord _ halftoneWord. 2 to: nWords-1 do:[ :word | self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4]. ] ifFalse:[ "Normal inner loop does merge" 2 to: nWords-1 do:[ :word | "Normal inner loop does merge" destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: halftoneWord with: destWord. self dstLongAt: destIndex put: mergeWord. destIndex _ destIndex + 4]. ]. "This last section, if used, requires masking of the destination store..." nWords > 1 ifTrue: [destMask _ mask2. destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: halftoneWord with: destWord. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4]. destIndex _ destIndex + destDelta]! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 12/7/1999 20:58'! copyLoopPixMap "This version of the inner loop maps source pixels to a destination form with different depth. Because it is already unweildy, the loop is not unrolled as in the other versions. Preload, skew and skewMask are all overlooked, since pickSourcePixels delivers its destination word already properly aligned. Note that pickSourcePixels could be copied in-line at the top of the horizontal loop, and some of its inits moved out of the loop." "ar 12/7/1999: The loop has been rewritten to use only one pickSourcePixels call. The idea is that the call itself could be inlined. If we decide not to inline pickSourcePixels we could optimize the loop instead." | skewWord halftoneWord mergeWord srcPixPerWord scrStartBits nSourceIncs startBits endBits sourcePixMask destPixMask nullMap mergeFnwith nPix srcShift dstShift destWord words | self inline: false. self var: #mergeFnwith declareC: 'int (*mergeFnwith)(int, int)'. mergeFnwith _ self cCoerce: (opTable at: combinationRule+1) to: 'int (*)(int, int)'. mergeFnwith. "null ref for compiler" "Additional inits peculiar to unequal source and dest pix size..." srcPixPerWord _ 32//sourcePixSize. sourcePixMask _ maskTable at: sourcePixSize. destPixMask _ maskTable at: destPixSize. nullMap _ colorMap = nil. sourceIndex _ sourceBits + (sy * sourcePitch) + ((sx // srcPixPerWord) *4). scrStartBits _ srcPixPerWord - (sx bitAnd: srcPixPerWord-1). bbW < scrStartBits ifTrue: [nSourceIncs _ 0] ifFalse: [nSourceIncs _ (bbW - scrStartBits)//srcPixPerWord + 1]. sourceDelta _ sourcePitch - (nSourceIncs * 4). "Note following two items were already calculated in destmask setup!!" startBits _ pixPerWord - (dx bitAnd: pixPerWord-1). endBits _ ((dx + bbW - 1) bitAnd: pixPerWord-1) + 1. bbW < startBits ifTrue:[startBits _ bbW]. "Precomputed shifts for pickSourcePixels" srcShift _ 32 - ((sx bitAnd: srcPixPerWord - 1) + 1 * sourcePixSize). dstShift _ 32 - ((dx bitAnd: pixPerWord - 1) + 1 * destPixSize). 1 to: bbH do: "here is the vertical loop" [ :i | "*** is it possible at all that noHalftone == false? ***" noHalftone ifTrue:[halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. "setup first load" srcBitShift _ srcShift. dstBitShift _ dstShift. destMask _ mask1. nPix _ startBits. "Here is the horizontal loop..." words _ nWords. ["pick up the word" skewWord _ self pickSourcePixels: nPix nullMap: nullMap srcMask: sourcePixMask destMask: destPixMask. destMask = AllOnes ifTrue:["avoid read-modify-write" mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: (self dstLongAt: destIndex). self dstLongAt: destIndex put: (destMask bitAnd: mergeWord). ] ifFalse:[ "General version using dest masking" destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: (destWord bitAnd: destMask). destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. ]. destIndex _ destIndex + 4. words = 2 "e.g., is the next word the last word?" ifTrue:["set mask for last word in this row" destMask _ mask2. nPix _ endBits] ifFalse:["use fullword mask for inner loop" destMask _ AllOnes. nPix _ pixPerWord]. (words _ words - 1) = 0] whileFalse. "--- end of inner loop ---" sourceIndex _ sourceIndex + sourceDelta. destIndex _ destIndex + destDelta] ! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 2/19/2000 21:24'! warpLoop "ar 12/7/1999: This version is unused but kept as reference implemenation" "This version of the inner loop traverses an arbirary quadrilateral source, thus producing a general affine transformation." | skewWord halftoneWord mergeWord startBits deltaP12x deltaP12y deltaP43x deltaP43y pAx pAy xDelta yDelta pBx pBy smoothingCount sourceMapOop nSteps t | self inline: false. (interpreterProxy slotSizeOf: bitBltOop) >= (BBWarpBase+12) ifFalse: [^ interpreterProxy primitiveFail]. nSteps _ height-1. nSteps <= 0 ifTrue: [nSteps _ 1]. pAx _ self fetchIntOrFloat: BBWarpBase ofObject: bitBltOop. t _ self fetchIntOrFloat: BBWarpBase+3 ofObject: bitBltOop. deltaP12x _ self deltaFrom: pAx to: t nSteps: nSteps. deltaP12x < 0 ifTrue: [pAx _ t - (nSteps*deltaP12x)]. pAy _ self fetchIntOrFloat: BBWarpBase+1 ofObject: bitBltOop. t _ self fetchIntOrFloat: BBWarpBase+4 ofObject: bitBltOop. deltaP12y _ self deltaFrom: pAy to: t nSteps: nSteps. deltaP12y < 0 ifTrue: [pAy _ t - (nSteps*deltaP12y)]. pBx _ self fetchIntOrFloat: BBWarpBase+9 ofObject: bitBltOop. t _ self fetchIntOrFloat: BBWarpBase+6 ofObject: bitBltOop. deltaP43x _ self deltaFrom: pBx to: t nSteps: nSteps. deltaP43x < 0 ifTrue: [pBx _ t - (nSteps*deltaP43x)]. pBy _ self fetchIntOrFloat: BBWarpBase+10 ofObject: bitBltOop. t _ self fetchIntOrFloat: BBWarpBase+7 ofObject: bitBltOop. deltaP43y _ self deltaFrom: pBy to: t nSteps: nSteps. deltaP43y < 0 ifTrue: [pBy _ t - (nSteps*deltaP43y)]. interpreterProxy failed ifTrue: [^ false]. "ie if non-integers above" interpreterProxy methodArgumentCount = 2 ifTrue: [smoothingCount _ interpreterProxy stackIntegerValue: 1. sourceMapOop _ interpreterProxy stackValue: 0. sourceMapOop = interpreterProxy nilObject ifTrue: [sourcePixSize < 16 ifTrue: ["color map is required to smooth non-RGB dest" ^ interpreterProxy primitiveFail]] ifFalse: [(interpreterProxy slotSizeOf: sourceMapOop) < (1 << sourcePixSize) ifTrue: ["sourceMap must be long enough for sourcePixSize" ^ interpreterProxy primitiveFail]]] ifFalse: [smoothingCount _ 1. sourceMapOop _ interpreterProxy nilObject]. startBits _ pixPerWord - (dx bitAnd: pixPerWord-1). nSteps _ width-1. nSteps <= 0 ifTrue: [nSteps _ 1]. destY to: clipY-1 do: [ :i | "Advance increments if there was clipping in y" pAx _ pAx + deltaP12x. pAy _ pAy + deltaP12y. pBx _ pBx + deltaP43x. pBy _ pBy + deltaP43y]. 1 to: bbH do: [ :i | "here is the vertical loop..." xDelta _ self deltaFrom: pAx to: pBx nSteps: nSteps. xDelta >= 0 ifTrue: [sx _ pAx] ifFalse: [sx _ pBx - (nSteps*xDelta)]. yDelta _ self deltaFrom: pAy to: pBy nSteps: nSteps. yDelta >= 0 ifTrue: [sy _ pAy] ifFalse: [sy _ pBy - (nSteps*yDelta)]. destX to: clipX-1 do: [:word | "Advance increments if there was clipping in x" sx _ sx + xDelta. sy _ sy + yDelta]. noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. destMask _ mask1. "pick up first word" bbW < startBits ifTrue: [skewWord _ self warpSourcePixels: bbW xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y smoothing: smoothingCount sourceMap: sourceMapOop. skewWord _ skewWord bitShift: (startBits - bbW)*destPixSize] ifFalse: [skewWord _ self warpSourcePixels: startBits xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y smoothing: smoothingCount sourceMap: sourceMapOop]. 1 to: nWords do: [ :word | "here is the inner horizontal loop..." mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: ((self dstLongAt: destIndex) bitAnd: destMask). self dstLongAt: destIndex put: (destMask bitAnd: mergeWord) mask: destMask bitInvert32. destIndex _ destIndex + 4. word >= (nWords - 1) ifTrue: [word = nWords ifFalse: ["set mask for last word in this row" destMask _ mask2. skewWord _ self warpSourcePixels: pixPerWord xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y smoothing: smoothingCount sourceMap: sourceMapOop]] ifFalse: ["use fullword mask for inner loop" destMask _ AllOnes. skewWord _ self warpSourcePixels: pixPerWord xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y smoothing: smoothingCount sourceMap: sourceMapOop]. ]. pAx _ pAx + deltaP12x. pAy _ pAy + deltaP12y. pBx _ pBx + deltaP43x. pBy _ pBy + deltaP43y. destIndex _ destIndex + destDelta]! ! !BitBltSimulation methodsFor: 'inner loop' stamp: 'ar 2/19/2000 21:24'! xWarpLoop "This version of the inner loop traverses an arbirary quadrilateral source, thus producing a general affine transformation." | skewWord halftoneWord mergeWord startBits deltaP12x deltaP12y deltaP43x deltaP43y pAx pAy pBx pBy xDelta yDelta smoothingCount sourceMapOop nSteps nPix words destWord endBits mergeFnwith | self inline: false. self var: #mergeFnwith declareC: 'int (*mergeFnwith)(int, int)'. mergeFnwith _ self cCoerce: (opTable at: combinationRule+1) to: 'int (*)(int, int)'. mergeFnwith. "null ref for compiler" (interpreterProxy slotSizeOf: bitBltOop) >= (BBWarpBase+12) ifFalse: [^ interpreterProxy primitiveFail]. nSteps _ height-1. nSteps <= 0 ifTrue: [nSteps _ 1]. pAx _ self fetchIntOrFloat: BBWarpBase ofObject: bitBltOop. words _ self fetchIntOrFloat: BBWarpBase+3 ofObject: bitBltOop. deltaP12x _ self deltaFrom: pAx to: words nSteps: nSteps. deltaP12x < 0 ifTrue: [pAx _ words - (nSteps*deltaP12x)]. pAy _ self fetchIntOrFloat: BBWarpBase+1 ofObject: bitBltOop. words _ self fetchIntOrFloat: BBWarpBase+4 ofObject: bitBltOop. deltaP12y _ self deltaFrom: pAy to: words nSteps: nSteps. deltaP12y < 0 ifTrue: [pAy _ words - (nSteps*deltaP12y)]. pBx _ self fetchIntOrFloat: BBWarpBase+9 ofObject: bitBltOop. words _ self fetchIntOrFloat: BBWarpBase+6 ofObject: bitBltOop. deltaP43x _ self deltaFrom: pBx to: words nSteps: nSteps. deltaP43x < 0 ifTrue: [pBx _ words - (nSteps*deltaP43x)]. pBy _ self fetchIntOrFloat: BBWarpBase+10 ofObject: bitBltOop. words _ self fetchIntOrFloat: BBWarpBase+7 ofObject: bitBltOop. deltaP43y _ self deltaFrom: pBy to: words nSteps: nSteps. deltaP43y < 0 ifTrue: [pBy _ words - (nSteps*deltaP43y)]. interpreterProxy failed ifTrue: [^ false]. "ie if non-integers above" interpreterProxy methodArgumentCount = 2 ifTrue: [smoothingCount _ interpreterProxy stackIntegerValue: 1. sourceMapOop _ interpreterProxy stackValue: 0. sourceMapOop = interpreterProxy nilObject ifTrue: [sourcePixSize < 16 ifTrue: ["color map is required to smooth non-RGB dest" ^ interpreterProxy primitiveFail]] ifFalse: [(interpreterProxy slotSizeOf: sourceMapOop) < (1 << sourcePixSize) ifTrue: ["sourceMap must be long enough for sourcePixSize" ^ interpreterProxy primitiveFail]. sourceMapOop _ self cCoerce: (interpreterProxy firstIndexableField: sourceMapOop) to:'int']] ifFalse: [smoothingCount _ 1. sourceMapOop _ interpreterProxy nilObject]. nSteps _ width-1. nSteps <= 0 ifTrue: [nSteps _ 1]. startBits _ pixPerWord - (dx bitAnd: pixPerWord-1). endBits _ ((dx + bbW - 1) bitAnd: pixPerWord-1) + 1. bbW < startBits ifTrue:[startBits _ bbW]. destY < clipY ifTrue:[ "Advance increments if there was clipping in y" pAx _ pAx + (clipY - destY * deltaP12x). pAy _ pAy + (clipY - destY * deltaP12y). pBx _ pBx + (clipY - destY * deltaP43x). pBy _ pBy + (clipY - destY * deltaP43y)]. "Setup values for faster pixel fetching. Note: this should really go into a separate method since it only sets up globals so there is no need to have it in this method." "warpSrcShift = log2(sourcePixSize)" warpSrcShift _ 0. words _ sourcePixSize. "recycle temp" [words = 1] whileFalse:[ warpSrcShift _ warpSrcShift + 1. words _ words >> 1]. "warpSrcMask = mask for extracting one pixel from source word" warpSrcMask _ maskTable at: sourcePixSize. "warpAlignShift: Shift for aligning x position to word boundary" warpAlignShift _ 5 - warpSrcShift. "warpAlignMask: Mask for extracting the pixel position from an x position" warpAlignMask _ 1 << warpAlignShift - 1. "Setup the lookup table for source bit shifts" "warpBitShiftTable: given an sub-word x value what's the bit shift?" 0 to: warpAlignMask do:[:i| warpBitShiftTable at: i put: 32 - ( i + 1 << warpSrcShift )]. 1 to: bbH do: [ :i | "here is the vertical loop..." xDelta _ self deltaFrom: pAx to: pBx nSteps: nSteps. xDelta >= 0 ifTrue: [sx _ pAx] ifFalse: [sx _ pBx - (nSteps*xDelta)]. yDelta _ self deltaFrom: pAy to: pBy nSteps: nSteps. yDelta >= 0 ifTrue: [sy _ pAy] ifFalse: [sy _ pBy - (nSteps*yDelta)]. dstBitShift _ 32 - ((dx bitAnd: pixPerWord - 1) + 1 * destPixSize). (destX < clipX) ifTrue:[ "Advance increments if there was clipping in x" sx _ sx + (clipX - destX * xDelta). sy _ sy + (clipX - destX * yDelta). ]. noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. destMask _ mask1. nPix _ startBits. "Here is the inner loop..." words _ nWords. ["pick up word" smoothingCount = 1 ifTrue:["Faster if not smoothing" skewWord _ self warpPickSourcePixels: nPix xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y. ] ifFalse:["more difficult with smoothing" skewWord _ self warpPickSmoothPixels: nPix xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y sourceMap: sourceMapOop smoothing: smoothingCount. ]. destMask = AllOnes ifTrue:["avoid read-modify-write" mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: (self dstLongAt: destIndex). self dstLongAt: destIndex put: (destMask bitAnd: mergeWord). ] ifFalse:[ "General version using dest masking" destWord _ self dstLongAt: destIndex. mergeWord _ self mergeFn: (skewWord bitAnd: halftoneWord) with: (destWord bitAnd: destMask). destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. ]. destIndex _ destIndex + 4. words = 2 "e.g., is the next word the last word?" ifTrue:["set mask for last word in this row" destMask _ mask2. nPix _ endBits] ifFalse:["use fullword mask for inner loop" destMask _ AllOnes. nPix _ pixPerWord]. (words _ words - 1) = 0] whileFalse. "--- end of inner loop ---" pAx _ pAx + deltaP12x. pAy _ pAy + deltaP12y. pBx _ pBx + deltaP43x. pBy _ pBy + deltaP43y. destIndex _ destIndex + destDelta]! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:11'! OLDrgbDiff: sourceWord with: destinationWord "Subract the pixels in the source and destination, color by color, and return the sum of the absolute value of all the differences. For non-rgb, XOR the two and return the number of differing pixels. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes." | diff pixMask | self inline: false. destPixSize < 16 ifTrue: ["Just xor and count differing bits if not RGB" diff _ sourceWord bitXor: destinationWord. pixMask _ maskTable at: destPixSize. [diff = 0] whileFalse: [(diff bitAnd: pixMask) ~= 0 ifTrue: [bitCount _ bitCount + 1]. diff _ diff >> destPixSize]. ^ destinationWord "for no effect"]. destPixSize = 16 ifTrue: [diff _ (self partitionedSub: sourceWord from: destinationWord nBits: 5 nPartitions: 3). bitCount _ bitCount + (diff bitAnd: 16r1F) + (diff>>5 bitAnd: 16r1F) + (diff>>10 bitAnd: 16r1F). diff _ (self partitionedSub: sourceWord>>16 from: destinationWord>>16 nBits: 5 nPartitions: 3). bitCount _ bitCount + (diff bitAnd: 16r1F) + (diff>>5 bitAnd: 16r1F) + (diff>>10 bitAnd: 16r1F)] ifFalse: [diff _ (self partitionedSub: sourceWord from: destinationWord nBits: 8 nPartitions: 3). bitCount _ bitCount + (diff bitAnd: 16rFF) + (diff>>8 bitAnd: 16rFF) + (diff>>16 bitAnd: 16rFF)]. ^ destinationWord "For no effect on dest"! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 10/23/1999 20:44'! OLDtallyIntoMap: sourceWord with: destinationWord "Tally pixels into the color map. Note that the source should be specified = destination, in order for the proper color map checks to be performed at setup. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes." | mapIndex pixMask shiftWord | colorMap = nil ifTrue: [^ destinationWord "no op"]. destPixSize < 16 ifTrue: ["loop through all packed pixels." pixMask _ maskTable at: destPixSize. shiftWord _ destinationWord. 1 to: pixPerWord do: [:i | mapIndex _ shiftWord bitAnd: pixMask. self colormapAt: mapIndex put: (self colormapAt: mapIndex) + 1. shiftWord _ shiftWord >> destPixSize]. ^ destinationWord]. destPixSize = 16 ifTrue: ["Two pixels Tally the right half..." mapIndex _ self rgbMap: (destinationWord bitAnd: 16rFFFF) from: 5 to: cmBitsPerColor. self colormapAt: mapIndex put: (self colormapAt: mapIndex) + 1. "... and then left half" mapIndex _ self rgbMap: destinationWord>>16 from: 5 to: cmBitsPerColor. self colormapAt: mapIndex put: (self colormapAt: mapIndex) + 1] ifFalse: ["Just one pixel." mapIndex _ self rgbMap: destinationWord from: 8 to: cmBitsPerColor. self colormapAt: mapIndex put: (self colormapAt: mapIndex) + 1]. ^ destinationWord "For no effect on dest"! ! !BitBltSimulation methodsFor: 'combination rules'! addWord: sourceWord with: destinationWord ^sourceWord + destinationWord! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'DSM 11/22/2000 13:45'! alphaBlend: sourceWord with: destinationWord "Blend sourceWord with destinationWord, assuming both are 32-bit pixels. The source is assumed to have 255*alpha in the high 8 bits of each pixel, while the high 8 bits of the destinationWord will be ignored. The blend produced is alpha*source + (1-alpha)*dest, with the computation being performed independently on each color component. The high byte of the result will be 0." | alpha unAlpha colorMask result blend shift | self inline: false. alpha _ sourceWord >> 24. "High 8 bits of source pixel" alpha = 0 ifTrue: [ ^ destinationWord ]. alpha = 255 ifTrue: [ ^ sourceWord ]. unAlpha _ 255 - alpha. colorMask _ 16rFF. result _ 0. "ar 9/9/2000 - include alpha in computation" 1 to: 4 do: [:i | shift _ (i-1)*8. blend _ (((sourceWord>>shift bitAnd: colorMask) * alpha) + ((destinationWord>>shift bitAnd: colorMask) * unAlpha)) + 254 // 255 bitAnd: colorMask. result _ result bitOr: blend<>shift bitAnd: rgbMask) * sourceAlpha) + ((destPixVal>>shift bitAnd: rgbMask) * unAlpha)) + 254 // 255 bitAnd: rgbMask. pixBlend _ pixBlend bitOr: blend<> destPixSize. sourceShifted _ sourceShifted >> destPixSize. destShifted _ destShifted >> destPixSize]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 11/27/1998 23:56'! alphaBlendScaled: sourceWord with: destinationWord "Blend sourceWord with destinationWord using the alpha value from sourceWord. Alpha is encoded as 0 meaning 0.0, and 255 meaning 1.0. In contrast to alphaBlend:with: the color produced is srcColor + (1-srcAlpha) * dstColor e.g., it is assumed that the source color is already scaled." | unAlpha dstMask srcMask b g r a | self inline: false. "Do NOT inline this into optimized loops" unAlpha _ 255 - (sourceWord >> 24). "High 8 bits of source pixel" dstMask _ destinationWord. srcMask _ sourceWord. b _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). b > 255 ifTrue:[b _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. g _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). g > 255 ifTrue:[g _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. r _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). r > 255 ifTrue:[r _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. a _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). a > 255 ifTrue:[a _ 255]. ^(((((a << 8) + r) << 8) + g) << 8) + b! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'di 6/29/1998 19:56'! alphaPaintConst: sourceWord with: destinationWord sourceWord = 0 ifTrue: [^ destinationWord "opt for all-transparent source"]. ^ self alphaBlendConst: sourceWord with: destinationWord paintMode: true! ! !BitBltSimulation methodsFor: 'combination rules'! bitAnd: sourceWord with: destinationWord ^sourceWord bitAnd: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! bitAndInvert: sourceWord with: destinationWord ^sourceWord bitAnd: destinationWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertAnd: sourceWord with: destinationWord ^sourceWord bitInvert32 bitAnd: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertAndInvert: sourceWord with: destinationWord ^sourceWord bitInvert32 bitAnd: destinationWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertDestination: sourceWord with: destinationWord ^destinationWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertOr: sourceWord with: destinationWord ^sourceWord bitInvert32 bitOr: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertOrInvert: sourceWord with: destinationWord ^sourceWord bitInvert32 bitOr: destinationWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertSource: sourceWord with: destinationWord ^sourceWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitInvertXor: sourceWord with: destinationWord ^sourceWord bitInvert32 bitXor: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! bitOr: sourceWord with: destinationWord ^sourceWord bitOr: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! bitOrInvert: sourceWord with: destinationWord ^sourceWord bitOr: destinationWord bitInvert32! ! !BitBltSimulation methodsFor: 'combination rules'! bitXor: sourceWord with: destinationWord ^sourceWord bitXor: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules'! clearWord: source with: destination ^ 0! ! !BitBltSimulation methodsFor: 'combination rules'! destinationWord: sourceWord with: destinationWord ^destinationWord! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'di 12/30/97 14:46'! merge: sourceWord with: destinationWord | mergeFnwith | "Sender warpLoop is too big to include this in-line" self var: #mergeFnwith declareC: 'int (*mergeFnwith)(int, int)'. mergeFnwith _ self cCoerce: (opTable at: combinationRule+1) to: 'int (*)(int, int)'. mergeFnwith. "null ref for compiler" ^ self mergeFn: sourceWord with: destinationWord! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:18'! partitionedAND: word1 to: word2 nBits: nBits nPartitions: nParts "AND word1 to word2 as nParts partitions of nBits each. Any field of word1 not all-ones is treated as all-zeroes. Used for erasing, eg, brush shapes prior to ORing in a color" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | (word1 bitAnd: mask) = mask ifTrue: [result _ result bitOr: (word2 bitAnd: mask)]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedAdd: word1 to: word2 nBits: nBits nPartitions: nParts "Add word1 to word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors" | mask sum result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | sum _ (word1 bitAnd: mask) + (word2 bitAnd: mask). sum <= mask "result must not carry out of partition" ifTrue: [result _ result bitOr: sum] ifFalse: [result _ result bitOr: mask]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedMax: word1 with: word2 nBits: nBits nPartitions: nParts "Max word1 to word2 as nParts partitions of nBits each" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | result _ result bitOr: ((word2 bitAnd: mask) max: (word1 bitAnd: mask)). mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedMin: word1 with: word2 nBits: nBits nPartitions: nParts "Min word1 to word2 as nParts partitions of nBits each" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | result _ result bitOr: ((word2 bitAnd: mask) min: (word1 bitAnd: mask)). mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:36'! partitionedSub: word1 from: word2 nBits: nBits nPartitions: nParts "Subtract word1 from word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors" | mask result p1 p2 | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | p1 _ word1 bitAnd: mask. p2 _ word2 bitAnd: mask. p1 < p2 "result is really abs value of thedifference" ifTrue: [result _ result bitOr: p2 - p1] ifFalse: [result _ result bitOr: p1 - p2]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !BitBltSimulation methodsFor: 'combination rules'! pixMask: sourceWord with: destinationWord self inline: false. ^ self partitionedAND: sourceWord bitInvert32 to: destinationWord nBits: destPixSize nPartitions: pixPerWord! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'di 12/27/97 10:39'! pixPaint: sourceWord with: destinationWord self inline: false. sourceWord = 0 ifTrue: [^ destinationWord]. ^ sourceWord bitOr: (self partitionedAND: sourceWord bitInvert32 to: destinationWord nBits: destPixSize nPartitions: pixPerWord)! ! !BitBltSimulation methodsFor: 'combination rules'! rgbAdd: sourceWord with: destinationWord self inline: false. destPixSize < 16 ifTrue: ["Add each pixel separately" ^ self partitionedAdd: sourceWord to: destinationWord nBits: destPixSize nPartitions: pixPerWord]. destPixSize = 16 ifTrue: ["Add RGB components of each pixel separately" ^ (self partitionedAdd: sourceWord to: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedAdd: sourceWord>>16 to: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Add RGB components of the pixel separately" ^ self partitionedAdd: sourceWord to: destinationWord nBits: 8 nPartitions: 3]! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:12'! rgbDiff: sourceWord with: destinationWord "Subract the pixels in the source and destination, color by color, and return the sum of the absolute value of all the differences. For non-rgb, return the number of differing pixels." | pixMask destShifted sourceShifted destPixVal bitsPerColor rgbMask sourcePixVal diff maskShifted | self inline: false. pixMask _ maskTable at: destPixSize. destPixSize = 16 ifTrue: [bitsPerColor _ 5. rgbMask _ 16r1F] ifFalse: [bitsPerColor _ 8. rgbMask _ 16rFF]. maskShifted _ destMask. destShifted _ destinationWord. sourceShifted _ sourceWord. 1 to: pixPerWord do: [:i | (maskShifted bitAnd: pixMask) > 0 ifTrue: ["Only tally pixels within the destination rectangle" destPixVal _ destShifted bitAnd: pixMask. sourcePixVal _ sourceShifted bitAnd: pixMask. destPixSize < 16 ifTrue: [sourcePixVal = destPixVal ifTrue: [diff _ 0] ifFalse: [diff _ 1]] ifFalse: [diff _ (self partitionedSub: sourcePixVal from: destPixVal nBits: bitsPerColor nPartitions: 3). diff _ (diff bitAnd: rgbMask) + (diff>>bitsPerColor bitAnd: rgbMask) + ((diff>>bitsPerColor)>>bitsPerColor bitAnd: rgbMask)]. bitCount _ bitCount + diff]. maskShifted _ maskShifted >> destPixSize. sourceShifted _ sourceShifted >> destPixSize. destShifted _ destShifted >> destPixSize]. ^ destinationWord "For no effect on dest" ! ! !BitBltSimulation methodsFor: 'combination rules'! rgbMax: sourceWord with: destinationWord self inline: false. destPixSize < 16 ifTrue: ["Max each pixel separately" ^ self partitionedMax: sourceWord with: destinationWord nBits: destPixSize nPartitions: pixPerWord]. destPixSize = 16 ifTrue: ["Max RGB components of each pixel separately" ^ (self partitionedMax: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMax: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Max RGB components of the pixel separately" ^ self partitionedMax: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !BitBltSimulation methodsFor: 'combination rules'! rgbMin: sourceWord with: destinationWord self inline: false. destPixSize < 16 ifTrue: ["Min each pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: destPixSize nPartitions: pixPerWord]. destPixSize = 16 ifTrue: ["Min RGB components of each pixel separately" ^ (self partitionedMin: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMin: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Min RGB components of the pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'di 1/21/98 21:57'! rgbMinInvert: wordToInvert with: destinationWord | sourceWord | self inline: false. sourceWord _ wordToInvert bitInvert32. destPixSize < 16 ifTrue: ["Min each pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: destPixSize nPartitions: pixPerWord]. destPixSize = 16 ifTrue: ["Min RGB components of each pixel separately" ^ (self partitionedMin: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMin: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Min RGB components of the pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !BitBltSimulation methodsFor: 'combination rules'! rgbSub: sourceWord with: destinationWord self inline: false. destPixSize < 16 ifTrue: ["Sub each pixel separately" ^ self partitionedSub: sourceWord from: destinationWord nBits: destPixSize nPartitions: pixPerWord]. destPixSize = 16 ifTrue: ["Sub RGB components of each pixel separately" ^ (self partitionedSub: sourceWord from: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedSub: sourceWord>>16 from: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Sub RGB components of the pixel separately" ^ self partitionedSub: sourceWord from: destinationWord nBits: 8 nPartitions: 3]! ! !BitBltSimulation methodsFor: 'combination rules'! sourceWord: sourceWord with: destinationWord ^sourceWord! ! !BitBltSimulation methodsFor: 'combination rules'! subWord: sourceWord with: destinationWord ^sourceWord - destinationWord! ! !BitBltSimulation methodsFor: 'combination rules' stamp: 'ar 10/23/1999 20:46'! tallyIntoMap: sourceWord with: destinationWord "Tally pixels into the color map. Those tallied are exactly those in the destination rectangle. Note that the source should be specified == destination, in order for the proper color map checks to be performed at setup." | mapIndex pixMask destShifted maskShifted pixVal | self inline: false. colorMap = nil ifTrue: [^ destinationWord "no op"]. pixMask _ maskTable at: destPixSize. destShifted _ destinationWord. maskShifted _ destMask. 1 to: pixPerWord do: [:i | (maskShifted bitAnd: pixMask) = 0 ifFalse: ["Only tally pixels within the destination rectangle" pixVal _ destShifted bitAnd: pixMask. destPixSize < 16 ifTrue: [mapIndex _ pixVal] ifFalse: [destPixSize = 16 ifTrue: [mapIndex _ self rgbMap: pixVal from: 5 to: cmBitsPerColor] ifFalse: [mapIndex _ self rgbMap: pixVal from: 8 to: cmBitsPerColor]]. self colormapAt: mapIndex put: (self colormapAt: mapIndex) + 1]. maskShifted _ maskShifted >> destPixSize. destShifted _ destShifted >> destPixSize]. ^ destinationWord "For no effect on dest"! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 11/16/1998 00:23'! default8To32Table "Return the default translation table from 1..8 bit indexed colors to 32bit" "The table has been generated by the following statements" "| pvs hex | String streamContents:[:s| s nextPutAll:'static unsigned int theTable[256] = { '. pvs _ (Color colorMapIfNeededFrom: 8 to: 32) asArray. 1 to: pvs size do:[:i| i > 1 ifTrue:[s nextPutAll:', ']. (i-1 \\ 8) = 0 ifTrue:[s cr]. s nextPutAll:'0x'. hex _ (pvs at: i) printStringBase: 16. s nextPutAll: (hex copyFrom: 4 to: hex size). ]. s nextPutAll:'};'. ]." | theTable | self returnTypeC:'unsigned int *'. self var: #theTable declareC:'static unsigned int theTable[256] = { 0x0, 0xFF000001, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050, 0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7, 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, 0xFF000001, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333, 0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966, 0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99, 0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF, 0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900, 0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33, 0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399, 0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC, 0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF, 0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333, 0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966, 0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99, 0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF, 0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900, 0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33, 0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399, 0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC, 0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF, 0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333, 0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966, 0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99, 0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF, 0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900, 0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33, 0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399, 0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC, 0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF};'. ^theTable! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/27/1999 17:54'! deltaFrom: x1 to: x2 nSteps: n "Utility routine for computing Warp increments." self inline: true. x2 > x1 ifTrue: [^ x2 - x1 + FixedPt1 // (n+1) + 1] ifFalse: [x2 = x1 ifTrue: [^ 0]. ^ 0 - (x1 - x2 + FixedPt1 // (n+1) + 1)]! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 7/24/1999 19:16'! dither32To16: srcWord threshold: ditherValue "Dither the given 32bit word to 16 bit. Ignore alpha." | pv threshold value out | self inline: true. "You bet" pv _ srcWord bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ value + 1] ifFalse:[out _ value]. pv _ (srcWord bitShift: -8) bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ out bitOr: (value+1 bitShift:5)] ifFalse:[out _ out bitOr: (value bitShift: 5)]. pv _ (srcWord bitShift: -16) bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ out bitOr: (value+1 bitShift:10)] ifFalse:[out _ out bitOr: (value bitShift: 10)]. ^out! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 12/7/1999 21:00'! pickSourcePixels: nPixels nullMap: nullMap srcMask: srcMask destMask: dstMask "Pick nPix pixels starting at srcBitIndex from the source, map by the color map, and justify them according to dstBitIndex in the resulting destWord. Incoming pixels of 16 or 32 bits are first reduced to cmBitsPerColor. With no color map, pixels are just masked or zero-filled or if 16- or 32-bit pixels, the r, g, and b are so treated individually." "ar 12/7/1999: - the method currently has a side effect (see at the end) - the idea is to inline this into a single sender and do most of the color space stuff here - the '[...] whileFalse' is intended to generate 'do { ... } while(...)' loops which are faster" | sourceWord destWord sourcePix destPix srcShift dstShift nPix | self inline: true. "oh please" sourceWord _ self srcLongAt: sourceIndex. destWord _ 0. srcShift _ srcBitShift. "Hint: Keep in register" dstShift _ dstBitShift. "Hint: Keep in register" nPix _ nPixels. "always > 0 so we can use do { } while(--nPix);" (nullMap or:[sourcePixSize > 8]) ifTrue:[ "Extract the degenerate case of sourcePixSize <= 8 and nullMap. Note: The case is considered degenerate because there should always be a colormap when copying between indexed color forms of differing depth." sourcePixSize <= 8 ifTrue:[ "Degenerate so the dirty version w/o comments..." [destWord _ destWord bitOr: ((sourceWord >> srcShift bitAnd: srcMask) bitAnd: dstMask) << dstShift. dstShift _ dstShift - destPixSize. (srcShift _ srcShift - sourcePixSize) < 0 ifTrue: [srcShift _ srcShift + 32. sourceWord _ self srcLongAt: (sourceIndex _ sourceIndex + 4)]. (nPix _ nPix - 1) = 0] whileFalse. ] ifFalse:["sourcePixSize > 8" "Convert RGB pixels. Since the cmMasks and cmShifts have been setup initially we only need one version here." ["pick source pixel" sourcePix _ sourceWord >> srcShift bitAnd: srcMask. "map the pixel(either into colorMap or destFormat)" cmDeltaBits = 0 "e.g., srcFormat == dstFormat" ifTrue:[destPix _ sourcePix] ifFalse:[ destPix _ self rgbMap: sourcePix. "Avoid transparency by color conversion" (destPix = 0 and:[sourcePix ~= 0]) ifTrue:[destPix _ 1]]. "if nullMap == false do colormap lookup after color reduction" nullMap ifFalse:[destPix _ self colormapAt: destPix]. "Mix it in (note: in theory we could avoid the bitAnd but its safer for now)" destWord _ destWord bitOr: (destPix bitAnd: dstMask) << dstShift. dstShift _ dstShift - destPixSize. "Adjust source if at pixel boundary" (srcShift _ srcShift - sourcePixSize) < 0 ifTrue: [srcShift _ srcShift + 32. sourceWord _ self srcLongAt: (sourceIndex _ sourceIndex + 4)]. (nPix _ nPix - 1) = 0] whileFalse. ]. ] ifFalse:[ "This part executed if we have a source pix size <= 8 and a colormap lookup as in the regular text display." [ "pick source pixel" sourcePix _ sourceWord >> srcShift bitAnd: srcMask. "Map it by color map" destPix _ (self colormapAt: sourcePix) bitAnd: dstMask. "**** How do we find out if we have to do color space conversion here ****" "Mix it in" destWord _ destWord bitOr: destPix << dstShift. "adjust shift" dstShift _ dstShift - destPixSize. "Adjust source if at pixel boundary" (srcShift _ srcShift - sourcePixSize) < 0 ifTrue: [srcShift _ srcShift + 32. sourceWord _ self srcLongAt: (sourceIndex _ sourceIndex + 4)]. (nPix _ nPix - 1) = 0] whileFalse. ]. srcBitShift _ srcShift. "Store back" "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" dstBitShift _ 32 - destPixSize. "Shift towards leftmost pixel" ^destWord! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/29/1999 00:11'! pickWarpPixelAtX: xx y: yy "Pick a single pixel from the source for WarpBlt. Note: This method is crucial for WarpBlt speed w/o smoothing and still relatively important when smoothing is used." | x y srcIndex sourceWord sourcePix | self inline: true. "*please*" "note: it would be much faster if we could just avoid these stupid tests for being inside sourceForm." (xx < 0 or:[yy < 0 or:[ (x _ xx >> BinaryPoint) >= srcWidth or:[ (y _ yy >> BinaryPoint) >= srcHeight]]]) ifTrue:[^0]. "out of bounds" "Fetch source word. Note: We should really update srcIndex with sx and sy so that we don't have to do the computation below. We might even be able to simplify the out of bounds test from above." srcIndex _ sourceBits + (y * sourcePitch) + (x >> warpAlignShift * 4). sourceWord _ self srcLongAt: srcIndex. "Extract pixel from word" srcBitShift _ warpBitShiftTable at: (x bitAnd: warpAlignMask). sourcePix _ sourceWord >> srcBitShift bitAnd: warpSrcMask. ^sourcePix! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/28/1999 18:30'! rgbMap: sourcePixel "Color map the given source pixel. Note: This relies on an accurate setup of the cmShifts and cmMasks by BitBlt and can therefore not be used from WarpBlt in smoothing mode (but hey, then we have to go over lots of different pixels before we even come to the output color conversion so that doesn't really matter)." self inline: true. "you bet" cmDeltaBits < 0 "Compress or expand RGB values?!!" ifTrue:[^((sourcePixel bitAnd: cmRedMask) >> cmRedShift) bitOr: (((sourcePixel bitAnd: cmGreenMask) >> cmGreenShift) bitOr: ((sourcePixel bitAnd: cmBlueMask) >> cmBlueShift))] ifFalse:[^((sourcePixel bitAnd: cmRedMask) << cmRedShift) bitOr: (((sourcePixel bitAnd: cmGreenMask) << cmGreenShift) bitOr: ((sourcePixel bitAnd: cmBlueMask) << cmBlueShift))]! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'di 4/10/1999 17:27'! rgbMap: sourcePixel from: nBitsIn to: nBitsOut "Convert the given pixel value with nBitsIn bits for each color component to a pixel value with nBitsOut bits for each color component. Typical values for nBitsIn/nBitsOut are 3, 5, or 8." | mask d srcPix destPix | self inline: true. (d _ nBitsOut - nBitsIn) > 0 ifTrue: ["Expand to more bits by zero-fill" mask _ (1 << nBitsIn) - 1. "Transfer mask" srcPix _ sourcePixel << d. mask _ mask << d. destPix _ srcPix bitAnd: mask. mask _ mask << nBitsOut. srcPix _ srcPix << d. ^ destPix + (srcPix bitAnd: mask) + (srcPix << d bitAnd: mask << nBitsOut)] ifFalse: ["Compress to fewer bits by truncation" d = 0 ifTrue: [nBitsIn = 5 ifTrue: ["Sometimes called with 16 bits, though pixel is 15, but we must never return more than 15." ^ sourcePixel bitAnd: 16r7FFF]. nBitsIn = 8 ifTrue: ["Sometimes called with 32 bits, though pixel is 24, but we must never return more than 24." ^ sourcePixel bitAnd: 16rFFFFFF]. ^ sourcePixel]. "no compression" sourcePixel = 0 ifTrue: [^ sourcePixel]. "always map 0 (transparent) to 0" d _ nBitsIn - nBitsOut. mask _ (1 << nBitsOut) - 1. "Transfer mask" srcPix _ sourcePixel >> d. destPix _ srcPix bitAnd: mask. mask _ mask << nBitsOut. srcPix _ srcPix >> d. destPix _ destPix + (srcPix bitAnd: mask) + (srcPix >> d bitAnd: mask << nBitsOut). destPix = 0 ifTrue: [^ 1]. "Dont fall into transparent by truncation" ^ destPix]! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/23/1999 20:45'! smoothPix: n atXf: xf yf: yf dxh: dxh dyh: dyh dxv: dxv dyv: dyv pixPerWord: srcPixPerWord pixelMask: sourcePixMask sourceMap: sourceMap | sourcePix r g b x y rgb bitsPerColor d nPix maxPix | self inline: false. r _ g _ b _ 0. "Separate r, g, b components" maxPix _ n*n. x _ xf. y _ yf. nPix _ 0. "actual number of pixels (not clipped and not transparent)" 0 to: n-1 do: [:i | 0 to: n-1 do: [:j | sourcePix _ (self sourcePixAtX: x + (dxh*i) + (dxv*j) >> BinaryPoint y: y + (dyh*i) + (dyv*j) >> BinaryPoint pixPerWord: srcPixPerWord) bitAnd: sourcePixMask. (combinationRule=25 "PAINT" and: [sourcePix = 0]) ifFalse: ["If not clipped and not transparent, then tally rgb values" nPix _ nPix + 1. sourcePixSize < 16 ifTrue: ["Get 24-bit RGB values from sourcemap table" rgb _ (interpreterProxy fetchWord: sourcePix ofObject: sourceMap) bitAnd: 16rFFFFFF] ifFalse: ["Already in RGB format" sourcePixSize = 32 ifTrue: [rgb _ sourcePix bitAnd: 16rFFFFFF] ifFalse: ["Note could be faster" rgb _ self rgbMap: sourcePix from: 5 to: 8]]. r _ r + ((rgb >> 16) bitAnd: 16rFF). g _ g + ((rgb >> 8) bitAnd: 16rFF). b _ b + (rgb bitAnd: 16rFF). ]]. ]. (nPix = 0 or: [combinationRule=25 "PAINT" and: [nPix < (maxPix//2)]]) ifTrue: [^ 0 "All pixels were 0, or most were transparent"]. colorMap ~= nil ifTrue: [bitsPerColor _ cmBitsPerColor] ifFalse: [destPixSize = 16 ifTrue: [bitsPerColor _ 5]. destPixSize = 32 ifTrue: [bitsPerColor _ 8]]. d _ 8 - bitsPerColor. rgb _ ((r // nPix >> d) << (bitsPerColor*2)) + ((g // nPix >> d) << bitsPerColor) + ((b // nPix >> d)). rgb = 0 ifTrue: [ "only generate zero if pixel is really transparent" (r + g + b) > 0 ifTrue: [rgb _ 1]]. colorMap ~= nil ifTrue: [^self colormapAt: rgb] ifFalse: [^ rgb] ! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/25/1999 19:24'! sourcePixAtX: x y: y pixPerWord: srcPixPerWord | sourceWord index | self inline: true. (x < 0 or: [x >= srcWidth]) ifTrue: [^ 0]. (y < 0 or: [y >= srcHeight]) ifTrue: [^ 0]. index _ (y * sourcePitch) + ((x // srcPixPerWord) *4). sourceWord _ self srcLongAt: sourceBits + index. ^ sourceWord >> ((32-sourcePixSize) - (x\\srcPixPerWord*sourcePixSize))! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/27/1999 17:10'! warpPickSmoothPixels: nPixels xDeltah: xDeltah yDeltah: yDeltah xDeltav: xDeltav yDeltav: yDeltav sourceMap: sourceMap smoothing: n "Pick n (sub-) pixels from the source form, mapped by sourceMap, average the RGB values, map by colorMap and return the new word. This version is only called from WarpBlt with smoothingCount > 1" | rgb x y a r g b xx yy xdh ydh xdv ydv dstMask destWord i j k nPix | self inline: false. "nope - too much stuff in here" dstMask _ maskTable at: destPixSize. destWord _ 0. n = 2 "Try avoiding divides for most common n (divide by 2 is generated as shift)" ifTrue:[xdh _ xDeltah // 2. ydh _ yDeltah // 2. xdv _ xDeltav // 2. ydv _ yDeltav // 2] ifFalse:[xdh _ xDeltah // n. ydh _ yDeltah // n. xdv _ xDeltav // n. ydv _ yDeltav // n]. i _ nPixels. [ x _ sx. y _ sy. a _ r _ g _ b _ 0. "Pick and average n*n subpixels" nPix _ 0. "actual number of pixels (not clipped and not transparent)" j _ n. [ xx _ x. yy _ y. k _ n. [ "get a single subpixel" rgb _ self pickWarpPixelAtX: xx y: yy. (combinationRule=25 "PAINT" and: [rgb = 0]) ifFalse:[ "If not clipped and not transparent, then tally rgb values" nPix _ nPix + 1. sourcePixSize < 16 ifTrue:[ "Get RGBA values from sourcemap table" rgb _ interpreterProxy longAt: sourceMap + (rgb << 2). ] ifFalse:["Already in RGB format" sourcePixSize = 16 ifTrue:[rgb _ self rgbMap16To32: rgb] ifFalse:[rgb _ self rgbMap32To32: rgb]]. b _ b + (rgb bitAnd: 255). g _ g + (rgb >> 8 bitAnd: 255). r _ r + (rgb >> 16 bitAnd: 255). a _ a + (rgb >> 24)]. xx _ xx + xdh. yy _ yy + ydh. (k _ k - 1) = 0] whileFalse. x _ x + xdv. y _ y + ydv. (j _ j - 1) = 0] whileFalse. (nPix = 0 or: [combinationRule=25 "PAINT" and: [nPix < (n * n // 2)]]) ifTrue:[ rgb _ 0 "All pixels were 0, or most were transparent" ] ifFalse:[ "normalize rgba sums" nPix = 4 "Try to avoid divides for most common n" ifTrue:[r _ r >> 2. g _ g >> 2. b _ b >> 2. a _ a >> 2] ifFalse:[ r _ r // nPix. g _ g // nPix. b _ b // nPix. a _ a // nPix]. rgb _ (a << 24) + (r << 16) + (g << 8) + b. "map the pixel" colorMap == nil "means we have different src/dst format" ifTrue:[rgb _ self rgbMap32ToX: rgb] ifFalse:[rgb _ self rgbMap32: rgb to: cmBitsPerColor]. rgb = 0 ifTrue: [ "only generate zero if pixel is really transparent" (r + g + b + a) > 0 ifTrue: [rgb _ 1]]. colorMap = nil ifFalse:[rgb _ self colormapAt: rgb]. ]. "Mix it in (note: in theory we could avoid the bitAnd but its safer for now)" destWord _ destWord bitOr: (rgb bitAnd: dstMask) << dstBitShift. dstBitShift _ dstBitShift - destPixSize. sx _ sx + xDeltah. sy _ sy + yDeltah. (i _ i - 1) = 0] whileFalse. "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" dstBitShift _ 32 - destPixSize. "Shift towards leftmost pixel" ^destWord ! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/29/1999 00:08'! warpPickSourcePixels: nPixels xDeltah: xDeltah yDeltah: yDeltah xDeltav: xDeltav yDeltav: yDeltav "Pick n pixels from the source form, map by colorMap and return aligned by dstBitShift. This version is only called from WarpBlt with smoothingCount = 1" | dstMask destWord nPix sourcePix destPix | self inline: true. "Yepp - this should go into warpLoop" dstMask _ maskTable at: destPixSize. destWord _ 0. nPix _ nPixels. [ "Pick a single pixel" sourcePix _ self pickWarpPixelAtX: sx y: sy. destPix _ sourcePix. sourcePixSize > 8 ifTrue:["Color map RGB pix" cmDeltaBits = 0 ifFalse:[ "but only if necessary" destPix _ self rgbMap: sourcePix. (destPix = 0 and:[sourcePix ~= 0]) ifTrue:[destPix _ 1]]]. "map by colormap if necessary" colorMap == nil ifFalse:[destPix _ self colormapAt: destPix]. "Mix it in (note: in theory we could avoid the bitAnd but its safer for now)" destWord _ destWord bitOr: (destPix bitAnd: dstMask) << dstBitShift. dstBitShift _ dstBitShift - destPixSize. sx _ sx + xDeltah. sy _ sy + yDeltah. (nPix _ nPix - 1) = 0] whileFalse. "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" dstBitShift _ 32 - destPixSize. "Shift towards leftmost pixel" ^destWord ! ! !BitBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/23/1999 20:45'! warpSourcePixels: nPix xDeltah: xDeltah yDeltah: yDeltah xDeltav: xDeltav yDeltav: yDeltav smoothing: n sourceMap: sourceMapOop "Pick nPix pixels using these x- and y-incs, and map color if necess." | destWord sourcePix sourcePixMask destPixMask srcPixPerWord destPix | self inline: false. sourcePixMask _ maskTable at: sourcePixSize. destPixMask _ maskTable at: destPixSize. srcPixPerWord _ 32 // sourcePixSize. destWord _ 0. 1 to: nPix do: [:i | n > 1 ifTrue: ["Average n pixels and compute dest pixel from color map" destPix _ (self smoothPix: n atXf: sx yf: sy dxh: xDeltah//n dyh: yDeltah//n dxv: xDeltav//n dyv: yDeltav//n pixPerWord: srcPixPerWord pixelMask: sourcePixMask sourceMap: sourceMapOop) bitAnd: destPixMask] ifFalse: ["No smoothing -- just pick pixel and map if difft depths or color map supplied" sourcePix _ (self sourcePixAtX: sx >> BinaryPoint y: sy >> BinaryPoint pixPerWord: srcPixPerWord) bitAnd: sourcePixMask. colorMap = nil ifTrue: [destPixSize = sourcePixSize ifTrue: [destPix _ sourcePix] ifFalse: [sourcePixSize >= 16 ifTrue: ["Map between RGB pixels" sourcePixSize = 16 ifTrue: [destPix _ self rgbMap: sourcePix from: 5 to: 8] ifFalse: [destPix _ self rgbMap: sourcePix from: 8 to: 5]] ifFalse: [destPix _ sourcePix bitAnd: destPixMask]]] ifFalse: [sourcePixSize >= 16 ifTrue: ["RGB pixels first get reduced to cmBitsPerColor" sourcePixSize = 16 ifTrue: [sourcePix _ self rgbMap: sourcePix from: 5 to: cmBitsPerColor] ifFalse: [sourcePix _ self rgbMap: sourcePix from: 8 to: cmBitsPerColor]]. "Then look up sourcePix in colorMap" destPix _ (self colormapAt: sourcePix) bitAnd: destPixMask]]. destPixSize = 32 ifTrue:[destWord _ destPix] ifFalse:[destWord _ (destWord << destPixSize) bitOr: destPix]. sx _ sx + xDeltah. sy _ sy + yDeltah. ]. ^ destWord! ! !BitBltSimulation methodsFor: 'translation support' stamp: 'ar 10/12/1998 17:43'! initBBOpTable self cCode: 'opTable[0+1] = (int)clearWordwith'. self cCode: 'opTable[1+1] = (int)bitAndwith'. self cCode: 'opTable[2+1] = (int)bitAndInvertwith'. self cCode: 'opTable[3+1] = (int)sourceWordwith'. self cCode: 'opTable[4+1] = (int)bitInvertAndwith'. self cCode: 'opTable[5+1] = (int)destinationWordwith'. self cCode: 'opTable[6+1] = (int)bitXorwith'. self cCode: 'opTable[7+1] = (int)bitOrwith'. self cCode: 'opTable[8+1] = (int)bitInvertAndInvertwith'. self cCode: 'opTable[9+1] = (int)bitInvertXorwith'. self cCode: 'opTable[10+1] = (int)bitInvertDestinationwith'. self cCode: 'opTable[11+1] = (int)bitOrInvertwith'. self cCode: 'opTable[12+1] = (int)bitInvertSourcewith'. self cCode: 'opTable[13+1] = (int)bitInvertOrwith'. self cCode: 'opTable[14+1] = (int)bitInvertOrInvertwith'. self cCode: 'opTable[15+1] = (int)destinationWordwith'. self cCode: 'opTable[16+1] = (int)destinationWordwith'. self cCode: 'opTable[17+1] = (int)destinationWordwith'. self cCode: 'opTable[18+1] = (int)addWordwith'. self cCode: 'opTable[19+1] = (int)subWordwith'. self cCode: 'opTable[20+1] = (int)rgbAddwith'. self cCode: 'opTable[21+1] = (int)rgbSubwith'. self cCode: 'opTable[22+1] = (int)OLDrgbDiffwith'. self cCode: 'opTable[23+1] = (int)OLDtallyIntoMapwith'. self cCode: 'opTable[24+1] = (int)alphaBlendwith'. self cCode: 'opTable[25+1] = (int)pixPaintwith'. self cCode: 'opTable[26+1] = (int)pixMaskwith'. self cCode: 'opTable[27+1] = (int)rgbMaxwith'. self cCode: 'opTable[28+1] = (int)rgbMinwith'. self cCode: 'opTable[29+1] = (int)rgbMinInvertwith'. self cCode: 'opTable[30+1] = (int)alphaBlendConstwith'. self cCode: 'opTable[31+1] = (int)alphaPaintConstwith'. self cCode: 'opTable[32+1] = (int)rgbDiffwith'. self cCode: 'opTable[33+1] = (int)tallyIntoMapwith'. self cCode: 'opTable[34+1] = (int)alphaBlendScaledwith'.! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:22'! colormapAt: idx "Return the word at position idx from the colorMap" ^interpreterProxy longAt: colorMap + (idx << 2)! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:22'! colormapAt: idx put: value "Store the word at position idx in the colorMap" ^interpreterProxy longAt: colorMap + (idx << 2) put: value! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:23'! dstLongAt: idx ^interpreterProxy longAt: idx! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/26/1999 18:08'! dstLongAt: idx put: value ^interpreterProxy longAt: idx put: value! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 12/7/1999 21:09'! dstLongAt: idx put: srcValue mask: dstMask "Store the given value back into destination form, using dstMask to mask out the bits to be modified. This is an essiantial read-modify-write operation on the destination form." | dstValue | self inline: true. dstValue _ self dstLongAt: idx. dstValue _ dstValue bitAnd: dstMask. dstValue _ dstValue bitOr: srcValue. self dstLongAt: idx put: dstValue.! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:22'! halftoneAt: idx "Return a value from the halftone pattern." ^interpreterProxy longAt: halftoneBase + (idx \\ halftoneHeight * 4)! ! !BitBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:23'! srcLongAt: idx ^interpreterProxy longAt: idx! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 12/7/1999 21:08'! rgbMap16: sourcePixel downTo: nBitsOut "Convert the given 16bit pixel value to a color map index using nBitsOut bits for each color component. Note: This method is intended to deal with different source formats." | delta | self inline: true. delta _ 5 - nBitsOut. "note: evaluated strictly left to right" ^((sourcePixel >> 10 bitAnd: 31) >> delta) << nBitsOut + ((sourcePixel >> 5 bitAnd: 31) >> delta) << nBitsOut + ((sourcePixel bitAnd: 31) >> delta)! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 12/7/1999 21:08'! rgbMap16: sourcePixel to: nBitsOut "Convert the given 16bit pixel value to a color map index using nBitsOut bits for each color component. Note: This method is intended to deal with different source formats." self inline: true. nBitsOut > 5 ifTrue:[^self rgbMap16: sourcePixel upTo: nBitsOut] ifFalse:[^self rgbMap16: sourcePixel downTo: nBitsOut]! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 12/7/1999 21:07'! rgbMap16: sourcePixel upTo: nBitsOut "Convert the given 16bit pixel value to a color map index using nBitsOut bits for each color component. Note: This method is intended to deal with different source formats." | delta | self inline: true. delta _ nBitsOut - 5. "note: evaluated strictly left to right" ^((sourcePixel >> 10 bitAnd: 31) << (5 + delta)) + (sourcePixel >> 5 bitAnd: 31) << (5 + delta) + (sourcePixel bitAnd: 31) << (delta)! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 10/28/1999 16:02'! rgbMap16To32: sourcePixel "Convert the given 16bit pixel value to a 32bit RGBA value. Note: This method is intended to deal with different source formats." ^(((sourcePixel bitAnd: 31) << 3) bitOr: ((sourcePixel bitAnd: 16r3E0) << 6)) bitOr: ((sourcePixel bitAnd: 16r7C00) << 9)! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 10/28/1999 16:03'! rgbMap16ToX: sourcePixel "Convert the given 16 pixel value to a color value in destination format. Note: This method is intended to deal with different destination formats." destPixSize = 32 ifTrue:[^self rgbMap16To32: sourcePixel] ifFalse:[^sourcePixel] "The above assumes that the caller is pickSourcePixels: using the standard 16bit to 32bit conversion"! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 12/7/1999 21:07'! rgbMap32: sourcePixel to: nBitsOut "Convert the given 32bit pixel value to a color map index using nBitsOut bits for each color component. Note: This method is intended to deal with different source formats." | delta | self inline: true. delta _ 8 - nBitsOut. "note: evaluated strictly left to right" ^((sourcePixel >> 16 bitAnd: 255) >> delta) << nBitsOut + ((sourcePixel >> 8 bitAnd: 255) >> delta) << nBitsOut + ((sourcePixel bitAnd: 255) >> delta)! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 10/27/1999 14:28'! rgbMap32To32: sourcePixel "Convert the given 32bit pixel value to a 32bit RGBA value. Note: This method is intended to deal with different source formats." ^sourcePixel "For now do it simple"! ! !BitBltSimulation methodsFor: 'color mapping' stamp: 'ar 10/27/1999 17:31'! rgbMap32ToX: sourcePixel "Convert the given 32bit pixel value to a color value in destination format. Note: This method is intended to deal with different destination formats." destPixSize = 16 ifTrue:[^self rgbMap32: sourcePixel to: 5] ifFalse:[^sourcePixel] "The above assumes that the caller is pickSourcePixels: using the standard 32bit to 16bit conversion"! ! !BitBltSimulation methodsFor: 'surface support' stamp: 'ar 10/25/1999 22:21'! lockSurfaces "Get a pointer to the bits of any OS surfaces." "Note: The VM support code must robustly handle multiple attempts to lock the same surface and return the same values since one might blt just a portion of the surface from one location to another (see below; ioLockSurfaceBits() is called twice if sourceForm == destForm)." "Note: It is possible to query for the actual regions (e.g., after clipping) that might be affected by the BB operation during ioLockSurfaceBits since clipping is always performed before ioLockSurfaceBits is called. This might an improvement on some platforms (e.g., Unix w/ X-Windows) where getting actual bits requires a round-trip to the server. Right now we don't have accessors for these values (basically sx, sy, dx, dy, bbW, and bbH) but it would be trivial to add them -- iff somebody is interested..." "ar 10/20/1999: Just noted that the above is not true for scanCharacters..." "ar 10/19/1999: This *should* be inlined but how do we pass a pointer to the pitch of the surfaces in this case?!!" | surfaceHandle | self inline: true. "If the CCodeGen learns how to inline #cCode: methods" hasSurfaceLock _ false. destBits == 0 ifTrue:["Blitting *to* OS surface" surfaceHandle _ interpreterProxy fetchInteger: FormBitsIndex ofObject: destForm. "destBits _ self cCode: 'ioLockSurfaceBits(surfaceHandle, &destPitch)'." hasSurfaceLock _ true. ]. (sourceBits == 0 and:[noSource not]) ifTrue:["Blitting *from* OS surface" surfaceHandle _ interpreterProxy fetchInteger: FormBitsIndex ofObject: sourceForm. "sourceBits _ self cCode:'ioLockSurfaceBits(surfaceHandle, &sourcePitch)'." hasSurfaceLock _ true. ]. ^destBits ~~ 0 and:[sourceBits ~~ 0 or:[noSource]].! ! !BitBltSimulation methodsFor: 'surface support' stamp: 'ar 12/7/1999 21:05'! queryDestSurface: handle "Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller." "^(self cCode:'ioGetSurfaceFormat(handle, &destWidth, &destHeight, &destPixSize, &dstFormat)') ~~ 0" ^false! ! !BitBltSimulation methodsFor: 'surface support' stamp: 'ar 12/7/1999 21:05'! querySourceSurface: handle "Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller." "^(self cCode:'ioGetSurfaceFormat(handle, &srcWidth, &srcHeight, &sourcePixSize, &srcFormat)') ~~ 0" ^false! ! !BitBltSimulation methodsFor: 'surface support' stamp: 'ar 10/25/1999 22:22'! unlockSurfaces "Unlock the bits of any OS surfaces." "Note: It is possible to query for the dirty rectangle from ioUnlockSurfaceBits() since the affected regions are set before this method is called. This is currently not part of the InterpreterProxy interface but one can query for affectedLeft(), affectedRight(), affectedTop(), and affectedBottom() if the surface support is compiled with the VM." | surfaceHandle | hasSurfaceLock ifTrue:[ surfaceHandle _ interpreterProxy fetchPointer: FormBitsIndex ofObject: sourceForm. (interpreterProxy isIntegerObject: surfaceHandle) ifTrue:[ surfaceHandle _ interpreterProxy integerValueOf: surfaceHandle. "self ioUnlockSurfaceBits: surfaceHandle." sourceBits _ sourcePitch _ 0. ]. surfaceHandle _ interpreterProxy fetchPointer: FormBitsIndex ofObject: destForm. (interpreterProxy isIntegerObject: surfaceHandle) ifTrue:[ surfaceHandle _ interpreterProxy integerValueOf: surfaceHandle. "self ioUnlockSurfaceBits: surfaceHandle." destBits _ destPitch _ 0. ]. hasSurfaceLock _ false. ].! ! !BitBltSimulation methodsFor: 'initialize-release' stamp: 'ar 5/11/2000 20:39'! initialiseModule self export: true. self initBBOpTable. ^true! ! !BitBltSimulation methodsFor: 'primitives' stamp: 'ar 2/19/2000 20:40'! primitiveCopyBits "Invoke the copyBits primitive. If the destination is the display, then copy it to the screen." | rcvr | self export: true. rcvr _ interpreterProxy stackValue: interpreterProxy methodArgumentCount. (self loadBitBltFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. self copyBits. self showDisplayBits.! ! !BitBltSimulation methodsFor: 'primitives' stamp: 'ar 5/23/2000 23:54'! primitiveDisplayString | kernDelta xTable glyphMap stopIndex startIndex sourceString bbObj maxGlyph ascii glyphIndex sourcePtr left | self export: true. self var: #sourcePtr type: 'unsigned char *'. interpreterProxy methodArgumentCount = 6 ifFalse:[^interpreterProxy primitiveFail]. kernDelta _ interpreterProxy stackIntegerValue: 0. xTable _ interpreterProxy stackObjectValue: 1. glyphMap _ interpreterProxy stackObjectValue: 2. ((interpreterProxy fetchClassOf: xTable) = interpreterProxy classArray and:[ (interpreterProxy fetchClassOf: glyphMap) = interpreterProxy classArray]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy slotSizeOf: glyphMap) = 256 ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy failed ifTrue:[^nil]. maxGlyph _ (interpreterProxy slotSizeOf: xTable) - 2. stopIndex _ interpreterProxy stackIntegerValue: 3. startIndex _ interpreterProxy stackIntegerValue: 4. sourceString _ interpreterProxy stackObjectValue: 5. (interpreterProxy isBytes: sourceString) ifFalse:[^interpreterProxy primitiveFail]. (startIndex > 0 and:[stopIndex > 0 and:[ stopIndex <= (interpreterProxy byteSizeOf: sourceString)]]) ifFalse:[^interpreterProxy primitiveFail]. bbObj _ interpreterProxy stackObjectValue: 6. (self loadBitBltFrom: bbObj) ifFalse:[^interpreterProxy primitiveFail]. left _ destX. sourcePtr _ interpreterProxy firstIndexableField: sourceString. startIndex to: stopIndex do:[:charIndex| ascii _ sourcePtr at: charIndex-1. glyphIndex _ interpreterProxy fetchInteger: ascii ofObject: glyphMap. (glyphIndex < 0 or:[glyphIndex > maxGlyph]) ifTrue:[^interpreterProxy primitiveFail]. sourceX _ interpreterProxy fetchInteger: glyphIndex ofObject: xTable. width _ (interpreterProxy fetchInteger: glyphIndex+1 ofObject: xTable) - sourceX. interpreterProxy failed ifTrue:[^nil]. self clipRange. "Must clip here" (bbW > 0 and:[bbH > 0]) ifTrue: [self copyBits]. interpreterProxy failed ifTrue:[^nil]. destX _ destX + width + kernDelta. ]. affectedL _ left. self showDisplayBits. interpreterProxy pop: 6. "pop args, return rcvr"! ! !BitBltSimulation methodsFor: 'primitives' stamp: 'ar 2/19/2000 20:42'! primitiveDrawLoop "Invoke the line drawing primitive." | rcvr xDelta yDelta | self export: true. rcvr _ interpreterProxy stackValue: 2. xDelta _ interpreterProxy stackIntegerValue: 1. yDelta _ interpreterProxy stackIntegerValue: 0. (self loadBitBltFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self drawLoopX: xDelta Y: yDelta. self showDisplayBits]. interpreterProxy failed ifFalse:[interpreterProxy pop: 2].! ! !BitBltSimulation methodsFor: 'primitives' stamp: 'ar 2/19/2000 20:44'! primitiveScanCharacters "Invoke the scanCharacters primitive." | rcvr start stop string rightX stopArray displayFlag | self export: true. rcvr _ interpreterProxy stackValue: 6. start _ interpreterProxy stackIntegerValue: 5. stop _ interpreterProxy stackIntegerValue: 4. string _ interpreterProxy stackValue: 3. rightX _ interpreterProxy stackIntegerValue: 2. stopArray _ interpreterProxy stackValue: 1. displayFlag _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). interpreterProxy failed ifTrue: [^ nil]. (self loadScannerFrom: rcvr start: start stop: stop string: string rightX: rightX stopArray: stopArray displayFlag: displayFlag) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse: [self scanCharacters]. interpreterProxy failed ifFalse: [ displayFlag ifTrue: [self showDisplayBits]. interpreterProxy pop: 7. interpreterProxy push: self stopReason].! ! !BitBltSimulation methodsFor: 'primitives' stamp: 'ar 2/19/2000 20:47'! primitiveWarpBits "Invoke the warpBits primitive. If the destination is the display, then copy it to the screen." | rcvr | self export: true. rcvr _ interpreterProxy stackValue: interpreterProxy methodArgumentCount. (self loadWarpBltFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. self warpBits. self showDisplayBits.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitBltSimulation class instanceVariableNames: ''! !BitBltSimulation class methodsFor: 'initialization' stamp: 'di 6/29/1998 23:24'! initialize "BitBltSimulation initialize" self initializeRuleTable. "Mask constants" AllOnes _ 16rFFFFFFFF. BinaryPoint _ 14. FixedPt1 _ 1 << BinaryPoint. "Value of 1.0 in Warp's fixed-point representation" "Indices into stopConditions for scanning" EndOfRun _ 257. CrossedX _ 258. "Form fields" FormBitsIndex _ 0. FormWidthIndex _ 1. FormHeightIndex _ 2. FormDepthIndex _ 3. "BitBlt fields" BBDestFormIndex _ 0. BBSourceFormIndex _ 1. BBHalftoneFormIndex _ 2. BBRuleIndex _ 3. BBDestXIndex _ 4. BBDestYIndex _ 5. BBWidthIndex _ 6. BBHeightIndex _ 7. BBSourceXIndex _ 8. BBSourceYIndex _ 9. BBClipXIndex _ 10. BBClipYIndex _ 11. BBClipWidthIndex _ 12. BBClipHeightIndex _ 13. BBColorMapIndex _ 14. BBWarpBase _ 15. BBLastIndex _ 15. BBXTableIndex _ 16.! ! !BitBltSimulation class methodsFor: 'initialization' stamp: 'ar 10/12/1998 17:42'! initializeRuleTable "BitBltSimulation initializeRuleTable" "**WARNING** You MUST change initBBOpTable if you change this" OpTable _ #( "0" clearWord:with: "1" bitAnd:with: "2" bitAndInvert:with: "3" sourceWord:with: "4" bitInvertAnd:with: "5" destinationWord:with: "6" bitXor:with: "7" bitOr:with: "8" bitInvertAndInvert:with: "9" bitInvertXor:with: "10" bitInvertDestination:with: "11" bitOrInvert:with: "12" bitInvertSource:with: "13" bitInvertOr:with: "14" bitInvertOrInvert:with: "15" destinationWord:with: "16" destinationWord:with: "unused - was old paint" "17" destinationWord:with: "unused - was old mask" "18" addWord:with: "19" subWord:with: "20" rgbAdd:with: "21" rgbSub:with: "22" OLDrgbDiff:with: "23" OLDtallyIntoMap:with: "24" alphaBlend:with: "25" pixPaint:with: "26" pixMask:with: "27" rgbMax:with: "28" rgbMin:with: "29" rgbMinInvert:with: "30" alphaBlendConst:with: "31" alphaPaintConst:with: "32" rgbDiff:with: "33" tallyIntoMap:with: "34" alphaBlendScaled:with: ). OpTableSize _ OpTable size + 1. "0-origin indexing" ! ! !BitBltSimulation class methodsFor: 'initialization'! test2 "BitBltSimulation test2" | f | Display fillWhite: (0@0 extent: 300@140). 1 to: 12 do: [:i | f _ (Form extent: i@5) fillBlack. 0 to: 20 do: [:x | f displayOn: Display at: (x*13) @ (i*10)]]! ! !BitBltSimulation class methodsFor: 'initialization'! timingTest: extent "BitBltSimulation timingTest: 640@480" | f f2 map | f _ Form extent: extent depth: 8. f2 _ Form extent: extent depth: 8. map _ Bitmap new: 1 << f2 depth. ^ Array with: (Time millisecondsToRun: [100 timesRepeat: [f fillWithColor: Color white]]) with: (Time millisecondsToRun: [100 timesRepeat: [f copy: f boundingBox from: 0@0 in: f2 rule: Form over]]) with: (Time millisecondsToRun: [100 timesRepeat: [f copyBits: f boundingBox from: f2 at: 0@0 colorMap: map]])! ! !BitBltSimulation class methodsFor: 'translation' stamp: 'ar 5/12/2000 01:11'! declareCVarsIn: aCCodeGenerator aCCodeGenerator var: 'opTable' declareC: 'int opTable[' , OpTableSize printString , ']'. aCCodeGenerator var: 'maskTable' declareC:'int maskTable[33] = { 0, 1, 3, 0, 15, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 }'. aCCodeGenerator var: 'ditherMatrix4x4' declareC:'const int ditherMatrix4x4[16] = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 }'. aCCodeGenerator var: 'ditherThresholds16' declareC:'const int ditherThresholds16[8] = { 0, 2, 4, 6, 8, 12, 14, 16 }'. aCCodeGenerator var: 'ditherValues16' declareC:'const int ditherValues16[32] = { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }'. aCCodeGenerator var: 'warpBitShiftTable' declareC:'int warpBitShiftTable[32]'.! ! !BitBltSimulation class methodsFor: 'translation' stamp: 'ar 2/19/2000 20:55'! moduleName ^'BitBltPlugin'! ! !BitBltSimulation class methodsFor: 'translation' stamp: 'jm 5/12/1999 12:02'! opTable ^ OpTable ! ! !BitBltSimulation class methodsFor: 'system simulation' stamp: 'ar 10/27/1999 23:34'! copyBitsFrom: aBitBlt "Simulate the copyBits primitive" | proxy bb | proxy _ InterpreterProxy new. proxy loadStackFrom: thisContext sender. bb _ self simulatorClass new. bb setInterpreter: proxy. proxy success: (bb loadBitBltFrom: aBitBlt). bb copyBits. proxy failed ifFalse:[ proxy showDisplayBits: aBitBlt destForm Left: bb affectedLeft Top: bb affectedTop Right: bb affectedRight Bottom: bb affectedBottom]. ^proxy stackValue: 0! ! !BitBltSimulation class methodsFor: 'system simulation' stamp: 'ar 10/27/1999 14:06'! simulatorClass ^BitBltSimulator! ! !BitBltSimulation class methodsFor: 'system simulation' stamp: 'ar 10/27/1999 23:35'! warpBitsFrom: aBitBlt "Simulate the warpBits primitive" | proxy bb | proxy _ InterpreterProxy new. proxy loadStackFrom: thisContext sender. bb _ self simulatorClass new. bb setInterpreter: proxy. proxy success: (bb loadWarpBltFrom: aBitBlt). bb warpBits. proxy failed ifFalse:[ proxy showDisplayBits: aBitBlt destForm Left: bb affectedLeft Top: bb affectedTop Right: bb affectedRight Bottom: bb affectedBottom]. ^proxy stackValue: 0! ! BitBltSimulation subclass: #BitBltSimulator instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Interpreter'! !BitBltSimulator methodsFor: 'as yet unclassified' stamp: 'ar 10/28/1999 22:13'! initBBOpTable opTable _ OpTable. maskTable _ Array new: 32. #(1 2 4 8 16 32) do:[:i| maskTable at: i put: (1 << i)-1]. self initializeDitherTables. warpBitShiftTable _ CArrayAccessor on: (Array new: 32).! ! !BitBltSimulator methodsFor: 'as yet unclassified' stamp: 'ar 7/24/1999 23:20'! initializeDitherTables ditherMatrix4x4 _ CArrayAccessor on: #( 0 8 2 10 12 4 14 6 3 11 1 9 15 7 13 5). ditherThresholds16 _ CArrayAccessor on:#(0 2 4 6 8 10 12 14 16). ditherValues16 _ CArrayAccessor on: #(0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30).! ! !BitBltSimulator methodsFor: 'as yet unclassified' stamp: 'di 12/30/97 11:07'! mergeFn: arg1 with: arg2 ^ self perform: (opTable at: combinationRule+1) with: arg1 with: arg2! ! !BitBltSimulator methodsFor: 'debug support' stamp: 'ar 10/27/1999 14:22'! dstLongAt: dstIndex interpreterProxy isInterpreterProxy ifTrue:[^dstIndex longAt: 0]. ((dstIndex anyMask: 3) or:[dstIndex + 4 < destBits or:[ dstIndex > (destBits + (destPitch * destHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: dstIndex! ! !BitBltSimulator methodsFor: 'debug support' stamp: 'ar 10/27/1999 14:23'! dstLongAt: dstIndex put: value interpreterProxy isInterpreterProxy ifTrue:[^dstIndex longAt: 0 put: value]. ((dstIndex anyMask: 3) or:[dstIndex < destBits or:[ dstIndex >= (destBits + (destPitch * destHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: dstIndex put: value! ! !BitBltSimulator methodsFor: 'debug support' stamp: 'ar 10/27/1999 14:22'! srcLongAt: srcIndex interpreterProxy isInterpreterProxy ifTrue:[^srcIndex longAt: 0]. ((srcIndex anyMask: 3) or:[srcIndex + 4 < sourceBits or:[ srcIndex > (sourceBits + (sourcePitch * srcHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: srcIndex! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitBltSimulator class instanceVariableNames: ''! !BitBltSimulator class methodsFor: 'instance creation' stamp: 'ar 5/11/2000 22:06'! new ^super new! ! MouseMenuController subclass: #BitEditor instanceVariableNames: 'scale squareForm color transparent ' classVariableNames: 'YellowButtonMenu ' poolDictionaries: '' category: 'ST80-Editors'! !BitEditor commentStamp: '' prior: 0! I am a bit-magnifying tool for editing small Forms directly on the display screen. I continue to be active until the user points outside of my viewing area.! !BitEditor methodsFor: 'initialize-release'! release super release. squareForm release. squareForm _ nil! ! !BitEditor methodsFor: 'view access'! view: aView super view: aView. scale _ aView transformation scale. scale _ scale x rounded @ scale y rounded. squareForm _ Form extent: scale depth: aView model depth. squareForm fillBlack! ! !BitEditor methodsFor: 'basic control sequence'! controlInitialize super controlInitialize. Cursor crossHair show! ! !BitEditor methodsFor: 'basic control sequence'! controlTerminate Cursor normal show! ! !BitEditor methodsFor: 'control defaults' stamp: 'sma 3/11/2000 14:52'! isControlActive ^ super isControlActive and: [sensor keyboardPressed not]! ! !BitEditor methodsFor: 'control defaults'! redButtonActivity | formPoint displayPoint | model depth = 1 ifTrue: ["If this is just a black&white form, then set the color to be the opposite of what it was where the mouse was clicked" formPoint _ (view inverseDisplayTransform: sensor cursorPoint - (scale//2)) rounded. color _ 1-(view workingForm pixelValueAt: formPoint). squareForm fillColor: (color=1 ifTrue: [Color black] ifFalse: [Color white])]. [sensor redButtonPressed] whileTrue: [formPoint _ (view inverseDisplayTransform: sensor cursorPoint - (scale//2)) rounded. displayPoint _ view displayTransform: formPoint. squareForm displayOn: Display at: displayPoint clippingBox: view insetDisplayBox rule: Form over fillColor: nil. view changeValueAt: formPoint put: color]! ! !BitEditor methodsFor: 'menu messages'! accept "The edited information should now be accepted by the view." view accept! ! !BitEditor methodsFor: 'menu messages'! cancel "The edited informatin should be forgotten by the view." view cancel! ! !BitEditor methodsFor: 'menu messages' stamp: 'jm 3/27/98 14:52'! fileOut | fileName | fileName _ FillInTheBlank request: 'File name?' initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. Cursor normal showWhile: [model writeOnFileNamed: fileName]. ! ! !BitEditor methodsFor: 'menu messages'! setColor: aColor "Set the color that the next edited dots of the model to be the argument, aSymbol. aSymbol can be any color changing message understood by a Form, such as white or black." color _ aColor pixelValueForDepth: model depth. squareForm fillColor: aColor. ! ! !BitEditor methodsFor: 'menu messages' stamp: 'sma 3/15/2000 21:10'! setTransparentColor squareForm fillColor: Color gray. color _ Color transparent! ! !BitEditor methodsFor: 'menu messages'! test view workingForm follow: [Sensor cursorPoint] while: [Sensor noButtonPressed]. Sensor waitNoButton! ! !BitEditor methodsFor: 'pluggable menus' stamp: 'sma 3/11/2000 15:04'! getPluggableYellowButtonMenu: shiftKeyState ^ YellowButtonMenu! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitEditor class instanceVariableNames: ''! !BitEditor class methodsFor: 'class initialization' stamp: 'sma 3/11/2000 14:48'! initialize "The Bit Editor is the only controller to override the use of the blue button with a different pop-up menu. Initialize this menu." YellowButtonMenu _ SelectionMenu labels: 'cancel accept file out test' lines: #(2 3) selections: #(cancel accept fileOut test) "BitEditor initialize"! ! !BitEditor class methodsFor: 'instance creation'! openOnForm: aForm "Create and schedule a BitEditor on the form aForm at its top left corner. Show the small and magnified view of aForm." | scaleFactor | scaleFactor _ 8 @ 8. ^self openOnForm: aForm at: (self locateMagnifiedView: aForm scale: scaleFactor) topLeft scale: scaleFactor! ! !BitEditor class methodsFor: 'instance creation'! openOnForm: aForm at: magnifiedLocation "Create and schedule a BitEditor on the form aForm at magnifiedLocation. Show the small and magnified view of aForm." ^self openOnForm: aForm at: magnifiedLocation scale: 8 @ 8! ! !BitEditor class methodsFor: 'instance creation'! openOnForm: aForm at: magnifiedLocation scale: scaleFactor "Create and schedule a BitEditor on the form aForm. Show the small and magnified view of aForm." | aScheduledView | aScheduledView _ self bitEdit: aForm at: magnifiedLocation scale: scaleFactor remoteView: nil. aScheduledView controller openDisplayAt: aScheduledView displayBox topLeft + (aScheduledView displayBox extent / 2)! ! !BitEditor class methodsFor: 'instance creation' stamp: 'sma 3/11/2000 11:29'! openScreenViewOnForm: aForm at: formLocation magnifiedAt: magnifiedLocation scale: scaleFactor "Create and schedule a BitEditor on the form aForm. Show the magnified view of aForm in a scheduled window." | smallFormView bitEditor savedForm r | smallFormView _ FormView new model: aForm. smallFormView align: smallFormView viewport topLeft with: formLocation. bitEditor _ self bitEdit: aForm at: magnifiedLocation scale: scaleFactor remoteView: smallFormView. savedForm _ Form fromDisplay: (r _ bitEditor displayBox expandBy: (0@23 corner: 0@0)). bitEditor controller startUp. savedForm displayOn: Display at: r topLeft. bitEditor release. smallFormView release. "BitEditor magnifyOnScreen."! ! !BitEditor class methodsFor: 'examples'! magnifyOnScreen "Bit editing of an area of the display screen. User designates a rectangular area that is magnified by 8 to allow individual screens dots to be modified. red button is used to set a bit to black and yellow button is used to set a bit to white. Editor is not scheduled in a view. Original screen location is updated immediately. This is the same as FormEditor magnify." | smallRect smallForm scaleFactor tempRect | scaleFactor _ 8 @ 8. smallRect _ Rectangle fromUser. smallRect isNil ifTrue: [^self]. smallForm _ Form fromDisplay: smallRect. tempRect _ self locateMagnifiedView: smallForm scale: scaleFactor. "show magnified form size until mouse is depressed" self openScreenViewOnForm: smallForm at: smallRect topLeft magnifiedAt: tempRect topLeft scale: scaleFactor "BitEditor magnifyOnScreen."! ! !BitEditor class methodsFor: 'examples'! magnifyWithSmall " Also try: BitEditor openOnForm: (Form extent: 32@32 depth: Display depth) BitEditor openOnForm: ((MaskedForm extent: 32@32 depth: Display depth) withTransparentPixelValue: -1) " "Open a BitEditor viewing an area on the screen which the user chooses" | area form | area _ Rectangle fromUser. area isNil ifTrue: [^ self]. form _ Form fromDisplay: area. self openOnForm: form "BitEditor magnifyWithSmall."! ! !BitEditor class methodsFor: 'private' stamp: 'di 1/16/98 15:46'! bitEdit: aForm at: magnifiedFormLocation scale: scaleFactor remoteView: remoteView "Create a BitEditor on aForm. That is, aForm is a small image that will change as a result of the BitEditor changing a second and magnified view of me. magnifiedFormLocation is where the magnified form is to be located on the screen. scaleFactor is the amount of magnification. This method implements a scheduled view containing both a small and magnified view of aForm. Upon accept, aForm is updated." | aFormView scaledFormView bitEditor topView extent menuView lowerRightExtent | scaledFormView _ FormHolderView new model: aForm. scaledFormView scaleBy: scaleFactor. bitEditor _ self new. scaledFormView controller: bitEditor. bitEditor setColor: Color black. topView _ StandardSystemView new. remoteView == nil ifTrue: [topView label: 'Bit Editor']. topView borderWidth: 2. topView addSubView: scaledFormView. remoteView == nil ifTrue: "If no remote view, then provide a local view of the form" [aFormView _ FormView new model: scaledFormView workingForm. aFormView controller: NoController new. aForm height < 50 ifTrue: [aFormView borderWidthLeft: 0 right: 2 top: 2 bottom: 2] ifFalse: [aFormView borderWidthLeft: 0 right: 2 top: 2 bottom: 0]. topView addSubView: aFormView below: scaledFormView] ifFalse: "Otherwise, the remote one should view the same form" [remoteView model: scaledFormView workingForm]. lowerRightExtent _ remoteView == nil ifTrue: [(scaledFormView viewport width - aFormView viewport width) @ (aFormView viewport height max: 50)] ifFalse: [scaledFormView viewport width @ 50]. menuView _ self buildColorMenu: lowerRightExtent colorCount: 1. menuView model: bitEditor. menuView borderWidthLeft: 0 right: 0 top: 2 bottom: 0. topView addSubView: menuView align: menuView viewport topRight with: scaledFormView viewport bottomRight. extent _ scaledFormView viewport extent + (0 @ lowerRightExtent y) + (4 @ 4). "+4 for borders" topView minimumSize: extent. topView maximumSize: extent. topView translateBy: magnifiedFormLocation. topView insideColor: Color white. ^topView! ! !BitEditor class methodsFor: 'private' stamp: 'jm 4/7/98 20:43'! buildColorMenu: extent colorCount: nColors "See BitEditor magnifyWithSmall." | menuView form aSwitchView button formExtent highlightForm color leftOffset | menuView _ FormMenuView new. menuView window: (0@0 corner: extent). formExtent _ 30@30 min: extent//(nColors*2+1@2). "compute this better" leftOffset _ extent x-(nColors*2-1*formExtent x)//2. highlightForm _ Form extent: formExtent. highlightForm borderWidth: 4. 1 to: nColors do: [:index | color _ (nColors = 1 ifTrue: [#(black)] ifFalse: [#(black gray)]) at: index. form _ Form extent: formExtent. form fill: form boundingBox fillColor: (Color perform: color). form borderWidth: 5. form border: form boundingBox width: 4 fillColor: Color white. button _ Button new. index = 1 ifTrue: [button onAction: [menuView model setColor: Color fromUser]] ifFalse: [button onAction: [menuView model setTransparentColor]]. aSwitchView _ PluggableButtonView on: button getState: #isOn action: #turnOn. aSwitchView shortcutCharacter: ((nColors=3 ifTrue: ['xvn'] ifFalse: ['xn']) at: index); label: form; window: (0@0 extent: form extent); translateBy: (((index - 1) * 2 * form width) + leftOffset)@(form height // 2); borderWidth: 1. menuView addSubView: aSwitchView]. ^ menuView ! ! !BitEditor class methodsFor: 'private'! locateMagnifiedView: aForm scale: scaleFactor "Answer a rectangle at the location where the scaled view of the form, aForm, should be displayed." ^ Rectangle originFromUser: (aForm extent * scaleFactor + (0@50)). ! ! ArrayedCollection variableWordSubclass: #Bitmap instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Primitives'! !Bitmap commentStamp: '' prior: 0! My instances provide contiguous storage of bits, primarily to hold the graphical data of Forms. Forms and their subclasses provide the additional structural information as to how the bits should be interpreted in two dimensions.! !Bitmap methodsFor: 'initialize-release' stamp: 'ar 12/23/1999 14:35'! fromByteStream: aStream "Initialize the array of bits by reading integers from the argument, aStream." aStream nextWordsInto: self! ! !Bitmap methodsFor: 'filing' stamp: 'ar 2/3/2001 16:11'! compress: bm toByteArray: ba "Store a run-coded compression of the receiver into the byteArray ba, and return the last index stored into. ba is assumed to be large enough. The encoding is as follows... S {N D}*. S is the size of the original bitmap, followed by run-coded pairs. N is a run-length * 4 + data code. D, the data, depends on the data code... 0 skip N words, D is absent 1 N words with all 4 bytes = D (1 byte) 2 N words all = D (4 bytes) 3 N words follow in D (4N bytes) S and N are encoded as follows... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes" | size k word j lowByte eqBytes i | self var: #bm declareC: 'int *bm'. self var: #ba declareC: 'unsigned char *ba'. size _ bm size. i _ self encodeInt: size in: ba at: 1. k _ 1. [k <= size] whileTrue: [word _ bm at: k. lowByte _ word bitAnd: 16rFF. eqBytes _ ((word >> 8) bitAnd: 16rFF) = lowByte and: [((word >> 16) bitAnd: 16rFF) = lowByte and: [((word >> 24) bitAnd: 16rFF) = lowByte]]. j _ k. [j < size and: [word = (bm at: j+1)]] "scan for = words..." whileTrue: [j _ j+1]. j > k ifTrue: ["We have two or more = words, ending at j" eqBytes ifTrue: ["Actually words of = bytes" i _ self encodeInt: j-k+1*4+1 in: ba at: i. ba at: i put: lowByte. i _ i+1] ifFalse: [i _ self encodeInt: j-k+1*4+2 in: ba at: i. i _ self encodeBytesOf: word in: ba at: i]. k _ j+1] ifFalse: ["Check for word of 4 = bytes" eqBytes ifTrue: ["Note 1 word of 4 = bytes" i _ self encodeInt: 1*4+1 in: ba at: i. ba at: i put: lowByte. i _ i+1. k _ k + 1] ifFalse: ["Finally, check for junk" [j < size and: [(bm at: j) ~= (bm at: j+1)]] "scan for ~= words..." whileTrue: [j _ j+1]. j = size ifTrue: [j _ j + 1]. "We have one or more unmatching words, ending at j-1" i _ self encodeInt: j-k*4+3 in: ba at: i. k to: j-1 do: [:m | i _ self encodeBytesOf: (bm at: m) in: ba at: i]. k _ j]]]. ^ i - 1 "number of bytes actually stored" " Space check: | n rawBytes myBytes b | n _ rawBytes _ myBytes _ 0. Form allInstancesDo: [:f | f unhibernate. b _ f bits. n _ n + 1. rawBytes _ rawBytes + (b size*4). myBytes _ myBytes + (b compressToByteArray size). f hibernate]. Array with: n with: rawBytes with: myBytes ColorForms: (116 230324 160318 ) Forms: (113 1887808 1325055 ) Integerity check: Form allInstances do: [:f | f unhibernate. f bits = (Bitmap decompressFromByteArray: f bits compressToByteArray) ifFalse: [self halt]. f hibernate] Speed test: MessageTally spyOn: [Form allInstances do: [:f | Bitmap decompressFromByteArray: f bits compressToByteArray]] "! ! !Bitmap methodsFor: 'filing' stamp: 'RAA 7/28/2000 08:40'! compressGZip | ba hackwa hackba blt rowsAtATime sourceOrigin rowsRemaining bufferStream gZipStream | "just hacking around to see if further compression would help Nebraska" bufferStream _ RWBinaryOrTextStream on: (ByteArray new: 5000). gZipStream _ GZipWriteStream on: bufferStream. ba _ nil. rowsAtATime _ 20000. "or 80000 bytes" hackwa _ Form new hackBits: self. sourceOrigin _ 0@0. [(rowsRemaining _ hackwa height - sourceOrigin y) > 0] whileTrue: [ rowsAtATime _ rowsAtATime min: rowsRemaining. (ba isNil or: [ba size ~= (rowsAtATime * 4)]) ifTrue: [ ba _ ByteArray new: rowsAtATime * 4. hackba _ Form new hackBits: ba. blt _ (BitBlt toForm: hackba) sourceForm: hackwa. ]. blt combinationRule: Form over; sourceOrigin: sourceOrigin; destX: 0 destY: 0 width: 4 height: rowsAtATime; copyBits. "bufferStream nextPutAll: ba." sourceOrigin _ sourceOrigin x @ (sourceOrigin y + rowsAtATime). ]. gZipStream close. ^bufferStream contents ! ! !Bitmap methodsFor: 'filing' stamp: 'di 8/5/1998 11:31'! compressToByteArray "Return a run-coded compression of this bitmap into a byteArray" | byteArray lastByte | "Without skip codes, it is unlikely that the compressed bitmap will be any larger than was the original. The run-code cases are... N >= 1 words of equal bytes: 4N bytes -> 2 bytes (at worst 4 -> 2) N > 1 equal words: 4N bytes -> 5 bytes (at worst 8 -> 5) N > 1 unequal words: 4N bytes -> 4N + M, where M is the number of bytes required to encode the run length. The worst that can happen is that the method begins with unequal words, and than has interspersed occurrences of a word with equal bytes. Thus we require a run-length at the beginning, and after every interspersed word of equal bytes. However, each of these saves 2 bytes, so it must be followed by a run of 1984 (7936//4) or more (for which M jumps from 2 to 5) to add any extra overhead. Therefore the worst case is a series of runs of 1984 or more, with single interspersed words of equal bytes. At each break we save 2 bytes, but add 5. Thus the overhead would be no more than 5 (encoded size) + 2 (first run len) + (S//1984*3)." "NOTE: This code is copied in Form hibernate for reasons given there." byteArray _ ByteArray new: (self size*4) + 7 + (self size//1984*3). lastByte _ self compress: self toByteArray: byteArray. ^ byteArray copyFrom: 1 to: lastByte! ! !Bitmap methodsFor: 'filing' stamp: 'ar 2/3/2001 16:11'! decompress: bm fromByteArray: ba at: index "Decompress the body of a byteArray encoded by compressToByteArray (qv)... The format is simply a sequence of run-coded pairs, {N D}*. N is a run-length * 4 + data code. D, the data, depends on the data code... 0 skip N words, D is absent (could be used to skip from one raster line to the next) 1 N words with all 4 bytes = D (1 byte) 2 N words all = D (4 bytes) 3 N words follow in D (4N bytes) S and N are encoded as follows (see decodeIntFrom:)... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes" "NOTE: If fed with garbage, this routine could read past the end of ba, but it should fail before writing past the ned of bm." | i code n anInt data end k pastEnd | self var: #bm declareC: 'int *bm'. self var: #ba declareC: 'unsigned char *ba'. i _ index. "byteArray read index" end _ ba size. k _ 1. "bitmap write index" pastEnd _ bm size + 1. [i <= end] whileTrue: ["Decode next run start N" anInt _ ba at: i. i _ i+1. anInt <= 223 ifFalse: [anInt <= 254 ifTrue: [anInt _ (anInt-224)*256 + (ba at: i). i _ i+1] ifFalse: [anInt _ 0. 1 to: 4 do: [:j | anInt _ (anInt bitShift: 8) + (ba at: i). i _ i+1]]]. n _ anInt >> 2. (k + n) > pastEnd ifTrue: [^ self primitiveFail]. code _ anInt bitAnd: 3. code = 0 ifTrue: ["skip"]. code = 1 ifTrue: ["n consecutive words of 4 bytes = the following byte" data _ ba at: i. i _ i+1. data _ data bitOr: (data bitShift: 8). data _ data bitOr: (data bitShift: 16). 1 to: n do: [:j | bm at: k put: data. k _ k+1]]. code = 2 ifTrue: ["n consecutive words = 4 following bytes" data _ 0. 1 to: 4 do: [:j | data _ (data bitShift: 8) bitOr: (ba at: i). i _ i+1]. 1 to: n do: [:j | bm at: k put: data. k _ k+1]]. code = 3 ifTrue: ["n consecutive words from the data..." 1 to: n do: [:m | data _ 0. 1 to: 4 do: [:j | data _ (data bitShift: 8) bitOr: (ba at: i). i _ i+1]. bm at: k put: data. k _ k+1]]]! ! !Bitmap methodsFor: 'filing' stamp: 'jm 2/15/98 17:27'! encodeBytesOf: anInt in: ba at: i "Copy the integer anInt into byteArray ba at index i, and return the next index" self inline: true. self var: #ba declareC: 'unsigned char *ba'. 0 to: 3 do: [:j | ba at: i+j put: (anInt >> (3-j*8) bitAnd: 16rFF)]. ^ i+4! ! !Bitmap methodsFor: 'filing' stamp: 'jm 2/12/98 17:32'! encodeInt: int "Encode the integer int as per encodeInt:in:at:, and return it as a ByteArray" | byteArray next | byteArray _ ByteArray new: 5. next _ self encodeInt: int in: byteArray at: 1. ^ byteArray copyFrom: 1 to: next - 1 ! ! !Bitmap methodsFor: 'filing' stamp: 'jm 2/15/98 17:26'! encodeInt: anInt in: ba at: i "Encode the integer anInt in byteArray ba at index i, and return the next index. The encoding is as follows... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes" self inline: true. self var: #ba declareC: 'unsigned char *ba'. anInt <= 223 ifTrue: [ba at: i put: anInt. ^ i+1]. anInt <= 7935 ifTrue: [ba at: i put: anInt//256+224. ba at: i+1 put: anInt\\256. ^ i+2]. ba at: i put: 255. ^ self encodeBytesOf: anInt in: ba at: i+1! ! !Bitmap methodsFor: 'filing' stamp: 'di 2/11/98 21:34'! readCompressedFrom: strm "Decompress an old-style run-coded stream into this bitmap: [0 means end of runs] [n = 1..127] [(n+3) copies of next byte] [n = 128..191] [(n-127) next bytes as is] [n = 192..255] [(n-190) copies of next 4 bytes]" | n byte out outBuff bytes | out _ WriteStream on: (outBuff _ ByteArray new: self size*4). [(n _ strm next) > 0] whileTrue: [(n between: 1 and: 127) ifTrue: [byte _ strm next. 1 to: n+3 do: [:i | out nextPut: byte]]. (n between: 128 and: 191) ifTrue: [1 to: n-127 do: [:i | out nextPut: strm next]]. (n between: 192 and: 255) ifTrue: [bytes _ (1 to: 4) collect: [:i | strm next]. 1 to: n-190 do: [:i | bytes do: [:b | out nextPut: b]]]]. out position = outBuff size ifFalse: [self error: 'Decompression size error']. "Copy the final byteArray into self" self copyFromByteArray: outBuff.! ! !Bitmap methodsFor: 'filing' stamp: 'tk 1/24/2000 22:37'! restoreEndianness "This word object was just read in from a stream. Bitmaps are always compressed and serialized in a machine-independent way. Do not correct the Endianness." "^ self" ! ! !Bitmap methodsFor: 'filing'! storeBits:startBit to:stopBit on:aStream self do: [:word | startBit to:stopBit by:-4 do:[:shift | aStream print:((word >>shift) bitAnd:15) asHexDigit. ] ].! ! !Bitmap methodsFor: 'filing' stamp: 'di 10/2/97 00:02'! swapBytesFrom: start to: stop "Perform a bigEndian/littleEndian byte reversal of my words" | hack blt | "The implementation is a hack, but fast for large ranges" hack _ Form new hackBits: self. blt _ (BitBlt toForm: hack) sourceForm: hack. blt combinationRule: Form reverse. "XOR" blt sourceY: start-1; destY: start-1; height: stop-start+1; width: 1. blt sourceX: 0; destX: 3; copyBits. "Exchange bytes 0 and 3" blt sourceX: 3; destX: 0; copyBits. blt sourceX: 0; destX: 3; copyBits. blt sourceX: 1; destX: 2; copyBits. "Exchange bytes 1 and 2" blt sourceX: 2; destX: 1; copyBits. blt sourceX: 1; destX: 2; copyBits. ! ! !Bitmap methodsFor: 'filing' stamp: 'jm 2/18/98 14:19'! writeOn: aStream "Store the array of bits onto the argument, aStream. A leading byte of 16r80 identifies this as compressed by compressToByteArray (qv)." | b | aStream nextPut: 16r80. b _ self compressToByteArray. aStream nextPutAll: (self encodeInt: b size); nextPutAll: b. ! ! !Bitmap methodsFor: 'filing' stamp: 'tk 2/19/1999 07:36'! writeUncompressedOn: aStream "Store the array of bits onto the argument, aStream. (leading byte ~= 16r80) identifies this as raw bits (uncompressed)." aStream nextInt32Put: self size. aStream nextPutAll: self ! ! !Bitmap methodsFor: 'printing' stamp: 'sma 6/1/2000 09:42'! printOn: aStream self printNameOn: aStream. aStream nextPutAll: ' of length '; print: self size! ! !Bitmap methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:00'! printOnStream: aStream aStream print: 'a Bitmap of length '; write:self size. ! ! !Bitmap methodsFor: 'accessing'! bitPatternForDepth: depth "The raw call on BitBlt needs a Bitmap to represent this color. I already am Bitmap like. I am already adjusted for a specific depth. Interpret me as an array of (32/depth) Color pixelValues. BitBlt aligns the first element of this array with the top scanline of the destinationForm, the second with the second, and so on, cycling through the color array as necessary. 6/18/96 tk" ^ self! ! !Bitmap methodsFor: 'accessing'! byteAt: byteAddress "Extract a byte from a Bitmap. Note that this is a byte address and it is one-order. For repeated use, create an instance of BitBlt and use pixelAt:. See Form pixelAt: 7/1/96 tk" | lowBits | lowBits _ byteAddress - 1 bitAnd: 3. ^((self at: byteAddress - 1 - lowBits // 4 + 1) bitShift: (lowBits - 3) * 8) bitAnd: 16rFF! ! !Bitmap methodsFor: 'accessing'! byteAt: byteAddress put: byte "Insert a byte into a Bitmap. Note that this is a byte address and it is one-order. For repeated use, create an instance of BitBlt and use pixelAt:put:. See Form pixelAt:put: 7/1/96 tk" | longWord shift lowBits longAddr | lowBits _ byteAddress - 1 bitAnd: 3. longWord _ self at: (longAddr _ (byteAddress - 1 - lowBits) // 4 + 1). shift _ (3 - lowBits) * 8. longWord _ longWord - (longWord bitAnd: (16rFF bitShift: shift)) + (byte bitShift: shift). self at: longAddr put: longWord. ^ byte! ! !Bitmap methodsFor: 'accessing' stamp: 'di 10/4/97 11:56'! copyFromByteArray: byteArray "This method should work with either byte orderings" | long | (self size * 4) = byteArray size ifFalse: [self halt]. 1 to: byteArray size by: 4 do: [:i | long _ Integer byte1: (byteArray at: i+3) byte2: (byteArray at: i+2) byte3: (byteArray at: i+1) byte4: (byteArray at: i). self at: i+3//4 put: long]! ! !Bitmap methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:19'! defaultElement "Return the default element of the receiver" ^0! ! !Bitmap methodsFor: 'accessing' stamp: 'tk 3/15/97'! pixelValueForDepth: depth "Self is being used to represent a single color. Answer bits that appear in ONE pixel of this color in a Bitmap of the given depth. The depth must be one of 1, 2, 4, 8, 16, or 32. Returns an integer. First pixel only. " ^ (self at: 1) bitAnd: (1 bitShift: depth) - 1! ! !Bitmap methodsFor: 'accessing'! primFill: aPositiveInteger "Fill the receiver, an indexable bytes or words object, with the given positive integer. The range of possible fill values is [0..255] for byte arrays and [0..(2^32 - 1)] for word arrays." self errorImproperStore.! ! !Bitmap methodsFor: 'accessing'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." super replaceFrom: start to: stop with: replacement startingAt: repStart! ! !Bitmap methodsFor: 'testing' stamp: 'ar 5/25/2000 19:42'! isColormap "Bitmaps were used as color maps for BitBlt. This method allows to recognize real color maps." ^false! ! !Bitmap methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 21:51'! copy ^self clone! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Bitmap class instanceVariableNames: ''! !Bitmap class methodsFor: 'instance creation' stamp: 'di 2/9/98 16:02'! decodeIntFrom: s "Decode an integer in stream s as follows... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes " | int | int _ s next. int <= 223 ifTrue: [^ int]. int <= 254 ifTrue: [^ (int-224)*256 + s next]. int _ s next. 1 to: 3 do: [:j | int _ (int bitShift: 8) + s next]. ^ int! ! !Bitmap class methodsFor: 'instance creation' stamp: 'di 2/12/98 14:34'! decompressFromByteArray: byteArray | s bitmap size | s _ ReadStream on: byteArray. size _ self decodeIntFrom: s. bitmap _ self new: size. bitmap decompress: bitmap fromByteArray: byteArray at: s position+1. ^ bitmap! ! !Bitmap class methodsFor: 'instance creation' stamp: 'ar 12/23/1999 14:35'! newFromStream: s | len | s next = 16r80 ifTrue: ["New compressed format" len _ self decodeIntFrom: s. ^ Bitmap decompressFromByteArray: (s nextInto: (ByteArray new: len))]. s skip: -1. len _ s nextInt32. len <= 0 ifTrue: ["Old compressed format" ^ (self new: len negated) readCompressedFrom: s] ifFalse: ["Old raw data format" ^ s nextWordsInto: (self new: len)]! ! OrientedFillStyle subclass: #BitmapFillStyle instanceVariableNames: 'form tileFlag ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Fills'! !BitmapFillStyle methodsFor: 'accessing' stamp: 'ar 11/11/1998 22:40'! form ^form! ! !BitmapFillStyle methodsFor: 'accessing' stamp: 'ar 11/11/1998 22:40'! form: aForm form _ aForm! ! !BitmapFillStyle methodsFor: 'accessing' stamp: 'ar 11/27/1998 14:37'! tileFlag ^tileFlag! ! !BitmapFillStyle methodsFor: 'accessing' stamp: 'ar 11/27/1998 14:30'! tileFlag: aBoolean tileFlag _ aBoolean! ! !BitmapFillStyle methodsFor: 'testing' stamp: 'ar 11/11/1998 22:40'! isBitmapFill ^true! ! !BitmapFillStyle methodsFor: 'testing' stamp: 'ar 11/27/1998 14:37'! isTiled "Return true if the receiver should be repeated if the fill shape is larger than the form" ^tileFlag == true! ! !BitmapFillStyle methodsFor: 'testing' stamp: 'ar 9/2/1999 14:31'! isTranslucent "Return true since the bitmap may be translucent and we don't really want to check" ^true! ! !BitmapFillStyle methodsFor: 'converting' stamp: 'ar 11/11/1998 22:41'! asColor ^form colorAt: 0@0! ! !BitmapFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 12:05'! addFillStyleMenuItems: aMenu hand: aHand from: aMorph "Add the items for changing the current fill style of the receiver" aMenu add: 'choose new graphic' target: self selector: #chooseNewGraphicIn:event: argument: aMorph. aMenu add: 'grab new graphic' target: self selector: #grabNewGraphicIn:event: argument: aMorph. super addFillStyleMenuItems: aMenu hand: aHand from: aMorph.! ! !BitmapFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 11:55'! chooseNewGraphicIn: aMorph event: evt "Used by any morph that can be represented by a graphic" | reasonableForms aGraphicalMenu myGraphic | reasonableForms _ (SketchMorph allSubInstances collect: [:m | m form]) asOrderedCollection. reasonableForms addAll: (Smalltalk imageImports collect: [:f | f]). reasonableForms addAll: (BitmapFillStyle allSubInstances collect:[:f| f form]). reasonableForms _ reasonableForms asSet asOrderedCollection. (reasonableForms includes: (myGraphic _ self form)) ifTrue: [reasonableForms remove: myGraphic]. reasonableForms addFirst: myGraphic. aGraphicalMenu _ GraphicalMenu new initializeFor: self withForms: reasonableForms coexist: true. aGraphicalMenu selector: #newForm:forMorph:; argument: aMorph. evt hand attachMorph: aGraphicalMenu.! ! !BitmapFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 12:06'! grabNewGraphicIn: aMorph event: evt "Used by any morph that can be represented by a graphic" self form: Form fromUser. self direction: self form width @ 0. self normal: 0 @ self form height. aMorph changed.! ! !BitmapFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 11:57'! newForm: aForm forMorph: aMorph self form: aForm. self direction: (aForm width @ 0). self normal: (0 @ aForm height). aMorph changed.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitmapFillStyle class instanceVariableNames: ''! !BitmapFillStyle class methodsFor: 'instance creation' stamp: 'ar 11/13/1998 20:32'! form: aForm ^self new form: aForm! ! !BitmapFillStyle class methodsFor: 'instance creation' stamp: 'ar 6/25/1999 12:01'! fromForm: aForm | fs | fs _ self form: aForm. fs direction: aForm width @ 0. fs normal: 0 @ aForm height. fs tileFlag: true. ^fs! ! !BitmapFillStyle class methodsFor: 'instance creation' stamp: 'ar 6/18/1999 07:09'! fromUser | fill | fill _ self form: Form fromUser. fill origin: 0@0. fill direction: fill form width @ 0. fill normal: 0 @ fill form height. fill tileFlag: true. "So that we can fill arbitrary objects" ^fill! ! PolygonMorph subclass: #BlobMorph instanceVariableNames: 'random velocity sneaky ' classVariableNames: 'AllBlobs ' poolDictionaries: '' category: 'Morphic-Demo'! !BlobMorph commentStamp: '' prior: 0! The Blob was written by David N Smith. It started out as a simple test of the CurveMorph and ended up as an oozing, pulsating, repulsive mess which will wander across your screen until killed. Each instance has its own rate of oozing, so some are faster than others. It's not good for anything. Try: BlobMorph new openInWorld 15 Jan 2000 by Bob Arning, a change so that the blob tries to be a color like the color under itself. 16 Jan 2000 by David N Smith, added blob merging: if two blobs meet then one eats the other. 18 Jan 2000 by Sean McGrath, smother color changes. 06 Feb 2000 by Stefan Matthias Aust, refactoring and support for duplication, dragging and translucent colors.! !BlobMorph methodsFor: 'copying' stamp: 'sma 2/6/2000 18:07'! veryDeepCopy ^ self class remember: super veryDeepCopy! ! !BlobMorph methodsFor: 'geometry' stamp: 'sma 2/6/2000 18:39'! setConstrainedPositionFrom: aPoint "Deal with dragging the blob over another blob which results in spontaneous deletations." self owner ifNil: [^ self]. super setConstrainedPositionFrom: aPoint! ! !BlobMorph methodsFor: 'initialization' stamp: 'di 9/7/2000 17:21'! initialize super initialize. self beSmoothCurve. random _ Random new. sneaky _ random next < 0.75. self initializeColor. self initializeBlobShape. self setVelocity! ! !BlobMorph methodsFor: 'initialization' stamp: 'sma 2/6/2000 18:22'! initializeBlobShape self vertices: {59@40. 74@54. 79@74. 77@93. 57@97. 37@97. 22@83. 15@67. 22@50. 33@35. 47@33} color: self color borderWidth: 1 borderColor: Color black! ! !BlobMorph methodsFor: 'initialization' stamp: 'dns 2/9/2000 16:37'! initializeColor color _ random next < 0.25 ifTrue: [Color random] ifFalse: [Color random alpha: random next * 0.4 + 0.4]! ! !BlobMorph methodsFor: 'initialization' stamp: 'sma 2/6/2000 18:28'! maximumVelocity ^ 6.0! ! !BlobMorph methodsFor: 'initialization' stamp: 'sma 2/6/2000 18:28'! setVelocity velocity _ ((random next - 0.5) * self maximumVelocity) @ ((random next - 0.5) * self maximumVelocity)! ! !BlobMorph methodsFor: 'stepping' stamp: 'tk 7/4/2000 12:02'! adjustColors "Bob Arning " "Color mixing - Sean McGrath " | nearbyColors center r degrees | center _ bounds center. nearbyColors _ vertices collect: [:each | degrees _ (each - center) degrees. r _ (each - center) r. Display colorAt: (Point r: r + 6 degrees: degrees) + center]. self color: ((self color alphaMixed: 0.95 with: (Color r: (nearbyColors collect: [:each | each red]) average g: (nearbyColors collect: [:each | each green]) average b: (nearbyColors collect: [:each | each blue]) average)) alpha: self color alpha). sneaky ifFalse: [self color: color negated]! ! !BlobMorph methodsFor: 'stepping' stamp: 'sma 3/24/2000 11:40'! bounceOffWalls " Change sign of velocity when we hit a wall of the container " | ob sb | " If owned by a handmorph we're being dragged or something; don't bounce since the boundaries are different than our real parent " owner isHandMorph ifTrue: [ ^ self ]. " If we're entirely within the parents bounds, we don't bounce " ob := owner bounds. sb := self bounds. (ob containsRect: sb) ifTrue: [ ^ self ]. " We're partly outside the parents bounds; better bounce or we disappear!! " sb top < ob top ifTrue: [ velocity := velocity x @ velocity y abs ]. sb left < ob left ifTrue: [ velocity := velocity x abs @ velocity y ]. sb bottom > ob bottom ifTrue: [ velocity := velocity x @ velocity y abs negated ]. sb right > ob right ifTrue: [ velocity := velocity x abs negated @ velocity y ]. ! ! !BlobMorph methodsFor: 'stepping' stamp: 'dns 1/16/2000 16:29'! limitRange: verts " limit radius to range 20-120; limit interpoint angle to surrounding angles with max of twice of average separation. " | cent new prevn nextn prevDeg nextDeg thisDeg dincr | cent := self bounds center. new := Array new: verts size. dincr := 360 // verts size. verts doWithIndex: [ :pt :n | "Find prev/next points, allowing for wrapping around " prevn := n-1 < 1 ifTrue: [new size] ifFalse: [n-1]. nextn := n+1 > new size ifTrue: [1] ifFalse: [n+1]. "Get prev/this/next point's angles " prevDeg := ((verts at: prevn)-cent) degrees. thisDeg := ((verts at: n)-cent) degrees. nextDeg := ((verts at: nextn)-cent) degrees. "Adjust if this is where angles wrap from 0 to 360" (thisDeg - prevDeg) abs > 180 ifTrue: [ prevDeg := prevDeg - 360 ]. (thisDeg - nextDeg) abs > 180 ifTrue: [ nextDeg := nextDeg + 360 ]. "Put adjusted point into new collection" new at: n put: cent + (self selfPolarPointRadius: ((((pt - cent) r) min: 80) max: 20) degrees: (((thisDeg min: nextDeg-5) max: prevDeg+5) min: dincr*2+prevDeg)) ]. ^ new ! ! !BlobMorph methodsFor: 'stepping' stamp: 'sma 2/6/2000 18:36'! mergeBlobs "See if we need to merge by checking our bounds against all other Blob bounds, then all our vertices against any Blob with overlapping bounds. If we find a need to merge, then someone else does all the work." AllBlobs size < 2 ifTrue: [^ self]. AllBlobs do: [:aBlob | aBlob owner == self owner ifTrue: [(self bounds intersects: aBlob bounds) ifTrue: [vertices do: [:aPoint | (aBlob containsPoint: aPoint) ifTrue: [^ self mergeSelfWithBlob: aBlob atPoint: aPoint]]]]] without: self! ! !BlobMorph methodsFor: 'stepping' stamp: 'dns 1/17/2000 13:34'! mergeSelfWithBlob: aBlob atPoint: aPoint " It has already been determined that we merge with aBlob; we do all the work here. " | v v2 c | c := self bounds center. " Merge the vertices by throwing them all together in one pot " v := vertices, aBlob vertices. " Sort the vertices by degrees to keep them in order " v := (v asSortedCollection: [ :a :b | (a-c) degrees < (b-c) degrees ]) asArray. " Now, pick half of the vertices so the count stays the same " v2 := Array new: v size // 2. 1 to: v2 size do: [ :n | v2 at: n put: (v at: n+n) ]. v := v2. " Average each contiguous pair to help minimize jaggies " 2 to: v size do: [ :n | v at: n put: ((v at: n) + (v at: n-1)) / 2.0 ]. " Remember the new vertices, set a new velocity, then delete the merged blob " vertices := v. self setVelocity. aBlob delete ! ! !BlobMorph methodsFor: 'stepping' stamp: 'dns 1/17/2000 13:36'! oozeAFewPointsOf: verts " change some points at random to cause oozing across screen " | n v | (verts size sqrt max: 2) floor timesRepeat: [ n := (verts size * random next) floor + 1. v := verts at: n. v := (v x + (random next * 2.0 - 1.0)) @ (v y + (random next * 2.0 - 1.0)). verts at: n put: v + velocity ]. ! ! !BlobMorph methodsFor: 'stepping' stamp: 'dns 1/14/2000 17:47'! selfPolarPointRadius: rho degrees: theta " Same as Point>>#r:degrees: in Point class except that x and y are not truncated to integers " | radians x y | radians _ theta asFloat degreesToRadians. x _ rho asFloat * radians cos. y _ rho asFloat * radians sin. ^ Point x: x y: y! ! !BlobMorph methodsFor: 'stepping' stamp: 'sma 2/12/2000 13:09'! step | verts | self comeToFront. self mergeBlobs. verts := vertices copy. " change two points at random to cause oozing across screen " self oozeAFewPointsOf: verts. " limit radius and interpoint angle " verts := self limitRange: verts. " Set new vertices; bounce off a wall if necessary " self setVertices: verts. self bounceOffWalls. self adjustColors ! ! !BlobMorph methodsFor: 'stepping' stamp: 'sma 2/6/2000 18:41'! stepTime "Answer the desired time between steps in milliseconds." ^ 125! ! !BlobMorph methodsFor: 'submorphs-add/remove' stamp: 'sma 2/6/2000 17:41'! delete self class delete: self. super delete! ! !BlobMorph methodsFor: 'debug and other' stamp: 'sma 2/12/2000 13:08'! installModelIn: aWorld "Overwritten to not add handles to the receiver."! ! !BlobMorph methodsFor: 'geometry testing' stamp: 'sma 2/12/2000 13:10'! containsPoint: aPoint (self color alpha = 1.0 or: [Sensor blueButtonPressed]) ifTrue: [^ super containsPoint: aPoint]. ^ false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BlobMorph class instanceVariableNames: ''! !BlobMorph class methodsFor: 'instance creation' stamp: 'dns 1/16/2000 15:11'! new ^ self remember: super new ! ! !BlobMorph class methodsFor: 'instance remembering' stamp: 'sma 2/6/2000 18:36'! delete: anInstance AllBlobs ifNotNil: [AllBlobs remove: anInstance ifAbsent: []]! ! !BlobMorph class methodsFor: 'instance remembering' stamp: 'sma 2/6/2000 18:35'! remember: anInstance AllBlobs isNil ifTrue: [AllBlobs := IdentitySet new]. ^ AllBlobs add: anInstance! ! ParseNode subclass: #BlockArgsNode instanceVariableNames: 'temporaries ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! Exception subclass: #BlockCannotReturn instanceVariableNames: 'result ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !BlockCannotReturn commentStamp: '' prior: 0! This class is private to the EHS implementation. Its use allows for ensured execution to survive code such as: [self doThis. ^nil] ensure: [self doThat] Signaling or handling this exception is not recommended.! !BlockCannotReturn methodsFor: 'accessing' stamp: 'tfei 3/30/1999 12:54'! result ^result! ! !BlockCannotReturn methodsFor: 'accessing' stamp: 'tfei 3/30/1999 12:54'! result: r result := r! ! !BlockCannotReturn methodsFor: 'exceptionDescription' stamp: 'tfei 3/30/1999 12:55'! defaultAction self messageText: 'Block cannot return'. ^super defaultAction! ! !BlockCannotReturn methodsFor: 'exceptionDescription' stamp: 'tfei 4/2/1999 15:49'! isResumable ^true! ! ContextPart variableSubclass: #BlockContext instanceVariableNames: 'nargs startpc home ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Methods'! !BlockContext commentStamp: '' prior: 0! My instances function similarly to instances of MethodContext, but they hold the dynamic state for execution of a block in Smalltalk. They access all temporary variables and the method sender via their home pointer, so that those values are effectively shared. Their indexable part is used to store their independent value stack during execution. My instance must hold onto its home in order to work. This can cause circularities if the home is also pointing (via a temp, perhaps) to the instance. In the rare event that this happens (as in SortedCollection sortBlock:) the message fixTemps will replace home with a copy of home, thus defeating the sharing of temps but, nonetheless, eliminating the circularity. BlockContexts must only be created using the method newForMethod:. Note that it is impossible to determine the real object size of a BlockContext except by asking for the frameSize of its method. Any fields above the stack pointer (stackp) are truly invisible -- even (and especially!!) to the garbage collector. Any store into stackp other than by the primitive method stackp: is potentially fatal.! !BlockContext methodsFor: 'initialize-release' stamp: 'ls 6/21/2000 17:42'! home: aContextPart startpc: position nargs: anInteger "This is the initialization message. The receiver has been initialized with the correct size only." home _ aContextPart. pc _ startpc _ position. nargs _ anInteger. stackp _ 0.! ! !BlockContext methodsFor: 'accessing' stamp: 'di 9/9/2000 10:44'! copyForSaving "Fix the values of the temporary variables used in the block that are ordinarily shared with the method in which the block is defined." home _ home copy. home swapSender: nil! ! !BlockContext methodsFor: 'accessing'! fixTemps "Fix the values of the temporary variables used in the block that are ordinarily shared with the method in which the block is defined." home _ home copy. home swapSender: nil! ! !BlockContext methodsFor: 'accessing' stamp: 'RAA 1/5/2001 08:50'! hasInstVarRef "Answer whether the receiver references an instance variable." | method scanner end printer | home ifNil: [^false]. method _ self method. "Determine end of block from long jump preceding it" end _ (method at: startpc-2)\\16-4*256 + (method at: startpc-1) + startpc - 1. scanner _ InstructionStream new method: method pc: startpc. printer _ InstVarRefLocator new. [scanner pc <= end] whileTrue: [ (printer interpretNextInstructionUsing: scanner) ifTrue: [^true]. ]. ^false! ! !BlockContext methodsFor: 'accessing'! hasMethodReturn "Answer whether the receiver has a return ('^') in its code." | method scanner end | method _ self method. "Determine end of block from long jump preceding it" end _ (method at: startpc-2)\\16-4*256 + (method at: startpc-1) + startpc - 1. scanner _ InstructionStream new method: method pc: startpc. scanner scanFor: [:byte | (byte between: 120 and: 124) or: [scanner pc > end]]. ^scanner pc <= end! ! !BlockContext methodsFor: 'accessing'! home "Answer the context in which the receiver was defined." ^home! ! !BlockContext methodsFor: 'accessing'! method "Answer the compiled method in which the receiver was defined." ^home method! ! !BlockContext methodsFor: 'accessing'! numArgs ^nargs! ! !BlockContext methodsFor: 'accessing'! receiver "Refer to the comment in ContextPart|receiver." ^home receiver! ! !BlockContext methodsFor: 'accessing'! tempAt: index "Refer to the comment in ContextPart|tempAt:." ^home at: index! ! !BlockContext methodsFor: 'accessing'! tempAt: index put: value "Refer to the comment in ContextPart|tempAt:put:." ^home at: index put: value! ! !BlockContext methodsFor: 'evaluating' stamp: 'bf 9/27/1999 16:50'! ifError: errorHandlerBlock "Evaluate the block represented by the receiver. If an error occurs the given is evaluated with the error message and the receiver as parameters. The error handler block may return a value to be used if the receiver block gets an error. The receiver should not contain an explicit return statement as this would leave an obsolete error handler hanging around." "Examples: [1 whatsUpDoc] ifError: [:err :rcvr | ^ 'huh?']. [1 / 0] ifError: [:err :rcvr | 'ZeroDivide' = err ifTrue: [^ Float infinity] ifFalse: [self error: err]] " | lastHandler val activeProcess | activeProcess _ Processor activeProcess. lastHandler _ activeProcess errorHandler. activeProcess errorHandler: [:aString :aReceiver | activeProcess errorHandler: lastHandler. ^ errorHandlerBlock value: aString value: aReceiver]. val _ self on: Error do: [:ex | activeProcess errorHandler: lastHandler. ^errorHandlerBlock value: ex description value: ex receiver]. activeProcess errorHandler: lastHandler. ^ val ! ! !BlockContext methodsFor: 'evaluating' stamp: 'jm 6/3/1998 14:25'! timeToRun "Answer the number of milliseconds taken to execute this block." ^ Time millisecondsToRun: self ! ! !BlockContext methodsFor: 'evaluating'! value "Primitive. Evaluate the block represented by the receiver. Fail if the block expects any arguments or if the block is already being executed. Optional. No Lookup. See Object documentation whatIsAPrimitive." ^self valueWithArguments: #()! ! !BlockContext methodsFor: 'evaluating'! value: arg "Primitive. Evaluate the block represented by the receiver. Fail if the block expects other than one argument or if the block is already being executed. Optional. No Lookup. See Object documentation whatIsAPrimitive." ^self valueWithArguments: (Array with: arg)! ! !BlockContext methodsFor: 'evaluating'! value: arg1 ifError: aBlock "Evaluate the block represented by the receiver. If an error occurs aBlock is evaluated with the error message and the receiver as parameters. The receiver should not contain an explicit return statement as this would leave an obsolete error handler hanging around." | lastHandler val activeProcess | activeProcess _ Processor activeProcess. lastHandler _ activeProcess errorHandler. activeProcess errorHandler: [:aString :aReceiver | activeProcess errorHandler: lastHandler. ^ aBlock value: aString value: aReceiver]. val _ self value: arg1. activeProcess errorHandler: lastHandler. ^ val! ! !BlockContext methodsFor: 'evaluating'! value: arg1 value: arg2 "Primitive. Evaluate the block represented by the receiver. Fail if the block expects other than two arguments or if the block is already being executed. Optional. See Object documentation whatIsAPrimitive." ^self valueWithArguments: (Array with: arg1 with: arg2)! ! !BlockContext methodsFor: 'evaluating'! value: arg1 value: arg2 value: arg3 "Primitive. Evaluate the block represented by the receiver. Fail if the block expects other than three arguments or if the block is already being executed. Optional. See Object documentation whatIsAPrimitive." ^self valueWithArguments: (Array with: arg1 with: arg2 with: arg3)! ! !BlockContext methodsFor: 'evaluating' stamp: 'di 11/30/97 09:19'! value: arg1 value: arg2 value: arg3 value: arg4 "Primitive. Evaluate the block represented by the receiver. Fail if the block expects other than three arguments or if the block is already being executed. Optional. See Object documentation whatIsAPrimitive." ^self valueWithArguments: (Array with: arg1 with: arg2 with: arg3 with: arg4)! ! !BlockContext methodsFor: 'evaluating' stamp: 'mdr 10/5/2000 10:33'! valueWithArguments: anArray "Primitive. Evaluate the block represented by the receiver. The argument is an Array whose elements are the arguments for the block. Fail if the length of the Array is not the same as the the number of arguments that the block was expecting. Fail if the block is already being executed. Essential. See Object documentation whatIsAPrimitive." self numArgs = anArray size ifTrue: [self error: 'Attempt to evaluate a block that is already being evaluated.'] ifFalse: [self error: 'This block requires ' , self numArgs printString , ' arguments and is being evaluated with ', anArray size printString]! ! !BlockContext methodsFor: 'controlling' stamp: 'sma 5/12/2000 13:22'! repeat "Evaluate the receiver repeatedly, ending only if the block explicitly returns." [self value. true] whileTrue! ! !BlockContext methodsFor: 'controlling' stamp: 'ls 9/24/1999 09:45'! repeatWithGCIf: testBlock | ans | "run the receiver, and if testBlock returns true, garbage collect and run the receiver again" ans _ self value. (testBlock value: ans) ifTrue: [ Smalltalk garbageCollect. ans _ self value ]. ^ans! ! !BlockContext methodsFor: 'controlling'! whileFalse "Ordinarily compiled in-line, and therefore not overridable. This is in case the message is sent to other than a literal block. Evaluate the receiver, as long as its value is false." ^ [self value] whileFalse: []! ! !BlockContext methodsFor: 'controlling'! whileFalse: aBlock "Ordinarily compiled in-line, and therefore not overridable. This is in case the message is sent to other than a literal block. Evaluate the argument, aBlock, as long as the value of the receiver is false." ^ [self value] whileFalse: [aBlock value]! ! !BlockContext methodsFor: 'controlling'! whileTrue "Ordinarily compiled in-line, and therefore not overridable. This is in case the message is sent to other than a literal block. Evaluate the receiver, as long as its value is true." ^ [self value] whileTrue: []! ! !BlockContext methodsFor: 'controlling'! whileTrue: aBlock "Ordinarily compiled in-line, and therefore not overridable. This is in case the message is sent to other than a literal block. Evaluate the argument, aBlock, as long as the value of the receiver is true." ^ [self value] whileTrue: [aBlock value]! ! !BlockContext methodsFor: 'scheduling' stamp: 'di 9/12/1998 11:53'! fork "Create and schedule a Process running the code in the receiver." ^ self newProcess resume! ! !BlockContext methodsFor: 'scheduling' stamp: 'jm 11/9/1998 10:16'! forkAt: priority "Create and schedule a Process running the code in the receiver at the given priority. Answer the newly created process." | forkedProcess | forkedProcess _ self newProcess. forkedProcess priority: priority. ^ forkedProcess resume ! ! !BlockContext methodsFor: 'scheduling' stamp: 'ar 6/5/1998 21:44'! newProcess "Answer a Process running the code in the receiver. The process is not scheduled." "Simulation guard" ^Process forContext: [self value. Processor terminateActive] priority: Processor activePriority! ! !BlockContext methodsFor: 'scheduling' stamp: 'ar 6/5/1998 21:44'! newProcessWith: anArray "Answer a Process running the code in the receiver. The receiver's block arguments are bound to the contents of the argument, anArray. The process is not scheduled." "Simulation guard" ^Process forContext: [self valueWithArguments: anArray. Processor terminateActive] priority: Processor activePriority! ! !BlockContext methodsFor: 'instruction decoding'! blockReturnTop "Simulate the interpreter's action when a ReturnTopOfStack bytecode is encountered in the receiver." | save dest | save _ home. "Needed because return code will nil it" dest _ self return: self pop to: self sender. home _ save. sender _ nil. ^dest! ! !BlockContext methodsFor: 'printing'! printOn: aStream home == nil ifTrue: [^aStream nextPutAll: 'a BlockContext with home=nil']. aStream nextPutAll: '[] in '. super printOn: aStream! ! !BlockContext methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:01'! printOnStream: aStream home == nil ifTrue: [^aStream print: 'a BlockContext with home=nil']. aStream print: '[] in '. super printOnStream: aStream! ! !BlockContext methodsFor: 'private' stamp: 'tfei 3/31/1999 17:40'! cannotReturn: result "The receiver tried to return result to a method context that no longer exists." | ex newResult | ex := BlockCannotReturn new. ex result: result. newResult := ex signal. ^newResult! ! !BlockContext methodsFor: 'private' stamp: 'di 1/14/1999 22:28'! instVarAt: index put: value index = 3 ifTrue: [self stackp: value. ^ value]. ^ super instVarAt: index put: value! ! !BlockContext methodsFor: 'private'! startpc "for use by the System Tracer only" ^startpc! ! !BlockContext methodsFor: 'private'! valueError self error: 'Incompatible number of args, or already active'! ! !BlockContext methodsFor: 'system simulation' stamp: 'di 1/11/1999 10:24'! pushArgs: args from: sendr "Simulates action of the value primitive." args size ~= nargs ifTrue: [^self error: 'incorrect number of args']. self stackp: 0. args do: [:arg | self push: arg]. sender _ sendr. pc _ startpc! ! !BlockContext methodsFor: 'exceptions' stamp: 'sma 5/11/2000 19:38'! assert self assert: self! ! !BlockContext methodsFor: 'exceptions' stamp: 'tfei 6/5/1999 18:54'! ensure: aBlock "Evaluate a termination block after evaluating the receiver, regardless of whether the receiver's evaluation completes." | returnValue | returnValue := self valueUninterruptably. "aBlock wasn't nil when execution of this method began; it is nil'd out by the unwind machinery, and that's how we know it's already been evaluated ... otherwise, obviously, it needs to be evaluated" aBlock == nil ifFalse: [aBlock value]. ^returnValue! ! !BlockContext methodsFor: 'exceptions' stamp: 'tfei 6/5/1999 18:53'! ifCurtailed: aBlock "Evaluate the receiver with an abnormal termination action." ^self valueUninterruptably! ! !BlockContext methodsFor: 'exceptions' stamp: 'ikp 9/18/2000 21:42'! on: exception do: handlerAction "Evaluate the receiver in the scope of an exception handler." | handlerActive | handlerActive _ true. ^self value! ! !BlockContext methodsFor: 'private-exceptions' stamp: 'tfei 6/9/1999 16:39'! valueUninterruptably "Temporarily make my home Context unable to return control to its sender, to guard against circumlocution of the ensured behavior." | sendingContext result homeSender | sendingContext := thisContext sender sender. homeSender _ home swapSender: nil. [[result := self on: BlockCannotReturn do: [:ex | thisContext unwindTo: sendingContext. sendingContext home answer: ex result. ex return: ex result]] on: ExceptionAboutToReturn do: [:ex | home sender == nil ifTrue: [home swapSender: homeSender. ex resume: homeSender] ifFalse: [ex resume: nil]]] on: Exception do: [:ex | home swapSender: homeSender. ex pass]. home swapSender: homeSender. ^result! ! !BlockContext methodsFor: 'private-debugger' stamp: 'tfei 3/20/2000 00:24'! hideFromDebugger ^home ~~ nil and: [home hideFromDebugger]! ! !BlockContext methodsFor: 'Worlds' stamp: 'RAA 7/5/2000 11:13'! valueWithWorld: aWorldOrNil ^self on: RequestCurrentWorldNotification do: [ :ex | ex resume: aWorldOrNil ]! ! !BlockContext methodsFor: 'tiles' stamp: 'RAA 8/16/1999 13:52'! valueWithPossibleArgs: anArray self numArgs = 0 ifTrue: [^self value]. self numArgs = anArray size ifTrue: [^self valueWithArguments: anArray]. self numArgs > anArray size ifTrue: [ ^self valueWithArguments: anArray, (Array new: (self numArgs - anArray size)) ]. ^self valueWithArguments: (anArray copyFrom: 1 to: self numArgs) ! ! ParseNode subclass: #BlockNode instanceVariableNames: 'arguments statements returns nArgsNode size remoteCopyNode temporaries ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !BlockNode commentStamp: '' prior: 0! I represent a bracketed block with 0 or more arguments and 1 or more statements. If I am initialized with no statements, I create one. I have a flag to tell whether my last statement returns a value from the enclosing method. My last three fields remember data needed for code generation. I can emit for value in the usual way, in which case I create a literal method (actually a context remotely copied) to be evaluated by sending it value: at run time. Or I can emit code to be evaluated in line; this only happens at the top level of a method and in conditionals and while-loops, none of which have arguments.! !BlockNode methodsFor: 'initialize-release'! arguments: argNodes statements: statementsCollection returns: returnBool from: encoder "Compile." arguments _ argNodes. statements _ statementsCollection size > 0 ifTrue: [statementsCollection] ifFalse: [argNodes size > 0 ifTrue: [statementsCollection copyWith: arguments last] ifFalse: [Array with: NodeNil]]. returns _ returnBool! ! !BlockNode methodsFor: 'initialize-release' stamp: 'sma 3/3/2000 13:38'! statements: statementsCollection returns: returnBool "Decompile." | returnLast | returnLast _ returnBool. returns _ false. statements _ (statementsCollection size > 1 and: [(statementsCollection at: statementsCollection size - 1) isReturningIf]) ifTrue: [returnLast _ false. statementsCollection allButLast] ifFalse: [statementsCollection size = 0 ifTrue: [Array with: NodeNil] ifFalse: [statementsCollection]]. arguments _ #(). temporaries _ #(). returnLast ifTrue: [self returnLast]! ! !BlockNode methodsFor: 'accessing'! arguments: argNodes "Decompile." arguments _ argNodes! ! !BlockNode methodsFor: 'accessing' stamp: 'tk 8/4/1999 22:53'! block ^ self! ! !BlockNode methodsFor: 'accessing'! firstArgument ^ arguments first! ! !BlockNode methodsFor: 'accessing'! numberOfArguments ^arguments size! ! !BlockNode methodsFor: 'accessing'! returnLast self returns ifFalse: [returns _ true. statements at: statements size put: statements last asReturnNode]! ! !BlockNode methodsFor: 'accessing'! returnSelfIfNoOther self returns ifFalse: [statements last == NodeSelf ifFalse: [statements add: NodeSelf]. self returnLast]! ! !BlockNode methodsFor: 'accessing' stamp: 'sma 2/27/2000 22:37'! temporaries: aCollection temporaries _ aCollection! ! !BlockNode methodsFor: 'testing'! canBeSpecialArgument "Can I be an argument of (e.g.) ifTrue:?" ^arguments size = 0! ! !BlockNode methodsFor: 'testing'! isComplex ^statements size > 1 or: [statements size = 1 and: [statements first isComplex]]! ! !BlockNode methodsFor: 'testing'! isJust: node returns ifTrue: [^false]. ^statements size = 1 and: [statements first == node]! ! !BlockNode methodsFor: 'testing'! isJustCaseError ^ statements size = 1 and: [statements first isMessage: #caseError receiver: [:r | r==NodeSelf] arguments: nil]! ! !BlockNode methodsFor: 'testing'! isQuick ^ statements size = 1 and: [statements first isVariableReference or: [statements first isSpecialConstant]]! ! !BlockNode methodsFor: 'testing'! returns ^returns or: [statements last isReturningIf]! ! !BlockNode methodsFor: 'code generation'! code ^statements first code! ! !BlockNode methodsFor: 'code generation' stamp: 'di 11/19/1999 19:32'! emitExceptLast: stack on: aStream | nextToLast | nextToLast _ statements size - 1. nextToLast < 1 ifTrue: [^ self]. "Only one statement" 1 to: nextToLast do: [:i | (statements at: i) emitForEffect: stack on: aStream]. ! ! !BlockNode methodsFor: 'code generation'! emitForEvaluatedEffect: stack on: aStream self returns ifTrue: [self emitForEvaluatedValue: stack on: aStream. stack pop: 1] ifFalse: [self emitExceptLast: stack on: aStream. statements last emitForEffect: stack on: aStream]! ! !BlockNode methodsFor: 'code generation' stamp: 'di 11/19/1999 19:44'! emitForEvaluatedValue: stack on: aStream self emitExceptLast: stack on: aStream. statements last emitForValue: stack on: aStream. ! ! !BlockNode methodsFor: 'code generation'! emitForValue: stack on: aStream aStream nextPut: LdThisContext. stack push: 1. nArgsNode emitForValue: stack on: aStream. remoteCopyNode emit: stack args: 1 on: aStream. "Force a two byte jump." self emitLong: size code: JmpLong on: aStream. stack push: arguments size. arguments reverseDo: [:arg | arg emitStorePop: stack on: aStream]. self emitForEvaluatedValue: stack on: aStream. self returns ifFalse: [aStream nextPut: EndRemote]. stack pop: 1! ! !BlockNode methodsFor: 'code generation' stamp: 'di 11/19/1999 19:33'! sizeExceptLast: encoder | codeSize nextToLast | nextToLast _ statements size - 1. nextToLast < 1 ifTrue: [^ 0]. "Only one statement" codeSize _ 0. 1 to: nextToLast do: [:i | codeSize _ codeSize + ((statements at: i) sizeForEffect: encoder)]. ^ codeSize! ! !BlockNode methodsFor: 'code generation'! sizeForEvaluatedEffect: encoder self returns ifTrue: [^self sizeForEvaluatedValue: encoder]. ^(self sizeExceptLast: encoder) + (statements last sizeForEffect: encoder)! ! !BlockNode methodsFor: 'code generation'! sizeForEvaluatedValue: encoder ^(self sizeExceptLast: encoder) + (statements last sizeForValue: encoder)! ! !BlockNode methodsFor: 'code generation'! sizeForValue: encoder nArgsNode _ encoder encodeLiteral: arguments size. remoteCopyNode _ encoder encodeSelector: #blockCopy:. size _ (self sizeForEvaluatedValue: encoder) + (self returns ifTrue: [0] ifFalse: [1]). "endBlock" arguments _ arguments collect: "Chance to prepare debugger remote temps" [:arg | arg asStorableNode: encoder]. arguments do: [:arg | size _ size + (arg sizeForStorePop: encoder)]. ^1 + (nArgsNode sizeForValue: encoder) + (remoteCopyNode size: encoder args: 1 super: false) + 2 + size! ! !BlockNode methodsFor: 'printing' stamp: 'RAA 7/5/2000 11:43'! printArgumentsOn: aStream indent: level arguments size = 0 ifTrue: [^ self]. aStream dialect = #SQ00 ifTrue: [aStream withStyleFor: #setOrReturn do: [aStream nextPutAll: 'With']. arguments do: [:arg | aStream space. aStream withStyleFor: #blockArgument do: [aStream nextPutAll: arg key]]. aStream nextPutAll: '. '] ifFalse: [arguments do: [:arg | aStream withStyleFor: #blockArgument do: [aStream nextPutAll: ':'; nextPutAll: arg key; space]]. aStream nextPutAll: '| ']. "If >0 args and >1 statement, put all statements on separate lines" statements size > 1 ifTrue: [aStream crtab: level]! ! !BlockNode methodsFor: 'printing' stamp: 'di 5/1/2000 23:49'! printOn: aStream indent: level "statements size <= 1 ifFalse: [aStream crtab: level]." aStream nextPut: $[. self printArgumentsOn: aStream indent: level. self printTemporariesOn: aStream indent: level. self printStatementsOn: aStream indent: level. aStream nextPut: $]! ! !BlockNode methodsFor: 'printing' stamp: 'di 4/3/1999 23:25'! printStatementsOn: aStream indent: levelOrZero | len shown thisStatement level | level _ 1 max: levelOrZero. comment == nil ifFalse: [self printCommentOn: aStream indent: level. aStream crtab: level]. len _ shown _ statements size. (levelOrZero = 0 "top level" and: [statements last isReturnSelf]) ifTrue: [shown _ 1 max: shown - 1] ifFalse: [(len = 1 and: [((statements at: 1) == NodeNil) & (arguments size = 0)]) ifTrue: [shown _ shown - 1]]. 1 to: shown do: [:i | thisStatement _ statements at: i. thisStatement printOn: aStream indent: level. i < shown ifTrue: [aStream nextPut: $.; crtab: level]. (thisStatement comment ~~ nil and: [thisStatement comment size > 0]) ifTrue: [i = shown ifTrue: [aStream crtab: level]. thisStatement printCommentOn: aStream indent: level. i < shown ifTrue: [aStream crtab: level]]]! ! !BlockNode methodsFor: 'printing' stamp: 'di 4/5/2000 15:09'! printTemporariesOn: aStream indent: level (temporaries == nil or: [temporaries size = 0]) ifFalse: [aStream nextPut: $|. temporaries do: [:arg | aStream space; withStyleFor: #temporaryVariable do: [aStream nextPutAll: arg key]]. aStream nextPutAll: ' | '. "If >0 args and >1 statement, put all statements on separate lines" statements size > 1 ifTrue: [aStream crtab: level]]! ! !BlockNode methodsFor: 'equation translation'! statements ^statements! ! !BlockNode methodsFor: 'equation translation'! statements: val statements _ val! ! !BlockNode methodsFor: 'C translation' stamp: 'hg 8/14/2000 15:33'! asTranslatorNode | statementList newS | statementList _ OrderedCollection new. statements do: [ :s | newS _ s asTranslatorNode. newS isStmtList ifTrue: [ "inline the statement list returned when a CascadeNode is translated" statementList addAll: newS statements. ] ifFalse: [ statementList add: newS. ]. ]. ^TStmtListNode new setArguments: (arguments asArray collect: [ :arg | arg key ]) statements: statementList; comment: comment! ! !BlockNode methodsFor: 'tiles' stamp: 'di 11/13/2000 20:32'! asMorphicSyntaxIn: parent | row column | (column _ parent addColumn: #block on: self) layoutInset: 2@-1. self addCommentToMorph: column. arguments size > 0 ifTrue: [row _ column addRow: #blockarg1 on: (BlockArgsNode new). arguments do: [:arg | (arg asMorphicSyntaxIn: row) color: #blockarg2]]. statements do: [ :each | (each asMorphicSyntaxIn: column) borderWidth: 1. each addCommentToMorph: column]. ^ column! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BlockNode class instanceVariableNames: ''! !BlockNode class methodsFor: 'instance creation' stamp: 'sma 3/3/2000 13:34'! statements: statements returns: returns ^ self new statements: statements returns: returns! ! !BlockNode class methodsFor: 'instance creation' stamp: 'sma 3/3/2000 13:34'! withJust: aNode ^ self statements: (Array with: aNode) returns: false! ! BooklikeMorph subclass: #BookMorph instanceVariableNames: 'pages currentPage ' classVariableNames: 'MethodHolders VersionNames VersionTimes ' poolDictionaries: '' category: 'Morphic-Books'! !BookMorph commentStamp: '' prior: 0! A collection of pages, each of which is a place to put morphs. Allows one or another page to show; orchestrates the page transitions; offers control panel for navigating among pages and for adding and deleting pages. To write a book out to the disk or to a file server, decide what folder it goes in. Construct a url to a typical page: file://myDisk/folder/myBook1.sp or ftp://aServer/folder/myBook1.sp Choose "send all pages to server" from the book's menu (press the <> part of the controls). Choose "use page numbers". Paste in the url. To load an existing book, find its ".bo" file in the file list browser. Choose "load as book". To load an existing book from its url, execute: Ã(URLMorph grabURL: 'ftp://aServer/folder/myBook1.sp') book: true. Multiple people may modify a book. If other people may have changed a book you have on your screen, choose "reload all from server". Add or modify a page, and choose "send this page to server". The polite thing to do is to reload before changing a book. Then write one or all pages soon after making your changes. If you store a stale book, it will wipe out changes that other people made in the mean time. Pages may be linked to each other. To create a named link to a new page, type the name of the page in a text area in a page. Select it and do Cmd-6. Choose 'link to'. A new page of that name will be added at the back of the book. Clicking on the blue text flips to that page. To create a link to an existing page, first name the page. Go to that page and Cmd-click on it. The name of the page is below the page. Click in it and backspace and type. Return to the page you are linking from. Type the name. Cmd-6, 'link to'. Text search: Search for a set of fragments. allStrings collects text of fields. Turn to page with all fragments on it and highlight the first one. Save the container and offset in properties: #searchContainer, #searchOffset, #searchKey. Search again from there. Clear those at each page turn, or change of search key. [rules about book indexes and pages: Index and pages must live in the same directory. They have the same file prefix, followed by .bo for the index or 4.sp for a page (or x4.sp). When a book is moved to a new directory, the load routine gets the new urls for all pages and saves those in the index. Book stores index url in property #url. Allow mulitple indexes (books) on the same shared set of pages. If book has a url in same directory as pages, allow them to have different prefixes. save all pages first time, save one page first time, fromRemoteStream: (first time) save all pages normal , save one page normal, reload where I check if same dir] URLMorph holds url of both page and book.! !BookMorph methodsFor: 'initialization' stamp: 'tk 1/24/1999 15:29'! fromRemoteStream: strm "Make a book from an index and a bunch of pages on a server. NOT showing any page!! Index and pages must live in the same directory. If the book has moved, save the current correct urls for each of the pages. Self must already have a url stored in property #url." | remote dict bookUrl oldStem stem oldUrl endPart | remote _ strm fileInObjectAndCode. bookUrl _ (SqueakPage new) url: (self valueOfProperty: #url); url. "expand a relative url" oldStem _ SqueakPage stemUrl: (remote at: 2) url. oldStem _ oldStem copyUpToLast: $/. stem _ SqueakPage stemUrl: bookUrl. stem _ stem copyUpToLast: $/. oldStem = stem ifFalse: [ "Book is in new directory, fix page urls" 2 to: remote size do: [:ii | oldUrl _ (remote at: ii) url. endPart _ oldUrl copyFrom: oldStem size+1 to: oldUrl size. (remote at: ii) url: stem, endPart]]. self initialize. pages _ OrderedCollection new. 2 to: remote size do: [:ii | pages add: (remote at: ii)]. currentPage fullReleaseCachedState; delete. "the blank one" currentPage _ remote at: 2. dict _ remote at: 1. self setProperty: #modTime toValue: (dict at: #modTime). dict at: #allText ifPresent: [:val | self setProperty: #allText toValue: val]. dict at: #allTextUrls ifPresent: [:val | self setProperty: #allTextUrls toValue: val]. #(color borderWidth borderColor pageSize) with: #(color: borderWidth: borderColor: pageSize:) do: [:key :sel | dict at: key ifPresent: [:val | self perform: sel with: val]]. ^ self! ! !BookMorph methodsFor: 'initialization' stamp: 'tk 1/15/1999 08:02'! fromURL: url "Make a book from an index and a bunch of pages on a server. NOT showing any page!!" | strm | Cursor wait showWhile: [ strm _ (ServerFile new fullPath: url) asStream]. strm class == String ifTrue: [self inform: 'Sorry, ',strm. ^ nil]. self setProperty: #url toValue: url. self fromRemoteStream: strm. ^ self! ! !BookMorph methodsFor: 'initialization' stamp: 'sw 7/4/1998 16:43'! initialize super initialize. self setInitialState. pages _ OrderedCollection new. self showPageControls. self class turnOffSoundWhile: [self insertPage]. ! ! !BookMorph methodsFor: 'initialization' stamp: 'sw 6/24/1998 09:23'! newPages: pageList "Replace all my pages with the given list of BookPageMorphs. After this call, currentPage may be invalid." pages _ pages species new. pages addAll: pageList! ! !BookMorph methodsFor: 'initialization' stamp: 'jm 11/17/97 17:26'! newPages: pageList currentIndex: index "Replace all my pages with the given list of BookPageMorphs. Make the current page be the page with the given index." pages _ pages species new. pages addAll: pageList. pages isEmpty ifTrue: [^ self insertPage]. self goToPage: index. ! ! !BookMorph methodsFor: 'initialization' stamp: 'sw 7/4/1998 16:45'! removeEverything currentPage _ nil. pages _ OrderedCollection new. self removeAllMorphs! ! !BookMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:10'! setInitialState self listDirection: #topToBottom. self wrapCentering: #topLeft. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self layoutInset: 5. color _ Color white. "pageSize _ 1060@800." pageSize _ 160@300. "back to the original since the pother was way too big" self enableDragNDrop! ! !BookMorph methodsFor: 'sorting' stamp: 'RAA 6/28/2000 19:44'! acceptSortedContentsFrom: aHolder "Update my page list from the given page sorter." | goodPages rejects toAdd sqPage | goodPages _ OrderedCollection new. rejects _ OrderedCollection new. aHolder submorphs doWithIndex: [:m :i | toAdd _ nil. (m isKindOf: PasteUpMorph) ifTrue: [toAdd _ m]. (m isKindOf: BookPageThumbnailMorph) ifTrue: [ toAdd _ m page. m bookMorph == self ifFalse: [ "borrowed from another book. preserve the original" toAdd _ toAdd veryDeepCopy. "since we came from elsewhere, cached strings are wrong" self removeProperty: #allTextUrls. self removeProperty: #allText. ]. ]. toAdd class == String ifTrue: ["a url" toAdd _ pages detect: [:aPage | aPage url = toAdd] ifNone: [toAdd]]. toAdd class == String ifTrue: [ sqPage _ SqueakPageCache atURL: toAdd. toAdd _ sqPage contentsMorph ifNil: [sqPage copyForSaving] "a MorphObjectOut" ifNotNil: [sqPage contentsMorph]]. toAdd ifNil: [rejects add: m] ifNotNil: [goodPages add: toAdd]]. self newPages: goodPages. goodPages size = 0 ifTrue: [self insertPage]. rejects size > 0 ifTrue: [self inform: rejects size printString, ' objects vanished in this process.'] ! ! !BookMorph methodsFor: 'sorting' stamp: 'sw 3/5/1999 17:38'! morphsForPageSorter | i thumbnails | 'Assembling thumbnail images...' displayProgressAt: self cursorPoint from: 0 to: pages size during: [:bar | i _ 0. thumbnails _ pages collect: [:p | bar value: (i_ i+1). pages size > 40 ifTrue: [p smallThumbnailForPageSorter inBook: self] ifFalse: [p thumbnailForPageSorter inBook: self]]]. ^ thumbnails! ! !BookMorph methodsFor: 'sorting' stamp: 'di 1/4/1999 13:52'! sortPages currentPage ifNotNil: [currentPage updateCachedThumbnail]. ^ super sortPages! ! !BookMorph methodsFor: 'sorting' stamp: 'di 1/4/1999 12:12'! sortPages: evt ^ self sortPages! ! !BookMorph methodsFor: 'accessing' stamp: 'tk 12/17/1998 11:19'! allNonSubmorphMorphs "Return a collection containing all morphs in this morph which are not currently in the submorph containment hierarchy. Especially the non-showing pages in BookMorphs. (As needed, make a variant of this that brings in all pages that are not in memory.)" | coll | coll _ OrderedCollection new. pages do: [:pg | pg isInMemory ifTrue: [ pg == currentPage ifFalse: [coll add: pg]]]. ^ coll! ! !BookMorph methodsFor: 'accessing' stamp: 'sw 10/16/1998 22:39'! currentPage (submorphs includes: currentPage) ifFalse: [currentPage _ nil]. ^ currentPage! ! !BookMorph methodsFor: 'accessing' stamp: 'tk 1/3/2001 08:54'! pageNamed: aName ^ pages detect: [:p | p knownName = aName] ifNone: [nil]! ! !BookMorph methodsFor: 'accessing' stamp: 'tk 12/24/1998 07:27'! pageNumberOf: aMorph "Modified so that if the page IS in memory, other pages don't have to be brought in. (This method may wrongly say a page is not here if pages has a tombstone (MorphObjectOut) and that tombstone would resolve to an object already in this image. This is an unlikely case, and callers just have to tolerate it.)" ^ pages identityIndexOf: aMorph ifAbsent: [0] ! ! !BookMorph methodsFor: 'accessing'! pages ^ pages ! ! !BookMorph methodsFor: 'accessing' stamp: 'tk 10/22/1998 15:47'! pages: aMorphList pages _ aMorphList asOrderedCollection. "It is tempting to force the first page to be the current page. But then, two pages might be shown at once!! Just trust the copying mechanism and let currentPage be copied correctly. --Ted."! ! !BookMorph methodsFor: 'accessing' stamp: 'mjg 9/28/1999 11:57'! setAllPagesColor: aColor "Set the color of all the pages to a new color" self pages do: [:page | page color: aColor].! ! !BookMorph methodsFor: 'accessing' stamp: 'tk 12/16/1998 12:05'! userString "Do I have a text string to be searched on?" | list | self getAllText. list _ OrderedCollection new. (self valueOfProperty: #allText ifAbsent: #()) do: [:aList | list addAll: aList]. ^ list! ! !BookMorph methodsFor: 'dropping/grabbing' stamp: 'sw 10/18/97 18:03'! acceptDroppingMorph: aMorph event: evt "Allow the user to add submorphs just by dropping them on this morph." (currentPage allMorphs includes: aMorph) ifFalse: [currentPage addMorph: aMorph]! ! !BookMorph methodsFor: 'dropping/grabbing'! allowSubmorphExtraction ^ false! ! !BookMorph methodsFor: 'dropping/grabbing' stamp: 'di 9/30/1998 10:38'! wantsDroppedMorph: aMorph event: evt (currentPage bounds containsPoint: (self pointFromWorld: evt cursorPoint)) ifFalse: [^ false]. ^ super wantsDroppedMorph: aMorph event: evt! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 10/13/2000 12:59'! defaultNameStemForNewPages "Answer a stem onto which to build default names for fresh pages" ^ 'page' ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 6/24/1998 18:50'! deletePage | message | message _ 'Are you certain that you want to delete this page and everything that is on it? '. (self confirm: message) ifTrue: [self deletePageBasic]. ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'di 9/7/1999 21:57'! deletePageBasic | thisPage | thisPage _ self pageNumberOf: currentPage. pages remove: currentPage. currentPage delete. currentPage _ nil. pages isEmpty ifTrue: [^ self insertPage]. self goToPage: (thisPage min: pages size) ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 10/12/97 21:48'! insertPage: aPage pageSize: aPageSize ^ self insertPage: aPage pageSize: aPageSize atIndex: (pages size + 1)! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 5/14/1998 11:06'! insertPage: aPage pageSize: aPageSize atIndex: anIndex | sz predecessor | sz _ aPageSize ifNil: [currentPage == nil ifTrue: [pageSize] ifFalse: [currentPage extent]] ifNotNil: [aPageSize]. aPage extent: sz. ((pages isEmpty | anIndex == nil) or: [anIndex > pages size]) ifTrue: [pages add: aPage] ifFalse: [anIndex <= 1 ifTrue: [pages addFirst: aPage] ifFalse: [predecessor _ anIndex == nil ifTrue: [currentPage] ifFalse: [pages at: anIndex]. self pages add: aPage after: predecessor]]. self goToPageMorph: aPage ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 10/13/2000 13:00'! insertPageColored: aColor "Insert a new page for the receiver, using the given color as its background color" | sz newPage bw bc | currentPage == nil ifTrue: [sz _ pageSize. bw _ 0. bc _ Color blue muchLighter] ifFalse: [sz _ currentPage extent. bw _ currentPage borderWidth. bc _ currentPage borderColor]. newPagePrototype ifNil: [newPage _ PasteUpMorph new extent: sz; color: aColor. newPage borderWidth: bw; borderColor: bc] ifNotNil: [Cursor wait showWhile: [newPage _ newPagePrototype veryDeepCopy]]. newPage setNameTo: self defaultNameStemForNewPages. newPage resizeToFit: false. pages isEmpty ifTrue: [pages add: (currentPage _ newPage)] ifFalse: [pages add: newPage after: currentPage]. self nextPage ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'ar 11/9/2000 21:10'! insertPageLabel: labelString morphs: morphList | m c labelAllowance | self insertPage. labelString ifNotNil: [m _ (TextMorph new extent: currentPage width@20; contents: labelString). m lock. m position: currentPage position + (((currentPage width - m width) // 2) @ 5). currentPage addMorph: m. labelAllowance _ 40] ifNil: [labelAllowance _ 0]. "use a column to align the given morphs, then add them to the page" c _ AlignmentMorph newColumn wrapCentering: #center; cellPositioning: #topCenter. c addAllMorphs: morphList. c position: currentPage position + (0 @ labelAllowance). currentPage addAllMorphs: morphList. ^ currentPage ! ! !BookMorph methodsFor: 'insert and delete' stamp: 'sw 10/13/2000 13:01'! insertPageSilentlyAtEnd "Create a new page at the end of the book. Do not turn to it." | sz newPage bw bc cc | currentPage == nil ifTrue: [sz _ pageSize. bw _ 0. bc _ Color blue muchLighter. cc _ color] ifFalse: [sz _ currentPage extent. bw _ currentPage borderWidth. bc _ currentPage borderColor. cc _ currentPage color]. newPagePrototype ifNil: [newPage _ PasteUpMorph new extent: sz; color: cc. newPage borderWidth: bw; borderColor: bc] ifNotNil: [Cursor wait showWhile: [newPage _ newPagePrototype veryDeepCopy]]. newPage setNameTo: self defaultNameStemForNewPages. newPage resizeToFit: false. pages isEmpty ifTrue: [pages add: (currentPage _ newPage)] "had been none" ifFalse: [pages add: newPage after: pages last]. ^ newPage! ! !BookMorph methodsFor: 'navigation' stamp: 'ar 11/9/2000 20:37'! buildFloatingPageControls | pageControls | pageControls _ self makePageControlsFrom: self fullControlSpecs. pageControls borderWidth: 0; layoutInset: 4. pageControls setProperty: #pageControl toValue: true. pageControls setNameTo: 'Page Controls'. pageControls color: Color yellow. ^FloatingBookControlsMorph new addMorph: pageControls. ! ! !BookMorph methodsFor: 'navigation' stamp: 'di 12/20/1998 10:18'! goToPage: pageNumber ^ self goToPage: pageNumber transitionSpec: nil! ! !BookMorph methodsFor: 'navigation' stamp: 'di 1/14/1999 12:07'! goToPage: pageNumber transitionSpec: transitionSpec | pageMorph | pages isEmpty ifTrue: [^ self]. pageMorph _ (self hasProperty: #dontWrapAtEnd) ifTrue: [pages atPin: pageNumber] ifFalse: [pages atWrap: pageNumber]. ^ self goToPageMorph: pageMorph transitionSpec: transitionSpec! ! !BookMorph methodsFor: 'navigation' stamp: 'tk 12/24/1998 07:17'! goToPageMorph: aMorph self goToPage: (pages identityIndexOf: aMorph ifAbsent: [^ self "abort"]). ! ! !BookMorph methodsFor: 'navigation' stamp: 'di 1/4/1999 12:37'! goToPageMorph: aMorph fromBookmark: aBookmark "This protocol enables sensitivity to a transitionSpec on the bookmark" self goToPageMorph: aMorph transitionSpec: (aBookmark valueOfProperty: #transitionSpec). ! ! !BookMorph methodsFor: 'navigation' stamp: 'RAA 11/20/2000 12:44'! goToPageMorph: newPage transitionSpec: transitionSpec | pageIndex aWorld oldPageIndex ascending tSpec readIn | pages isEmpty ifTrue: [^ self]. self setProperty: #searchContainer toValue: nil. "forget previous search" self setProperty: #searchOffset toValue: nil. self setProperty: #searchKey toValue: nil. pageIndex _ pages identityIndexOf: newPage ifAbsent: [^ self "abort"]. readIn _ newPage isInMemory not. oldPageIndex _ pages identityIndexOf: currentPage ifAbsent: [nil]. ascending _ ((oldPageIndex == nil) or: [newPage == currentPage]) ifTrue: [nil] ifFalse: [oldPageIndex < pageIndex]. tSpec _ transitionSpec ifNil: "If transition not specified by requestor..." [newPage valueOfProperty: #transitionSpec " ... then consult new page" ifAbsent: [self transitionSpecFor: self " ... otherwise this is the default"]]. self flag: #arNote. "Probably unnecessary" (aWorld _ self world) ifNotNil: [self primaryHand releaseKeyboardFocus]. currentPage ifNotNil: [currentPage updateCachedThumbnail]. self currentPage ~~ nil ifTrue: [(((pages at: pageIndex) owner isKindOf: TransitionMorph) and: [(pages at: pageIndex) isInWorld]) ifTrue: [^ self "In the process of a prior pageTurn"]. self currentPlayerDo: [:aPlayer | aPlayer runAllClosingScripts]. ascending ifNotNil: ["Show appropriate page transition and start new page when done" currentPage stopStepping. (pages at: pageIndex) position: currentPage position. ^ (TransitionMorph effect: tSpec second direction: tSpec third inverse: (ascending or: [transitionSpec notNil]) not) showTransitionFrom: currentPage to: (pages at: pageIndex) in: self whenStart: [self playPageFlipSound: tSpec first] whenDone: [currentPage delete; fullReleaseCachedState. self insertPageMorphInCorrectSpot: (pages at: pageIndex). self adjustCurrentPageForFullScreen. self snapToEdgeIfAppropriate. aWorld ifNotNil: [self world startSteppingSubmorphsOf: currentPage]. self currentPlayerDo: [:aPlayer | aPlayer runAllOpeningScripts]. (aWorld _ self world) ifNotNil: ["WHY??" aWorld displayWorld]. readIn ifTrue: [currentPage updateThumbnailUrlInBook: self url. currentPage sqkPage computeThumbnail]. "just store it" ]]. "No transition, but at least decommission current page" currentPage delete; fullReleaseCachedState]. self insertPageMorphInCorrectSpot: (pages at: pageIndex). self adjustCurrentPageForFullScreen. self snapToEdgeIfAppropriate. aWorld ifNotNil: [self world startSteppingSubmorphsOf: currentPage]. self currentPlayerDo: [:aPlayer | aPlayer runAllOpeningScripts]. (aWorld _ self world) ifNotNil: ["WHY??" aWorld displayWorld]. readIn ifTrue: [currentPage updateThumbnailUrl. currentPage sqkPage computeThumbnail]. "just store it" ! ! !BookMorph methodsFor: 'navigation' stamp: 'tk 3/28/2000 13:40'! goToPageUrl: aUrl | pp short | pp _ pages detect: [:pg | pg url = aUrl] ifNone: [nil]. pp ifNil: [short _ (aUrl findTokens: '/') last. pp _ pages detect: [:pg | pg url ifNil: [false] ifNotNil: [(pg url findTokens: '/') last = short]] "it moved" ifNone: [pages at: 1]]. self goToPageMorph: pp. ! ! !BookMorph methodsFor: 'navigation' stamp: 'sw 10/26/1998 15:41'! goto: aPlayer self goToPageMorph: aPlayer costume! ! !BookMorph methodsFor: 'navigation' stamp: 'RAA 11/20/2000 12:43'! insertPageMorphInCorrectSpot: aPageMorph self addMorphBack: (currentPage _ aPageMorph). ! ! !BookMorph methodsFor: 'navigation' stamp: 'sw 8/4/97 12:05'! lastPage self goToPage: pages size ! ! !BookMorph methodsFor: 'navigation' stamp: 'di 1/14/1999 12:01'! nextPage currentPage == nil ifTrue: [^ self goToPage: 1]. self goToPage: (self pageNumberOf: currentPage) + 1. ! ! !BookMorph methodsFor: 'navigation' stamp: 'tk 12/24/1998 07:19'! pageNumber ^ self pageNumberOf: currentPage! ! !BookMorph methodsFor: 'navigation' stamp: 'di 1/14/1999 12:01'! previousPage currentPage == nil ifTrue: [^ self goToPage: 1]. self goToPage: (self pageNumberOf: currentPage) - 1. ! ! !BookMorph methodsFor: 'navigation' stamp: 'di 1/14/1999 12:20'! setWrapPages: doWrap doWrap ifTrue: [self removeProperty: #dontWrapAtEnd] ifFalse: [self setProperty: #dontWrapAtEnd toValue: true]. ! ! !BookMorph methodsFor: 'navigation' stamp: 'sw 5/23/2000 13:11'! showMoreControls self currentEvent shiftPressed ifTrue: [self hidePageControls] ifFalse: [self showPageControls: self fullControlSpecs]! ! !BookMorph methodsFor: 'navigation' stamp: 'di 12/21/1998 11:15'! transitionSpecFor: aMorph ^ aMorph valueOfProperty: #transitionSpec " check for special propety" ifAbsent: [Array with: 'camera' " ... otherwise this is the default" with: #none with: #none]! ! !BookMorph methodsFor: 'menu' stamp: 'ar 10/5/2000 18:55'! addBookMenuItemsTo: aMenu hand: aHandMorph | controlsShowing subMenu | subMenu _ MenuMorph new defaultTarget: self. subMenu add: 'previous page' action: #previousPage. subMenu add: 'next page' action: #nextPage. subMenu add: 'goto page' action: #goToPage. subMenu add: 'insert a page' action: #insertPage. subMenu add: 'delete this page' action: #deletePage. controlsShowing _ self hasSubmorphWithProperty: #pageControl. controlsShowing ifTrue: [subMenu add: 'hide page controls' action: #hidePageControls. subMenu add: 'fewer page controls' action: #fewerPageControls] ifFalse: [subMenu add: 'show page controls' action: #showPageControls]. self isInFullScreenMode ifTrue: [ subMenu add: 'exit full screen' action: #exitFullScreen. ] ifFalse: [ subMenu add: 'show full screen' action: #goFullScreen. ]. subMenu addLine. subMenu add: 'sound effect for all pages' action: #menuPageSoundForAll:. subMenu add: 'sound effect this page only' action: #menuPageSoundForThisPage:. subMenu add: 'visual effect for all pages' action: #menuPageVisualForAll:. subMenu add: 'visual effect this page only' action: #menuPageVisualForThisPage:. subMenu addLine. subMenu add: 'sort pages' action: #sortPages:. subMenu add: 'uncache page sorter' action: #uncachePageSorter. (self hasProperty: #dontWrapAtEnd) ifTrue: [subMenu add: 'wrap after last page' selector: #setWrapPages: argument: true] ifFalse: [subMenu add: 'stop at last page' selector: #setWrapPages: argument: false]. subMenu addLine. subMenu add: 'search for text' action: #textSearch. (aHandMorph pasteBuffer class isKindOf: PasteUpMorph class) ifTrue: [subMenu add: 'paste book page' action: #pasteBookPage]. subMenu add: 'send all pages to server' action: #savePagesOnURL. subMenu add: 'send this page to server' action: #saveOneOnURL. subMenu add: 'reload all from server' action: #reload. subMenu add: 'copy page url to clipboard' action: #copyUrl. subMenu add: 'keep in one file' action: #keepTogether. subMenu add: 'save as new-page prototype' action: #setNewPagePrototype. newPagePrototype ifNotNil: [subMenu add: 'clear new-page prototype' action: #clearNewPagePrototype]. aMenu add: 'book...' subMenu: subMenu ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/17/1999 12:52'! bookmarkForThisPage "If this book exists on a server, make the reference via a URL" | bb url um | (url _ self url) ifNil: [ bb _ SimpleButtonMorph new target: self. bb actionSelector: #goToPageMorph:fromBookmark:. bb label: 'Bookmark'. bb arguments: (Array with: currentPage with: bb). self primaryHand attachMorph: bb. ^ bb]. currentPage url ifNil: [currentPage saveOnURLbasic]. um _ URLMorph newForURL: currentPage url. um setURL: currentPage url page: currentPage sqkPage. (SqueakPage stemUrl: url) = (SqueakPage stemUrl: currentPage url) ifTrue: [um book: true] ifFalse: [um book: url]. "remember which book" um isBookmark: true; label: 'Bookmark'. um borderWidth: 1; borderColor: #raised. um color: (Color r: 0.4 g: 0.8 b: 0.6). self primaryHand attachMorph: um. ^ um! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 11/10/2000 11:27'! buildThreadOfProjects | thisPVM projectNames threadName | projectNames _ pages collect: [ :each | (thisPVM _ each findA: ProjectViewMorph) ifNil: [ nil ] ifNotNil: [ {thisPVM project name}. ]. ]. projectNames _ projectNames reject: [ :each | each isNil]. threadName _ FillInTheBlank request: 'Please name this thread.' initialAnswer: ( self valueOfProperty: #nameOfThreadOfProjects ifAbsent: ['Projects on Parade'] ). threadName isEmptyOrNil ifTrue: [^self]. InternalThreadNavigationMorph know: projectNames as: threadName; openThreadNamed: threadName. ! ! !BookMorph methodsFor: 'menu' stamp: 'ar 1/15/2001 18:37'! copyUrl "Copy this page's url to the clipboard" | str | str _ currentPage url ifNil: [str _ 'Page does not have a url. Send page to server first.']. Clipboard clipboardText: str asText. ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 1/26/1999 10:10'! findText: wants "Turn to the next page that has all of the strings mentioned on it. Highlight where it is found. allText and allTextUrls have been set. Case insensitive search. Resuming a search. If container's text is still in the list and secondary keys are still in the page, (1) search rest of that container. (2) search rest of containers on that page (3) pages till end of book, (4) from page 1 to this page again." "Later sort wants so longest key is first" | allText good thisWord here fromHereOn startToHere oldContainer oldIndex otherKeys strings | allText _ self valueOfProperty: #allText ifAbsent: [#()]. here _ pages identityIndexOf: currentPage ifAbsent: [1]. fromHereOn _ here+1 to: pages size. startToHere _ 1 to: here. "repeat this page" (self valueOfProperty: #searchKey ifAbsent: [#()]) = wants ifTrue: [ "does page have all the other keys? No highlight if found!!" otherKeys _ wants allButFirst. strings _ allText at: here. good _ true. otherKeys do: [:searchString | "each key" good ifTrue: [thisWord _ false. strings do: [:longString | (longString findString: searchString startingAt: 1 caseSensitive: false) > 0 ifTrue: [ thisWord _ true]]. good _ thisWord]]. good ifTrue: ["all are on this page. Look in rest for string again." oldContainer _ self valueOfProperty: #searchContainer. oldIndex _ self valueOfProperty: #searchOffset. (self findText: (OrderedCollection with: wants first) inStrings: strings startAt: oldIndex+1 container: oldContainer pageNum: here) ifTrue: [ self setProperty: #searchKey toValue: wants. ^ true]]] ifFalse: [fromHereOn _ here to: pages size]. "do search this page" "other pages" fromHereOn do: [:pageNum | (self findText: wants inStrings: (allText at: pageNum) startAt: 1 container: nil pageNum: pageNum) ifTrue: [^ true]]. startToHere do: [:pageNum | (self findText: wants inStrings: (allText at: pageNum) startAt: 1 container: nil pageNum: pageNum) ifTrue: [^ true]]. "if fail" self setProperty: #searchContainer toValue: nil. self setProperty: #searchOffset toValue: nil. self setProperty: #searchKey toValue: nil. ^ false! ! !BookMorph methodsFor: 'menu' stamp: 'tk 11/8/2000 13:08'! findText: keys inStrings: rawStrings startAt: startIndex container: oldContainer pageNum: pageNum "Call once to search a page of the book. Return true if found and highlight the text. oldContainer should be NIL. (oldContainer is only non-nil when (1) doing a 'search again' and (2) the page is in memory and (3) keys has just one element. oldContainer is a TextMorph.)" | good thisWord index insideOf place container start wasIn strings | good _ true. start _ startIndex. strings _ oldContainer ifNil: [rawStrings] "normal case" ifNotNil: [(pages at: pageNum) isInMemory ifFalse: [rawStrings] ifTrue: [(pages at: pageNum) allStringsAfter: oldContainer]]. keys do: [:searchString | "each key" good ifTrue: [thisWord _ false. strings do: [:longString | (index _ longString findString: searchString startingAt: start caseSensitive: false) > 0 ifTrue: [ thisWord not & (searchString == (keys at: 1)) ifTrue: [ insideOf _ longString. place _ index]. thisWord _ true]. start _ 1]. "only first key on first container" good _ thisWord]]. good ifTrue: ["all are on this page" wasIn _ (pages at: pageNum) isInMemory. self goToPage: pageNum. wasIn ifFalse: ["search again, on the real current text. Know page is in." ^ self findText: keys inStrings: ((pages at: pageNum) allStringsAfter: nil) "recompute" startAt: startIndex container: oldContainer pageNum: pageNum]]. good ifTrue: ["have the exact string object" (container _ oldContainer) ifNil: [container _ self highlightText: (keys at: 1) at: place in: insideOf] ifNotNil: [ container userString == insideOf ifFalse: [ container _ self highlightText: (keys at: 1) at: place in: insideOf] ifTrue: [(container isKindOf: TextMorph) ifTrue: [ container editor selectFrom: place to: (keys at: 1) size - 1 + place. container changed]. ]]. self setProperty: #searchContainer toValue: container. self setProperty: #searchOffset toValue: place. self setProperty: #searchKey toValue: keys. "override later" ^ true]. ^ false! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/26/1999 22:39'! forgetURLs "About to save these objects in a new place. Forget where stored now. Must bring in all pages we don't have." | pg | pages do: [:aPage | aPage yourself. "bring it into memory" (pg _ aPage valueOfProperty: #SqueakPage) ifNotNil: [ SqueakPageCache removeURL: pg url. pg contentsMorph setProperty: #SqueakPage toValue: nil]]. self setProperty: #url toValue: nil.! ! !BookMorph methodsFor: 'menu' stamp: 'tk 1/26/1999 09:26'! getAllText "Collect the text for each page. Just point at strings so don't have to recopy them. Parallel array of urls for ID of pages. allText = Array (pages size) of arrays (fields in it) of strings of text. allTextUrls = Array (pages size) of urls or page numbers. For any page that is out, text data came from .bo file on server. Is rewritten when one or all pages are stored." | oldUrls oldStringLists allText allTextUrls aUrl which | oldUrls _ self valueOfProperty: #allTextUrls ifAbsent: [#()]. oldStringLists _ self valueOfProperty: #allText ifAbsent: [#()]. allText _ pages collect: [:pg | OrderedCollection new]. allTextUrls _ Array new: pages size. pages doWithIndex: [:aPage :ind | aUrl _ aPage url. aPage isInMemory ifTrue: [(allText at: ind) addAll: (aPage allStringsAfter: nil). aUrl ifNil: [aUrl _ ind]. allTextUrls at: ind put: aUrl] ifFalse: ["Order of pages on server may be different. (later keep up to date?)" which _ oldUrls indexOf: aUrl. allTextUrls at: ind put: aUrl. which = 0 ifFalse: [allText at: ind put: (oldStringLists at: which)]]]. self setProperty: #allText toValue: allText. self setProperty: #allTextUrls toValue: allTextUrls. ^ allText! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 7/5/2000 11:25'! getStemUrl "Try to find the old place where this book was stored. Confirm with the user. Else ask for new place." | initial pg url knownURL | knownURL _ false. initial _ ''. (pg _ currentPage valueOfProperty: #SqueakPage) ifNotNil: [pg contentsMorph == currentPage ifTrue: [initial _ pg url. knownURL _ true]]. "If this page has a url" pages doWithIndex: [:aPage :ind | initial isEmpty ifTrue: [aPage isInMemory ifTrue: [(pg _ aPage valueOfProperty: #SqueakPage) ifNotNil: [initial _ pg url]]]]. "any page with a url" initial isEmpty ifTrue: [initial _ ServerDirectory defaultStemUrl , '1.sp']. "A new legal place" url _ knownURL ifTrue: [initial] ifFalse: [[FillInTheBlank request: 'url of the place to store a typical page in this book. Must begin with file:// or ftp://' initialAnswer: initial] valueWithWorld: self world]. ^ SqueakPage stemUrl: url! ! !BookMorph methodsFor: 'menu' stamp: 'sge 2/13/2000 05:33'! goToPage | pageNum | pageNum _ FillInTheBlank request: 'Page?' initialAnswer: '0'. pageNum isEmptyOrNil ifTrue: [^true]. self goToPage: pageNum asNumber. ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 1/19/1999 07:11'! highlightText: stringToHilite at: index in: insideOf "Find the container with this text and highlight it. May not be able to do it for stringMorphs." "Find the container with that text" | container | self allMorphsDo: [:sub | insideOf == sub userString ifTrue: [container _ sub]]. container ifNil: [ self allMorphsDo: [:sub | insideOf = sub userString ifTrue: [container _ sub]]]. "any match" container ifNil: [^ nil]. "Order it highlighted" (container isKindOf: TextMorph) ifTrue: [ container editor selectFrom: index to: stringToHilite size - 1 + index]. container changed. ^ container ! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 11/10/2000 11:27'! invokeBookMenu "Invoke the book's control panel menu." | aMenu | aMenu _ MenuMorph new defaultTarget: self. aMenu addStayUpItem. aMenu add: 'find...' action: #textSearch. aMenu add: 'go to page...' action: #goToPage. aMenu addLine. aMenu addList: #(('sort pages' sortPages) ('uncache page sorter' uncachePageSorter)). (self hasProperty: #dontWrapAtEnd) ifTrue: [aMenu add: 'wrap after last page' selector: #setWrapPages: argument: true] ifFalse: [aMenu add: 'stop at last page' selector: #setWrapPages: argument: false]. aMenu addList: #(('make bookmark' bookmarkForThisPage) ('make thumbnail' thumbnailForThisPage)). aMenu addUpdating: #showingPageControlsString action: #toggleShowingOfPageControls. aMenu addUpdating: #showingFullScreenString action: #toggleFullScreen. aMenu addLine. aMenu add: 'sound effect for all pages' action: #menuPageSoundForAll:. aMenu add: 'sound effect this page only' action: #menuPageSoundForThisPage:. aMenu add: 'visual effect for all pages' action: #menuPageVisualForAll:. aMenu add: 'visual effect this page only' action: #menuPageVisualForThisPage:. aMenu addLine. (self primaryHand pasteBuffer class isKindOf: PasteUpMorph class) ifTrue: [aMenu add: 'paste book page' action: #pasteBookPage]. aMenu add: 'save as new-page prototype' action: #setNewPagePrototype. newPagePrototype ifNotNil: [ aMenu add: 'clear new-page prototype' action: #clearNewPagePrototype]. aMenu add: (self dragNDropEnabled ifTrue: ['close'] ifFalse: ['open']) , ' dragNdrop' action: #toggleDragNDrop. aMenu add: 'make all pages this size' action: #makeUniformPageSize. aMenu add: 'send all pages to server' action: #savePagesOnURL. aMenu add: 'send this page to server' action: #saveOneOnURL. aMenu add: 'reload all from server' action: #reload. aMenu add: 'copy page url to clipboard' action: #copyUrl. aMenu add: 'keep in one file' action: #keepTogether. aMenu addLine. aMenu add: 'load PPT images from slide #1' action: #loadImagesIntoBook. aMenu add: 'background color for all pages...' action: #setPageColor. aMenu add: 'make a thread of projects in this book' action: #buildThreadOfProjects. aMenu popUpEvent: self world activeHand lastEvent in: self world! ! !BookMorph methodsFor: 'menu' stamp: 'tk 12/2/1998 19:31'! keepTogether "Mark this book so that each page will not go into a separate file. Do this when pages share referenes to a common Player. Don't want many copies of that Player when bring in. Do not write pages of book out. Write the PasteUpMorph that the entire book lives in." self setProperty: #keepTogether toValue: true.! ! !BookMorph methodsFor: 'menu' stamp: 'mjg 5/15/2000 16:29'! loadImagesIntoBook "PowerPoint stores GIF presentations as individual slides named Slide1, Slide2, etc. Load these into the book. mjg 9/99" | directory filenumber form newpage | directory _ ((StandardFileMenu oldFileFrom: FileDirectory default) ifNil: [^ nil]) directory. directory isNil ifTrue: [^ nil]. "Start loading 'em up!!" filenumber _ 1. [directory fileExists: 'Slide',(filenumber asString)] whileTrue: [Transcript show: 'Slide',(filenumber asString); cr. (Smalltalk bytesLeft < 1000000) ifTrue: ["Make some room" (self valueOfProperty: #url) == nil ifTrue: [self savePagesOnURL] ifFalse: [self saveAsNumberedURLs].]. form _ Form fromFileNamed: (directory fullNameFor: 'Slide', (filenumber asString)). newpage _ PasteUpMorph new extent: (form extent). newpage addMorph: (SketchMorph withForm: form). self pages addLast: newpage. filenumber _ filenumber + 1.]. "After adding all, delete the first page." self goToPage: 1. self deletePageBasic. "Save the book" (self valueOfProperty: #url) == nil ifTrue: [self savePagesOnURL] ifFalse: [self saveAsNumberedURLs].! ! !BookMorph methodsFor: 'menu' stamp: 'sw 1/25/1999 16:15'! makeUniformPageSize "Make all pages be of the same size as the current page." currentPage ifNil: [^ self beep]. self resizePagesTo: currentPage extent. newPagePrototype ifNotNil: [newPagePrototype extent: currentPage extent]! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! menuPageSoundFor: target event: evt | tSpec menu | tSpec _ self transitionSpecFor: target. menu _ (MenuMorph entitled: 'Choose a sound (it is now ' , tSpec first , ')') defaultTarget: target. SampledSound soundNames do: [:soundName | menu add: soundName target: target selector: #setProperty:toValue: argumentList: (Array with: #transitionSpec with: (tSpec copy at: 1 put: soundName; yourself))]. menu popUpEvent: evt in: self world! ! !BookMorph methodsFor: 'menu' stamp: 'di 12/20/1998 13:53'! menuPageSoundForAll: evt ^ self menuPageSoundFor: self event: evt! ! !BookMorph methodsFor: 'menu' stamp: 'di 12/20/1998 13:55'! menuPageSoundForThisPage: evt currentPage ifNotNil: [^ self menuPageSoundFor: currentPage event: evt]! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! menuPageVisualFor: target event: evt | tSpec menu subMenu directionChoices | tSpec _ self transitionSpecFor: target. menu _ (MenuMorph entitled: 'Choose an effect (it is now ' , tSpec second , ')') defaultTarget: target. TransitionMorph allEffects do: [:effect | directionChoices _ TransitionMorph directionsForEffect: effect. directionChoices isEmpty ifTrue: [menu add: effect target: target selector: #setProperty:toValue: argumentList: (Array with: #transitionSpec with: (Array with: tSpec first with: effect with: #none))] ifFalse: [subMenu _ MenuMorph new. directionChoices do: [:dir | subMenu add: dir target: target selector: #setProperty:toValue: argumentList: (Array with: #transitionSpec with: (Array with: tSpec first with: effect with: dir))]. menu add: effect subMenu: subMenu]]. menu popUpEvent: evt in: self world! ! !BookMorph methodsFor: 'menu' stamp: 'di 12/20/1998 17:16'! menuPageVisualForAll: evt ^ self menuPageVisualFor: self event: evt! ! !BookMorph methodsFor: 'menu' stamp: 'di 12/20/1998 13:55'! menuPageVisualForThisPage: evt currentPage ifNotNil: [^ self menuPageVisualFor: currentPage event: evt]! ! !BookMorph methodsFor: 'menu' stamp: 'sw 5/23/2000 02:14'! pageControlsVisible ^ self hasSubmorphWithProperty: #pageControl! ! !BookMorph methodsFor: 'menu' stamp: 'sw 5/14/1998 11:04'! pasteBookPage | aPage | aPage _ self primaryHand objectToPaste. self insertPage: aPage pageSize: aPage extent atIndex: ((pages indexOf: currentPage) - 1). "self goToPageMorph: aPage"! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 7/5/2000 11:14'! reload "Fetch the pages of this book from the server again. For all pages that have not been modified, keep current ones. Use new pages. For each, look up in cache, if time there is equal to time of new, and its in, use the current morph. Later do fancy things when a page has changed here, and also on the server." | url onServer onPgs sq which | (url _ self valueOfProperty: #url) ifNil: ["for .bo index file" [ url _ FillInTheBlank request: 'url of the place where this book''s index is stored. Must begin with file:// or ftp://' initialAnswer: (self getStemUrl, '.bo'). ] valueWithWorld: self world. url size > 0 ifTrue: [self setProperty: #url toValue: url] ifFalse: [^ self]]. onServer _ self class new fromURL: url. "Later: test book times?" onPgs _ onServer pages collect: [:out | sq _ SqueakPageCache pageCache at: out url ifAbsent: [nil]. (sq ~~ nil and: [sq contentsMorph isInMemory]) ifTrue: [((out sqkPage lastChangeTime > sq lastChangeTime) or: [sq contentsMorph == nil]) ifTrue: [SqueakPageCache atURL: out url put: out sqkPage. out] ifFalse: [sq contentsMorph]] ifFalse: [SqueakPageCache atURL: out url put: out sqkPage. out]]. which _ (onPgs findFirst: [:pg | pg url = currentPage url]) max: 1. self newPages: onPgs currentIndex: which. "later stay at current page" self setProperty: #modTime toValue: (onServer valueOfProperty: #modTime). self setProperty: #allText toValue: (onServer valueOfProperty: #allText). self setProperty: #allTextUrls toValue: (onServer valueOfProperty: #allTextUrls). ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 3/28/2000 22:03'! reserveUrls "Save a dummy version of the book first, assign all pages URLs, write dummy files to reserve the url, and write the index. Good when I have pages with interpointing bookmarks." | stem | (stem _ self getStemUrl) size = 0 ifTrue: [^ self]. pages doWithIndex: [:pg :ind | "does write the current page too" pg url ifNil: [pg reserveUrl: stem,(ind printString),'.sp']]. "self saveIndexOnURL." ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/25/1999 10:37'! reserveUrlsIfNeeded "See if this book needs to pre-allocate urls. Harmless if have urls already. Actually writes dummy files to reserve names." | baddies bad2 | pages size > 25 ifTrue: [^ self reserveUrls]. baddies _ BookPageThumbnailMorph withAllSubclasses. bad2 _ FlexMorph withAllSubclasses. pages do: [:aPage | aPage allMorphsDo: [:mm | (baddies includes: mm class) ifTrue: [^ self reserveUrls]. (bad2 includes: mm class) ifTrue: [ mm originalMorph class == aPage class ifTrue: [ ^ self reserveUrls]]]]. ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/27/1999 14:24'! saveAsNumberedURLs "Write out all pages in this book that are not showing, onto a server. The local disk could be the server. For any page that does not have a SqueakPage and a url already, name that page file by its page number. Any pages that are already totally out will stay that way." | stem list firstTime | firstTime _ (self valueOfProperty: #url) == nil. stem _ self getStemUrl. "user must approve" stem size = 0 ifTrue: [^ self]. firstTime ifTrue: [self setProperty: #futureUrl toValue: stem, '.bo']. self reserveUrlsIfNeeded. pages doWithIndex: [:aPage :ind | "does write the current page too" aPage isInMemory ifTrue: ["not out now" aPage presenter ifNotNil: [aPage presenter flushPlayerListCache]. aPage saveOnURL: stem,(ind printString),'.sp'. ]]. list _ pages collect: [:aPage | aPage sqkPage prePurge]. "knows not to purge the current page" list _ (list select: [:each | each notNil]) asArray. "do bulk become:" (list collect: [:each | each contentsMorph]) elementsExchangeIdentityWith: (list collect: [:spg | MorphObjectOut new xxxSetUrl: spg url page: spg]). self saveIndexOnURL. self presenter ifNotNil: [self presenter flushPlayerListCache]. firstTime ifTrue: ["Put a thumbnail into the hand" URLMorph grabForBook: self. self setProperty: #futureUrl toValue: nil]. "clean up" ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 3/28/2000 21:42'! saveIndexOfOnly: aPage "Modify the index of this book on a server. Read the index, modify the entry for just this page, and write back. See saveIndexOnURL. (page file names must be unique even if they live in different directories.)" | mine sf remoteFile strm remote pageURL num pre index after dict allText allTextUrls fName | mine _ self valueOfProperty: #url. mine ifNil: [^ self saveIndexOnURL]. Cursor wait showWhile: [strm _ (ServerFile new fullPath: mine)]. strm ifNil: [^ self saveIndexOnURL]. strm class == String ifTrue: [^ self saveIndexOnURL]. strm exists ifFalse: [^ self saveIndexOnURL]. "write whole thing if missing" strm _ strm asStream. strm class == String ifTrue: [^ self saveIndexOnURL]. remote _ strm fileInObjectAndCode. dict _ remote at: 1. allText _ dict at: #allText ifAbsent: [nil]. "remote, not local" allTextUrls _ dict at: #allTextUrls ifAbsent: [nil]. allText size + 1 ~= remote size ifTrue: [self error: '.bo size mismatch. Please tell Ted what you just did to this book.']. (pageURL _ aPage url) ifNil: [self error: 'just had one!!']. fName _ pageURL copyAfterLast: $/. 2 to: remote size do: [:ii | ((remote at: ii) url findString: fName startingAt: 1 caseSensitive: false) > 0 ifTrue: [index _ ii]. "fast" (remote at: ii) xxxReset]. index ifNil: ["new page, what existing page does it follow?" num _ self pageNumberOf: aPage. 1 to: num-1 do: [:ii | (pages at: ii) url ifNotNil: [pre _ (pages at: ii) url]]. pre ifNil: [after _ remote size+1] ifNotNil: ["look for it on disk, put me after" pre _ pre copyAfterLast: $/. 2 to: remote size do: [:ii | ((remote at: ii) url findString: pre startingAt: 1 caseSensitive: false) > 0 ifTrue: [after _ ii+1]]. after ifNil: [after _ remote size+1]]. remote _ remote copyReplaceFrom: after to: after-1 with: #(1). allText ifNotNil: [ dict at: #allText put: (allText copyReplaceFrom: after-1 to: after-2 with: #(())). dict at: #allTextUrls put: (allTextUrls copyReplaceFrom: after-1 to: after-2 with: #(()))]. index _ after]. remote at: index put: (aPage sqkPage copyForSaving). (dict at: #modTime ifAbsent: [0]) < Time totalSeconds ifTrue: [dict at: #modTime put: Time totalSeconds]. allText ifNotNil: [ (dict at: #allText) at: index-1 put: (aPage allStringsAfter: nil). (dict at: #allTextUrls) at: index-1 put: pageURL]. sf _ ServerDirectory new fullPath: mine. Cursor wait showWhile: [ remoteFile _ sf fileNamed: mine. remoteFile fileOutClass: nil andObject: remote. "remoteFile close"]. ! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 8/30/2000 11:47'! saveIndexOnURL "Make up an index to the pages of this book, with thumbnails, and store it on the server. (aDictionary, aMorphObjectOut, aMorphObjectOut, aMorphObjectOut). The last part corresponds exactly to what pages looks like when they are all out. Each holds onto a SqueakPage, which holds a url and a thumbnail." | dict list mine sf remoteFile urlList | pages size = 0 ifTrue: [^ self]. dict _ Dictionary new. dict at: #modTime put: Time totalSeconds. "self getAllText MUST have been called at start of this operation." dict at: #allText put: (self valueOfProperty: #allText). #(color borderWidth borderColor pageSize) do: [:sel | dict at: sel put: (self perform: sel)]. self reserveUrlsIfNeeded. "should already be done" list _ pages copy. "paste dict on front below" "Fix up the entries, should already be done" list doWithIndex: [:out :ind | out isInMemory ifTrue: [ (out valueOfProperty: #SqueakPage) ifNil: [ out saveOnURLbasic]. list at: ind put: (out sqkPage copyForSaving)]]. urlList _ list collect: [:ppg | ppg url]. self setProperty: #allTextUrls toValue: urlList. dict at: #allTextUrls put: urlList. list _ (Array with: dict), list. mine _ self valueOfProperty: #url. mine ifNil: [mine _ self getStemUrl, '.bo'. self setProperty: #url toValue: mine]. sf _ ServerDirectory new fullPath: mine. Cursor wait showWhile: [ remoteFile _ sf fileNamed: mine. remoteFile dataIsValid. remoteFile fileOutClass: nil andObject: list. "remoteFile close"]. ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/25/1999 10:22'! saveOnUrlPage: pageMorph "Write out this single page in this book onto a server. See savePagesOnURL. (Don't compute the texts, only this page's is written.)" | stem ind response rand newPlace dir | (self valueOfProperty: #keepTogether) ifNotNil: [ self inform: 'This book is marked ''keep in one file''. Several pages use a common Player. Save the owner of the book instead.'. ^ self]. "Don't give the chance to put in a different place. Assume named by number" ((self valueOfProperty: #url) == nil and: [pages first url ~~ nil]) ifTrue: [ response _ (PopUpMenu labels: 'Old book\New book sharing old pages' withCRs) startUpWithCaption: 'Modify the old book, or make a new\book sharing its pages?' withCRs. response = 2 ifTrue: [ "Make up new url for .bo file and confirm with user." "Mark as shared" [rand _ String new: 4. 1 to: rand size do: [:ii | rand at: ii put: ('bdfghklmnpqrstvwz' at: 17 atRandom)]. (newPlace _ self getStemUrl) size = 0 ifTrue: [^ self]. newPlace _ (newPlace copyUpToLast: $/), '/BK', rand, '.bo'. dir _ ServerFile new fullPath: newPlace. (dir includesKey: dir fileName)] whileTrue. "keep doing until a new file" self setProperty: #url toValue: newPlace]. response = 0 ifTrue: [^ self]]. stem _ self getStemUrl. "user must approve" stem size = 0 ifTrue: [^ self]. ind _ pages identityIndexOf: pageMorph ifAbsent: [self error: 'where is the page?']. pageMorph isInMemory ifTrue: ["not out now" pageMorph saveOnURL: stem,(ind printString),'.sp']. self saveIndexOfOnly: pageMorph.! ! !BookMorph methodsFor: 'menu' stamp: 'tk 1/12/1999 18:58'! saveOneOnURL "Write out this single page onto a server. See savePagesOnURL. (Don't compute the texts, only this page's is written.)" ^ self saveOnUrlPage: currentPage! ! !BookMorph methodsFor: 'menu' stamp: 'tk 2/27/1999 14:23'! savePagesOnURL "Write out all pages in this book onto a server. For any page that does not have a SqueakPage and a url already, ask the user for one. Give the option of naming all page files by page number. Any pages that are not in memory will stay that way. The local disk could be the server." | response list firstTime newPlace rand dir bookUrl | (self valueOfProperty: #keepTogether) ifNotNil: [ self inform: 'This book is marked ''keep in one file''. Several pages use a common Player. Save the owner of the book instead.'. ^ self]. self getAllText. "stored with index later" response _ (PopUpMenu labels: 'Use page numbers\Type in file names\Save in a new place (using page numbers)\Save in a new place (typing names)\Save new book sharing old pages' withCRs) startUpWithCaption: 'Each page will be a file on the server. \Do you want to page numbers be the names of the files? \or name each one yourself?' withCRs. response = 1 ifTrue: [self saveAsNumberedURLs. ^ self]. response = 3 ifTrue: [self forgetURLs; saveAsNumberedURLs. ^ self]. response = 4 ifTrue: [self forgetURLs]. response = 5 ifTrue: [ "Make up new url for .bo file and confirm with user." "Mark as shared" [rand _ String new: 4. 1 to: rand size do: [:ii | rand at: ii put: ('bdfghklmnpqrstvwz' at: 17 atRandom)]. (newPlace _ self getStemUrl) size = 0 ifTrue: [^ self]. newPlace _ (newPlace copyUpToLast: $/), '/BK', rand, '.bo'. dir _ ServerFile new fullPath: newPlace. (dir includesKey: dir fileName)] whileTrue. "keep doing until a new file" self setProperty: #url toValue: newPlace. self saveAsNumberedURLs. bookUrl _ self valueOfProperty: #url. (SqueakPage stemUrl: bookUrl) = (SqueakPage stemUrl: currentPage url) ifTrue: [ bookUrl _ true]. "not a shared book" (URLMorph grabURL: currentPage url) book: bookUrl. ^ self]. response = 0 ifTrue: [^ self]. "self reserveUrlsIfNeeded. Need two passes here -- name on one, write on second" pages do: [:aPage | "does write the current page too" aPage isInMemory ifTrue: ["not out now" aPage presenter ifNotNil: [aPage presenter flushPlayerListCache]. aPage saveOnURLbasic. ]]. "ask user if no url" list _ pages collect: [:aPage | aPage sqkPage prePurge]. "knows not to purge the current page" list _ (list select: [:each | each notNil]) asArray. "do bulk become:" (list collect: [:each | each contentsMorph]) elementsExchangeIdentityWith: (list collect: [:spg | MorphObjectOut new xxxSetUrl: spg url page: spg]). firstTime _ (self valueOfProperty: #url) == nil. self saveIndexOnURL. self presenter ifNotNil: [self presenter flushPlayerListCache]. firstTime ifTrue: ["Put a thumbnail into the hand" URLMorph grabForBook: self. self setProperty: #futureUrl toValue: nil]. "clean up" ! ! !BookMorph methodsFor: 'menu' stamp: 'tk 8/13/1998 12:09'! setNewPagePrototype "Record the current page as the prototype to be copied when inserting new pages." currentPage ifNotNil: [newPagePrototype _ currentPage veryDeepCopy]. ! ! !BookMorph methodsFor: 'menu' stamp: 'sw 9/6/2000 18:43'! setPageColor "Get a color from the user, then set all the pages to that color" self currentPage ifNil: [^ self]. ColorPickerMorph new choseModalityFromPreference; sourceHand: self activeHand; target: self; selector: #setAllPagesColor:; originalColor: self currentPage color; putUpFor: self near: self fullBoundsInWorld! ! !BookMorph methodsFor: 'menu' stamp: 'tk 11/8/2000 11:44'! textSearch "search the text on all pages of this book" | wanted wants list str | list _ self valueOfProperty: #searchKey ifAbsent: [#()]. str _ String streamContents: [:strm | list do: [:each | strm nextPutAll: each; space]]. wanted _ FillInTheBlank request: 'words to search for. Order is not important. Beginnings of words are OK.' initialAnswer: str. wants _ wanted findTokens: Character separators. wants size = 0 ifTrue: [^ self]. self getAllText. "save in allText, allTextUrls" ^ self findText: wants "goes to the page and highlights the text"! ! !BookMorph methodsFor: 'menu' stamp: 'tk 11/8/2000 11:48'! textSearch: stringWithKeys "search the text on all pages of this book" | wants | wants _ stringWithKeys findTokens: Character separators. wants size = 0 ifTrue: [^ self]. self getAllText. "save in allText, allTextUrls" ^ self findText: wants "goes to the page and highlights the text"! ! !BookMorph methodsFor: 'menu' stamp: 'di 1/4/1999 12:49'! thumbnailForThisPage self primaryHand attachMorph: (currentPage thumbnailForPageSorter pageMorph: currentPage inBook: self) ! ! !BookMorph methodsFor: 'menu' stamp: 'RAA 8/23/2000 12:20'! toggleFullScreen self isInFullScreenMode ifTrue: [self exitFullScreen] ifFalse: [self goFullScreen]! ! !BookMorph methodsFor: 'menu' stamp: 'sw 5/23/2000 02:18'! toggleShowingOfPageControls self pageControlsVisible ifTrue: [self hidePageControls] ifFalse: [self showPageControls]! ! !BookMorph methodsFor: 'menu' stamp: 'di 12/23/1998 14:55'! uncachePageSorter pages do: [:aPage | aPage removeProperty: #cachedThumbnail].! ! !BookMorph methodsFor: 'copying' stamp: 'tk 8/13/97 15:00'! copyRecordingIn: dict "Overridden to copy the pages of this book as well." | new | new _ super copyRecordingIn: dict. new pages: (pages collect: [:pg | "the current page was copied with the submorphs" (dict includesKey: pg) ifTrue: [dict at: pg] "current page; already copied" ifFalse: [pg copyRecordingIn: dict]]). ^ new ! ! !BookMorph methodsFor: 'copying' stamp: 'jm 7/1/97 17:06'! updateReferencesUsing: aDictionary super updateReferencesUsing: aDictionary. pages do: [:page | page allMorphsDo: [:m | m updateReferencesUsing: aDictionary]]. ! ! !BookMorph methodsFor: 'other' stamp: 'tk 12/15/1998 14:32'! abandon "Like delete, but we really intend not to use this morph again. Make the page cache release the page object." | pg | self delete. pages do: [:aPage | (pg _ aPage sqkPage) ifNotNil: [ pg contentsMorph == aPage ifTrue: [ pg contentsMorph: nil]]].! ! !BookMorph methodsFor: 'other' stamp: 'RAA 8/23/2000 13:02'! adjustCurrentPageForFullScreen self isInFullScreenMode ifTrue: [ (currentPage hasProperty: #sizeWhenNotFullScreen) ifFalse: [ currentPage setProperty: #sizeWhenNotFullScreen toValue: currentPage extent. ]. currentPage extent: Display extent. ] ifFalse: [ (currentPage hasProperty: #sizeWhenNotFullScreen) ifTrue: [ currentPage extent: (currentPage valueOfProperty: #sizeWhenNotFullScreen). currentPage removeProperty: #sizeWhenNotFullScreen. ]. ].! ! !BookMorph methodsFor: 'other' stamp: 'sw 10/2/97 15:22'! configureForKids super configureForKids. pages do: [:aPage | aPage configureForKids].! ! !BookMorph methodsFor: 'other' stamp: 'ar 9/14/2000 16:46'! defersHaloOnClickTo: aSubMorph "If a cmd-click on aSubMorph would make it a preferred recipient of the halo, answer true" ^ currentPage notNil and: [aSubMorph hasOwner: currentPage] ! ! !BookMorph methodsFor: 'other' stamp: 'RAA 8/23/2000 12:43'! exitFullScreen | floater | self isInFullScreenMode ifFalse: [^self]. self setProperty: #fullScreenMode toValue: false. floater _ self valueOfProperty: #floatingPageControls ifAbsent: [nil]. floater ifNotNil: [ floater delete. self removeProperty: #floatingPageControls. ]. self position: 0@0. self adjustCurrentPageForFullScreen. ! ! !BookMorph methodsFor: 'other' stamp: 'RAA 8/23/2000 12:42'! goFullScreen | floater | self isInFullScreenMode ifTrue: [^self]. self setProperty: #fullScreenMode toValue: true. self position: (currentPage topLeft - self topLeft) negated. self adjustCurrentPageForFullScreen. floater _ self buildFloatingPageControls. self setProperty: #floatingPageControls toValue: floater. floater openInWorld. ! ! !BookMorph methodsFor: 'other' stamp: 'RAA 8/23/2000 11:58'! isInFullScreenMode ^self valueOfProperty: #fullScreenMode ifAbsent: [false]! ! !BookMorph methodsFor: 'other' stamp: 'ar 11/9/2000 20:38'! makeMinimalControlsWithColor: aColor title: aString | aButton aColumn aRow but | aButton _ SimpleButtonMorph new target: self; borderColor: Color black; color: aColor; borderWidth: 0. aColumn _ AlignmentMorph newColumn. aColumn color: aButton color; borderWidth: 0; layoutInset: 0. aColumn hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. aRow _ AlignmentMorph newRow. aRow color: aButton color; borderWidth: 0; layoutInset: 0. aRow hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. aRow addTransparentSpacerOfSize: 40@0. aRow addMorphBack: (but _ aButton fullCopy label: ' < ' ; actionSelector: #previousPage). "fullCopy is OK, since we just made it and it can't own any Players" but setBalloonText: 'Go to previous page'. aRow addTransparentSpacerOfSize: 82@0. aRow addMorphBack: (StringMorph contents: aString) lock. aRow addTransparentSpacerOfSize: 82@0. aRow addMorphBack: (but _ aButton fullCopy label: ' > ' ; actionSelector: #nextPage). but setBalloonText: 'Go to next page'. aRow addTransparentSpacerOfSize: 40@0. aColumn addMorphBack: aRow. aColumn setNameTo: 'Page Controls'. ^ aColumn! ! !BookMorph methodsFor: 'other' stamp: 'tk 10/22/1998 15:42'! releaseCachedState "Release the cached state of all my pages." super releaseCachedState. pages do: [:page | page fullReleaseCachedState]. ! ! !BookMorph methodsFor: 'other' stamp: 'sw 10/1/1998 13:40'! resizePagesTo: anExtent pages do: [:aPage | aPage extent: anExtent]! ! !BookMorph methodsFor: 'other' stamp: 'sw 8/11/1998 16:50'! succeededInRevealing: aPlayer currentPage ifNotNil: [currentPage player == aPlayer ifTrue: [^ true]]. pages do: [:aPage | (aPage succeededInRevealing: aPlayer) ifTrue: [self goToPageMorph: aPage. ^ true]]. ^ false! ! !BookMorph methodsFor: 'other' stamp: 'RAA 12/26/2000 14:31'! wrappedInPartsWindowWithTitle: aTitle | aWindow | self fullBounds. aWindow _ (PartsWindow labelled: aTitle) model: Model new. aWindow book: self. ^ aWindow! ! !BookMorph methodsFor: 'drawing' stamp: 'mpw 9/13/1999 20:22'! fullDrawPostscriptOn:aCanvas ^aCanvas fullDrawBookMorph:self. ! ! !BookMorph methodsFor: 'printing'! asPostscript ^self asPostscriptPrintJob. ! ! !BookMorph methodsFor: 'printing' stamp: 'RAA 2/1/2001 17:41'! pagesHandledAutomatically ^true! ! !BookMorph methodsFor: 'printing' stamp: 'di 9/22/1999 10:51'! printPSToFile "Ask the user for a filename and print this morph as postscript." | fileName rotateFlag | fileName _ ('MyBook') asFileName. fileName _ FillInTheBlank request: 'File name? (".ps" will be added to end)' initialAnswer: fileName. fileName size == 0 ifTrue: [^ self beep]. (fileName endsWith: '.ps') ifFalse: [fileName _ fileName,'.ps']. rotateFlag _ ((PopUpMenu labels: 'portrait (tall) landscape (wide)') startUpWithCaption: 'Choose orientation...') = 2. (FileStream newFileNamed: fileName) nextPutAll: (DSCPostscriptCanvas morphAsPostscript: self rotated: rotateFlag); close. ! ! !BookMorph methodsFor: 'scripting' stamp: 'tk 9/7/2000 15:10'! chooseAndRevertToVersion | time which | "Let the user choose an older version for all code in MethodMorphs in this book. Run through that code and revert each one to that time." self methodHolders. "find them in me" self methodHolderVersions. which _ PopUpMenu withCaption: 'Put all scripts in this book back the way they were at this time:' chooseFrom: #('leave as is'), VersionNames. which <= 1 ifTrue: [^ self]. time _ VersionTimes at: which-1. self revertToCheckpoint: time.! ! !BookMorph methodsFor: 'scripting' stamp: 'tk 9/8/2000 14:42'! installRollBackButtons | all | "In each script in me, put a versions button it the upper right." all _ IdentitySet new. self allMorphsAndBookPagesInto: all. all _ all select: [:mm | mm class = MethodMorph]. all do: [:mm | mm installRollBackButtons: self].! ! !BookMorph methodsFor: 'scripting' stamp: 'tk 9/6/2000 23:31'! methodHolderVersions | arrayOfVersions vTimes strings | "Create lists of times of older versions of all code in MethodMorphs in this book." arrayOfVersions _ MethodHolders collect: [:mh | mh versions]. "equality, hash for MethodHolders?" vTimes _ SortedCollection new. arrayOfVersions do: [:versionBrowser | versionBrowser changeList do: [:cr | (strings _ cr stamp findTokens: ' ') size > 2 ifTrue: [ vTimes add: strings second asDate asSeconds + strings third asTime asSeconds]]]. VersionTimes _ Time condenseBunches: vTimes. VersionNames _ Time namesForTimes: VersionTimes. ! ! !BookMorph methodsFor: 'scripting' stamp: 'tk 9/8/2000 14:41'! methodHolders | all | "search for all scripts that are in MethodHolders. These are the ones that have versions." all _ IdentitySet new. self allMorphsAndBookPagesInto: all. all _ all select: [:mm | mm class = MethodMorph]. MethodHolders _ all asArray collect: [:mm | mm model]. ! ! !BookMorph methodsFor: 'scripting' stamp: 'tk 9/7/2000 15:08'! revertToCheckpoint: secsSince1901 | cngRecord | "Put all scripts (that appear in MethodPanes) back to the way they were at an earlier time." MethodHolders do: [:mh | cngRecord _ mh versions versionFrom: secsSince1901. cngRecord ifNotNil: [ (cngRecord stamp: Utilities changeStamp) fileIn]]. "does not delete method if no earlier version" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BookMorph class instanceVariableNames: ''! !BookMorph class methodsFor: 'scripting' stamp: 'sw 9/8/2000 15:24'! additionsToViewerCategories "Answer a list of ( ) pairs that characterize the phrases this kind of morph wishes to add to various Viewer categories." ^ #((#'book navigation' ((command goto: 'go to the given page' player) (command nextPage 'go to next page') (command previousPage 'go to previous page') (command firstPage 'go to first page') (command lastPage 'go to last page'))))! ! !BookMorph class methodsFor: 'scripting' stamp: 'sw 3/6/1999 01:21'! authoringPrototype "Answer an instance of the receiver suitable for placing in a parts bin for authors" | book | book _ self new markAsPartsDonor. book removeEverything; pageSize: 360@228; color: (Color gray: 0.9). book borderWidth: 1; borderColor: Color black. book beSticky. book showPageControls; insertPage. ^ book! ! !BookMorph class methodsFor: 'url' stamp: 'tk 1/13/1999 09:07'! alreadyInFromUrl: aUrl "Does a bookMorph living in some world in this image represent the same set of server pages? If so, don't create another one. It will steal pages from the existing one. Go delete the first one." self withAllSubclassesDo: [:cls | cls allInstancesDo: [:aBook | (aBook valueOfProperty: #url) = aUrl ifTrue: [ aBook world ifNotNil: [ self inform: 'This book is already open in some project'. ^ true]]]]. ^ false! ! !BookMorph class methodsFor: 'url' stamp: 'sma 4/30/2000 10:36'! grabURL: aURLString "Create a BookMorph for this url and put it in the hand." | book | book _ self new fromURL: aURLString. "If this book is already in, we will steal the pages out of it!!!!!!!!" book goToPage: 1. "install it" HandMorph attach: book! ! !BookMorph class methodsFor: 'url' stamp: 'tk 3/28/2000 13:30'! isInWorld: aWorld withUrl: aUrl | urls bks short | "If a book with this url is in the that (current) world, return it. Say if it is out or in another world." urls _ OrderedCollection new. bks _ OrderedCollection new. aWorld allMorphsDo: [:aBook | (aBook isKindOf: BookMorph) ifTrue: [ bks add: aBook. (urls add: (aBook valueOfProperty: #url)) = aUrl ifTrue: [ aBook world == aWorld ifTrue: [^ aBook]]]]. "shortcut" self withAllSubclassesDo: [:cls | cls allInstancesDo: [:aBook | (aBook valueOfProperty: #url) = aUrl ifTrue: [ aBook world == aWorld ifTrue: [^ aBook] ifFalse: [ self inform: 'Book may be open in some other project'. ^ aBook]]]]. "if same book name, use it" short _ (aUrl findTokens: '/') last. urls withIndexDo: [:kk :ind | (kk findTokens: '/') last = short ifTrue: [ ^ bks at: ind]]. ^ #out! ! !BookMorph class methodsFor: 'booksAsProjects' stamp: 'RAA 11/10/2000 11:26'! makeBookOfProjects: aListOfProjects named: aString " BookMorph makeBookOfProjects: (Project allProjects select: [ :each | each world isMorph]) " | book pvm page | book _ self new. book setProperty: #transitionSpec toValue: {'silence'. #none. #none}. aListOfProjects do: [ :each | pvm _ ProjectViewMorph on: each. page _ PasteUpMorph new addMorph: pvm; extent: pvm extent. book insertPage: page pageSize: page extent ]. book goToPage: 1. book deletePageBasic. book setProperty: #nameOfThreadOfProjects toValue: aString. book removeProperty: #transitionSpec. book openInWorld! ! AlignmentMorph subclass: #BookPageSorterMorph instanceVariableNames: 'book pageHolder ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Books'! !BookPageSorterMorph methodsFor: 'copying' stamp: 'tk 1/7/1999 14:36'! veryDeepFixupWith: deepCopier "If fields were weakly copied, fix them here. If they were in the tree being copied, fix them up, otherwise point to the originals." super veryDeepFixupWith: deepCopier. book _ deepCopier references at: book ifAbsent: [book]. ! ! !BookPageSorterMorph methodsFor: 'copying' stamp: 'tk 1/7/1999 14:36'! veryDeepInner: deepCopier "Copy all of my instance variables. Some need to be not copied at all, but shared. Warning!!!! Every instance variable defined in this class must be handled. We must also implement veryDeepFixupWith:. See DeepCopier class comment." super veryDeepInner: deepCopier. "book _ book. Weakly copied" pageHolder _ pageHolder veryDeepCopyWith: deepCopier.! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'jm 6/17/1998 21:27'! acceptSort book acceptSortedContentsFrom: pageHolder. self delete. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:11'! addControls | b r aButton str | b _ SimpleButtonMorph new target: self; borderColor: Color black. r _ AlignmentMorph newRow color: Color transparent; borderWidth: 0; layoutInset: 0. r wrapCentering: #center; cellPositioning: #topCenter; hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. r addMorphBack: (self wrapperFor: (b fullCopy label: 'Okay'; actionSelector: #acceptSort)). r addMorphBack: (self wrapperFor: (b fullCopy label: 'Cancel'; actionSelector: #delete)). r addTransparentSpacerOfSize: 8 @ 0. r addMorphBack: (self wrapperFor: (aButton _ UpdatingThreePhaseButtonMorph checkBox)). aButton target: self; actionSelector: #togglePartsBinStatus; arguments: #(); getSelector: #getPartsBinStatus. str _ StringMorph contents: 'Parts bin'. r addMorphBack: (self wrapperFor: str lock). self addMorphFront: r. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/8/2000 22:49'! book: aBookMorph morphsToSort: morphList | innerBounds | book _ aBookMorph. pageHolder removeAllMorphs. pageHolder addAllMorphs: morphList. pageHolder extent: pageHolder width@pageHolder fullBounds height. innerBounds _ Rectangle merging: (morphList collect: [:m | m bounds]). pageHolder extent: innerBounds extent + pageHolder borderWidth + 6.! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/14/2000 12:10'! changeExtent: aPoint self extent: aPoint. pageHolder extent: self extent - borderWidth. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:11'! closeButtonOnly "Replace my default control panel with one that has only a close button." | b r | self firstSubmorph delete. "remove old control panel" b _ SimpleButtonMorph new target: self; borderColor: Color black. r _ AlignmentMorph newRow. r color: b color; borderWidth: 0; layoutInset: 0. r hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. r wrapCentering: #topLeft. r addMorphBack: (b fullCopy label: 'Close'; actionSelector: #delete). self addMorphFront: r. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:11'! columnWith: aMorph ^AlignmentMorph newColumn color: Color transparent; hResizing: #shrinkWrap; vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #topCenter; layoutInset: 1; addMorph: aMorph ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'di 1/8/1999 16:27'! forBook: aBookMorph ^ self book: aBookMorph morphsToSort: (aBookMorph pages collect: [:p | p thumbnailForPageSorter])! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/28/2000 11:57'! getPartsBinStatus ^pageHolder isPartsBin! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 16:45'! initialize super initialize. self extent: Display extent - 100; listDirection: #topToBottom; wrapCentering: #topLeft; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 3; color: Color lightGray; borderWidth: 2. pageHolder _ PasteUpMorph new behaveLikeHolder extent: self extent - borderWidth. pageHolder hResizing: #shrinkWrap. pageHolder cursor: 0. self addControls. self addMorphBack: pageHolder. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'jm 11/17/97 16:46'! pageHolder ^ pageHolder ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:11'! rowWith: aMorph ^AlignmentMorph newColumn color: Color transparent; hResizing: #shrinkWrap; vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #topCenter; layoutInset: 1; addMorph: aMorph ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/28/2000 11:58'! togglePartsBinStatus pageHolder isPartsBin: pageHolder isPartsBin not! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'ar 9/18/2000 18:34'! wantsToBeDroppedInto: aMorph "Return true if it's okay to drop the receiver into aMorph" ^aMorph isWorldMorph "only into worlds"! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/28/2000 12:10'! wrapperFor: aMorph ^self columnWith: (self rowWith: aMorph) ! ! SketchMorph subclass: #BookPageThumbnailMorph instanceVariableNames: 'page pageNumber bookMorph flipOnClick ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Books'! !BookPageThumbnailMorph commentStamp: '' prior: 0! A small picture representing a page of a BookMorph here or somewhere else. When clicked, make that book turn to the page and do a visual effect and a noise. page either the morph of the page, or a url pageNumber bookMorph either the book, or a url flipOnClick! !BookPageThumbnailMorph methodsFor: 'fileIn/Out' stamp: 'tk 9/28/2000 15:37'! objectForDataStream: refStrm "I am about to be written on an object file. It would be bad to write a whole BookMorph out. Store a string that is the url of the book or page in my inst var." | clone bookUrl bb stem ind | (bookMorph class == String) & (page class == String) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph == nil) & (page class == String) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph == nil) & (page url ~~ nil) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph == nil) & (page url == nil) ifTrue: [ self error: 'page should already have a url'. "find page's book, and remember it" "bookMorph _ "]. clone _ self clone. (bookUrl _ bookMorph url) ifNil: [bookUrl _ self valueOfProperty: #futureUrl]. bookUrl ifNil: [ bb _ RectangleMorph new. "write out a dummy" bb bounds: bounds. refStrm replace: self with: bb. ^ bb] ifNotNil: [clone instVarNamed: 'bookMorph' put: bookUrl]. page url ifNil: [ "Need to assign a url to a page that will be written later. It might have bookmarks too. Don't want to recurse deeply. Have that page write out a dummy morph to save its url on the server." stem _ SqueakPage stemUrl: bookUrl. ind _ bookMorph pages identityIndexOf: page. page reserveUrl: stem,(ind printString),'.sp']. clone instVarNamed: 'page' put: page url. refStrm replace: self with: clone. ^ clone! ! !BookPageThumbnailMorph methodsFor: 'fileIn/Out' stamp: 'tk 2/24/1999 15:00'! objectsInMemory "See if page or bookMorph need to be brought in from a server." | bookUrl bk wld try | bookMorph ifNil: ["fetch the page" page class == String ifFalse: [^ self]. "a morph" try _ (SqueakPageCache atURL: page) fetchContents. try ifNotNil: [page _ try]. ^ self]. bookMorph class == String ifTrue: [ bookUrl _ bookMorph. (wld _ self world) ifNil: [wld _ Smalltalk currentWorld]. bk _ BookMorph isInWorld: wld withUrl: bookUrl. bk == #conflict ifTrue: [ ^ self inform: 'This book is already open in some other project']. bk == #out ifTrue: [ (bk _ BookMorph new fromURL: bookUrl) ifNil: [^ self]]. bookMorph _ bk]. page class == String ifTrue: [ page _ (bookMorph pages detect: [:pg | pg url = page] ifNone: [bookMorph pages at: 1])]. ! ! !BookPageThumbnailMorph methodsFor: 'copying' stamp: 'tk 1/6/1999 19:35'! veryDeepFixupWith: deepCopier "If target and arguments fields were weakly copied, fix them here. If they were in the tree being copied, fix them up, otherwise point to the originals!!!!" super veryDeepFixupWith: deepCopier. page _ deepCopier references at: page ifAbsent: [page]. bookMorph _ deepCopier references at: bookMorph ifAbsent: [bookMorph]. ! ! !BookPageThumbnailMorph methodsFor: 'copying' stamp: 'tk 1/7/1999 14:35'! veryDeepInner: deepCopier "Copy all of my instance variables. Some need to be not copied at all, but shared. Warning!!!! Every instance variable defined in this class must be handled. We must also implement veryDeepFixupWith:. See DeepCopier class comment." super veryDeepInner: deepCopier. "page _ page. Weakly copied" pageNumber _ pageNumber veryDeepCopyWith: deepCopier. "bookMorph _ bookMorph. All weakly copied" flipOnClick _ flipOnClick veryDeepCopyWith: deepCopier. ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:57'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. aCustomMenu add: 'make a flex morph' selector: #makeFlexMorphFor: argument: aHandMorph. flipOnClick ifTrue: [aCustomMenu add: 'disable bookmark action' action: #toggleBookmark] ifFalse: [aCustomMenu add: 'enable bookmark action' action: #toggleBookmark]. (bookMorph isKindOf: BookMorph) ifTrue: [aCustomMenu add: 'set page sound' action: #setPageSound:. aCustomMenu add: 'set page visual' action: #setPageVisual:] ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 18:50'! addMorphsTo: morphList pianoRoll: pianoRoll eventTime: t betweenTime: leftTime and: rightTime t > rightTime ifTrue: [^ self]. t < leftTime ifTrue: [^ self]. morphList add: (self left: (pianoRoll xForTime: t)). ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/28/2000 11:12'! bookMorph ^bookMorph! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'tk 2/24/1999 00:01'! computeThumbnail | f scale | self objectsInMemory. f _ page imageForm. scale _ (self height / f height). "keep height invariant" "(Sensor shiftPressed) ifTrue: [scale _ scale * 1.4]." self form: (f magnify: f boundingBox by: scale@scale smoothing: 2). ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'tk 2/24/1999 13:24'! doPageFlip "Flip to this page" self objectsInMemory. bookMorph ifNil: [^ self]. bookMorph goToPageMorph: page transitionSpec: (self valueOfProperty: #transitionSpec). (owner isKindOf: PasteUpMorph) ifTrue: [owner cursor: (owner submorphs indexOf: self ifAbsent: [1])]! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 12/23/1998 15:57'! encounteredAtTime: ticks inScorePlayer: scorePlayer atIndex: index inEventTrack: track secsPerTick: secsPerTick "Flip to this page with no extra sound" BookMorph turnOffSoundWhile: [self doPageFlip]! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 1/4/1999 12:19'! handlesMouseDown: event ^ event shiftPressed or: [flipOnClick and: [event controlKeyPressed not]]! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 1/4/1999 12:52'! inBook: book bookMorph _ book! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 12/23/1998 15:45'! initialize | f | super initialize. flipOnClick _ false. color _ Color lightGray. "background color" f _ Form extent: 60@80 depth: Display depth. f fill: f boundingBox fillColor: color. self form: f. ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 19:03'! justDroppedIntoPianoRoll: pianoRoll event: evt | ambientEvent startTimeInScore | startTimeInScore _ pianoRoll timeForX: self left. ambientEvent _ AmbientEvent new morph: self; time: startTimeInScore. pianoRoll score addAmbientEvent: ambientEvent. "self endTime > pianoRoll scorePlayer durationInTicks ifTrue: [pianoRoll scorePlayer updateDuration]" ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/6/2000 16:36'! makeFlexMorphFor: aHand aHand grabMorph: (FlexMorph new originalMorph: page)! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'tk 3/10/1999 11:25'! mouseDown: event "turn the book to that page" event setButtons: 0. "Lie to it. So mouseUp won't go to menu that may come up during fetch of a page in doPageFlip" self doPageFlip. ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'jm 11/17/97 17:30'! page ^ page ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 1/4/1999 13:39'! page: aMorph page _ aMorph. self computeThumbnail. self setNameTo: aMorph externalName. page fullReleaseCachedState. ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 1/4/1999 12:48'! pageMorph: pageMorph inBook: book page _ pageMorph. bookMorph _ book! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 8/6/1998 23:45'! pageNumber: n inBook: b pageNumber _ n. bookMorph _ b! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 18:28'! pauseFrom: x! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 18:29'! resetFrom: scorePlayer "Ignored"! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/9/2000 18:30'! resumeFrom: scorePlayer "Ignored"! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 12/20/1998 17:29'! setPageSound: event ^ bookMorph menuPageSoundFor: self event: event! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 12/20/1998 17:29'! setPageVisual: event ^ bookMorph menuPageVisualFor: self event: event! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 8/8/1998 14:06'! smaller self form: (self form copy: (0@0 extent: self form extent//2)). ! ! !BookPageThumbnailMorph methodsFor: 'as yet unclassified' stamp: 'di 12/23/1998 15:53'! toggleBookmark "Enable or disable sensitivity as a bookmark enabled means that a normal click will cause a pageFlip disabled means this morph can be picked up normally by the hand." flipOnClick _ flipOnClick not! ! AlignmentMorph subclass: #BooklikeMorph instanceVariableNames: 'pageSize newPagePrototype ' classVariableNames: 'PageFlipSoundOn ' poolDictionaries: '' category: 'Morphic-Books'! !BooklikeMorph commentStamp: '' prior: 0! A common superclass for BookMorph and WebBookMorph! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 5/23/2000 13:07'! fewerPageControls self currentEvent shiftPressed ifTrue: [self hidePageControls] ifFalse: [self showPageControls: self shortControlSpecs]! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 3/8/1999 10:22'! fullControlSpecs ^ #( spacer variableSpacer ('-' deletePage 'Delete this page') spacer ( 'Ç' firstPage 'First page') spacer ( '<' previousPage 'Previous page') spacer ('¥' invokeBookMenu 'Click here to get a menu of options for this book.') spacer ('>' nextPage 'Next page') spacer ( 'È' lastPage 'Final page') spacer ('+' insertPage 'Add a new page after this one') variableSpacer ('×' fewerPageControls 'Fewer controls') )! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 7/4/1998 16:12'! hidePageControls "Delete all submorphs answering to the property #pageControl" self deleteSubmorphsWithProperty: #pageControl! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'ar 11/9/2000 20:38'! makePageControlsFrom: controlSpecs "From the controlSpecs, create a set of page control and return them -- this method does *not* add the controls to the receiver." | c aButton col row b lastGuy | c _ (color saturation > 0.1) ifTrue: [color slightlyLighter] ifFalse: [color slightlyDarker]. aButton _ SimpleButtonMorph new target: self; borderWidth: 1; borderColor: Color veryLightGray; color: c. col _ AlignmentMorph newColumn. col color: c; borderWidth: 0; layoutInset: 0. col hResizing: #spaceFill; vResizing: #shrinkWrap; extent: 5@5. row _ AlignmentMorph newRow. row color: c; borderWidth: 0; layoutInset: 0. row hResizing: #spaceFill; vResizing: #shrinkWrap; extent: 5@5. controlSpecs do: [:spec | spec == #spacer ifTrue: [row addTransparentSpacerOfSize: (10 @ 0)] ifFalse: [spec == #variableSpacer ifTrue: [row addMorphBack: AlignmentMorph newVariableTransparentSpacer] ifFalse: [b _ aButton fullCopy label: spec first; actionSelector: spec second; borderWidth: 0; setBalloonText: spec third. row addMorphBack: b. (((lastGuy _ spec last asLowercase) includesSubString: 'menu') or: [lastGuy includesSubString: 'designations']) ifTrue: [b actWhen: #buttonDown]]]]. "pop up menu on mouseDown" col addMorphBack: row. ^ col! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 3/8/1999 10:22'! shortControlSpecs ^ #( spacer variableSpacer ( '<' previousPage 'Previous page') spacer ('¥' invokeBookMenu 'Click here to get a menu of options for this book.') spacer ('>' nextPage 'Next page') variableSpacer ('×' showMoreControls 'More controls'))! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 3/2/1999 15:01'! showPageControls self showPageControls: self shortControlSpecs! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'ar 11/9/2000 20:38'! showPageControls: controlSpecs | spacer pageControls anIndex | self hidePageControls. anIndex _ (submorphs size > 0 and: [submorphs first hasProperty: #header]) ifTrue: [2] ifFalse: [1]. spacer _ Morph new color: color; extent: 0@10. spacer setProperty: #pageControl toValue: true. self privateAddMorph: spacer atIndex: anIndex. pageControls _ self makePageControlsFrom: controlSpecs. pageControls borderWidth: 0; layoutInset: 4. pageControls setProperty: #pageControl toValue: true. pageControls setNameTo: 'Page Controls'. pageControls eventHandler: (EventHandler new on: #mouseDown send: #move to: self). self privateAddMorph: pageControls beSticky atIndex: anIndex! ! !BooklikeMorph methodsFor: 'menu commands' stamp: 'sw 7/4/1998 17:36'! addCustomMenuItems: aCustomMenu hand: aHandMorph "This factoring allows subclasses to have different menu yet still use the super call for the rest of the metamenu." super addCustomMenuItems: aCustomMenu hand: aHandMorph. self addBookMenuItemsTo: aCustomMenu hand: aHandMorph! ! !BooklikeMorph methodsFor: 'menu commands' stamp: 'sw 7/4/1998 15:39'! clearNewPagePrototype newPagePrototype _ nil ! ! !BooklikeMorph methodsFor: 'menu commands' stamp: 'sw 7/4/1998 15:40'! firstPage self goToPage: 1! ! !BooklikeMorph methodsFor: 'menu commands' stamp: 'sw 7/4/1998 17:18'! insertPage self insertPageColored: self color! ! !BooklikeMorph methodsFor: 'menu commands' stamp: 'tk 2/25/1999 11:04'! sortPages | sorter | sorter _ BookPageSorterMorph new book: self morphsToSort: self morphsForPageSorter. sorter pageHolder cursor: self pageNumber. "Align at bottom right of screen, but leave 20-pix margin." self bottom + sorter height < Display height ifTrue: "Place it below if it fits" [^ self world addMorphFront: (sorter align: sorter topLeft with: self bottomLeft)]. self right + sorter width < Display width ifTrue: "Place it below if it fits" [^ self world addMorphFront: (sorter align: sorter bottomLeft with: self bottomRight)]. "Otherwise, place it at lower right of screen" self world addMorphFront: (sorter position: Display extent - (20@20) - sorter extent). ! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 7/4/1998 15:56'! addBookMenuItemsTo: aCustomMenu hand: aHandMorph (self hasSubmorphWithProperty: #pageControl) ifTrue: [aCustomMenu add: 'hide page controls' action: #hidePageControls] ifFalse: [aCustomMenu add: 'show page controls' action: #showPageControls]! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 8/11/1998 16:51'! currentPlayerDo: aBlock | aPlayer aPage | (aPage _ self currentPage) ifNil: [^ self]. (aPlayer _ aPage player) ifNotNil: [aBlock value: aPlayer]! ! !BooklikeMorph methodsFor: 'misc' stamp: 'ar 10/10/2000 16:09'! move (owner isWorldMorph and:[self isSticky not]) ifTrue: [self activeHand grabMorph: self]! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 7/4/1998 15:36'! pageSize ^ pageSize ! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 7/4/1998 16:51'! pageSize: aPoint pageSize _ aPoint! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 2/15/1999 19:17'! playPageFlipSound: soundName self presenter ifNil: [^ self]. "Avoid failures when called too early" (Preferences soundsEnabled "user-controllable" and: [PageFlipSoundOn]) "mechanism to suppress sounds at init time" ifTrue: [self playSoundNamed: soundName]. ! ! !BooklikeMorph methodsFor: 'misc' stamp: 'RAA 8/23/2000 12:19'! showingFullScreenString ^ self isInFullScreenMode ifTrue: ['exit full screen'] ifFalse: ['show full screen']! ! !BooklikeMorph methodsFor: 'misc' stamp: 'sw 5/23/2000 02:16'! showingPageControlsString ^ self pageControlsVisible ifTrue: ['hide page controls'] ifFalse: ['show page controls']! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BooklikeMorph class instanceVariableNames: ''! !BooklikeMorph class methodsFor: 'as yet unclassified' stamp: 'sw 7/4/1998 15:59'! initialize "BooklikeMorph initialize" PageFlipSoundOn _ true ! ! !BooklikeMorph class methodsFor: 'as yet unclassified' stamp: 'sw 7/4/1998 16:43'! turnOffSoundWhile: aBlock "Turn off page flip sound during the given block." | old | old _ PageFlipSoundOn. PageFlipSoundOn _ false. aBlock value. PageFlipSoundOn _ old! ! Object subclass: #Boolean instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Objects'! !Boolean commentStamp: '' prior: 0! Boolean is an abstract class defining the protocol for logic testing operations and conditional control structures for the logical values represented by the instances of its subclasses True and False. Boolean redefines #new so no instances of Boolean can be created. It also redefines several messages in the 'copying' protocol to ensure that only one instance of each of its subclasses True (the global true, logical assertion) and False (the global false, logical negation) ever exist in the system.! !Boolean methodsFor: 'logical operations'! & aBoolean "Evaluating conjunction. Evaluate the argument. Then answer true if both the receiver and the argument are true." self subclassResponsibility! ! !Boolean methodsFor: 'logical operations'! eqv: aBoolean "Answer true if the receiver is equivalent to aBoolean." ^self == aBoolean! ! !Boolean methodsFor: 'logical operations'! not "Negation. Answer true if the receiver is false, answer false if the receiver is true." self subclassResponsibility! ! !Boolean methodsFor: 'logical operations'! xor: aBoolean "Exclusive OR. Answer true if the receiver is not equivalent to aBoolean." ^(self == aBoolean) not! ! !Boolean methodsFor: 'logical operations'! | aBoolean "Evaluating disjunction (OR). Evaluate the argument. Then answer true if either the receiver or the argument is true." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! and: alternativeBlock "Nonevaluating conjunction. If the receiver is true, answer the value of the argument, alternativeBlock; otherwise answer false without evaluating the argument." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! ifFalse: alternativeBlock "If the receiver is true (i.e., the condition is true), then the value is the true alternative, which is nil. Otherwise answer the result of evaluating the argument, alternativeBlock. Create an error notification if the receiver is nonBoolean. Execution does not actually reach here because the expression is compiled in-line." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock "Same as ifTrue:ifFalse:." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! ifTrue: alternativeBlock "If the receiver is false (i.e., the condition is false), then the value is the false alternative, which is nil. Otherwise answer the result of evaluating the argument, alternativeBlock. Create an error notification if the receiver is nonBoolean. Execution does not actually reach here because the expression is compiled in-line." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock "If the receiver is true (i.e., the condition is true), then answer the value of the argument trueAlternativeBlock. If the receiver is false, answer the result of evaluating the argument falseAlternativeBlock. If the receiver is a nonBoolean then create an error notification. Execution does not actually reach here because the expression is compiled in-line." self subclassResponsibility! ! !Boolean methodsFor: 'controlling'! or: alternativeBlock "Nonevaluating disjunction. If the receiver is false, answer the value of the argument, alternativeBlock; otherwise answer true without evaluating the argument." self subclassResponsibility! ! !Boolean methodsFor: 'copying' stamp: 'tk 6/26/1998 11:32'! clone "Receiver has two concrete subclasses, True and False. Only one instance of each should be made, so return self."! ! !Boolean methodsFor: 'copying'! deepCopy "Receiver has two concrete subclasses, True and False. Only one instance of each should be made, so return self."! ! !Boolean methodsFor: 'copying'! shallowCopy "Receiver has two concrete subclasses, True and False. Only one instance of each should be made, so return self."! ! !Boolean methodsFor: 'copying' stamp: 'tk 8/20/1998 16:07'! veryDeepCopyWith: deepCopier "Return self. I can't be copied. Do not record me."! ! !Boolean methodsFor: 'printing' stamp: 'sw 4/25/1998 12:51'! basicType ^ #boolean! ! !Boolean methodsFor: 'printing'! storeOn: aStream "Refer to the comment in Object|storeOn:." self printOn: aStream! ! !Boolean methodsFor: 'misc' stamp: 'sw 8/20/1999 17:42'! newTileMorphRepresentative ^ TileMorph new addArrows; setLiteral: self ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Boolean class instanceVariableNames: ''! !Boolean class methodsFor: 'instance creation' stamp: 'sw 5/5/2000 00:31'! initializedInstance ^ nil! ! !Boolean class methodsFor: 'instance creation'! new self error: 'You may not create any more Booleans - this is two-valued logic'! ! !Boolean class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:06'! ccg: cg emitLoadFor: aString from: anInteger on: aStream cg emitLoad: aString asBooleanValueFrom: anInteger on: aStream ! ! !Boolean class methodsFor: 'plugin generation' stamp: 'acg 10/5/1999 06:05'! ccg: cg generateCoerceToOopFrom: aNode on: aStream cg generateCoerceToBooleanObjectFrom: aNode on: aStream! ! !Boolean class methodsFor: 'plugin generation' stamp: 'acg 10/5/1999 06:10'! ccg: cg generateCoerceToValueFrom: aNode on: aStream cg generateCoerceToBooleanValueFrom: aNode on: aStream! ! !Boolean class methodsFor: 'plugin generation' stamp: 'acg 9/18/1999 17:08'! ccg: cg prolog: aBlock expr: aString index: anInteger ^cg ccgLoad: aBlock expr: aString asBooleanValueFrom: anInteger! ! ScriptEditorMorph subclass: #BooleanScriptEditor instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Support'! !BooleanScriptEditor commentStamp: '' prior: 0! A ScriptEditor required to hold a Boolean! !BooleanScriptEditor methodsFor: 'as yet unclassified' stamp: 'jm 5/28/1998 19:17'! storeCodeOn: aStream indent: tabCount ((submorphs size > 0) and: [submorphs first submorphs size > 0]) ifTrue: [ aStream nextPutAll: '(('. super storeCodeOn: aStream indent: tabCount. aStream nextPutAll: ') ~~ false)'. ^ self]. aStream nextPutAll: ' true '. ! ! !BooleanScriptEditor methodsFor: 'as yet unclassified' stamp: 'tk 8/6/1999 14:31'! tilesFrom: parseTree "Fill myself with tiles to corresponding to an existing boolean expression. parseTree is the MessageNode that is the top of a parse tree." | lineOfTiles msgNode | msgNode _ parseTree. lineOfTiles _ Array with: (PhraseTileMorph new tilesFrom: msgNode in: self). self insertTileRow: lineOfTiles after: 0. "no row of control buttons" ! ! !BooleanScriptEditor methodsFor: 'as yet unclassified' stamp: 'ar 5/17/2000 00:04'! wantsDroppedMorph: aMorph event: evt ((aMorph isKindOf: PhraseTileMorph) and: [submorphs size == 1]) ifTrue: [^ false]. ^ aMorph isTileLike and: [aMorph resultType ~~ #command] ! ! TileMorph subclass: #BooleanTile instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !BooleanTile commentStamp: '' prior: 0! A tile whose result type is boolean.! !BooleanTile methodsFor: 'type' stamp: 'sw 8/5/1998 17:52'! resultType ^ #boolean! ! Morph subclass: #BorderedMorph instanceVariableNames: 'borderWidth borderColor ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Kernel'! !BorderedMorph methodsFor: 'initialization' stamp: 'sw 11/29/1999 17:35'! initialize super initialize. borderColor _ Color black. borderWidth _ 2! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 8/6/97 14:34'! borderColor ^ borderColor! ! !BorderedMorph methodsFor: 'accessing' stamp: 'jm 5/14/1998 11:07'! borderColor: colorOrSymbolOrNil borderColor = colorOrSymbolOrNil ifFalse: [ borderColor _ colorOrSymbolOrNil. self changed]. ! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 6/20/97 11:24'! borderInset self borderColor: #inset! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 6/20/97 11:25'! borderRaised self borderColor: #raised! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 6/20/97 11:09'! borderWidth ^ borderWidth! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 6/4/1999 09:42'! borderWidth: anInteger borderColor ifNil: [borderColor _ Color black]. borderWidth _ anInteger max: 0. self changed! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:52'! cornerStyle ^ self valueOfProperty: #cornerStyle ifAbsent: [#square]! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:32'! cornerStyle: aSymbol aSymbol == #square ifTrue: [self removeProperty: #cornerStyle] ifFalse: [self setProperty: #cornerStyle toValue: aSymbol]. self changed! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/24/1999 14:56'! couldHaveRoundedCorners "subclases unhappy with rounded corners reimplement" ^ true! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 6/20/97 11:19'! doesBevels "To return true means that this object can show bevelled borders, and therefore can accept, eg, #raised or #inset as valid borderColors. Must be overridden by subclasses that do not support bevelled borders." ^ true! ! !BorderedMorph methodsFor: 'accessing' stamp: 'di 1/3/1999 12:24'! hasTranslucentColor "Answer true if this any of this morph is translucent but not transparent." (color isColor and: [color isTranslucentColor]) ifTrue: [^ true]. (borderColor isColor and: [borderColor isTranslucentColor]) ifTrue: [^ true]. ^ false ! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:36'! toggleCornerRounding self cornerStyle == #rounded ifTrue: [self useSquareCorners] ifFalse: [self useRoundedCorners]. self changed! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:36'! useRoundedCorners self cornerStyle: #rounded! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:37'! useSquareCorners self cornerStyle: #square! ! !BorderedMorph methodsFor: 'drawing' stamp: 'di 3/25/2000 11:13'! areasRemainingToFill: aRectangle (color isColor and: [color isTranslucent]) ifTrue: [^ Array with: aRectangle]. self wantsRoundedCorners ifTrue: [(borderWidth > 0 and: [borderColor isColor and: [borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: (self innerBounds intersect: self boundsWithinCorners)] ifFalse: [^ aRectangle areasOutside: self boundsWithinCorners]] ifFalse: [(borderWidth > 0 and: [borderColor isColor and: [borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: self innerBounds] ifFalse: [^ aRectangle areasOutside: self bounds]]! ! !BorderedMorph methodsFor: 'drawing' stamp: 'di 3/25/2000 11:13'! boundsWithinCorners ^ CornerRounder rectWithinCornersOf: self bounds! ! !BorderedMorph methodsFor: 'drawing' stamp: 'RAA 7/14/2000 09:36'! drawOn: aCanvas "Draw a rectangle with a solid, inset, or raised border. Note: the raised border color is generated from the receiver's own color, while the inset border color is generated from the color of its owner. This behavior is visually more consistent. Thanks to Hans-Martin Mosner." | insetColor | borderWidth = 0 ifTrue: [ "no border" "Note: This is the hook for border styles. When converting to the new borders we'll just put 0 into the borderWidth" super drawOn: aCanvas. ^ self]. borderColor == #raised ifTrue: [ "Use a hack for now" aCanvas fillRectangle: self bounds fillStyle: self fillStyle. ^ aCanvas frameAndFillRectangle: bounds fillColor: Color transparent borderWidth: borderWidth topLeftColor: (borderWidth = 1 ifTrue: [color twiceLighter] ifFalse: [color lighter]) bottomRightColor: (borderWidth = 1 ifTrue: [color twiceDarker] ifFalse: [color darker])]. borderColor == #inset ifTrue: [ insetColor _ owner ifNil: [Color black] ifNotNil: [owner colorForInsets]. aCanvas fillRectangle: self bounds fillStyle: self fillStyle. ^ aCanvas frameAndFillRectangle: bounds fillColor: Color transparent borderWidth: borderWidth topLeftColor: (borderWidth = 1 ifTrue: [insetColor twiceDarker] ifFalse: [insetColor darker]) bottomRightColor: (borderWidth = 1 ifTrue: [insetColor twiceLighter] ifFalse: [insetColor lighter])]. "solid color border" aCanvas fillRectangle: (self bounds insetBy: borderWidth) fillStyle: self fillStyle. aCanvas frameAndFillRectangle: bounds fillColor: Color transparent borderWidth: borderWidth borderColor: borderColor.! ! !BorderedMorph methodsFor: 'drawing' stamp: 'sw 11/29/1999 17:34'! wantsRoundedCorners ^ self cornerStyle == #rounded! ! !BorderedMorph methodsFor: 'geometry' stamp: 'di 6/20/97 11:15'! innerBounds ^ bounds insetBy: borderWidth! ! !BorderedMorph methodsFor: 'menu' stamp: 'sw 9/6/2000 05:14'! addCustomMenuItems: aMenu hand: aHandMorph super addCustomMenuItems: aMenu hand: aHandMorph. self isWorldMorph ifFalse: [aMenu addList: #(('border color...' changeBorderColor:) ('border width...' changeBorderWidth:)). self couldHaveRoundedCorners ifTrue: [aMenu addUpdating: #roundedCornersString target: self action: #toggleCornerRounding]. self doesBevels ifTrue: [borderColor == #raised ifFalse: [aMenu add: 'raised bevel' action: #borderRaised]. borderColor == #inset ifFalse: [aMenu add: 'inset bevel' action: #borderInset]]] ! ! !BorderedMorph methodsFor: 'menu' stamp: 'ar 10/5/2000 18:50'! changeBorderColor: evt | aHand | aHand _ evt ifNotNil: [evt hand] ifNil: [self primaryHand]. self changeColorTarget: self selector: #borderColor: originalColor: self borderColor hand: aHand! ! !BorderedMorph methodsFor: 'menu' stamp: 'ar 10/3/2000 17:04'! changeBorderWidth: evt | handle origin aHand newWidth oldWidth | aHand _ evt ifNil: [self primaryHand] ifNotNil: [evt hand]. origin _ aHand position. oldWidth _ borderWidth. handle _ HandleMorph new forEachPointDo: [:newPoint | handle removeAllMorphs. handle addMorph: (LineMorph from: origin to: newPoint color: Color black width: 1). newWidth _ (newPoint - origin) r asInteger // 5. self borderWidth: newWidth] lastPointDo: [:newPoint | handle deleteBalloon. self halo doIfNotNil: [:halo | halo addHandles]. self rememberCommand: (Command new cmdWording: 'border change'; undoTarget: self selector: #borderWidth: argument: oldWidth; redoTarget: self selector: #borderWidth: argument: newWidth)]. aHand attachMorph: handle. handle setProperty: #helpAtCenter toValue: true. handle showBalloon: 'Move cursor farther from this point to increase border width. Click when done.' hand: evt hand. handle startStepping! ! !BorderedMorph methodsFor: 'printing' stamp: 'di 6/20/97 11:20'! fullPrintOn: aStream aStream nextPutAll: '('. super fullPrintOn: aStream. aStream nextPutAll: ') setBorderWidth: '; print: borderWidth; nextPutAll: ' borderColor: ' , (self colorString: borderColor)! ! !BorderedMorph methodsFor: 'private' stamp: 'di 6/20/97 11:21'! setBorderWidth: w borderColor: bc self borderWidth: w. self borderColor: bc.! ! !BorderedMorph methodsFor: 'private' stamp: 'di 6/20/97 11:22'! setColor: c borderWidth: w borderColor: bc self color: c. self borderWidth: w. self borderColor: bc.! ! Morph subclass: #BouncingAtomsMorph instanceVariableNames: 'damageReported infectionHistory transmitInfection recentTemperatures temperature ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !BouncingAtomsMorph commentStamp: '' prior: 0! This morph shows how an ideal gas simulation might work. When it gets step messages, it makes all its atom submorphs move along their velocity vectors, bouncing when they hit a wall. It also exercises the Morphic damage reporting and display architecture. Here are some things to try: 1. Resize this morph as the atoms bounce around. 2. In an inspector on this morph, evaluate "self addAtoms: 10." 3. Try setting quickRedraw to false in invalidRect:. This gives the default damage reporting and incremental redraw. Try it for 100 atoms. 4. In the drawOn: method of AtomMorph, change drawAsRect to true. 5. Create a HeaterCoolerMorph and embed it in the simulation. Extract it and use an inspector on it to evaluate "self velocityDelta: -5", then re-embed it. Note the effect on atoms passing over it. ! !BouncingAtomsMorph methodsFor: 'initialization' stamp: 'jm 7/30/97 09:45'! initialize super initialize. damageReported _ false. self extent: 400@250. self color: (Color r: 0.8 g: 1.0 b: 0.8). infectionHistory _ OrderedCollection new. transmitInfection _ false. self addAtoms: 30. ! ! !BouncingAtomsMorph methodsFor: 'menu' stamp: 'jm 6/28/1998 18:17'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'startInfection' action: #startInfection. aCustomMenu add: 'set atom count' action: #setAtomCount. aCustomMenu add: 'show infection history' action: #showInfectionHistory:. ! ! !BouncingAtomsMorph methodsFor: 'menu' stamp: 'jm 6/28/1998 18:04'! setAtomCount | countString count | countString _ FillInTheBlank request: 'Number of atoms?' initialAnswer: self submorphCount printString. countString isEmpty ifTrue: [^ self]. count _ Integer readFrom: (ReadStream on: countString). self removeAllMorphs. self addAtoms: count. ! ! !BouncingAtomsMorph methodsFor: 'menu'! startInfection self submorphsDo: [:m | m infected: false]. self firstSubmorph infected: true. infectionHistory _ OrderedCollection new: 500. transmitInfection _ true. self startStepping. ! ! !BouncingAtomsMorph methodsFor: 'stepping' stamp: 'sw 7/15/1999 07:32'! step "Bounce those atoms!!" | r bounces | super step. bounces _ 0. r _ bounds origin corner: (bounds corner - (8@8)). self submorphsDo: [ :m | (m isMemberOf: AtomMorph) ifTrue: [ (m bounceIn: r) ifTrue: [bounces _ bounces + 1]]]. "compute a 'temperature' that is proportional to the number of bounces divided by the circumference of the enclosing rectangle" self updateTemperature: (10000.0 * bounces) / (r width + r height). transmitInfection ifTrue: [self transmitInfection]. ! ! !BouncingAtomsMorph methodsFor: 'stepping' stamp: 'jm 6/28/1998 18:10'! stepTime "As fast as possible." ^ 0 ! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'jm 6/28/1998 18:10'! addAtoms: n "Add a bunch of new atoms." | a | n timesRepeat: [ a _ AtomMorph new. a randomPositionIn: bounds maxVelocity: 10. self addMorph: a]. self stopStepping. ! ! !BouncingAtomsMorph methodsFor: 'other'! addMorphFront: aMorph "Called by the 'embed' meta action. We want non-atoms to go to the back." "Note: A user would not be expected to write this method. However, a sufficiently advanced user (e.g, an e-toy author) might do something equivalent by overridding the drag-n-drop messages when they are implemented." (aMorph isMemberOf: AtomMorph) ifTrue: [super addMorphFront: aMorph] ifFalse: [super addMorphBack: aMorph].! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'di 1/4/1999 20:22'! areasRemainingToFill: aRectangle color isTranslucent ifTrue: [^ Array with: aRectangle] ifFalse: [^ aRectangle areasOutside: self bounds]! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'ls 10/10/1999 13:59'! collisionPairs "Return a list of pairs of colliding atoms, which are assumed to be circles of known radius. This version uses the morph's positions--i.e. the top-left of their bounds rectangles--rather than their centers." | count sortedAtoms radius twoRadii radiiSquared collisions p1 continue j p2 distSquared m1 m2 | count _ submorphs size. sortedAtoms _ submorphs asSortedCollection: [ :mt1 :mt2 | mt1 position x < mt2 position x]. radius _ 8. twoRadii _ 2 * radius. radiiSquared _ radius squared * 2. collisions _ OrderedCollection new. 1 to: count - 1 do: [ :i | m1 _ sortedAtoms at: i. p1 _ m1 position. continue _ (j _ i + 1) <= count. [continue] whileTrue: [ m2 _ sortedAtoms at: j. p2 _ m2 position. (p2 x - p1 x) <= twoRadii ifTrue: [ distSquared _ (p1 x - p2 x) squared + (p1 y - p2 y) squared. distSquared < radiiSquared ifTrue: [ collisions add: (Array with: m1 with: m2)]. continue _ (j _ j + 1) <= count. ] ifFalse: [ continue _ false. ]. ]. ]. ^ collisions! ! !BouncingAtomsMorph methodsFor: 'other'! drawOn: aCanvas "Clear the damageReported flag when redrawn." super drawOn: aCanvas. damageReported _ false.! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'ar 11/12/2000 18:42'! invalidRect: damageRect from: aMorph "Try setting 'quickRedraw' to true. This invalidates the entire morph, whose bounds typically subsume all it's submorphs. (However, this code checks that assumption and passes through any damage reports for out-of-bounds submorphs. Note that atoms with super-high velocities do occaisionally shoot through the walls!!) An additional optimization is to only submit only damage report per display cycle by using the damageReported flag, which is reset to false when the morph is drawn." | quickRedraw | quickRedraw _ true. "false gives the original invalidRect: behavior" (quickRedraw and: [(bounds origin <= damageRect topLeft) and: [damageRect bottomRight <= bounds corner]]) ifTrue: [ "can use quick redraw if damage is within my bounds" damageReported ifFalse: [super invalidRect: bounds from: self]. "just report once" damageReported _ true. ] ifFalse: [super invalidRect: damageRect from: aMorph]. "ordinary damage report"! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'jm 6/28/1998 18:31'! showInfectionHistory: evt "Place a graph of the infection history in the world." | graph | infectionHistory isEmpty ifTrue: [^ self]. graph _ GraphMorph new data: infectionHistory. graph extent: ((infectionHistory size + (2 * graph borderWidth) + 5)@(infectionHistory last max: 50)). evt hand attachMorph: graph. ! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'jm 6/28/1998 18:20'! transmitInfection | infected count | self collisionPairs do: [:pair | infected _ false. pair do: [:atom | atom infected ifTrue: [infected _ true]]. infected ifTrue: [pair do: [:atom | atom infected: true]]]. count _ 0. self submorphsDo: [:m | m infected ifTrue: [count _ count + 1]]. infectionHistory addLast: count. count = submorphs size ifTrue: [ transmitInfection _ false. self stopStepping]. ! ! !BouncingAtomsMorph methodsFor: 'other' stamp: 'jm 8/10/1998 18:32'! updateTemperature: currentTemperature "Record the current temperature, which is taken to be the number of atoms that have bounced in the last cycle. To avoid too much jitter in the reading, the last several readings are averaged." recentTemperatures == nil ifTrue: [ recentTemperatures _ OrderedCollection new. 20 timesRepeat: [recentTemperatures add: 0]]. recentTemperatures removeLast. recentTemperatures addFirst: currentTemperature. temperature _ recentTemperatures sum asFloat / recentTemperatures size. ! ! ParseNode subclass: #BraceNode instanceVariableNames: 'elements sourceLocations emitNode ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !BraceNode commentStamp: '' prior: 0! Used for compiling and decompiling brace constructs. These now compile into either a fast short form for 4 elements or less: Array braceWith: a with: b ... or a long form of indefinfite length: (Array braceStream: N) nextPut: a; nextPut: b; ...; braceArray. The erstwhile brace assignment form is no longer supported.! !BraceNode methodsFor: 'initialize-release'! elements: collection "Decompile." elements _ collection! ! !BraceNode methodsFor: 'initialize-release'! elements: collection sourceLocations: locations "Compile." elements _ collection. sourceLocations _ locations! ! !BraceNode methodsFor: 'initialize-release' stamp: 'di 11/19/1999 11:06'! matchBraceStreamReceiver: receiver messages: messages ((receiver isMessage: #braceStream: receiver: nil arguments: [:arg | arg isConstantNumber]) and: [messages last isMessage: #braceArray receiver: nil arguments: nil]) ifFalse: [^ nil "no match"]. "Appears to be a long form brace construct" self elements: (messages allButLast collect: [:msg | (msg isMessage: #nextPut: receiver: nil arguments: nil) ifFalse: [^ nil "not a brace element"]. msg arguments first])! ! !BraceNode methodsFor: 'initialize-release' stamp: 'di 11/19/1999 11:19'! matchBraceWithReceiver: receiver selector: selector arguments: arguments selector = (self selectorForShortForm: arguments size) ifFalse: [^ nil "no match"]. "Appears to be a short form brace construct" self elements: arguments! ! !BraceNode methodsFor: 'testing'! blockAssociationCheck: encoder "If all elements are MessageNodes of the form [block]->[block], and there is at least one element, answer true. Otherwise, notify encoder of an error." elements size = 0 ifTrue: [^encoder notify: 'At least one case required']. elements with: sourceLocations do: [:x :loc | (x isMessage: #-> receiver: [:rcvr | (rcvr isKindOf: BlockNode) and: [rcvr numberOfArguments = 0]] arguments: [:arg | (arg isKindOf: BlockNode) and: [arg numberOfArguments = 0]]) ifFalse: [^encoder notify: 'Association between 0-argument blocks required' at: loc]]. ^true! ! !BraceNode methodsFor: 'testing'! numElements ^ elements size! ! !BraceNode methodsFor: 'code generation' stamp: 'di 11/19/1999 08:58'! emitForValue: stack on: aStream ^ emitNode emitForValue: stack on: aStream! ! !BraceNode methodsFor: 'code generation' stamp: 'di 1/4/2000 11:24'! selectorForShortForm: nElements nElements > 4 ifTrue: [^ nil]. ^ #(braceWithNone braceWith: braceWith:with: braceWith:with:with: braceWith:with:with:with:) at: nElements + 1! ! !BraceNode methodsFor: 'code generation' stamp: 'di 11/19/1999 11:13'! sizeForValue: encoder emitNode _ elements size <= 4 ifTrue: ["Short form: Array braceWith: a with: b ... " MessageNode new receiver: (encoder encodeVariable: #Array) selector: (self selectorForShortForm: elements size) arguments: elements precedence: 3 from: encoder] ifFalse: ["Long form: (Array braceStream: N) nextPut: a; nextPut: b; ...; braceArray" CascadeNode new receiver: (MessageNode new receiver: (encoder encodeVariable: #Array) selector: #braceStream: arguments: (Array with: (encoder encodeLiteral: elements size)) precedence: 3 from: encoder) messages: ((elements collect: [:elt | MessageNode new receiver: nil selector: #nextPut: arguments: (Array with: elt) precedence: 3 from: encoder]) copyWith: (MessageNode new receiver: nil selector: #braceArray arguments: (Array new) precedence: 1 from: encoder))]. ^ emitNode sizeForValue: encoder! ! !BraceNode methodsFor: 'enumerating'! casesForwardDo: aBlock "For each case in forward order, evaluate aBlock with three arguments: the key block, the value block, and whether it is the last case." | numCases case | 1 to: (numCases _ elements size) do: [:i | case _ elements at: i. aBlock value: case receiver value: case arguments first value: i=numCases]! ! !BraceNode methodsFor: 'enumerating'! casesReverseDo: aBlock "For each case in reverse order, evaluate aBlock with three arguments: the key block, the value block, and whether it is the last case." | numCases case | (numCases _ elements size) to: 1 by: -1 do: [:i | case _ elements at: i. aBlock value: case receiver value: case arguments first value: i=numCases]! ! !BraceNode methodsFor: 'printing' stamp: 'di 11/19/1999 09:17'! printOn: aStream indent: level aStream nextPut: ${. 1 to: elements size do: [:i | (elements at: i) printOn: aStream indent: level. i < elements size ifTrue: [aStream nextPutAll: '. ']]. aStream nextPut: $}! ! !BraceNode methodsFor: 'tiles' stamp: 'di 11/13/2000 21:17'! asMorphicSyntaxIn: parent | row | row _ (parent addRow: #brace on: self) layoutInset: 1. row addMorphBack: (StringMorph new contents: (String streamContents: [:aStream | self printOn: aStream indent: 0])). ^row ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BraceNode class instanceVariableNames: ''! !BraceNode class methodsFor: 'examples' stamp: 'di 11/19/1999 09:05'! example "Test the {a. b. c} syntax." | x | x _ {1. {2. 3}. 4}. ^ {x first. x second first. x second last. x last. 5} as: Set "BraceNode example Set (0 1 2 3 4 5 )" ! ! CodeHolder subclass: #Browser instanceVariableNames: 'systemOrganizer classOrganizer metaClassOrganizer systemCategoryListIndex classListIndex messageCategoryListIndex messageListIndex editSelection metaClassIndicated ' classVariableNames: 'RecentClasses ' poolDictionaries: '' category: 'Tools-Browser'! !Browser commentStamp: '' prior: 0! I represent a query path into the class descriptions, the software of the system.! !Browser methodsFor: 'accessing' stamp: 'sma 6/18/2000 18:14'! contents "Depending on the current selection, different information is retrieved. Answer a string description of that information. This information is the method of the currently selected class and message." | comment theClass latestCompiledMethod | latestCompiledMethod _ currentCompiledMethod. currentCompiledMethod _ nil. editSelection == #none ifTrue: [^ '']. editSelection == #editSystemCategories ifTrue: [^ systemOrganizer printString]. editSelection == #newClass ifTrue: [^ (theClass _ self selectedClass) ifNil: [Class template: self selectedSystemCategoryName] ifNotNil: [Class templateForSubclassOf: theClass category: self selectedSystemCategoryName]]. editSelection == #editClass ifTrue: [^ self selectedClassOrMetaClass definitionST80: Preferences printAlternateSyntax not]. editSelection == #editComment ifTrue: [(theClass _ self selectedClass) ifNil: [^ '']. comment _ theClass comment. comment size = 0 ifTrue: [^ 'This class has not yet been commented.'] ifFalse: [^ comment]]. editSelection == #hierarchy ifTrue: [^ self selectedClassOrMetaClass printHierarchy]. editSelection == #editMessageCategories ifTrue: [^ self classOrMetaClassOrganizer printString]. editSelection == #newMessage ifTrue: [^ self selectedClassOrMetaClass sourceCodeTemplate]. editSelection == #editMessage ifTrue: [currentCompiledMethod _ latestCompiledMethod. ^ self selectedMessage]. editSelection == #byteCodes ifTrue: [^ (self selectedClassOrMetaClass compiledMethodAt: self selectedMessageName) symbolic asText]. self error: 'Browser internal error: unknown edit selection.'! ! !Browser methodsFor: 'accessing' stamp: 'sw 9/30/1999 13:19'! contents: input notifying: aController "The retrieved information has changed and its source must now be updated. The information can be a variety of things, depending on the list selections (such as templates for class or message definition, methods) or the user menu commands (such as definition, comment, hierarchy). Answer the result of updating the source." | aString aText theClass | self changed: #annotation. aString _ input asString. aText _ input asText. editSelection == #editSystemCategories ifTrue: [^ self changeSystemCategories: aString]. editSelection == #editClass | (editSelection == #newClass) ifTrue: [^ self defineClass: aString notifying: aController]. editSelection == #editComment ifTrue: [theClass _ self selectedClass. theClass ifNil: [PopUpMenu notify: 'You must select a class before giving it a comment.'. ^ false]. theClass comment: aText stamp: Utilities changeStamp. ^ true]. editSelection == #hierarchy ifTrue: [^ true]. editSelection == #editMessageCategories ifTrue: [^ self changeMessageCategories: aString]. editSelection == #editMessage | (editSelection == #newMessage) ifTrue: [^ self okayToAccept ifFalse: [false] ifTrue: [self compileMessage: aText notifying: aController]]. editSelection == #none ifTrue: [PopUpMenu notify: 'This text cannot be accepted in this part of the browser.'. ^ false]. self error: 'unacceptable accept'! ! !Browser methodsFor: 'accessing' stamp: 'dew 7/28/2000 00:44'! contentsSelection "Return the interval of text in the code pane to select when I set the pane's contents" messageCategoryListIndex > 1 & (messageListIndex = 0) ifTrue: [^ 1 to: 500] "entire empty method template" ifFalse: [^ 1 to: 0] "null selection"! ! !Browser methodsFor: 'accessing' stamp: 'di 6/21/1998 22:20'! couldBrowseAnyClass "Answer whether the receiver is equipped to browse any class. This is in support of the system-brower feature that allows the browser to be redirected at the selected class name. This implementation is clearly ugly, but the feature it enables is handsome enough. 3/1/96 sw" self dependents detect: [:d | ((d isKindOf: PluggableListView) or: [d isKindOf: PluggableListMorph]) and: [d getListSelector == #systemCategoryList]] ifNone: [^ false]. ^ true ! ! !Browser methodsFor: 'accessing' stamp: 'sma 5/28/2000 11:28'! doItReceiver "This class's classPool has been jimmied to be the classPool of the class being browsed. A doIt in the code pane will let the user see the value of the class variables." ^ self selectedClass ifNil: [FakeClassPool new]! ! !Browser methodsFor: 'accessing'! editSelection ^editSelection! ! !Browser methodsFor: 'accessing' stamp: 'sw 12/17/2000 23:24'! editSelection: aSelection "Set the editSelection as requested." editSelection _ aSelection! ! !Browser methodsFor: 'accessing' stamp: 'sw 10/30/1999 22:59'! noteSelectionIndex: anInteger for: aSymbol aSymbol == #systemCategoryList ifTrue: [systemCategoryListIndex _ anInteger]. aSymbol == #classList ifTrue: [classListIndex _ anInteger]. aSymbol == #messageCategoryList ifTrue: [messageCategoryListIndex _ anInteger]. aSymbol == #messageList ifTrue: [messageListIndex _ anInteger].! ! !Browser methodsFor: 'accessing' stamp: 'jm 4/28/1998 05:55'! request: prompt initialAnswer: initialAnswer ^ FillInTheBlank request: prompt initialAnswer: initialAnswer ! ! !Browser methodsFor: 'accessing' stamp: 'sw 1/4/2001 12:24'! spawn: aString "Create and schedule a fresh browser and place aString in its code pane. This method is called when the user issues the #spawn command (cmd-o) in any code pane. Whatever text was in the original code pane comes in to this method as the aString argument; the changes in the original code pane have already been cancelled by the time this method is called, so aString is the only copy of what the user had in his code pane." self selectedClassOrMetaClass ifNotNil: [^ super spawn: aString]. systemCategoryListIndex ~= 0 ifTrue: ["This choice is slightly useless but is the historical implementation" ^ self buildSystemCategoryBrowserEditString: aString]. ^ super spawn: aString "This bail-out at least saves the text being spawned, which would otherwise be lost"! ! !Browser methodsFor: 'class functions' stamp: 'sw 1/30/2001 15:52'! addAllMethodsToCurrentChangeSet "Add all the methods in the selected class or metaclass to the current change set. You ought to know what you're doing before you invoke this!!" | aClass | (aClass _ self selectedClassOrMetaClass) ifNotNil: [aClass selectors do: [:sel | Smalltalk changes adoptSelector: sel forClass: aClass]. self changed: #annotation] ! ! !Browser methodsFor: 'class functions'! buildClassBrowser "Create and schedule a new class category browser for the current class selection, if one exists." self buildClassBrowserEditString: nil! ! !Browser methodsFor: 'class functions' stamp: 'sw 12/6/2000 16:32'! classListMenu: aMenu "For backward compatibility with old browers stored in image segments" ^ self classListMenu: aMenu shifted: false! ! !Browser methodsFor: 'class functions' stamp: 'sw 1/30/2001 15:46'! classListMenu: aMenu shifted: shifted "Set up the menu to apply to the receiver's class list, honoring the #shifted boolean" ^ aMenu addList: (shifted ifFalse: [#( - ('browse full (b)' browseMethodFull) ('browse hierarchy (h)' spawnHierarchy) ('browse protocol (p)' browseFullProtocol) - ('printOut' printOutClass) ('fileOut' fileOutClass) - ('show hierarchy' hierarchy) ('show definition' editClass) ('show comment' editComment) - ('inst var refs...' browseInstVarRefs) ('inst var defs...' browseInstVarDefs) - ('class var refs...' browseClassVarRefs) ('class vars' browseClassVariables) ('class refs (N)' browseClassRefs) - ('rename class ...' renameClass) ('copy class' copyClass) ('remove class (x)' removeClass) - ('find method...' findMethod) - ('more...' shiftedYellowButtonActivity))] ifTrue: [#( - ('unsent methods' browseUnusedMethods) ('unreferenced inst vars' showUnreferencedInstVars) ('subclass template' makeNewSubclass) - ('sample instance' makeSampleInstance) ('inspect instances' inspectInstances) ('inspect subinstances' inspectSubInstances) - ('fetch documentation' fetchClassDocPane) ('add all meths to current chgs' addAllMethodsToCurrentChangeSet) - ('more...' unshiftedYellowButtonActivity))])! ! !Browser methodsFor: 'class functions' stamp: 'dwh 11/23/1999 00:09'! copyClass | originalName copysName class oldDefinition newDefinition | classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. originalName _ self selectedClass name. copysName _ self request: 'Please type new class name' initialAnswer: originalName. copysName = '' ifTrue: [^ self]. " Cancel returns '' " copysName _ copysName asSymbol. copysName = originalName ifTrue: [^ self]. (Smalltalk includesKey: copysName) ifTrue: [^ self error: copysName , ' already exists']. oldDefinition _ self selectedClass definition. newDefinition _ oldDefinition copyReplaceAll: '#' , originalName asString with: '#' , copysName asString. Cursor wait showWhile: [class _ Compiler evaluate: newDefinition logged: true. class copyAllCategoriesFrom: (Smalltalk at: originalName). class class copyAllCategoriesFrom: (Smalltalk at: originalName) class]. self classListIndex: 0. self changed: #classList! ! !Browser methodsFor: 'class functions' stamp: 'bf 10/19/2000 11:39'! defineClass: defString notifying: aController "The receiver's textual content is a request to define a new class. The source code is defString. If any errors occur in compilation, notify aController." | oldClass class newClassName defTokens keywdIx envt | oldClass _ self selectedClassOrMetaClass. defTokens _ defString findTokens: Character separators. keywdIx _ defTokens findFirst: [:x | x beginsWith: 'category']. envt _ Smalltalk environmentForCategory: ((defTokens at: keywdIx+1) copyWithout: $'). keywdIx _ defTokens findFirst: [:x | '*subclass*' match: x]. newClassName _ (defTokens at: keywdIx+1) copyWithoutAll: '#()'. ((oldClass isNil or: [oldClass name asString ~= newClassName]) and: [envt includesKeyOrAbove: newClassName asSymbol]) ifTrue: ["Attempting to define new class over existing one when not looking at the original one in this browser..." (self confirm: ((newClassName , ' is an existing class in this system. Redefining it might cause serious problems. Is this really what you want to do?') asText makeBoldFrom: 1 to: newClassName size)) ifFalse: [^ false]]. "ar 8/29/1999: Use oldClass superclass for defining oldClass since oldClass superclass knows the definerClass of oldClass." oldClass ifNotNil:[oldClass _ oldClass superclass]. class _ oldClass subclassDefinerClass evaluate: defString notifying: aController logged: true. (class isKindOf: Behavior) ifTrue: [self changed: #classList. self classListIndex: (self classList indexOf: ((class isKindOf: Metaclass) ifTrue: [class soleInstance name] ifFalse: [class name])). self clearUserEditFlag; editClass. ^ true] ifFalse: [^ false]! ! !Browser methodsFor: 'class functions' stamp: 'tk 4/2/98 13:49'! editClass "Retrieve the description of the class definition." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. editSelection _ #editClass. self changed: #editClass. self changed: #contents. ! ! !Browser methodsFor: 'class functions' stamp: 'sw 1/28/1999 22:56'! editComment "Retrieve the description of the class comment." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. editSelection _ #editComment. self changed: #classSelectionChanged. self contentsChanged ! ! !Browser methodsFor: 'class functions'! explainSpecial: string "Answer a string explaining the code pane selection if it is displaying one of the special edit functions." | classes whole lits reply | (editSelection == #editClass or: [editSelection == #newClass]) ifTrue: ["Selector parts in class definition" string last == $: ifFalse: [^nil]. lits _ Array with: #subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:. (whole _ lits detect: [:each | (each keywords detect: [:frag | frag = string] ifNone: []) ~~ nil] ifNone: []) ~~ nil ifTrue: [reply _ '"' , string , ' is one part of the message selector ' , whole , '.'] ifFalse: [^nil]. classes _ Smalltalk allClassesImplementing: whole. classes _ 'these classes ' , classes printString. ^reply , ' It is defined in ' , classes , '." Smalltalk browseAllImplementorsOf: #' , whole]. editSelection == #hierarchy ifTrue: ["Instance variables in subclasses" classes _ self selectedClassOrMetaClass allSubclasses. classes _ classes detect: [:each | (each instVarNames detect: [:name | name = string] ifNone: []) ~~ nil] ifNone: [^nil]. classes _ classes printString. ^'"is an instance variable in class ' , classes , '." ' , classes , ' browseAllAccessesTo: ''' , string , '''.']. editSelection == #editSystemCategories ifTrue: [^nil]. editSelection == #editMessageCategories ifTrue: [^nil]. ^nil! ! !Browser methodsFor: 'class functions' stamp: 'tk 3/12/1999 18:30'! fetchClassDocPane "Look on servers to see if there is documentation pane for the selected class. Take into account the current update number. If not, ask the user if she wants to create one." DocLibrary external fetchDocSel: '' class: self selectedClassName! ! !Browser methodsFor: 'class functions' stamp: 'tk 4/2/98 13:50'! fileOutClass "Print a description of the selected class onto a file whose name is the category name followed by .st." Cursor write showWhile: [classListIndex ~= 0 ifTrue: [self selectedClass fileOut]]! ! !Browser methodsFor: 'class functions' stamp: 'tk 4/2/98 13:50'! findMethod "Pop up a list of the current class's methods, and select the one chosen by the user. 5/21/96 sw, based on a suggestion of John Maloney's." | aClass selectors reply cat messageCatIndex messageIndex | self classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. aClass _ self selectedClassOrMetaClass. selectors _ aClass selectors asSortedArray. reply _ (SelectionMenu labelList: selectors selections: selectors) startUp. reply == nil ifTrue: [^ self]. cat _ aClass whichCategoryIncludesSelector: reply. messageCatIndex _ self messageCategoryList indexOf: cat. self messageCategoryListIndex: messageCatIndex. messageIndex _ (self messageList indexOf: reply). self messageListIndex: messageIndex. ! ! !Browser methodsFor: 'class functions' stamp: 'sw 1/28/1999 12:30'! hierarchy "Display the inheritance hierarchy of the receiver's selected class." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. editSelection := #hierarchy. self changed: #editComment. self contentsChanged. ^ self! ! !Browser methodsFor: 'class functions' stamp: 'sw 5/4/2000 20:19'! makeNewSubclass self selectedClassOrMetaClass ifNil: [^ self]. self okToChange ifFalse: [^ self]. editSelection _ #newClass. self contentsChanged! ! !Browser methodsFor: 'class functions' stamp: 'sw 1/5/2001 07:20'! plusButtonHit "Cycle among definition, comment, and hierachy" editSelection == #editComment ifTrue: [self hierarchy. ^ self]. editSelection == #hierarchy ifTrue: [editSelection := #editClass. classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self changed: #editComment. self contentsChanged. ^self]. classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. editSelection := #editComment. self changed: #classSelectionChanged. self decorateButtons. self contentsChanged.! ! !Browser methodsFor: 'class functions' stamp: 'tk 4/2/98 13:50'! printOutClass "Print a description of the selected class onto a file whose name is the category name followed by .html." Cursor write showWhile: [classListIndex ~= 0 ifTrue: [self selectedClass fileOutAsHtml: true]]! ! !Browser methodsFor: 'class functions' stamp: 'tk 4/24/1998 23:52'! removeClass "The selected class should be removed from the system. Use a Confirmer to make certain the user intends this irrevocable command to be carried out." | message class className | classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. class _ self selectedClass. className _ class name. message _ 'Are you certain that you want to delete the class ', className, '?'. (self confirm: message) ifTrue: [class subclasses size > 0 ifTrue: [self notify: 'class has subclasses: ' , message]. class removeFromSystem. self classListIndex: 0]. self changed: #classList. ! ! !Browser methodsFor: 'class functions' stamp: 'dwh 11/23/1999 00:25'! renameClass | oldName newName obs | classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. oldName _ self selectedClass name. newName _ self request: 'Please type new class name' initialAnswer: oldName. newName = '' ifTrue: [^ self]. " Cancel returns '' " newName _ newName asSymbol. newName = oldName ifTrue: [^ self]. (Smalltalk includesKey: newName) ifTrue: [^ self error: newName , ' already exists']. self selectedClass rename: newName. self changed: #classList. self classListIndex: ((systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) indexOf: newName). obs _ Smalltalk allCallsOn: (Smalltalk associationAt: newName). obs isEmpty ifFalse: [Smalltalk browseMessageList: obs name: 'Obsolete References to ' , oldName autoSelect: oldName]. ! ! !Browser methodsFor: 'class list'! classList "Answer an array of the class names of the selected category. Answer an empty array if no selection exists." systemCategoryListIndex = 0 ifTrue: [^Array new] ifFalse: [^systemOrganizer listAtCategoryNumber: systemCategoryListIndex]! ! !Browser methodsFor: 'class list'! classListIndex "Answer the index of the current class selection." ^classListIndex! ! !Browser methodsFor: 'class list' stamp: 'dew 7/28/2000 01:10'! classListIndex: anInteger "Set anInteger to be the index of the current class selection." | className | classListIndex _ anInteger. self setClassOrganizer. messageCategoryListIndex _ 1. messageListIndex _ 0. self classCommentIndicated ifTrue: [] ifFalse: [editSelection _ anInteger = 0 ifTrue: [metaClassIndicated | (systemCategoryListIndex == 0) ifTrue: [#none] ifFalse: [#newClass]] ifFalse: [#editClass]]. contents _ nil. self selectedClass isNil ifFalse: [className _ self selectedClass name. (RecentClasses includes: className) ifTrue: [RecentClasses remove: className]. RecentClasses addFirst: className. RecentClasses size > 16 ifTrue: [RecentClasses removeLast]]. self changed: #classSelectionChanged. self changed: #classListIndex. "update my selection" self changed: #messageCategoryList. self changed: #messageList. self contentsChanged! ! !Browser methodsFor: 'class list' stamp: 'tk 4/5/98 12:25'! classListSingleton | name | name _ self selectedClassName. ^ name ifNil: [Array new] ifNotNil: [Array with: name]! ! !Browser methodsFor: 'class list' stamp: 'stp 01/13/2000 12:57'! recent "Let the user select from a list of recently visited classes. 11/96 stp. 12/96 di: use class name, not classes themselves. : dont fall into debugger in empty case" | className class recentList | recentList _ RecentClasses select: [:n | Smalltalk includesKey: n]. recentList size == 0 ifTrue: [^ self beep]. className := (SelectionMenu selections: recentList) startUp. className == nil ifTrue: [^ self]. class := Smalltalk at: className. self selectCategoryForClass: class. self classListIndex: (self classList indexOf: class name)! ! !Browser methodsFor: 'class list' stamp: 'sr 10/29/1999 20:28'! selectClass: classNotMeta self classListIndex: (self classList indexOf: classNotMeta name)! ! !Browser methodsFor: 'class list' stamp: 'di 12/6/1999 20:41'! selectedClass "Answer the class that is currently selected. Answer nil if no selection exists." | name envt | (name _ self selectedClassName) ifNil: [^ nil]. (envt _ self selectedEnvironment) ifNil: [^ nil]. ^ envt at: name! ! !Browser methodsFor: 'class list' stamp: 'sw 11/24/1999 14:48'! selectedClassName | aClassList | "Answer the name of the current class. Answer nil if no selection exists." (classListIndex = 0 or: [classListIndex > (aClassList _ self classList) size]) ifTrue: [^ nil]. ^ aClassList at: classListIndex! ! !Browser methodsFor: 'class list'! toggleClassListIndex: anInteger "If anInteger is the current class index, deselect it. Else make it the current class selection." self classListIndex: (classListIndex = anInteger ifTrue: [0] ifFalse: [anInteger])! ! !Browser methodsFor: 'code pane' stamp: 'sw 5/26/1999 23:43'! compileMessage: aText notifying: aController "Compile the code that was accepted by the user, placing the compiled method into an appropriate message category. Return true if the compilation succeeded, else false." | fallBackCategoryIndex fallBackMethodIndex originalSelectorName result | self selectedMessageCategoryName asSymbol = ClassOrganizer allCategory ifTrue: [ "User tried to save a method while the ALL category was selected" fallBackCategoryIndex _ messageCategoryListIndex. fallBackMethodIndex _ messageListIndex. editSelection == #newMessage ifTrue: [ "Select the 'as yet unclassified' category" messageCategoryListIndex _ 0. (result _ self defineMessageFrom: aText notifying: aController) ifNil: ["Compilation failure: reselect the original category & method" messageCategoryListIndex _ fallBackCategoryIndex. messageListIndex _ fallBackMethodIndex] ifNotNil: [self setSelector: result]] ifFalse: [originalSelectorName _ self selectedMessageName. self setOriginalCategoryIndexForCurrentMethod. messageListIndex _ fallBackMethodIndex _ self messageList indexOf: originalSelectorName. (result _ self defineMessageFrom: aText notifying: aController) ifNotNil: [self setSelector: result] ifNil: [ "Compilation failure: reselect the original category & method" messageCategoryListIndex _ fallBackCategoryIndex. messageListIndex _ fallBackMethodIndex. ^ result notNil]]. self changed: #messageCategoryList. ^ result notNil] ifFalse: [ "User tried to save a method while the ALL category was NOT selected" ^ (self defineMessageFrom: aText notifying: aController) notNil]! ! !Browser methodsFor: 'code pane' stamp: 'sma 5/28/2000 11:03'! showBytecodes "Show or hide the bytecodes of the selected method." (messageListIndex = 0 or: [self okToChange not]) ifTrue: [^ self changed: #flash]. editSelection == #byteCodes ifTrue: [editSelection _ #editMessage] ifFalse: [editSelection _ #byteCodes]. self contentsChanged! ! !Browser methodsFor: 'copying' stamp: 'tk 12/5/1999 17:59'! veryDeepInner: deepCopier "Copy all of my instance variables. Some need to be not copied at all, but shared. See DeepCopier class comment." super veryDeepInner: deepCopier. "systemOrganizer _ systemOrganizer. clone has the old value. we share it" "classOrganizer _ classOrganizer clone has the old value. we share it" "metaClassOrganizer _ metaClassOrganizer clone has the old value. we share it" systemCategoryListIndex _ systemCategoryListIndex veryDeepCopyWith: deepCopier. classListIndex _ classListIndex veryDeepCopyWith: deepCopier. messageCategoryListIndex _ messageCategoryListIndex veryDeepCopyWith: deepCopier. messageListIndex _ messageListIndex veryDeepCopyWith: deepCopier. editSelection _ editSelection veryDeepCopyWith: deepCopier. metaClassIndicated _ metaClassIndicated veryDeepCopyWith: deepCopier. ! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/23/2000 17:25'! acceptDroppingMorph: transferMorph event: evt inMorph: dstListMorph "Here we are fetching informations from the dropped transferMorph and performing the correct action for this drop." | srcType success srcBrowser | success _ false. srcType _ transferMorph dragTransferType. srcBrowser _ transferMorph source model. srcType == #messageList ifTrue: [success _ self acceptMethod: transferMorph passenger value messageCategory: srcBrowser selectedMessageCategoryName class: transferMorph passenger key atListMorph: dstListMorph internal: self == srcBrowser copy: transferMorph shouldCopy]. srcType == #classList ifTrue: [success _ self changeCategoryForClass: transferMorph passenger srcSystemCategory: srcBrowser selectedSystemCategoryName atListMorph: dstListMorph internal: self == srcBrowser copy: transferMorph shouldCopy]. ^success! ]style[(67 620 4 223)f1b,f1,f1cblue;b,f1! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:08'! acceptMethod: methodSel dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel dstClass: dstClass dstClassOrMeta: dstClassOrMeta srcClassOrMeta: srcClassOrMeta internal: internal copySemantic: copyFlag | success hierarchyChange higher checkForOverwrite | (success _ dstClassOrMeta ~~ nil) ifFalse: [^false]. checkForOverwrite _ dstClassOrMeta selectors includes: methodSel. hierarchyChange _ (higher _ srcClassOrMeta inheritsFrom: dstClassOrMeta) | (dstClassOrMeta inheritsFrom: srcClassOrMeta). success _ (checkForOverwrite not or: [self overwriteDialogHierarchyChange: hierarchyChange higher: higher sourceClassName: srcClassOrMeta name destinationClassName: dstClassOrMeta name methodSelector: methodSel]) and: [self message: methodSel compileInClass: dstClassOrMeta fromClass: srcClassOrMeta dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel internal: internal copySemantic: copyFlag]. ^ success! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:27'! acceptMethod: methodSel messageCategory: srcMessageCategorySel class: srcClassOrMeta atListMorph: dstListMorph internal: internal copy: copyFlag | success dstClassOrMeta dstClass dstMessageCategorySel | dstClass _ self dstClassDstListMorph: dstListMorph. dstClassOrMeta _ dstClass ifNotNil: [self metaClassIndicated ifTrue: [dstClass class] ifFalse: [dstClass]]. dstMessageCategorySel _ self dstMessageCategoryDstListMorph: dstListMorph. success _ (dstClassOrMeta notNil and: [dstClassOrMeta == srcClassOrMeta]) ifTrue: ["one class" self changeMessageCategoryForMethod: methodSel dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel insideClassOrMeta: dstClassOrMeta internal: internal copySemantic: copyFlag] ifFalse: ["different classes" self acceptMethod: methodSel dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel dstClass: dstClass dstClassOrMeta: dstClassOrMeta srcClassOrMeta: srcClassOrMeta internal: internal copySemantic: copyFlag]. ^ success! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:27'! changeCategoryForClass: class srcSystemCategory: srcSystemCategorySel atListMorph: dstListMorph internal: internal copy: copyFlag "only move semantic" | newClassCategory success | self flag: #stringSymbolProblem. success _ copyFlag not ifFalse: [^ false]. newClassCategory _ self dstCategoryDstListMorph: dstListMorph. (success _ newClassCategory notNil & (newClassCategory ~= class category)) ifTrue: [class category: newClassCategory. self changed: #classList. internal ifFalse: [self selectClass: class]]. ^ success! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/23/2000 17:27'! changeMessageCategoryForMethod: methodSel dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel insideClassOrMeta: classOrMeta internal: internal copySemantic: copyFlag "only move semantic" | success messageCategorySel | (success _ copyFlag not) ifFalse: [^ false]. messageCategorySel _ dstMessageCategorySel ifNil: [srcMessageCategorySel]. (success _ messageCategorySel notNil & (messageCategorySel ~= '-- all --' asSymbol) and: [messageCategorySel ~= srcMessageCategorySel and: [classOrMeta organization categories includes: messageCategorySel]]) ifTrue: [classOrMeta organization classify: methodSel under: messageCategorySel suppressIfDefault: false. self changed: #messageList]. success & internal not ifTrue: [self setSelector: methodSel]. ^ success! ! !Browser methodsFor: 'drag and drop' stamp: 'sr 4/25/2000 07:12'! codeTextMorph ^ self dependents detect: [:dep | (dep isKindOf: PluggableTextMorph) and: [dep getTextSelector == #contents]] ifNone: []! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/16/2000 11:35'! dragAnimationFor: item transferMorph: transferMorph TransferMorphLineAnimation on: transferMorph! ! !Browser methodsFor: 'drag and drop' stamp: 'len 5/17/2000 12:35'! dragPassengerFor: item inMorph: dragSource | transferType | (dragSource isKindOf: PluggableListMorph) ifFalse: [^item]. transferType _ self dragTransferTypeForMorph: dragSource. transferType == #messageList ifTrue: [^self selectedClassOrMetaClass->item contents]. transferType == #classList ifTrue: [^self selectedClass]. ^item contents! ! !Browser methodsFor: 'drag and drop' stamp: 'panda 4/28/2000 16:18'! dragTransferTypeForMorph: dragSource ^(dragSource isKindOf: PluggableListMorph) ifTrue: [dragSource getListSelector]! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:26'! dstCategoryDstListMorph: dstListMorph | dropMorph | ^(dstListMorph getListSelector == #systemCategoryList) ifTrue: [(dropMorph _ dstListMorph potentialDropMorph) ifNotNil: [dropMorph contents]] ifFalse: [self selectedSystemCategoryName]! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:45'! dstClassDstListMorph: dstListMorph | dropMorph | ^(dstListMorph getListSelector == #classList) ifTrue: [(dropMorph _ dstListMorph potentialDropMorph) ifNotNil: [Smalltalk at: dropMorph contents withBlanksCondensed asSymbol]] ifFalse: [dstListMorph model selectedClass]! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:27'! dstMessageCategoryDstListMorph: dstListMorph | dropMorph | ^dstListMorph getListSelector == #messageCategoryList ifTrue: [dropMorph _ dstListMorph potentialDropMorph. dropMorph ifNotNil: [dropMorph contents asSymbol]] ifFalse: [self selectedMessageCategoryName]! ! !Browser methodsFor: 'drag and drop' stamp: 'mir 5/25/2000 13:47'! message: messageSel compileInClass: dstClassOrMeta fromClass: srcClassOrMeta dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel internal: internal copySemantic: copyFlag | source messageCategorySel tm success oldOrNoMethod newMethod | source _ srcClassOrMeta sourceCodeAt: messageSel. messageCategorySel _ dstMessageCategorySel ifNil: [srcMessageCategorySel]. self selectClass: dstClassOrMeta theNonMetaClass. (self messageCategoryList includes: messageCategorySel) ifFalse: ["create message category" self classOrMetaClassOrganizer addCategory: messageCategorySel]. self selectMessageCategoryNamed: messageCategorySel. tm _ self codeTextMorph. tm setText: source. tm setSelection: (0 to: 0). tm hasUnacceptedEdits: true. oldOrNoMethod _ srcClassOrMeta compiledMethodAt: messageSel ifAbsent: []. tm accept. "compilation successful?" newMethod _ dstClassOrMeta compiledMethodAt: messageSel ifAbsent: []. success _ newMethod ~~ nil & (newMethod ~~ oldOrNoMethod). " success ifFalse: [TransferMorph allInstances do: [:e | e delete]]. " success ifTrue: [copyFlag not ifTrue: ["remove old method in move semantic if new exists" srcClassOrMeta removeSelector: messageSel].internal ifTrue: [self selectClass: srcClassOrMeta] ifFalse: [self selectClass: dstClassOrMeta]. self setSelector: messageSel]. ^ success! ! !Browser methodsFor: 'drag and drop'! overwriteDialogHierarchyChange: hierarchyChange higher: higherFlag sourceClassName: srcClassName destinationClassName: dstClassName methodSelector: methodSelector | lf success | lf _ Character cr asString. success _ SelectionMenu confirm: 'There is a conflict.' , ' Overwrite' , (hierarchyChange ifTrue: [higherFlag ifTrue: [' superclass'] ifFalse: [' subclass']] ifFalse: ['']) , ' method' , lf , dstClassName , '>>' , methodSelector , lf , 'by ' , (hierarchyChange ifTrue: ['moving'] ifFalse: ['copying']) , ' method' , lf , srcClassName name , '>>' , methodSelector , ' ?' trueChoice: 'Yes, don''t care.' falseChoice: 'No, I have changed my opinion.'. ^ success! ! !Browser methodsFor: 'drag and drop' stamp: 'jcg 11/5/2000 22:23'! wantsDroppedMorph: transferMorph event: anEvent inMorph: destinationLM "We are only interested in TransferMorphs as wrappers for informations. If their content is really interesting for us, will determined later in >>acceptDroppingMorph:event:." | srcType dstType | "only want drops on lists (not, for example, on pluggable texts)" (destinationLM isKindOf: PluggableListMorph) ifFalse: [^ false]. srcType _ transferMorph dragTransferType. dstType _ destinationLM getListSelector. (srcType == #messageList and: [dstType == #messageCategoryList or: [dstType == #classList]]) ifTrue: [^true]. (srcType == #classList and: [dstType == #systemCategoryList]) ifTrue: [^true]. " [ srcLS == #messageList ifTrue: [^ dstLS == #messageList | (dstLS == #messageCategoryList) | (dstLS == #classList)]. srcLS == #classList ifTrue: [^ dstLS == #classList | (dstLS == #systemCategoryList)]]. " ^ false! ! !Browser methodsFor: 'initialize-release' stamp: 'JW 2/3/2001 09:45'! addAListPane: aListPane to: window at: nominalFractions plus: verticalOffset | row switchHeight | row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 1; layoutPolicy: ProportionalLayout new. switchHeight _ 25. row addMorph: aListPane fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@1) offsets: (0@0 corner: 0@switchHeight negated) ). row addMorph: SubpaneDividerMorph forTopEdge fullFrame: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@switchHeight negated corner: 0@(1-switchHeight)) ). self addMorphicSwitchesTo: row at: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@(1-switchHeight) corner: 0@0) ). window addMorph: row fullFrame: ( LayoutFrame fractions: nominalFractions offsets: (0@verticalOffset corner: 0@0) ). row on: #mouseEnter send: #paneTransition: to: window. row on: #mouseLeave send: #paneTransition: to: window. ! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/10/2001 11:46'! addClassAndSwitchesTo: window at: nominalFractions plus: verticalOffset ^self addAListPane: self buildMorphicClassList to: window at: nominalFractions plus: verticalOffset ! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/9/2001 10:39'! addMorphicSwitchesTo: window at: aLayoutFrame window addMorph: (self buildMorphicSwitches borderWidth: 0) fullFrame: aLayoutFrame. ! ! !Browser methodsFor: 'initialize-release'! browserWindowActivated "Called when a window whose model is the receiver is reactivated, giving the receiver an opportunity to take steps if it wishes. The default is to do nothing. 8/5/96 sw"! ! !Browser methodsFor: 'initialize-release' stamp: 'tk 4/8/98 15:22'! buildClassSwitchView | aSwitchView | aSwitchView _ PluggableButtonView on: self getState: #classMessagesIndicated action: #indicateClassMessages. aSwitchView label: 'class'; window: (0@0 extent: 15@8); askBeforeChanging: true. ^ aSwitchView ! ! !Browser methodsFor: 'initialize-release' stamp: 'di 4/13/1999 13:54'! buildCommentSwitchView | aSwitchView | aSwitchView _ PluggableButtonView on: self getState: #classCommentIndicated action: #plusButtonHit. aSwitchView label: '?' asText allBold; borderWidthLeft: 0 right: 1 top: 0 bottom: 0; window: (0@0 extent: 10@8); askBeforeChanging: true. ^ aSwitchView ! ! !Browser methodsFor: 'initialize-release' stamp: 'tk 4/8/98 16:11'! buildInstanceClassSwitchView | aView aSwitchView instSwitchView comSwitchView | aView _ View new model: self. aView window: (0 @ 0 extent: 50 @ 8). instSwitchView _ self buildInstanceSwitchView. aView addSubView: instSwitchView. comSwitchView _ self buildCommentSwitchView. aView addSubView: comSwitchView toRightOf: instSwitchView. aSwitchView _ self buildClassSwitchView. aView addSubView: aSwitchView toRightOf: comSwitchView. ^aView! ! !Browser methodsFor: 'initialize-release' stamp: 'tk 4/8/98 16:10'! buildInstanceSwitchView | aSwitchView | aSwitchView _ PluggableButtonView on: self getState: #instanceMessagesIndicated action: #indicateInstanceMessages. aSwitchView label: 'instance'; borderWidthLeft: 0 right: 1 top: 0 bottom: 0; window: (0@0 extent: 25@8); askBeforeChanging: true. ^ aSwitchView ! ! !Browser methodsFor: 'initialize-release' stamp: 'JW 2/2/2001 16:07'! buildMorphicClassList | myClassList | myClassList _ PluggableListMorph on: self list: #classList selected: #classListIndex changeSelected: #classListIndex: menu: #classListMenu:shifted: keystroke: #classListKey:from:. myClassList borderWidth: 0. myClassList enableDragNDrop: Preferences browseWithDragNDrop. myClassList highlightSelector: #highlightClassList:with:. ^myClassList ! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:58'! buildMorphicMessageCatList | myMessageCatList | myMessageCatList _ PluggableMessageCategoryListMorph on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu: keystroke: #arrowKey:from: getRawListSelector: #rawMessageCategoryList. myMessageCatList enableDragNDrop: Preferences browseWithDragNDrop. myMessageCatList highlightSelector: #highlightMessageCategoryList:with:. ^myMessageCatList ! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:58'! buildMorphicMessageList | aListMorph | aListMorph _ PluggableListMorph on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. aListMorph enableDragNDrop: Preferences browseWithDragNDrop. aListMorph menuTitleSelector: #messageListSelectorTitle. aListMorph highlightSelector: #highlightMessageList:with:. ^aListMorph ! ! !Browser methodsFor: 'initialize-release' stamp: 'JW 2/3/2001 09:33'! buildMorphicSwitches | instanceSwitch divider1 divider2 commentSwitch classSwitch row aColor | instanceSwitch _ PluggableButtonMorph on: self getState: #instanceMessagesIndicated action: #indicateInstanceMessages. instanceSwitch label: 'instance'; askBeforeChanging: true; borderWidth: 0. commentSwitch _ PluggableButtonMorph on: self getState: #classCommentIndicated action: #plusButtonHit. commentSwitch label: '?' asText allBold; askBeforeChanging: true; setBalloonText: 'class comment'; borderWidth: 0. classSwitch _ PluggableButtonMorph on: self getState: #classMessagesIndicated action: #indicateClassMessages. classSwitch label: 'class'; askBeforeChanging: true; borderWidth: 0. divider1 := SubpaneDividerMorph vertical. divider2 := SubpaneDividerMorph vertical. row _ AlignmentMorph newRow hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 0; addMorphBack: instanceSwitch; addMorphBack: divider1; addMorphBack: commentSwitch; addMorphBack: divider2; addMorphBack: classSwitch. aColor _ Color colorFrom: self defaultBackgroundColor. {instanceSwitch. commentSwitch. classSwitch} do: [:m | m color: aColor; onColor: aColor darker offColor: aColor; hResizing: #spaceFill; vResizing: #spaceFill. ]. ^ row ! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:58'! buildMorphicSystemCatList | dragNDropFlag myCatList | dragNDropFlag _ Preferences browseWithDragNDrop. myCatList _ PluggableListMorph on: self list: #systemCategoryList selected: #systemCategoryListIndex changeSelected: #systemCategoryListIndex: menu: #systemCategoryMenu: keystroke: #systemCatListKey:from:. myCatList enableDragNDrop: dragNDropFlag. myCatList highlightSelector: #highlightSystemCategoryList:with:. ^myCatList ! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/4/2001 15:55'! buildOptionalButtonsView "Build the view for the optional buttons (mvc)" | aView buttonView offset bWidth bHeight first previousView | aView _ View new model: self. bHeight _ self optionalButtonHeight. aView window: (0 @ 0 extent: 200 @ bHeight). offset _ 0. first _ true. previousView _ nil. self optionalButtonPairs do: [:pair | buttonView _ PluggableButtonView on: self getState: nil action: pair second. buttonView label: pair first asParagraph. bWidth _ buttonView label boundingBox width // 2. "Need something more deterministic." buttonView window: (offset@0 extent: bWidth@bHeight). offset _ offset + bWidth + 0. first ifTrue: [aView addSubView: buttonView. first _ false] ifFalse: [buttonView borderWidthLeft: 1 right: 0 top: 0 bottom: 0. aView addSubView: buttonView toRightOf: previousView]. previousView _ buttonView]. ^ aView! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/13/2000 16:45'! defaultBrowserTitle ^ 'System Browser'! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:56'! highlightClassList: list with: morphList! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:56'! highlightMessageCategoryList: list with: morphList! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:56'! highlightMessageList: list with: morphList! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:56'! highlightSystemCategoryList: list with: morphList! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 9/22/1999 17:13'! methodCategoryChanged self changed: #messageCategoryList. self changed: #messageList. self changed: #annotation. self messageListIndex: 0! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/10/2001 11:02'! openAsMorphClassEditing: editString "Create a pluggable version a Browser on just a single class." | window dragNDropFlag hSepFrac switchHeight mySingletonClassList | window _ (SystemWindow labelled: 'later') model: self. dragNDropFlag _ Preferences browseWithDragNDrop. hSepFrac _ 0.3. switchHeight _ 25. mySingletonClassList _ PluggableListMorph on: self list: #classListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #classListMenu:shifted: keystroke: #classListKey:from:. mySingletonClassList enableDragNDrop: dragNDropFlag. window addMorph: mySingletonClassList fullFrame: ( LayoutFrame fractions: (0@0 corner: 0.5@0) offsets: (0@0 corner: 0@switchHeight) ). self addMorphicSwitchesTo: window at: ( LayoutFrame fractions: (0.5@0 corner: 1.0@0) offsets: (0@0 corner: 0@switchHeight) ). window addMorph: self buildMorphicMessageCatList fullFrame: ( LayoutFrame fractions: (0@0 corner: 0.5@hSepFrac) offsets: (0@switchHeight corner: 0@0) ). window addMorph: self buildMorphicMessageList fullFrame: ( LayoutFrame fractions: (0.5@0 corner: 1.0@hSepFrac) offsets: (0@switchHeight corner: 0@0) ). self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString. window setUpdatablePanesFrom: #(messageCategoryList messageList). ^ window! ! !Browser methodsFor: 'initialize-release' stamp: 'ar 1/31/2001 20:55'! openAsMorphEditing: editString "Create a pluggable version of all the morphs for a Browser in Morphic" | window hSepFrac | hSepFrac _ 0.4. window _ (SystemWindow labelled: 'later') model: self. window addMorph: self buildMorphicSystemCatList frame: (0@0 corner: 0.25@hSepFrac). self addClassAndSwitchesTo: window at: (0.25@0 corner: 0.5@hSepFrac) plus: 0. window addMorph: self buildMorphicMessageCatList frame: (0.5@0 extent: 0.25@hSepFrac). window addMorph: self buildMorphicMessageList frame: (0.75@0 extent: 0.25@hSepFrac). self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString. window setUpdatablePanesFrom: #(systemCategoryList classList messageCategoryList messageList). ^ window! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/9/2001 11:31'! openAsMorphMessageEditing: editString "Create a pluggable version a Browser that shows just one message" | window mySingletonMessageList verticalOffset nominalFractions | window _ (SystemWindow labelled: 'later') model: self. mySingletonMessageList _ PluggableListMorph on: self list: #messageListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. mySingletonMessageList enableDragNDrop: Preferences browseWithDragNDrop. verticalOffset _ 25. nominalFractions _ 0@0 corner: 1@0. window addMorph: mySingletonMessageList fullFrame: ( LayoutFrame fractions: nominalFractions offsets: (0@0 corner: 0@verticalOffset) ). verticalOffset _ self addOptionalAnnotationsTo: window at: nominalFractions plus: verticalOffset. verticalOffset _ self addOptionalButtonsTo: window at: nominalFractions plus: verticalOffset. window addMorph: (self buildMorphicCodePaneWith: editString) fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@1) offsets: (0@verticalOffset corner: 0@0) ). ^ window! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/10/2001 10:59'! openAsMorphMsgCatEditing: editString "Create a pluggable version a Browser on just a message category." | window hSepFrac | window _ (SystemWindow labelled: 'later') model: self. hSepFrac _ 0.3. window addMorph: ((PluggableListMorph on: self list: #messageCatListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageCategoryMenu:) enableDragNDrop: Preferences browseWithDragNDrop) fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@0) offsets: (0@0 corner: 0@25) ). window addMorph: self buildMorphicMessageList fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@hSepFrac) offsets: (0@25 corner: 0@0) ). self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString. window setUpdatablePanesFrom: #(messageCatListSingleton messageList). ^ window! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/10/2001 11:31'! openAsMorphSysCatEditing: editString "Create a pluggable version of all the views for a Browser, including views and controllers." | window hSepFrac switchHeight mySingletonList nextOffsets | window _ (SystemWindow labelled: 'later') model: self. hSepFrac _ 0.30. switchHeight _ 25. mySingletonList _ PluggableListMorph on: self list: #systemCategorySingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #systemCatSingletonMenu: keystroke: #systemCatSingletonKey:from:. mySingletonList enableDragNDrop: Preferences browseWithDragNDrop. window addMorph: mySingletonList fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@0) offsets: (0@0 corner: 0@switchHeight) ). self addClassAndSwitchesTo: window at: (0@0 corner: 0.3333@hSepFrac) plus: switchHeight. nextOffsets _ 0@switchHeight corner: 0@0. window addMorph: self buildMorphicMessageCatList fullFrame: ( LayoutFrame fractions: (0.3333@0 corner: 0.6666@hSepFrac) offsets: nextOffsets ). window addMorph: self buildMorphicMessageList fullFrame: ( LayoutFrame fractions: (0.6666@0 corner: 1@hSepFrac) offsets: nextOffsets ). self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString. window setUpdatablePanesFrom: #( classList messageCategoryList messageList). ^ window! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/24/2001 21:24'! openEditString: aString "Create a pluggable version of all the views for a Browser, including views and controllers." | systemCategoryListView classListView messageCategoryListView messageListView browserCodeView topView switchView underPane y optionalButtonsView annotationPane | self couldOpenInMorphic ifTrue: [^ self openAsMorphEditing: aString]. "Sensor leftShiftDown ifTrue: [^ self openAsMorphEditing: aString]. uncomment-out for testing morphic browser embedded in mvc project" topView _ StandardSystemView new model: self. topView borderWidth: 1. "label and minSize taken care of by caller" systemCategoryListView _ PluggableListView on: self list: #systemCategoryList selected: #systemCategoryListIndex changeSelected: #systemCategoryListIndex: menu: #systemCategoryMenu: keystroke: #systemCatListKey:from:. systemCategoryListView window: (0 @ 0 extent: 50 @ 70). topView addSubView: systemCategoryListView. classListView _ PluggableListView on: self list: #classList selected: #classListIndex changeSelected: #classListIndex: menu: #classListMenu:shifted: keystroke: #classListKey:from:. classListView window: (0 @ 0 extent: 50 @ 62). topView addSubView: classListView toRightOf: systemCategoryListView. switchView _ self buildInstanceClassSwitchView. switchView borderWidth: 1. topView addSubView: switchView below: classListView. messageCategoryListView _ PluggableListView on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu:. messageCategoryListView window: (0 @ 0 extent: 50 @ 70). topView addSubView: messageCategoryListView toRightOf: classListView. messageListView _ PluggableListView on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. messageListView window: (0 @ 0 extent: 50 @ 70). messageListView menuTitleSelector: #messageListSelectorTitle. topView addSubView: messageListView toRightOf: messageCategoryListView. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 200@self optionalAnnotationHeight). topView addSubView: annotationPane below: systemCategoryListView. underPane _ annotationPane. y _ 110 - self optionalAnnotationHeight] ifFalse: [ underPane _ systemCategoryListView. y _ 110]. self wantsOptionalButtons ifTrue: [optionalButtonsView _ self buildOptionalButtonsView. optionalButtonsView borderWidth: 1. topView addSubView: optionalButtonsView below: underPane. underPane _ optionalButtonsView. y _ y - self optionalButtonHeight]. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: 200@y). topView addSubView: browserCodeView below: underPane. aString ifNotNil: [browserCodeView editString: aString. browserCodeView hasUnacceptedEdits: true]. topView setUpdatablePanesFrom: #(systemCategoryList classList messageCategoryList messageList). ^ topView! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/24/2001 21:24'! openMessageCatEditString: aString "Create a pluggable version of the views for a Browser that just shows one message category." | messageCategoryListView messageListView browserCodeView topView annotationPane underPane y optionalButtonsView | self couldOpenInMorphic ifTrue: [^ self openAsMorphMsgCatEditing: aString]. topView _ (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" messageCategoryListView _ PluggableListView on: self list: #messageCatListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageCategoryMenu:. messageCategoryListView window: (0 @ 0 extent: 200 @ 12). topView addSubView: messageCategoryListView. messageListView _ PluggableListView on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. messageListView menuTitleSelector: #messageListSelectorTitle. messageListView window: (0 @ 0 extent: 200 @ 70). topView addSubView: messageListView below: messageCategoryListView. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 200@self optionalAnnotationHeight). topView addSubView: annotationPane below: messageListView. underPane _ annotationPane. y _ (200 - 12 - 70) - self optionalAnnotationHeight] ifFalse: [underPane _ messageListView. y _ (200 - 12 - 70)]. self wantsOptionalButtons ifTrue: [optionalButtonsView _ self buildOptionalButtonsView. optionalButtonsView borderWidth: 1. topView addSubView: optionalButtonsView below: underPane. underPane _ optionalButtonsView. y _ y - self optionalButtonHeight]. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: 200@y). topView addSubView: browserCodeView below: underPane. aString ifNotNil: [browserCodeView editString: aString. browserCodeView hasUnacceptedEdits: true]. topView setUpdatablePanesFrom: #(messageCatListSingleton messageList). ^ topView! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/24/2001 21:24'! openMessageEditString: aString "Create a pluggable version of the views for a Browser that just shows one message." | messageListView browserCodeView topView annotationPane underPane y | Smalltalk isMorphic ifTrue: [^ self openAsMorphMessageEditing: aString]. topView _ (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" messageListView _ PluggableListView on: self list: #messageListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageListMenu:shifted:. messageListView window: (0 @ 0 extent: 200 @ 12). topView addSubView: messageListView. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 200@self optionalAnnotationHeight). topView addSubView: annotationPane below: messageListView. underPane _ annotationPane. y _ (200 - 12) - self optionalAnnotationHeight] ifFalse: [underPane _ messageListView. y _ 200 - 12]. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: 200@y). topView addSubView: browserCodeView below: underPane. aString ifNotNil: [browserCodeView editString: aString. browserCodeView hasUnacceptedEdits: true]. ^ topView! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/24/2001 21:24'! openOnClassWithEditString: aString "Create a pluggable version of all the views for a Browser, including views and controllers." | classListView messageCategoryListView messageListView browserCodeView topView switchView annotationPane underPane y optionalButtonsView | Smalltalk isMorphic ifTrue: [^ self openAsMorphClassEditing: aString]. topView _ (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" classListView _ PluggableListView on: self list: #classListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #classListMenu:shifted: keystroke: #classListKey:from:. classListView window: (0 @ 0 extent: 100 @ 12). topView addSubView: classListView. messageCategoryListView _ PluggableListView on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu:. messageCategoryListView window: (0 @ 0 extent: 100 @ 70). topView addSubView: messageCategoryListView below: classListView. messageListView _ PluggableListView on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. messageListView menuTitleSelector: #messageListSelectorTitle. messageListView window: (0 @ 0 extent: 100 @ 70). topView addSubView: messageListView toRightOf: messageCategoryListView. switchView _ self buildInstanceClassSwitchView. switchView borderWidth: 1. switchView window: switchView window viewport: (classListView viewport topRight corner: messageListView viewport topRight). topView addSubView: switchView toRightOf: classListView. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 200@self optionalAnnotationHeight). topView addSubView: annotationPane below: messageCategoryListView. underPane _ annotationPane. y _ (200-12-70) - self optionalAnnotationHeight] ifFalse: [underPane _ messageCategoryListView. y _ (200-12-70)]. self wantsOptionalButtons ifTrue: [optionalButtonsView _ self buildOptionalButtonsView. optionalButtonsView borderWidth: 1. topView addSubView: optionalButtonsView below: underPane. underPane _ optionalButtonsView. y _ y - self optionalButtonHeight]. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: 200@y). topView addSubView: browserCodeView below: underPane. aString ifNotNil: [browserCodeView editString: aString. browserCodeView hasUnacceptedEdits: true]. topView setUpdatablePanesFrom: #(messageCategoryList messageList). ^ topView! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 1/24/2001 21:24'! openSystemCatEditString: aString "Create a pluggable version of all the views for a Browser, including views and controllers. The top list view is of the currently selected system class category--a single item list." | systemCategoryListView classListView messageCategoryListView messageListView browserCodeView topView switchView y annotationPane underPane optionalButtonsView | Smalltalk isMorphic ifTrue: [^ self openAsMorphSysCatEditing: aString]. topView _ (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" systemCategoryListView _ PluggableListView on: self list: #systemCategorySingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #systemCatSingletonMenu: keystroke: #systemCatSingletonKey:from:. systemCategoryListView window: (0 @ 0 extent: 200 @ 12). topView addSubView: systemCategoryListView. classListView _ PluggableListView on: self list: #classList selected: #classListIndex changeSelected: #classListIndex: menu: #classListMenu:shifted: keystroke: #classListKey:from:. classListView window: (0 @ 0 extent: 67 @ 62). topView addSubView: classListView below: systemCategoryListView. messageCategoryListView _ PluggableListView on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu:. messageCategoryListView window: (0 @ 0 extent: 66 @ 70). topView addSubView: messageCategoryListView toRightOf: classListView. switchView _ self buildInstanceClassSwitchView. switchView window: switchView window viewport: (classListView viewport bottomLeft corner: messageCategoryListView viewport bottomLeft). switchView borderWidth: 1. topView addSubView: switchView below: classListView. messageListView _ PluggableListView on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. messageListView menuTitleSelector: #messageListSelectorTitle. messageListView window: (0 @ 0 extent: 67 @ 70). topView addSubView: messageListView toRightOf: messageCategoryListView. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 200@self optionalAnnotationHeight). topView addSubView: annotationPane below: switchView. y _ 110 - 12 - self optionalAnnotationHeight. underPane _ annotationPane] ifFalse: [y _ 110 - 12. underPane _ switchView]. self wantsOptionalButtons ifTrue: [optionalButtonsView _ self buildOptionalButtonsView. optionalButtonsView borderWidth: 1. topView addSubView: optionalButtonsView below: underPane. underPane _ optionalButtonsView. y _ y - self optionalButtonHeight]. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: 200@y). topView addSubView: browserCodeView below: underPane. aString ifNotNil: [browserCodeView editString: aString. browserCodeView hasUnacceptedEdits: true]. topView setUpdatablePanesFrom: #(classList messageCategoryList messageList). ^ topView! ! !Browser methodsFor: 'initialize-release' stamp: 'sbw 12/8/1999 12:37'! optionalAnnotationHeight ^ 10! ! !Browser methodsFor: 'initialize-release' stamp: 'sbw 12/8/1999 12:23'! optionalButtonHeight ^ 10! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 12/28/2000 17:42'! optionalButtonPairs "Answer a tuple (formerly pairs) defining buttons, in the format: button label selector to send help message" ^ #( ('senders' browseSendersOfMessages 'browse senders of...') ('implementors' browseMessages 'browse implementors of...') ('versions' browseVersions 'browse versions') ('inheritance' methodHierarchy 'browse method inheritance green: sends to super tan: has override(s) mauve: both of the above') ('hierarchy' classHierarchy 'browse class hierarchy') ('inst vars' browseInstVarRefs 'inst var refs...') ('class vars' browseClassVarRefs 'class var refs...'))! ! !Browser methodsFor: 'initialize-release' stamp: 'RAA 1/17/2001 14:18'! optionalButtonRow "Answer a row of control buttons" | aRow aButton | aRow _ AlignmentMorph newRow. aRow setNameTo: 'buttonPane'. aRow beSticky. aRow hResizing: #spaceFill. aRow wrapCentering: #center; cellPositioning: #leftCenter. aRow clipSubmorphs: true. aRow addTransparentSpacerOfSize: (5@0). self optionalButtonPairs do: [:tuple | aButton _ PluggableButtonMorph on: self getState: nil action: tuple second. aButton useRoundedCorners; hResizing: #spaceFill; vResizing: #spaceFill; label: tuple first asString; onColor: Color transparent offColor: Color transparent. tuple size > 2 ifTrue: [aButton setBalloonText: tuple third]. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0)]. aRow addMorphBack: self diffButton. Preferences sourceCommentToggleInBrowsers ifTrue: [aRow addMorphBack: self sourceOrInfoButton]. ^ aRow! ! !Browser methodsFor: 'initialize-release' stamp: 'm3r 3/5/1999 22:58'! setClass: aBehavior selector: aSymbol "Set the state of a new, uninitialized Browser." | isMeta aClass systemCatIndex messageCatIndex | aBehavior ifNil: [^ self]. (aBehavior isKindOf: Metaclass) ifTrue: [isMeta _ true. aClass _ aBehavior soleInstance] ifFalse: [isMeta _ false. aClass _ aBehavior]. systemCatIndex _ SystemOrganization categories indexOf: aClass category. self systemCategoryListIndex: systemCatIndex. self classListIndex: ((SystemOrganization listAtCategoryNumber: systemCatIndex) indexOf: aClass name). self metaClassIndicated: isMeta. aSymbol ifNil: [^ self]. messageCatIndex _ aBehavior organization numberOfCategoryOfElement: aSymbol. self messageCategoryListIndex: messageCatIndex + 1. "<- FIXED offset" messageCatIndex = 0 ifTrue: [^ self]. self messageListIndex: ((aBehavior organization listAtCategoryNumber: messageCatIndex) indexOf: aSymbol)! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 5/26/1999 23:46'! setSelector: aSymbol "Make the receiver point at the given selector, in the currently chosen class" | aClass messageCatIndex | aSymbol ifNil: [^ self]. (aClass _ self selectedClassOrMetaClass) ifNil: [^ self]. messageCatIndex _ aClass organization numberOfCategoryOfElement: aSymbol. self messageCategoryListIndex: messageCatIndex + 1. messageCatIndex = 0 ifTrue: [^ self]. self messageListIndex: ((aClass organization listAtCategoryNumber: messageCatIndex) indexOf: aSymbol)! ! !Browser methodsFor: 'initialize-release' stamp: 'sw 11/8/1999 13:36'! systemCatSingletonKey: aChar from: aView ^ self messageListKey: aChar from: aView! ! !Browser methodsFor: 'initialize-release'! systemOrganizer: aSystemOrganizer "Initialize the receiver as a perspective on the system organizer, aSystemOrganizer. Typically there is only one--the system variable SystemOrganization." super initialize. contents _ nil. systemOrganizer _ aSystemOrganizer. systemCategoryListIndex _ 0. classListIndex _ 0. messageCategoryListIndex _ 0. messageListIndex _ 0. metaClassIndicated _ false. self setClassOrganizer. editSelection _ #none! ! !Browser methodsFor: 'message category functions' stamp: 'mir 5/5/2000 16:02'! addCategory "Present a choice of categories or prompt for a new category name and add it before the current selection, or at the end if no current selection" | labels reject lines cats menuIndex oldIndex newName | self okToChange ifFalse: [^ self]. classListIndex = 0 ifTrue: [^ self]. labels _ OrderedCollection with: 'new...'. reject _ Set new. reject addAll: self selectedClassOrMetaClass organization categories; add: ClassOrganizer nullCategory; add: ClassOrganizer default. lines _ OrderedCollection new. self selectedClassOrMetaClass allSuperclasses do: [:cls | cls = Object ifFalse: [ cats _ cls organization categories reject: [:cat | reject includes: cat]. cats isEmpty ifFalse: [ lines add: labels size. labels addAll: cats asSortedCollection. reject addAll: cats]]]. newName _ (labels size = 1 or: [ menuIndex _ (PopUpMenu labelArray: labels lines: lines) startUpWithCaption: 'Add Category'. menuIndex = 0 ifTrue: [^ self]. menuIndex = 1]) ifTrue: [ self request: 'Please type new category name' initialAnswer: 'category name'] ifFalse: [ labels at: menuIndex]. oldIndex _ messageCategoryListIndex. newName isEmpty ifTrue: [^ self] ifFalse: [newName _ newName asSymbol]. self classOrMetaClassOrganizer addCategory: newName before: (messageCategoryListIndex = 0 ifTrue: [nil] ifFalse: [self selectedMessageCategoryName]). self changed: #messageCategoryList. self messageCategoryListIndex: (oldIndex = 0 ifTrue: [self classOrMetaClassOrganizer categories size + 1] ifFalse: [oldIndex]). self changed: #messageCategoryList. ! ! !Browser methodsFor: 'message category functions' stamp: 'SqR 11/16/2000 13:53'! alphabetizeMessageCategories classListIndex = 0 ifTrue: [^ false]. self okToChange ifFalse: [^ false]. Smalltalk changes reorganizeClass: self selectedClassOrMetaClass. self classOrMetaClassOrganizer sortCategories. self clearUserEditFlag. self editClass. self classListIndex: classListIndex. ^ true! ! !Browser methodsFor: 'message category functions'! buildMessageCategoryBrowser "Create and schedule a message category browser for the currently selected message category." self buildMessageCategoryBrowserEditString: nil! ! !Browser methodsFor: 'message category functions' stamp: 'wod 6/24/1998 02:10'! buildMessageCategoryBrowserEditString: aString "Create and schedule a message category browser for the currently selected message category. The initial text view contains the characters in aString." "wod 6/24/1998: set newBrowser classListIndex so that it works whether the receiver is a standard or a Hierarchy Browser." | newBrowser | messageCategoryListIndex ~= 0 ifTrue: [newBrowser _ Browser new. newBrowser systemCategoryListIndex: systemCategoryListIndex. newBrowser classListIndex: (newBrowser classList indexOf: self selectedClassName). newBrowser metaClassIndicated: metaClassIndicated. newBrowser messageCategoryListIndex: messageCategoryListIndex. newBrowser messageListIndex: messageListIndex. Browser openBrowserView: (newBrowser openMessageCatEditString: aString) label: 'Message Category Browser (' , newBrowser selectedClassOrMetaClassName , ')']! ! !Browser methodsFor: 'message category functions' stamp: 'sw 1/4/2001 12:05'! categoryOfCurrentMethod "Determine the method category associated with the receiver. If there is a method currently selected, answer its category. If no that owns the current method. Return the category name." | aCategory | ^ super categoryOfCurrentMethod ifNil: [(aCategory _ self messageCategoryListSelection) == ClassOrganizer allCategory ifTrue: [nil] ifFalse: [aCategory]]! ! !Browser methodsFor: 'message category functions' stamp: 'di 3/28/2000 15:56'! changeMessageCategories: aString "The characters in aString represent an edited version of the the message categories for the selected class. Update this information in the system and inform any dependents that the categories have been changed. This message is invoked because the user had issued the categories command and edited the message categories. Then the user issued the accept command." Smalltalk changes reorganizeClass: self selectedClassOrMetaClass. self classOrMetaClassOrganizer changeFromString: aString. self clearUserEditFlag. self editClass. self classListIndex: classListIndex. ^ true! ! !Browser methodsFor: 'message category functions' stamp: 'sw 1/28/1999 12:30'! editMessageCategories "Indicate to the receiver and its dependents that the message categories of the selected class have been changed." self okToChange ifFalse: [^ self]. classListIndex ~= 0 ifTrue: [self messageCategoryListIndex: 0. editSelection _ #editMessageCategories. self changed: #editMessageCategories. self contentsChanged]! ! !Browser methodsFor: 'message category functions' stamp: 'tk 4/2/98 13:53'! fileOutMessageCategories "Print a description of the selected message category of the selected class onto an external file." Cursor write showWhile: [messageCategoryListIndex ~= 0 ifTrue: [self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName]]! ! !Browser methodsFor: 'message category functions' stamp: 'sw 10/14/1999 16:53'! messageCategoryMenu: aMenu ^ aMenu labels: 'browse printOut fileOut reorganize alphabetize remove empty categories new category... rename... remove' lines: #(3 7) selections: #(buildMessageCategoryBrowser printOutMessageCategories fileOutMessageCategories editMessageCategories alphabetizeMessageCategories removeEmptyCategories addCategory renameCategory removeMessageCategory) ! ! !Browser methodsFor: 'message category functions' stamp: 'tk 4/2/98 13:53'! printOutMessageCategories "Print a description of the selected message category of the selected class onto an external file in Html format." Cursor write showWhile: [messageCategoryListIndex ~= 0 ifTrue: [self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName asHtml: true]]! ! !Browser methodsFor: 'message category functions' stamp: 'sma 2/27/2000 10:14'! removeEmptyCategories messageCategoryListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self selectedClassOrMetaClass organization removeEmptyCategories. self changed: #messageCategoryList ! ! !Browser methodsFor: 'message category functions' stamp: 'tk 4/2/98 13:54'! removeMessageCategory "If a message category is selected, create a Confirmer so the user can verify that the currently selected message category should be removed from the system. If so, remove it." | messageCategoryName | messageCategoryListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. messageCategoryName _ self selectedMessageCategoryName. (self messageList size = 0 or: [self confirm: 'Are you sure you want to remove this method category and all its methods?']) ifTrue: [self selectedClassOrMetaClass removeCategory: messageCategoryName. self messageCategoryListIndex: 0. self changed: #classSelectionChanged]. self changed: #messageCategoryList. ! ! !Browser methodsFor: 'message category functions' stamp: 'di 3/28/2000 15:56'! renameCategory "Prompt for a new category name and add it before the current selection, or at the end if no current selection" | oldIndex oldName newName | classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. (oldIndex _ messageCategoryListIndex) = 0 ifTrue: [^ self]. oldName _ self selectedMessageCategoryName. newName _ self request: 'Please type new category name' initialAnswer: oldName. newName isEmpty ifTrue: [^ self] ifFalse: [newName _ newName asSymbol]. newName = oldName ifTrue: [^ self]. Smalltalk changes reorganizeClass: self selectedClassOrMetaClass. self classOrMetaClassOrganizer renameCategory: oldName toBe: newName. self classListIndex: classListIndex. self messageCategoryListIndex: oldIndex. self changed: #messageCategoryList. ! ! !Browser methodsFor: 'message category list' stamp: 'tk 4/5/98 12:25'! messageCatListSingleton | name | name _ self selectedMessageCategoryName. ^ name ifNil: [Array new] ifNotNil: [Array with: name]! ! !Browser methodsFor: 'message category list' stamp: 'ccn 3/22/1999 17:56'! messageCategoryList "Answer the selected category of messages." classListIndex = 0 ifTrue: [^ Array new] ifFalse: [^ (Array with: ClassOrganizer allCategory), self classOrMetaClassOrganizer categories]! ! !Browser methodsFor: 'message category list'! messageCategoryListIndex "Answer the index of the selected message category." ^messageCategoryListIndex! ! !Browser methodsFor: 'message category list' stamp: 'dew 7/28/2000 01:13'! messageCategoryListIndex: anInteger "Set the selected message category to be the one indexed by anInteger." messageCategoryListIndex _ anInteger. messageListIndex _ 0. editSelection _ anInteger <= 1 ifTrue: [#editClass] ifFalse: [#newMessage]. contents _ nil. self changed: #messageCategorySelectionChanged. self changed: #messageCategoryListIndex. "update my selection" self changed: #messageList. self contentsChanged ! ! !Browser methodsFor: 'message category list' stamp: 'ccn 3/24/1999 11:02'! messageCategoryListSelection "Return the selected category name or nil." ^ ((self messageCategoryList size = 0 or: [self messageCategoryListIndex = 0]) or: [self messageCategoryList size < self messageCategoryListIndex]) ifTrue: [nil] ifFalse: [self messageCategoryList at: (self messageCategoryListIndex max: 1)]! ! !Browser methodsFor: 'message category list' stamp: 'sw 10/16/1999 22:56'! rawMessageCategoryList ^ classListIndex = 0 ifTrue: [Array new] ifFalse: [self classOrMetaClassOrganizer categories]! ! !Browser methodsFor: 'message category list' stamp: 'ccn+ceg 2/9/1999 20:25'! selectMessageCategoryNamed: aSymbol "Given aSymbol, select the category with that name. Do nothing if aSymbol doesn't exist." (self messageCategoryList includes: aSymbol) ifFalse: [^ self]. self messageCategoryListIndex: (self messageCategoryList indexOf: aSymbol)! ! !Browser methodsFor: 'message category list' stamp: 'ccn 3/22/1999 17:57'! selectOriginalCategoryForCurrentMethod "private - Select the message category for the current method. Note: This should only be called when somebody tries to save a method that they are modifying while ALL is selected. Returns: true on success, false on failure." | aSymbol | aSymbol _ self categoryOfCurrentMethod. (aSymbol notNil and: [aSymbol ~= ClassOrganizer allCategory]) ifTrue: [self selectMessageCategoryNamed: aSymbol. ^ true]. ^ false! ! !Browser methodsFor: 'message category list'! selectedMessageCategoryName "Answer the name of the selected message category, if any. Answer nil otherwise." messageCategoryListIndex = 0 ifTrue: [^nil]. ^self messageCategoryList at: messageCategoryListIndex! ! !Browser methodsFor: 'message category list' stamp: 'ccn+ceg 5/13/1999 19:54'! setOriginalCategoryIndexForCurrentMethod "private - Set the message category index for the currently selected method. Note: This should only be called when somebody tries to save a method that they are modifying while ALL is selected." messageCategoryListIndex _ self messageCategoryList indexOf: self categoryOfCurrentMethod ! ! !Browser methodsFor: 'message category list'! toggleMessageCategoryListIndex: anInteger "If the currently selected message category index is anInteger, deselect the category. Otherwise select the category whose index is anInteger." self messageCategoryListIndex: (messageCategoryListIndex = anInteger ifTrue: [0] ifFalse: [anInteger])! ! !Browser methodsFor: 'message functions' stamp: 'sw 1/11/2001 07:22'! addExtraShiftedItemsTo: aMenu "The shifted selector-list menu is being built; some menu items are appropriate only for certain kinds of browsers, and this gives a hook for them to be added as approrpiate. If any is added here, a line should be added first -- browse reimplementors of this message for examples." ! ! !Browser methodsFor: 'message functions'! browseImplementors "Create and schedule a message set browser on all implementors of the currently selected message selector. Do nothing if no message is selected." messageListIndex ~= 0 ifTrue: [Smalltalk browseAllImplementorsOf: self selectedMessageName]! ! !Browser methodsFor: 'message functions'! buildMessageBrowser "Create and schedule a message browser on the currently selected message. Do nothing if no message is selected. The initial text view contains nothing." self buildMessageBrowserEditString: nil! ! !Browser methodsFor: 'message functions' stamp: 'tk 4/6/98 21:47'! buildMessageBrowserEditString: aString "Create and schedule a message browser for the receiver in which the argument, aString, contains characters to be edited in the text view." messageListIndex = 0 ifTrue: [^ self]. ^ Browser openMessageBrowserForClass: self selectedClassOrMetaClass selector: self selectedMessageName editString: aString! ! !Browser methodsFor: 'message functions' stamp: 'tk 4/25/1998 00:08'! defineMessage: aString notifying: aController "Compile the expressions in aString. Notify aController if a syntax error occurs. Install the compiled method in the selected class classified under the currently selected message category name. Answer true if compilation succeeds, false otherwise." | selectedMessageName selector category oldMessageList | selectedMessageName _ self selectedMessageName. oldMessageList _ self messageList. contents _ nil. selector _ self selectedClassOrMetaClass compile: aString classified: (category _ self selectedMessageCategoryName) notifying: aController. selector == nil ifTrue: [^ false]. contents _ aString copy. selector ~~ selectedMessageName ifTrue: [category = ClassOrganizer nullCategory ifTrue: [self changed: #classSelectionChanged. self changed: #classList. self messageCategoryListIndex: 1]. self setClassOrganizer. "In case organization not cached" (oldMessageList includes: selector) ifFalse: [self changed: #messageList]. self messageListIndex: (self messageList indexOf: selector)]. ^ true! ! !Browser methodsFor: 'message functions' stamp: 'di 11/24/1999 13:40'! defineMessageFrom: aString notifying: aController "Compile the expressions in aString. Notify aController if a syntax error occurs. Install the compiled method in the selected class classified under the currently selected message category name. Answer the selector obtained if compilation succeeds, nil otherwise." | selectedMessageName selector category oldMessageList | selectedMessageName _ self selectedMessageName. oldMessageList _ self messageList. contents _ nil. selector _ (Parser new parseSelector: aString). (self metaClassIndicated and: [(self selectedClassOrMetaClass includesSelector: selector) not and: [Metaclass isScarySelector: selector]]) ifTrue: ["A frist-time definition overlaps the protocol of Metaclasses" (self confirm: ((selector , ' is used in the existing class system. Overriding it could cause serious problems. Is this really what you want to do?') asText makeBoldFrom: 1 to: selector size)) ifFalse: [^nil]]. selector _ self selectedClassOrMetaClass compile: aString classified: (category _ self selectedMessageCategoryName) notifying: aController. selector == nil ifTrue: [^ nil]. contents _ aString copy. selector ~~ selectedMessageName ifTrue: [category = ClassOrganizer nullCategory ifTrue: [self changed: #classSelectionChanged. self changed: #classList. self messageCategoryListIndex: 1]. self setClassOrganizer. "In case organization not cached" (oldMessageList includes: selector) ifFalse: [self changed: #messageList]. self messageListIndex: (self messageList indexOf: selector)]. ^ selector! ! !Browser methodsFor: 'message functions' stamp: 'tk 4/2/98 17:02'! inspectInstances "Inspect all instances of the selected class. 1/26/96 sw" | myClass | myClass _ self selectedClassOrMetaClass. myClass ~~ nil ifTrue: [myClass theNonMetaClass inspectAllInstances]. ! ! !Browser methodsFor: 'message functions' stamp: 'tk 4/2/98 17:02'! inspectSubInstances "Inspect all instances of the selected class and all its subclasses 1/26/96 sw" | aClass | aClass _ self selectedClassOrMetaClass. aClass ~~ nil ifTrue: [aClass _ aClass theNonMetaClass. aClass inspectSubInstances]. ! ! !Browser methodsFor: 'message functions' stamp: 'sw 12/28/2000 17:50'! messageListMenu: aMenu shifted: shifted "Answer the message-list menu" shifted ifTrue: [^ self shiftedMessageListMenu: aMenu]. aMenu addList:#( ('browse full (b)' browseMethodFull) ('browse hierarchy (h)' classHierarchy) ('browse method (O)' openSingleMessageBrowser) ('browse protocol (p)' browseFullProtocol) - ('fileOut' fileOutMessage) ('printOut' printOutMessage) - ('senders of... (n)' browseSendersOfMessages) ('implementors of... (m)' browseMessages) ('inheritance (i)' methodHierarchy) ('tile scriptor' openSyntaxView) ('versions (v)' browseVersions) - ('inst var refs...' browseInstVarRefs) ('inst var defs...' browseInstVarDefs) ('class var refs...' browseClassVarRefs) ('class variables' browseClassVariables) ('class refs (N)' browseClassRefs) - ('remove method (x)' removeMessage) - ('more...' shiftedYellowButtonActivity)). ^ aMenu ! ! !Browser methodsFor: 'message functions' stamp: 'di 5/27/1998 15:45'! removeMessage "If a message is selected, create a Confirmer so the user can verify that the currently selected message should be removed from the system. If so, remove it. If the Preference 'confirmMethodRemoves' is set to false, the confirmer is bypassed." | messageName confirmation | messageListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. messageName _ self selectedMessageName. confirmation _ self selectedClassOrMetaClass confirmRemovalOf: messageName. confirmation == 3 ifTrue: [^ self]. self selectedClassOrMetaClass removeSelector: self selectedMessageName. self messageListIndex: 0. self changed: #messageList. self setClassOrganizer. "In case organization not cached" confirmation == 2 ifTrue: [Smalltalk browseAllCallsOn: messageName] ! ! !Browser methodsFor: 'message functions' stamp: 'tk 4/2/98 17:03'! removeMessageFromBrowser "Our list speaks the truth and can't have arbitrary things removed" ^ self changed: #flash! ! !Browser methodsFor: 'message functions' stamp: 'sw 1/25/2001 07:24'! shiftedMessageListMenu: aMenu "Fill aMenu with the items appropriate when the shift key is held down" aMenu addList: #( ('method pane' makeIsolatedCodePane) "('make a scriptor' makeScriptor)" ('toggle diffing (D)' toggleDiffing) ('implementors of sent messages' browseAllMessages) - ('spawn sub-protocol' spawnProtocol) ('spawn full protocol' spawnFullProtocol) - ('sample instance' makeSampleInstance) ('inspect instances' inspectInstances) ('inspect subinstances' inspectSubInstances)). self addExtraShiftedItemsTo: aMenu. aMenu addList: #( - ('change category...' changeCategory) - ('change sets with this method' findMethodInChangeSets) ('revert to previous version' revertToPreviousVersion) ('remove from current change set' removeFromCurrentChanges) ('revert & remove from changes' revertAndForget) ('add to current change set' adoptMessageInCurrentChangeset) - ('fetch documentation' fetchDocPane) ('more...' unshiftedYellowButtonActivity)). ^ aMenu ! ! !Browser methodsFor: 'message list' stamp: 'ccn 3/24/1999 10:48'! messageList "Answer an Array of the message selectors of the currently selected message category, provided that the messageCategoryListIndex is in proper range. Otherwise, answer an empty Array If messageCategoryListIndex is found to be larger than the number of categories (it happens!!), it is reset to zero." | sel | (sel _ self messageCategoryListSelection) ifNil: [^ Array new]. ^ sel = ClassOrganizer allCategory ifTrue: [self classOrMetaClassOrganizer ifNil: [Array new] ifNotNil: [self classOrMetaClassOrganizer allMethodSelectors]] ifFalse: [(self classOrMetaClassOrganizer listAtCategoryNumber: messageCategoryListIndex - 1) ifNil: [messageCategoryListIndex _ 0. Array new]]! ! !Browser methodsFor: 'message list'! messageListIndex "Answer the index of the selected message selector into the currently selected message category." ^messageListIndex! ! !Browser methodsFor: 'message list' stamp: 'sw 1/5/2001 07:20'! messageListIndex: anInteger "Set the selected message selector to be the one indexed by anInteger." messageListIndex _ anInteger. editSelection _ anInteger = 0 ifTrue: [#newMessage] ifFalse: [#editMessage]. contents _ nil. self changed: #messageListIndex. "update my selection" self contentsChanged. self decorateButtons! ! !Browser methodsFor: 'message list' stamp: 'tk 4/6/98 10:48'! messageListSingleton | name | name _ self selectedMessageName. ^ name ifNil: [Array new] ifNotNil: [Array with: name]! ! !Browser methodsFor: 'message list' stamp: 'sw 12/1/2000 11:17'! reformulateList "If the receiver has a way of reformulating its message list, here is a chance for it to do so" super reformulateList. self messageListIndex: 0! ! !Browser methodsFor: 'message list' stamp: 'sw 12/5/2000 11:32'! selectedMessage "Answer a copy of the source code for the selected message." | class selector method tempNames | contents == nil ifFalse: [^ contents copy]. class _ self selectedClassOrMetaClass. selector _ self selectedMessageName. method _ class compiledMethodAt: selector ifAbsent: [ ^ '']. "method deleted while in another project" currentCompiledMethod _ method. (Sensor controlKeyPressed or: [method fileIndex > 0 and: [(SourceFiles at: method fileIndex) == nil]]) ifTrue: ["Emergency or no source file -- decompile without temp names" contents _ (class decompilerClass new decompile: selector in: class method: method) decompileString. contents _ contents asText makeSelectorBoldIn: class. ^ contents copy]. Sensor leftShiftDown ifTrue: ["Special request to decompile -- get temps from source file" tempNames _ (class compilerClass new parse: method getSourceFromFile asString in: class notifying: nil) tempNames. contents _ ((class decompilerClass new withTempNames: tempNames) decompile: selector in: class method: method) decompileString. contents _ contents asText makeSelectorBoldIn: class. ^ contents copy]. self showComment ifFalse: [contents _ class sourceCodeAt: selector. self validateMessageSource: selector. Preferences browseWithPrettyPrint ifTrue: [contents _ class compilerClass new format: contents in: class notifying: nil decorated: Preferences colorWhenPrettyPrinting]. self showDiffs ifTrue: [contents _ self diffFromPriorSourceFor: contents]. contents _ contents asText makeSelectorBoldIn: class] ifTrue: [contents _ self commentContents]. ^ contents copy! ! !Browser methodsFor: 'message list' stamp: 'sw 10/19/1999 17:39'! selectedMessageName | aList | "Answer the message selector of the currently selected message, if any. Answer nil otherwise." messageListIndex = 0 ifTrue: [^ nil]. ^ (aList _ self messageList) size >= messageListIndex ifTrue: [aList at: messageListIndex] ifFalse: [nil]! ! !Browser methodsFor: 'message list'! toggleMessageListIndex: anInteger "If the currently selected message index is anInteger, deselect the message selector. Otherwise select the message selector whose index is anInteger." self messageListIndex: (messageListIndex = anInteger ifTrue: [0] ifFalse: [anInteger])! ! !Browser methodsFor: 'message list' stamp: 'hg 3/13/2000 12:07'! validateMessageSource: selector (self selectedClass compilerClass == Object compilerClass and: [(contents asString findString: selector keywords first ) ~= 1]) ifTrue: [ PopUpMenu notify: 'Possible problem with source file!! The method source should start with the method selector but this is not the case!! You may proceed with caution but it is recommended that you get a new source file. This can happen if you download the "SqueakV2.sources" file, or the ".changes" file you use, as TEXT. It must be transfered in BINARY mode, even if it looks like a text file, to preserve the CR line ends. Mac users: This may have been caused by Stuffit Expander. To prevent the files above to be converted to Mac line ends when they are expanded, do this: Start the program, then from Preferences... in the File menu, choose the Cross Platform panel, then select "Never" and press OK. Then expand the compressed archive again.'].! ! !Browser methodsFor: 'metaclass' stamp: 'di 1/14/98 12:25'! classCommentIndicated "Answer true iff we're viewing the class comment." ^ editSelection == #editComment ! ! !Browser methodsFor: 'metaclass' stamp: 'ak 11/24/2000 21:46'! classMessagesIndicated "Answer whether the messages to be presented should come from the metaclass." ^ self metaClassIndicated and: [self classCommentIndicated not]! ! !Browser methodsFor: 'metaclass'! classOrMetaClassOrganizer "Answer the class organizer for the metaclass or class, depending on which (instance or class) is indicated." self metaClassIndicated ifTrue: [^metaClassOrganizer] ifFalse: [^classOrganizer]! ! !Browser methodsFor: 'metaclass'! indicateClassMessages "Indicate that the message selection should come from the metaclass messages." self metaClassIndicated: true! ! !Browser methodsFor: 'metaclass'! indicateInstanceMessages "Indicate that the message selection should come from the class (instance) messages." self metaClassIndicated: false! ! !Browser methodsFor: 'metaclass' stamp: 'di 1/14/98 13:20'! instanceMessagesIndicated "Answer whether the messages to be presented should come from the class." ^metaClassIndicated not and: [self classCommentIndicated not]! ! !Browser methodsFor: 'metaclass' stamp: 'sr 6/21/2000 17:23'! metaClassIndicated "Answer the boolean flag that indicates which of the method dictionaries, class or metaclass." ^ metaClassIndicated! ! !Browser methodsFor: 'metaclass' stamp: 'sw 1/5/2001 07:20'! metaClassIndicated: trueOrFalse "Indicate whether browsing instance or class messages." metaClassIndicated _ trueOrFalse. self setClassOrganizer. systemCategoryListIndex > 0 ifTrue: [editSelection _ classListIndex = 0 ifTrue: [metaClassIndicated ifTrue: [#none] ifFalse: [#newClass]] ifFalse: [#editClass]]. messageCategoryListIndex _ 1. messageListIndex _ 0. contents _ nil. self changed: #classSelectionChanged. self changed: #messageCategoryList. self changed: #messageList. self changed: #contents. self decorateButtons ! ! !Browser methodsFor: 'metaclass' stamp: 'tk 4/9/98 10:48'! selectedClassOrMetaClass "Answer the selected class or metaclass." | cls | self metaClassIndicated ifTrue: [^ (cls _ self selectedClass) ifNil: [nil] ifNotNil: [cls class]] ifFalse: [^ self selectedClass]! ! !Browser methodsFor: 'metaclass'! selectedClassOrMetaClassName "Answer the selected class name or metaclass name." ^self selectedClassOrMetaClass name! ! !Browser methodsFor: 'metaclass' stamp: 'di 1/14/98 13:27'! setClassOrganizer "Install whatever organization is appropriate" | theClass | classOrganizer _ nil. metaClassOrganizer _ nil. classListIndex = 0 ifTrue: [^ self]. classOrganizer _ (theClass _ self selectedClass) organization. metaClassOrganizer _ theClass class organization.! ! !Browser methodsFor: 'system category functions' stamp: 'di 4/12/98 13:56'! addSystemCategory "Prompt for a new category name and add it before the current selection, or at the end if no current selection" | oldIndex newName | self okToChange ifFalse: [^ self]. oldIndex _ systemCategoryListIndex. newName _ self request: 'Please type new category name' initialAnswer: 'Category-Name'. newName isEmpty ifTrue: [^ self] ifFalse: [newName _ newName asSymbol]. systemOrganizer addCategory: newName before: (systemCategoryListIndex = 0 ifTrue: [nil] ifFalse: [self selectedSystemCategoryName]). self systemCategoryListIndex: (oldIndex = 0 ifTrue: [systemOrganizer categories size] ifFalse: [oldIndex]). self changed: #systemCategoryList.! ! !Browser methodsFor: 'system category functions' stamp: 'tk 4/6/98 21:09'! browseAllClasses "Create and schedule a new browser on all classes alphabetically." | newBrowser | newBrowser _ HierarchyBrowser new initAlphabeticListing. Browser openBrowserView: (newBrowser openSystemCatEditString: nil) label: 'All Classes Alphabetically'! ! !Browser methodsFor: 'system category functions'! buildSystemCategoryBrowser "Create and schedule a new system category browser." self buildSystemCategoryBrowserEditString: nil! ! !Browser methodsFor: 'system category functions' stamp: 'tk 5/4/1998 15:56'! buildSystemCategoryBrowserEditString: aString "Create and schedule a new system category browser with initial textual contents set to aString." | newBrowser | systemCategoryListIndex > 0 ifTrue: [newBrowser _ Browser new. newBrowser systemCategoryListIndex: systemCategoryListIndex. newBrowser setClass: self selectedClassOrMetaClass selector: self selectedMessageName. Browser openBrowserView: (newBrowser openSystemCatEditString: aString) label: 'Classes in category ', newBrowser selectedSystemCategoryName]! ! !Browser methodsFor: 'system category functions' stamp: 'di 4/12/98 13:21'! changeSystemCategories: aString "Update the class categories by parsing the argument aString." systemOrganizer changeFromString: aString. self changed: #systemCategoryList. ^ true! ! !Browser methodsFor: 'system category functions' stamp: 'tk 4/2/98 13:43'! classNotFound self changed: #flash.! ! !Browser methodsFor: 'system category functions' stamp: 'sw 1/28/1999 12:30'! editSystemCategories "Retrieve the description of the class categories of the system organizer." self okToChange ifFalse: [^ self]. self systemCategoryListIndex: 0. editSelection _ #editSystemCategories. self changed: #editSystemCategories. self contentsChanged! ! !Browser methodsFor: 'system category functions' stamp: 'tk 3/31/98 07:52'! fileOutSystemCategory "Print a description of each class in the selected category onto a file whose name is the category name followed by .st." systemCategoryListIndex ~= 0 ifTrue: [systemOrganizer fileOutCategory: self selectedSystemCategoryName]! ! !Browser methodsFor: 'system category functions' stamp: 'stp 01/13/2000 12:26'! findClass "Search for a class by name." | pattern foundClass classNames index toMatch exactMatch potentialClassNames | self okToChange ifFalse: [^ self classNotFound]. pattern _ FillInTheBlank request: 'Class name or fragment?'. pattern isEmpty ifTrue: [^ self classNotFound]. toMatch _ (pattern copyWithout: $.) asLowercase. potentialClassNames _ self potentialClassNames asOrderedCollection. classNames _ pattern last = $. ifTrue: [potentialClassNames select: [:nm | nm asLowercase = toMatch]] ifFalse: [potentialClassNames select: [:n | n includesSubstring: toMatch caseSensitive: false]]. classNames isEmpty ifTrue: [^ self classNotFound]. exactMatch _ classNames detect: [:each | each asLowercase = toMatch] ifNone: [nil]. index _ classNames size = 1 ifTrue: [1] ifFalse: [exactMatch ifNil: [(PopUpMenu labelArray: classNames lines: #()) startUp] ifNotNil: [classNames addFirst: exactMatch. (PopUpMenu labelArray: classNames lines: #(1)) startUp]]. index = 0 ifTrue: [^ self classNotFound]. foundClass _ Smalltalk at: (classNames at: index) asSymbol. self selectCategoryForClass: foundClass. self selectClass: foundClass ! ! !Browser methodsFor: 'system category functions' stamp: 'sw 11/8/1999 10:04'! potentialClassNames "Answer the names of all the classes that could be viewed in this browser. This hook is provided so that HierarchyBrowsers can indicate their restricted subset. For generic Browsers, the entire list of classes known to Smalltalk is provided, though of course that really only is accurate in the case of full system browsers." ^ Smalltalk classNames! ! !Browser methodsFor: 'system category functions' stamp: 'tk 4/2/98 13:46'! printOutSystemCategory "Print a description of each class in the selected category as Html." Cursor write showWhile: [systemCategoryListIndex ~= 0 ifTrue: [systemOrganizer fileOutCategory: self selectedSystemCategoryName asHtml: true ]] ! ! !Browser methodsFor: 'system category functions' stamp: 'di 4/12/98 13:55'! removeSystemCategory "If a class category is selected, create a Confirmer so the user can verify that the currently selected class category and all of its classes should be removed from the system. If so, remove it." systemCategoryListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. (self classList size = 0 or: [self confirm: 'Are you sure you want to remove this system category and all its classes?']) ifTrue: [systemOrganizer removeSystemCategory: self selectedSystemCategoryName. self systemCategoryListIndex: 0. self changed: #systemCategoryList]! ! !Browser methodsFor: 'system category functions' stamp: 'di 4/12/98 13:55'! renameSystemCategory "Prompt for a new category name and add it before the current selection, or at the end if no current selection" | oldIndex oldName newName | (oldIndex _ systemCategoryListIndex) = 0 ifTrue: [^ self]. "no selection" self okToChange ifFalse: [^ self]. oldName _ self selectedSystemCategoryName. newName _ self request: 'Please type new category name' initialAnswer: oldName. newName isEmpty ifTrue: [^ self] ifFalse: [newName _ newName asSymbol]. oldName = newName ifTrue: [^ self]. systemOrganizer renameCategory: oldName toBe: newName. self systemCategoryListIndex: oldIndex. self changed: #systemCategoryList.! ! !Browser methodsFor: 'system category functions' stamp: 'sw 11/8/1999 14:07'! systemCatSingletonMenu: aMenu ^ aMenu labels: 'browse all browse printOut fileOut update rename... remove' lines: #(2 4) selections: #(browseAllClasses buildSystemCategoryBrowser printOutSystemCategory fileOutSystemCategory updateSystemCategories renameSystemCategory removeSystemCategory) ! ! !Browser methodsFor: 'system category functions' stamp: 'sma 2/5/2000 13:24'! systemCategoryMenu: aMenu ^ aMenu labels: 'find class... (f) recent classes... (r) browse all browse printOut fileOut reorganize update add item... rename... remove' lines: #(2 4 6 8) selections: #(findClass recent browseAllClasses buildSystemCategoryBrowser printOutSystemCategory fileOutSystemCategory editSystemCategories updateSystemCategories addSystemCategory renameSystemCategory removeSystemCategory )! ! !Browser methodsFor: 'system category functions' stamp: 'di 4/12/98 13:17'! updateSystemCategories "The class categories were changed in another browser. The receiver must reorganize its lists based on these changes." self okToChange ifFalse: [^ self]. self changed: #systemCategoryList! ! !Browser methodsFor: 'system category list' stamp: 'tk 5/4/1998 15:46'! indexIsOne "When used as a singleton list, index is always one" ^ 1! ! !Browser methodsFor: 'system category list' stamp: 'tk 5/4/1998 15:46'! indexIsOne: value "When used as a singleton list, can't change it" ^ self! ! !Browser methodsFor: 'system category list' stamp: 'stp 01/13/2000 12:25'! selectCategoryForClass: theClass self systemCategoryListIndex: (self systemCategoryList indexOf: theClass category) ! ! !Browser methodsFor: 'system category list' stamp: 'di 12/6/1999 20:11'! selectedEnvironment "Answer the name of the selected system category or nil." systemCategoryListIndex = 0 ifTrue: [^nil]. ^ Smalltalk environmentForCategory: self selectedSystemCategoryName! ! !Browser methodsFor: 'system category list'! selectedSystemCategoryName "Answer the name of the selected system category or nil." systemCategoryListIndex = 0 ifTrue: [^nil]. ^self systemCategoryList at: systemCategoryListIndex! ! !Browser methodsFor: 'system category list'! systemCategoryList "Answer the class categories modelled by the receiver." ^systemOrganizer categories! ! !Browser methodsFor: 'system category list'! systemCategoryListIndex "Answer the index of the selected class category." ^systemCategoryListIndex! ! !Browser methodsFor: 'system category list' stamp: 'sw 1/28/1999 12:30'! systemCategoryListIndex: anInteger "Set the selected system category index to be anInteger. Update all other selections to be deselected." systemCategoryListIndex _ anInteger. classListIndex _ 0. messageCategoryListIndex _ 0. messageListIndex _ 0. editSelection _ anInteger = 0 ifTrue: [#none] ifFalse: [#newClass]. metaClassIndicated _ false. self setClassOrganizer. contents _ nil. self changed: #systemCategorySelectionChanged. self changed: #systemCategoryListIndex. "update my selection" self changed: #classList. self changed: #messageCategoryList. self changed: #messageList. self contentsChanged. ! ! !Browser methodsFor: 'system category list' stamp: 'tk 4/3/98 10:30'! systemCategorySingleton | cat | cat _ self selectedSystemCategoryName. ^ cat ifNil: [Array new] ifNotNil: [Array with: cat]! ! !Browser methodsFor: 'system category list'! toggleSystemCategoryListIndex: anInteger "If anInteger is the current system category index, deselect it. Else make it the current system category selection." self systemCategoryListIndex: (systemCategoryListIndex = anInteger ifTrue: [0] ifFalse: [anInteger])! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Browser class instanceVariableNames: ''! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/10/1998 17:37'! fullOnClass: aClass "Open a new full browser set to class." | brow | brow _ Browser new. brow setClass: aClass selector: nil. Browser openBrowserView: (brow openEditString: nil) label: 'System Browser'! ! !Browser class methodsFor: 'instance creation' stamp: 'sw 1/13/2000 16:45'! fullOnClass: aClass selector: aSelector "Open a new full browser set to class." | brow classToUse | classToUse _ Preferences browseToolClass. brow _ classToUse new. brow setClass: aClass selector: aSelector. classToUse openBrowserView: (brow openEditString: nil) label: brow defaultBrowserTitle! ! !Browser class methodsFor: 'instance creation' stamp: 'di 10/18/1999 22:03'! new ^super new systemOrganizer: SystemOrganization! ! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/6/98 22:04'! newOnCategory: aCategory "Browse the system category of the given name. 7/13/96 sw" "Browser newOnCategory: 'Interface-Browser'" | newBrowser catList | newBrowser _ Browser new. catList _ newBrowser systemCategoryList. newBrowser systemCategoryListIndex: (catList indexOf: aCategory asSymbol ifAbsent: [^ self inform: 'No such category']). Browser openBrowserView: (newBrowser openSystemCatEditString: nil) label: 'Classes in category ', aCategory ! ! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/18/1998 16:28'! newOnClass: aClass "Open a new class browser on this class." ^ self newOnClass: aClass label: 'Class Browser: ', aClass name! ! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/6/98 22:55'! newOnClass: aClass label: aLabel "Open a new class browser on this class." | newBrowser | newBrowser _ Browser new. newBrowser setClass: aClass selector: nil. Browser openBrowserView: (newBrowser openOnClassWithEditString: nil) label: aLabel ! ! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/18/1998 16:29'! newOnClass: aClass selector: aSymbol "Open a new class browser on this class." | newBrowser | newBrowser _ Browser new. newBrowser setClass: aClass selector: aSymbol. Browser openBrowserView: (newBrowser openOnClassWithEditString: nil) label: 'Class Browser: ', aClass name ! ! !Browser class methodsFor: 'instance creation' stamp: 'sw 1/13/2000 16:46'! openBrowser "Create and schedule a BrowserView with label 'System Browser'. The view consists of five subviews, starting with the list view of system categories of SystemOrganization. The initial text view part is empty." Browser openBrowserView: (Browser new openEditString: nil) label: 'System Browser' ! ! !Browser class methodsFor: 'instance creation' stamp: 'di 5/14/1998 09:43'! openBrowserView: aBrowserView label: aString "Schedule aBrowserView, labelling the view aString." aBrowserView isMorph ifTrue: [(aBrowserView setLabel: aString) openInWorld] ifFalse: [aBrowserView label: aString. aBrowserView minimumSize: 300 @ 200. aBrowserView subViews do: [:each | each controller]. aBrowserView controller open]! ! !Browser class methodsFor: 'instance creation' stamp: 'tk 4/6/98 21:44'! openMessageBrowserForClass: aBehavior selector: aSymbol editString: aString "Create and schedule a message browser for the class, aBehavior, in which the argument, aString, contains characters to be edited in the text view. These characters are the source code for the message selector aSymbol." | newBrowser | (newBrowser _ Browser new) setClass: aBehavior selector: aSymbol. ^ Browser openBrowserView: (newBrowser openMessageEditString: aString) label: newBrowser selectedClassOrMetaClassName , ' ' , newBrowser selectedMessageName ! ! !Browser class methodsFor: 'class initialization'! initialize "Browser initialize" RecentClasses := OrderedCollection new! ! GenericUrl subclass: #BrowserUrl instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !BrowserUrl commentStamp: '' prior: 0! URLs that instruct a browser to do something.! !BrowserUrl methodsFor: 'downloading' stamp: 'ls 8/4/1998 20:42'! hasContents ^true! ! !BrowserUrl methodsFor: 'downloading' stamp: 'ls 7/26/1998 21:21'! retrieveContentsForBrowser: aBrowser ^aBrowser browserUrlContents: locator! ! PluggableCanvas subclass: #BufferedCanvas instanceVariableNames: 'remote previousVersion lastTick dirtyRect mirrorOfScreen ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Remote'! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 13:52'! apply: aBlock "self checkIfTimeToDisplay"! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/8/2000 15:04'! asBufferedCanvas ^self! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 15:03'! checkIfTimeToDisplay remote backlog > 0 ifTrue: [^self]. "why bother if network full?" dirtyRect ifNil: [^self]. self sendDeltas. lastTick _ Time millisecondClockValue. ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 20:44'! clipBy: aRectangle during: aBlock ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 13:04'! clipRect ^0@0 extent: 99999@99999 ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/8/2000 14:35'! connection: connection clipRect: newClipRect transform: transform remoteCanvas: remoteCanvas remote _ remoteCanvas. lastTick _ 0. ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 13:54'! displayIsFullyUpdated self checkIfTimeToDisplay! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 20:32'! drawMorph: x ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 22:36'! extent ^Display extent! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 15:00'! forceToScreen: rect mirrorOfScreen ifNil: [ mirrorOfScreen _ (previousVersion ifNil: [Display]) deepCopy. ]. mirrorOfScreen copy: rect from: rect origin in: Display rule: Form over. dirtyRect _ dirtyRect ifNil: [rect] ifNotNil: [dirtyRect merge: rect]. ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 22:36'! origin ^0@0! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/8/2000 15:06'! purgeOutputQueue! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/7/2000 18:08'! sendDeltas " NebraskaDebug showStats: #sendDeltas " | t deltas dirtyFraction | previousVersion ifNil: [ previousVersion _ Display deepCopy. remote image: previousVersion at: 0@0 sourceRect: previousVersion boundingBox rule: Form paint. ^remote forceToScreen: previousVersion boundingBox. ]. dirtyRect ifNil: [^self]. t _ Time millisecondClockValue. dirtyFraction _ dirtyRect area / previousVersion boundingBox area roundTo: 0.0001. deltas _ mirrorOfScreen deltaFrom: (previousVersion copy: dirtyRect) at: dirtyRect origin. previousVersion _ mirrorOfScreen. mirrorOfScreen _ nil. remote image: deltas at: dirtyRect origin sourceRect: deltas boundingBox rule: Form reverse; forceToScreen: dirtyRect. t _ Time millisecondClockValue - t. NebraskaDebug at: #sendDeltas add: {t. dirtyFraction. deltas boundingBox}. dirtyRect _ nil. ! ! Switch subclass: #Button instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Menus'! !Button commentStamp: '' prior: 0! I am a Switch that turns off automatically after being turned on, that is, I act like a push-button switch.! !Button methodsFor: 'state'! turnOff "Sets the state of the receiver to 'off'. The off action of the receiver is not executed." on _ false! ! !Button methodsFor: 'state'! turnOn "The receiver remains in the 'off' state'." self doAction: onAction. self doAction: offAction! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Button class instanceVariableNames: ''! !Button class methodsFor: 'instance creation'! newOn "Refer to the comment in Switch|newOn." self error: 'Buttons cannot be created in the on state'. ^nil! ! ArrayedCollection variableByteSubclass: #ByteArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !ByteArray commentStamp: '' prior: 0! I represent an ArrayedCollection whose elements are integers between 0 and 255. ! !ByteArray methodsFor: 'accessing' stamp: 'sma 4/22/2000 17:47'! atAllPut: value "Fill the receiver with the given value" super atAllPut: value! ! !ByteArray methodsFor: 'accessing' stamp: 'ar 12/5/1998 14:52'! byteAt: index ^self at: index! ! !ByteArray methodsFor: 'accessing' stamp: 'ar 12/5/1998 14:52'! byteAt: index put: value ^self at: index put: value! ! !ByteArray methodsFor: 'accessing' stamp: 'tk 3/13/2000 14:46'! bytesPerElement "Number of bytes in each item. This multiplied by (self size)*8 gives the number of bits stored." ^ 1! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:44'! longAt: index bigEndian: aBool "Return a 32bit integer quantity starting from the given byte index" | b0 b1 b2 w h | aBool ifTrue:[ b0 _ self at: index. b1 _ self at: index+1. b2 _ self at: index+2. w _ self at: index+3. ] ifFalse:[ w _ self at: index. b2 _ self at: index+1. b1 _ self at: index+2. b0 _ self at: index+3. ]. "Minimize LargeInteger arithmetic" h _ ((b0 bitAnd: 16r7F) - (b0 bitAnd: 16r80) bitShift: 8) + b1. b2 = 0 ifFalse:[w _ (b2 bitShift: 8) + w]. h = 0 ifFalse:[w _ (h bitShift: 16) + w]. ^w! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:47'! longAt: index put: value bigEndian: aBool "Return a 32bit integer quantity starting from the given byte index" | b0 b1 b2 b3 | b0 _ value bitShift: -24. b0 _ (b0 bitAnd: 16r7F) - (b0 bitAnd: 16r80). b1 _ (value bitShift: -16) bitAnd: 255. b2 _ (value bitShift: -8) bitAnd: 255. b3 _ value bitAnd: 255. aBool ifTrue:[ self at: index put: b0. self at: index+1 put: b1. self at: index+2 put: b2. self at: index+3 put: b3. ] ifFalse:[ self at: index put: b3. self at: index+1 put: b2. self at: index+2 put: b1. self at: index+3 put: b0. ]. ^value! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:57'! shortAt: index bigEndian: aBool "Return a 16 bit integer quantity starting from the given byte index" | uShort | uShort _ self unsignedShortAt: index bigEndian: aBool. ^(uShort bitAnd: 16r7FFF) - (uShort bitAnd: 16r8000)! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/3/1998 14:20'! shortAt: index put: value bigEndian: aBool "Store a 16 bit integer quantity starting from the given byte index" self unsignedShortAt: index put: (value bitAnd: 16r7FFF) - (value bitAnd: -16r8000) bigEndian: aBool. ^value! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:49'! unsignedLongAt: index bigEndian: aBool "Return a 32bit unsigned integer quantity starting from the given byte index" | b0 b1 b2 w | aBool ifTrue:[ b0 _ self at: index. b1 _ self at: index+1. b2 _ self at: index+2. w _ self at: index+3. ] ifFalse:[ w _ self at: index. b2 _ self at: index+1. b1 _ self at: index+2. b0 _ self at: index+3. ]. "Minimize LargeInteger arithmetic" b2 = 0 ifFalse:[w _ (b2 bitShift: 8) + w]. b1 = 0 ifFalse:[w _ (b1 bitShift: 16) + w]. b0 = 0 ifFalse:[w _ (b0 bitShift: 24) + w]. ^w! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:49'! unsignedLongAt: index put: value bigEndian: aBool "Store a 32bit unsigned integer quantity starting from the given byte index" | b0 b1 b2 b3 | b0 _ value bitShift: -24. b1 _ (value bitShift: -16) bitAnd: 255. b2 _ (value bitShift: -8) bitAnd: 255. b3 _ value bitAnd: 255. aBool ifTrue:[ self at: index put: b0. self at: index+1 put: b1. self at: index+2 put: b2. self at: index+3 put: b3. ] ifFalse:[ self at: index put: b3. self at: index+1 put: b2. self at: index+2 put: b1. self at: index+3 put: b0. ]. ^value! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:51'! unsignedShortAt: index bigEndian: aBool "Return a 16 bit unsigned integer quantity starting from the given byte index" ^aBool ifTrue:[((self at: index) bitShift: 8) + (self at: index+1)] ifFalse:[((self at: index+1) bitShift: 8) + (self at: index)].! ! !ByteArray methodsFor: 'platform independent access' stamp: 'ar 11/1/1998 20:53'! unsignedShortAt: index put: value bigEndian: aBool "Store a 16 bit unsigned integer quantity starting from the given byte index" aBool ifTrue:[ self at: index put: (value bitShift: -8). self at: index+1 put: (value bitAnd: 255). ] ifFalse:[ self at: index+1 put: (value bitShift: -8). self at: index put: (value bitAnd: 255). ]. ^value! ! !ByteArray methodsFor: 'converting' stamp: 'sma 5/12/2000 17:35'! asByteArray ^ self! ! !ByteArray methodsFor: 'converting'! asString "Convert to a String with Characters for each byte. Fast code uses primitive that avoids character conversion" ^ (String new: self size) replaceFrom: 1 to: self size with: self! ! !ByteArray methodsFor: 'private' stamp: 'ar 1/28/2000 17:45'! asByteArrayPointer "Return a ByteArray describing a pointer to the contents of the receiver." ^self shouldNotImplement! ! !ByteArray methodsFor: 'private' stamp: 'ar 1/28/2000 17:45'! asExternalPointer "Convert the receiver assuming that it describes a pointer to an object." ^(ExternalAddress new) basicAt: 1 put: (self byteAt: 1); basicAt: 2 put: (self byteAt: 2); basicAt: 3 put: (self byteAt: 3); basicAt: 4 put: (self byteAt: 4); yourself! ! !ByteArray methodsFor: 'private'! defaultElement ^0! ! !ByteArray methodsFor: 'private'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." super replaceFrom: start to: stop with: replacement startingAt: repStart! ! !ByteArray methodsFor: 'comparing' stamp: 'SqR 8/3/2000 13:30'! hash | hash | hash _ 0. 1 to: self size do: [:i | hash _ (hash + (self at: i)) hashMultiply]. ^hash! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:15'! booleanAt: byteOffset "bool is only valid with function declarations" ^self shouldNotImplement! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:15'! booleanAt: byteOffset put: value "bool is only valid with function declarations" ^self shouldNotImplement! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:44'! doubleAt: byteOffset ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:13'! doubleAt: byteOffset put: value ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:13'! floatAt: byteOffset ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:13'! floatAt: byteOffset put: value ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:56'! integerAt: byteOffset put: value size: nBytes signed: aBoolean "Primitive. Store the given value as integer of nBytes size in the receiver. Fail if the value is out of range. Note: This primitive will access memory in the outer space if invoked from ExternalAddress." ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:55'! integerAt: byteOffset size: nBytes signed: aBoolean "Primitive. Return an integer of nBytes size from the receiver. Note: This primitive will access memory in the outer space if invoked from ExternalAddress." ^self primitiveFailed! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:09'! isExternalAddress "Return true if the receiver describes an object in the outside world" ^false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:15'! pointerAt: byteOffset "Return a pointer object stored at the given byte address" | addr | addr _ ExternalAddress new. 1 to: 4 do:[:i| addr basicAt: i put: (self unsignedByteAt: byteOffset+i-1)]. ^addr! ! !ByteArray methodsFor: 'external access' stamp: 'hg 2/28/2000 15:34'! pointerAt: byteOffset put: value "Store a pointer object at the given byte address" value isExternalAddress ifFalse:[^self error:'Only external addresses can be stored']. 1 to: 4 do:[:i| self unsignedByteAt: byteOffset+i-1 put: (value basicAt: i)]. ^value! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:39'! signedByteAt: byteOffset "Return a 8bit signed integer starting at the given byte offset" ^self integerAt: byteOffset size: 1 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:39'! signedByteAt: byteOffset put: value "Store a 8bit signed integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 1 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:53'! signedCharAt: byteOffset ^(self unsignedByteAt: byteOffset) asCharacter! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:54'! signedCharAt: byteOffset put: aCharacter ^self unsignedByteAt: byteOffset put: aCharacter asciiValue! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 15:54'! signedLongAt: byteOffset "Return a 32bit signed integer starting at the given byte offset" ^self integerAt: byteOffset size: 4 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 15:54'! signedLongAt: byteOffset put: value "Store a 32bit signed integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 4 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:16'! signedLongLongAt: byteOffset "This is not yet supported" ^self notYetImplemented! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:17'! signedLongLongAt: byteOffset put: value "This is not yet supported" ^self notYetImplemented! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 15:54'! signedShortAt: byteOffset "Return a 16bit signed integer starting at the given byte offset" ^self integerAt: byteOffset size: 2 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 15:54'! signedShortAt: byteOffset put: value "Store a 16bit signed integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 2 signed: true! ! !ByteArray methodsFor: 'external access' stamp: 'hg 2/28/2000 13:56'! structAt: byteOffset length: length "Return a structure of the given length starting at the indicated byte offset." | value | value _ ByteArray new: length. 1 to: length do:[:i| value unsignedByteAt: i put: (self unsignedByteAt: byteOffset+i-1)]. ^value! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 21:11'! structAt: byteOffset put: value length: length "Store a structure of the given length starting at the indicated byte offset." 1 to: length do:[:i| self unsignedByteAt: byteOffset+i-1 put: (value unsignedByteAt: i)]. ^value! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:40'! unsignedByteAt: byteOffset "Return a 8bit unsigned integer starting at the given byte offset" ^self integerAt: byteOffset size: 1 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:40'! unsignedByteAt: byteOffset put: value "Store a 8bit unsigned integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 1 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:53'! unsignedCharAt: byteOffset ^(self unsignedByteAt: byteOffset) asCharacter! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/28/1999 23:54'! unsignedCharAt: byteOffset put: aCharacter ^self unsignedByteAt: byteOffset put: aCharacter asciiValue! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:23'! unsignedLongAt: byteOffset "Return a 32bit unsigned integer starting at the given byte offset" ^self integerAt: byteOffset size: 4 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 01:23'! unsignedLongAt: byteOffset put: value "Store a 32bit signed integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 4 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:17'! unsignedLongLongAt: byteOffset "This is not yet supported" ^self notYetImplemented! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:17'! unsignedLongLongAt: byteOffset put: value "This is not yet supported" ^self notYetImplemented! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 00:55'! unsignedShortAt: byteOffset "Return a 16bit unsigned integer starting at the given byte offset" ^self integerAt: byteOffset size: 2 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/21/1999 00:56'! unsignedShortAt: byteOffset put: value "Store a 16bit unsigned integer starting at the given byte offset" ^self integerAt: byteOffset put: value size: 2 signed: false! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:16'! voidAt: byteOffset "no accessors for void" ^self shouldNotImplement! ! !ByteArray methodsFor: 'external access' stamp: 'ar 11/29/1999 00:16'! voidAt: byteOffset put: value "no accessors for void" ^self shouldNotImplement! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ByteArray class instanceVariableNames: ''! !ByteArray class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:13'! ccg: cg emitLoadFor: aString from: anInteger on: aStream cg emitLoad: aString asCharPtrFrom: anInteger on: aStream! ! !ByteArray class methodsFor: 'plugin generation' stamp: 'acg 9/19/1999 00:25'! ccg: cg prolog: aBlock expr: aString index: anInteger ^cg ccgLoad: aBlock expr: aString asCharPtrFrom: anInteger andThen: (cg ccgValBlock: 'isBytes')! ! !ByteArray class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:13'! ccgDeclareCForVar: aSymbolOrString ^'char *', aSymbolOrString! ! FlattenEncoder subclass: #ByteEncoder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Postscript Filters'! !ByteEncoder methodsFor: 'accessing' stamp: 'MPW 1/1/1901 01:33'! elementSeparator ^' '.! ! !ByteEncoder methodsFor: 'accessing' stamp: 'MPW 1/1/1901 22:45'! numberDefaultBase ^self class numberDefaultBase. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 15:17'! cr ^target cr. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 00:48'! print:encodedObject ^target write:encodedObject. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 02:18'! space ^target space. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 15:16'! tab ^target tab. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 20:51'! writeArray:aCollection ^self writeArrayedCollection:aCollection. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 20:53'! writeAssocation:anAssociation ^self write:anAssociation key; print:'->'; write:anAssociation value. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 02:31'! writeCollection:aCollection ^self print:aCollection class name; writeCollectionContents:aCollection. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 02:31'! writeCollectionContents:aCollection self print:'( '. super writeCollectionContents:aCollection. self print:')'. ^self. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 22:44'! writeNumber:aNumber ^self writeNumber:aNumber base:self numberDefaultBase. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 00:03'! writeNumber:aNumber base:aBase ^aNumber byteEncode:self base:aBase. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 01:25'! writeObject:anObject ^self print:anObject stringRepresentation. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 00:21'! writeString:aString ^aString encodeDoublingQuoteOn:self.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ByteEncoder class instanceVariableNames: ''! !ByteEncoder class methodsFor: 'configuring' stamp: 'MPW 1/1/1901 01:18'! defaultTarget ^WriteStream on:(String new: 40000).! ! !ByteEncoder class methodsFor: 'configuring' stamp: 'MPW 1/1/1901 00:41'! filterSelector ^#byteEncode:.! ! !ByteEncoder class methodsFor: 'configuring' stamp: 'MPW 1/1/1901 22:46'! numberDefaultBase ^10. ! ! CObjectAccessor subclass: #CArrayAccessor instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-TestPlugins'! !CArrayAccessor commentStamp: '' prior: 0! I am used to simulate the indexed access to arrays during plugin simulation.! !CArrayAccessor methodsFor: 'accessing' stamp: 'ar 10/9/1998 21:56'! at: index ^object at: index + offset + 1! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'ar 10/9/1998 21:56'! at: index put: value ^object at: index + offset + 1 put: value! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'acg 9/19/1999 01:50'! cPtrAsOop offset = 0 ifFalse: [self error: 'offset must be zero']. ^object! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'ar 10/10/1998 16:26'! longAt: index | idx | idx _ (offset + index) // 4 + 1. "Note: This is a special hack for BitBlt." (idx = (object basicSize + 1)) ifTrue:[^0]. ^object basicAt: idx! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'ar 10/10/1998 16:26'! longAt: index put: value ^object basicAt: (offset + index) // 4 + 1 put: value! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'acg 9/19/1999 01:48'! next |val| val _ self at: 0. offset _ offset + 1. ^val! ! !CArrayAccessor methodsFor: 'accessing' stamp: 'acg 9/19/1999 01:46'! size ^object size! ! Object subclass: #CCodeGenerator instanceVariableNames: 'translationDict inlineList constants variables variableDeclarations methods variablesSetCache headerFiles pluginPrefix extraDefs postProcesses isCPP ' classVariableNames: 'UseRightShiftForDivide ' poolDictionaries: '' category: 'VMConstruction-Translation to C'! !CCodeGenerator commentStamp: '' prior: 0! This class oversees the translation of a subset of Smalltalk to C, allowing the comforts of Smalltalk during development and the efficiency and portability of C for the resulting interpreter. Executing Interpreter translate: 'interp.c' doInlining: true. (with single quotes) will cause all the methods of Interpreter, ObjectMemory and BitBltSimulation to be translated to C, and stored in the named file. This file together with the files emitted by InterpreterSupportCode (qv) should be adequate to produce a complete interpreter for the Macintosh environment.! !CCodeGenerator methodsFor: 'public' stamp: 'TPR 3/2/2000 11:22'! addAllClassVarsFor: aClass "Add the class variables for the given class (and its superclasses) to the code base as constants." | allClasses | allClasses _ aClass withAllSuperclasses. allClasses do: [:c | self addClassVarsFor: c]. ! ! !CCodeGenerator methodsFor: 'public' stamp: 'TPR 3/2/2000 11:26'! addClass: aClass "Add the variables and methods of the given class to the code base." | source | self checkClassForNameConflicts: aClass. self addClassVarsFor: aClass. "ikp..." self addPoolVarsFor: aClass. variables addAll: aClass instVarNames. 'Adding Class ' , aClass name , '...' displayProgressAt: Sensor cursorPoint from: 0 to: aClass selectors size during: [:bar | aClass selectors doWithIndex: [:sel :i | bar value: i. source _ aClass sourceCodeAt: sel. self addMethod: ((Compiler new parse: source in: aClass notifying: nil) asTranslationMethodOfClass: self translationMethodClass)]]! ! !CCodeGenerator methodsFor: 'public'! addClassVarsFor: aClass "Add the class variables for the given class to the code base as constants." aClass classPool associationsDo: [:assoc | constants at: assoc key asString put: (TConstantNode new setValue: assoc value)]! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/14/1999 01:08'! addHeaderFile: aString "Add a header file. The argument must be a quoted string!!" headerFiles addLast: aString.! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/3/2001 17:55'! addMethodsForPrimitives: classAndSelectorList | sel aClass source verbose meth | classAndSelectorList do: [:classAndSelector | aClass _ Smalltalk at: (classAndSelector at: 1). self addAllClassVarsFor: aClass. "TPR - should pool vars also be added here?" "find the method in either the class or the metaclass" sel _ classAndSelector at: 2. (aClass includesSelector: sel) ifTrue: [source _ aClass sourceCodeAt: sel] ifFalse: [source _ aClass class sourceCodeAt: sel]. "compile the method source and convert to a suitable translation method " meth _ (Compiler new parse: source in: aClass notifying: nil) asTranslationMethodOfClass: self translationMethodClass. (aClass includesSelector: sel) ifTrue: [meth definingClass: aClass] ifFalse: [meth definingClass: aClass class]. meth primitive > 0 ifTrue:[meth preparePrimitiveName]. "for old-style array accessing: meth covertToZeroBasedArrayReferences." meth replaceSizeMessages. self addMethod: meth]. "method preparation" verbose _ false. self prepareMethods. verbose ifTrue: [self printUnboundCallWarnings. self printUnboundVariableReferenceWarnings. Transcript cr]. "code generation" self doInlining: true. methods do:[:m| "if this method is supposed to be a primitive (rather than a helper routine), add assorted prolog and epilog items" m primitive > 0 ifTrue: [m preparePrimitivePrologue]].! ! !CCodeGenerator methodsFor: 'public'! addPoolVarsFor: aClass "Add the pool variables for the given class to the code base as constants." aClass sharedPools do: [:pool | pool associationsDo: [:assoc | constants at: assoc key asString put: (TConstantNode new setValue: assoc value)]]! ! !CCodeGenerator methodsFor: 'public' stamp: 'ikp 9/26/97 14:48'! codeString "Return a string containing all the C code for the code base. Used for testing." | stream | stream _ ReadWriteStream on: (String new: 1000). self emitCCodeOn: stream doInlining: true doAssertions: true. ^stream contents! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/3/2001 18:10'! codeStringForPrimitives: classAndSelectorList self addMethodsForPrimitives: classAndSelectorList. ^self generateCodeStringForPrimitives! ! !CCodeGenerator methodsFor: 'public' stamp: 'TPR 5/23/2000 17:10'! declareModuleName: nameString local: bool "add the declaration of a module name, version and local/external tag" self var: #moduleName declareC:'const char *moduleName = "', nameString, (bool ifTrue:[' (i)"'] ifFalse:[' (e)"']) ! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 5/9/2000 12:24'! exportedPrimitiveNames "Return an array of all exported primitives" ^methods select:[:m| m export] thenCollect:[:m| m selector copyWithout: $:]. ! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/3/2001 17:04'! generateCodeStringForPrimitives | s methodList | s _ ReadWriteStream on: (String new: 1000). methodList _ methods asSortedCollection: [:m1 :m2 | m1 selector < m2 selector]. self emitCHeaderForPrimitivesOn: s. self emitCVariablesOn: s. self emitCFunctionPrototypes: methodList on: s. methodList do: [:m | m emitCCodeOn: s generator: self]. ^ s contents ! ! !CCodeGenerator methodsFor: 'public'! globalsAsSet "Used by the inliner to avoid name clashes with global variables." ((variablesSetCache == nil) or: [variablesSetCache size ~= variables size]) ifTrue: [ variablesSetCache _ variables asSet. ]. ^ variablesSetCache! ! !CCodeGenerator methodsFor: 'public' stamp: 'RMF 3/27/2000 09:53'! initialize translationDict _ Dictionary new. inlineList _ Array new. constants _ Dictionary new: 100. variables _ OrderedCollection new: 100. variableDeclarations _ Dictionary new: 100. methods _ Dictionary new: 500. self initializeCTranslationDictionary. headerFiles _ OrderedCollection new. isCPP _ false! ! !CCodeGenerator methodsFor: 'public' stamp: 'sma 4/22/2000 12:33'! isCPP: aBoolean isCPP _ aBoolean! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/21/2000 14:58'! isTranslatingLocally "Return true if the CG has been setup to translate a plugin locally." ^pluginPrefix notNil! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/21/2000 14:58'! pluginPrefix "Return the plugin prefix when generating local plugins. Local plugins are plugins compiled with the main interpreter source but are not included (nor inlined into) interp.c" ^pluginPrefix! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 2/21/2000 14:58'! pluginPrefix: aString "Set the plugin prefix when generating local plugins. Local plugins are plugins compiled with the main interpreter source but are not included (nor inlined into) interp.c" pluginPrefix _ aString.! ! !CCodeGenerator methodsFor: 'public' stamp: 'ikp 9/26/97 14:50'! storeCodeOnFile: fileName doInlining: inlineFlag "Store C code for this code base on the given file." self storeCodeOnFile: fileName doInlining: inlineFlag doAssertions: true! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 5/9/2000 14:53'! storeCodeOnFile: fileName doInlining: inlineFlag doAssertions: assertionFlag "Store C code for this code base on the given file." | stream realName | "(self isTranslatingLocally and:[(fileName beginsWith: 'sq') not]) ifTrue:[realName _ 'sq', fileName] ifFalse:[realName _ fileName]." stream _ CrLfFileStream newFileNamed: fileName. stream ifNil: [Error signal: 'Could not open C code file: ', realName]. self emitCCodeOn: stream doInlining: inlineFlag doAssertions: assertionFlag. stream close! ! !CCodeGenerator methodsFor: 'public' stamp: 'ar 3/10/2000 17:58'! var: varName declareC: declarationString "Record the given C declaration for a global variable." variableDeclarations at: varName asString put: declarationString.! ! !CCodeGenerator methodsFor: 'public' stamp: 'sma 3/3/2000 12:01'! var: varName type: type self var: varName declareC: type , ' ' , varName! ! !CCodeGenerator methodsFor: 'public' stamp: 'sma 3/3/2000 12:00'! var: varName type: type array: array self var: varName declareC: (String streamContents: [:s | s nextPutAll: type. s space. s nextPutAll: varName. s nextPutAll: '[] = {'. self printArray: array on: s. s nextPut: $}])! ! !CCodeGenerator methodsFor: 'error notification'! checkClassForNameConflicts: aClass "Verify that the given class does not have constant, variable, or method names that conflict with those of previously added classes. Raise an error if a conflict is found, otherwise just return." "check for constant name collisions" aClass classPool associationsDo: [ :assoc | (constants includesKey: assoc key asString) ifTrue: [ self error: 'Constant was defined in a previously added class: ', assoc key. ]. ]. "ikp..." aClass sharedPools do: [:pool | pool associationsDo: [ :assoc | (constants includesKey: assoc key asString) ifTrue: [ self error: 'Constant was defined in a previously added class: ', assoc key. ]. ]. ]. "check for instance variable name collisions" aClass instVarNames do: [ :varName | (variables includes: varName) ifTrue: [ self error: 'Instance variable was defined in a previously added class: ', varName. ]. ]. "check for method name collisions" aClass selectors do: [ :sel | (methods includesKey: sel) ifTrue: [ self error: 'Method was defined in a previously added class: ', sel. ]. ].! ! !CCodeGenerator methodsFor: 'error notification'! printUnboundCallWarnings "Print a warning message for every unbound method call in the code base." | knownSelectors undefinedCalls | undefinedCalls _ Dictionary new. knownSelectors _ translationDict keys asSet. knownSelectors add: #error:. methods do: [ :m | knownSelectors add: m selector ]. methods do: [ :m | m allCalls do: [ :sel | (knownSelectors includes: sel) ifFalse: [ (undefinedCalls includesKey: sel) ifTrue: [ (undefinedCalls at: sel) add: m selector ] ifFalse: [ undefinedCalls at: sel put: (OrderedCollection with: m selector) ]. ]. ]. ]. Transcript cr. undefinedCalls keys asSortedCollection do: [ :undefined | Transcript show: undefined, ' -- undefined method sent by:'; cr. (undefinedCalls at: undefined) do: [ :caller | Transcript tab; show: caller; cr. ]. ].! ! !CCodeGenerator methodsFor: 'error notification'! printUnboundVariableReferenceWarnings "Print a warning message for every unbound variable reference in the code base." | undefinedRefs globalVars knownVars | undefinedRefs _ Dictionary new. globalVars _ Set new: 100. globalVars addAll: variables. methods do: [ :m | knownVars _ globalVars copy. m args do: [ :var | knownVars add: var ]. m locals do: [ :var | knownVars add: var ]. m freeVariableReferences do: [ :varName | (knownVars includes: varName) ifFalse: [ (undefinedRefs includesKey: varName) ifTrue: [ (undefinedRefs at: varName) add: m selector ] ifFalse: [ undefinedRefs at: varName put: (OrderedCollection with: m selector) ]. ]. ]. ]. Transcript cr. undefinedRefs keys asSortedCollection do: [ :var | Transcript show: var, ' -- undefined variable used in:'; cr. (undefinedRefs at: var) do: [ :sel | Transcript tab; show: sel; cr. ]. ].! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'ls 10/10/1999 13:56'! collectInlineList "Make a list of methods that should be inlined." "Details: The method must not include any inline C, since the translator cannot currently map variable names in inlined C code. Methods to be inlined must be small or called from only one place." | methodsNotToInline callsOf inlineIt hasCCode nodeCount senderCount sel | methodsNotToInline _ Set new: methods size. "build dictionary to record the number of calls to each method" callsOf _ Dictionary new: methods size * 2. methods keys do: [ :s | callsOf at: s put: 0 ]. "For each method, scan its parse tree once to: 1. determine if the method contains C code or declarations 2. determine how many nodes it has 3. increment the sender counts of the methods it calls 4. determine if it includes any C declarations or code" inlineList _ Set new: methods size * 2. methods do: [ :m | inlineIt _ #dontCare. (translationDict includesKey: m selector) ifTrue: [ hasCCode _ true. ] ifFalse: [ hasCCode _ m declarations size > 0. nodeCount _ 0. m parseTree nodesDo: [ :node | node isSend ifTrue: [ sel _ node selector. (sel = #cCode: or: [sel = #cCode:inSmalltalk:]) ifTrue: [ hasCCode _ true ]. senderCount _ callsOf at: sel ifAbsent: [ nil ]. nil = senderCount ifFalse: [ callsOf at: sel put: senderCount + 1. ]. ]. nodeCount _ nodeCount + 1. ]. inlineIt _ m extractInlineDirective. "may be true, false, or #dontCare" ]. (hasCCode or: [inlineIt = false]) ifTrue: [ "don't inline if method has C code and is contains negative inline directive" methodsNotToInline add: m selector. ] ifFalse: [ ((nodeCount < 40) or: [inlineIt = true]) ifTrue: [ "inline if method has no C code and is either small or contains inline directive" inlineList add: m selector. ]. ]. ]. callsOf associationsDo: [ :assoc | ((assoc value = 1) and: [(methodsNotToInline includes: assoc key) not]) ifTrue: [ inlineList add: assoc key. ]. ].! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'jm 5/17/1999 13:05'! doInlining: inlineFlag "Inline the bodies of all methods that are suitable for inlining." "Modified slightly for the translator, since the first level of inlining for the interpret loop must be performed in order that the instruction implementations can easily discover their addresses." | pass progress | inlineFlag ifFalse: [ self inlineDispatchesInMethodNamed: #interpret localizingVars: #(). ^ self]. self collectInlineList. pass _ 0. progress _ true. [progress] whileTrue: [ "repeatedly attempt to inline methods until no further progress is made" progress _ false. ('Inlining pass ', (pass _ pass + 1) printString, '...') displayProgressAt: Sensor cursorPoint from: 0 to: methods size during: [:bar | methods doWithIndex: [:m :i | bar value: i. (m tryToInlineMethodsIn: self) ifTrue: [progress _ true]]]]. 'Inlining bytecodes' displayProgressAt: Sensor cursorPoint from: 1 to: 2 during: [:bar | self inlineDispatchesInMethodNamed: #interpret localizingVars: #(currentBytecode localIP localSP localHomeContext). bar value: 1. self removeMethodsReferingToGlobals: #( currentBytecode localIP localSP localHomeContext) except: #interpret. bar value: 2]. "make receiver on the next line false to generate code for all methods, even those that are inlined or unused" true ifTrue: [ (methods includesKey: #interpret) ifTrue: [ "only prune when generating the interpreter itself" self pruneUnreachableMethods]]. ! ! !CCodeGenerator methodsFor: 'inlining'! inlineDispatchesInMethodNamed: selector localizingVars: varsList "Inline dispatches (case statements) in the method with the given name." | m varString | m _ self methodNamed: selector. m = nil ifFalse: [ m inlineCaseStatementBranchesIn: self localizingVars: varsList. m parseTree nodesDo: [ :n | n isCaseStmt ifTrue: [ n customizeShortCasesForDispatchVar: 'currentBytecode'. ]. ]. ]. variables _ variables asOrderedCollection. varsList do: [ :v | varString _ v asString. variables remove: varString ifAbsent: []. (variableDeclarations includesKey: varString) ifTrue: [ m declarations at: v asString put: (variableDeclarations at: varString). variableDeclarations removeKey: varString. ]. ]. ! ! !CCodeGenerator methodsFor: 'inlining'! mayInline: sel "Answer true if the method with the given selector may be inlined." ^ inlineList includes: sel! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'ls 10/10/1999 13:55'! methodStatsString "Return a string describing the size, # of locals, and # of senders of each method. Note methods that have inline C code or C declarations." | methodsWithCCode sizesOf callsOf hasCCode nodeCount senderCount s calls registers selr m | methodsWithCCode _ Set new: methods size. sizesOf _ Dictionary new: methods size * 2. "selector -> nodeCount" callsOf _ Dictionary new: methods size * 2. "selector -> senderCount" "For each method, scan its parse tree once to: 1. determine if the method contains C code or declarations 2. determine how many nodes it has 3. increment the sender counts of the methods it calls 4. determine if it includes any C declarations or code" methods do: [ :m0 | m _ m0. (translationDict includesKey: m selector) ifTrue: [ hasCCode _ true. ] ifFalse: [ hasCCode _ m declarations size > 0. nodeCount _ 0. m parseTree nodesDo: [ :node | node isSend ifTrue: [ selr _ node selector. selr = #cCode: ifTrue: [ hasCCode _ true ]. senderCount _ callsOf at: selr ifAbsent: [ 0 ]. callsOf at: selr put: senderCount + 1. ]. nodeCount _ nodeCount + 1. ]. ]. hasCCode ifTrue: [ methodsWithCCode add: m selector ]. sizesOf at: m selector put: nodeCount. ]. s _ WriteStream on: (String new: 5000). methods keys asSortedCollection do: [ :sel | m _ methods at: sel. registers _ m locals size + m args size. calls _ callsOf at: sel ifAbsent: [0]. registers > 11 ifTrue: [ s nextPutAll: sel; tab. s nextPutAll: (sizesOf at: sel) printString; tab. s nextPutAll: calls printString; tab. s nextPutAll: registers printString; tab. (methodsWithCCode includes: sel) ifTrue: [ s nextPutAll: 'CCode' ]. s cr. ]. ]. ^ s contents! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'ar 2/3/2001 17:08'! pruneMethods: selectorList "Explicitly prune some methods" selectorList do:[:sel| methods removeKey: sel ifAbsent:[]].! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'ikp 10/27/2000 15:02'! pruneUnreachableMethods "Remove any methods that are not reachable. Retain methods needed by the BitBlt operation table, primitives, plug-ins, or interpreter support code." | retain | "Build a set of selectors for methods that should be output even though they have no apparent callers. Some of these are stored in tables for indirect lookup, some are called by the C support code or by primitives." retain _ BitBltSimulation opTable asSet. #(checkedLongAt: fullDisplayUpdate interpret printCallStack readImageFromFile:HeapSize:StartingAt: success: "Windows needs the following two for startup and debug" readableFormat: getCurrentBytecode "Jitter reuses all of these" allocateChunk: characterForAscii: findClassOfMethod:forReceiver: findSelectorOfMethod:forReceiver: firstAccessibleObject loadInitialContext noteAsRoot:headerLoc: nullCompilerHook primitiveFloatAdd primitiveFloatDivide primitiveFloatMultiply primitiveFloatSubtract primitiveFlushExternalPrimitives setCompilerInitialized: splObj:) do: [:sel | retain add: sel]. InterpreterProxy organization categories do: [:cat | ((cat ~= 'initialize') and: [cat ~= 'private']) ifTrue: [ retain addAll: (InterpreterProxy organization listAtCategoryNamed: cat)]]. "Remove all the unreachable methods that aren't retained for the reasons above." self unreachableMethods do: [:sel | (retain includes: sel) ifFalse: [ methods removeKey: sel ifAbsent: []]]. ! ! !CCodeGenerator methodsFor: 'inlining' stamp: 'ikp 9/26/97 14:50'! removeAssertions "Remove all assertions in method bodies. This is for the benefit of inlining, which fails to recognise and disregard empty method bodies when checking the inlinability of sends." | newMethods | newMethods _ Dictionary new. 'Removing assertions...' displayProgressAt: Sensor cursorPoint from: 0 to: methods size during: [ :bar | methods doWithIndex: [ :m :i | bar value: i. m isAssertion ifFalse: [ newMethods at: m selector put: m. m removeAssertions]]]. methods _ newMethods.! ! !CCodeGenerator methodsFor: 'inlining'! removeMethodsReferingToGlobals: varList except: methodName "Remove any methods (presumably inlined) that still contain references to the given obsolete global variables." | varListAsStrings removeIt mVars | varListAsStrings _ varList collect: [ :sym | sym asString ]. methods keys copy do: [ :sel | removeIt _ false. mVars _ (self methodNamed: sel) freeVariableReferences asSet. varListAsStrings do: [ :v | (mVars includes: v) ifTrue: [ removeIt _ true ]. ]. (removeIt and: [sel ~= methodName]) ifTrue: [ methods removeKey: sel ifAbsent: []. ]. ].! ! !CCodeGenerator methodsFor: 'utilities'! addMethod: aTMethod "Add the given method to the code base." (methods includesKey: aTMethod selector) ifTrue: [ self error: 'Method name conflict: ', aTMethod selector. ]. methods at: aTMethod selector put: aTMethod.! ! !CCodeGenerator methodsFor: 'utilities'! builtin: sel "Answer true if the given selector is one of the builtin selectors." ((sel = #longAt:) or: [(sel = #longAt:put:) or: [sel = #error:]]) ifTrue: [ ^true ]. ((sel = #byteAt:) or: [sel = #byteAt:put:]) ifTrue: [ ^true ]. ^translationDict includesKey: sel! ! !CCodeGenerator methodsFor: 'utilities'! cCodeForMethod: selector "Answer a string containing the C code for the given method." "Example: ((CCodeGenerator new initialize addClass: TestCClass1; prepareMethods) cCodeForMethod: #ifTests)" | m s | m _ self methodNamed: selector. m = nil ifTrue: [ self error: 'method not found in code base: ', selector ]. s _ (ReadWriteStream on: ''). m emitCCodeOn: s generator: self. ^ s contents! ! !CCodeGenerator methodsFor: 'utilities'! emitBuiltinConstructFor: msgNode on: aStream level: level "If the given selector is in the translation dictionary, translate it into a target code construct and return true. Otherwise, do nothing and return false." | action | action _ translationDict at: msgNode selector ifAbsent: [ ^false ]. self perform: action with: msgNode with: aStream with: level. ^true! ! !CCodeGenerator methodsFor: 'utilities' stamp: 'ar 10/7/1998 17:53'! isGeneratingPluginCode ^false! ! !CCodeGenerator methodsFor: 'utilities'! methodNamed: selector "Answer the method in the code base with the given selector." ^ methods at: selector ifAbsent: [ nil ]! ! !CCodeGenerator methodsFor: 'utilities'! methodsReferringToGlobal: v "Return a collection of methods that refer to the given global variable." | out | out _ OrderedCollection new. methods associationsDo: [ :assoc | (assoc value freeVariableReferences includes: v) ifTrue: [ out add: assoc key. ]. ]. ^ out! ! !CCodeGenerator methodsFor: 'utilities'! methodsThatCanInvoke: aSelectorList "Return a set of methods that can invoke one of the given selectors, either directly or via a sequence of intermediate methods." | out todo sel mSelector | out _ Set new. todo _ aSelectorList copy asOrderedCollection. [todo isEmpty] whileFalse: [ sel _ todo removeFirst. out add: sel. methods do: [ :m | (m allCalls includes: sel) ifTrue: [ mSelector _ m selector. ((out includes: mSelector) or: [todo includes: mSelector]) ifFalse: [ todo add: mSelector. ]. ]. ]. ]. ^ out ! ! !CCodeGenerator methodsFor: 'utilities' stamp: 'jm 11/25/1998 19:02'! nilOrBooleanConstantReceiverOf: sendNode "Answer nil or the boolean constant that is the receiver of the given message send. Used to suppress conditional code when the condition is a translation-time constant." | rcvr val | rcvr _ sendNode receiver. rcvr isConstant ifTrue: [ val _ rcvr value. ((val == true) or: [val == false]) ifTrue: [^ val]]. ^ nil ! ! !CCodeGenerator methodsFor: 'utilities'! prepareMethods "Prepare methods for browsing." | globals | globals _ Set new: 200. globals addAll: variables. methods do: [ :m | (m locals, m args) do: [ :var | (globals includes: var) ifTrue: [ self error: 'Local variable name may mask global when inlining: ', var. ]. (methods includesKey: var) ifTrue: [ self error: 'Local variable name may mask method when inlining: ', var. ]. ]. m bindClassVariablesIn: constants. m prepareMethodIn: self. ].! ! !CCodeGenerator methodsFor: 'utilities'! reportRecursiveMethods "Report in transcript all methods that can call themselves directly or indirectly or via a chain of N intermediate methods." | visited calls newCalls sel called | methods do: [: m | visited _ translationDict keys asSet. calls _ m allCalls asOrderedCollection. 5 timesRepeat: [ newCalls _ Set new: 50. [calls isEmpty] whileFalse: [ sel _ calls removeFirst. sel = m selector ifTrue: [ Transcript show: m selector, ' is recursive'; cr. ] ifFalse: [ (visited includes: sel) ifFalse: [ called _ self methodNamed: sel. called = nil ifFalse: [ newCalls addAll: called allCalls ]. ]. visited add: sel. ]. ]. calls _ newCalls asOrderedCollection. ]. ].! ! !CCodeGenerator methodsFor: 'utilities' stamp: 'TPR 3/2/2000 11:45'! translationMethodClass "return the class used to produce C translation methods from MethodNodes" ^TMethod! ! !CCodeGenerator methodsFor: 'utilities' stamp: 'ar 7/17/1999 15:06'! unreachableMethods "Return a collection of methods that are never invoked." | sent out | sent _ Set new. methods do: [ :m | m export ifTrue:[sent add: m selector]. sent addAll: m allCalls. ]. out _ OrderedCollection new. methods keys do: [ :sel | (sent includes: sel) ifFalse: [ out add: sel ]. ]. ^ out! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'ar 2/21/2000 14:58'! cFunctionNameFor: aSelector "Create a C function name from the given selector by omitting colons and prefixing with the plugin name if the method is exported." | meth | pluginPrefix == nil ifTrue:[^aSelector copyWithout: $:]. meth _ methods at: aSelector ifAbsent:[nil]. (meth notNil and:[meth export]) ifTrue:[^pluginPrefix,'_', (aSelector copyWithout: $:)] ifFalse:[^aSelector copyWithout: $:].! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'ar 11/19/1999 14:44'! cLiteralFor: anObject "Return a string representing the C literal value for the given object." (anObject isKindOf: Integer) ifTrue: [ (anObject < 16r7FFFFFFF) ifTrue: [^ anObject printString] ifFalse: [^ anObject printString , 'U']]. (anObject isKindOf: String) ifTrue: [^ '"', anObject, '"' ]. (anObject isKindOf: Float) ifTrue: [^ anObject printString ]. anObject == nil ifTrue: [^ 'null' ]. anObject == true ifTrue: [^ '1' ]. "ikp" anObject == false ifTrue: [^ '0' ]. "ikp" (anObject isKindOf: Character) ifTrue:[^anObject asString printString]. "ar" self error: "ikp" 'Warning: A Smalltalk literal could not be translated into a C constant: ', anObject printString. ^'"XXX UNTRANSLATABLE CONSTANT XXX"'! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'ar 2/21/2000 19:53'! emitCCodeOn: aStream doInlining: inlineFlag doAssertions: assertionFlag "Emit C code for all methods in the code base onto the given stream. All inlined method calls should already have been expanded." | verbose methodList | "method preparation" verbose _ false. self prepareMethods. verbose ifTrue: [ self printUnboundCallWarnings. self printUnboundVariableReferenceWarnings. Transcript cr. ]. assertionFlag ifFalse: [ self removeAssertions ]. self doInlining: inlineFlag. "code generation" methodList _ methods asSortedCollection: [ :m1 :m2 | m1 selector < m2 selector ]. self emitCHeaderOn: aStream. self emitCVariablesOn: aStream. self emitCFunctionPrototypes: methodList on: aStream. 'Writing Translated Code...' displayProgressAt: Sensor cursorPoint from: 0 to: methods size during: [:bar | methodList doWithIndex: [ :m :i | bar value: i. m emitCCodeOn: aStream generator: self. ]].! ! !CCodeGenerator methodsFor: 'C code generator'! emitCExpression: aParseNode on: aStream "Emit C code for the expression described by the given parse node." aParseNode isLeaf ifTrue: [ "omit parens" aParseNode emitCCodeOn: aStream level: 0 generator: self. ] ifFalse: [ aStream nextPut: $(. aParseNode emitCCodeOn: aStream level: 0 generator: self. aStream nextPut: $). ].! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'ar 5/9/2000 11:58'! emitCFunctionPrototypes: methodList on: aStream "Store prototype declarations for all non-inlined methods on the given stream." | exporting | aStream nextPutAll: '/*** Function Prototypes ***/'; cr. isCPP ifTrue: [aStream nextPutAll: 'extern "C" {'; cr]. exporting _ false. methodList do: [:m | m export ifTrue: [exporting ifFalse: [aStream nextPutAll: '#pragma export on'; cr. exporting _ true]] ifFalse: [exporting ifTrue: [aStream nextPutAll: '#pragma export off'; cr. exporting _ false]]. m emitCFunctionPrototype: aStream generator: self. aStream nextPutAll: ';'; cr]. exporting ifTrue: [aStream nextPutAll: '#pragma export off'; cr]. isCPP ifTrue: [aStream nextPutAll: '}'; cr]! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'TPR 4/10/2000 10:56'! emitCHeaderForPrimitivesOn: aStream "Write a C file header for compiled primitives onto the given stream." aStream nextPutAll: '/* Automatically generated from Squeak on '. aStream nextPutAll: Time dateAndTimeNow printString. aStream nextPutAll: ' */'; cr; cr. aStream nextPutAll: '#include "sq.h"'; cr; cr. "Additional header files" headerFiles do:[:hdr| aStream nextPutAll:'#include '; nextPutAll: hdr; cr]. aStream nextPutAll: ' /* Memory Access Macros */ #define byteAt(i) (*((unsigned char *) (i))) #define byteAtput(i, val) (*((unsigned char *) (i)) = val) #define longAt(i) (*((int *) (i))) #define longAtput(i, val) (*((int *) (i)) = val) /*** Imported Functions/Variables ***/ extern int stackValue(int); extern int stackIntegerValue(int); extern int successFlag; /* allows accessing Strings in both C and Smalltalk */ #define asciiValue(c) c '. aStream cr.! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'jm 5/17/1999 13:01'! emitCHeaderOn: aStream "Write a C file header onto the given stream." aStream nextPutAll: '/* Automatically generated from Squeak on '. aStream nextPutAll: Time dateAndTimeNow printString. aStream nextPutAll: ' */'; cr; cr. aStream nextPutAll: '#include "sq.h"'; cr. "Additional header files" headerFiles do:[:hdr| aStream nextPutAll:'#include '; nextPutAll: hdr; cr]. aStream nextPutAll: ' /* memory access macros */ #define byteAt(i) (*((unsigned char *) (i))) #define byteAtput(i, val) (*((unsigned char *) (i)) = val) #define longAt(i) (*((int *) (i))) #define longAtput(i, val) (*((int *) (i)) = val) int printCallStack(void); void error(char *s); void error(char *s) { /* Print an error message and exit. */ static int printingStack = false; printf("\n%s\n\n", s); if (!!printingStack) { /* flag prevents recursive error when trying to print a broken stack */ printingStack = true; printCallStack(); } exit(-1); } '. aStream cr.! ! !CCodeGenerator methodsFor: 'C code generator'! emitCTestBlock: aBlockNode on: aStream "Emit C code for the given block node to be used as a loop test." aBlockNode statements size > 1 ifTrue: [ aBlockNode emitCCodeOn: aStream level: 0 generator: self. ] ifFalse: [ aBlockNode statements first emitCCodeOn: aStream level: 0 generator: self. ].! ! !CCodeGenerator methodsFor: 'C code generator' stamp: 'ar 5/9/2000 14:50'! emitCVariablesOn: aStream "Store the global variable declarations on the given stream." | varString | aStream nextPutAll: '/*** Variables ***/'; cr. variables asSortedCollection do: [ :var | (self isGeneratingPluginCode and:[self isTranslatingLocally]) ifTrue:[aStream nextPutAll:'static ']. varString _ var asString. (variableDeclarations includesKey: varString) ifTrue: [ aStream nextPutAll: (variableDeclarations at: varString), ';'; cr. ] ifFalse: [ "default variable declaration" aStream nextPutAll: 'int ', varString, ';'; cr. ]. ]. aStream cr.! ! !CCodeGenerator methodsFor: 'C translation'! generateAnd: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' && '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'ar 10/3/1998 13:45'! generateAsFloat: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll:'((double) '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' )'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'ar 10/3/1998 13:45'! generateAsInteger: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll:'((int) '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' )'.! ! !CCodeGenerator methodsFor: 'C translation'! generateAt: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: '['. msgNode args first emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: ']'.! ! !CCodeGenerator methodsFor: 'C translation'! generateAtPut: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: '['. msgNode args first emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: '] = '. self emitCExpression: msgNode args last on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateBitAnd: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' & '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateBitInvert32: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '~'. self emitCExpression: msgNode receiver on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateBitOr: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' | '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateBitShift: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | arg rcvr | arg _ msgNode args first. rcvr _ msgNode receiver. arg isConstant ifTrue: [ "bit shift amount is a constant" aStream nextPutAll: '((unsigned) '. self emitCExpression: rcvr on: aStream. arg value < 0 ifTrue: [ aStream nextPutAll: ' >> ', arg value negated printString. ] ifFalse: [ aStream nextPutAll: ' << ', arg value printString. ]. aStream nextPutAll: ')'. ] ifFalse: [ "bit shift amount is an expression" aStream nextPutAll: '(('. self emitCExpression: arg on: aStream. aStream nextPutAll: ' < 0) ? ((unsigned) '. self emitCExpression: rcvr on: aStream. aStream nextPutAll: ' >> -'. self emitCExpression: arg on: aStream. aStream nextPutAll: ') : ((unsigned) '. self emitCExpression: rcvr on: aStream. aStream nextPutAll: ' << '. self emitCExpression: arg on: aStream. aStream nextPutAll: '))'. ].! ! !CCodeGenerator methodsFor: 'C translation'! generateBitXor: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' ^ '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateCCoercion: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '(('. aStream nextPutAll: msgNode args last value. aStream nextPutAll: ') '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ')'. ! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'len 2/13/1999 06:33'! generateDivide: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | rcvr arg divisor | rcvr _ msgNode receiver. arg _ msgNode args first. (arg isConstant and: [UseRightShiftForDivide and: [(divisor _ arg value) isInteger and: [divisor isPowerOfTwo and: [divisor > 0 and: [divisor <= (1 bitShift: 31)]]]]]) ifTrue: [ "use signed (arithmetic) right shift instead of divide" aStream nextPutAll: '((int) '. self emitCExpression: rcvr on: aStream. aStream nextPutAll: ' >> ', (divisor log: 2) asInteger printString. aStream nextPutAll: ')'. ] ifFalse: [ self emitCExpression: rcvr on: aStream. aStream nextPutAll: ' / '. self emitCExpression: arg on: aStream]. ! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:40'! generateDoWhileFalse: msgNode on: aStream indent: level "Generate do {stmtList} while(!!(cond))" | stmts testStmt | stmts _ msgNode receiver statements asOrderedCollection. testStmt _ stmts removeLast. msgNode receiver setStatements: stmts. aStream nextPutAll: 'do {'; cr. msgNode receiver emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '} while(!!('. testStmt emitCCodeOn: aStream level: 0 generator: self. aStream nextPutAll: '))'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:39'! generateDoWhileTrue: msgNode on: aStream indent: level "Generate do {stmtList} while(cond)" | stmts testStmt | stmts _ msgNode receiver statements asOrderedCollection. testStmt _ stmts removeLast. msgNode receiver setStatements: stmts. aStream nextPutAll: 'do {'; cr. msgNode receiver emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '} while('. testStmt emitCCodeOn: aStream level: 0 generator: self. aStream nextPutAll: ')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateEqual: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' == '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateGreaterThan: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' > '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateGreaterThanOrEqual: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' >= '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'jm 11/25/1998 19:04'! generateIfFalse: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." "Note: PP 2.3 compiler produces two arguments for ifFalse:, presumably to help with inlining later. Taking the last agument should do the correct thing even if your compiler is different." | const | const _ self nilOrBooleanConstantReceiverOf: msgNode. const ifNotNil: [ const ifFalse: [msgNode args first emitCCodeOn: aStream level: level generator: self]. ^ self]. aStream nextPutAll: 'if (!!('. msgNode receiver emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: ')) {'; cr. msgNode args last emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'jm 11/25/1998 19:06'! generateIfFalseIfTrue: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." "Note: PP 2.3 compiler reverses the argument blocks for ifFalse:ifTrue:, presumably to help with inlining later. That is, the first argument is the block to be evaluated if the condition is true. Squeak's compiler does not reverse the blocks, but you may need to fix this method if you wish to cross-compile using VisualWorks." | const | const _ self nilOrBooleanConstantReceiverOf: msgNode. const ifNotNil: [ const ifTrue: [msgNode args last emitCCodeOn: aStream level: level generator: self] ifFalse: [msgNode args first emitCCodeOn: aStream level: level generator: self]. ^ self]. aStream nextPutAll: 'if ('. msgNode receiver emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: ') {'; cr. msgNode args first emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '} else {'; cr. msgNode args last emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'jm 11/25/1998 19:04'! generateIfTrue: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | const | const _ self nilOrBooleanConstantReceiverOf: msgNode. const ifNotNil: [ const ifTrue: [msgNode args first emitCCodeOn: aStream level: level generator: self]. ^ self]. aStream nextPutAll: 'if ('. msgNode receiver emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: ') {'; cr. msgNode args first emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'jm 11/25/1998 19:04'! generateIfTrueIfFalse: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | const | const _ self nilOrBooleanConstantReceiverOf: msgNode. const ifNotNil: [ const ifTrue: [msgNode args first emitCCodeOn: aStream level: level generator: self] ifFalse: [msgNode args last emitCCodeOn: aStream level: level generator: self]. ^ self]. aStream nextPutAll: 'if ('. msgNode receiver emitCCodeOn: aStream level: level generator: self. aStream nextPutAll: ') {'; cr. msgNode args first emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '} else {'; cr. msgNode args last emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation'! generateInlineCCode: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: msgNode args first value.! ! !CCodeGenerator methodsFor: 'C translation'! generateInlineDirective: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '/* inline: '. aStream nextPutAll: msgNode args first name. aStream nextPutAll: ' */'. ! ! !CCodeGenerator methodsFor: 'C translation'! generateIntegerObjectOf: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '(('. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ' << 1) | 1)'.! ! !CCodeGenerator methodsFor: 'C translation'! generateIntegerValueOf: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '('. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ' >> 1)'.! ! !CCodeGenerator methodsFor: 'C translation'! generateIsIntegerObject: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '('. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ' & 1)'.! ! !CCodeGenerator methodsFor: 'C translation'! generateIsNil: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' == '. aStream nextPutAll: (self cLiteralFor: nil).! ! !CCodeGenerator methodsFor: 'C translation'! generateLessThan: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' < '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateLessThanOrEqual: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' <= '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateMax: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '(('. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' < '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ') ? '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ' : '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateMin: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '(('. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' < '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ') ? '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' : '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: ')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateMinus: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' - '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateModulo: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' % '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateNot: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '!!'. self emitCExpression: msgNode receiver on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateNotEqual: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' !!= '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateNotNil: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' !!= '. aStream nextPutAll: (self cLiteralFor: nil).! ! !CCodeGenerator methodsFor: 'C translation'! generateOr: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' || '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'ar 5/25/2000 16:36'! generatePerform: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: '('. (msgNode args copyFrom: 2 to: msgNode args size) do:[:arg| self emitCExpression: arg on: aStream. ] separatedBy:[aStream nextPutAll:', ']. aStream nextPutAll:')'.! ! !CCodeGenerator methodsFor: 'C translation'! generatePlus: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' + '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generatePreDecrement: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | varNode | varNode _ msgNode receiver. varNode isVariable ifFalse: [ self error: 'preDecrement can only be applied to variables' ]. aStream nextPutAll: '--'. aStream nextPutAll: varNode name. ! ! !CCodeGenerator methodsFor: 'C translation'! generatePreIncrement: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | varNode | varNode _ msgNode receiver. varNode isVariable ifFalse: [ self error: 'preIncrement can only be applied to variables' ]. aStream nextPutAll: '++'. aStream nextPutAll: varNode name. ! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'ar 2/15/1999 21:43'! generateRaisedTo: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll:'pow('. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ','. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll:')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateSequentialAnd: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' && ('. self emitCTestBlock: msgNode args first on: aStream. aStream nextPutAll: ')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateSequentialOr: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." "Note: PP 2.3 compiler produces two arguments for or:, presumably to help with inlining later. Taking the last agument should do the correct thing even if your compiler is different." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' || ('. self emitCTestBlock: msgNode args last on: aStream. aStream nextPutAll: ')'.! ! !CCodeGenerator methodsFor: 'C translation'! generateSharedCodeDirective: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '/* common code: '. aStream nextPutAll: msgNode args first value. aStream nextPutAll: ' */'. ! ! !CCodeGenerator methodsFor: 'C translation'! generateShiftLeft: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' << '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateShiftRight: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." aStream nextPutAll: '((unsigned) '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ')'. aStream nextPutAll: ' >> '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation'! generateTimes: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: ' * '. self emitCExpression: msgNode args first on: aStream.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'len 2/13/1999 07:36'! generateToByDo: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | iterationVar step | (msgNode args last args size = 1) ifFalse: [ self error: 'wrong number of block arguments'. ]. iterationVar _ msgNode args last args first. aStream nextPutAll: 'for (', iterationVar, ' = '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: '; ', iterationVar, (((step _ msgNode args at: 2) isConstant and: [step value > 0]) ifTrue: [' <= '] ifFalse: [' >= ']). self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: '; ', iterationVar, ' += '. self emitCExpression: step on: aStream. aStream nextPutAll: ') {'; cr. msgNode args last emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation'! generateToDo: msgNode on: aStream indent: level "Generate the C code for this message onto the given stream." | iterationVar | (msgNode args last args size = 1) ifFalse: [ self error: 'wrong number of block arguments'. ]. iterationVar _ msgNode args last args first. aStream nextPutAll: 'for (', iterationVar, ' = '. self emitCExpression: msgNode receiver on: aStream. aStream nextPutAll: '; ', iterationVar, ' <= '. self emitCExpression: msgNode args first on: aStream. aStream nextPutAll: '; ', iterationVar, '++) {'; cr. msgNode args last emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:41'! generateWhileFalse: msgNode on: aStream indent: level "Generate C code for a loop in one of the following formats, as appropriate: while(!!(cond)) { stmtList } do {stmtList} while(!!(cond)) while(1) {stmtListA; if (cond) break; stmtListB}" msgNode receiver statements size <= 1 ifTrue: [^self generateWhileFalseLoop: msgNode on: aStream indent: level]. msgNode args first isNilStmtListNode ifTrue: [^self generateDoWhileFalse: msgNode on: aStream indent: level]. ^self generateWhileForeverBreakTrueLoop: msgNode on: aStream indent: level! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:41'! generateWhileFalseLoop: msgNode on: aStream indent: level "Generate while(!!(cond)) {stmtList}." aStream nextPutAll: 'while (!!('. self emitCTestBlock: msgNode receiver on: aStream. aStream nextPutAll: ')) {'; cr. msgNode args first isNilStmtListNode ifFalse: [msgNode args first emitCCodeOn: aStream level: level + 1 generator: self]. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:40'! generateWhileForeverBreakFalseLoop: msgNode on: aStream indent: level "Generate while(1) {stmtListA; if(!!(cond)) break; stmtListB}." | stmts testStmt | stmts _ msgNode receiver statements asOrderedCollection. testStmt _ stmts removeLast. msgNode receiver setStatements: stmts. level - 1 timesRepeat: [ aStream tab ]. aStream nextPutAll: 'while (1) {'; cr. msgNode receiver emitCCodeOn: aStream level: level + 1 generator: self. (level + 1) timesRepeat: [ aStream tab ]. aStream nextPutAll: 'if (!!('. testStmt emitCCodeOn: aStream level: 0 generator: self. aStream nextPutAll: ')) break;'; cr. msgNode args first emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:38'! generateWhileForeverBreakTrueLoop: msgNode on: aStream indent: level "Generate while(1) {stmtListA; if(cond) break; stmtListB}." | stmts testStmt | stmts _ msgNode receiver statements asOrderedCollection. testStmt _ stmts removeLast. msgNode receiver setStatements: stmts. level - 1 timesRepeat: [ aStream tab ]. aStream nextPutAll: 'while (1) {'; cr. msgNode receiver emitCCodeOn: aStream level: level + 1 generator: self. (level + 1) timesRepeat: [ aStream tab ]. aStream nextPutAll: 'if ('. testStmt emitCCodeOn: aStream level: 0 generator: self. aStream nextPutAll: ') break;'; cr. msgNode args first emitCCodeOn: aStream level: level + 1 generator: self. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:41'! generateWhileTrue: msgNode on: aStream indent: level "Generate C code for a loop in one of the following formats, as appropriate: while(cond) { stmtList } do {stmtList} while(cond) while(1) {stmtListA; if (!!(cond)) break; stmtListB}" msgNode receiver statements size <= 1 ifTrue: [^self generateWhileTrueLoop: msgNode on: aStream indent: level]. msgNode args first isNilStmtListNode ifTrue: [^self generateDoWhileTrue: msgNode on: aStream indent: level]. ^self generateWhileForeverBreakFalseLoop: msgNode on: aStream indent: level! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'acg 12/22/1999 01:38'! generateWhileTrueLoop: msgNode on: aStream indent: level "Generate while(cond) {stmtList}." aStream nextPutAll: 'while ('. self emitCTestBlock: msgNode receiver on: aStream. aStream nextPutAll: ') {'; cr. msgNode args first isNilStmtListNode ifFalse: [msgNode args first emitCCodeOn: aStream level: level + 1 generator: self]. level timesRepeat: [ aStream tab ]. aStream nextPutAll: '}'.! ! !CCodeGenerator methodsFor: 'C translation' stamp: 'ar 5/25/2000 16:36'! initializeCTranslationDictionary "Initialize the dictionary mapping message names to actions for C code generation." | pairs | translationDict _ Dictionary new: 200. pairs _ #( #& #generateAnd:on:indent: #| #generateOr:on:indent: #and: #generateSequentialAnd:on:indent: #or: #generateSequentialOr:on:indent: #not #generateNot:on:indent: #+ #generatePlus:on:indent: #- #generateMinus:on:indent: #* #generateTimes:on:indent: #/ #generateDivide:on:indent: #// #generateDivide:on:indent: #\\ #generateModulo:on:indent: #<< #generateShiftLeft:on:indent: #>> #generateShiftRight:on:indent: #min: #generateMin:on:indent: #max: #generateMax:on:indent: #bitAnd: #generateBitAnd:on:indent: #bitOr: #generateBitOr:on:indent: #bitXor: #generateBitXor:on:indent: #bitShift: #generateBitShift:on:indent: #bitInvert32 #generateBitInvert32:on:indent: #< #generateLessThan:on:indent: #<= #generateLessThanOrEqual:on:indent: #= #generateEqual:on:indent: #> #generateGreaterThan:on:indent: #>= #generateGreaterThanOrEqual:on:indent: #~= #generateNotEqual:on:indent: #== #generateEqual:on:indent: #~~ #generateNotEqual:on:indent: #isNil #generateIsNil:on:indent: #notNil #generateNotNil:on:indent: #whileTrue: #generateWhileTrue:on:indent: #whileFalse: #generateWhileFalse:on:indent: #whileTrue #generateDoWhileTrue:on:indent: #whileFalse #generateDoWhileFalse:on:indent: #to:do: #generateToDo:on:indent: #to:by:do: #generateToByDo:on:indent: #ifTrue: #generateIfTrue:on:indent: #ifFalse: #generateIfFalse:on:indent: #ifTrue:ifFalse: #generateIfTrueIfFalse:on:indent: #ifFalse:ifTrue: #generateIfFalseIfTrue:on:indent: #at: #generateAt:on:indent: #at:put: #generateAtPut:on:indent: #basicAt: #generateAt:on:indent: #basicAt:put: #generateAtPut:on:indent: #integerValueOf: #generateIntegerValueOf:on:indent: #integerObjectOf: #generateIntegerObjectOf:on:indent: #isIntegerObject: #generateIsIntegerObject:on:indent: #cCode: #generateInlineCCode:on:indent: #cCode:inSmalltalk: #generateInlineCCode:on:indent: #cCoerce:to: #generateCCoercion:on:indent: #preIncrement #generatePreIncrement:on:indent: #preDecrement #generatePreDecrement:on:indent: #inline: #generateInlineDirective:on:indent: #sharedCodeNamed:inCase: #generateSharedCodeDirective:on:indent: #asFloat #generateAsFloat:on:indent: #asInteger #generateAsInteger:on:indent: #anyMask: #generateBitAnd:on:indent: #raisedTo: #generateRaisedTo:on:indent: #perform: #generatePerform:on:indent: #perform:with: #generatePerform:on:indent: #perform:with:with: #generatePerform:on:indent: #perform:with:with:with: #generatePerform:on:indent: #perform:with:with:with:with: #generatePerform:on:indent: ). 1 to: pairs size by: 2 do: [:i | translationDict at: (pairs at: i) put: (pairs at: i + 1)]. ! ! !CCodeGenerator methodsFor: 'private' stamp: 'sma 3/3/2000 12:08'! printArray: array on: aStream | first | first _ true. 1 to: array size do: [:i | first ifTrue: [first _ false] ifFalse: [aStream nextPutAll: ', ']. i \\ 16 = 1 ifTrue: [aStream cr]. self printInt: (array at: i) on: aStream]! ! !CCodeGenerator methodsFor: 'private' stamp: 'sma 3/3/2000 12:13'! printInt: int on: aStream aStream print: int. (int between: -2147483648 and: 2147483647) ifFalse: [(int between: 2147483648 and: 4294967295) ifTrue: [aStream nextPut: $U] ifFalse: [aStream nextPut: $L]]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CCodeGenerator class instanceVariableNames: ''! !CCodeGenerator class methodsFor: 'removing from system' stamp: 'jm 5/16/1998 10:26'! removeCompilerMethods "Before removing the C code generator classes from the system, use this method to remove the compiler node methods that support it. This avoids leaving dangling references to C code generator classes in the compiler node classes." ParseNode withAllSubclasses do: [ :nodeClass | nodeClass removeCategory: 'C translation'. ]. Smalltalk at: #AbstractSound ifPresent: [:abstractSound | abstractSound class removeCategory: 'primitive generation']. ! ! !CCodeGenerator class methodsFor: 'class initialization' stamp: 'jm 8/19/1998 10:03'! initialize "CCodeGenerator initialize" UseRightShiftForDivide _ true. "If UseRightShiftForDivide is true, the translator will generate a right-shift when it encounters a division by a constant that is a small power of two. For example, 'x / 8' will generate '((int) x >> 3)'. The coercion to int is done to make it clear that the C compiler should generate a signed shift." "Note: The Kernighan and Ritchie 2nd Edition C manual, p. 49, leaves the semantics of right-shifting a negative number open to the discretion of the compiler implementor. However, it strongly suggests that most compilers should generate an arithmetic right shift (i.e., shifting in the sign bit), which is the same as dividing by a power of two. If your compiler does not generate or simulate an arithmetic shift, then make this class variable false and re-translate." ! ! Object subclass: #CObjectAccessor instanceVariableNames: 'object offset ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !CObjectAccessor commentStamp: '' prior: 0! I am used to simulate the indexed access to any object during plugin simulation.! !CObjectAccessor methodsFor: 'accessing' stamp: 'ar 10/9/1998 21:56'! at: index ^object instVarAt: index + offset + 1! ! !CObjectAccessor methodsFor: 'accessing' stamp: 'ar 10/9/1998 21:56'! at: index put: value ^object instVarAt: index + offset + 1 put: value! ! !CObjectAccessor methodsFor: 'pointer arithmetic' stamp: 'ar 10/9/1998 21:57'! + increment ^self clone += increment! ! !CObjectAccessor methodsFor: 'pointer arithmetic' stamp: 'ar 10/9/1998 21:57'! += increment offset _ offset + increment! ! !CObjectAccessor methodsFor: 'pointer arithmetic' stamp: 'ar 10/9/1998 21:58'! - decrement ^self clone -= decrement! ! !CObjectAccessor methodsFor: 'pointer arithmetic' stamp: 'ar 10/9/1998 21:58'! -= decrement offset _ offset - decrement! ! !CObjectAccessor methodsFor: 'printing' stamp: 'ar 9/16/1998 21:38'! printOn: aStream super printOn: aStream. aStream nextPutAll:' on: '; print: object.! ! !CObjectAccessor methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:01'! printOnStream: aStream super printOnStream: aStream. aStream print:' on: '; write: object.! ! !CObjectAccessor methodsFor: 'private' stamp: 'ar 11/3/1998 22:37'! getObject ^object! ! !CObjectAccessor methodsFor: 'private' stamp: 'ar 10/9/1998 21:56'! setObject: anObject object _ anObject. offset _ 0.! ! !CObjectAccessor methodsFor: 'converting' stamp: 'acg 9/20/1999 11:08'! asOop: aClass (aClass ccgCanConvertFrom: object) ifFalse: [^self error: 'incompatible object for autocoercion']. ^object! ! !CObjectAccessor methodsFor: 'converting' stamp: 'ar 11/24/1998 20:51'! asPluggableAccessor: accessorArray ^((CPluggableAccessor on: object) += offset) readBlock: accessorArray first writeBlock: accessorArray last! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CObjectAccessor class instanceVariableNames: ''! !CObjectAccessor class methodsFor: 'instance creation' stamp: 'ar 9/16/1998 21:36'! on: anObject ^self new setObject: anObject! ! CArrayAccessor subclass: #CPluggableAccessor instanceVariableNames: 'readBlock writeBlock ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !CPluggableAccessor methodsFor: 'initialize' stamp: 'ar 11/24/1998 20:51'! readBlock: rBlock writeBlock: wBlock readBlock _ rBlock. writeBlock _ wBlock! ! !CPluggableAccessor methodsFor: 'accessing' stamp: 'ar 11/24/1998 20:45'! at: index ^readBlock value: object value: index + offset + 1! ! !CPluggableAccessor methodsFor: 'accessing' stamp: 'ar 11/24/1998 20:45'! at: index put: value ^writeBlock value: object value: index + offset + 1 value: value! ! HTTPRequest subclass: #CachedHTTPRequest instanceVariableNames: 'cachedName ' classVariableNames: '' poolDictionaries: '' category: 'Framework-Download'! !CachedHTTPRequest methodsFor: 'accessing' stamp: 'ar 12/14/1999 14:53'! cachedName ^cachedName! ! !CachedHTTPRequest methodsFor: 'accessing' stamp: 'ar 12/14/1999 14:53'! cachedName: aString cachedName _ aString.! ! !CachedHTTPRequest methodsFor: 'accessing' stamp: 'ar 12/14/1999 15:00'! startRetrieval | fileStream | cachedName == nil ifTrue:[^super startRetrieval]. (FileDirectory default fileExists: cachedName) ifTrue:[ fileStream _ FileStream concreteStream new open: cachedName forWrite: false. fileStream == nil ifFalse:[^self content: (MIMEDocument contentType: 'text/plain' content: fileStream contentsOfEntireFile)]. FileDirectory default deleteFileNamed: cachedName ifAbsent:[]]. super startRetrieval. "fetch from URL" "and cache in file dir" fileStream _ FileStream concreteStream new open: cachedName forWrite: true. fileStream == nil ifFalse:[ fileStream nextPutAll: (content content). fileStream close].! ! SwikiAction subclass: #CachedSwikiAction instanceVariableNames: 'cacheDirectory cacheURL pwsURL ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !CachedSwikiAction commentStamp: '' prior: 0! CachedSwikiAction caches SwikiAction pages so that they can be served as plain HTML files (no embedded Squeak code) even by a native webServer. You must edit three class methods in CachedSwikiAction to get it to serve appropriately. * CachedSwikiAction class defaultCacheDirectory is where to store cached pages * CachedSwikiAction class defaultCacheURL is the URL to precede cached pages * CachedSwikiAction class defaultPWSURL is where the PWS is that can handle editing and searching. ! ]style[(25 12 201 45 34 39 38 37 61)f1,f1LSwikiAction Comment;,f1,f1LCachedSwikiAction class defaultCacheDirectory;,f1,f1LCachedSwikiAction class defaultCacheURL;,f1,f1LCachedSwikiAction class defaultPWSURL;,f1! !CachedSwikiAction methodsFor: 'save/restore' stamp: 'mjg 3/18/98 12:44'! restore: nameOfSwiki super restore: nameOfSwiki. self source: 'cswiki',(ServerAction pathSeparator). self cacheDirectory: (self class defaultCacheDirectory). self cacheURL: (self class defaultCacheURL). self pwsURL: (self class defaultPWSURL). self generate. ! ! !CachedSwikiAction methodsFor: 'save/restore' stamp: 'mjg 3/23/98 11:35'! restoreNoGen: nameOfSwiki super restore: nameOfSwiki. self source: 'cswiki',(ServerAction pathSeparator). self cacheDirectory: (self class defaultCacheDirectory). self cacheURL: (self class defaultCacheURL). self pwsURL: (self class defaultPWSURL). "self generate." ! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'mjg 9/1/1998 12:44'! browse: pageRef from: request "Just reply with a page in HTML format" | formattedPage | formattedPage _ pageRef copy. "Make a copy, then format the text." formattedPage formatted: (HTMLformatter swikify: pageRef text linkhandler: [:link | urlmap linkFor: link from: request peerName storingTo: OrderedCollection new page: formattedPage]). request reply: (HTMLformatter evalEmbedded: (self fileContents: source ,'page.html') with: formattedPage). ! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'mjg 3/18/98 12:34'! generate 1 to: (urlmap pages size) do: [:ref | self generate: (urlmap atID: ref) from: 'Beginning'.]. self generateRecent. ! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'TPR 7/21/1998 18:14'! generate: pageRef from: request "Just reply with a page in HTML format" | formattedPage peer cacheFile file| (request isKindOf: PWS) ifFalse: [(request isKindOf: String) ifTrue: [peer _ request] ifFalse: [peer _ ' ']] ifTrue: [peer _ request peerName]. formattedPage _ pageRef copy. "Make a copy, then format the text." formattedPage formatted: (HTMLformatter swikify: pageRef text linkhandler: [:link | urlmap linkFor: link from: peer storingTo: OrderedCollection new]). cacheFile _ (self cacheDirectory),(self name),(ServerAction pathSeparator),(pageRef coreID),'.html'. (StandardFileStream isAFileNamed: cacheFile) ifTrue: [FileDirectory deleteFilePath: cacheFile]. file _ FileStream fileNamed: cacheFile. file nextPutAll: (HTMLformatter evalEmbedded: (self fileContents: source ,'page.html') with: formattedPage). file close. ! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'TPR 7/21/1998 18:15'! generateRecent | file | file _ FileStream fileNamed: (self cacheDirectory),(self name),(ServerAction pathSeparator),'recent.html'. file nextPutAll: (HTMLformatter evalEmbedded: (self fileContents: source, 'recent.html') with: urlmap recent). file close.! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'mjg 10/13/1998 12:29'! inputFrom: request "Take user's input and respond with a searchresult or store the edit" | coreRef page theText | coreRef _ request message size < 2 ifTrue: ['1'] ifFalse: [request message at: 2]. coreRef = 'searchresult' ifTrue: [ "If contains search string, do search" request reply: PWS crlf, (HTMLformatter evalEmbedded: (self fileContents: source, 'results.html') with: (urlmap searchFor: ( request fields at: 'searchFor' ifAbsent: ['nothing']))). ^ #return]. (theText _ request fields at: 'text' ifAbsent: [nil]) ifNotNil: [ "It's a response from an edit, so store the page" page _ urlmap atID: coreRef. page user: request peerName. "Address is machine, user only if logged in" page pageStatus = #new ifTrue: [page pageStatus: #standard]. page _ urlmap storeID: coreRef text: theText withSqueakLineEndings from: request peerName. self generate: (urlmap atID: coreRef) from: request. self generateRecent. ^ self]. "return self means do serve the edited page afterwards" request fields keys do: [:aTag | (aTag beginsWith: 'text-') ifTrue: [ urlmap storeID: coreRef text: (request fields at: aTag) withSqueakLineEndings insertAt: (aTag copyFrom: 6 to: aTag size). "string" self generate: (urlmap atID: coreRef) from: request. self generateRecent. ^ self]]. "oops, a new kind!!" Transcript show: 'Unknown data from client. '; show: request fields printString; cr.! ! !CachedSwikiAction methodsFor: 'URL processing' stamp: 'BJP 9/9/1998 21:34'! pageURL: aPage "make the url suited to aPage" ^(self url),(self name),'/',aPage coreID,'.html'! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 11:39'! cacheDirectory ^cacheDirectory! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 11:39'! cacheDirectory: directory cacheDirectory _ directory! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 11:44'! cacheURL ^cacheURL! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 11:44'! cacheURL: urlString cacheURL _ urlString! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 12:44'! pwsURL ^pwsURL ! ! !CachedSwikiAction methodsFor: 'access' stamp: 'mjg 3/18/98 12:44'! pwsURL: urlString pwsURL _ urlString ! ! !CachedSwikiAction methodsFor: 'access' stamp: 'TPR 7/21/1998 18:05'! url ^cacheURL! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CachedSwikiAction class instanceVariableNames: ''! !CachedSwikiAction class methodsFor: 'initialization' stamp: 'tk 5/21/1998 12:58'! setUp: named | newAction | super setUp: named. newAction _ PWS actions at: named. newAction cacheDirectory: (self defaultCacheDirectory). newAction cacheURL: (self defaultCacheURL). newAction source: 'cswiki',(ServerAction pathSeparator). ^ newAction! ! !CachedSwikiAction class methodsFor: 'services' stamp: 'mjg 3/23/98 12:05'! defaultCacheDirectory ^'Guz 7600:WebSTAR 2.0:'! ! !CachedSwikiAction class methodsFor: 'services' stamp: 'mjg 3/23/98 12:05'! defaultCacheURL ^'http://guzdial.cc.gatech.edu/'! ! !CachedSwikiAction class methodsFor: 'services' stamp: 'mjg 3/23/98 12:05'! defaultPWSURL ^'http://guzdial.cc.gatech.edu:8080/'! ! PluggableCanvas subclass: #CachingCanvas instanceVariableNames: 'cacheCanvas mainCanvas ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !CachingCanvas commentStamp: '' prior: 0! A canvas which has a hidden form caching the events. contentsOfArea:into: uses the cache, instead of the main canvas. This is typically used with remote canvases, where querying the bits would involve a network transaction. ! !CachingCanvas methodsFor: 'initialization' stamp: 'ls 4/8/2000 22:35'! mainCanvas: mainCanvas0 mainCanvas := mainCanvas0. cacheCanvas := FormCanvas extent: mainCanvas extent depth: mainCanvas depth.! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'RAA 7/20/2000 13:08'! allocateForm: extentPoint ^cacheCanvas form allocateForm: extentPoint! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/26/2000 13:35'! apply: aBlock aBlock value: cacheCanvas. aBlock value: mainCanvas.! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/27/2000 22:50'! contentsOfArea: area into: aForm ^cacheCanvas contentsOfArea: area into: aForm! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/26/2000 20:21'! form ^cacheCanvas form! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'RAA 7/21/2000 09:54'! showAt: pt invalidRects: rects mainCanvas showAt: pt invalidRects: rects! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CachingCanvas class instanceVariableNames: ''! !CachingCanvas class methodsFor: 'instance creation' stamp: 'ls 3/26/2000 13:37'! on: aCanvas ^super new mainCanvas: aCanvas! ! CodeLoader subclass: #CachingCodeLoader instanceVariableNames: 'cacheDir ' classVariableNames: '' poolDictionaries: '' category: 'Framework-Download'! !CachingCodeLoader methodsFor: 'accessing' stamp: 'mir 12/22/1999 14:10'! cacheDir ^cacheDir! ! !CachingCodeLoader methodsFor: 'accessing' stamp: 'mir 12/22/1999 14:10'! cacheDir: aString cacheDir _ aString.! ! !CachingCodeLoader methodsFor: 'accessing' stamp: 'mir 12/22/1999 14:10'! localCache: stringArray | fd | fd _ FileDirectory default. stringArray do:[:part| (fd directoryNames includes: part) ifFalse:[fd createDirectory: part]. fd _ fd directoryNamed: part]. self cacheDir: (fd pathName copyWith: fd pathNameDelimiter).! ! !CachingCodeLoader methodsFor: 'accessing' stamp: 'mir 12/22/1999 14:10'! localCacheDir: aString self cacheDir: (FileDirectory default pathName, FileDirectory slash, aString, FileDirectory slash)! ! !CachingCodeLoader methodsFor: 'private' stamp: 'mir 12/22/1999 14:11'! createRequestFor: name in: aLoader | request | request _ super createRequestFor: name in: aLoader. request cachedName: cacheDir, name. ^request! ! !CachingCodeLoader methodsFor: 'private' stamp: 'mir 12/22/1999 14:12'! httpRequestClass ^CachedHTTPRequest ! ! Morph subclass: #CachingMorph instanceVariableNames: 'damageRecorder cacheCanvas ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Kernel'! !CachingMorph commentStamp: '' prior: 0! This morph can be used to cache the picture of a morph that takes a long time to draw. It should be used with judgement, however, since heavy use of caching can consume large amounts of memory.! !CachingMorph methodsFor: 'as yet unclassified'! drawOn: aCanvas submorphs isEmpty ifTrue: [^ super drawOn: aCanvas]. ! ! !CachingMorph methodsFor: 'as yet unclassified' stamp: 'ar 5/28/2000 17:12'! fullDrawOn: aCanvas self updateCacheCanvas: aCanvas. aCanvas cache: self fullBounds using: cacheCanvas form during:[:cachingCanvas| super fullDrawOn: cachingCanvas]. ! ! !CachingMorph methodsFor: 'as yet unclassified' stamp: 'ar 5/28/2000 17:12'! imageForm self updateCacheCanvas: Display getCanvas. ^ cacheCanvas form offset: self fullBounds topLeft ! ! !CachingMorph methodsFor: 'as yet unclassified'! initialize super initialize. color _ Color veryLightGray. damageRecorder _ DamageRecorder new. ! ! !CachingMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/12/2000 18:43'! invalidRect: damageRect from: aMorph "Record the given rectangle in the damage list." damageRecorder recordInvalidRect: (damageRect translateBy: self fullBounds origin negated). super invalidRect: damageRect from: aMorph! ! !CachingMorph methodsFor: 'as yet unclassified' stamp: 'jm 11/13/97 16:31'! releaseCachedState super releaseCachedState. cacheCanvas _ nil. ! ! !CachingMorph methodsFor: 'as yet unclassified' stamp: 'ar 5/28/2000 17:12'! updateCacheCanvas: aCanvas "Update the cached image of the morphs being held by this hand." | myBnds rectList | myBnds _ self fullBounds. (cacheCanvas == nil or: [cacheCanvas extent ~= myBnds extent]) ifTrue: [ cacheCanvas _ (aCanvas allocateForm: myBnds extent) getCanvas. cacheCanvas translateBy: myBnds origin negated during:[:tempCanvas| super fullDrawOn: tempCanvas]. ^self]. "incrementally update the cache canvas" rectList _ damageRecorder invalidRectsFullBounds: (0@0 extent: myBnds extent). damageRecorder reset. rectList do: [:r | cacheCanvas translateTo: myBnds origin negated clippingTo: r during:[:c| c fillColor: Color transparent. "clear to transparent" super fullDrawOn: c]]. ! ! FlattenEncoder subclass: #Canvas instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !Canvas commentStamp: '' prior: 0! A canvas is a two-dimensional medium on which morphs are drawn in a device-independent manner. Canvases keep track of the origin and clipping rectangle, as well as the underlying drawing medium (such as a window, pixmap, or postscript script). Subclasses must implement (at least) the following methods: * Drawing: #fillOval:color:borderWidth:borderColor: #frameAndFillRectangle:fillColor:borderWidth:borderColor: #drawPolygon:color:borderWidth:borderColor: #image:at:sourceRect:rule: #stencil:at:sourceRect:rule: #line:to:width:color: #paragraph:bounds:color: #text:bounds:font:color: * Support #clipBy:during: #translateBy:during: #translateBy:clippingTo:during: #transformBy:clippingTo:during: ! !Canvas methodsFor: 'initialization' stamp: 'ar 5/27/2000 21:50'! finish "If there are any pending operations on the receiver complete them. Do not return before all modifications have taken effect." ^self flush! ! !Canvas methodsFor: 'initialization' stamp: 'ar 2/9/1999 06:29'! flush! ! !Canvas methodsFor: 'initialization' stamp: 'di 9/22/1999 19:21'! reset "Reset the canvas." super initWithTarget:self class defaultTarget. ! ! !Canvas methodsFor: 'copying' stamp: 'jm 8/2/97 13:54'! copy ^ self clone ! ! !Canvas methodsFor: 'copying' stamp: 'ls 3/20/2000 21:24'! copyClipRect: newClipRect ^ ClippingCanvas canvas: self clipRect: newClipRect ! ! !Canvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 14:07'! clipRect "Return the currently active clipping rectangle" ^self subclassResponsibility! ! !Canvas methodsFor: 'accessing' stamp: 'ar 2/12/2000 18:17'! contentsOfArea: aRectangle "Return the contents of the given area" ^self contentsOfArea: aRectangle into: (Form extent: aRectangle extent depth: self depth)! ! !Canvas methodsFor: 'accessing' stamp: 'ar 2/12/2000 18:17'! contentsOfArea: aRectangle into: aForm "Return the contents of the given area" ^self subclassResponsibility! ! !Canvas methodsFor: 'accessing'! depth ^ Display depth ! ! !Canvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 14:15'! extent "Return the physical extent of the output device" ^self subclassResponsibility! ! !Canvas methodsFor: 'accessing' stamp: 'jm 6/2/1998 06:39'! form ^ Display ! ! !Canvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 14:11'! origin "Return the current origin for drawing operations" ^self subclassResponsibility! ! !Canvas methodsFor: 'accessing' stamp: 'ar 2/17/2000 01:46'! shadowColor "Return the current override color or nil if no such color exists" ^nil! ! !Canvas methodsFor: 'accessing' stamp: 'ar 2/17/2000 01:46'! shadowColor: aColor "Set a shadow color. If set this color overrides any client-supplied color."! ! !Canvas methodsFor: 'testing' stamp: 'di 8/12/2000 15:04'! doesRoundedCorners ^ true! ! !Canvas methodsFor: 'testing' stamp: 'ar 11/13/1998 13:19'! isBalloonCanvas ^false! ! !Canvas methodsFor: 'testing' stamp: 'ar 6/22/1999 19:03'! isShadowDrawing ^false! ! !Canvas methodsFor: 'testing' stamp: 'ar 6/22/1999 14:10'! isVisible: aRectangle "Return true if the given rectangle is (partially) visible" ^self clipRect intersects: aRectangle ! ! !Canvas methodsFor: 'testing' stamp: 'di 9/24/2000 16:10'! seesNothingOutside: aRectangle "Return true if this canvas will not touch anything outside aRectangle" ^ aRectangle containsRect: self clipRect ! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:18'! fillColor: aColor "Fill the receiver with the given color. Note: This method should be named differently since it is intended to fill the background and thus fills even if the color is transparent" ^self fillRectangle: self clipRect color: (aColor alpha: 1.0).! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:30'! line: pt1 to: pt2 brushForm: brush "Obsolete - will be removed in the future"! ! !Canvas methodsFor: 'drawing' stamp: 'jm 8/2/97 13:54'! line: pt1 to: pt2 color: c self line: pt1 to: pt2 width: 1 color: c. ! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:31'! line: pt1 to: pt2 width: w color: c "Draw a line using the given width and color" ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing' stamp: 'di 9/9/2000 12:51'! line: pt1 to: pt2 width: width color: color1 dashLength: s1 secondColor: color2 secondDashLength: s2 startingOffset: startingOffset "Draw a line using the given width, colors and dash lengths. Originally written by Stephan Rudlof; tweaked by Dan Ingalls to use startingOffset for sliding offset as in 'ants' animations. Returns the sum of the starting offset and the length of this line." | dist deltaBig colors nextPhase segmentOffset phase segmentLength startPoint distDone endPoint segLens | dist _ pt1 dist: pt2. dist = 0 ifTrue: [^ startingOffset]. s1 = 0 & (s2 = 0) ifTrue: [^ startingOffset]. deltaBig _ pt2 - pt1. colors _ {color1. color2}. segLens _ {s1 asFloat. s2 asFloat}. nextPhase _ {2. 1}. "Figure out what phase we are in and how far, given startingOffset." segmentOffset _ startingOffset \\ (s1 + s2). segmentOffset < s1 ifTrue: [phase _ 1. segmentLength _ s1 - segmentOffset] ifFalse: [phase _ 2. segmentLength _ s1 + s2 - segmentOffset]. startPoint _ pt1. distDone _ 0.0. [distDone < dist] whileTrue: [segmentLength _ segmentLength min: dist - distDone. endPoint _ startPoint + (deltaBig * segmentLength / dist). self line: startPoint truncated to: endPoint truncated width: width color: (colors at: phase). distDone _ distDone + segmentLength. phase _ nextPhase at: phase. startPoint _ endPoint. segmentLength _ segLens at: phase]. ^ startingOffset + dist! ! !Canvas methodsFor: 'drawing' stamp: 'sr 4/27/2000 03:31'! line: pt1 to: pt2 width: w1 color: c1 stepWidth: s1 secondWidth: w2 secondColor: c2 secondStepWidth: s2 "Draw a line using the given width, colors and steps; both steps can have different stepWidths (firstStep, secondStep), draw widths and colors." | bigSteps offsetPoint dist p1p2Vec deltaBig delta1 delta2 lastPoint bigStep | s1 = 0 & (s2 = 0) ifTrue: [^ self]. dist _ pt1 dist: pt2. dist = 0 ifTrue: [^ self]. bigStep _ s1 + s2. bigSteps _ dist / bigStep. p1p2Vec _ pt2 - pt1. deltaBig _ p1p2Vec / bigSteps. delta1 _ deltaBig * (s1 / bigStep). delta2 _ deltaBig * (s2 / bigStep). dist <= s1 ifTrue: [self line: pt1 rounded to: pt2 rounded width: w1 color: c1. ^ self]. 0 to: bigSteps truncated - 1 do: [:bigStepIx | self line: (pt1 + (offsetPoint _ deltaBig * bigStepIx)) rounded to: (pt1 + (offsetPoint _ offsetPoint + delta1)) rounded width: w1 color: c1. self line: (pt1 + offsetPoint) rounded to: (pt1 + (offsetPoint + delta2)) rounded width: w2 color: c2]. "if there was no loop, offsetPoint is nil" lastPoint _ pt1 + ((offsetPoint ifNil: [0 @ 0]) + delta2). (lastPoint dist: pt2) <= s1 ifTrue: [self line: lastPoint rounded to: pt2 rounded width: w1 color: c1] ifFalse: [self line: lastPoint rounded to: (lastPoint + delta1) rounded width: w1 color: c1. self line: (lastPoint + delta1) rounded to: pt2 width: w1 color: c2]! ! !Canvas methodsFor: 'drawing' stamp: 'ls 3/19/2000 15:12'! paragraph2: para bounds: bounds color: c | scanner | scanner _ CanvasCharacterScanner new. scanner canvas: self; text: para text textStyle: para textStyle; textColor: c. para displayOn: self using: scanner at: bounds topLeft. ! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:31'! paragraph: paragraph bounds: bounds color: c "Draw the given paragraph" ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:32'! point: p color: c "Obsolete - will be removed in the future"! ! !Canvas methodsFor: 'drawing' stamp: 'ar 2/5/1999 18:28'! render: anObject "Do some 3D operations with the object if possible"! ! !Canvas methodsFor: 'drawing' stamp: 'jm 8/2/97 13:54'! text: s at: pt font: fontOrNil color: c ^ self text: s bounds: (pt extent: 10000@10000) font: fontOrNil color: c ! ! !Canvas methodsFor: 'drawing' stamp: 'ar 6/17/1999 01:32'! text: s bounds: boundsRect font: fontOrNil color: c "Draw the given string in the given font and color clipped to the given rectangle. If the font is nil, the default font is used." ^self subclassResponsibility! ! !Canvas methodsFor: 'private' stamp: 'ar 2/12/2000 18:12'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Note: The public use of this protocol is deprecated. It will become private. Nobody in the outside world must assume that a thing like a combination rule has any specific effect." ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 5/29/1999 05:14'! draw: anObject ^anObject drawOn: self! ! !Canvas methodsFor: 'drawing-general'! drawMorph: aMorph (self isVisible: aMorph bounds) ifTrue:[self draw: aMorph]! ! !Canvas methodsFor: 'drawing-general'! fullDraw: anObject ^anObject fullDrawOn: self! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 10/26/2000 19:45'! fullDrawMorph: aMorph (self isVisible: aMorph fullBounds) ifTrue:[self fullDraw: aMorph].! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 10/26/2000 19:39'! roundCornersOf: aMorph during: aBlock ^aBlock value! ! !Canvas methodsFor: 'drawing-support' stamp: 'ar 2/12/2000 18:05'! cache: aRectangle using: aCache during: aBlock "Cache the execution of aBlock by the given cache. Note: At some point we may want to actually *create* the cache here; for now we're only using it." (aCache notNil and:[(aCache isKindOf: Form) and:[aCache extent = aRectangle extent]]) ifTrue:[^self paintImage: aCache at: aRectangle origin]. aBlock value: self.! ! !Canvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 02:53'! clipBy: aRectangle during: aBlock "Set a clipping rectangle active only during the execution of aBlock. Note: In the future we may want to have more general clip shapes - not just rectangles" ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 01:43'! preserveStateDuring: aBlock "Preserve the full canvas state during the execution of aBlock" ^aBlock value: self copy! ! !Canvas methodsFor: 'drawing-support' stamp: 'di 10/16/1999 16:02'! transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock "Transform the receiver by the given display transformation during the execution of aBlock. The given clip rectangle defines the *global* (e.g., outer) rectangle against which the receiver should clip (which would be equivalent to 'self clipRect: aClipRect; transformBy: aDisplayTransform')." ^ self transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: 1 ! ! !Canvas methodsFor: 'drawing-support' stamp: 'di 10/16/1999 15:56'! transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize "Transform the receiver by the given display transformation during the execution of aBlock. The given clip rectangle defines the *global* (e.g., outer) rectangle against which the receiver should clip (which would be equivalent to 'self clipRect: aClipRect; transformBy: aDisplayTransform')." ^ self subclassResponsibility! ! !Canvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 03:00'! translateBy: delta during: aBlock "Set a translation only during the execution of aBlock." ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 14:08'! translateTo: newOrigin clippingTo: aRectangle during: aBlock "Set a new origin and clipping rectangle only during the execution of aBlock." self translateBy: newOrigin - self origin clippingTo: (aRectangle translateBy: self origin negated) during: aBlock! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 07:32'! fillRectangle: r color: c "Fill the rectangle using the given color" ^self frameAndFillRectangle: r fillColor: c borderWidth: 0 borderColor: Color transparent! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 07:34'! fillRectangle: aRectangle fillStyle: aFillStyle "Fill the given rectangle. Note: The default implementation does not recognize any enhanced fill styles" self fillRectangle: aRectangle color: aFillStyle asColor.! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 07:32'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: borderColor "Draw the rectangle using the given attributes" ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'RAA 8/14/2000 14:22'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth topLeftColor: topLeftColor bottomRightColor: bottomRightColor "Draw the rectangle using the given attributes. Note: This is a *very* simple implementation" | bw pt | self frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: bottomRightColor. bottomRightColor = topLeftColor ifFalse: [ bw _ borderWidth asPoint. pt _ r topLeft + (bw // 2). self line: pt to: pt + ((r extent x - bw x)@0) width: borderWidth color: topLeftColor. self line: pt to: pt + (0@(r extent y - bw y)) width: borderWidth color: topLeftColor. ].! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 07:33'! frameRectangle: r color: c self frameRectangle: r width: 1 color: c. ! ! !Canvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/18/1999 07:33'! frameRectangle: r width: w color: c ^self frameAndFillRectangle: r fillColor: Color transparent borderWidth: w borderColor: c.! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:45'! fillOval: r color: c self fillOval: r color: c borderWidth: 0 borderColor: Color transparent. ! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:45'! fillOval: r color: c borderWidth: borderWidth borderColor: borderColor "Fill the given oval." ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:51'! fillOval: aRectangle fillStyle: aFillStyle "Fill the given oval." ^self fillOval: aRectangle fillStyle: aFillStyle borderWidth: 0 borderColor: Color transparent! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:50'! fillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc "Fill the given oval. Note: The default implementation does not recognize any enhanced fill styles" self fillOval: aRectangle color: aFillStyle asColor borderWidth: bw borderColor: bc! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:45'! frameOval: r color: c self fillOval: r color: Color transparent borderWidth: 1 borderColor: c. ! ! !Canvas methodsFor: 'drawing-ovals' stamp: 'ar 6/18/1999 08:45'! frameOval: r width: w color: c self fillOval: r color: Color transparent borderWidth: w borderColor: c. ! ! !Canvas methodsFor: 'drawing-polygons' stamp: 'ar 6/18/1999 08:56'! drawPolygon: vertices color: aColor borderWidth: bw borderColor: bc "Draw the given polygon." ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-polygons' stamp: 'ar 6/25/1999 12:18'! drawPolygon: vertices fillStyle: aFillStyle "Fill the given polygon." self drawPolygon: vertices fillStyle: aFillStyle borderWidth: 0 borderColor: Color transparent! ! !Canvas methodsFor: 'drawing-polygons' stamp: 'ar 6/18/1999 08:58'! drawPolygon: vertices fillStyle: aFillStyle borderWidth: bw borderColor: bc "Fill the given polygon. Note: The default implementation does not recognize any enhanced fill styles" self drawPolygon: vertices color: aFillStyle asColor borderWidth: bw borderColor: bc! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/16/2000 23:45'! drawImage: aForm at: aPoint "Draw the given Form, which is assumed to be a Form or ColorForm" self drawImage: aForm at: aPoint sourceRect: aForm boundingBox! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/17/2000 01:47'! drawImage: aForm at: aPoint sourceRect: sourceRect "Draw the given form." self shadowColor ifNotNil:[ ^self fillRectangle: ((aForm boundingBox intersect: sourceRect) translateBy: aPoint) color: self shadowColor]. ^self image: aForm at: aPoint sourceRect: sourceRect rule: Form over! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/16/2000 23:48'! paintImage: aForm at: aPoint "Draw the given Form, which is assumed to be a Form or ColorForm following the convention that zero is the transparent pixel value." self paintImage: aForm at: aPoint sourceRect: aForm boundingBox ! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/17/2000 01:48'! paintImage: aForm at: aPoint sourceRect: sourceRect "Draw the given Form, which is assumed to be a Form or ColorForm following the convention that zero is the transparent pixel value." self shadowColor ifNotNil:[ ^self stencil: aForm at: aPoint sourceRect: sourceRect color: self shadowColor]. ^self image: aForm at: aPoint sourceRect: sourceRect rule: Form paint! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 6/25/1999 12:17'! stencil: stencilForm at: aPoint color: aColor "Flood this canvas with aColor wherever stencilForm has non-zero pixels" ^self stencil: stencilForm at: aPoint sourceRect: stencilForm boundingBox color: aColor! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 6/25/1999 12:17'! stencil: stencilForm at: aPoint sourceRect: sourceRect color: aColor "Flood this canvas with aColor wherever stencilForm has non-zero pixels" ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/17/2000 14:05'! translucentImage: aForm at: aPoint "Draw a translucent image using the best available way of representing translucency." self translucentImage: aForm at: aPoint sourceRect: aForm boundingBox! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 2/17/2000 01:48'! translucentImage: aForm at: aPoint sourceRect: sourceRect "Draw a translucent image using the best available way of representing translucency. Note: This will be fixed in the future." self shadowColor ifNotNil:[ ^self stencil: aForm at: aPoint sourceRect: sourceRect color: self shadowColor]. (self depth < 32 or:[aForm depth < 32]) ifTrue:[^self paintImage: aForm at: aPoint sourceRect: sourceRect]. self image: aForm at: aPoint sourceRect: sourceRect rule: Form blend! ! !Canvas methodsFor: 'converting' stamp: 'ar 6/24/1999 17:46'! asShadowDrawingCanvas ^self asShadowDrawingCanvas: (Color black alpha: 0.5).! ! !Canvas methodsFor: 'converting' stamp: 'ar 6/22/1999 18:59'! asShadowDrawingCanvas: aColor ^(ShadowDrawingCanvas on: self) shadowColor: aColor! ! !Canvas methodsFor: 'other'! flushDisplay " Dummy ."! ! !Canvas methodsFor: 'other'! forceToScreen:rect " dummy " ! ! !Canvas methodsFor: 'other'! translateBy:aPoint clippingTo:aRect during:aBlock ^aBlock value:(self copyOffset:aPoint clipRect:aRect).! ! !Canvas methodsFor: 'drawing-obsolete' stamp: 'ar 2/12/2000 18:10'! image: aForm at: aPoint "Note: This protocol is deprecated. Use #paintImage: instead." self image: aForm at: aPoint sourceRect: aForm boundingBox rule: Form paint. ! ! !Canvas methodsFor: 'drawing-obsolete' stamp: 'ar 2/12/2000 18:11'! image: aForm at: aPoint rule: combinationRule "Note: This protocol is deprecated. Use one of the explicit image drawing messages (#paintImage, #drawImage) instead." self image: aForm at: aPoint sourceRect: aForm boundingBox rule: combinationRule. ! ! !Canvas methodsFor: 'drawing-obsolete' stamp: 'ar 2/12/2000 18:11'! imageWithOpaqueWhite: aForm at: aPoint "Note: This protocol is deprecated. Use #drawImage: instead" self image: aForm at: aPoint sourceRect: (0@0 extent: aForm extent) rule: Form over. ! ! !Canvas methodsFor: 'Nebraska/embeddedWorlds' stamp: 'RAA 11/7/2000 13:54'! displayIsFullyUpdated! ! !Canvas methodsFor: 'Nebraska/embeddedWorlds' stamp: 'RAA 12/5/2000 18:28'! transform2By: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize "an attempt to use #displayInterpolatedOn: instead of WarpBlt." | patchRect subCanvas pureRect biggerPatch biggerClip interForm | self flag: #bob. "added to Canvas in hopes it will work for Nebraska" (aDisplayTransform isPureTranslation) ifTrue: [ ^aBlock value: (self copyOffset: aDisplayTransform offset negated truncated clipRect: aClipRect) ]. "Prepare an appropriate warp from patch to aClipRect" pureRect _ (aDisplayTransform globalBoundsToLocal: aClipRect). patchRect _ pureRect rounded. patchRect area = 0 ifTrue: [^self]. "oh, well!!" biggerPatch _ patchRect expandBy: 1. biggerClip _ (aDisplayTransform localBoundsToGlobal: biggerPatch) rounded. "Render the submorphs visible in the clipping rectangle, as patchForm" subCanvas _ FormCanvas extent: biggerPatch extent depth: self depth. self isShadowDrawing ifTrue: [ subCanvas shadowColor: self shadowColor ]. "this biggerPatch/biggerClip is an attempt to improve positioning of the final image in high magnification conditions. Since we cannot grab fractional pixels from the source, take one extra and then take just the part we need from the expanded form" subCanvas translateBy: biggerPatch topLeft negated rounded during: [ :offsetCanvas | aBlock value: offsetCanvas]. interForm _ Form extent: biggerClip extent depth: self depth. subCanvas form displayInterpolatedIn: interForm boundingBox on: interForm. self drawImage: interForm at: aClipRect origin sourceRect: (aClipRect origin - biggerClip origin extent: aClipRect extent) ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Canvas class instanceVariableNames: ''! !Canvas class methodsFor: 'configuring'! filterSelector ^#drawOnCanvas:.! ! CharacterScanner subclass: #CanvasCharacterScanner instanceVariableNames: 'canvas fillBlt foregroundColor runX lineY ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !CanvasCharacterScanner commentStamp: '' prior: 0! A displaying scanner which draws its output to a Morphic canvas.! !CanvasCharacterScanner methodsFor: 'private' stamp: 'ls 9/26/1999 10:03'! doesDisplaying ^false "it doesn't do displaying using copyBits"! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'ls 9/30/1999 12:34'! setFont foregroundColor _ Color black. super setFont. destY _ lineY + line baseline - font ascent! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'ls 9/25/1999 16:24'! textColor: color foregroundColor _ color! ! !CanvasCharacterScanner methodsFor: 'scanning' stamp: 'RAA 7/22/2000 10:06'! displayLine: textLine offset: offset leftInRun: leftInRun | nowLeftInRun done startLoc startIndex stopCondition | "largely copied from DisplayScanner's routine" line _ textLine. foregroundColor ifNil: [ foregroundColor _ Color black ]. leftMargin _ (line leftMarginForAlignment: textStyle alignment) + offset x. rightMargin _ line rightMargin + offset x. lineY _ line top + offset y. lastIndex _ textLine first. leftInRun <= 0 ifTrue: [self setStopConditions. "also sets the font" nowLeftInRun _ text runLengthFor: lastIndex] ifFalse: [nowLeftInRun _ leftInRun]. runX _ destX _ leftMargin. runStopIndex _ lastIndex + (nowLeftInRun - 1) min: line last. spaceCount _ 0. done _ false. [done] whileFalse: [ "remember where this portion of the line starts" startLoc _ destX@destY. startIndex _ lastIndex. "find the end of this portion of the line" stopCondition _ self scanCharactersFrom: lastIndex to: runStopIndex in: text string rightX: rightMargin stopConditions: stopConditions kern: kern "displaying: false". "display that portion of the line" canvas text: (text string copyFrom: startIndex to: lastIndex) bounds: (startLoc corner: 99999@99999) font: font color: foregroundColor. "handle the stop condition" done _ self perform: stopCondition ]. ^runStopIndex - lastIndex! ! !CanvasCharacterScanner methodsFor: 'accessing' stamp: 'ls 9/25/1999 15:59'! canvas: aCanvas "set the canvas to draw on" canvas ifNotNil: [ self inform: 'initializing twice!!' ]. canvas _ aCanvas! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/25/1999 16:07'! cr "When a carriage return is encountered, simply increment the pointer into the paragraph." lastIndex_ lastIndex + 1. ^false! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/25/1999 16:10'! crossedX "This condition will sometimes be reached 'legally' during display, when, for instance the space that caused the line to wrap actually extends over the right boundary. This character is allowed to display, even though it is technically outside or straddling the clipping ectangle since it is in the normal case not visible and is in any case appropriately clipped by the scanner." "self fillLeading." ^ true ! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/25/1999 16:11'! endOfRun "The end of a run in the display case either means that there is actually a change in the style (run code) to be associated with the string or the end of this line has been reached." | runLength | lastIndex = line last ifTrue: [^true]. runX _ destX. runLength _ text runLengthFor: (lastIndex _ lastIndex + 1). runStopIndex _ lastIndex + (runLength - 1) min: line last. self setStopConditions. ^ false! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/29/1999 20:13'! paddedSpace "Each space is a stop condition when the alignment is right justified. Padding must be added to the base width of the space according to which space in the line this space is and according to the amount of space that remained at the end of the line when it was composed." destX _ destX + spaceWidth + (line justifiedPadFor: spaceCount). lastIndex _ lastIndex + 1. ^ false! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/25/1999 16:14'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. stopConditions at: Space asciiValue + 1 put: (textStyle alignment = Justified ifTrue: [#paddedSpace])! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ls 9/25/1999 16:14'! tab destX _ (textStyle alignment == Justified and: [self leadingTab not]) ifTrue: "imbedded tabs in justified text are weird" [destX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount)) max: destX] ifFalse: [textStyle nextTabXFrom: destX leftMargin: leftMargin rightMargin: rightMargin]. lastIndex _ lastIndex + 1. ^ false! ! Object subclass: #CanvasDecoder instanceVariableNames: 'drawingCanvas clipRect transform connection fonts ' classVariableNames: 'CachedForms ' poolDictionaries: '' category: 'Morphic-Remote'! !CanvasDecoder commentStamp: '' prior: 0! Decodes commands encoded by MREncoder, and draws them onto a canvas.! !CanvasDecoder methodsFor: 'initialization' stamp: 'ls 4/9/2000 14:26'! initialize "set the canvas to draw on" drawingCanvas := FormCanvas extent: 100@100 depth: 16. clipRect _ drawingCanvas extent. transform _ MorphicTransform identity. fonts := Array new: 2.! ! !CanvasDecoder methodsFor: 'network' stamp: 'ls 9/26/1999 14:59'! connection: aStringSocket "set this terminal to talk over the given socket" connection _ aStringSocket! ! !CanvasDecoder methodsFor: 'network' stamp: 'ls 3/18/2000 13:38'! processIO | command didSomething | connection ifNil: [ ^self ]. connection processIO. didSomething := false. [ command _ connection nextOrNil. command notNil ] whileTrue: [ didSomething := true. self processCommand: command ]. ^didSomething! ! !CanvasDecoder methodsFor: 'network' stamp: 'ls 3/26/2000 22:16'! processIOOnForce: forceBlock | command didSomething | connection ifNil: [ ^self ]. connection processIO. didSomething := false. [ command _ connection nextOrNil. command notNil ] whileTrue: [ didSomething := true. self processCommand: command onForceDo: forceBlock]. ^didSomething! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 3/27/2000 17:57'! addFontToCache: command | index font | index := self class decodeInteger: command second. font := self class decodeFont: command third. index > fonts size ifTrue: [ | newFonts | newFonts := Array new: index. newFonts replaceFrom: 1 to: fonts size with: fonts. fonts := newFonts ]. fonts at: index put: font! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 11/6/2000 15:40'! drawBalloonOval: command | aRectangle aFillStyle borderWidth borderColor | aRectangle _ self class decodeRectangle: (command at: 2). aFillStyle _ self class decodeFillStyle: (command at: 3). borderWidth _ self class decodeInteger: (command at: 4). borderColor _ self class decodeColor: (command at: 5). self drawCommand: [ :c | c asBalloonCanvas fillOval: aRectangle fillStyle: aFillStyle borderWidth: borderWidth borderColor: borderColor ].! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 7/28/2000 07:55'! drawBalloonRect: command | aRectangle aFillStyle | aRectangle _ self class decodeRectangle: (command at: 2). aFillStyle _ self class decodeFillStyle: (command at: 3). self drawCommand: [ :c | c asBalloonCanvas fillRectangle: aRectangle fillStyle: aFillStyle. ].! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 4/9/2000 14:26'! drawCommand: aBlock "call aBlock with the canvas it should actually draw on so that the clipping rectangle and transform are set correctly" drawingCanvas transformBy: transform clippingTo: clipRect during: aBlock! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 11/1/2000 23:23'! drawImage: command | image point sourceRect rule cacheID cacheNew previousImage | image := self class decodeImage: (command at: 2). point := self class decodePoint: (command at: 3). sourceRect := self class decodeRectangle: (command at: 4). rule := self class decodeInteger: (command at: 5). command size >= 7 ifTrue: [ false ifTrue: [self showSpaceUsed]. "debugging" cacheID _ self class decodeInteger: (command at: 6). cacheNew _ (self class decodeInteger: (command at: 7)) = 1. cacheID > 0 ifTrue: [ CachedForms ifNil: [CachedForms _ Array new: 100]. cacheNew ifTrue: [ CachedForms at: cacheID put: image ] ifFalse: [ previousImage _ CachedForms at: cacheID. image ifNil: [ image _ previousImage ] ifNotNil: [ (previousImage notNil and: [image depth > 8]) ifTrue: [ image _ previousImage addDeltasFrom: image. ]. CachedForms at: cacheID put: image ]. ]. ]. ]. self drawCommand: [ :c | c image: image at: point sourceRect: sourceRect rule: rule ]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 8/25/2000 13:37'! drawInfiniteFill: command | aRectangle aFillStyle | aRectangle _ self class decodeRectangle: (command at: 2). aFillStyle _ InfiniteForm with: (self class decodeImage: (command at: 3)). self drawCommand: [ :c | c asBalloonCanvas fillRectangle: aRectangle fillStyle: aFillStyle. ].! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 2/28/2000 00:22'! drawLine: command | verb pt1Enc pt2Enc widthEnc colorEnc pt1 pt2 width color | verb := command at: 1. pt1Enc := command at: 2. pt2Enc := command at: 3. widthEnc := command at: 4. colorEnc := command at: 5. pt1 _ self class decodePoint: pt1Enc. pt2 _ self class decodePoint: pt2Enc. width _ self class decodeInteger: widthEnc. color _ self class decodeColor: colorEnc. self drawCommand: [ :c | c line: pt1 to: pt2 width: width color: color ]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 2/28/2000 00:24'! drawOval: command | verb rectEnc colorEnc borderWidthEnc borderColorEnc rect color borderWidth borderColor | verb := command at: 1. rectEnc := command at: 2. colorEnc := command at: 3. borderWidthEnc := command at: 4. borderColorEnc := command at: 5. rect _ self class decodeRectangle: rectEnc. color _ self class decodeColor: colorEnc. borderWidth _ self class decodeInteger: borderWidthEnc. borderColor _ self class decodeColor: borderColorEnc. self drawCommand: [ :c | c fillOval: rect color: color borderWidth: borderWidth borderColor: borderColor ] ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 4/8/2000 22:28'! drawPoly: command | verticesEnc fillColorEnc borderWidthEnc borderColorEnc vertices fillColor borderWidth borderColor | fillColorEnc := command at: 2. borderWidthEnc := command at: 3. borderColorEnc := command at: 4. verticesEnc := command copyFrom: 5 to: command size. fillColor := self class decodeColor: fillColorEnc. borderWidth := self class decodeInteger: borderWidthEnc. borderColor := self class decodeColor: borderColorEnc. vertices := verticesEnc collect: [ :enc | self class decodePoint: enc ]. self drawCommand: [ :c | c drawPolygon: vertices color: fillColor borderWidth: borderWidth borderColor: borderColor ]. ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 2/28/2000 00:24'! drawRect: command | verb rectEnc fillColorEnc borderWidthEnc borderColorEnc rect fillColor borderWidth borderColor | verb := command at: 1. rectEnc := command at: 2. fillColorEnc := command at: 3. borderWidthEnc := command at: 4. borderColorEnc := command at: 5. rect _ self class decodeRectangle: rectEnc. fillColor _ self class decodeColor: fillColorEnc. borderWidth _ self class decodeInteger: borderWidthEnc. borderColor _ self class decodeColor: borderColorEnc. self drawCommand: [ :c | c frameAndFillRectangle: rect fillColor: fillColor borderWidth: borderWidth borderColor: borderColor ]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 3/26/2000 13:20'! drawStencil: command | stencilFormEnc locationEnc sourceRectEnc colorEnc stencilForm location sourceRect color | stencilFormEnc := command at: 2. locationEnc := command at: 3. sourceRectEnc := command at: 4. colorEnc := command at: 5. stencilForm := self class decodeImage: stencilFormEnc. location := self class decodePoint: locationEnc. sourceRect := self class decodeRectangle: sourceRectEnc. color := self class decodeColor: colorEnc. self drawCommand: [ :executor | executor stencil: stencilForm at: location sourceRect: sourceRect color: color ]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 3/27/2000 18:02'! drawText: command | boundsEnc colorEnc text bounds color fontIndexEnc fontIndex | text := command at: 2. boundsEnc := command at: 3. fontIndexEnc := command at: 4. colorEnc := command at: 5. bounds _ self class decodeRectangle: boundsEnc. fontIndex := self class decodeInteger: fontIndexEnc. color _ self class decodeColor: colorEnc. self drawCommand: [ :c | c text: text bounds: bounds font: (fonts at: fontIndex) color: color ]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 4/9/2000 14:40'! extentDepth: command | depth extent | extent := self class decodePoint: (command at: 2). depth := self class decodeInteger: (command at: 3). drawingCanvas := FormCanvas extent: extent depth: depth.! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 3/26/2000 22:04'! forceToScreen: aCommand withBlock: forceBlock | region | region := self class decodeRectangle: aCommand second. forceBlock value: region.! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 11/6/2000 15:36'! processCommand: command onForceDo: forceBlock | verb verbCode | command isEmpty ifTrue: [ ^self ]. verb _ command first. verbCode := verb at: 1. verbCode = CanvasEncoder codeClip ifTrue: [ ^self setClip: command ]. verbCode = CanvasEncoder codeTransform ifTrue: [ ^self setTransform: command ]. verbCode = CanvasEncoder codeText ifTrue: [ ^self drawText: command ]. verbCode = CanvasEncoder codeLine ifTrue: [ ^self drawLine: command ]. verbCode = CanvasEncoder codeRect ifTrue: [ ^self drawRect: command ]. verbCode = CanvasEncoder codeBalloonRect ifTrue: [ ^self drawBalloonRect: command ]. verbCode = CanvasEncoder codeBalloonOval ifTrue: [ ^self drawBalloonOval: command ]. verbCode = CanvasEncoder codeInfiniteFill ifTrue: [ ^self drawInfiniteFill: command ]. verbCode = CanvasEncoder codeOval ifTrue: [ ^self drawOval: command ]. verbCode = CanvasEncoder codeImage ifTrue: [ ^self drawImage: command ]. verbCode = CanvasEncoder codeReleaseCache ifTrue: [ ^self releaseImage: command ]. verbCode = CanvasEncoder codePoly ifTrue: [ ^self drawPoly: command ]. verbCode = CanvasEncoder codeStencil ifTrue: [ ^self drawStencil: command ]. verbCode = CanvasEncoder codeForce ifTrue: [ ^self forceToScreen: command withBlock: forceBlock ]. verbCode = CanvasEncoder codeFont ifTrue: [ ^self addFontToCache: command ]. verbCode = CanvasEncoder codeExtentDepth ifTrue: [ ^self extentDepth: command ]. self error: 'unknown command: ', command first.! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 7/28/2000 17:13'! releaseImage: command | cacheID | CachedForms ifNil: [^self]. cacheID _ self class decodeInteger: (command at: 2). CachedForms at: cacheID put: nil. ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 10/9/1999 20:28'! setClip: command | clipRectEnc | clipRectEnc _ command at: 2. clipRect _ self class decodeRectangle: clipRectEnc! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'ls 10/9/1999 20:28'! setTransform: command | transformEnc | transformEnc _ command at: 2. transform _ self class decodeTransform: transformEnc! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 8/28/2000 11:46'! showSpaceUsed | total | CachedForms ifNil: [^self]. total _ 0. CachedForms do: [ :each | each ifNotNil: [ total _ total + (each depth * each width * each height // 8). ]. ]. (total // 1024) printString,' ', (Smalltalk garbageCollectMost // 1024) printString,' ' displayAt: 0@0! ! !CanvasDecoder methodsFor: 'attributes' stamp: 'ls 4/9/2000 14:29'! drawingForm "return the form that we are drawing on behind thescenes" ^drawingCanvas form! ! !CanvasDecoder methodsFor: 'shutting down' stamp: 'ls 4/9/2000 14:33'! delete connection ifNotNil: [ connection destroy ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CanvasDecoder class instanceVariableNames: ''! !CanvasDecoder class methodsFor: 'decoding' stamp: 'RAA 7/25/2000 13:06'! decodeColor: string | rgb a rgb1 rgb2 | rgb1 := string getInteger32: 1. rgb2 := string getInteger32: 5. a := string getInteger32: 9. rgb := rgb2 << 16 + rgb1. a < 255 ifTrue: [ ^TranslucentColor basicNew setRgb: rgb alpha: a/255.0 ] ifFalse: [ ^Color basicNew setRGB: rgb ]! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'RAA 7/28/2000 08:33'! decodeFillStyle: string ^DataStream unStream: string! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 3/27/2000 17:57'! decodeFont: fontString ^StrikeFont decodedFromRemoteCanvas: fontString! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'RAA 9/19/2000 15:14'! decodeImage: string | bitsStart depth width height bits rs numColors colorArray | bitsStart _ string indexOf: $|. bitsStart = 0 ifTrue: [^nil]. rs := ReadStream on: string. rs peek == $C ifTrue: [ rs next. numColors _ Integer readFromString: (rs upTo: $,). colorArray _ Array new: numColors. 1 to: numColors do: [ :i | colorArray at: i put: (self decodeColor: (rs next: 12)) ]. ]. depth := Integer readFromString: (rs upTo: $,). width := Integer readFromString: (rs upTo: $,). height := Integer readFromString: (rs upTo: $|). bits _ Bitmap newFromStream: (RWBinaryOrTextStream with: rs upToEnd) binary reset. colorArray ifNil: [ ^Form extent: width@height depth: depth bits: bits ]. ^(ColorForm extent: width@height depth: depth bits: bits) colors: colorArray ! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 9/24/1999 20:10'! decodeInteger: string ^Integer readFromString: string! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 3/27/2000 00:36'! decodePoint: string | x y | x := string getInteger32: 1. y := string getInteger32: 5. ^x@y! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 3/25/2000 23:02'! decodePoints: aString ^(aString findTokens: '|') asArray collect: [ :encPoint | self decodePoint: encPoint ]! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 3/27/2000 22:24'! decodeRectangle: string | x y cornerX cornerY | x := string getInteger32: 1. y := string getInteger32: 5. cornerX := string getInteger32: 9. cornerY := string getInteger32: 13. ^x@y corner: cornerX@cornerY! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'ls 10/9/1999 20:28'! decodeTransform: transformEnc "decode an encoded transform" ^DisplayTransform fromRemoteCanvasEncoding: transformEnc! ! !CanvasDecoder class methodsFor: 'instance creation' stamp: 'ls 4/9/2000 14:24'! connection: aConnection ^self new initialize; connection: aConnection; yourself! ! Object subclass: #CanvasEncoder instanceVariableNames: 'connection lastClipRect lastTransform fontCache cachedObjects cachingEnabled ' classVariableNames: 'SentTypesAndSizes SimpleCounters ' poolDictionaries: '' category: 'Morphic-Remote'! !CanvasEncoder commentStamp: '' prior: 0! Encodes canvas commands into string-arrays format. ---possible further compression for forms --- 600 * 359 * 4 861600 self encodeForRemoteCanvas size 76063 Time millisecondsToRun: [self encodeForRemoteCanvas] | raw data | data _ self encodeForRemoteCanvas. raw _ RWBinaryOrTextStream on: (String new: 1000). Time millisecondsToRun: [(GZipWriteStream on: raw) nextPutAll: data; close]. raw contents size (GZipReadStream on: (ReadStream on: raw contents)) upToEnd size | raw | raw _ RWBinaryOrTextStream on: (String new: bits size). raw nextPutAll: bits Time millisecondsToRun: [bits compressGZip] 50 bits compressGZip size 861620! !CanvasEncoder methodsFor: 'connection' stamp: 'RAA 8/1/2000 00:17'! backlog ^connection backlog! ! !CanvasEncoder methodsFor: 'connection' stamp: 'RAA 11/7/2000 17:54'! connection: aStringSocket "set this connection to talk over the given socket" cachingEnabled _ true. connection _ aStringSocket! ! !CanvasEncoder methodsFor: 'connection' stamp: 'ls 9/26/1999 15:47'! disconnect connection ifNotNil: [ connection destroy. connection _ nil. ].! ! !CanvasEncoder methodsFor: 'connection' stamp: 'ls 9/26/1999 15:45'! isConnected ^connection notNil and: [ connection isConnected ]! ! !CanvasEncoder methodsFor: 'connection' stamp: 'RAA 11/8/2000 15:06'! purgeOutputQueue connection purgeOutputQueue.! ! !CanvasEncoder methodsFor: 'clipping and transforming' stamp: 'ls 4/11/2000 18:59'! setClipRect: newClipRect self sendCommand: { String with: CanvasEncoder codeClip. self class encodeRectangle: newClipRect }! ! !CanvasEncoder methodsFor: 'clipping and transforming' stamp: 'ls 4/11/2000 18:59'! setTransform: newTransform self sendCommand: { String with: CanvasEncoder codeTransform. self class encodeTransform: newTransform }! ! !CanvasEncoder methodsFor: 'clipping and transforming' stamp: 'ls 10/9/1999 18:19'! updateTransform: aTransform andClipRect: aClipRect "sets the given transform and clip rectangle, if they aren't already the ones being used" aTransform = lastTransform ifFalse: [ self setTransform: aTransform. lastTransform _ aTransform ]. aClipRect = lastClipRect ifFalse: [ self setClipRect: aClipRect. lastClipRect _ aClipRect. ].! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 11/6/2000 15:38'! balloonFillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc self sendCommand: { String with: CanvasEncoder codeBalloonOval. self class encodeRectangle: aRectangle. aFillStyle encodeForRemoteCanvas. self class encodeInteger: bw. self class encodeColor: bc. }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/25/2000 13:30'! balloonFillRectangle: aRectangle fillStyle: aFillStyle self sendCommand: { String with: CanvasEncoder codeBalloonRect. self class encodeRectangle: aRectangle. aFillStyle encodeForRemoteCanvas }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 11/7/2000 17:56'! cachingEnabled: aBoolean (cachingEnabled _ aBoolean) ifFalse: [ cachedObjects _ nil. ]. ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/11/2000 18:59'! drawPolygon: vertices color: aColor borderWidth: bw borderColor: bc | encodedVertices | encodedVertices := vertices collect: [ :vertex | self class encodePoint: vertex ]. self sendCommand: { String with: CanvasEncoder codePoly. self class encodeColor: aColor. self class encodeInteger: bw. self class encodeColor: bc}, encodedVertices .! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/9/2000 14:39'! extent: newExtent depth: newDepth self sendCommand: { self class codeExtentDepth asString. self class encodePoint: newExtent. self class encodeInteger: newDepth. }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/11/2000 18:59'! fillOval: r color: c borderWidth: borderWidth borderColor: borderColor self sendCommand: { String with: CanvasEncoder codeOval. self class encodeRectangle: r. self class encodeColor: c. self class encodeInteger: borderWidth. self class encodeColor: borderColor }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/11/2000 18:59'! forceToScreen: aRectangle self sendCommand: { String with: CanvasEncoder codeForce. self class encodeRectangle: aRectangle }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/25/2000 13:12'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: borderColor self sendCommand: { String with: CanvasEncoder codeRect. self class encodeRectangle: r. fillColor encodeForRemoteCanvas. self class encodeInteger: borderWidth. self class encodeColor: borderColor }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 11/1/2000 23:21'! image: aForm at: aPoint sourceRect: sourceRect rule: rule | cacheID cacheNew cacheReply formToSend cacheEntry destRect visRect aFormArea d2 | "first if we are only going to be able to draw a small part of the form, it may be faster just to send the part of the form that will actually show up" destRect _ aPoint extent: sourceRect extent. d2 _ (lastTransform invertBoundsRect: destRect) expandBy: 1. (d2 intersects: lastClipRect) ifFalse: [ ^NebraskaDebug at: #bigImageSkipped add: {lastClipRect. d2}. ]. aFormArea _ aForm boundingBox area. (aFormArea > 20000 and: [aForm isStatic not and: [lastTransform isPureTranslation]]) ifTrue: [ visRect _ destRect intersect: lastClipRect. visRect area < (aFormArea // 20) ifTrue: [ "NebraskaDebug at: #bigImageReduced add: {lastClipRect. aPoint. sourceRect extent. lastTransform}." formToSend _ aForm copy: (visRect translateBy: sourceRect origin - aPoint). ^self image: formToSend at: visRect origin sourceRect: formToSend boundingBox rule: rule cacheID: 0 "no point in trying to cache this - it's a one-timer" newToCache: false. ]. ]. cacheID _ 0. cacheNew _ false. formToSend _ aForm. (aFormArea > 1000 and: [(cacheReply _ self testCache: aForm) notNil]) ifTrue: [ cacheID _ cacheReply first. cacheEntry _ cacheReply third. (cacheNew _ cacheReply second) ifFalse: [ formToSend _ aForm isStatic ifTrue: [nil] ifFalse: [aForm depth <= 8 ifTrue: [aForm] ifFalse: [aForm deltaFrom: cacheEntry fourth]]. ]. cacheEntry at: 4 put: (aForm isStatic ifTrue: [aForm] ifFalse: [aForm deepCopy]). ]. self image: formToSend at: aPoint sourceRect: sourceRect rule: rule cacheID: cacheID newToCache: cacheNew. ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 12/14/2000 11:30'! image: aFormOrNil at: aPoint sourceRect: sourceRect rule: rule cacheID: cacheID newToCache: newToCache | t destRect d2 | destRect _ aPoint extent: sourceRect extent. d2 _ (lastTransform invertBoundsRect: destRect) expandBy: 1. (d2 intersects: lastClipRect) ifFalse: [ ^NebraskaDebug at: #bigImageSkipped add: {lastClipRect. d2}. ]. t _ Time millisecondsToRun: [ self sendCommand: { String with: CanvasEncoder codeImage. self class encodeImage: aFormOrNil. self class encodePoint: aPoint. self class encodeRectangle: sourceRect. self class encodeInteger: rule. self class encodeInteger: cacheID. self class encodeInteger: (newToCache ifTrue: [1] ifFalse: [0]). }. ]. (aFormOrNil notNil and: [aFormOrNil boundingBox area > 10000]) ifTrue: [ NebraskaDebug at: #bigImage add: {lastClipRect. aPoint. sourceRect extent. t. cacheID. newToCache}. ]. ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/25/2000 13:32'! infiniteFillRectangle: aRectangle fillStyle: aFillStyle self sendCommand: { String with: CanvasEncoder codeInfiniteFill. self class encodeRectangle: aRectangle. aFillStyle encodeForRemoteCanvas }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/14/2000 14:27'! line: pt1 to: pt2 width: w color: c "Smalltalk at: #Q3 put: thisContext longStack." self sendCommand: { String with: CanvasEncoder codeLine. self class encodePoint: pt1. self class encodePoint: pt2. self class encodeInteger: w. self class encodeColor: c }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/28/2000 11:52'! purgeCache | spaceUsed spaceBefore s | spaceBefore _ spaceUsed _ self purgeCacheInner. spaceBefore > 8000000 ifTrue: [ Smalltalk garbageCollect. spaceUsed _ self purgeCacheInner. ]. false ifTrue: [ s _ (spaceBefore // 1024) printString,' ',(spaceUsed // 1024) printString,' ', Time now printString,' '. WorldState addDeferredUIMessage: [s displayAt: 0@0.] fixTemps. ]. ^spaceUsed ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 8/25/2000 17:27'! purgeCacheInner | cachedObject totalSize thisSize | cachedObjects ifNil: [^0]. totalSize _ 0. cachedObjects withIndexDo: [ :each :index | cachedObject _ each first first. cachedObject ifNil: [ each second ifNotNil: [ 2 to: each size do: [ :j | each at: j put: nil]. self sendCommand: { String with: CanvasEncoder codeReleaseCache. self class encodeInteger: index. }. ]. ] ifNotNil: [ thisSize _ cachedObject depth * cachedObject width * cachedObject height // 8. totalSize _ totalSize + thisSize. ]. ]. ^totalSize "--- newEntry _ { WeakArray with: anObject. 1. Time millisecondClockValue. nil. }. ---" ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/11/2000 18:59'! stencil: stencilForm at: aPoint sourceRect: sourceRect color: aColor self sendCommand: { String with: CanvasEncoder codeStencil. self class encodeImage: stencilForm. self class encodePoint: aPoint. self class encodeRectangle: sourceRect. self class encodeColor: aColor }! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 11/7/2000 17:55'! testCache: anObject | firstFree cachedObject newEntry | cachingEnabled ifFalse: [ cachedObjects _ nil. ^nil ]. cachedObjects ifNil: [ cachedObjects _ (1 to: 100) collect: [ :x | {WeakArray new: 1. nil. nil. nil}]. ]. self purgeCache. firstFree _ nil. cachedObjects withIndexDo: [ :each :index | cachedObject _ each first first. firstFree ifNil: [cachedObject ifNil: [firstFree _ index]]. cachedObject == anObject ifTrue: [ each at: 2 put: (each at: 2) + 1. ^{index. false. each} ]. ]. firstFree ifNil: [^nil]. newEntry _ { WeakArray with: anObject. 1. Time millisecondClockValue. nil. }. cachedObjects at: firstFree put: newEntry. ^{firstFree. true. newEntry} ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'RAA 7/22/2000 08:02'! testRectangleFillTiming | r fillColor borderWidth borderColor t | " CanvasEncoder new testRectangleFillTiming " r _ 100@100 extent: 300@300. fillColor _ Color blue. borderWidth _ 1. borderColor _ Color red. t _ Time millisecondsToRun: [ 1000 timesRepeat: [ { String with: CanvasEncoder codeRect. self class encodeRectangle: r. self class encodeColor: fillColor. self class encodeInteger: borderWidth. self class encodeColor: borderColor } ]. ]. t inspect.! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'ls 4/11/2000 18:59'! text: s bounds: boundsRect font: fontOrNil color: c | fontIndex | fontIndex := self establishFont: (fontOrNil ifNil: [ TextStyle defaultFont ]). self sendCommand: { String with: CanvasEncoder codeText. s. self class encodeRectangle: boundsRect. self class encodeInteger: fontIndex. self class encodeColor: c }! ! !CanvasEncoder methodsFor: 'private' stamp: 'RAA 7/28/2000 09:05'! sendCommand: stringArray | bucket | connection ifNil: [ ^self ]. connection isConnected ifFalse: [ ^self ]. connection nextPut: stringArray. SentTypesAndSizes ifNil: [^self]. bucket _ SentTypesAndSizes at: stringArray first ifAbsentPut: [{0. 0. 0}]. bucket at: 1 put: (bucket at: 1) + 1. bucket at: 2 put: (bucket at: 2) + ( stringArray inject: 4 into: [ :sum :array | sum + (array size + 4) ] ). ! ! !CanvasEncoder methodsFor: 'network' stamp: 'ls 9/24/1999 19:52'! destroy self disconnect.! ! !CanvasEncoder methodsFor: 'network' stamp: 'ls 3/21/2000 23:22'! flush connection ifNotNil: [ connection flush ]! ! !CanvasEncoder methodsFor: 'network' stamp: 'ls 9/24/1999 19:52'! processIO connection ifNil: [ ^self ]. connection isConnected ifFalse: [ ^self ]. connection processIO.! ! !CanvasEncoder methodsFor: 'initialization' stamp: 'RAA 11/7/2000 17:55'! initialize cachingEnabled _ true. fontCache := FontCache new: 5.! ! !CanvasEncoder methodsFor: 'fonts' stamp: 'ls 3/27/2000 18:06'! establishFont: aFont "make sure that the given font is in the fonts cache. If it is not there already, then transmit it. Either way, after this returns, the font is in the cache at the index specified by the return value" | index | (fontCache includesFont: aFont) ifTrue: [ ^fontCache indexOf: aFont ]. index := fontCache indexForNewFont: aFont. self sendFont: aFont atIndex: index. ^index! ! !CanvasEncoder methodsFor: 'fonts' stamp: 'ls 4/11/2000 18:59'! sendFont: aFont atIndex: index "transmit the given fint to the other side" self sendCommand: { String with: CanvasEncoder codeFont. self class encodeInteger: index. self class encodeFont: aFont }. ! ! !CanvasEncoder methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:44'! convertToCurrentVersion: varDict refStream: smartRefStrm cachingEnabled ifNil: [cachingEnabled _ true]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CanvasEncoder class instanceVariableNames: ''! !CanvasEncoder class methodsFor: 'encoding' stamp: 'RAA 7/24/2000 13:24'! encodeColor: color ^color encodeForRemoteCanvas! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'RAA 7/28/2000 07:53'! encodeFillStyle: aFillStyle ^aFillStyle encodeForRemoteCanvas! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'ls 3/27/2000 17:57'! encodeFont: aFont ^aFont encodedForRemoteCanvas! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'RAA 12/14/2000 11:30'! encodeImage: form | t answer | form ifNil: [^'']. t _ Time millisecondsToRun: [answer _ form encodeForRemoteCanvas]. form boundingBox area > 5000 ifTrue: [ NebraskaDebug at: #FormEncodeTimes add: {t. form extent. answer size} ]. ^answer "HandMorph>>restoreSavedPatchOn: is one culprit here" ! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'ls 3/26/2000 23:12'! encodeInteger: integer ^integer asInteger storeString! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'RAA 7/28/2000 08:20'! encodePoint: point ^point encodeForRemoteCanvas! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'RAA 8/9/2000 16:11'! encodeRectangle: rectangle | x y encoded cornerX cornerY | x _ rectangle origin x asInteger. y _ rectangle origin y asInteger. cornerX _ rectangle corner x asInteger. cornerY _ rectangle corner y asInteger. CanvasEncoder at: 2 count: 1. encoded := String new: 16. encoded putInteger32: x at: 1. encoded putInteger32: y at: 5. encoded putInteger32: cornerX at: 9. encoded putInteger32: cornerY at: 13. ^encoded! ! !CanvasEncoder class methodsFor: 'encoding' stamp: 'ls 10/9/1999 18:54'! encodeTransform: transform ^transform encodeForRemoteCanvas! ! !CanvasEncoder class methodsFor: 'instance creation' stamp: 'ls 9/26/1999 16:22'! new ^super new initialize! ! !CanvasEncoder class methodsFor: 'instance creation' stamp: 'ls 10/20/1999 21:17'! on: connection ^self new connection: connection! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:29'! aaaReadme "these codes are used instead of strings, because String>>= was taking around 20% of the decoder's time" ! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'RAA 11/6/2000 15:28'! codeBalloonOval ^$O! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'RAA 7/28/2000 07:43'! codeBalloonRect ^$R! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeClip ^$A! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 4/9/2000 14:39'! codeExtentDepth ^$M! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeFont ^$L! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeForce ^$J! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeImage ^$G! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'RAA 8/25/2000 13:31'! codeInfiniteFill ^$i! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeLine ^$D! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeOval ^$F! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codePoly ^$H! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeRect ^$E! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'RAA 7/28/2000 16:50'! codeReleaseCache ^$z! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeStencil ^$I! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeText ^$C! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:35'! codeTransform ^$B! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 17:27'! at: anIndex count: anInteger SimpleCounters ifNil: [(SimpleCounters _ Array new: 10) atAllPut: 0]. SimpleCounters at: anIndex put: (SimpleCounters at: anIndex) + anInteger.! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 09:01'! beginStats SentTypesAndSizes _ Dictionary new.! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 17:30'! clearTestVars " CanvasEncoder clearTestVars " SimpleCounters _ nil ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 17:47'! explainTestVars " CanvasEncoder explainTestVars " | answer total oneBillion data putter nReps | SimpleCounters ifNil: [^1 beep]. total _ 0. oneBillion _ 1000 * 1000 * 1000. answer _ String streamContents: [ :strm | data _ SimpleCounters copy. putter _ [ :msg :index :nSec | nReps _ data at: index. total _ total + (nSec * nReps). strm nextPutAll: nReps asStringWithCommas,' * ',nSec printString,' ', (nSec * nReps / oneBillion roundTo: 0.01) printString,' secs for ',msg; cr ]. putter value: 'string socket' value: 1 value: 8000. putter value: 'rectangles' value: 2 value: 40000. putter value: 'points' value: 3 value: 18000. putter value: 'colors' value: 4 value: 8000. ]. StringHolder new contents: answer; openLabel: 'put integer times'. ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 17:26'! inspectTestVars " CanvasEncoder inspectTestVars " ^SimpleCounters ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 09:01'! killStats SentTypesAndSizes _ nil! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 11/6/2000 15:29'! nameForCode: aStringOrChar | ch | ch _ (aStringOrChar isKindOf: String) ifTrue: [aStringOrChar first] ifFalse: [aStringOrChar]. ch == self codeBalloonOval ifTrue: [^'balloon oval']. ch == self codeBalloonRect ifTrue: [^'balloon rectangle']. ch == self codeClip ifTrue: [^'clip']. ch == self codeExtentDepth ifTrue: [^'codeExtentDepth']. ch == self codeFont ifTrue: [^'codeFont']. ch == self codeForce ifTrue: [^'codeForce']. ch == self codeImage ifTrue: [^'codeImage']. ch == self codeLine ifTrue: [^'codeLine']. ch == self codeOval ifTrue: [^'codeOval']. ch == self codePoly ifTrue: [^'codePoly']. ch == self codeRect ifTrue: [^'codeRect']. ch == self codeReleaseCache ifTrue: [^'codeReleaseCache']. ch == self codeStencil ifTrue: [^'codeStencil']. ch == self codeText ifTrue: [^'codeText']. ch == self codeTransform ifTrue: [^'codeTransform']. ch == self codeInfiniteFill ifTrue: [^'codeInfiniteFill']. ^'????' ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 8/14/2000 14:18'! showStats " CanvasEncoder showStats " | answer bucket | SentTypesAndSizes ifNil: [^1 beep]. answer _ WriteStream on: String new. SentTypesAndSizes keys asSortedCollection do: [ :each | bucket _ SentTypesAndSizes at: each. answer nextPutAll: each printString,' ', bucket first printString,' ', bucket second asStringWithCommas,' ', (self nameForCode: each); cr. ]. StringHolder new contents: answer contents; openLabel: 'send/receive stats'. ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 17:48'! timeSomeThings " CanvasEncoder timeSomeThings " | s iter answer ms pt rect bm writer array color | iter _ 1000000. array _ Array new: 4. color _ Color red. answer _ String streamContents: [ :strm | writer _ [ :msg :doer | ms _ [iter timesRepeat: doer] timeToRun. strm nextPutAll: msg,((ms * 1000 / iter) roundTo: 0.01) printString,' usec'; cr. ]. s _ String new: 4. bm _ Bitmap new: 20. pt _ 100@300. rect _ pt extent: pt. iter _ 1000000. writer value: 'empty loop ' value: [self]. writer value: 'modulo ' value: [12345678 \\ 256]. writer value: 'bitAnd: ' value: [12345678 bitAnd: 255]. strm cr. iter _ 100000. writer value: 'putInteger ' value: [s putInteger32: 12345678 at: 1]. writer value: 'bitmap put ' value: [bm at: 1 put: 12345678]. writer value: 'encodeBytesOf: (big) ' value: [bm encodeInt: 12345678 in: bm at: 1]. writer value: 'encodeBytesOf: (small) ' value: [bm encodeInt: 5000 in: bm at: 1]. writer value: 'array at: (in) ' value: [array at: 1]. writer value: 'array at: (out) ' value: [array at: 6 ifAbsent: []]. strm cr. iter _ 10000. writer value: 'color encode ' value: [color encodeForRemoteCanvas]. writer value: 'pt encode ' value: [pt encodeForRemoteCanvas]. writer value: 'rect encode ' value: [self encodeRectangle: rect]. writer value: 'rect encode2 ' value: [rect encodeForRemoteCanvas]. writer value: 'rect encodeb ' value: [rect encodeForRemoteCanvasB]. ]. StringHolder new contents: answer; openLabel: 'send/receive stats'. ! ! Player subclass: #CardPlayer instanceVariableNames: 'privateMorphs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Stacks'! !CardPlayer commentStamp: '' prior: 0! CardPlayer Instance variables of the Uniclass represent the data in the "fields" of each card in the stack. Each Instance variable is some kind of value holder. The code for the *buttons* on the background resides in the CardPlayer uniclass. ! !CardPlayer methodsFor: 'printing' stamp: 'sw 10/23/2000 17:58'! printOn: aStream "Print out a human-readable representation of the receiver onto aStream" super printOn: aStream. self class instVarNames do: [:aName | aStream nextPutAll: ', ', aName, ' = ', (self instVarNamed: aName) printString]! ! !CardPlayer methodsFor: 'card data' stamp: 'sw 10/13/2000 16:46'! commitCardPlayerData "Transport data back from the morphs that may be holding it into the instance variables that must hold it when the receiver is not being viewed" | prior | self class variableDocks do: [:aDock | aDock storeMorphDataInInstance: self]. prior _ nil. privateMorphs _ OrderedCollection new. self costume ifNotNil: [self costume submorphs do: [:aMorph | aMorph renderedMorph isShared ifFalse: [aMorph setProperty: #priorMorph toValue: prior. privateMorphs add: aMorph. aMorph delete]. prior _ aMorph]]! ! !CardPlayer methodsFor: 'card data' stamp: 'sw 11/14/2000 11:21'! commitCardPlayerDataFrom: aPlayfield "Transport data back from the morphs that may be holding it into the instance variables that must hold it when the receiver is not being viewed" | prior itsOrigin | itsOrigin _ aPlayfield topLeft. self class variableDocks do: [:aDock | aDock storeMorphDataInInstance: self]. prior _ nil. privateMorphs _ OrderedCollection new. self costume ifNotNil: [self costume submorphs do: [:aMorph | aMorph renderedMorph isShared ifFalse: [aMorph setProperty: #priorMorph toValue: prior. privateMorphs add: aMorph. aMorph delete. aMorph position: (aMorph position - itsOrigin)]. prior _ aMorph]]! ! !CardPlayer methodsFor: 'card data' stamp: 'tk 1/16/2001 16:12'! installPrivateMorphsInto: aBackground "The receiver is being installed as the current card in a given pasteup morph being used as a background. Install the receiver's private morphs into that playfield" | prior originToUse | self flag: #deferred. "not robust if the background is showing a list view" privateMorphs ifNotNil: [privateMorphs do: [:aMorph | originToUse _ aBackground topLeft. prior _ aMorph valueOfProperty: #priorMorph ifAbsent: [nil]. aMorph position: (aMorph position + originToUse). (prior notNil and: [aBackground submorphs includes: prior]) ifTrue: [aBackground addMorph: aMorph after: prior] ifFalse: [aBackground addMorphFront: aMorph]. aMorph removeProperty: #priorMorph]]! ! !CardPlayer methodsFor: 'card data' stamp: 'tk 1/30/2001 23:42'! privateMorphs ^ privateMorphs! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CardPlayer class instanceVariableNames: 'variableDocks '! !CardPlayer class methodsFor: 'class properties' stamp: 'sw 10/13/2000 13:05'! isUniClass "Answer, for the purpose of providing annotation in a method holder, whether the receiver is a uniClass." ^ self ~~ CardPlayer! ! !CardPlayer class methodsFor: 'class properties' stamp: 'sw 10/13/2000 13:07'! officialClass "Answer (for the purpose of copying mechanisms) the system class underlying the receiver." ^ CardPlayer! ! !CardPlayer class methodsFor: 'user-defined inst vars' stamp: 'sw 10/13/2000 13:03'! compileAccessorsFor: varName "Compile instance-variable accessor methods for the given variable name" | nameString | nameString _ varName asString capitalized. self compileUnlogged: ('get', nameString, ' ^ ', varName) classified: 'access' notifying: nil. self compileUnlogged: ('set', nameString, ': val ', varName, ' _ val') classified: 'access' notifying: nil! ! !CardPlayer class methodsFor: 'user-defined inst vars' stamp: 'sw 10/27/2000 17:09'! removeAccessorsFor: varName "Remove the instance-variable accessor methods associated with varName" | nameString | nameString _ varName asString capitalized. self removeSelectorUnlogged: ('get', nameString) asSymbol. self removeSelectorUnlogged: ('set', nameString, ':') asSymbol! ! !CardPlayer class methodsFor: 'user-defined inst vars' stamp: 'sw 10/13/2000 16:37'! setNewInstVarNames: listOfStrings "Make listOfStrings be the new list of instance variable names for the receiver" | disappearing firstAppearing instVarString instVarList | instVarList _ self instVarNames asOrderedCollection. disappearing _ instVarList copy. disappearing removeAllFoundIn: listOfStrings. disappearing do: [:oldName | self removeAccessorsFor: oldName]. firstAppearing _ listOfStrings copy. firstAppearing removeAllFoundIn: instVarList. firstAppearing do: [:newName | self compileAccessorsFor: newName]. instVarString _ String streamContents: [:aStream | listOfStrings do: [:aString | aStream nextPutAll: aString; nextPut: $ ]]. superclass subclass: self name instanceVariableNames: instVarString classVariableNames: '' poolDictionaries: '' category: self categoryForUniclasses ! ! !CardPlayer class methodsFor: 'logging' stamp: 'sw 10/13/2000 13:02'! acceptsLoggingOfCompilation "Answer whether methods of the receiver should be logged when submitted." ^ #(CardPlayer) includes: self class theNonMetaClass name! ! !CardPlayer class methodsFor: 'logging' stamp: 'sw 10/13/2000 16:45'! wantsChangeSetLogging "Log changes for CardPlayer itself, but not for automatically-created subclasses like CardPlayer1, CardPlayer2, but *do* log it for uniclasses that have been manually renamed." ^ self == CardPlayer or: [(self name beginsWith: 'CardPlayer') not]! ! !CardPlayer class methodsFor: 'variable docks' stamp: 'sw 10/13/2000 16:36'! newVariableDocks: dockList "Set the receiver's variableDocks to be the list provided in dockList. Assimilate this new information into the receiver's slotInfo, which contains both automatically-generated variables such as the variable docks and also explicitly-user-specified variables" self variableDocks: dockList. self setSlotInfoFromVariableDocks! ! !CardPlayer class methodsFor: 'variable docks' stamp: 'sw 10/9/2000 07:51'! setSlotInfoFromVariableDocks "Get the slotInfo fixed up after a change in background shape. Those instance variables that are proactively added by the user will persist, whereas those that are automatically generated will be updated" | aDock newInfo | self slotInfo copy do: "Remove old automatically-created slots" [:aSlotInfo | (aDock _ aSlotInfo variableDock) ifNotNil: [slotInfo removeKey: aDock variableName]]. self variableDocks do: "Generate fresh slots from variable docks" [:dock | newInfo _ SlotInformation new type: dock variableType. newInfo variableDock: dock. slotInfo at: dock variableName asSymbol put: newInfo]! ! !CardPlayer class methodsFor: 'variable docks' stamp: 'sw 10/13/2000 16:39'! variableDocks "Answer the list of variable docks in the receiver. Initialize the variable-dock list if not already done." variableDocks ifNil: [variableDocks _ OrderedCollection new]. ^ variableDocks! ! !CardPlayer class methodsFor: 'variable docks' stamp: 'sw 10/13/2000 16:39'! variableDocks: dockList "Set the variable-dock list as indicated" variableDocks _ dockList! ! ParseNode subclass: #CascadeNode instanceVariableNames: 'receiver messages ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !CascadeNode commentStamp: '' prior: 0! The first message has the common receiver, the rest have receiver == nil, which signifies cascading.! !CascadeNode methodsFor: 'initialize-release'! receiver: receivingObject messages: msgs " Transcript show: 'abc'; cr; show: 'def' " receiver _ receivingObject. messages _ msgs! ! !CascadeNode methodsFor: 'code generation'! emitForValue: stack on: aStream receiver emitForValue: stack on: aStream. 1 to: messages size - 1 do: [:i | aStream nextPut: Dup. stack push: 1. (messages at: i) emitForValue: stack on: aStream. aStream nextPut: Pop. stack pop: 1]. messages last emitForValue: stack on: aStream! ! !CascadeNode methodsFor: 'code generation'! sizeForValue: encoder | size | size _ (receiver sizeForValue: encoder) + (messages size - 1 * 2). messages do: [:aMessage | size _ size + (aMessage sizeForValue: encoder)]. ^size! ! !CascadeNode methodsFor: 'printing'! printOn: aStream indent: level self printOn: aStream indent: level precedence: 0! ! !CascadeNode methodsFor: 'printing' stamp: 'di 4/25/2000 19:17'! printOn: aStream indent: level precedence: p p > 0 ifTrue: [aStream nextPut: $(]. messages first printReceiver: receiver on: aStream indent: level. 1 to: messages size do: [:i | (messages at: i) printOn: aStream indent: level. i < messages size ifTrue: [aStream nextPut: $;. messages first precedence >= 2 ifTrue: [aStream crtab: level + 1]]]. p > 0 ifTrue: [aStream nextPut: $)]! ! !CascadeNode methodsFor: 'C translation' stamp: 'hg 8/14/2000 15:33'! asTranslatorNode ^TStmtListNode new setArguments: #() statements: (messages collect: [ :msg | msg asTranslatorNode receiver: receiver asTranslatorNode ]); comment: comment! ! !CascadeNode methodsFor: 'tiles' stamp: 'tk 1/15/2001 22:08'! asMorphicSyntaxIn: parent | row | row _ parent addRow: #cascade on: self. receiver asMorphicSyntaxIn: row. messages do: [:m | m asMorphicSyntaxIn: row]. ^ row " (node2 _ self copy) receiver: nil messages: messages. cascadeMorph _ row addColumn: #cascade2 on: node2. messages do: [ :m | m asMorphicSyntaxIn: cascadeMorph]. ^row " ! ! !CascadeNode methodsFor: 'accessing' stamp: 'tk 10/22/2000 16:55'! receiver ^receiver! ! MailDBFile variableSubclass: #CategoriesFile instanceVariableNames: 'categories ' classVariableNames: '' poolDictionaries: '' category: 'Network-Mail Reader'! !CategoriesFile commentStamp: '' prior: 0! I represent the organization of the mail database into set of message lists called "categories". Each category contains a collection of message ID's. The same message may be cross-filed quite cheaply by storing it's ID in multiple categories. The categories information is kept in a binary file on the disk. It is read into memory in its entirety when the mail database is opened. To make changes persist, the categories information must be saved out to disk. This should be done after fetching new mail and when the mail database is closed. It could also be done periodically by some sort of background process. Note that the categories file, unlike the index file, cannot be re-created from the messages file. ! !CategoriesFile methodsFor: 'categories access' stamp: 'dvf 6/10/2000 18:32'! addCategory: categoryName "Add a new category, if it doesn't already exist." (self categories includes: categoryName) ifFalse: [categories at: categoryName put: PluggableSet integerSet]! ! !CategoriesFile methodsFor: 'categories access'! categories "Answer a collection of my categories, including the pseudo-categories '.unclassified.' and '.all.'. '.unclassified.' contains the orphaned messages that would otherwise not appear in any category. '.all.' contains all the messages in the database. Since these pseudo-categories are computed on the fly, there may be a noticable delay when one of them is selected." ^(categories keys) add: '.all.'; add: '.unclassified.'; yourself! ! !CategoriesFile methodsFor: 'categories access' stamp: 'dvf 6/10/2000 18:32'! file: messageID inCategory: categoryName "Add the given message ID to the given category. The target category must be a real category, not a pseudo-category." categoryName = '.unclassified.' | categoryName = '.all.' ifTrue: [^ self]. (categories includesKey: categoryName) ifFalse: [categories at: categoryName put: PluggableSet integerSet]. (categories at: categoryName) add: messageID! ! !CategoriesFile methodsFor: 'categories access'! isUnclassified: messageID "Answer true if the given message ID does not appear in any of my real (not pseudo) categories." categories do: [: category | (category includes: messageID) ifTrue: [^false]]. ^true! ! !CategoriesFile methodsFor: 'categories access'! messagesIn: category "Answer a collection of message ID's for the messages in the given category. The pseudo-categories are dynamically computed and so they cannot be accessed in this manner." ^categories at: category ifAbsent: [#()]! ! !CategoriesFile methodsFor: 'categories access'! remove: messageID fromCategory: categoryName "Remove the given message ID from the given category." | msgList | msgList _ categories at: categoryName ifAbsent: [^self]. msgList remove: messageID ifAbsent: [].! ! !CategoriesFile methodsFor: 'categories access'! removeCategory: categoryName "Remove the given category, if it exists." categories removeKey: categoryName ifAbsent: [].! ! !CategoriesFile methodsFor: 'categories access'! removeMessagesInCategory: categoryName butNotIn: indexFile "Used to clean the dead wood out of a category." | oldMsgs newMsgs | oldMsgs _ categories at: categoryName ifAbsent: [^self]. newMsgs _ oldMsgs copy. oldMsgs do: [: msgID | (indexFile includesKey: msgID) ifFalse: [newMsgs remove: msgID]]. categories at: categoryName put: newMsgs.! ! !CategoriesFile methodsFor: 'categories access' stamp: 'dvf 6/10/2000 19:21'! renameCategory: oldName to: newName "Rename the given category." | oldEntry | oldName = '.all.' | oldName = '.unclassified.' | (self categories includes: newName) ifTrue: [^ self]. "can't rename a special category or overwrite an existing one" oldEntry _ categories removeKey: oldName ifAbsent: [PluggableSet integerSet]. categories at: newName put: oldEntry! ! !CategoriesFile methodsFor: 'categories access'! unclassifiedFrom: messageIDs "Answer the subset of the given set of message ID's that do not appear in any category." ^messageIDs select: [: msgID | self isUnclassified: msgID]! ! !CategoriesFile methodsFor: 'read-write' stamp: 'dvf 6/10/2000 18:33'! readFrom: aFileStream "Read the categories from the given FileStream." | name categorySize messageIDs | categories _ Dictionary new: 64. aFileStream binary; position: 0. [aFileStream atEnd] whileFalse: [name _ aFileStream ascii; nextString. categorySize _ aFileStream binary; nextWord. messageIDs _ PluggableSet integerSet. categorySize timesRepeat: [messageIDs add: aFileStream nextInt32]. categories at: name put: messageIDs]! ! !CategoriesFile methodsFor: 'read-write'! writeOn: aFileStream "Write the categories to the given FileStream. The categories data is stored in binary (as opposed to a human-readable form) to save space." aFileStream binary; position: 0. categories associationsDo: [: category | "(category key) is the category name" "(category value) is the set of message ID's in that category" aFileStream nextStringPut: (category key). aFileStream nextWordPut: (category value) size. (category value) do: [: messageID | aFileStream nextInt32Put: messageID]].! ! Viewer subclass: #CategoryViewer instanceVariableNames: 'namePane ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting'! !CategoryViewer commentStamp: '' prior: 0! A viewer on an object. Consists of three panes: Header pane -- category-name, arrows for moving among categories, etc. List pane -- contents are a list of subparts in the chosen category. Editing pane -- optional, a detail pane with info relating to the selected element of the list pane.! !CategoryViewer methodsFor: 'initialization' stamp: 'sw 9/8/2000 10:58'! initializeFor: aPlayer "Initialize the category pane to show the #basic category by default" ^ self initializeFor: aPlayer categoryChoice: #basic ! ! !CategoryViewer methodsFor: 'initialization' stamp: 'ar 11/9/2000 20:52'! initializeFor: aPlayer categoryChoice: aChoice "Initialize the receiver to be associated with the player and category specified" self listDirection: #topToBottom; hResizing: #spaceFill; vResizing: #spaceFill; borderWidth: 1; beSticky. self color: Color green muchLighter muchLighter. scriptedPlayer _ aPlayer. self addHeaderMorph. self categoryChoice: aChoice asSymbol ! ! !CategoryViewer methodsFor: 'categories' stamp: 'ar 11/9/2000 20:52'! categoryChoice: aCategory "Temporarily switch-hits in support of two competing ui designs for the list" | bin actualPane | ((actualPane _ namePane renderedMorph) isKindOf: StringMorph) ifTrue: [namePane contents: aCategory; color: Color black] ifFalse: [(actualPane isKindOf: RectangleMorph) ifTrue: [actualPane firstSubmorph contents: aCategory; color: Color black. actualPane extent: actualPane firstSubmorph extent] ifFalse: [actualPane selection: (scriptedPlayer categories indexOf: aCategory)]]. bin _ PhraseWrapperMorph new borderWidth: 0; listDirection: #topToBottom. bin addAllMorphs: ((scriptedPlayer tilePhrasesForCategory: aCategory inViewer: self) collect: [:aViewerRow | self viewerEntryFor: aViewerRow]). bin enforceTileColorPolicy. submorphs size < 2 ifTrue: [self addMorphBack: bin] ifFalse: [self replaceSubmorph: self listPane by: bin]. self world ifNotNil: [self world startSteppingSubmorphsOf: self] ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 10/5/2000 10:03'! chooseCategory "The mouse went down on the receiver; pop up a list of category choices" | aList aMenu reply aLinePosition lineList | aList _ scriptedPlayer categoriesForViewer: self. aLinePosition _ aList indexOf: #miscellaneous ifAbsent: [nil]. lineList _ aLinePosition ifNil: [#()] ifNotNil: [Array with: aLinePosition]. aMenu _ CustomMenu labels: aList lines: lineList selections: aList. reply _ aMenu startUpWithCaption: 'category'. reply ifNil: [^ self]. self categoryChoice: reply asSymbol ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/8/2000 16:31'! currentCategory "Answer the symbol representing the receiver's currently-selected category" | current actualPane | actualPane _ namePane renderedMorph. current _ (actualPane isKindOf: StringMorph) ifTrue: [actualPane contents] ifFalse: [actualPane firstSubmorph contents]. ^ current ifNotNil: [current asSymbol] ifNil: [#basic]! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 10/24/1998 14:24'! downArrowHit self previousCategory! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 11/6/2000 15:57'! nextCategory "Change the receiver to point at the category following the one currently seen" | aList anIndex newIndex already aChoice | aList _ scriptedPlayer categoriesForViewer: self. already _ self outerViewer ifNil: [#()] ifNotNil: [self outerViewer categoriesCurrentlyShowing]. anIndex _ aList indexOf: self currentCategory ifAbsent: [0]. newIndex _ anIndex = aList size ifTrue: [1] ifFalse: [anIndex + 1]. [already includes: (aChoice _ aList at: newIndex)] whileTrue: [newIndex _ (newIndex \\ aList size) + 1]. self categoryChoice: aChoice! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 11/6/2000 15:57'! previousCategory "Change the receiver to point at the category preceding the one currently seen" | aList anIndex newIndex already aChoice | aList _ scriptedPlayer categoriesForViewer: self. already _ self outerViewer ifNil: [#()] ifNotNil: [self outerViewer categoriesCurrentlyShowing]. anIndex _ aList indexOf: self currentCategory ifAbsent: [aList size + 1]. newIndex _ anIndex = 1 ifTrue: [aList size] ifFalse: [anIndex - 1]. [already includes: (aChoice _ aList at: newIndex)] whileTrue: [newIndex _ newIndex = 1 ifTrue: [aList size] ifFalse: [newIndex - 1]]. self categoryChoice: aChoice! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 10/24/1998 14:25'! upArrowHit self nextCategory! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 11/5/1998 09:09'! viewerEntryFor: aViewerRow | anEntry | anEntry _ ViewerEntry newColumn. anEntry addMorphBack: aViewerRow. ^ anEntry! ! !CategoryViewer methodsFor: 'editing pane' stamp: 'sw 10/30/1998 18:16'! contents: c notifying: k "later, spruce this up so that it can accept input such as new method source" self beep. ^ false! ! !CategoryViewer methodsFor: 'header pane' stamp: 'ar 11/9/2000 21:12'! addHeaderMorph "Add the header at the top of the viewer, with a control for choosing the category, etc." | header aFont aButton wrpr | header _ AlignmentMorph newRow color: self color; wrapCentering: #center; cellPositioning: #leftCenter. aFont _ Preferences standardButtonFont. header addMorph: (aButton _ SimpleButtonMorph new label: 'O' font: aFont). aButton target: self; color: Color tan; actionSelector: #delete; setBalloonText: 'remove this pane from the screen don''t worry -- nothing will be lost!!.'. header addTransparentSpacerOfSize: 5@5. header addUpDownArrowsFor: self. (wrpr _ header submorphs last) submorphs second setBalloonText: 'previous category'. wrpr submorphs first setBalloonText: 'next category'. header beSticky. self addMorph: header. namePane _ RectangleMorph newSticky color: Color brown veryMuchLighter. namePane borderWidth: 0. aButton _ (StringButtonMorph contents: '-----' font: (StrikeFont familyName: #NewYork size: 12)) color: Color black. aButton target: self; arguments: Array new; actionSelector: #chooseCategory. aButton actWhen: #buttonDown. namePane addMorph: aButton. aButton position: namePane position. namePane align: namePane topLeft with: (bounds topLeft + (50 @ 0)). namePane setBalloonText: 'category (click here to choose a different one)'. header addMorphBack: namePane. (namePane isKindOf: RectangleMorph) ifTrue: [namePane addDropShadow. namePane shadowColor: Color gray]. self categoryChoice: #basic! ! !CategoryViewer methodsFor: 'list pane' stamp: 'sw 10/23/1998 13:50'! listPane ^ submorphs second! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 10/30/1998 18:23'! addIsOverColorDetailTo: aRow | clrTile readout aTile | aRow addMorphBack: (Morph new color: self color; extent: 2@10). " spacer". aRow addMorphBack: (clrTile _ Color blue newTileMorphRepresentative). aRow addMorphBack: (AlignmentMorph new beTransparent). "flexible spacer" readout _ UpdatingStringMorphWithArgument new target: scriptedPlayer; getSelector: #seesColor:; growable: false; putSelector: nil; argumentTarget: clrTile colorSwatch argumentGetSelector: #color. readout useDefaultFormat. aTile _ StringReadoutTile new typeColor: Color lightGray lighter. aTile addMorphBack: readout. aRow addMorphBack: aTile. aTile setLiteralTo: (scriptedPlayer seesColor: clrTile colorSwatch color) printString width: 30! ! !CategoryViewer methodsFor: 'entries' stamp: 'RAA 1/13/2001 09:37'! addTouchesADetailTo: aRow | clrTile readout aTile | aRow addMorphBack: (Morph new color: self color; extent: 2@10). " spacer". aRow addMorphBack: (clrTile _ self tileForSelf). aRow addMorphBack: (AlignmentMorph new beTransparent). "flexible spacer" "readout _ UpdatingStringMorphWithArgument new target: scriptedPlayer; getSelector: #seesColor:; growable: false; putSelector: nil; argumentTarget: clrTile colorSwatch argumentGetSelector: #color. readout useDefaultFormat. aTile _ StringReadoutTile new typeColor: Color lightGray lighter. aTile addMorphBack: readout. aRow addMorphBack: aTile. aTile setLiteralTo: (scriptedPlayer seesColor: clrTile colorSwatch color) printString width: 30"! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 10/9/2000 16:54'! infoButtonFor: aScriptOrSlotSymbol "Answer a fully-formed morph that will serve as the 'info button' alongside an entry corresponding to the given slot or script symbol" | aButton balloonTextSelector | balloonTextSelector _ nil. ((scriptedPlayer isKindOf: Player) and: [scriptedPlayer slotInfo includesKey: aScriptOrSlotSymbol asSymbol]) ifTrue: [balloonTextSelector _ #userSlot]. (scriptedPlayer belongsToUniClass and: [scriptedPlayer class includesSelector: aScriptOrSlotSymbol]) ifTrue: [balloonTextSelector _ #userScript]. aButton _ SimpleButtonMorph new. aButton target: scriptedPlayer; actionSelector: #infoFor:inViewer:; arguments: (Array with: aScriptOrSlotSymbol with: self); label: '¥' font: (StrikeFont familyName: #ComicBold size: 12); color: Color transparent; borderWidth: 0; actWhen: #buttonDown. balloonTextSelector ifNotNil: [aButton balloonTextSelector: balloonTextSelector] ifNil: [aButton setBalloonText: 'Press here to get a menu']. ^ aButton! ! !CategoryViewer methodsFor: 'entries' stamp: 'tk 1/27/2001 12:06'! phraseForSlot: slotSpec "Return a PhraseTileMorph representing a variable belonging to the player" "The slot spec if a tuple with the following structure: 1 #slot 2 slot name 3 balloon help 4 slot type 5 #readOnly,# readWrite, or #writeOnly 6 getter receiver indicator 7 getter selector 8 setter receiver indicator 9 setter selector NB: all are symbols except #3, which is a string" | r anArrow slotName getterButton ut cover inner | r _ ViewerRow newRow color: self color; beSticky; elementSymbol: (slotName _ slotSpec second); wrapCentering: #center; cellPositioning: #leftCenter. r addMorphBack: (self slotHeaderFor: slotName). r addMorphBack: (Morph new color: self color; extent: 2@10). " spacer" r addMorphBack: (self infoButtonFor: slotName). r addMorphBack: (Morph new color: self color; extent: 2@10). " spacer" ut _ scriptedPlayer isUniversalTiles. ut ifTrue: [inner _ self newTilesFor: scriptedPlayer getter: slotSpec. cover _ (Morph new) color: Color transparent. cover extent: inner fullBounds extent. (getterButton _ cover copy) addMorph: cover; addMorphBack: inner. cover on: #mouseDown send: #newMakeGetter:from:forPart: to: self withValue: slotSpec] ifFalse: [r addMorphBack: self tileForSelf bePossessive. r addMorphBack: (Morph new color: self color; extent: 2@10). " spacer" getterButton _ self getterButtonFor: slotName type: slotSpec fourth]. r addMorphBack: getterButton. getterButton setBalloonText: slotSpec third. (slotName == #isOverColor) ifTrue: [ self addIsOverColorDetailTo: r. ^ r ]. (slotName == #touchesA) ifTrue: [ self addTouchesADetailTo: r. ^ r ]. (slotSpec fifth == #readOnly) ifFalse: [r addMorphBack: (Morph new color: self color; extent: 2@10). " spacer" anArrow _ ut ifTrue: [self arrowSetterButton: #newMakeSetter:from:forPart: args: slotSpec] ifFalse: [self arrowSetterButton: #makeSetter:from:forPart: args: (Array with: slotName with: slotSpec fourth)]. r addMorphBack: anArrow. ]. r addMorphBack: (AlignmentMorph new beTransparent). "flexible spacer" (#(colorSees playerSeeingColor copy touchesA) includes: slotName) ifFalse: [r addMorphBack: (self readoutFor: slotName type: slotSpec fourth readOnly: slotSpec fifth getSelector: slotSpec seventh putSelector: slotSpec ninth)]. anArrow ifNotNil: [anArrow step]. ^ r! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 3/10/2000 17:25'! readoutFor: partName type: partType readOnly: readOnly getSelector: getSelector putSelector: putSelector | readout | (partType == #player) ifTrue: [readout _ PlayerReferenceReadout new objectToView: scriptedPlayer viewSelector: getSelector putSelector: putSelector]. (partType == #color) ifTrue: [readout _ UpdatingRectangleMorph new getSelector: (ScriptingSystem getterSelectorFor: partName); target: scriptedPlayer costume renderedMorph; borderWidth: 1; extent: 22@22. putSelector == #unused ifFalse: [readout putSelector: (ScriptingSystem setterSelectorFor: partName)]]. readout ifNil: [readout _ scriptedPlayer costume updatingTileForArgType: partType partName: partName getSelector: getSelector putSelector: putSelector]. readout step. ^ readout! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 1/13/1999 13:00'! slotHeaderFor: aSlotName ^ Morph new beTransparent extent: 9@22! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 11/8/2000 21:59'! arrowSetterButton: sel args: argArray | m | m _ RectangleMorph new color: (ScriptingSystem colorForType: #command); extent: 24@TileMorph defaultH; borderWidth: 0. m addMorphCentered: (ImageMorph new image: (ScriptingSystem formAtKey: 'Gets')). m setBalloonText: 'drag from here to obtain an assignment phrase.'. m on: #mouseDown send: sel to: self withValue: argArray. ^ m ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 10/28/1999 08:49'! arrowSetterButtonFor: partName type: partType | m | m _ RectangleMorph new color: (ScriptingSystem colorForType: #command); extent: 24@TileMorph defaultH; borderWidth: 0. m addMorphCentered: (ImageMorph new image: (ScriptingSystem formAtKey: 'Gets')). m setBalloonText: 'drag from here to obtain an assignment phrase.'. m on: #mouseDown send: #makeSetter:from:forPart: to: self withValue: (Array with: partName with: partType). ^ m ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 10/29/1998 15:59'! getterButtonFor: partName type: partType | m | m _ TileMorph new setOperator: partName. m typeColor: (ScriptingSystem colorForType: partType). m on: #mouseDown send: #makeGetter:from:forPart: to: self withValue: (Array with: partName with: partType). ^ m! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'RAA 1/13/2001 11:42'! makeGetter: evt from: aMorph forPart: args | m selfTile selector aType firstArg | (aType _ args last) == #unknown ifTrue: [^ self beep]. (#(colorSees isOverColor touchesA) includes: (firstArg _ args first)) ifFalse: [m _ PhraseTileMorph new setSlotRefOperator: args first asSymbol type: aType] ifTrue: [(firstArg == #colorSees) ifTrue: [m _ self colorSeesPhrase]. (firstArg == #isOverColor) ifTrue: [m _ self seesColorPhrase]. (firstArg == #touchesA) ifTrue: [m _ self touchesAPhrase]. ]. selfTile _ self tileForSelf bePossessive. selfTile position: m firstSubmorph position. m firstSubmorph addMorph: selfTile. selector _ m submorphs at: 2. (aType == #number) ifTrue: [selector addSuffixArrow]. selector updateLiteralLabel. m enforceTileColorPolicy. owner ifNotNil: [self primaryHand attachMorph: m] ifNil: [^ m]. ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 8/3/1999 15:47'! makeSetter: evt from: aMorph forPart: args | argType m argTile selfTile argValue | argType _ args last. m _ PhraseTileMorph new setAssignmentRoot: args first asSymbol type: #command rcvrType: #player argType: argType. argValue _ self scriptedPlayer perform: (ScriptingSystem getterSelectorFor: args first asSymbol). (argValue isKindOf: Player) ifTrue: [argTile _ argValue tileReferringToSelf] ifFalse: [argTile _ scriptedPlayer tileForArgType: argType inViewer: self. argTile setLiteral: argValue; updateLiteralLabel.]. argTile position: m lastSubmorph position. m lastSubmorph addMorph: argTile. selfTile _ self tileForSelf bePossessive. selfTile position: m firstSubmorph position. m firstSubmorph addMorph: selfTile. m enforceTileColorPolicy. owner ifNotNil: [self primaryHand attachMorph: m] ifNil: [^ m]. ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 11/9/2000 13:48'! newMakeGetter: evt from: aMorph forPart: aSpec "Button in viewer performs this to make a new style tile and attach to hand." | m | m _ self newTilesFor: scriptedPlayer getter: aSpec. owner ifNotNil: [self primaryHand attachMorph: m. m align: m topLeft with: evt hand position + (7@14)] ifNil: [^ m]. ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 11/9/2000 13:48'! newMakeSetter: evt from: aMorph forPart: aSpec "Button in viewer performs this to make a new style tile and attach to hand." | m | m _ self newTilesFor: scriptedPlayer setter: aSpec. owner ifNotNil: [self primaryHand attachMorph: m. m align: m topLeft with: evt hand position + (7@14)] ifNil: [^ m]. ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 2/3/2001 01:57'! newTilesFor: aPlayer command: aSpec | ms argTile argArray sel | "Return universal tiles for a command. Record who self is." sel _ aSpec second. aSpec size > 3 ifTrue: [argTile _ aPlayer tileForArgType: aSpec fourth inViewer: nil. argArray _ Array with: (aSpec fourth == #player ifTrue: [argTile actualObject] ifFalse: [argTile literal]). "default value for each type" sel == #colorSees ifTrue: [sel _ #color:sees:. argArray _ argArray, argArray]. "two colors" sel == #isOverColor ifTrue: [sel _ #seesColor:]. sel == #touchesA ifTrue: [sel _ #touchesA:]. ] ifFalse: [argArray _ #()]. ms _ MessageSend receiver: aPlayer selector: sel arguments: argArray. ^ ms asTilesIn: aPlayer class! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 1/26/2001 13:02'! newTilesFor: aPlayer getter: aSpec | ms argTile argArray | "Return universal tiles for a getter on this property. Record who self is." ms _ MessageSend receiver: aPlayer selector: aSpec seventh arguments: #(). aSpec second == #colorSees ifTrue: [ ms selector: #color:sees:. argTile _ aPlayer tileForArgType: #color inViewer: nil. argArray _ Array with: argTile colorSwatch color with: argTile colorSwatch color copy. ms arguments: argArray]. aSpec second == #isOverColor ifTrue: [ ms selector: #seesColor:. argTile _ aPlayer tileForArgType: #color inViewer: nil. ms arguments: (Array with: argTile colorSwatch color)]. aSpec second == #touchesA ifTrue: [ ms selector: #touchesA:. argTile _ aPlayer tileForArgType: #player inViewer: nil. ms arguments: (Array with: argTile actualObject)]. ^ ms asTilesIn: aPlayer class! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'tk 1/26/2001 12:44'! newTilesFor: aPlayer setter: aSpec | ms argValue | "Return universal tiles for a getter on this property. Record who self is." argValue _ aPlayer perform: (ScriptingSystem getterSelectorFor: aSpec second asSymbol). ms _ MessageSend receiver: aPlayer selector: aSpec ninth arguments: (Array with: argValue). ^ ms asTilesIn: aPlayer class! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 9/21/2000 22:36'! booleanPhraseForRetrieverOfType: retrieverType retrieverOp: retrieverOp player: aPlayer "Answer a boolean-valued phrase derived from a retriever (e.g. 'car's heading'); this is in order to assure that tiles laid down in a TEST area will indeed produce a boolean result" | outerPhrase getterPhrase receiverTile rel finalTile | rel _ (retrieverType == #number) ifTrue: [#<] ifFalse: [#=]. outerPhrase _ PhraseTileMorph new setOperator: rel type: #boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setOperator: retrieverOp type: retrieverType rcvrType: #player. getterPhrase submorphs last setSlotRefOperator: (Utilities inherentSelectorForGetter: retrieverOp). receiverTile _ (self tileForPlayer: aPlayer) bePossessive. receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ aPlayer tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 3/1/1999 11:56'! booleanPhraseFromPhrase: phrase | retrieverOp retrieverTile | phrase isBoolean ifTrue: [^ phrase]. scriptedPlayer costume isInWorld ifFalse: [^ Array new]. ((retrieverTile _ phrase submorphs last) isKindOf: TileMorph) ifFalse: [^ phrase]. retrieverOp _ retrieverTile operatorOrExpression. (#(color number player) includes: phrase resultType) ifTrue: [^ self booleanPhraseForRetrieverOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject]. ^ phrase! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 10/30/1998 18:15'! contentsSelection "Not well understood why this needs to be here!!" ^ 1 to: 0! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 3/1/1999 11:57'! invisiblySetPlayer: aPlayer scriptedPlayer _ aPlayer! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 11/18/1999 16:04'! outerViewer "Answer the StandardViewer or equivalent that contains this object" ^ self ownerThatIsA: Viewer! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 10/29/1998 15:59'! tileForPlayer: aPlayer "Return a tile representing aPlayer" ^ TileMorph new setObjectRef: nil "disused parm" actualObject: aPlayer; typeColor: (ScriptingSystem colorForType: #player) ! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 10/21/1998 14:55'! tileForSelf "Return a tile representing the target morph itself." ^ self tileForPlayer: scriptedPlayer ! ! WorldViewModel subclass: #CautiousModel instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Morphic'! !CautiousModel commentStamp: '' prior: 0! A model for a morphic world view which will ask for confirmation before being closed, unless the corresponding preference is set to false. ! !CautiousModel methodsFor: 'as yet unclassified' stamp: 'sw 9/15/1998 16:45'! okToChange Preferences cautionBeforeClosing ifFalse: [^ true]. Sensor leftShiftDown ifTrue: [^ true]. self beep. ^ self confirm: 'Warning!! If you answer "yes" here, this window will disappear and its contents will be lost!! Do you really want to do that?' "CautiousModel new okToChange"! ! Model subclass: #Celeste instanceVariableNames: 'mailDB currentCategory currentMessages currentTOC currentMsgID lastCategory subjectFilter fromFilter dateFilter customFilterBlock formatMessages lastCategoryList lastCategoryMenu messageTextView userPassword status tocLists participantFilter ' classVariableNames: 'CCList CustomFilters DeleteInboxAfterFetching FormatWhenFetching MessageCountLimit PopServer PopUserName SmtpServer SuppressWorthlessHeaderFields TimeZone UserName ' poolDictionaries: '' category: 'Network-Mail Reader'! !Celeste commentStamp: '' prior: 0! I am the core of a mail reading and organizing program. The name "Celeste" is a reference to an earlier mail reader named "Babar", which was written at Xerox PARC by Steve Putz and John Maloney. This object provides a user interface and some higher-level functionality for the application. The foundation of of the mail reader is really the mail database, implemented by the class MailDB. ! !Celeste methodsFor: 'open-close' stamp: 'jm 10/4/1998 14:01'! close "Close the mail database." userPassword _ nil. mailDB ifNotNil: [ mailDB close; release. mailDB _ nil]. ! ! !Celeste methodsFor: 'open-close' stamp: 'dvf 11/18/2000 17:06'! isActive ^mailDB notNil! ! !Celeste methodsFor: 'open-close' stamp: 'ls 1/27/2001 18:21'! openOnDatabase: aMailDB "Initialize myself for the mail database with the given root filename." mailDB _ aMailDB. mailDB addDependent: self. currentCategory _ 'new'. lastCategory _ ''. subjectFilter _ ''. fromFilter _ ''. participantFilter _ ''. dateFilter _ nil. self setCategory: currentCategory. ! ! !Celeste methodsFor: 'open-close' stamp: 'jm 8/20/1998 18:37'! windowIsClosing "Close the mail database when my window is closed." self close. ! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 11/22/1999 14:05'! addCategory "Create a new category with the user-specified name. This does nothing if the category already exists." | newCatName | newCatName _ FillInTheBlank request: 'Name for new category?'. (newCatName isEmpty) ifTrue: [^self]. "user aborted" self requiredCategory: newCatName. self setCategory: newCatName. ! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/28/2001 23:14'! cacheTOC "Caches a version of the TOC" | s tocString tocStringColumns | self initializeTocLists. currentTOC _ OrderedCollection new: currentMessages size. 'Processing ' , currentMessages size printString , ' messages.' displayProgressAt: Sensor cursorPoint from: 0 to: currentMessages size during: [:bar | 1 to: currentMessages size do: [:i | bar value: i. s _ WriteStream on: (String new: 100). s nextPutAll: i printString; space. (self tocLists at: 1) add: i printString. [s position < 4] whileTrue: [s space]. tocString _ mailDB getTOCstring: (currentMessages at: i). "columns from the database are 5" tocStringColumns _ mailDB getTOCstringAsColumns: (currentMessages at: i). s nextPutAll: tocString. currentTOC add: s contents. (self tocLists at: 2) add: ((tocStringColumns at: 5) ifTrue: ['@'] ifFalse: [' ']). (self tocLists at: 3) add: (tocStringColumns at: 1). (self tocLists at: 4) add: (tocStringColumns at: 2). (self tocLists at: 5) add: (tocStringColumns at: 4). (self tocLists at: 6) add: (tocStringColumns at: 3)]]. currentTOC _ currentTOC asArray. (currentMessages includes: currentMsgID) ifFalse: [currentMsgID _ nil]! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 8/20/1998 09:24'! categoriesKeystroke: aCharacter aCharacter asciiValue = 30 ifTrue: [self previousCategory]. aCharacter asciiValue = 31 ifTrue: [self nextCategory]. ! ! !Celeste methodsFor: 'categories pane'! category "Answer the currently selected category or nil." ^currentCategory! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 8/20/1998 10:25'! categoryList "Answer a list of categories for the categories pane." ^ mailDB allCategories ! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 21:29'! categoryMenu: aMenu "Answer the menu for the categories pane." aMenu add: 'save' action: #save. aMenu balloonTextForLastItem: 'Save the database'. aMenu addLine. aMenu add: 'fetch mail' action: #fetchMail. aMenu balloonTextForLastItem: 'Fetch new mail from the server'. aMenu add: 'send queued mail' action: #sendQueuedMail. aMenu balloonTextForLastItem: 'Send newly written mail'. aMenu addLine. aMenu add: 'add category' action: #addCategory. aMenu balloonTextForLastItem: 'Add a new organizational category'. currentCategory notNil ifTrue: [aMenu add: 'view all messages' action: #viewAllMessages. aMenu balloonTextForLastItem: 'View all the messages']. "add extra commands if a normal category is selected" (currentCategory notNil and: [currentCategory ~= '.all.' & (currentCategory ~= '.unclassified.')]) ifTrue: [aMenu add: 'edit category filter' action: #editCategoryFilter. aMenu balloonTextForLastItem: 'Edit a custom filter for this category'. aMenu add: 'rename category' action: #renameCategory. aMenu balloonTextForLastItem: 'Rename this organizational category'. aMenu add: 'remove category' action: #removeCategory. aMenu balloonTextForLastItem: 'Remove this organizational category (NB: all messages will be safely available in other categories)'. aMenu addLine. aMenu add: 'import into category' action: #importIntoCategory. aMenu balloonTextForLastItem: 'Import messages from a Unix/Eudora file into this category'. aMenu add: 'export category (Celeste)' action: #exportCategory. aMenu balloonTextForLastItem: 'Copy all messages from this category to another Celeste database'. aMenu add: 'export category (Unix/Eudora)' action: #exportCategoryUnix. aMenu balloonTextForLastItem: 'Write a copy of all messages from this category to a Unix/Eudora file']. aMenu addLine. aMenu add: 'empty trash' action: #emptyTrash. aMenu balloonTextForLastItem: 'Completely remove all messages in the category .trash. from Celeste'. aMenu add: 'salvage & compact' action: #compact. aMenu balloonTextForLastItem: 'Salvage any work done since the last database save & recover space used by old deleted messages. (This may be a bit slow)'. aMenu add: 'find duplicates' action: #findDuplicates. aMenu balloonTextForLastItem: 'Find messages which are exact duplicates'. aMenu addLine. aMenu addUpdating: #suppressingHeadersString action: #toggleSuppressHeaders. aMenu balloonTextForLastItem: 'Show either a complete or an easy-to-read message header'. aMenu add: 'change max current messages (' , self class messageCountLimit printString , ')' action: #changeMaxMessageCount. aMenu addLine. aMenu add: 'set user name' action: #setUserName. aMenu balloonTextForLastItem: 'Specify the ''From:'' user name for new messages'. aMenu add: 'set cc: list' action: #setCCList. aMenu balloonTextForLastItem: 'Specify a cc: list that is added to each new message'. aMenu add: 'set POP server' action: #setPopServer. aMenu balloonTextForLastItem: 'Specify which (POP3) server to check for new messages'. aMenu add: 'set POP username' action: #setPopUserName. aMenu balloonTextForLastItem: 'Specify the username to use when checking for new messages'. aMenu add: 'set SMTP server' action: #setSmtpServer. aMenu balloonTextForLastItem: 'Specify which (SMTP) server to use when sending messages'. aMenu addLine. aMenu addUpdating: #messagesOnServerString action: #toggleKeepMessagesOnServer. aMenu balloonTextForLastItem: 'When true, messages are not deleted from the server when you retreive them (typically used for testing only). When false, messages are deleted from the server after you retreive them'. ^ aMenu! ! !Celeste methodsFor: 'categories pane'! compact "Compact the messages file." | stats | Transcript cr; show: 'Compacting message file...'. Cursor execute showWhile: [stats _ mailDB compact]. Transcript show: 'Done.'; cr. Transcript show: 'Recovered ', (stats at: 1) printString, ' message', (((stats at: 1) > 1) ifTrue: ['s, '] ifFalse: [', ']), (stats at: 2) printString, ' bytes.'; cr.! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/12/1998 01:45'! deleteMessagesAfterFetching "start deleting messages off the server after POP-ing" DeleteInboxAfterFetching _ true! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 8/17/2000 17:38'! emptyTrash "Delete all messages in the '.trash.' category. WARNING: The messages will be totally removed from the Celeste index, and the .messages file will be marked so that the message contents are removed when it is next compressed." | msgList | self requiredCategory: '.trash.'. msgList _ mailDB messagesIn: '.trash.'. "Look at ALL messages in the trash" "Remove from the list messages which are also in other categories" msgList _ msgList select: [ :msgID | (mailDB categoriesThatInclude: msgID) size = 1]. mailDB deleteAll: msgList. mailDB cleanUpCategories. self updateTOC. (mailDB messagesIn: '.trash.') isEmpty ifFalse: [self inform: 'Some messages were not removed because they are also filed in other categories'].! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 11/22/1999 12:54'! exportCategory "Store the filtered message list of the current category to another mail database. The user is prompted for the name of the other database." | destDBName destDB | currentCategory ifNil: [ ^self ]. destDBName _ FillInTheBlank request: 'Destination mail database?' initialAnswer: ''. (destDBName isEmpty) ifTrue: [^self]. destDB _ MailDB openOn: destDBName. (destDB isNil) ifTrue: [^self]. destDB mergeMessages: (self filteredMessagesIn: currentCategory) from: mailDB. destDB saveDB. ! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/17/1998 05:42'! exportCategoryUnix "Store the filtered message list of the current category into a Eudora/Unix database" | destFileName destFile messageIds count | currentCategory ifNil: [ ^self ]. destFileName _ FillInTheBlank request: 'Destination mail file?' initialAnswer: ''. (destFileName isEmpty) ifTrue: [^self]. destFile _ FileStream fileNamed: destFileName. destFile ifNil: [ ^self error: 'could not open file' ]. destFile setToEnd. messageIds _ self filteredMessagesIn: currentCategory. ('exporting ', messageIds size printString, ' messages') displayProgressAt: Sensor mousePoint from: 0 to: messageIds size during: [ :bar | count _ 0. messageIds do: [ :messageId | destFile nextPutAll: Celeste eudoraSeparator. (mailDB getMessage: messageId) text linesDo: [ :line | (line beginsWith: 'From ') ifTrue: [ destFile nextPut: $> ]. destFile nextPutAll: line. destFile cr ]. count _ count + 1. bar value: count. ]. ]. destFile close. ! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 11/22/1999 12:55'! fetchMail "Append messages from the user's mailbox to this mail database." | server password msgCount | server _ self class popServer. password _ self popPassword. (password isNil or: [password isEmpty]) ifTrue: [^ self]. self requiredCategory: 'new'. msgCount _ mailDB fetchMailFromPOP: server userName: self class popUserName password: password doFormatting: FormatWhenFetching deleteFromServer: DeleteInboxAfterFetching. msgCount < 0 ifTrue: [self inform: 'could not connect to the mail server'] ifFalse: [self inform: msgCount printString, ' messages fetched']. msgCount <= 0 ifTrue: [^ self]. self setCategory: 'new'. ! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 11/22/1999 15:52'! findDuplicates "Find duplicate messages, and move the redundant copies to a given category." | duplicatesCategory | duplicatesCategory _ FillInTheBlank request: 'File duplicates in category?' initialAnswer: '.duplicates.'. duplicatesCategory isEmpty ifTrue:[^ self]. self requiredCategory: duplicatesCategory. Utilities informUser: 'Searching for duplicates...' during: [mailDB fileDuplicatesIn: duplicatesCategory]. self setCategory: duplicatesCategory. ! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/17/1998 05:17'! importIntoCategory "Add the messages from a Unix or Eudora format file into this category" | inboxPath count | currentCategory ifNil: [ ^self ]. "get the file to import from" inboxPath _ ''. [ inboxPath _ FillInTheBlank request: 'file to import from?\(should be Eudora or Unix format)' withCRs. inboxPath isEmpty ifTrue: [ ^self ]. FileStream isAFileNamed: inboxPath ] whileFalse: [ self inform: 'file does not exist' ]. Utilities informUser: 'Fetching mail from ', inboxPath during: [ count _ mailDB importMailFrom: inboxPath intoCategory: currentCategory. ]. self inform: count printString, ' messages imported'. self updateTOC.! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/12/1998 01:45'! keepMessagesOnServer "start keeping messages on the server after POP-ing" DeleteInboxAfterFetching _ false! ! !Celeste methodsFor: 'categories pane' stamp: 'sma 4/22/2000 20:17'! maxMessageCount ^ 200! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 20:12'! maxMessagesToDisplay "return the maximum number of mesasges to display at one time; the motivation is that huge lists of messages are impractical" ^ self class messageCountLimit! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 20:25'! messages: actuallyShown from: possible self class includeStatusPane ifTrue: [ self status: 'Showing ' , actuallyShown printString , ' of ' , possible printString , ' messages in "' , self category , '"']! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 21:07'! messagesOnServerString | string | string _ 'leave messages on server'. ^ DeleteInboxAfterFetching ifTrue: ['' , string] ifFalse: ['' , string]! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 10/4/1998 10:39'! nextCategory "Select the next category." | catList i | catList _ self categoryList. (currentCategory isNil) ifTrue: [currentCategory _ catList last]. i _ catList indexOf: currentCategory. i < catList size ifTrue: [self setCategory: (catList at: i + 1)] ifFalse: [self setCategory: (catList at: 1)]. ! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 10/4/1998 10:39'! previousCategory "Select the next category." | catList i | catList _ self categoryList. (currentCategory isNil) ifTrue: [currentCategory _ catList last]. i _ catList indexOf: currentCategory. i > 1 ifTrue: [self setCategory: (catList at: i - 1)] ifFalse: [self setCategory: (catList at: catList size)]. ! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 10/4/1998 10:37'! removeCategory "Remove the existing category with the user-specified name." | msgList | currentCategory ifNil: [ ^self ]. msgList _ mailDB messagesIn: currentCategory. (mailDB messagesIn: '.trash.') do: [: id | msgList remove: id ifAbsent: []]. msgList isEmpty ifFalse: [ (self confirm: 'This category is not empty. Are you sure you wish to remove it?') ifFalse: [^self]]. mailDB removeCategory: currentCategory. self changed: #categoryList. self setCategory: nil. ! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/17/1998 05:45'! renameCategory "Rename the category with the user-specified name." | newCatName | currentCategory ifNil: [ ^self ]. newCatName _ FillInTheBlank request: 'New name?' initialAnswer: currentCategory. (newCatName isEmpty) ifTrue: [^self]. "user aborted" mailDB renameCategory: currentCategory to: newCatName. currentCategory _ newCatName. self changed: #categoryList.! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 8/20/1998 18:34'! save "Snapshot the database to disk." mailDB saveDB. ! ! !Celeste methodsFor: 'categories pane'! setCCList "Change the default cc: list for use in composing messages." self class setCCList.! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 20:20'! setCategory: newCategory "Change the currently selected category. We must also compute the table of contents and message list for the new category." | messageCount | currentCategory _ newCategory. newCategory isNil ifTrue: [currentMessages _ currentTOC _ currentMsgID _ nil. self class includeStatusPane ifTrue: [status _ nil]] ifFalse: [currentMessages _ self filteredMessagesIn: newCategory. messageCount _ currentMessages size. messageCount > self maxMessagesToDisplay ifTrue: [self messages: self maxMessagesToDisplay from: messageCount. currentMessages _ currentMessages copyLast: self maxMessagesToDisplay] ifFalse: [self messages: messageCount from: messageCount]. self cacheTOC]. self changed: #category. self changed: #tocEntryList. self changed: #tocEntry. self changed: #messageText. self changed: #status! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/12/1998 00:37'! setPopServer ^self class setPopServer! ! !Celeste methodsFor: 'categories pane' stamp: 'mdr 7/31/2000 19:03'! setPopUserName userPassword _ nil. "Clear the password when a new username is set" ^self class setPopUserName! ! !Celeste methodsFor: 'categories pane' stamp: 'ls 9/13/1998 02:31'! setSmtpServer ^self class setSmtpServer! ! !Celeste methodsFor: 'categories pane'! setUserName "Change the user's email name for use in composing messages." self class setUserName.! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 21:02'! suppressingHeadersString | string | string _ 'suppress header'. ^ SuppressWorthlessHeaderFields ifTrue: ['' , string] ifFalse: ['' , string]! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 21:22'! toggleKeepMessagesOnServer DeleteInboxAfterFetching _ DeleteInboxAfterFetching not! ! !Celeste methodsFor: 'categories pane' stamp: 'jm 8/20/1998 10:35'! toggleSuppressHeaders SuppressWorthlessHeaderFields _ SuppressWorthlessHeaderFields not. self changed: #messageText. ! ! !Celeste methodsFor: 'categories pane' stamp: 'sbw 1/30/2001 19:50'! viewAllMessages currentMessages _ self filteredMessagesIn: self category. self messages: currentMessages size from: currentMessages size. self cacheTOC. self changed: #tocEntryList. self changed: #tocEntry. self changed: #messageText! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 1/27/2001 17:04'! autoFile "automatically pick a folder for the current message, and file the current message there" | folder | folder := self chooseFilterForCurrentMessage. folder ifNil: [ ^self]. mailDB file: currentMsgID inCategory: folder.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 1/27/2001 17:04'! autoMove "automatically pick a folder for the current message, and move the message there" | folder | folder := self chooseFilterForCurrentMessage. folder ifNil: [ ^self]. mailDB file: currentMsgID inCategory: folder. self removeMessage.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:01'! deleteAll "Move all visible messages in the current category to '.trash.'." | | self requiredCategory: '.trash.'. mailDB fileAll: currentMessages inCategory: '.trash.'. self removeAll.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'sbw 2/1/2001 10:25'! deleteMessage "Move the current message to the '.trash.' category and select the next message. Deleted messages can later purged by the 'empty trash' menu item" currentMsgID isNil ifTrue: [^ self]. self requiredCategory: '.trash.'. mailDB file: currentMsgID inCategory: '.trash.'. self removeMessage! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:11'! fileAgain "File the current message in the same category as last time." | newCatName | (lastCategory isEmpty not) ifTrue: [newCatName _ lastCategory] ifFalse: [newCatName _ self getCategoryNameIfNone: [^self]]. mailDB file: currentMsgID inCategory: newCatName. ! ! !Celeste methodsFor: 'table of contents pane'! fileAll "File all visible messages in the current category in some other category as well." | newCatName msgList | newCatName _ self getCategoryNameIfNone: [^self]. msgList _ self filteredMessagesIn: currentCategory. mailDB fileAll: msgList inCategory: newCatName. self updateTOC.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:07'! fileMessage "File the current message in another category." | newCatName | newCatName _ self getCategoryNameIfNone: [^self]. mailDB file: currentMsgID inCategory: newCatName. ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'jm 8/20/1998 08:14'! getCategoryNameIfNone: aBlock "Prompt the user for a category name, remembering it for the next time." | catList categoryName | catList _ self categoryList. catList remove: '.all.' ifAbsent: []. catList remove: '.trash.' ifAbsent: []. catList remove: '.unclassified.' ifAbsent: []. catList add: ''. ((lastCategoryList ~= catList) or: [lastCategoryMenu = nil]) ifTrue: [lastCategoryMenu _ CustomMenu selections: catList]. categoryName _ lastCategoryMenu startUp. categoryName = nil ifTrue: [^aBlock value]. categoryName = '' ifTrue: [ categoryName _ FillInTheBlank request: 'New category name?' initialAnswer: ''. (categoryName isEmpty) ifTrue: [^aBlock value]. lastCategoryMenu _ nil. mailDB addCategory: categoryName. self changed: #categoryList. ]. lastCategoryList _ catList. ^lastCategory _ categoryName! ! !Celeste methodsFor: 'table of contents pane' stamp: 'mdr 8/31/2000 11:47'! moveAgain "Move the current message to the same category as last time." | newCatName | currentMsgID ifNil: [ ^self ]. (lastCategory isEmpty not) ifTrue: [newCatName _ lastCategory] ifFalse: [newCatName _ self getCategoryNameIfNone: [^self]]. newCatName = currentCategory ifTrue: [ ^self ]. mailDB file: currentMsgID inCategory: newCatName. self removeMessage.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:09'! moveAll "Move all visible messages in the current category to another category." | newCatName | newCatName _ self getCategoryNameIfNone: [^self]. newCatName = currentCategory ifTrue:[ ^self ]. mailDB fileAll: currentMessages inCategory: newCatName. self removeAll.! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:08'! moveMessage "Move the current message to another category; this consists of filing it in the new category, and then removing it from the current category" | newCatName | newCatName _ self getCategoryNameIfNone: [^self]. newCatName = currentCategory ifTrue: [ ^self ]. mailDB file: currentMsgID inCategory: newCatName. self removeMessage. ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'mdr 7/12/1999 15:16'! nextMessage "Select the next message." | index | (currentCategory isNil | currentMsgID isNil) ifTrue: [^ self]. index _ currentMessages indexOf: currentMsgID. index < currentMessages size ifTrue: [self setTOCEntry: (currentTOC at: index + 1)] ifFalse: [self setTOCEntry: (currentTOC at: 1)]. ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'jm 8/20/1998 08:14'! otherCategories "Prompt the user with a menu of all other categories in which the currently selected message appears. If the user chooses a category from this menu, go to that category." | otherCategories choice | otherCategories _ (mailDB categoriesThatInclude: currentMsgID) asOrderedCollection. otherCategories remove: currentCategory ifAbsent: []. (otherCategories isEmpty) ifTrue: [^self]. choice _ (CustomMenu selections: otherCategories) startUp. choice = nil ifFalse: [self setCategory: choice].! ! !Celeste methodsFor: 'table of contents pane' stamp: 'dvf 4/27/2000 18:58'! partsMenu | menu currMessage part | menu _ CustomMenu new. currMessage _ self currentMessage. currMessage body atomicParts do: [:e | menu add: 'save ' , e printString action: e]. part _ menu startUp. part ifNotNil: [part save]! ! !Celeste methodsFor: 'table of contents pane' stamp: 'mdr 7/12/1999 15:16'! previousMessage "Select the previous message." | index | (currentCategory isNil | currentMsgID isNil) ifTrue: [^ self]. index _ currentMessages indexOf: currentMsgID. index > 1 ifTrue: [self setTOCEntry: (currentTOC at: index - 1)] ifFalse: [self setTOCEntry: (currentTOC at: currentMessages size)]. ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'di 2/3/2001 12:30'! removeAll "Remove all messages from the current category." mailDB removeAll: currentMessages fromCategory: currentCategory. currentMsgID _ nil. currentMessages _ #(). currentTOC _ #(). self initializeTocLists. self changed: #tocEntryList. self changed: #tocEntry. self changed: #messageText ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'sbw 2/1/2001 10:44'! removeMessage "Remove the current message from the current category." | currentMessageIndex | currentMsgID ifNil: [^ self]. mailDB remove: currentMsgID fromCategory: currentCategory. "remove the message from the listing" currentMessageIndex _ currentMessages indexOf: currentMsgID. currentMessages _ currentMessages copyWithout: currentMsgID. currentTOC _ currentTOC copyWithoutIndex: currentMessageIndex. 1 to: self tocLists size do: [:index | (tocLists at: index) removeAt: currentMessageIndex]. "update the message index and message ID" currentMessages isEmpty ifTrue: [currentMsgID _ nil] ifFalse: [currentMsgID _ currentMessages at: (currentMessageIndex min: currentMessages size)]. self changed: #tocEntryList. self changed: #tocEntry. self changed: #messageText! ! !Celeste methodsFor: 'table of contents pane' stamp: 'dvf 4/27/2000 00:14'! saveMessage "save the currently selected message to a file" | fileName file | currentMsgID ifNil: [^ self]. fileName _ FillInTheBlank request: 'file to save in'. fileName isEmpty ifTrue: [^ self]. file _ FileStream fileNamed: fileName. file nextPutAll: (self currentMessage) text. file close! ! !Celeste methodsFor: 'table of contents pane' stamp: 'mdr 11/22/1999 13:14'! search "Search the text of all messages in the present category" | destCat matchString msgText | destCat _ FillInTheBlank request: 'In what category should the search results be filed?' initialAnswer: '.search results.'. (destCat isEmpty) ifTrue: [^self]. matchString _ FillInTheBlank request: 'String sought in message text?' initialAnswer: ''. (matchString isEmpty) ifTrue: [^self]. self requiredCategory: destCat. (self filteredMessagesIn: currentCategory) do: [: msgID | msgText _ mailDB getText: msgID. ((msgText findString: matchString startingAt: 1) > 0) ifTrue: [mailDB file: msgID inCategory: destCat]]. self setCategory: destCat. ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'bf 3/10/2000 11:29'! selectMessage: id "Change the currently selected message." currentMsgID _ id. self changed: #tocEntry. self changed: #messageText ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'sbw 2/2/2001 13:03'! setTOCEntry: newTOCentry "Change the currently selected message. This is done by finding the message ID corresponding to the selected table of contents entry." | i | newTOCentry isNil | currentTOC isNil ifTrue: [currentMsgID _ nil] ifFalse: [i _ (self tocLists at: 1) indexOf: newTOCentry ifAbsent: []. i isNil ifTrue: [currentMsgID _ nil] ifFalse: [currentMsgID _ currentMessages at: i]]. self changed: #tocEntry. Cursor read showWhile: [self changed: #messageText]! ! !Celeste methodsFor: 'table of contents pane' stamp: 'sbw 1/28/2001 23:18'! tocEntry "Answer the table of contents entry for the currently selected message or nil." currentMsgID isNil ifTrue: [^ nil] ifFalse: [^ (self tocLists at: 1) at: (currentMessages indexOf: currentMsgID)]! ! !Celeste methodsFor: 'table of contents pane' stamp: 'sbw 1/28/2001 23:18'! tocEntryList currentCategory isNil ifTrue: [self initializeTocLists]. self tocLists == nil ifTrue: [self initializeTocLists]. ^ self tocLists! ! !Celeste methodsFor: 'table of contents pane' stamp: 'dvf 7/1/2000 15:24'! tocKeystroke: aCharacter aCharacter = Character backspace ifTrue: [self deleteMessage]. aCharacter asciiValue = 30 ifTrue: [self previousMessage]. aCharacter asciiValue = 31 ifTrue: [self nextMessage]. aCharacter = $c ifTrue: [self customFilterOn]. aCharacter = $m ifTrue: [self customFilterMove]. aCharacter = $s ifTrue: [self subjectFilterOn] ! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 1/27/2001 18:18'! tocMenu: aMenu "Answer the menu for the table of contents pane." | messageSelected autoFolder | currentCategory ifNil: [^ nil]. messageSelected _ currentMsgID isNil not. messageSelected ifTrue: [aMenu add: 'delete' action: #deleteMessage. aMenu balloonTextForLastItem: 'Move this message to the .trash. category'. aMenu addLine. aMenu add: 'compose' action: #compose. aMenu balloonTextForLastItem: 'Compose a new message'. aMenu add: 'reply' action: #reply. aMenu balloonTextForLastItem: 'Reply to this message'. aMenu add: 'forward' action: #forward. aMenu balloonTextForLastItem: 'Forward this message'. self currentMessage body isMultipart ifTrue: [aMenu add: 'parts...' action: #partsMenu. aMenu balloonTextForLastItem: 'Forward this message']. aMenu addLine. lastCategory isEmpty ifFalse: [aMenu add: 'file -> ' , lastCategory action: #fileAgain. aMenu balloonTextForLastItem: 'Add this message also to the category ' , lastCategory. aMenu add: 'move -> ' , lastCategory action: #moveAgain. aMenu balloonTextForLastItem: 'Move this message to the category ' , lastCategory. aMenu addLine]. autoFolder := self chooseFilterForCurrentMessage. autoFolder ifNotNil: [ aMenu add: ('file -> ', autoFolder) action: #autoFile. aMenu add: ('move -> ', autoFolder) action: #autoMove. aMenu addLine ]. aMenu add: 'file' action: #fileMessage. aMenu balloonTextForLastItem: 'Add this message also to a different category'. aMenu add: 'move' action: #moveMessage. aMenu balloonTextForLastItem: 'Move this message to a different category'. aMenu add: 'remove' action: #removeMessage. aMenu balloonTextForLastItem: 'Remove this message from this category (NB: the message will be safely available in another category)'. aMenu addLine] ifFalse: [aMenu add: 'compose' action: #compose. aMenu balloonTextForLastItem: 'Compose a new message'. aMenu addLine]. "The following are common for all menus" aMenu add: 'file all' action: #fileAll. aMenu balloonTextForLastItem: 'Add all messages also to another category'. aMenu add: 'move all' action: #moveAll. aMenu balloonTextForLastItem: 'Move all messages to another category'. aMenu add: 'remove all' action: #removeAll. aMenu balloonTextForLastItem: 'Remove all messages from this catgegory (NB: each message will be safely available in other categories)'. aMenu add: 'delete all' action: #deleteAll. aMenu balloonTextForLastItem: 'Move all messages to the .trash. category'. aMenu addLine. messageSelected ifTrue: [aMenu add: 'other categories' action: #otherCategories. aMenu balloonTextForLastItem: 'Check which other categories also contain this message'. aMenu add: 'save message' action: #saveMessage. aMenu balloonTextForLastItem: 'Save this message'. aMenu addLine]. self tocEntryList size = self maxMessagesToDisplay ifTrue: [ "the test above is not exactly correct, but is usually correct." aMenu add: 'view all messages' action: #viewAllMessages. aMenu balloonTextForLastItem: 'View all messages that match the current filters, even if there are many thousands of such messages'. ]. aMenu add: 'search' action: #search. aMenu balloonTextForLastItem: 'Search all messages in this category for something'. ^ aMenu! ! !Celeste methodsFor: 'table of contents pane' stamp: 'ls 6/20/2000 11:33'! updateTOC "Update the table of contents after a moving, removing, or deleting a message. Select a message near the removed message in the table of contents if possible." | savedMsgID | "remember the currently selected message" savedMsgID _ currentMsgID. "update the TOC listing" currentMsgID _ nil. self setCategory: currentCategory. "update currentMessages, currentTOC" "try to select the previously selected message; if impossible, select the first message" currentMessages isEmptyOrNil ifFalse: [ (currentMessages includes: savedMsgID) ifTrue: [ currentMsgID _ savedMsgID ] ifFalse: [ currentMsgID _ currentMessages first ] ]. "NB: self changed: #tocEntryList is already done above by setCategory: and can be slow" self changed: #tocEntry. self changed: #messageText. ! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:19'! chooseFilterFor: msgID from: filterNames | res | res _ self filtersFor: msgID from: filterNames. res isEmpty ifTrue:[^nil]. res size = 1 ifTrue: [^res anyOne]. ^self selectFilterFrom: res ! ! !Celeste methodsFor: 'filtering' stamp: 'ls 1/27/2001 17:00'! chooseFilterForCurrentMessage "automatically choose a filter to move the selected message. Returns nil if there isn't a message selected, or if there isn't exactly 1 matching filter" | matchingFilters | currentMsgID ifNil: [ ^nil ]. matchingFilters := self filtersFor: currentMsgID from: CustomFilters keys. matchingFilters size = 1 ifTrue: [ ^matchingFilters someElement ] ifFalse: [ ^nil ]! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:28'! customFilterMove "Select or define and activate a custom filter." | filterList filterName msgList | filterList _ CustomFilters keys select: [:e | self categoryList includes: e]. filterName _ currentMsgID ifNil: [self selectFilterFrom: filterList] ifNotNil: [self chooseFilterFor: currentMsgID from: filterList]. ((filterName isNil or: [filterName isEmpty]) or: [filterName = 'none']) ifTrue: [^ self]. customFilterBlock _ self customFilterNamed: filterName. msgList _ self filteredMessagesIn: currentCategory. mailDB removeAll: msgList fromCategory: currentCategory. mailDB fileAll: msgList inCategory: filterName. customFilterBlock _ nil. self updateTOC! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:06'! customFilterNamed: filterName ^self makeFilterFor: (CustomFilters at: filterName) ! ! !Celeste methodsFor: 'filtering' stamp: 'ls 10/17/1998 16:20'! customFilterOff "Cancel custom filtering." customFilterBlock ifNil: [ "it's already turned off" ^self ]. customFilterBlock _ nil. self updateTOC. self changed: #isCustomFilterOn.! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 1/28/2001 19:16'! customFilterOn "Select or define and activate a custom filter." | filterName filterExpr filterMenu | filterMenu := CustomMenu new. currentMsgID ifNotNil: [ (self filtersFor: currentMsgID from: (CustomFilters keys asSortedArray)) do: [ :name | filterMenu add: name action: name ]. filterMenu addLine.]. filterMenu add: '(none)' action: #none. filterMenu add: '' action: #define. filterMenu add: '' action: #edit. filterMenu add: '' action: #delete. filterMenu addLine. (CustomFilters keys asSortedArray) do: [ :name | filterMenu add: name action: name ]. filterName _ filterMenu startUpWithCaption: 'Select a filter:'. filterName ifNil: [ ^self ]. filterName = #none ifTrue: [^self customFilterOff ]. filterName = #delete ifTrue: [ ^self deleteFilter]. filterName = #edit ifTrue: [filterExpr _ self editFilter] ifFalse: [ filterName = #define ifTrue: [filterExpr _ self defineFilter] ifFalse: [filterExpr _ CustomFilters at: filterName]]. filterExpr isEmpty ifTrue: [^self]. customFilterBlock _ Compiler evaluate: '[ :m | ', filterExpr, ']'. self updateTOC. self changed: #isCustomFilterOn.! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/4/2000 00:48'! defineFilter | filterName | filterName _ FillInTheBlank request: 'Filter name?'. filterName isEmpty ifTrue: [^ '']. ^self editFilterNamed: filterName ! ! !Celeste methodsFor: 'filtering' stamp: 'jm 8/20/1998 08:14'! deleteFilter | filterList filterName | CustomFilters isEmpty ifTrue: [^'']. filterList _ CustomFilters keys asOrderedCollection. filterName _ (CustomMenu selections: filterList) startUpWithCaption: 'Filter to delete?'. filterName = nil ifTrue: [^'']. CustomFilters removeKey: filterName ifAbsent: [].! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/4/2000 00:49'! editCategoryFilter self editFilterNamed: currentCategory! ! !Celeste methodsFor: 'filtering' stamp: 'jm 8/20/1998 08:14'! editFilter | filterList filterName | CustomFilters isEmpty ifTrue: [^'']. filterList _ CustomFilters keys asOrderedCollection. filterName _ (CustomMenu selections: filterList) startUpWithCaption: 'Filter to edit?'. filterName = nil ifTrue: [^'']. ^self editFilterNamed: filterName filterExpr: (CustomFilters at: filterName)! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/4/2000 00:47'! editFilterNamed: filterName | expr | (CustomFilters includesKey: filterName) ifTrue: [expr _ CustomFilters at: filterName] ifFalse: [expr _ '']. ^ self editFilterNamed: filterName filterExpr: expr! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:22'! editFilterNamed: filterName filterExpr: oldExpr | newDefinition | newDefinition _ FillInTheBlank request: 'Enter a filter definition where "m" is the message being testing. The expression can send "fromHas:", "toHas:", "ccHas:", "subjectHas:", "participantHas:", or "textHas:" to m to test for inclusion of a string--or one of an array of strings--in a field. It can also test m''s time and/or date and can combine several tests with logical operators. Examples: m fromHas: ''johnm'' -- messages from johnm m participantHas: ''johnm'' -- messages from, to, or cc-ing johnm m textHas: #(squeak smalltalk java) -- messages with any of these words m subjectHas: #(0 1 2 3 4 5 6 7 8 9) -- numbers in lists treated as strings NOTE: "textHas:" is very slow, since it must read the message from disk.' initialAnswer: oldExpr. newDefinition isEmpty ifTrue: [^'']. CustomFilters at: filterName put: newDefinition. ^filterName! ! !Celeste methodsFor: 'filtering' stamp: 'ls 1/27/2001 18:33'! filteredMessagesIn: categoryName | msgList | msgList _ mailDB messagesIn: categoryName. (customFilterBlock notNil) ifTrue: [msgList _ msgList select: [: id | customFilterBlock value: (mailDB getTOCentry: id) ]]. (fromFilter size > 0) ifTrue: [msgList _ msgList select: [: id | (mailDB getTOCentry: id) fromHas: fromFilter ]]. (participantFilter size > 0) ifTrue: [msgList _ msgList select: [: id | (mailDB getTOCentry: id) participantHas: participantFilter ]]. (subjectFilter size > 0) ifTrue: [msgList _ msgList select: [: id | (mailDB getTOCentry: id) subject includesSubstring: subjectFilter caseSensitive: false]]. ^msgList! ! !Celeste methodsFor: 'filtering' stamp: 'ls 1/27/2001 17:24'! filtersFor: msgID from: filterNames | currentTocEntry | currentTocEntry := mailDB getTOCentry: msgID. ^filterNames select: [:e | (self customFilterNamed: e) value: currentTocEntry].! ! !Celeste methodsFor: 'filtering'! fromFilterOff "Cancel from filtering." fromFilter _ ''. self updateTOC.! ! !Celeste methodsFor: 'filtering' stamp: 'mdr 1/14/2000 14:49'! fromFilterOn: aSwitch "Show only those messages from the same person as the currently selected message. The user is given a chance to edit the pattern string used to match 'From:' fields." fromFilter _ (currentMsgID isNil) ifTrue: [''] ifFalse: [(mailDB getTOCentry: currentMsgID) from]. fromFilter _ FillInTheBlank request: '''From:'' filter pattern?' initialAnswer: fromFilter. fromFilter = '' ifTrue: [aSwitch turnOff. ^self]. "User cancelled, so turn off the switch" fromFilter _ fromFilter withoutTrailingBlanks. self updateTOC.! ! !Celeste methodsFor: 'filtering' stamp: 'ls 10/15/1998 14:57'! isCustomFilterOn "whether there is a custom filter in effect" ^customFilterBlock ~~ nil! ! !Celeste methodsFor: 'filtering' stamp: 'ls 1/27/2001 18:25'! isParticipantFilterOn "return whether a non-trivial participant filter is installed" ^participantFilter notNil and: [ participantFilter isEmpty not ]! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 7/1/2000 15:45'! isSubjectFilterOn "whether there is a subject filter in effect" ^ subjectFilter isEmptyOrNil not! ]style[(17 2 45 4 13 17)f3b,f3,f3c150048000,f3,f3cblack;,f3! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:06'! makeFilterFor: filterExpr ^Compiler evaluate: '[ :m | ', filterExpr, ']'. ! ! !Celeste methodsFor: 'filtering' stamp: 'ls 1/27/2001 18:33'! participantFilterOn "Show only those messages where a specified user is either the sender or a receiver." participantFilter _ (currentMsgID isNil) ifTrue: [''] ifFalse: [(mailDB getTOCentry: currentMsgID) from]. participantFilter _ FillInTheBlank request: '''Participant:'' filter pattern?' initialAnswer: participantFilter. participantFilter _ participantFilter withBlanksTrimmed. self updateTOC. self changed: #isParticipantFilterOn.! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 6/17/2000 22:24'! selectFilterFrom: filters | filterName filterList | filterList _ filters asSortedCollection asOrderedCollection. filterList addFirst: '(none)'. filterList addLast: ''. filterList addLast: ''. filterList addLast: ''. filterName _ (CustomMenu selections: filterList) startUpWithCaption: 'Select a filter:'. (filterName isNil or: [filterName isEmpty]) ifTrue: [ ^nil ]. filterName = '(none)' ifTrue: [^'none']. filterName = '' ifTrue: [ ^self deleteFilter]. filterName = '' ifTrue: [filterName _ self editFilter] ifFalse: [ filterName = '' ifTrue: [filterName _ self defineFilter]]. ^filterName! ! !Celeste methodsFor: 'filtering'! subjectFilterOff "Cancel subject filter." subjectFilter _ ''. self updateTOC.! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 7/1/2000 15:42'! subjectFilterOn "Show only those messages whose subject matches the currently selected message. The user is given a chance to edit the pattern string used to match 'Subject:' fields." subjectFilter _ currentMsgID isNil ifTrue: [''] ifFalse: [(mailDB getTOCentry: currentMsgID) subject]. subjectFilter _ subjectFilter normalizedSubject. subjectFilter _ FillInTheBlank request: '''Subject:'' filter pattern?' initialAnswer: subjectFilter. self updateTOC. self changed: #isSubjectFilterOn! ]style[(15 2 173 2 13 3 12 20 2 17 6 14 12 13 13 3 13 21 13 3 14 10 30 16 13 3 4 27 18)f3b,f3,f3c150048000,f3,f3cblack;,f3,f3cblack;,f3,f3cblue;,f3,f3cblack;,f3,f3cblack;,f3,f3cblack;,f3,f3cblack;,f3,f3cblack;,f3,f3cblack;,f3,f3cblue;,f3,f3cblack;,f3,f3cblack;,f3,f3cblue;! ! !Celeste methodsFor: 'filtering' stamp: 'dvf 7/1/2000 14:17'! subjectFilterOn: aSwitch "Show only those messages whose subject matches the currently selected message. The user is given a chance to edit the pattern string used to match 'Subject:' fields." subjectFilter _ currentMsgID isNil ifTrue: [''] ifFalse: [(mailDB getTOCentry: currentMsgID) subject]. subjectFilter _ subjectFilter normalizedSubject. subjectFilter _ FillInTheBlank request: '''Subject:'' filter pattern?' initialAnswer: subjectFilter. subjectFilter = '' ifTrue: [aSwitch turnOff. ^ self]. "User cancelled so turn off the switch and return" self updateTOC! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 9/13/1998 02:56'! compose "Make a MailSendTool for composing a new message." self openSender: self composeText.! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 9/14/1998 22:23'! doItContext ^nil! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 9/14/1998 22:23'! doItReceiver ^nil! ! !Celeste methodsFor: 'message text pane' stamp: 'jm 8/20/1998 11:59'! format messageTextView editString: self formatedMessageText; hasUnacceptedEdits: true. ! ! !Celeste methodsFor: 'message text pane' stamp: 'dvf 4/27/2000 00:14'! formatedMessageText "Answer a string that is my formatted mail message." | message header body bodyText | currentMsgID isNil ifTrue: [^ '']. message _ self currentMessage. header _ message cleanedHeader. body _ message body. body contentType = 'text/html' ifTrue: [bodyText _ (HtmlParser parse: (ReadStream on: body content)) formattedText] ifFalse: [bodyText _ body content]. ^ header asText , String cr , bodyText! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 9/13/1998 02:57'! forward "Make a MailSendTool for forwarding the current message." (currentMsgID notNil) ifTrue: [self openSender: (self forwardTextFor: currentMsgID)].! ! !Celeste methodsFor: 'message text pane'! message "Answer the text of the currently selected message or nil if there isn't one." (currentMsgID isNil) ifTrue: [^''] ifFalse: [^(mailDB getText: currentMsgID) asText]! ! !Celeste methodsFor: 'message text pane' stamp: 'jm 8/20/1998 08:14'! messageMenu "Answer the menu for the message text pane." (currentMsgID notNil) ifTrue: [^CustomMenu labels: 'again\undo\copy\cut\paste\format\accept\cancel compose\reply\forward' withCRs lines: #(2 5 6 8) selections: #(again undo copySelection cut paste format accept cancel compose reply forward)] ifFalse: [^SelectionMenu labels: 'again\undo\copy\cut\paste\compose' withCRs lines: #(2 5) selections: #(again undo copySelection cut paste compose)].! ! !Celeste methodsFor: 'message text pane' stamp: 'jm 8/20/1998 11:28'! messageMenu: aMenu shifted: shifted "Use the standard text menu." ^ StringHolder basicNew codePaneMenu: aMenu shifted: shifted ! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 4/30/2000 18:33'! messageText "Answer the text which makes up the complete message (header+body)" (currentMsgID isNil) ifTrue: [^'']. "Always show the full message header for messages in the category .tosend. so that all special header lines are preserved, shown and can be edited." (currentCategory = '.tosend.') ifTrue: [^ mailDB getText: currentMsgID]. SuppressWorthlessHeaderFields ifTrue: [^ self currentMessage formattedText] ifFalse: [^ mailDB getText: currentMsgID]. ! ! !Celeste methodsFor: 'message text pane' stamp: 'bf 3/9/2000 17:54'! messageText: aStringOrText currentCategory isNil | currentMsgID isNil ifTrue: [^ self]. mailDB newText: aStringOrText asString squeakToIso for: currentMsgID. self updateTOC. "in case the message header was changed" messageTextView hasUnacceptedEdits: false. self changed: #messageText. ^ true ! ! !Celeste methodsFor: 'message text pane' stamp: 'jm 8/14/1998 15:13'! messageTextView: aView messageTextView _ aView. ! ! !Celeste methodsFor: 'message text pane' stamp: 'ls 9/13/1998 03:08'! reply "Make a MailSendTool for replying to the current message." (currentMsgID notNil) ifTrue: [self openSender: (self replyTextFor: currentMsgID)].! ! !Celeste methodsFor: 'other' stamp: 'sbw 1/30/2001 20:14'! changeMaxMessageCount | countString count | countString _ FillInTheBlank request: 'Maximum number of messages displayed?' initialAnswer: self class messageCountLimit printString. countString isEmpty ifTrue: [^ self]. count _ Integer readFrom: (ReadStream on: countString). count _ count max: 10. count _ count min: 500. "Arbitrary. We could calculate the real upper bound." self class messageCountLimit: count. self setCategory: currentCategory! ! !Celeste methodsFor: 'other' stamp: 'jm 8/14/1998 15:26'! clearUserEditFlag messageTextView hasUnacceptedEdits: false. ! ! !Celeste methodsFor: 'other' stamp: 'dvf 4/27/2000 00:12'! currentMessage ^mailDB getMessage: currentMsgID! ! !Celeste methodsFor: 'other' stamp: 'jm 8/20/1998 08:14'! okToChange "This message is sent when changing the selection in either the message cateory or message list panes. Eventually, this should ask the user if it is okay to throw away and unaccepted edits of the current message. For now, it always gives permission." currentCategory isNil | currentMsgID isNil ifTrue: [ "no message selected; discard edits in message pane silently" messageTextView hasUnacceptedEdits: false. ^ true]. messageTextView hasUnacceptedEdits ifFalse: [^ true]. (CustomMenu confirm: 'Discard changes to currently selected message?') ifTrue: [messageTextView hasUnacceptedEdits: false. ^ true] ifFalse: [^ false]. ! ! !Celeste methodsFor: 'other' stamp: 'di 10/27/1999 08:48'! perform: selector orSendTo: otherTarget "Celeste handles all menu commands." selector = #format ifTrue: [^ self perform: selector]. ((#(yellowButtonActivity shiftedYellowButtonActivity) includes: selector) or: [(ParagraphEditor yellowButtonMessages includes: selector) or: [ParagraphEditor shiftedYellowButtonMenu selections includes: selector]]) ifTrue: [otherTarget perform: selector] ifFalse: [self perform: selector]. ! ! !Celeste methodsFor: 'other' stamp: 'mdr 11/22/1999 13:29'! requiredCategory: catName "catName is a required category. If it does not exist in the database, then create it, and update the category list to reflect that it now exists." (self categoryList includes: catName) ifFalse: [mailDB addCategory: catName. self changed: #categoryList.] ! ! !Celeste methodsFor: 'other' stamp: 'dvf 4/26/2000 20:58'! status ^ status! ! !Celeste methodsFor: 'other' stamp: 'sge 5/29/2000 20:29'! status: aStringOrNil status _ aStringOrNil. self changed: #status! ! !Celeste methodsFor: 'sending mail' stamp: 'RAA 6/1/2000 17:55'! PROTOqueueMessageWithText: aStringOrText "Queue a message to be sent later. The message is added to the database and filed in the '.tosend.' category." | messageText id | messageText _ 'X-Mailer: ', Celeste versionString, String cr, 'Date: ', MailMessage dateStampNow, String cr. messageText _ messageText, aStringOrText asString. self requiredCategory: '.tosend.'. "queue the message" id _ mailDB addNewMessage: (MailMessage from: messageText). mailDB file: id inCategory: '.tosend.'. (self category = '.tosend.') ifTrue: [self updateTOC]. ^id ! ! !Celeste methodsFor: 'sending mail' stamp: 'RAA 6/1/2000 17:55'! PROTOsendQueuedMail "Post queued messages to the SMTP server." | outgoing | outgoing _ mailDB messagesIn: '.tosend.'. outgoing isEmpty ifTrue: [^ self inform: 'no mail to be sent']. self sendMail: outgoing.! ! !Celeste methodsFor: 'sending mail' stamp: 'jm 10/4/1998 10:55'! composeText "Answer the template for a new message." ^ String streamContents: [:str | str nextPutAll: 'From: '. str nextPutAll: Celeste userName; cr. str nextPutAll: 'To: '; cr. str nextPutAll: 'Subject: '; cr. Celeste ccList isEmpty ifFalse: [ str nextPutAll: 'Cc: '. str nextPutAll: Celeste ccList; cr]. str cr]. ! ! !Celeste methodsFor: 'sending mail' stamp: 'sge 6/9/2000 20:42'! forwardTextFor: msgID "Answer the template for forwarding the message with the given ID." | msg separator | msg := self currentMessage. ^String streamContents: [ :str | "From header" str nextPutAll: 'From: '; nextPutAll: Celeste userName; cr. "Put a blank To" str nextPutAll: 'To: '; cr. "Add a subject modified from the original" str nextPutAll: 'Subject: (fwd) '. str nextPutAll: msg subject. str cr. "Add auto-cc if it's been set" Celeste ccList isEmpty ifFalse: [ str nextPutAll: 'Cc: '. str nextPutAll: Celeste ccList; cr]. "add the mime headers to make it multi-part" separator := MailMessage generateSeparator. str nextPutAll: 'MIME-Version: 1.0'; cr. str nextPutAll: 'Content-type: multipart/mixed; boundary="'. str nextPutAll: separator; nextPut: $". str cr. "skip down to the main part of the message" str cr. str nextPutAll: '--'; nextPutAll: separator; cr. str nextPutAll: 'Content-type: text/plain'; cr; cr. "insert the forwarded message" str cr; cr; nextPutAll: '====forwarded===='; cr; cr. str nextPutAll: '--'; nextPutAll: separator; cr. str nextPutAll: 'Content-type: message/rfc822'; cr; cr. str nextPutAll: msg text; cr. "final separator" str nextPutAll: '--'; nextPutAll: separator; nextPutAll: '--'; cr. ].! ! !Celeste methodsFor: 'sending mail' stamp: 'ls 10/16/1998 09:09'! openSender: initialText CelesteComposition openForCeleste: self initialText: initialText.! ! !Celeste methodsFor: 'sending mail' stamp: 'jdr 6/4/2000 15:05'! popPassword "Answer the password to use when retrieving mail via POP3. The password is stored in an instance variable, which disappears when you close the Celeste window." userPassword ifNotNil: [^ userPassword]. userPassword _ FillInTheBlank requestPassword: 'POP password'. ^ userPassword ! ! !Celeste methodsFor: 'sending mail' stamp: 'mdr 11/22/1999 15:55'! preSendAuthentication "Where required, authenticate ourselves to the SMTP server before sending mail" "This is a placeholder for any required authentication"! ! !Celeste methodsFor: 'sending mail' stamp: 'dvf 5/17/2000 20:48'! queueMessageWithText: aStringOrText "Queue a message to be sent later. The message is added to the database and filed in the '.tosend.' category." | messageText id | messageText _ 'X-Mailer: ' , Celeste versionString , String cr , 'Date: ' , MailMessage dateStampNow, ' ' , self timeZoneString, ' ', String cr. messageText _ messageText , aStringOrText asString. self requiredCategory: '.tosend.'. "queue the message" id _ mailDB addNewMessage: (MailMessage from: messageText). mailDB file: id inCategory: '.tosend.'. self category = '.tosend.' ifTrue: [self updateTOC]! ! !Celeste methodsFor: 'sending mail' stamp: 'mdr 2/18/1999 09:07'! replyTextFor: msgID "Answer the template for a reply to the message with the given ID." | msg s anyCCs replyaddress | msg _ mailDB getMessage: msgID. s _ WriteStream on: (String new: 500). "add From:" s nextPutAll: 'From: ', Celeste userName; cr. "add Subject:" ((msg subject asLowercase indexOfSubCollection: 're:' startingAt: 1) ~= 0) ifTrue: [s nextPutAll: 'Subject: ', msg subject] ifFalse: [s nextPutAll: 'Subject: Re: ', msg subject]. s cr. "add To:" "Use the Reply-To: address if there is one, otherwise the From: address" replyaddress _ msg from. msg headerFieldsNamed: 'reply-to' do: [ :destAdd | replyaddress _ destAdd ]. s nextPutAll: 'To: ', replyaddress; cr. "add CC:s from the message and from the user's CC list" s nextPutAll: 'CC: '. anyCCs _ false. (msg to isEmpty) ifFalse: [ anyCCs ifTrue:[ s nextPutAll: ', '] ifFalse: [ anyCCs _ true ]. s nextPutAll: msg to ]. (msg cc isEmpty) ifFalse: [ anyCCs ifTrue: [ s nextPutAll: ', ' ] ifFalse: [ anyCCs _ true ]. s nextPutAll: msg cc ]. (Celeste ccList isEmpty) ifFalse: [ anyCCs ifTrue: [ s nextPutAll: ', ' ] ifFalse: [ anyCCs _ true ]. s nextPutAll: Celeste ccList ]. s cr. "add contents of previous message" s cr. s nextPutAll: msg from; nextPutAll: ' wrote:'; cr. msg bodyText linesDo: [ :line | s nextPutAll: '> '. s nextPutAll: line. s cr ]. s cr. ^s contents! ! !Celeste methodsFor: 'sending mail' stamp: 'RAA 5/19/2000 12:27'! sendMail: aCollectionOfMessages "Send to the SMTP server." | sender n message recipients socket | self requiredCategory: '.sent.'. self preSendAuthentication. sender _ (MailAddressParser addressesIn: self class userName) first. [socket _ SMTPSocket usingServer: Celeste smtpServer] ifError: [ :a :b | self error: 'error opening connection to mail server']. ('sending ', aCollectionOfMessages size printString, ' messages...') displayProgressAt: Sensor mousePoint from: 1 to: aCollectionOfMessages size during: [:progressBar | n _ 0. aCollectionOfMessages do: [:id | progressBar value: (n _ n + 1). message _ mailDB getMessage: id. recipients _ Set new. recipients addAll: (MailAddressParser addressesIn: message to). recipients addAll: (MailAddressParser addressesIn: message cc). [socket mailFrom: sender to: recipients text: message text. "send this one message on the stream" mailDB remove: id fromCategory: '.tosend.'. mailDB file: id inCategory: '.sent.' ] ifError: [ :a :b | self error: 'error posting mail'] ]]. socket quit; close. mailDB saveDB. (self category = '.tosend.') | (self category = '.sent.') ifTrue: [self updateTOC]. ! ! !Celeste methodsFor: 'sending mail' stamp: 'mdr 11/22/1999 14:32'! sendQueuedMail "Post queued messages to the SMTP server." | outgoing sender n message recipients socket | outgoing _ mailDB messagesIn: '.tosend.'. outgoing isEmpty ifTrue: [^ self inform: 'no mail to be sent']. self requiredCategory: '.sent.'. self preSendAuthentication. sender _ (MailAddressParser addressesIn: self class userName) first. [socket _ SMTPSocket usingServer: Celeste smtpServer] ifError: [ :a :b | self error: 'error opening connection to mail server']. ('sending ', outgoing size printString, ' messages...') displayProgressAt: Sensor mousePoint from: 1 to: outgoing size during: [:progressBar | n _ 0. outgoing do: [:id | progressBar value: (n _ n + 1). message _ mailDB getMessage: id. recipients _ Set new. recipients addAll: (MailAddressParser addressesIn: message to). recipients addAll: (MailAddressParser addressesIn: message cc). [socket mailFrom: sender to: recipients text: message text. "send this one message on the stream" mailDB remove: id fromCategory: '.tosend.'. mailDB file: id inCategory: '.sent.' ] ifError: [ :a :b | self error: 'error posting mail'] ]]. socket quit; close. mailDB saveDB. (self category = '.tosend.') | (self category = '.sent.') ifTrue: [self updateTOC]. ! ! !Celeste methodsFor: 'sending mail' stamp: 'dvf 5/17/2000 20:48'! timeZoneString ^Celeste userTimeZone! ! !Celeste methodsFor: 'initialize variables' stamp: 'sbw 1/28/2001 23:10'! initializeTocLists tocLists _ Array new: 6. 1 to: tocLists size do: [:index | tocLists at: index put: OrderedCollection new]! ! !Celeste methodsFor: 'accessing' stamp: 'sbw 1/28/2001 22:36'! tocLists "Generated - Return the value of tocLists." tocLists == nil ifTrue: [self initializeTocLists]. ^tocLists! ! !Celeste methodsFor: 'accessing' stamp: 'sbw 1/28/2001 22:36'! tocLists: anObject "Generated - Set the value of tocLists to ." tocLists _ anObject! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Celeste class instanceVariableNames: ''! !Celeste class methodsFor: 'class initialization' stamp: 'jm 10/4/1998 13:55'! initialize "Celeste initialize" "user preferences" CCList _ nil. DeleteInboxAfterFetching _ false. PopServer _ nil. PopUserName _ nil. SmtpServer _ nil. SuppressWorthlessHeaderFields _ true. UserName _ nil. "options with no UI; just set their values directly" FormatWhenFetching _ false. "dictionary of custom filters" CustomFilters _ Dictionary new. ! ! !Celeste class methodsFor: 'class initialization' stamp: 'sbw 1/30/2001 20:10'! initializeMessageCountLimit "An arbitrary bound." self messageCountLimit: 200! ! !Celeste class methodsFor: 'class initialization' stamp: 'ls 6/19/2000 12:20'! versionString "Answer a short string describing this version of Celeste." | highestChangeSet versionAddendum | "the changeset number should probably be removed whenever Celeste settles down" highestChangeSet _ ChangeSorter highestNumberedChangeSet. versionAddendum _ highestChangeSet ifNil: ['.x'] ifNotNil: ['.' , highestChangeSet name initialIntegerOrNil printString]. ^ 'Celeste 2.0' , versionAddendum! ! !Celeste class methodsFor: 'instance creation' stamp: 'dvf 11/18/2000 17:08'! current "Answer the currently active Celeste (assuming that there's only one Celeste open at a given time) or open a new one." ^Celeste allInstances detect: [:e | e isActive] ifNone: [self open]. ! ! !Celeste class methodsFor: 'instance creation' stamp: 'sma 6/12/2000 12:09'! open "Open a MailReader on the default mail database." ^ self openOn: 'EMAIL'! ! !Celeste class methodsFor: 'instance creation' stamp: 'ls 9/17/1998 03:05'! openOn: rootFilename "Open a MailReader on the mail database with the given root filename." |database | database _ MailDB openOn: rootFilename. database ifNotNil: [ ^ self openOnDatabase: database ].! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:46'! ccList "Answer the default cc list to be used in composing messages." CCList isNil ifTrue: [CCList _ '']. ^CCList! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:51'! popServer "Answer the server for downloading email via POP" (PopServer isNil or: [PopServer isEmpty]) ifTrue: [self setPopServer]. PopServer isEmpty ifTrue: [ self error: 'POP server not specified' ]. ^PopServer! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:51'! popUserName "Answer the user's username on the POP server" (PopUserName isNil or: [PopUserName isEmpty]) ifTrue: [self setPopUserName]. PopUserName isEmpty ifTrue: [ self error: 'no POP user name specified' ]. ^PopUserName! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:46'! setCCList "Change the list of names used in the default cc list. Items in the list should be valid mail addresses and should be separated by commas." | newList | (CCList isNil) ifTrue: [CCList _ '']. newList _ FillInTheBlank request: 'addresses to automatically add to CC: fields?' initialAnswer: CCList. CCList _ newList.! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:48'! setPopServer "Change the user's email name for use in composing messages." (PopServer isNil) ifTrue: [PopServer _ '']. PopServer _ FillInTheBlank request: 'What is your POP server''s hostname?' initialAnswer: PopServer. ! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:48'! setPopUserName "set the POP server used for downloading email" (PopUserName isNil) ifTrue: [PopUserName _ '']. PopUserName _ FillInTheBlank request: 'What is your username on your POP server?' initialAnswer: PopUserName. "be kind, if they include the host name here" (PopUserName includes: $@) ifTrue: [ PopUserName _ PopUserName copyFrom: 1 to: (PopUserName indexOf: $@)-1 ].! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:48'! setSmtpServer "Set the SMTP server used to send outgoing messages via" (SmtpServer isNil) ifTrue: [ PopServer isNil ifTrue: [ SmtpServer _ '' ] ifFalse: [ SmtpServer _ PopServer ] ]. SmtpServer _ FillInTheBlank request: 'What is your mail server for outgoing mail?' initialAnswer: SmtpServer. ! ! !Celeste class methodsFor: 'user preferences' stamp: 'dvf 5/18/2000 00:08'! setTimeZone TimeZone _ FillInTheBlank request: 'What is your time zone ?' initialAnswer: '+0300'! ! !Celeste class methodsFor: 'user preferences' stamp: 'bf 3/9/2000 18:11'! setUserName "Change the user's email name for use in composing messages." (UserName isNil) ifTrue: [UserName _ '']. UserName _ FillInTheBlank request: 'What is your email address?\(This is the address other people will reply to you)' withCRs initialAnswer: UserName isoToSqueak. UserName ifNotNil: [UserName _ UserName squeakToIso]! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:50'! smtpServer "Answer the server for sending email" (SmtpServer isNil or: [SmtpServer isEmpty]) ifTrue: [self setSmtpServer]. SmtpServer isEmpty ifTrue: [ self error: 'no SMTP server specified' ]. ^SmtpServer! ! !Celeste class methodsFor: 'user preferences' stamp: 'ls 9/17/1998 05:50'! userName "Answer the user name to be used in composing messages." (UserName isNil or: [UserName isEmpty]) ifTrue: [self setUserName]. UserName isEmpty ifTrue: [ self error: 'no user name specified' ]. ^UserName! ! !Celeste class methodsFor: 'user preferences' stamp: 'sma 5/20/2000 10:18'! userTimeZone "Answer the user's timezone string to be used when sending messages." TimeZone isEmptyOrNil ifTrue: [self setTimeZone]. ^ TimeZone ifNil: ['']! ! !Celeste class methodsFor: 'sending' stamp: 'di 4/13/1999 14:44'! addMVCMailSenderButtons: topView textView: mailTextView "Add some handy buttons to the mail sender window." | sendButton sendAndKeepButton doneButton | sendButton _ PluggableButtonView new model: (Button new onAction: [mailTextView controller accept. mailTextView controller controlTerminate. Celeste postMessage: mailTextView model contents]); action: #turnOn; label: 'Send'; window: (0@0 extent: 34@10); borderWidth: 1. sendAndKeepButton _ PluggableButtonView new model: (Button new onAction: [mailTextView controller accept. mailTextView controller controlTerminate. Celeste postMessage: mailTextView model contents. Celeste addMessageToInbox: mailTextView model contents]); action: #turnOn; label: 'Send&Keep'; window: (0@0 extent: 33@10); borderWidth: 1. doneButton _ PluggableButtonView new model: (Button new onAction: [topView controller close]); action: #turnOn; label: 'Done'; window: (0@0 extent: 33@10); borderWidth: 1. topView addSubView: sendButton above: topView firstSubView; addSubView: sendAndKeepButton toRightOf: sendButton; addSubView: doneButton toRightOf: sendAndKeepButton. ! ! !Celeste class methodsFor: 'sending' stamp: 'sbw 1/30/2001 19:37'! addMVCViews: views andButtons: buttons to: topWindow (views at: 1) window: (0 @ 0 extent: 20 @ 25). (views at: 2) window: (0 @ 0 extent: 80 @ 25). (views at: 3) window: (0 @ 0 extent: 100 @ 70). (buttons at: 1) window: (0 @ 0 extent: 12 @ 5). (buttons at: 2) window: (0 @ 0 extent: 12 @ 5). (buttons at: 3) window: (0 @ 0 extent: 12 @ 5). (buttons at: 4) window: (0 @ 0 extent: 10 @ 5). (buttons at: 5) window: (0 @ 0 extent: 13 @ 5). (buttons at: 6) window: (0 @ 0 extent: 13 @ 5). (buttons at: 7) window: (0 @ 0 extent: 15 @ 5). (buttons at: 8) window: (0 @ 0 extent: 13 @ 5). topWindow addSubView: (buttons at: 1); addSubView: (buttons at: 2) toRightOf: (buttons at: 1); addSubView: (buttons at: 3) toRightOf: (buttons at: 2); addSubView: (buttons at: 4) toRightOf: (buttons at: 3); addSubView: (buttons at: 5) toRightOf: (buttons at: 4); addSubView: (buttons at: 6) toRightOf: (buttons at: 5); addSubView: (buttons at: 7) toRightOf: (buttons at: 6); addSubView: (buttons at: 8) toRightOf: (buttons at: 7); addSubView: (views at: 1) below: (buttons at: 1); addSubView: (views at: 2) toRightOf: (views at: 1); addSubView: (views at: 3) below: (views at: 1)! ! !Celeste class methodsFor: 'sending' stamp: 'jm 10/2/1998 16:24'! addMessageToInbox: msgString "Append a copy of the given message to the inbox file." self inform: 'The "keep" feature is not yet implemented; please cc: yourself to retain a copy of your message.'! ! !Celeste class methodsFor: 'sending'! eudoraSeparator "Return a Eudora-style message separator string." | s today dateString | s _ WriteStream on: (String new: 50). today _ Date today. dateString _ today printFormat: #(2 1 3 32 2 1). dateString _ dateString copyFrom: 1 to: dateString size - 4. s nextPutAll: 'From ???@??? '. s nextPutAll: (today weekday copyFrom: 1 to: 3); space. s nextPutAll: dateString. Time now print24: true on: s. s space. s print: today year; cr. ^s contents ! ! !Celeste class methodsFor: 'options' stamp: 'sbw 1/28/2001 22:32'! includeStatusPane ^Preferences celesteHasStatusPane! ! !Celeste class methodsFor: 'enhancements documentation' stamp: 'sbw 2/2/2001 12:48'! postLoadOperationsForEnhancements "Celeste postLoadOperationsForEnhancements." Preferences addPreferenceForOptionalCelesteStatusPane. true ifFalse: [Celeste postReadMeEnhancements]! ! !Celeste class methodsFor: 'enhancements documentation' stamp: 'sbw 1/28/2001 22:28'! postReadMeEnhancements "Celeste postReadMeEnhancements." Workspace new textContents: self readMeEnhancementsString; openLabel: 'Celeste Enhancements'! ! !Celeste class methodsFor: 'enhancements documentation' stamp: 'sbw 1/30/2001 21:40'! readMeEnhancementsString ^ 'These enhancements are a joint effort between Lex Spoon and Steve Wessels. 1. The custom menu button always presents a menu; matching filters are listed at the top of the the menu. 2. The auto-move button is removed. Instead, the regular TOC menu will give the option of an automove. 3. Huge message lists are not shown, but a menu item is added to forcibly show all messages. You can also specify the maximum number to display. 4. The From Filter is switched to a Participant Filter. 5. The table of contents pane uses a new multi-column list morph, which makes for nicer looking lists. 6. Messages that have attachments are indicated with @. 7. If attachments are jpeg or gif then open view will correctly process the image, and save will suggest a proper extension for the file name. 8. The delete button is back. 9. The morphic panes use the new fixed pane scheme (layouts). 10. The status pane is displayed depending on an option in Preferences. Default is to not show the status pane. 11. Menus use checkbox style for some of the options: (suppress header, and leave messages on server).'! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/28/2001 23:22'! buildButtonFromSpec: spec forModel: model | buttonViewClass b | Smalltalk isMorphic ifTrue: [buttonViewClass _ self morphicButtonsClass] ifFalse: [buttonViewClass _ PluggableButtonView]. b _ buttonViewClass on: model getState: (self specificationFromList: spec at: 1) action: (self specificationFromList: spec at: 2). b label: (self specificationFromList: spec at: 3); borderWidth: 1. b setBalloonText: (self specificationFromList: spec at: 4). ^ b! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/28/2001 23:22'! buildButtonFromSpec: spec withBlock: aBlock | b buttonViewClass | Smalltalk isMorphic ifTrue: [buttonViewClass _ self morphicButtonsClass] ifFalse: [buttonViewClass _ PluggableButtonView]. b _ buttonViewClass new model: (Button new onAction: aBlock); action: (self specificationFromList: spec at: 1); label: (self specificationFromList: spec at: 2); borderWidth: 1. b setBalloonText: (self specificationFromList: spec at: 3). ^ b! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/30/2001 20:42'! buildButtonsFor: model "Answer a collection of handy buttons for the Celeste user interface." | buttons b | buttons _ OrderedCollection new. b _ self buildButtonFromSpec: self specForSubjectFilterButton forModel: model. buttons add: b. true ifFalse: ["Skip these buttons..." b _ self buildFromFilterButtonForModel: model. buttons add: b]. b _ self buildButtonFromSpec: self specForParticipantFilterButton forModel: model. buttons add: b. b _ self buildButtonFromSpec: self specForCustomFilterButton forModel: model. buttons add: b. true ifFalse: ["Skip these buttons..." b _ self buildButtonFromSpec: self specForCustomFilterMoveButton forModel: model. buttons add: b]. b _ self buildButtonFromSpec: self specForComposeButton withBlock: [model compose]. buttons add: b. b _ self buildButtonFromSpec: self specForReplyButton withBlock: [model reply]. buttons add: b. b _ self buildButtonFromSpec: self specForForwardButton withBlock: [model forward]. buttons add: b. b _ self buildButtonFromSpec: self specForMoveAgainButton withBlock: [model moveAgain]. buttons add: b. b _ self buildButtonFromSpec: self specForDeleteButton withBlock: [model deleteMessage]. buttons add: b. ^ buttons! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/28/2001 23:22'! buildFromFilterButtonForModel: model | sw b buttonViewClass | Smalltalk isMorphic ifTrue: [buttonViewClass _ self morphicButtonsClass] ifFalse: [buttonViewClass _ PluggableButtonView]. sw _ Switch new. b _ buttonViewClass on: (sw onAction: [model fromFilterOn: sw]; offAction: [model fromFilterOff]). b label: 'From F.'; borderWidth: 1. b setBalloonText: 'Show messages with specific From: line content'. ^ b! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/21/2001 22:03'! openOnDatabase: aMailDB "Open a MailReader on the given mail database." | model topWindow title | model _ self new openOnDatabase: aMailDB. title _ self defaultWindowTitle. Smalltalk isMorphic ifTrue: [topWindow _ self buildTopMorphicWindowTitled: title model: model. topWindow openInWorld] ifFalse: [topWindow _ self buildTopMVCWindowTitled: title model: model. topWindow controller open]. "in case the sender wants to know" ^ model! ! !Celeste class methodsFor: 'common build' stamp: 'sbw 1/28/2001 23:23'! specificationFromList: list at: index | value | value _ list at: index. value = #nil ifTrue: [value _ nil]. ^ value! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:19'! specForComposeButton "action label helpText" ^ #(#turnOn 'New' 'Compose a new message' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/30/2001 20:38'! specForCustomFilterButton "getState action label helpText" ^ #(#isCustomFilterOn #customFilterOn 'Custom F.' 'Select messages with a general custom filter' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:20'! specForCustomFilterMoveButton "getState action label helpText" ^ #(#nil #customFilterMove 'Custom F. Move' 'Move messages which match a custom filter to appropriate category' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:20'! specForDeleteButton "action label helpText" ^ #(#turnOn 'Delete' 'Delete the selected message' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:20'! specForForwardButton "action label helpText" ^ #(#turnOn 'Forward' 'Forward the selected message' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:20'! specForMoveAgainButton "action label helpText" ^ #(#turnOn 'Move Again' 'Move the selected message to the same category as previously' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/30/2001 20:40'! specForParticipantFilterButton "getState action label helpText" ^ #(#isParticipantFilterOn #participantFilterOn 'Part. F.' 'Select messages by their From:, To:, and Cc: lines' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/28/2001 23:20'! specForReplyButton "action label helpText" ^ #(#turnOn 'Reply' 'Reply to the selected message' )! ! !Celeste class methodsFor: 'button specs' stamp: 'sbw 1/30/2001 20:35'! specForSubjectFilterButton "getState action label helpText" ^ #(#isSubjectFilterOn #subjectFilterOn 'Subj. F.' 'Select messages by their subject' )! ! !Celeste class methodsFor: 'build-common' stamp: 'sbw 1/30/2001 19:44'! defaultWindowTitle ^ 'Celeste'! ! !Celeste class methodsFor: 'build-mvc' stamp: 'sbw 1/21/2001 22:02'! buildTopMVCWindowTitled: title model: model | topWindow views buttons | topWindow _ StandardSystemView new model: model; label: title; minimumSize: 400 @ 250. views _ self buildViewsFor: model. buttons _ self buildButtonsFor: model. self addMVCViews: views andButtons: buttons to: topWindow. ^ topWindow! ! !Celeste class methodsFor: 'build-mvc' stamp: 'sbw 1/22/2001 00:09'! buildViewsFor: model "Answer a collection of window panes for the Celeste user interface." | listViewClass textViewClass listFont views v multiListViewClass | listViewClass _ PluggableListViewByItem. multiListViewClass _ PluggableListViewByItem. textViewClass _ PluggableTextView. listFont _ StrikeFont allSubInstances detect: [:f | (f name beginsWith: 'CourierFixed') and: [f height = 11]] ifNone: [TextStyle defaultFont]. views _ OrderedCollection new. v _ listViewClass on: model list: #categoryList selected: #category changeSelected: #setCategory: menu: #categoryMenu: keystroke: #categoriesKeystroke:. views add: v. v _ multiListViewClass on: model list: #tocEntryList selected: #tocEntry changeSelected: #setTOCEntry: menu: #tocMenu: keystroke: #tocKeystroke:. v font: listFont. views add: v. v _ textViewClass new on: model text: #messageText accept: #messageText: readSelection: nil menu: #messageMenu:shifted:. v borderWidth: 1. model messageTextView: v. views add: v. v _ textViewClass new on: model text: #status accept: nil readSelection: nil menu: nil. v borderWidth: 1. model messageTextView: v. views add: v. ^ views! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/30/2001 20:04'! addLowerMorphicViews: views andButtons: buttons to: topWindow offset: offset | row verticalOffset innerFractions | row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 1; borderColor: Color black; layoutPolicy: ProportionalLayout new. verticalOffset _ 0. innerFractions _ 0 @ 0 corner: 1 @ 0. verticalOffset _ self addMorphicButtons: buttons to: row at: innerFractions plus: verticalOffset. self includeStatusPane ifTrue: [ verticalOffset _ self addMorphicStatusPaneTo: row from: views at: innerFractions plus: verticalOffset]. self addMorphicTextPaneTo: row from: views at: innerFractions plus: verticalOffset. topWindow addMorph: row frame: (0 @ offset extent: 1 @ (1 - offset)). row on: #mouseEnter send: #paneTransition: to: topWindow. row on: #mouseLeave send: #paneTransition: to: topWindow! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/22/2001 20:53'! addMorphicButtons: buttons to: row at: innerFractions plus: verticalOffset | delta buttonRow | delta _ 25. buttonRow _ self morphicButtonRowFrom: buttons. buttonRow color: (Color gray alpha: 0.2); borderWidth: 1; borderColor: Color lightGray. row addMorph: buttonRow fullFrame: (LayoutFrame fractions: innerFractions offsets: (0@verticalOffset corner: 0@(verticalOffset + delta))). ^ verticalOffset + delta! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/30/2001 19:46'! addMorphicStatusPaneTo: row from: views at: innerFractions plus: verticalOffset | delta | delta _ 20. row addMorph: (views at: #status) fullFrame: (LayoutFrame fractions: innerFractions offsets: (0 @ verticalOffset corner: 0 @ (verticalOffset + delta))). ^ verticalOffset + delta! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/22/2001 20:43'! addMorphicTextPaneTo: row from: views at: innerFractions plus: verticalOffset row addMorph: (views at: #messageText) fullFrame: (LayoutFrame fractions: (innerFractions withBottom: 1) offsets: (0 @ verticalOffset corner: 0@0)). ^ verticalOffset ! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/22/2001 20:22'! addMorphicViews: views andButtons: buttons to: topWindow topWindow addMorph: (views at: #categoryList) frame: (0.0 @ 0.0 extent: 0.2 @ 0.25). topWindow addMorph: (views at: #tocEntryList) frame: (0.2 @ 0.0 extent: 0.8 @ 0.25). self addLowerMorphicViews: views andButtons: buttons to: topWindow offset: 0.25 ! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 22:17'! buildMorphicCategoryListFor: model ^PluggableListMorphByItem on: model list: #categoryList selected: #category changeSelected: #setCategory: menu: #categoryMenu: keystroke: #categoriesKeystroke:. ! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 22:19'! buildMorphicMessageTextPaneFor: model ^ PluggableTextMorph new on: model text: #messageText accept: #messageText: readSelection: nil menu: #messageMenu:shifted:! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 22:20'! buildMorphicStatusPaneFor: model ^ PluggableTextMorph new on: model text: #status accept: nil readSelection: nil menu: nil.! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 22:18'! buildMorphicTocEntryListFor: model ^ PluggableMultiColumnListMorphByItem on: model list: #tocEntryList selected: #tocEntry changeSelected: #setTOCEntry: menu: #tocMenu: keystroke: #tocKeystroke:! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 22:21'! buildMorphicViewsFor: model "Answer a dictionary of window panes for the Celeste user interface." | listFont views v | listFont _ StrikeFont allSubInstances detect: [:f | (f name beginsWith: 'CourierFixed') and: [f height = 11]] ifNone: [TextStyle defaultFont]. views _ Dictionary new. v _ self buildMorphicCategoryListFor: model. views at: #categoryList put: v. v _ self buildMorphicTocEntryListFor: model. v font: listFont. views at: #tocEntryList put: v. v _ self buildMorphicMessageTextPaneFor: model. v borderWidth: 1. model messageTextView: v. views at: #messageText put: v. v _ self buildMorphicStatusPaneFor: model. v borderWidth: 1. views at: #status put: v. ^ views! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/22/2001 00:50'! buildTopMorphicWindowTitled: title model: model | topWindow views buttons | topWindow _ (SystemWindow labelled: title) model: model. buttons _ self buildButtonsFor: model. views _ self buildMorphicViewsFor: model. self addMorphicViews: views andButtons: buttons to: topWindow . buttons do: [:b | b onColor: Color lightGray offColor: Color white]. ^ topWindow! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 2/2/2001 13:01'! morphicButtonRowFrom: buttons | aRow | aRow _ AlignmentMorph newRow. aRow setNameTo: 'buttonPane'. aRow beSticky. aRow hResizing: #spaceFill. aRow wrapCentering: #center; cellPositioning: #leftCenter. aRow clipSubmorphs: true. aRow addTransparentSpacerOfSize: 5 @ 0. buttons do: [:btn | btn useRoundedCorners; hResizing: #spaceFill; vResizing: #spaceFill. aRow addMorphBack: btn. aRow addTransparentSpacerOfSize: 3 @ 0]. ^ aRow! ! !Celeste class methodsFor: 'build-morphic' stamp: 'sbw 1/21/2001 20:55'! morphicButtonsClass ^PluggableButtonMorph! ! !Celeste class methodsFor: 'accessing' stamp: 'sbw 1/30/2001 20:11'! messageCountLimit MessageCountLimit == nil ifTrue: [self initializeMessageCountLimit]. ^ MessageCountLimit! ! !Celeste class methodsFor: 'accessing' stamp: 'sbw 1/30/2001 20:09'! messageCountLimit: anInteger MessageCountLimit _ anInteger! ! Model subclass: #CelesteComposition instanceVariableNames: 'celeste messageText textEditor attachmentSeparator morphicWindow mvcWindow ' classVariableNames: '' poolDictionaries: '' category: 'Network-Mail Reader'! !CelesteComposition commentStamp: '' prior: 0! a message being composed. When finished, it will be submitted via a Celeste.! !CelesteComposition methodsFor: 'private' stamp: 'ls 12/8/1999 22:04'! breakLines: aString atWidth: width "break lines in the given string into shorter lines" | result start end pastHeader atAttachment | result _ WriteStream on: (String new: (aString size * 50 // 49)). pastHeader _ false. atAttachment _ false. aString asString linesDo: [ :line | line isEmpty ifTrue: [ pastHeader _ true ]. pastHeader ifFalse: [ result nextPutAll: line. result cr. ] ifTrue: [ (line beginsWith: '====') ifTrue: [ atAttachment _ true ]. atAttachment ifTrue: [ "at or after an attachment line; no more wrapping for the rest of the message" result nextPutAll: line. result cr ] ifFalse: [ (line beginsWith: '>') ifTrue: [ "it's quoted text; don't wrap it" result nextPutAll: line. result cr. ] ifFalse: [ "regular old line. Wrap it to multiple lines" start _ 1. "output one shorter line each time through this loop" [ start + width <= line size ] whileTrue: [ "find the end of the line" end _ start + width - 1. [end >= start and: [ (line at: (end+1)) isSeparator not ]] whileTrue: [ end _ end - 1 ]. end < start ifTrue: [ "a word spans the entire width!!" end _ start + width - 1 ]. "copy the line to the output" result nextPutAll: (line copyFrom: start to: end). result cr. "get ready for next iteration" start _ end+1. (line at: start) isSeparator ifTrue: [ start _ start + 1 ]. ]. "write out the final part of the line" result nextPutAll: (line copyFrom: start to: line size). result cr. ]. ]. ]. ]. ^result contents! ! !CelesteComposition methodsFor: 'private' stamp: 'dvf 4/28/2000 02:19'! hasAttachments ^ attachmentSeparator notNil! ! !CelesteComposition methodsFor: 'access' stamp: 'bf 3/9/2000 18:02'! messageText "return the current text" ^messageText isoToSqueak! ! !CelesteComposition methodsFor: 'access' stamp: 'bf 3/9/2000 18:26'! messageText: aText "change the current text" messageText _ aText squeakToIso. self changed: #messageText. ^true! ! !CelesteComposition methodsFor: 'access' stamp: 'dvf 5/13/2000 16:51'! submit "submit the message" textEditor ifNotNil: [self hasUnacceptedEdits ifTrue: [textEditor accept]]. celeste queueMessageWithText: (MailMessage from: messageText asString) asSendableText. morphicWindow ifNotNil: [morphicWindow delete]. mvcWindow ifNotNil: [mvcWindow controller close]! ! !CelesteComposition methodsFor: 'initialization' stamp: 'ls 10/15/1998 21:51'! celeste: aCeleste initialText: aText celeste _ aCeleste. messageText _ aText.! ! !CelesteComposition methodsFor: 'interface' stamp: 'mdr 8/31/2000 18:37'! addAttachment | file fileResult fileName | textEditor ifNotNil: [self hasUnacceptedEdits ifTrue: [textEditor accept]]. "transform into multipart if needed" self hasAttachments ifFalse: [self transformToMultipart]. "then simply append another attachment section" (fileResult _ StandardFileMenu oldFile) ifNotNil: [fileName _ fileResult directory fullNameFor: fileResult name. file _ FileStream readOnlyFileNamed: fileName. file ifNotNil: [self messageText: ((MailMessage from: self messageText) asTextEncodingNewPart: file named: fileResult name)]] ! ! !CelesteComposition methodsFor: 'interface' stamp: 'ls 10/16/1998 09:11'! open "open an interface" Smalltalk isMorphic ifTrue: [ self openInMorphic ] ifFalse: [ self openInMVC ]! ! !CelesteComposition methodsFor: 'interface' stamp: 'ls 10/16/1998 09:17'! openInMVC | textView sendButton | mvcWindow _ StandardSystemView new label: 'Mister Postman'; minimumSize: 400@250; model: self. textView _ PluggableTextView on: self text: #messageText accept: #messageText:. textEditor _ textView controller. sendButton _ PluggableButtonView on: self getState: nil action: #submit. sendButton label: 'Send'. sendButton borderWidth: 1. sendButton window: (1@1 extent: 398@38). mvcWindow addSubView: sendButton. textView window: (0@40 corner: 400@250). mvcWindow addSubView: textView below: sendButton. mvcWindow controller open. ! ! !CelesteComposition methodsFor: 'interface' stamp: 'RAA 1/17/2001 14:20'! openInMorphic "open an interface for sending a mail message with the given initial text " | textMorph buttonsList sendButton attachmentButton | morphicWindow _ SystemWindow labelled: 'Mister Postman'. morphicWindow model: self. textEditor _ textMorph _ PluggableTextMorph on: self text: #messageText accept: #messageText:. morphicWindow addMorph: textMorph frame: (0 @ 0.1 corner: 1 @ 1). buttonsList _ AlignmentMorph newRow. sendButton _ PluggableButtonMorph on: self getState: nil action: #submit. sendButton hResizing: #spaceFill; vResizing: #spaceFill; label: 'send message'; setBalloonText: 'add this to the queue of messages to be sent'; onColor: Color white offColor: Color white. buttonsList addMorphBack: sendButton. attachmentButton _ PluggableButtonMorph on: self getState: nil action: #addAttachment. attachmentButton hResizing: #spaceFill; vResizing: #spaceFill; label: 'add attachment'; setBalloonText: 'Send a file with the message'; onColor: Color white offColor: Color white. buttonsList addMorphBack: attachmentButton. morphicWindow addMorph: buttonsList frame: (0 @ 0 extent: 1 @ 0.1). morphicWindow openInMVC! ! !CelesteComposition methodsFor: 'interface' stamp: 'dvf 5/13/2000 12:16'! transformToMultipart | oldPart | oldPart _ MailMessage from: self messageText asString. self messageText: oldPart asMultipartText ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CelesteComposition class instanceVariableNames: ''! !CelesteComposition class methodsFor: 'instance creation' stamp: 'ls 10/16/1998 09:08'! celeste: aCeleste initialText: initialText "create an instance for the given mail reader, editting the given text" ^self new celeste: aCeleste initialText: initialText! ! !CelesteComposition class methodsFor: 'instance creation' stamp: 'ls 10/16/1998 09:08'! openForCeleste: aCeleste initialText: initialText "open a composition window for the given mail reader, editting the given text" (self celeste: aCeleste initialText: initialText) open! ! CodeHolder subclass: #ChangeList instanceVariableNames: 'changeList list listIndex listSelections file lostMethodPointer showsVersions ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeList commentStamp: '' prior: 0! A ChangeList represents a list of changed methods that reside on a file in fileOut format. The classes and methods in my list are not necessarily in this image!! Used as the model for both Version Lists and Changed Methods (in Screen Menu, Changes...). Note that the two kinds of window have different controller classes!!!! It holds three lists: changeList - a list of ChangeRecords list - a list of one-line printable headers listSelections - a list of Booleans (true = selected, false = not selected) multiple OK. listIndex Items that are removed (removeDoits, remove an item) are removed from all three lists. Most recently clicked item is the one showing in the bottom pane.! !ChangeList methodsFor: 'initialization-release'! addItem: item text: text | cr | cr _ Character cr. changeList addLast: item. list addLast: (text collect: [:x | x = cr ifTrue: [$/] ifFalse: [x]])! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sw 1/7/2000 12:42'! changeListButtonSpecs ^#( ('select all' selectAll 'select all entries') ('deselect all' deselectAll 'deselect all entries') ('select conflicts' selectAllConflicts 'select all methods that occur in any change set') ('file in selections' fileInSelections 'file in all selected entries') )! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sw 1/13/2000 10:50'! initialize showDiffs _ Preferences diffsInChangeList. changeList _ OrderedCollection new. list _ OrderedCollection new. listIndex _ 0. super initialize! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sw 1/18/2001 13:09'! openAsMorphName: labelString multiSelect: multiSelect "Open a morphic view for the messageSet, whose label is labelString. The listView may be either single or multiple selection type" | window listHeight listPane | listHeight _ 0.4. window _ (SystemWindow labelled: labelString) model: self. listPane _ (multiSelect ifTrue: [PluggableListMorphOfMany] ifFalse: [PluggableListMorph]) on: self list: #list selected: #listIndex changeSelected: #toggleListIndex: menu: (self showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:]). listPane keystrokeActionSelector: #changeListKey:from:. window addMorph: listPane frame: (0 @ 0 extent: 1 @ listHeight). self addLowerPanesTo: window at: (0@listHeight corner: 1@1) with: nil. ^ window openInWorld! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sbw 12/30/1999 11:02'! optionalButtonHeight ^ 15! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sw 1/18/2001 16:35'! optionalButtonsView "Answer the a View containing the optional buttons" | view bHeight vWidth first offset previousView bWidth button | vWidth _ 200. bHeight _ self optionalButtonHeight. previousView _ nil. offset _ 0. first _ true. view _ View new model: self; window: (0 @ 0 extent: vWidth @ bHeight). self changeListButtonSpecs do: [:triplet | button _ PluggableButtonView on: self getState: nil action: triplet second. button label: triplet first asParagraph. bWidth _ button label boundingBox width // 2. button window: (offset@0 extent: bWidth@bHeight); borderWidthLeft: 0 right: 1 top: 0 bottom: 0. offset _ offset + bWidth. first ifTrue: [view addSubView: button. first _ false.] ifFalse: [view addSubView: button toRightOf: previousView]. previousView _ button]. button _ PluggableButtonView on: self getState: #showDiffs action: #toggleDiffing. button label: 'diffs' asParagraph; window: (offset@0 extent: (vWidth - offset)@bHeight). view addSubView: button toRightOf: previousView. ^ view! ! !ChangeList methodsFor: 'scanning' stamp: 'sw 1/15/98 21:56'! scanCategory "Scan anything that involves more than one chunk; method name is historical only" | itemPosition item tokens stamp isComment anIndex | itemPosition _ file position. item _ file nextChunk. isComment _ (item includesSubString: 'commentStamp:'). (isComment or: [item includesSubString: 'methodsFor:']) ifFalse: ["Maybe a preamble, but not one we recognize; bail out with the preamble trick" ^ self addItem: (ChangeRecord new file: file position: itemPosition type: #preamble) text: ('preamble: ' , item contractTo: 50)]. tokens _ Scanner new scanTokens: item. tokens size >= 3 ifTrue: [stamp _ ''. anIndex _ tokens indexOf: #stamp: ifAbsent: [nil]. anIndex ifNotNil: [stamp _ tokens at: (anIndex + 1)]. tokens second == #methodsFor: ifTrue: [^ self scanCategory: tokens third class: tokens first meta: false stamp: stamp]. tokens third == #methodsFor: ifTrue: [^ self scanCategory: tokens fourth class: tokens first meta: true stamp: stamp]]. tokens second == #commentStamp: ifTrue: [stamp _ tokens third. self addItem: (ChangeRecord new file: file position: file position type: #classComment class: tokens first category: nil meta: false stamp: stamp) text: 'class comment for ' , tokens first, (stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp]). file nextChunk. ^ file skipStyleChunk]! ! !ChangeList methodsFor: 'scanning' stamp: 'di 1/13/98 16:56'! scanCategory: category class: class meta: meta stamp: stamp | itemPosition method | [itemPosition _ file position. method _ file nextChunk. file skipStyleChunk. method size > 0] "done when double terminators" whileTrue: [self addItem: (ChangeRecord new file: file position: itemPosition type: #method class: class category: category meta: meta stamp: stamp) text: 'method: ' , class , (meta ifTrue: [' class '] ifFalse: [' ']) , (Parser new parseSelector: method) , (stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp])]! ! !ChangeList methodsFor: 'scanning' stamp: 'sw 10/19/1999 15:13'! scanFile: aFile from: startPosition to: stopPosition | itemPosition item prevChar | file _ aFile. changeList _ OrderedCollection new. list _ OrderedCollection new. listIndex _ 0. file position: startPosition. 'Scanning ', aFile localName, '...' displayProgressAt: Sensor cursorPoint from: startPosition to: stopPosition during: [:bar | [file position < stopPosition] whileTrue: [bar value: file position. [file atEnd not and: [file peek isSeparator]] whileTrue: [prevChar _ file next]. (file peekFor: $!!) ifTrue: [(prevChar = Character cr or: [prevChar = Character lf]) ifTrue: [self scanCategory]] ifFalse: [itemPosition _ file position. item _ file nextChunk. file skipStyleChunk. item size > 0 ifTrue: [self addItem: (ChangeRecord new file: file position: itemPosition type: #doIt) text: 'do it: ' , (item contractTo: 50)]]]]. listSelections _ Array new: list size withAll: false! ! !ChangeList methodsFor: 'menu actions' stamp: 'jm 5/3/1998 19:15'! acceptFrom: aView aView controller text = aView controller initialText ifFalse: [ aView flash. ^ self inform: 'You can only accept this version as-is. If you want to edit, copy the text to a browser']. (aView setText: aView controller text from: self) ifTrue: [aView ifNotNil: [aView controller accept]]. "initialText" ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/7/2000 12:59'! browseCurrentVersionsOfSelections "Opens a message-list browser on the current in-memory versions of all methods that are currently seleted" | aClass aChange aList | aList _ OrderedCollection new. Cursor read showWhile: [1 to: changeList size do: [:i | (listSelections at: i) ifTrue: [aChange _ changeList at: i. (aChange type = #method and: [(aClass _ aChange methodClass) notNil and: [aClass includesSelector: aChange methodSelector]]) ifTrue: [aList add: aClass name, ' ', aChange methodSelector]]]]. aList size == 0 ifTrue: [^ self inform: 'no selected methods have in-memory counterparts']. MessageSet openMessageList: aList name: 'Current versions of selected methods in ', file localName! ! !ChangeList methodsFor: 'menu actions' stamp: 'RAA 1/11/2001 08:42'! buildMorphicCodePaneWith: editString | codePane | codePane _ AcceptableCleanTextMorph on: self text: #contents accept: #contents: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. editString ifNotNil: [ codePane editString: editString. codePane hasUnacceptedEdits: true ]. ^codePane ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/25/2001 07:22'! changeListKey: aChar from: view "Respond to a Command key in the list pane." aChar == $D ifTrue: [^ self toggleDiffing]. aChar == $a ifTrue: [^ self selectAll]. ^ self arrowKey: aChar from: view! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/25/2001 08:40'! changeListMenu: aMenu "Fill aMenu up so that it comprises the primary changelist-browser menu" Smalltalk isMorphic ifTrue: [aMenu addTitle: 'change list'. aMenu addStayUpItemSpecial]. aMenu addList: #( ('fileIn selections' fileInSelections) ('fileOut selections... ' fileOutSelections) - ('compare to current' compareToCurrentVersion) ('toggle diffing (D)' toggleDiffing) - ('select conflicts with any changeset' selectAllConflicts) ('select conflicts with current changeset' selectConflicts) - ('select conflicts with...' selectConflictsWith) ('select unchanged methods' selectUnchangedMethods) ('select methods for this class' selectMethodsForThisClass) ('invert selections' invertSelections) - ('select all (a)' selectAll) ('deselect all' deselectAll) - ('browse current versions of selections' browseCurrentVersionsOfSelections) ('remove current methods of selections' destroyCurrentCodeOfSelections) - ('remove doIts' removeDoIts) ('remove older versions' removeOlderMethodVersions) ('remove selected items' removeSelections) ('remove unselected items' removeNonSelections)). ^ aMenu ! ! !ChangeList methodsFor: 'menu actions' stamp: 'nk 10/29/2000 12:34'! compareToCurrentVersion "If the current selection corresponds to a method in the system, then spawn a window showing the diffs as text" | change class s1 s2 | listIndex = 0 ifTrue: [^ self]. change _ changeList at: listIndex. ((class _ change methodClass) notNil and: [class includesSelector: change methodSelector]) ifTrue: [s1 _ (class sourceCodeAt: change methodSelector) asString. s2 _ change string. s1 = s2 ifTrue: [^ self inform: 'Exact Match']. (StringHolder new textContents: (TextDiffBuilder buildDisplayPatchFrom: s1 to: s2 inClass: class)) openLabel: 'Comparison to Current Version'] ifFalse: [self flash]! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/25/2001 08:38'! deselectAll "Deselect all items in the list pane, and clear the code pane" listIndex _ 0. listSelections atAllPut: false. self changed: #allSelections. self contentsChanged! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/25/2001 09:04'! destroyCurrentCodeOfSelections "Actually remove from the system any in-memory methods with class and selector identical to items current selected. This may seem rather arcane but believe me it has its great uses, when trying to split out code. To use effectively, first file out a change set that you wish to split off. Then open a ChangeList browser on that fileout. Now look through the methods, and select any of them which you want to remove completely from the system, then issue this command. For those methods where you have made changes to pre-existing versions, of course, you won't want to remove them from the system, so use this mechanism with care!!" | aClass aChange aList | aList _ OrderedCollection new. 1 to: changeList size do: [:index | (listSelections at: index) ifTrue: [aChange _ changeList at: index. (aChange type = #method and: [(aClass _ aChange methodClass) notNil and: [aClass includesSelector: aChange methodSelector]]) ifTrue: [aList add: {aClass. aChange methodSelector}]]]. aList size > 0 ifTrue: [(self confirm: 'Warning!! This will actually remove ', aList size printString, ' method(s) from the system!!') ifFalse: [^ self]]. aList do: [:aPair | Transcript cr; show: 'Removed: ', aPair first printString, '.', aPair second. aPair first removeSelector: aPair second]! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 10/11/1999 17:10'! fileInSelections | any | any _ false. listSelections with: changeList do: [:selected :item | selected ifTrue: [any _ true. item fileIn]]. any ifFalse: [self inform: 'nothing selected, so nothing done']! ! !ChangeList methodsFor: 'menu actions' stamp: 'sma 2/5/2000 19:13'! fileOutSelections | f | f _ FileStream newFileNamed: (FillInTheBlank request: 'Enter file name' initialAnswer: 'Filename.st'). f ifNil: [^ self]. f header; timeStamp. listSelections with: changeList do: [:selected :item | selected ifTrue: [item fileOutOn: f]]. f close! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/25/2001 08:35'! invertSelections "Invert the selectedness of each item in the changelist" listSelections _ listSelections collect: [ :ea | ea not]. listIndex _ 0. self changed: #allSelections. self contentsChanged! ! !ChangeList methodsFor: 'menu actions' stamp: 'RAA 1/17/2001 14:25'! optionalButtonRow | aRow aButton | aRow _ AlignmentMorph newRow. aRow hResizing: #spaceFill. aRow clipSubmorphs: true. aRow addTransparentSpacerOfSize: (5@0). aRow wrapCentering: #center; cellPositioning: #leftCenter. self changeListButtonSpecs do: [:triplet | aButton _ PluggableButtonMorph on: self getState: nil action: triplet second. aButton hResizing: #spaceFill; vResizing: #spaceFill; useRoundedCorners; label: triplet first asString; askBeforeChanging: true; onColor: Color transparent offColor: Color transparent. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0). aButton setBalloonText: triplet third. ]. aRow addMorphBack: self diffButton. ^ aRow! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 10/11/1999 17:18'! perform: selector orSendTo: otherTarget "Selector was just chosen from a menu by a user. If I can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked." (#accept == selector) ifTrue: [otherTarget isMorph ifFalse: [^ self acceptFrom: otherTarget view]]. "weird special case just for mvc changlist" ^ super perform: selector orSendTo: otherTarget! ! !ChangeList methodsFor: 'menu actions' stamp: 'tk 4/8/98 12:38'! removeDoIts "Remove doits from the receiver, other than initializes. 1/26/96 sw" | newChangeList newList | newChangeList _ OrderedCollection new. newList _ OrderedCollection new. changeList with: list do: [:chRec :str | (chRec type ~~ #doIt or: [str endsWith: 'initialize']) ifTrue: [newChangeList add: chRec. newList add: str]]. newChangeList size < changeList size ifTrue: [changeList _ newChangeList. list _ newList. listIndex _ 0. listSelections _ Array new: list size withAll: false]. self changed: #list. ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 9/18/2000 12:21'! removeNonSelections "Remove the unselected items from the receiver." | newChangeList newList | newChangeList _ OrderedCollection new. newList _ OrderedCollection new. 1 to: changeList size do: [:i | (listSelections at: i) ifTrue: [newChangeList add: (changeList at: i). newList add: (list at: i)]]. newChangeList size == 0 ifTrue: [^ self inform: 'That would remove everything. Why would you want to do that?']. newChangeList size < changeList size ifTrue: [changeList _ newChangeList. list _ newList. listIndex _ 0. listSelections _ Array new: list size withAll: false]. self changed: #list ! ! !ChangeList methodsFor: 'menu actions' stamp: 'di 6/13/97 23:10'! removeOlderMethodVersions "Remove older versions of entries from the receiver." | newChangeList newList found str | newChangeList _ OrderedCollection new. newList _ OrderedCollection new. found _ OrderedCollection new. changeList reverseWith: list do: [:chRec :strNstamp | str _ strNstamp copyUpTo: $;. (found includes: str) ifFalse: [found add: str. newChangeList add: chRec. newList add: strNstamp]]. newChangeList size < changeList size ifTrue: [changeList _ newChangeList reversed. list _ newList reversed. listIndex _ 0. listSelections _ Array new: list size withAll: false]. self changed: #list! ! !ChangeList methodsFor: 'menu actions'! removeSelections "Remove the selected items from the receiver. 9/18/96 sw" | newChangeList newList | newChangeList _ OrderedCollection new. newList _ OrderedCollection new. 1 to: changeList size do: [:i | (listSelections at: i) ifFalse: [newChangeList add: (changeList at: i). newList add: (list at: i)]]. newChangeList size < changeList size ifTrue: [changeList _ newChangeList. list _ newList. listIndex _ 0. listSelections _ Array new: list size withAll: false]. self changed: #list ! ! !ChangeList methodsFor: 'menu actions'! selectAll listIndex _ 0. listSelections atAllPut: true. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 12/27/1999 12:24'! selectAllConflicts "Selects all method definitions in the receiver which are also in any existing change set in the system. This makes no statement about whether the content of the methods differ, only whether there is a change represented. " | aClass aChange | Cursor read showWhile: [1 to: changeList size do: [:i | aChange _ changeList at: i. listSelections at: i put: (aChange type = #method and: [(aClass _ aChange methodClass) notNil and: [(ChangeSorter allChangeSetsWithClass: aClass selector: aChange methodSelector) size > 0]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions'! selectConflicts "Selects all method definitions for which there is ALSO an entry in changes" | change class systemChanges | Cursor read showWhile: [1 to: changeList size do: [:i | change _ changeList at: i. listSelections at: i put: (change type = #method and: [(class _ change methodClass) notNil and: [(Smalltalk changes atSelector: change methodSelector class: class) ~~ #none]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions'! selectConflicts: changeSetOrList "Selects all method definitions for which there is ALSO an entry in the specified changeSet or changList" | change class systemChanges | Cursor read showWhile: [(changeSetOrList isKindOf: ChangeSet) ifTrue: [ 1 to: changeList size do: [:i | change _ changeList at: i. listSelections at: i put: (change type = #method and: [(class _ change methodClass) notNil and: [(changeSetOrList atSelector: change methodSelector class: class) ~~ #none]])]] ifFalse: ["a ChangeList" 1 to: changeList size do: [:i | change _ changeList at: i. listSelections at: i put: (change type = #method and: [(class _ change methodClass) notNil and: [changeSetOrList list includes: (list at: i)]])]] ]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'tk 6/24/1999 07:22'! selectConflictsWith "Selects all method definitions for which there is ALSO an entry in the specified changeSet or changList chosen by the user. 4/11/96 tk" | aStream all index | aStream _ WriteStream on: (String new: 200). all _ ChangeSet allSubInstances asOrderedCollection. all do: [:sel | aStream nextPutAll: (sel name contractTo: 40); cr]. ChangeList allSubInstancesDo: [:sel | aStream nextPutAll: (sel file name); cr. all addLast: sel]. aStream skip: -1. index _ (PopUpMenu labels: aStream contents) startUp. index > 0 ifTrue: [ self selectConflicts: (all at: index)]. ! ! !ChangeList methodsFor: 'menu actions' stamp: 'ls 11/14/1998 14:30'! selectMethodsForThisClass | name | self currentChange ifNil: [ ^self ]. name _ self currentChange methodClassName. name ifNil: [ ^self ]. ^self selectSuchThat: [ :change | change methodClassName = name ].! ! !ChangeList methodsFor: 'menu actions' stamp: 'sw 1/7/2000 15:04'! selectSuchThat "query the user for a selection criterio. By Lex Spoon. NB: the UI for invoking this from a changelist browser is currently commented out; to reenfranchise it, you'll need to mild editing to ChangeList method #changeListMenu:" | code block | code _ FillInTheBlank request: 'selection criteria for a change named aChangeRecord?\For instance, ''aChangeRecord category = ''System-Network''''' withCRs. code isEmpty ifTrue: [^ self ]. block _ Compiler evaluate: '[:aChangeRecord | ', code, ']'. self selectSuchThat: block! ! !ChangeList methodsFor: 'menu actions' stamp: 'ls 5/12/1999 07:56'! selectSuchThat: aBlock "select all changes for which block returns true" listSelections _ changeList collect: [ :change | aBlock value: change ]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'tk 1/7/98 10:12'! selectUnchangedMethods "Selects all method definitions for which there is already a method in the current image, whose source is exactly the same. 9/18/96 sw" | change class | Cursor read showWhile: [1 to: changeList size do: [:i | change _ changeList at: i. listSelections at: i put: ((change type = #method and: [(class _ change methodClass) notNil]) and: [(class includesSelector: change methodSelector) and: [change string = (class sourceCodeAt: change methodSelector) asString]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 12/28/2000 10:12'! annotation "Answer the string to be shown in an annotation pane. Make plain that the annotation is associated with the current in-image version of the code, not of the selected disk-based version." | annot | annot _ super annotation. ^ annot asString = '------' ifTrue: [annot] ifFalse: ['current version: ', annot]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 9/27/1999 16:01'! contents ^ self showDiffs ifFalse: [self undiffedContents] ifTrue: [self showsVersions ifTrue: [self diffedVersionContents] ifFalse: [self contentsDiffedFromCurrent]]! ! !ChangeList methodsFor: 'viewing access' stamp: 'tk 4/10/1998 09:25'! contents: aString listIndex = 0 ifTrue: [self changed: #flash. ^ false]. lostMethodPointer ifNotNil: [^ self restoreDeletedMethod]. self okToChange "means not dirty" ifFalse: ["is dirty" self inform: 'This is a view of a method on a file.\Please cancel your changes. You may\accept, but only when the method is untouched.' withCRs. ^ false]. "Can't accept changes here. Method text must be unchanged!!" (changeList at: listIndex) fileIn. ^ true! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 1/12/1999 12:40'! contentsDiffedFromCurrent | aChange aClass | listIndex = 0 ifTrue: [^ '']. aChange _ changeList at: listIndex. ^ ((aChange type == #method and: [(aClass _ aChange methodClass) notNil]) and: [aClass includesSelector: aChange methodSelector]) ifTrue: [Utilities methodDiffFor: aChange text class: aClass selector: aChange methodSelector] ifFalse: [(changeList at: listIndex) text]! ! !ChangeList methodsFor: 'viewing access' stamp: 'nk 10/29/2000 12:38'! diffedVersionContents | thisText change class | (listIndex = 0 or: [changeList size < listIndex]) ifTrue: [^ '']. change _ changeList at: listIndex. thisText _ change text. class _ change methodClass. ^ listIndex == changeList size ifTrue: [thisText] ifFalse: [TextDiffBuilder buildDisplayPatchFrom: (changeList at: listIndex + 1) text to: thisText inClass: class]! ! !ChangeList methodsFor: 'viewing access'! list ^ list! ! !ChangeList methodsFor: 'viewing access'! listIndex ^ listIndex! ! !ChangeList methodsFor: 'viewing access'! listSelectionAt: index ^ listSelections at: index! ! !ChangeList methodsFor: 'viewing access' stamp: 'di 1/13/1999 14:59'! listSelectionAt: index put: value ^ listSelections at: index put: value! ! !ChangeList methodsFor: 'viewing access' stamp: 'di 6/15/97 16:46'! restoreDeletedMethod "If lostMethodPointer is not nil, then this is a version browser for a method that has been removed. In this case we want to establish a sourceCode link to prior versions. We do this by installing a dummy method with the correct source code pointer prior to installing this version." | dummyMethod class selector | dummyMethod _ CompiledMethod toReturnSelf setSourcePointer: lostMethodPointer. class _ (changeList at: listIndex) methodClass. selector _ (changeList at: listIndex) methodSelector. class addSelector: selector withMethod: dummyMethod. (changeList at: listIndex) fileIn. "IF for some reason, the dummy remains, remove it, but (N.B.!!) we might not get control back if the compile (fileIn above) fails." (class compiledMethodAt: selector) == dummyMethod ifTrue: [class removeSelectorSimply: selector]. ^ true! ! !ChangeList methodsFor: 'viewing access' stamp: 'sma 2/5/2000 19:09'! selectedClassOrMetaClass | c | ^ (c _ self currentChange) ifNotNil: [c methodClass]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sma 2/5/2000 19:10'! selectedMessageName | c | ^ (c _ self currentChange) ifNotNil: [c methodSelector]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 1/28/1999 12:30'! toggleListIndex: newListIndex listIndex ~= 0 ifTrue: [listSelections at: listIndex put: false]. newListIndex ~= 0 ifTrue: [listSelections at: newListIndex put: true]. listIndex _ newListIndex. self changed: #listIndex. self contentsChanged! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 1/25/1999 14:45'! undiffedContents ^ listIndex = 0 ifTrue: [''] ifFalse: [(changeList at: listIndex) text]! ! !ChangeList methodsFor: 'accessing'! changeList ^ changeList! ! !ChangeList methodsFor: 'accessing' stamp: 'ls 5/12/1999 07:55'! currentChange "return the current change being viewed, or nil if none" listIndex = 0 ifTrue: [ ^nil ]. ^changeList at: listIndex! ! !ChangeList methodsFor: 'accessing'! file ^file! ! !ChangeList methodsFor: 'accessing' stamp: 'TPR 11/28/1998 17:38'! listHasSingleEntry "does the list of changes have only a single item?" ^list size = 1! ! !ChangeList methodsFor: 'accessing' stamp: 'tk 6/21/1999 20:43'! listSelections listSelections ifNil: [ list ifNotNil: [ listSelections _ Array new: list size withAll: false]]. ^ listSelections! ! !ChangeList methodsFor: 'accessing' stamp: 'di 6/15/97 15:13'! setLostMethodPointer: sourcePointer lostMethodPointer _ sourcePointer! ! !ChangeList methodsFor: 'accessing' stamp: 'sw 10/19/1999 15:11'! showsVersions ^ false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangeList class instanceVariableNames: ''! !ChangeList class methodsFor: 'public access' stamp: 'di 1/18/2001 15:30'! browseFile: fileName "ChangeList browseFile: 'AutoDeclareFix.st'" "Opens a changeList on the file named fileName" ^ self browseStream: (FileStream readOnlyFileNamed: fileName)! ! !ChangeList class methodsFor: 'public access' stamp: 'di 5/16/1998 21:53'! browseRecent: charCount "ChangeList browseRecent: 5000" "Opens a changeList on the end of the changes log file" | changesFile changeList end | changesFile _ (SourceFiles at: 2) readOnlyCopy. end _ changesFile size. Cursor read showWhile: [changeList _ self new scanFile: changesFile from: (0 max: end-charCount) to: end]. changesFile close. self open: changeList name: 'Recent changes' multiSelect: true! ! !ChangeList class methodsFor: 'public access' stamp: 'di 6/12/1998 16:33'! browseRecentLog "ChangeList browseRecentLog" "Prompt with a menu of how far back to go" | end changesFile banners positions pos chunk i | changesFile _ (SourceFiles at: 2) readOnlyCopy. banners _ OrderedCollection new. positions _ OrderedCollection new. end _ changesFile size. pos _ Smalltalk lastQuitLogPosition. [pos = 0 or: [banners size > 20]] whileFalse: [changesFile position: pos. chunk _ changesFile nextChunk. i _ chunk indexOfSubCollection: 'priorSource: ' startingAt: 1. i > 0 ifTrue: [positions addLast: pos. banners addLast: (chunk copyFrom: 5 to: i-2). pos _ Number readFrom: (chunk copyFrom: i+13 to: chunk size)] ifFalse: [pos _ 0]]. changesFile close. pos _ (SelectionMenu labelList: banners selections: positions) startUpWithCaption: 'Browse as far back as...'. pos == nil ifTrue: [^ self]. self browseRecent: end-pos! ! !ChangeList class methodsFor: 'public access' stamp: 'di 1/18/2001 15:23'! browseStream: changesFile "Opens a changeList on a fileStream" | changeList charCount | changesFile readOnly. charCount _ changesFile size. charCount > 1000000 ifTrue: [(self confirm: 'The file ', changesFile name , ' is really long (' , charCount printString , ' characters). Would you prefer to view only the last million characters?') ifTrue: [charCount _ 1000000]]. Cursor read showWhile: [changeList _ self new scanFile: changesFile from: changesFile size-charCount to: changesFile size]. changesFile close. self open: changeList name: changesFile localName , ' log' multiSelect: true! ! !ChangeList class methodsFor: 'public access' stamp: 'sw 2/3/2000 16:16'! getRecentLocatorWithPrompt: aPrompt "Prompt with a menu of how far back to go. Return nil if user backs out. Otherwise return the number of characters back from the end of the .changes file the user wishes to include" "ChangeList getRecentPosition" | end changesFile banners positions pos chunk i | changesFile _ (SourceFiles at: 2) readOnlyCopy. banners _ OrderedCollection new. positions _ OrderedCollection new. end _ changesFile size. pos _ Smalltalk lastQuitLogPosition. [pos = 0 or: [banners size > 20]] whileFalse: [changesFile position: pos. chunk _ changesFile nextChunk. i _ chunk indexOfSubCollection: 'priorSource: ' startingAt: 1. i > 0 ifTrue: [positions addLast: pos. banners addLast: (chunk copyFrom: 5 to: i-2). pos _ Number readFrom: (chunk copyFrom: i+13 to: chunk size)] ifFalse: [pos _ 0]]. changesFile close. pos _ (SelectionMenu labelList: banners selections: positions) startUpWithCaption: aPrompt. pos == nil ifTrue: [^ nil]. ^ end - pos! ! !ChangeList class methodsFor: 'instance creation' stamp: 'sw 1/25/2001 08:44'! open: aChangeList name: aString multiSelect: multiSelect "Create a standard system view for the messageSet, whose label is aString. The listView may be either single or multiple selection type" | topView listHeight annoHeight optButtonHeight codeHeight aListView underPane annotationPane buttonsView aBrowserCodeView | Smalltalk isMorphic ifTrue: [^ self openAsMorph: aChangeList name: aString multiSelect: multiSelect]. listHeight _ 70. annoHeight _ 10. optButtonHeight _ aChangeList optionalButtonHeight. codeHeight _ 110. topView _ (StandardSystemView new) model: aChangeList; label: aString; minimumSize: 200 @ 120; borderWidth: 1. aListView _ (multiSelect ifTrue: [PluggableListViewOfMany] ifFalse: [PluggableListView]) on: aChangeList list: #list selected: #listIndex changeSelected: #toggleListIndex: menu: (aChangeList showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:]) keystroke: #changeListKey:from:. aListView window: (0 @ 0 extent: 200 @ listHeight). topView addSubView: aListView. underPane _ aListView. aChangeList wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: aChangeList text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0 @ 0 extent: 200 @ 10). topView addSubView: annotationPane below: underPane. underPane _ annotationPane. codeHeight _ codeHeight - annoHeight]. aChangeList wantsOptionalButtons ifTrue: [buttonsView _ aChangeList optionalButtonsView. buttonsView borderWidth: 1. topView addSubView: buttonsView below: underPane. underPane _ buttonsView. codeHeight _ codeHeight - optButtonHeight]. aBrowserCodeView _ PluggableTextView on: aChangeList text: #contents accept: #contents: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. aBrowserCodeView controller: ReadOnlyTextController new; window: (0 @ 0 extent: 200 @ codeHeight). topView addSubView: aBrowserCodeView below: underPane. topView controller open.! ! !ChangeList class methodsFor: 'instance creation' stamp: 'RAA 1/11/2001 08:20'! openAsMorph: aChangeList name: labelString multiSelect: multiSelect "Open a morphic view for the messageSet, whose label is labelString. The listView may be either single or multiple selection type" ^aChangeList openAsMorphName: labelString multiSelect: multiSelect ! ! ChangeList subclass: #ChangeListForProjects instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeListForProjects methodsFor: 'as yet unclassified' stamp: 'sw 1/13/2000 12:48'! contents ^ self showDiffs ifFalse: [self undiffedContents] ifTrue: [self currentDiffedFromContents] "Current is writing over one in list. Show how I would change it"! ! !ChangeListForProjects methodsFor: 'as yet unclassified' stamp: 'nk 10/29/2000 12:39'! currentDiffedFromContents | aChange aClass | listIndex = 0 ifTrue: [^ '']. aChange _ changeList at: listIndex. ^ ((aChange type == #method and: [(aClass _ aChange methodClass) notNil]) and: [aClass includesSelector: aChange methodSelector]) ifTrue: [TextDiffBuilder buildDisplayPatchFrom: aChange text to: (aClass sourceCodeAt: aChange methodSelector) inClass: aClass] ifFalse: [(changeList at: listIndex) text]! ! Object subclass: #ChangeRecord instanceVariableNames: 'file position type class category meta stamp ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeRecord commentStamp: '' prior: 0! A ChangeRecord represents a change recorded on a file in fileOut format. It includes a type (more needs to be done here), and additional information for certain types such as method defs which need class and category.! !ChangeRecord methodsFor: 'access'! category ^category! ! !ChangeRecord methodsFor: 'access' stamp: 'sw 8/24/1998 08:16'! fileIn | methodClass | Cursor read showWhile: [(methodClass _ self methodClass) notNil ifTrue: [methodClass compile: self text classified: category withStamp: stamp notifying: nil]. (type == #doIt) ifTrue: [Compiler evaluate: self string]. (type == #classComment) ifTrue: [(Smalltalk at: class asSymbol) comment: self text stamp: stamp]]! ! !ChangeRecord methodsFor: 'access' stamp: 'sw 1/15/98 22:09'! fileOutOn: f type == #method ifTrue: [f nextPut: $!!. f nextChunkPut: class asString , (meta ifTrue: [' class methodsFor: '] ifFalse: [' methodsFor: ']) , category asString printString. f cr]. type == #preamble ifTrue: [f nextPut: $!!]. type == #classComment ifTrue: [f nextPut: $!!. f nextChunkPut: class asString, ' commentStamp: ', stamp storeString. f cr]. f nextChunkPut: self string. type == #method ifTrue: [f nextChunkPut: ' ']. f cr! ! !ChangeRecord methodsFor: 'access' stamp: 'tk 6/24/1999 15:27'! headerFor: selector ^ ' ' , class , (meta ifTrue: [' class '] ifFalse: [' ']) , selector , (stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp])! ! !ChangeRecord methodsFor: 'access'! isMetaClassChange ^meta! ! !ChangeRecord methodsFor: 'access'! methodClass | methodClass | type == #method ifFalse: [^ nil]. (Smalltalk includesKey: class asSymbol) ifFalse: [^ nil]. methodClass _ Smalltalk at: class asSymbol. meta ifTrue: [^ methodClass class] ifFalse: [^ methodClass]! ! !ChangeRecord methodsFor: 'access'! methodClassName ^class! ! !ChangeRecord methodsFor: 'access'! methodSelector type == #method ifFalse: [^ nil]. ^ Parser new parseSelector: self string! ! !ChangeRecord methodsFor: 'access' stamp: 'tk 6/21/1999 20:34'! readStamp "Get the time stamp of this method off the file" | item tokens anIndex | stamp _ ''. file ifNil: [^ stamp]. file position: position. item _ file nextChunk. tokens _ Scanner new scanTokens: item. tokens size < 3 ifTrue: [^ stamp]. anIndex _ tokens indexOf: #stamp: ifAbsent: [^ stamp]. ^ stamp _ tokens at: (anIndex + 1). ! ! !ChangeRecord methodsFor: 'access' stamp: '6/6/97 08:56 dhhi'! stamp ^ stamp! ! !ChangeRecord methodsFor: 'access' stamp: 'tk 9/7/2000 15:09'! stamp: threePartString stamp _ threePartString! ! !ChangeRecord methodsFor: 'access' stamp: 'di 1/13/98 16:57'! string | string | file openReadOnly. file position: position. string _ file nextChunk. file close. ^ string! ! !ChangeRecord methodsFor: 'access' stamp: 'tk 6/23/1999 08:20'! text | text | ^ file ifNil: [''] ifNotNil: [ file openReadOnly. file position: position. text _ file nextChunkText. file close. text]! ! !ChangeRecord methodsFor: 'access'! type ^ type! ! !ChangeRecord methodsFor: 'initialization' stamp: 'tk 6/24/1999 14:51'! class: clsName category: cat method: method sourceFiles: fileArray "This should be enough to find all the information for a method, or method deletion" file _ fileArray at: method fileIndex. position _ method filePosition. type _ #method. class _ clsName copyUpTo: $ . "the non-meta part of a class name" category _ cat. meta _ clsName endsWith: ' class'. self readStamp.! ! !ChangeRecord methodsFor: 'initialization'! file: f position: p type: t file _ f. position _ p. type _ t! ! !ChangeRecord methodsFor: 'initialization' stamp: '6/6/97 08:48 dhhi'! file: f position: p type: t class: c category: cat meta: m stamp: s self file: f position: p type: t. class _ c. category _ cat. meta _ m. stamp _ s! ! Object subclass: #ChangeSet instanceVariableNames: 'name preamble postscript revertable isolationSet isolatedProject changeRecords structures superclasses ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSet commentStamp: '' prior: 0! ChangeSets keep track of the changes made to a system, so they can be written on a file as source code (a "fileOut"). Every project has an associated changeSet. For simple projects, a different changeSet may be designated to capture changes at any time. This implementation of ChangeSet is capable of remembering and manipulating methods for which the classes are not present in the system. However at the present time, this capability is not used in normal rearranging and fileOuts, but only for invoking and revoking associated with isolation layers. For isolated projects (see Project class comment), the changeSet binding is semi-permanent. Every project exists in an isolation layer defined by its closest enclosing parent (or itself) that is isolated. If a project is not isolated, then changes reported to its designated changeSet must also be reported to the permanent changeSet for that layer, designated in the isolated project. This ensures that that outer project will be able to revert all changes upon exit. Note that only certain changes may be reverted. Classes may not be added, removed, renamed or reshaped except in the layer in which they are defined because these operations on non-local classes are not revertable. If a Squeak Project is established as being isolated, then its associated changeSet will be declared to be revertable. In this case all changes stored can be reverted. The changeSet associated with an isolated project is tied to that project, and cannot be edited in a changeSorter. ------ name - a String used to name the changeSet, and thus any associated project or fileOut. preamble and postscript: two strings that serve as prefix (useful for documentation) and suffix (useful for doits) to the fileout of the changeSet. revertable - a Boolean If this variable is true, then all of the changes recorded by this changeSet can be reverted. isolationSet - a ChangeSet or nil The isolationSet is the designated changeSet for an isolation layer. If this changeSet is an isolationSet, then this variable will be nil. If not, then it points to the isolationSet for this layer, and all changes reported here will also be reported to the isolationSet. isolatedProject - a Project or nil If this is an isolationSet, then this variable points to the project with which it is associated. changeRecords - Dictionary {class name -> a ClassChangeRecord}. These classChangeRecords (qv) remember all of the system changes. structures - Dictionary {#Rectangle -> #( 'origin' 'corner')}. Of the names of the instances variables before any changes for all classes in classChanges, and all of their superclasses. In the same format used in SmartRefStream. Inst var names are strings. superclasses - Dictionary {#Rectangle -> #Object}. Of all classes in classChanges, and all of their superclasses. Structures and superclasses save the instance variable names of this class and all of its superclasses. Later we can tell how it changed and write a conversion method. The conversion method is used when old format objects are brought in from the disk from ImageSegment files (.extSeg) or SmartRefStream files (.obj .morph .bo .sp). NOTE: It should be fairly simple, by adding a bit more information to the classChangeRecords, to reconstruct the information now stored in 'structures' and 'superclasses'. This would be a welcome simplification. ! !ChangeSet methodsFor: 'initialize-release' stamp: 'di 3/29/2000 20:42'! beIsolationSetFor: aProject self isEmpty ifFalse: [self error: 'Must be empty at the start.']. isolatedProject _ aProject. revertable _ true.! ! !ChangeSet methodsFor: 'initialize-release' stamp: 'di 4/1/2000 12:00'! clear "Reset the receiver to be empty. " changeRecords _ Dictionary new. preamble _ nil. postscript _ nil! ! !ChangeSet methodsFor: 'initialize-release' stamp: 'di 4/3/2000 14:46'! initialize "Reset the receiver to be empty." revertable _ false. self clear. "Avoid duplicate entries in AllChanges if initialize gets called twice" name _ ChangeSet defaultName. ! ! !ChangeSet methodsFor: 'initialize-release'! isMoribund "Answer whether the receiver is obsolete and about to die; part of an effort to get such guys cleared out from the change sorter. 2/7/96 sw" ^ name == nil ! ! !ChangeSet methodsFor: 'initialize-release' stamp: 'sw 3/6/1999 09:31'! veryDeepCopyWith: deepCopier "Return self; this is NOT the way to launch new change sets!! Having this method here allows Change Sorters to be in parts bins"! ! !ChangeSet methodsFor: 'initialize-release' stamp: 'di 3/23/2000 12:14'! wither "The receiver is to be clobbered. Clear it out. 2/7/96 sw" self clear. name _ nil! ! !ChangeSet methodsFor: 'initialize-release' stamp: 'di 9/21/2000 15:29'! zapHistory "Much stronger than trimHistory, but it should still leave the changeSet in good shape. Must not be done on revertable changeSets ChangeSet allInstancesDo: [:cs | cs zapHistory]." revertable ifTrue: [^ self]. "No can do" changeRecords do: [:chgRecord | chgRecord zapHistory]! ! !ChangeSet methodsFor: 'change logging' stamp: 'di 3/29/2000 13:10'! addClass: class "Include indication that a new class was created." class wantsChangeSetLogging ifFalse: [^ self]. isolationSet ifNotNil: ["If there is an isolation layer above me, inform it as well." isolationSet addClass: class]. self atClass: class add: #new. self atClass: class add: #change. self addCoherency: class name! ! !ChangeSet methodsFor: 'change logging' stamp: 'di 3/29/2000 13:08'! changeClass: class from: oldClass "Remember that a class definition has been changed. Record the original structure, so that a conversion method can be built." class wantsChangeSetLogging ifFalse: [^ self]. isolationSet ifNotNil: ["If there is an isolation layer above me, inform it as well." isolationSet changeClass: class from: oldClass]. self atClass: class add: #change. self addCoherency: class name. (self changeRecorderFor: class) notePriorDefinition: oldClass. self noteClassStructure: oldClass! ! !ChangeSet methodsFor: 'change logging' stamp: 'di 3/29/2000 11:08'! noteNewMethod: newMethod forClass: class selector: selector priorMethod: methodOrNil class wantsChangeSetLogging ifFalse: [^ self]. isolationSet ifNotNil: ["If there is an isolation layer above me, inform it as well." isolationSet noteNewMethod: newMethod forClass: class selector: selector priorMethod: methodOrNil]. (self changeRecorderFor: class) noteNewMethod: newMethod selector: selector priorMethod: methodOrNil ! ! !ChangeSet methodsFor: 'change logging' stamp: 'di 3/29/2000 12:29'! removeSelector: selector class: class priorMethod: priorMethod lastMethodInfo: info "Include indication that a method has been forgotten. info is a pair of the source code pointer and message category for the method that was removed." class wantsChangeSetLogging ifFalse: [^ self]. isolationSet ifNotNil: ["If there is an isolation layer above me, inform it as well." isolationSet removeSelector: selector class: class priorMethod: priorMethod lastMethodInfo: info]. (self changeRecorderFor: class) noteRemoveSelector: selector priorMethod: priorMethod lastMethodInfo: info ! ! !ChangeSet methodsFor: 'change logging' stamp: 'di 5/8/2000 20:40'! renameClass: class as: newName "Include indication that a class has been renamed." | recorder | isolationSet ifNotNil: ["If there is an isolation layer above me, inform it as well." isolationSet renameClass: class as: newName]. (recorder _ self changeRecorderFor: class) noteChangeType: #rename; noteNewName: newName asSymbol. "store under new name (metaclass too)" changeRecords at: newName put: recorder. changeRecords removeKey: class name. recorder _ changeRecords at: class class name ifAbsent: [^ nil]. changeRecords at: (newName, ' class') put: recorder. changeRecords removeKey: class class name. recorder noteNewName: newName , ' class'! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 4/1/2000 09:25'! compileAll: newClass from: oldClass "If I have changes for this class, recompile them" (changeRecords at: newClass ifAbsent: [^ self]) compileAll: newClass from: oldClass ! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 3/29/2000 14:47'! invoke "Do the first part of the invoke operation -- no particular hurry." changeRecords do: [:changeRecord | changeRecord invokePhase1]. "Complete the invoke process -- this must be very simple." "Replace method dicts for any method changes." changeRecords do: [:changeRecord | changeRecord invokePhase2]. Behavior flushCache. ! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 4/13/2000 12:47'! isolatedProject "Return the isolated project for which I am the changeSet." ^ isolatedProject! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 3/29/2000 13:59'! isolationSet: setOrNil setOrNil == self ifTrue: [isolationSet _ nil] "Means this IS the isolation set" ifFalse: [isolationSet _ setOrNil]! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 3/29/2000 14:47'! revoke "Do the first part of the revoke operation -- this must be very simple." "Replace original method dicts if there are method changes." changeRecords do: [:changeRecord | changeRecord revokePhase1]. Behavior flushCache. "Complete the revoke process -- no particular hurry." changeRecords do: [:changeRecord | changeRecord revokePhase2]. ! ! !ChangeSet methodsFor: 'isolation layers' stamp: 'di 3/23/2000 12:00'! uninstall self halt. ! ! !ChangeSet methodsFor: 'accessing' stamp: 'di 4/1/2000 12:00'! classRemoves ^ changeRecords keys select: [:className | (changeRecords at: className) isClassRemoval]! ! !ChangeSet methodsFor: 'accessing' stamp: 'di 4/8/2000 23:16'! editPostscript "edit the receiver's postscript, in a separate window. " | deps found | self assurePostscriptExists. deps _ postscript dependents select: [:m | (m isKindOf: SystemWindow) or: [m isKindOf: StandardSystemView]]. deps size > 0 ifTrue: [Smalltalk isMorphic ifTrue: [found _ deps detect: [:obj | (obj isKindOf: SystemWindow) and: [obj world == self currentWorld]] ifNone: [nil]. found ifNotNil: [^ found activate]] ifFalse: [found _ deps detect: [:obj | (obj isKindOf: StandardSystemView) and: [ScheduledControllers scheduledControllers includes: obj controller]] ifNone: [nil]. found ifNotNil: [^ ScheduledControllers activateController: found controller]]. . self inform: 'Caution -- there', (deps size isOrAreStringWith: 'other window'), ' already open on this postscript elsewhere']. postscript openLabel: 'Postscript for ChangeSet named ', name! ! !ChangeSet methodsFor: 'accessing' stamp: 'sw 6/29/1999 14:44'! hasPostscript ^ postscript notNil! ! !ChangeSet methodsFor: 'accessing' stamp: 'di 4/1/2000 12:00'! methodChanges | methodChangeDict changeTypes | methodChangeDict _ Dictionary new. changeRecords associationsDo: [:assn | changeTypes _ assn value methodChangeTypes. changeTypes isEmpty ifFalse: [methodChangeDict at: assn key put: changeTypes]]. ^ methodChangeDict! ! !ChangeSet methodsFor: 'accessing' stamp: 'di 3/29/2000 16:22'! methodInfoFromRemoval: classAndSelector ^ (self changeRecorderFor: classAndSelector first) infoFromRemoval: classAndSelector last! ! !ChangeSet methodsFor: 'accessing'! name "The name of this changeSet. 2/7/96 sw: If name is nil, we've got garbage. Help to identify." ^ name == nil ifTrue: [''] ifFalse: [name]! ! !ChangeSet methodsFor: 'accessing'! name: anObject name _ anObject! ! !ChangeSet methodsFor: 'accessing' stamp: 'sw 6/29/1999 20:51'! postscriptHasDependents ^ postscript dependents size > 0! ! !ChangeSet methodsFor: 'accessing'! printOn: aStream "2/7/96 sw: provide the receiver's name in the printout" super printOn: aStream. aStream nextPutAll: ' named ', self name! ! !ChangeSet methodsFor: 'accessing' stamp: 'MPW 1/1/1901 22:02'! printOnStream: aStream "2/7/96 sw: provide the receiver's name in the printout" super printOnStream: aStream. aStream print: ' named ', self name! ! !ChangeSet methodsFor: 'accessing' stamp: 'sw 6/29/1999 14:48'! removePostscript postscript _ nil! ! !ChangeSet methodsFor: 'accessing' stamp: 'tk 6/8/1999 22:25'! structures ^structures! ! !ChangeSet methodsFor: 'accessing' stamp: 'tk 6/8/1999 22:25'! superclasses ^superclasses! ! !ChangeSet methodsFor: 'testing' stamp: 'RAA 10/1/2000 12:28'! belongsToAProject Smalltalk at: #Project ifPresent: [:projClass | projClass allProjects do: [:proj | proj projectChangeSet == self ifTrue: [^ true]]]. ^ false! ! !ChangeSet methodsFor: 'testing' stamp: 'RAA 11/13/2000 17:15'! correspondingProject "If the receiver is the current change set for any project, answer it, else answer nil" ^Project allProjects detect: [ :proj | proj projectChangeSet == self ] ifNone: [nil] ! ! !ChangeSet methodsFor: 'testing' stamp: 'RAA 10/19/2000 13:17'! isEmpty "Answer whether the receiver contains any elements." changeRecords ifNil: [^true]. ^ changeRecords isEmpty ! ! !ChangeSet methodsFor: 'testing' stamp: 'sw 8/3/1998 16:25'! okayToRemove ^ self okayToRemoveInforming: true! ! !ChangeSet methodsFor: 'testing' stamp: 'sw 8/3/1998 16:25'! okayToRemoveInforming: aBoolean "Answer whether it is okay to remove the receiver. If aBoolean is true, inform the receiver if it is not okay" | aName | aName _ self name. self == Smalltalk changes ifTrue: [aBoolean ifTrue: [self inform: 'Cannot remove "', aName, '" because it is the current change set.']. ^ false]. self belongsToAProject ifTrue: [aBoolean ifTrue: [self inform: 'Cannot remove "', aName, '" because it belongs to a project.']. ^ false]. ^ true ! ! !ChangeSet methodsFor: 'testing' stamp: 'RAA 9/27/2000 22:40'! projectsBelongedTo "Answer a list of all the projects for which the receiver is the current change set" ^ Project allProjects select: [:proj | proj projectChangeSet == self] ! ! !ChangeSet methodsFor: 'converting' stamp: 'RAA 12/20/2000 16:02'! convertApril2000: varDict using: smartRefStrm | cls info selector pair classChanges methodChanges methodRemoves classRemoves | "These variables are automatically stored into the new instance: ('name' 'preamble' 'postscript' 'structures' 'superclasses' ). This method is for additional changes. It initializes the isolation variables, and then duplicates the logic fo assimilateAllChangesFoundIn:." revertable _ false. isolationSet _ nil. isolatedProject _ nil. changeRecords _ Dictionary new. classChanges _ varDict at: 'classChanges'. classChanges keysDo: [:className | (cls _ Smalltalk classNamed: className) ifNotNil: [info _ classChanges at: className ifAbsent: [Set new]. info do: [:each | self atClass: cls add: each]]]. methodChanges _ varDict at: 'methodChanges'. methodRemoves _ varDict at: 'methodRemoves'. methodChanges keysDo: [:className | (cls _ Smalltalk classNamed: className) ifNotNil: [info _ methodChanges at: className ifAbsent: [Dictionary new]. info associationsDo: [:assoc | selector _ assoc key. (assoc value == #remove or: [assoc value == #addedThenRemoved]) ifTrue: [assoc value == #addedThenRemoved ifTrue: [self atSelector: selector class: cls put: #add]. pair _ methodRemoves at: {cls name. selector} ifAbsent: [nil] . self removeSelector: selector class: cls priorMethod: nil lastMethodInfo: pair] ifFalse: [self atSelector: selector class: cls put: assoc value]]]]. classRemoves _ varDict at: 'classRemoves'. classRemoves do: [:className | self noteRemovalOf: className]. ! ! !ChangeSet methodsFor: 'converting' stamp: 'RAA 12/20/2000 17:57'! convertToCurrentVersion: varDict refStream: smartRefStrm "major change - 4/4/2000" varDict at: 'classChanges' ifPresent: [ :x | self convertApril2000: varDict using: smartRefStrm ]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !ChangeSet methodsFor: 'method changes' stamp: 'sw 12/28/2000 18:08'! adoptSelector: aSelector forClass: aClass "Adopt the given selector/class combination as a change in the receiver" self noteNewMethod: (aClass methodDictionary at: aSelector) forClass: aClass selector: aSelector priorMethod: nil! ! !ChangeSet methodsFor: 'method changes' stamp: 'di 3/29/2000 11:01'! atSelector: selector class: class put: changeType (selector == #DoIt or: [selector == #DoItIn:]) ifTrue: [^ self]. (self changeRecorderFor: class) atSelector: selector put: changeType. ! ! !ChangeSet methodsFor: 'method changes' stamp: 'sw 10/19/1999 15:01'! browseMessagesWithPriorVersions "Open a message list browser on the new and changed methods in the receiver which have at least one prior version. 6/28/96 sw" | aList aSelector aClass | aList _ self changedMessageListAugmented select: [:msg | Utilities setClassAndSelectorFrom: msg in: [:cl :sl | aClass _ cl. aSelector _ sl]. (VersionsBrowser versionCountForSelector: aSelector class: aClass) > 1]. aList size > 0 ifFalse: [self inform: 'None!!'. ^ nil]. Smalltalk browseMessageList: aList name: (self name, ' methods that have prior versions')! ! !ChangeSet methodsFor: 'method changes' stamp: 'di 4/1/2000 12:00'! changedMessageList "Used by a message set browser to access the list view information." | messageList | messageList _ SortedCollection new. changeRecords associationsDo: [:clAssoc | clAssoc value methodChangeTypes associationsDo: [:mAssoc | (#(remove addedThenRemoved) includes: mAssoc value) ifFalse: [messageList add: clAssoc key asString, ' ' , mAssoc key]]]. ^ messageList asArray! ! !ChangeSet methodsFor: 'method changes' stamp: 'tk 6/7/1999 18:57'! changedMessageListAugmented "Even added classes have all messages in changedMessageList." ^ self changedMessageList asArray! ! !ChangeSet methodsFor: 'method changes' stamp: 'di 4/1/2000 12:00'! methodChangesAtClass: className "Return an old-style dictionary of method change types." ^(changeRecords at: className ifAbsent: [^ Dictionary new]) methodChangeTypes! ! !ChangeSet methodsFor: 'method changes' stamp: 'di 4/4/2000 11:14'! removeSelectorChanges: selector class: class "Remove all memory of changes associated with the argument, selector, in this class." | chgRecord | (chgRecord _ changeRecords at: class name ifAbsent: [^ self]) removeSelector: selector. chgRecord hasNoChanges ifTrue: [changeRecords removeKey: class name]! ! !ChangeSet methodsFor: 'method changes' stamp: 'SqR 6/13/2000 19:16'! selectorsInClass: aClassName "Used by a ChangeSorter to access the list methods." ^ (changeRecords at: aClassName ifAbsent: [^#()]) changedSelectors! ! !ChangeSet methodsFor: 'class changes' stamp: 'di 4/1/2000 12:00'! changedClassNames "Answer a OrderedCollection of the names of changed or edited classes. DOES include removed classes. Sort alphabetically." ^ changeRecords keysSortedSafely ! ! !ChangeSet methodsFor: 'class changes' stamp: 'di 3/23/2000 08:12'! changedClasses "Answer an OrderedCollection of changed or edited classes. Does not include removed classes. Sort alphabetically by name." "Much faster to sort names first, then convert back to classes. Because metaclasses reconstruct their name at every comparison in the sorted collection. 8/91 sw chgd to filter out non-existent classes (triggered by problems with class-renames" ^ self changedClassNames collect: [:className | Smalltalk classNamed: className] thenSelect: [:aClass | aClass notNil]! ! !ChangeSet methodsFor: 'class changes' stamp: 'di 4/1/2000 12:00'! classChangeAt: className "Return what we know about class changes to this class." ^ (changeRecords at: className ifAbsent: [^ Set new]) allChangeTypes! ! !ChangeSet methodsFor: 'class changes'! commentClass: class "Include indication that a class comment has been changed." self atClass: class add: #comment! ! !ChangeSet methodsFor: 'class changes' stamp: 'RAA 6/16/2000 15:13'! fatDefForClass: class | newDef oldDef oldStrm newStrm outStrm oldVars newVars addedVars | newDef _ class definition. oldDef _ (self changeRecorderFor: class) priorDefinition. oldDef ifNil: [^ newDef]. oldDef = newDef ifTrue: [^ newDef]. oldStrm _ ReadStream on: oldDef. newStrm _ ReadStream on: newDef. outStrm _ WriteStream on: (String new: newDef size * 2). "Merge inst vars from old and new defs..." oldStrm upToAll: 'instanceVariableNames'; upTo: $'. outStrm nextPutAll: (newStrm upToAll: 'instanceVariableNames'); nextPutAll: 'instanceVariableNames:'. newStrm peek = $: ifTrue: [newStrm next]. "may or may not be there, but already written" outStrm nextPutAll: (newStrm upTo: $'); nextPut: $'. oldVars _ (oldStrm upTo: $') findTokens: Character separators. newVars _ (newStrm upTo: $') findTokens: Character separators. addedVars _ oldVars asSet addAll: newVars; removeAll: oldVars; asOrderedCollection. oldVars , addedVars do: [:var | outStrm nextPutAll: var; space]. outStrm nextPut: $'. class isMeta ifFalse: ["Merge class vars from old and new defs..." oldStrm upToAll: 'classVariableNames:'; upTo: $'. outStrm nextPutAll: (newStrm upToAll: 'classVariableNames:'); nextPutAll: 'classVariableNames:'; nextPutAll: (newStrm upTo: $'); nextPut: $'. oldVars _ (oldStrm upTo: $') findTokens: Character separators. newVars _ (newStrm upTo: $') findTokens: Character separators. addedVars _ oldVars asSet addAll: newVars; removeAll: oldVars; asOrderedCollection. oldVars , addedVars do: [:var | outStrm nextPutAll: var; space]. outStrm nextPut: $']. outStrm nextPutAll: newStrm upToEnd. ^ outStrm contents ! ! !ChangeSet methodsFor: 'class changes' stamp: 'tk 6/9/1999 19:54'! noteClassForgotten: className "Remove from structures if class is not a superclass of some other one we are remembering" structures ifNil: [^ self]. Smalltalk at: className ifPresent: [:cls | cls subclasses do: [:sub | (structures includesKey: sub) ifTrue: [ ^ self]]]. "No delete" structures removeKey: className ifAbsent: [].! ! !ChangeSet methodsFor: 'class changes' stamp: 'tk 6/9/1999 21:51'! noteClassStructure: aClass "Save the instance variable names of this class and all of its superclasses. Later we can tell how it changed and write a conversion method. The conversion method is used when old format objects are brought in from the disk from ImageSegment files (.extSeg) or SmartRefStream files (.obj .morph .bo .sp)." | clsName | aClass ifNil: [^ self]. structures ifNil: [structures _ Dictionary new. superclasses _ Dictionary new]. clsName _ (aClass name asLowercase beginsWith: 'anobsolete') ifTrue: [(aClass name copyFrom: 11 to: aClass name size) asSymbol] ifFalse: [aClass name]. (structures includesKey: clsName) ifFalse: [ structures at: clsName put: ((Array with: aClass classVersion), (aClass allInstVarNames)). superclasses at: clsName put: aClass superclass name]. "up the superclass chain" aClass superclass ifNotNil: [self noteClassStructure: aClass superclass]. ! ! !ChangeSet methodsFor: 'class changes' stamp: 'di 4/1/2000 23:17'! noteRemovalOf: class "The class is about to be removed from the system. Adjust the receiver to reflect that fact." (self changeRecorderFor: class) noteChangeType: #remove fromClass: class. changeRecords removeKey: class class name ifAbsent: [].! ! !ChangeSet methodsFor: 'class changes'! reorganizeClass: class "Include indication that a class was reorganized." self atClass: class add: #reorganize! ! !ChangeSet methodsFor: 'class changes' stamp: 'di 5/16/2000 09:03'! trimHistory "Drop non-essential history: methods added and then removed, as well as rename and reorganization of newly-added classes." changeRecords do: [:chgRecord | chgRecord trimHistory]! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/4/2000 09:37'! absorbClass: className from: otherChangeSet "Absorb into the receiver all the changes found in the class in the other change set. *** Classes renamed in otherChangeSet may have problems" | cls | (self changeRecorderFor: className) assimilateAllChangesIn: (otherChangeSet changeRecorderFor: className). (cls _ Smalltalk classNamed: className) ifNotNil: [self absorbStructureOfClass: cls from: otherChangeSet]. ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 3/23/2000 11:52'! absorbMethod: selector class: aClass from: aChangeSet "Absorb into the receiver all the changes for the method in the class in the other change set." | info | info _ aChangeSet methodChanges at: aClass name ifAbsent: [Dictionary new]. self atSelector: selector class: aClass put: (info at: selector). ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 1/30/2001 15:41'! absorbStructureOfClass: aClass from: otherChangeSet "Absorb into the receiver all the structure and superclass info in the other change set. Used to write conversion methods." | sup next | otherChangeSet structures ifNil: [^ self]. (otherChangeSet structures includesKey: aClass name) ifFalse: [^ self]. structures ifNil: [structures _ Dictionary new. superclasses _ Dictionary new]. sup _ aClass name. [(structures includesKey: sup) ifTrue: ["use what is here" true] ifFalse: [self flag: #noteToDan. "sw 1/30/2001 13:57 emergency workaround -- a case arose where the otherChangeSet's structures did not have the key, and it gummed up the works." (otherChangeSet structures includesKey: sup) ifTrue: [structures at: sup put: (otherChangeSet structures at: sup)]. next _ otherChangeSet superclasses at: sup. superclasses at: sup put: next. (sup _ next) = 'nil'] ] whileFalse. ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/4/2000 11:21'! assimilateAllChangesFoundIn: otherChangeSet "Make all changes in otherChangeSet take effect on self as if they happened just now." otherChangeSet changedClassNames do: [:className | self absorbClass: className from: otherChangeSet] ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/8/2000 23:16'! editPreamble "edit the receiver's preamble, in a separate window. " self assurePreambleExists. preamble openLabel: 'Preamble for ChangeSet named ', name! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/4/2000 11:49'! expungeEmptyClassChangeEntries changeRecords keysAndValuesRemove: [:className :classRecord | classRecord hasNoChanges]! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 4/19/2000 16:17'! expungeUniclasses changeRecords keysAndValuesRemove: [:className :classRecord | className endsWithDigit]! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/4/2000 12:40'! forgetAllChangesFoundIn: otherChangeSet "Remove from the receiver all method changes found in aChangeSet. The intention is facilitate the process of factoring a large set of changes into disjoint change sets. To use: in a change sorter, copy over all the changes you want into some new change set, then use the subtract-other-side feature to subtract those changes from the larger change set, and continue in this manner." otherChangeSet == self ifTrue: [^ self]. otherChangeSet changedClassNames do: [:className | self forgetChangesForClass: className in: otherChangeSet]. self expungeEmptyClassChangeEntries. " Old code... aChangeSet changedClassNames do: [:className | (cls _ Smalltalk classNamed: className) ~~ nil ifTrue: [itsMethodChanges _ aChangeSet methodChanges at: className ifAbsent: [Dictionary new]. itsMethodChanges associationsDo: [:assoc | self forgetChange: assoc value forSelector: assoc key class: cls]. myClassChange _ self classChangeAt: className. myClassChange size > 0 ifTrue: [(aChangeSet classChangeAt: className) do: [:aChange | myClassChange remove: aChange ifAbsent: []]]. self noteClassForgotten: className]]. aChangeSet classRemoves do: [:className | (recorder _ changeRecords at: className ifAbsent: []) ifNotNil: [recorder forgetClassRemoval]]. self expungeEmptyClassChangeEntries " ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/4/2000 12:04'! forgetChangesForClass: className in: otherChangeSet "See forgetAllChangesFoundIn:. Used in culling changeSets." (self changeRecorderFor: className) forgetChangesIn: (otherChangeSet changeRecorderFor: className). self noteClassForgotten: className ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 3/5/1999 19:27'! hasPreamble ^ preamble notNil! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 10/29/2000 17:39'! methodsWithInitialsOtherThan: myInits "Return a collection of method refs whose author appears to be different from the given one" | slips method aTimeStamp | slips _ OrderedCollection new. self changedClasses do: [:aClass | (self methodChangesAtClass: aClass name) associationsDo: [:mAssoc | (#(remove addedThenRemoved) includes: mAssoc value) ifFalse: [method _ aClass compiledMethodAt: mAssoc key ifAbsent: [nil]. method ifNotNil: [((aTimeStamp _ Utilities timeStampForMethod: method) notNil and: [(aTimeStamp beginsWith: myInits) not]) ifTrue: [slips add: aClass name , ' ' , mAssoc key]]]]]. ^ slips "Smalltalk browseMessageList: (Smalltalk changes methodsWithInitialsOtherThan: 'sw') name: 'authoring problems'"! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 10/30/2000 08:42'! methodsWithoutComments "Return a collection representing methods in the receiver which have no comments" | slips | slips _ OrderedCollection new. self changedClasses do: [:aClass | (self methodChangesAtClass: aClass name) associationsDo: [:mAssoc | (#(remove addedThenRemoved) includes: mAssoc value) ifFalse: [(aClass selectors includes: mAssoc key) ifTrue: [(aClass firstCommentAt: mAssoc key) isEmptyOrNil ifTrue: [slips add: aClass name , ' ' , mAssoc key]]]]]. ^ slips "Smalltalk browseMessageList: (Smalltalk changes methodsWithoutComments) name: 'methods lacking comments'"! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/1/2000 12:00'! removeClassAndMetaClassChanges: class "Remove all memory of changes associated with this class and its metaclass. 7/18/96 sw" changeRecords removeKey: class name ifAbsent: []. changeRecords removeKey: class class name ifAbsent: []. ! ! !ChangeSet methodsFor: 'moving changes' stamp: 'di 4/1/2000 12:00'! removeClassChanges: class "Remove all memory of changes associated with this class" | cname | (class isKindOf: String) ifTrue: [ cname _ class ] ifFalse: [ cname _ class name ]. changeRecords removeKey: cname ifAbsent: []. self noteClassForgotten: cname.! ! !ChangeSet methodsFor: 'moving changes' stamp: 'sw 3/5/1999 19:32'! removePreamble preamble _ nil! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 9/11/1998 16:13'! assurePostscriptExists "Make sure there is a StringHolder holding the postscript. " "NOTE: FileIn recognizes the postscript by the line with Postscript: on it" postscript == nil ifTrue: [postscript _ StringHolder new contents: '"Postscript: Leave the line above, and replace the rest of this comment by a useful one. Executable statements should follow this comment, and should be separated by periods, with no exclamation points (!!). Be sure to put any further comments in double-quotes, like this one." ']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 4/7/1999 17:45'! assurePreambleExists "Make sure there is a StringHolder holding the preamble; if it's found to have reverted to empty contents, put up the template" (preamble == nil or: [preamble contents isEmptyOrNil]) ifTrue: [preamble _ StringHolder new contents: self preambleTemplate]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 1/27/2001 22:30'! checkForAlienAuthorship "Check to see if there are any methods in the receiver that have author initials other than that of the current author, and open a browser on all found" | aList initials | (initials _ Utilities authorInitialsPerSe) ifNil: [^ self inform: 'No author initials set in this image']. (aList _ self methodsWithInitialsOtherThan: initials) size > 0 ifFalse: [^ self inform: 'All methods in "', self name, '" have authoring stamps which start with "', initials, '"'] ifTrue: [Smalltalk browseMessageList: aList name: 'methods in "', self name, '" whose authoring stamps do not start with "', initials, '"']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ls 10/10/1999 11:37'! checkForConversionMethods "See if any conversion methods are needed" | needConversion oldList newList tell choice list need oldVer newVer sel smart restore | "Check preference" Preferences conversionMethodsAtFileOut ifFalse: [^ self]. structures ifNil: [^ self]. needConversion _ false. list _ OrderedCollection new. smart _ SmartRefStream on: (RWBinaryOrTextStream on: '12345'). self changedClasses do: [:class | need _ (self atClass: class includes: #new) not. need ifTrue: [ "Also consider renamed classes." (self atClass: class includes: #rename) ifTrue: [ needConversion _ true. list add: class]. need _ (self atClass: class includes: #change)]. need ifTrue: [oldList _ structures at: class name ifAbsent: [need _ false. #()]]. need ifTrue: [ newList _ (Array with: class classVersion), (class allInstVarNames). need _ (oldList ~= newList)]. need ifTrue: [ oldVer _ smart versionSymbol: oldList. newVer _ smart versionSymbol: newList. sel _ 'convert',oldVer,':',newVer, ':'. (Symbol hasInterned: sel ifTrue: [:ignored |]) ifFalse: [ need _ false. needConversion _ true. list add: class]]. need ifTrue: [sel _ sel asSymbol. (#(add change) includes: (self atSelector: sel class: class)) ifFalse: [ needConversion _ true. list add: class]]. ]. needConversion ifTrue: ["Ask user if want to do this" tell _ 'If there might be instances of ', list asArray printString, '\in a file full of objects on someone''s disk, please fill in conversion methods.\' withCRs, 'After you edit the methods, you''ll have to fileOut again.\' withCRs, 'The preference conversionMethodsAtFileOut controls this feature.'. choice _ (PopUpMenu labels: 'Write a conversion method by editing a prototype These classes are not used in any object file. fileOut my changes now. I''m too busy. fileOut my changes now. Don''t ever ask again. fileOut my changes now.') startUpWithCaption: tell. choice = 4 ifTrue: [Preferences disable: #conversionMethodsAtFileOut]. choice = 2 ifTrue: [ list do: [:cls | cls withAllSubclassesDo: [:ccc | structures removeKey: ccc name ifAbsent: []]]]. choice ~= 1 ifTrue: [^ self]]. list isEmpty ifTrue: [^ self]. smart structures: structures. "we will test all classes in structures." smart superclasses: superclasses. (restore _ Smalltalk changes) == self ifFalse: [ Smalltalk newChanges: self]. [smart verifyStructure = 'conversion method needed'] whileTrue. "new method is added to changeSet. Then filed out with the rest." restore == self ifFalse: [Smalltalk newChanges: restore]. "tell 'em to fileout again after modifying methods." self inform: 'Remember to fileOut again after modifying these methods.'.! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 3/26/2000 10:06'! checkForSlips "Return a collection of method refs with possible debugging code in them." | slips method | slips _ OrderedCollection new. self changedClasses do: [:aClass | (self methodChangesAtClass: aClass name) associationsDo: [:mAssoc | (#(remove addedThenRemoved) includes: mAssoc value) ifFalse: [method _ aClass compiledMethodAt: mAssoc key ifAbsent: [nil]. method ifNotNil: [method hasReportableSlip ifTrue: [slips add: aClass name , ' ' , mAssoc key]]]]]. ^ slips! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 10/30/2000 10:44'! checkForUncommentedMethods | aList | "Check to see if there are any methods in the receiver that have no comments, and open a browser on all found" (aList _ self methodsWithoutComments) size > 0 ifFalse: [^ self inform: 'All methods in "', self name, '" have comments'] ifTrue: [Smalltalk browseMessageList: aList name: 'methods in "', self name, '" that lack comments']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 12/14/2000 10:59'! checkForUnsentMessages "Check the change set for unsent messages, and if any are found, open up a message-list browser on them" | nameLine allChangedSelectors augList unsent messageList | nameLine _ '"', self name, '"'. allChangedSelectors _ Set new. (augList _ self changedMessageListAugmented) do: [:aChange | MessageSet parse: aChange toClassAndSelector: [:cls :sel | (cls notNil and: [cls includesSelector: sel]) ifTrue: [allChangedSelectors add: sel]]]. unsent _ Smalltalk allUnSentMessagesIn: allChangedSelectors. unsent size = 0 ifTrue: [self inform: 'There are no unsent messages in change set ', nameLine] ifFalse: [messageList _ augList select: [:aChange | MessageSet parse: aChange toClassAndSelector: [:cls :sel | unsent includes: sel]]. Smalltalk browseMessageList: messageList name: 'Unsent messages in ', nameLine] ! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'dew 8/26/2000 21:45'! chooseSubjectPrefixForEmail | subjectIndex | subjectIndex _ (PopUpMenu labels: 'Bug fix [FIX]\Enhancement [ENH]\Goodie [GOODIE]\None of the above (will not be archived)' withCRs) startUpWithCaption: 'What type of change set\are you submitting to the list?' withCRs. ^ #('[CS] ' '[FIX] ' '[ENH] ' '[GOODIE] ' '[CS] ') at: subjectIndex + 1! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 10/27/2000 08:22'! fileOut "File out the receiver, to a file whose name is a function of the change-set name and either of the date & time or chosen to have a unique numeric tag, depending on the preference 'sequentialChangeSetRevertableFileNames'" | file slips nameToUse | self checkForConversionMethods. nameToUse _ Preferences changeSetVersionNumbers ifTrue: [FileDirectory default nextNameFor: self name extension: 'cs'] ifFalse: [(self name, FileDirectory dot, Utilities dateTimeSuffix, FileDirectory dot, 'cs') asFileName]. Cursor write showWhile: [[file _ FileStream newFileNamed: nameToUse. file header; timeStamp. self fileOutPreambleOn: file. self fileOutOn: file. self fileOutPostscriptOn: file. file trailer] ensure: [file close]]. Preferences checkForSlips ifFalse: [^ self]. slips _ self checkForSlips. (slips size > 0 and: [(PopUpMenu withCaption: 'Methods in this fileOut have halts or references to the Transcript or other ''slips'' in them. Would you like to browse them?' chooseFrom: 'Ignore\Browse slips') = 2]) ifTrue: [Smalltalk browseMessageList: slips name: 'Possible slips in ', name]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 3/28/2000 09:35'! fileOutChangesFor: class on: stream "Write out all the method changes for this class." | changes | changes _ Set new. (self methodChangesAtClass: class name) associationsDo: [:mAssoc | (mAssoc value = #remove or: [mAssoc value = #addedThenRemoved]) ifFalse: [changes add: mAssoc key]]. changes isEmpty ifFalse: [class fileOutChangedMessages: changes on: stream. stream cr]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 4/3/2000 14:46'! fileOutOn: stream "Write out all the changes the receiver knows about" | classList | (self isEmpty and: [stream isKindOf: FileStream]) ifTrue: [self notify: 'Warning: no changes to file out']. classList _ ChangeSet superclassOrder: self changedClasses asOrderedCollection. "First put out rename, max classDef and comment changes." classList do: [:aClass | self fileOutClassDefinition: aClass on: stream]. "Then put out all the method changes" classList do: [:aClass | self fileOutChangesFor: aClass on: stream]. "Finally put out removals, final class defs and reorganization if any" classList reverseDo: [:aClass | self fileOutPSFor: aClass on: stream]. self classRemoves asSortedCollection do: [:aClassName | stream nextChunkPut: 'Smalltalk removeClassNamed: #', aClassName; cr].! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 5/8/2000 20:47'! fileOutPSFor: class on: stream "Write out removals and initialization for this class." | dict changeType classRecord currentDef | classRecord _ changeRecords at: class name ifAbsent: [^ self]. dict _ classRecord methodChangeTypes. dict keysSortedSafely do: [:key | changeType _ dict at: key. (#(remove addedThenRemoved) includes: changeType) ifTrue: [stream nextChunkPut: class name, ' removeSelector: ', key storeString; cr] ifFalse: [(key = #initialize and: [class isMeta]) ifTrue: [stream nextChunkPut: class soleInstance name, ' initialize'; cr]]]. ((classRecord includesChangeType: #change) and: [(currentDef _ class definition) ~= (self fatDefForClass: class)]) ifTrue: [stream command: 'H3'; nextChunkPut: currentDef; cr; command: '/H3']. (classRecord includesChangeType: #reorganize) ifTrue: [class fileOutOrganizationOn: stream. stream cr]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 3/29/1999 13:35'! fileOutPostscriptOn: stream "If the receiver has a postscript, put it out onto the stream. " | aString | aString _ self postscriptString. (aString ~~ nil and: [aString size > 0]) ifTrue: [stream nextChunkPut: aString "surroundedBySingleQuotes". stream cr; cr]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 3/29/1999 14:58'! fileOutPreambleOn: stream "If the receiver has a preamble, put it out onto the stream. " | aString | aString _ self preambleString. (aString ~~ nil and: [aString size > 0]) ifTrue: [stream nextChunkPut: aString "surroundedBySingleQuotes". stream cr; cr]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 10/27/2000 08:21'! lookForSlips "Scan the receiver for changes that the user may regard as slips to be remedied" | slips nameLine msg | nameLine _ ' "', self name, '" '. (slips _ self checkForSlips) size == 0 ifTrue: [^ self inform: 'No slips detected in change set', nameLine]. msg _ slips size == 1 ifTrue: [ 'One method in change set', nameLine, 'has a halt, reference to the Transcript, and/or some other ''slip'' in it. Would you like to browse it? ?'] ifFalse: [ slips size printString, ' methods in change set', nameLine, 'have halts or references to the Transcript or other ''slips'' in them. Would you like to browse them?']. (PopUpMenu withCaption: msg chooseFrom: 'Ignore\Browse slips') = 2 ifTrue: [Smalltalk browseMessageList: slips name: 'Possible slips in ', name]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'dew 8/27/2000 14:25'! mailOut "File out the receiver, to a file whose name is a function of the change-set name and either of the date & time or chosen to have a unique numeric tag, depending on the preference 'sequentialChangeSetRevertableFileNames'." | subjectPrefix slips messageStrm message compressBuffer compressStream data compressedStream compressTarget | (Smalltalk includesKey: #Celeste) ifFalse: [^ self notify: 'no mail reader present']. subjectPrefix _ self chooseSubjectPrefixForEmail. self checkForConversionMethods. Cursor write showWhile: ["Prepare the message" messageStrm _ WriteStream on: (String new: 30). messageStrm nextPutAll: 'From: '; nextPutAll: Celeste userName; cr; nextPutAll: 'To: squeak@cs.uiuc.edu'; cr; nextPutAll: 'Subject: '; nextPutAll: subjectPrefix; nextPutAll: name; cr; nextPutAll: 'from preamble:'; cr; cr. self fileOutPreambleOn: messageStrm. "Prepare the gzipped data" message _ MailMessage from: messageStrm contents. message _ MailMessage from: message asMultipartText. data _ WriteStream on: String new. data header. self fileOutPreambleOn: data. self fileOutOn: data. self fileOutPostscriptOn: data. data trailer. data _ ReadStream on: data contents. compressBuffer _ ByteArray new: 1000. compressStream _ GZipWriteStream on: (compressTarget _ WriteStream on: (ByteArray new: 1000)). [data atEnd] whileFalse: [compressStream nextPutAll: (data nextInto: compressBuffer)]. compressStream close. compressedStream _ ReadStream on: compressTarget contents asString. CelesteComposition openForCeleste: Celeste current initialText: (message asTextEncodingNewPart: compressedStream named: name , '.cs.gz')]. Preferences suppressCheckForSlips ifTrue: [^ self]. slips _ self checkForSlips. (slips size > 0 and: [self confirm: 'Methods in this fileOut have halts or references to the Transcript or other ''slips'' in them. Would you like to browse them?']) ifTrue: [Smalltalk browseMessageList: slips name: 'Possible slips in ' , name]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'RAA 10/19/2000 13:17'! objectForDataStream: refStrm "I am about to be written on an object file. Write a path to me in the other system instead." refStrm projectChangeSet == self ifTrue: [^ self]. "try to write reference for me". ^ DiskProxy global: #ChangeSorter selector: #existingOrNewChangeSetNamed: args: (Array with: self name) "=== refStrm replace: self with: nil. ^ nil ===" ! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 5/7/1998 12:16'! postscriptString "Answer the string representing the postscript. " ^ postscript == nil ifTrue: [postscript] ifFalse: [postscript contents asString]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 11/27/96'! postscriptString: aString "Establish aString as the new contents of the postscript. " postscript _ StringHolder new contents: aString! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 5/7/1998 12:08'! preambleString "Answer the string representing the preamble" ^ preamble == nil ifTrue: [preamble] ifFalse: [preamble contents asString]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 11/27/96'! preambleString: aString "Establish aString as the new contents of the preamble. " preamble _ StringHolder new contents: aString! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sw 9/10/1998 12:37'! preambleTemplate "Answer a string that will form the default contents for a change set's preamble. Just a first stab at what the content should be." ^ String streamContents: [:strm | strm nextPutAll: '"Change Set:'. "NOTE: fileIn recognizes preambles by this string." strm tab;tab; nextPutAll: self name. strm cr; nextPutAll: 'Date:'; tab; tab; tab; nextPutAll: Date today printString. strm cr; nextPutAll: 'Author:'; tab; tab; tab; nextPutAll: Preferences defaultAuthorName. strm cr; cr; nextPutAll: '"'] "Smalltalk changes preambleTemplate"! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 9/24/1999 12:33'! summaryString "Answer the string summarizing this changeSet" ^ self summaryStringDelta: 0 " To summarize all recent changeSets on a file... (FileStream newFileNamed: 'Summaries.txt') nextPutAll: (String streamContents: [:s | (ChangeSorter changeSetsNamedSuchThat: [:name | name first isDigit and: [name initialIntegerOrNil >= 948]]) do: [:cs | s nextPutAll: cs summaryString; cr]]); close To list all changeSets with a certain string in the preamble... (FileStream newFileNamed: 'MyUpdates.txt') nextPutAll: (String streamContents: [:s | ChangeSorter gatherChangeSetRevertables do: [:cs | (cs preambleString notNil and: [cs preambleString includesSubString: 'Author Name']) ifTrue: [s nextPutAll: cs summaryString; cr]]]); close "! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'di 9/24/1999 12:27'! summaryStringDelta: delta "Answer the string summarizing this changeSet" | ps s2 date author line intName | ^ String streamContents: [:s | intName _ self name splitInteger. intName first isNumber ifTrue: [s nextPutAll: (intName first + delta) printString , intName last] ifFalse: [s nextPutAll: intName first "weird convention of splitInteger"]. (ps _ self preambleString) ifNil: [s cr] ifNotNil: [s2 _ ReadStream on: ps. s2 match: 'Date:'; skipSeparators. date _ s2 upTo: Character cr. s2 match: 'Author:'; skipSeparators. author _ s2 upTo: Character cr. s nextPutAll: ' -- '; nextPutAll: author; nextPutAll: ' -- '; nextPutAll: date; cr. [s2 atEnd] whileFalse: [line _ s2 upTo: Character cr. (line isEmpty or: [line = '"']) ifFalse: [s nextPutAll: line; cr]]]]. ! ! !ChangeSet methodsFor: 'private' stamp: 'di 3/23/2000 08:37'! addCoherency: className "SqR!! 19980923: If I recreate the class then don't remove it" (self changeRecorderFor: className) checkCoherence. " classRemoves remove: className ifAbsent: []. (classChanges includesKey: className) ifTrue: [(classChanges at: className) remove: #remove ifAbsent: []] "! ! !ChangeSet methodsFor: 'private' stamp: 'di 3/28/2000 14:40'! atClass: class add: changeType (self changeRecorderFor: class) noteChangeType: changeType fromClass: class! ! !ChangeSet methodsFor: 'private' stamp: 'di 4/1/2000 12:00'! atClass: class includes: changeType ^(changeRecords at: class name ifAbsent: [^false]) includesChangeType: changeType! ! !ChangeSet methodsFor: 'private' stamp: 'di 4/1/2000 12:00'! atSelector: selector class: class ^ (changeRecords at: class name ifAbsent: [^ #none]) atSelector: selector ifAbsent: [^ #none]! ! !ChangeSet methodsFor: 'private' stamp: 'di 3/29/2000 20:46'! changeRecorderFor: class | cname | (class isKindOf: String) ifTrue: [ cname _ class ] ifFalse: [ cname _ class name ]. "Later this will init the changeRecords so according to whether they should be revertable." ^ changeRecords at: cname ifAbsent: [^ changeRecords at: cname put: (ClassChangeRecord new initFor: cname revertable: revertable)]! ! !ChangeSet methodsFor: 'private' stamp: 'di 9/8/2000 11:28'! fileOutClassDefinition: class on: stream "Write out class definition for the given class on the given stream, if the class definition was added or changed." (self atClass: class includes: #rename) ifTrue: [stream nextChunkPut: 'Smalltalk renameClassNamed: #', (self oldNameFor: class), ' as: #', class name; cr]. (self atClass: class includes: #change) ifTrue: [ "fat definition only needed for changes" stream command: 'H3'; nextChunkPut: (self fatDefForClass: class); cr; command: '/H3' ] ifFalse: [ (self atClass: class includes: #add) ifTrue: [ "use current definition for add" stream command: 'H3'; nextChunkPut: class definition; cr; command: '/H3' ]. ]. (self atClass: class includes: #comment) ifTrue: [class theNonMetaClass organization putCommentOnFile: stream numbered: 0 moveSource: false forClass: class theNonMetaClass. stream cr]. ! ! !ChangeSet methodsFor: 'private' stamp: 'di 4/1/2000 12:00'! oldNameFor: class ^ (changeRecords at: class name) priorName! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangeSet class instanceVariableNames: ''! !ChangeSet class methodsFor: 'fileIn/Out' stamp: 'SqR 11/14/2000 11:36'! doWeFileOut: aClass given: aSet cache: cache | aClassAllSuperclasses aClassSoleInstanceAllSuperclasses | aClassAllSuperclasses _ cache at: aClass ifAbsent: [cache at: aClass put: aClass allSuperclasses asArray]. (aSet includesAnyOf: aClassAllSuperclasses) ifTrue: [^false]. aClass isMeta ifFalse: [^true]. (aSet includes: aClass soleInstance) ifTrue: [^false]. aClassSoleInstanceAllSuperclasses _ cache at: aClass soleInstance ifAbsent: [cache at: aClass soleInstance put: aClass soleInstance allSuperclasses asArray]. (aSet includesAnyOf: aClassSoleInstanceAllSuperclasses) ifTrue: [^false]. ^true! ! !ChangeSet class methodsFor: 'fileIn/Out' stamp: 'SqR 11/14/2000 11:37'! superclassOrder: classes "Arrange the classes in the collection, classes, in superclass order so the classes can be properly filed in. Do it in sets instead of ordered collections. SqR 4/12/2000 22:04" | all list aClass inclusionSet aClassIndex cache | list _ classes copy. "list is indexable" inclusionSet _ list asSet. cache _ Dictionary new. all _ OrderedCollection new: list size. list size timesRepeat: [ aClassIndex _ list findFirst: [:one | one isNil not and: [self doWeFileOut: one given: inclusionSet cache: cache]]. aClass _ list at: aClassIndex. all addLast: aClass. inclusionSet remove: aClass. list at: aClassIndex put: nil ]. ^all! ! !ChangeSet class methodsFor: 'defaults' stamp: 'di 1/10/2001 12:03'! defaultName | namesInUse try | namesInUse _ ChangeSorter gatherChangeSets collect: [:each | each name]. 1 to: 999999 do: [:i | try _ 'Unnamed' , i printString. (namesInUse includes: try) ifFalse: [^ try]] ! ! !ChangeSet class methodsFor: 'defaults' stamp: 'RAA 7/15/2000 18:38'! uniqueNameLike: aString | namesInUse try | namesInUse _ ChangeSorter gatherChangeSets collect: [:each | each name]. 1 to: 999999 do: [:i | try _ aString , i printString. (namesInUse includes: try) ifFalse: [^ try] ]! ! CodeHolder subclass: #ChangeSorter instanceVariableNames: 'parent myChangeSet currentClassName currentSelector priorChangeSetList ' classVariableNames: 'AllChangeSets ' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSorter commentStamp: '' prior: 0! I display a ChangeSet. Two of me are in a DualChangeSorter.! !ChangeSorter methodsFor: 'creation' stamp: 'sw 3/6/1999 09:33'! morphicWindow "ChangeSorter new openAsMorph" | window | myChangeSet ifNil: [self myChangeSet: Smalltalk changes]. window _ (SystemWindow labelled: self labelString) model: self. self openAsMorphIn: window rect: (0@0 extent: 1@1). ^ window ! ! !ChangeSorter methodsFor: 'creation' stamp: 'sma 4/30/2000 09:29'! open "ChangeSorterPluggable new open" | topView | Smalltalk isMorphic | Sensor leftShiftDown ifTrue: [^ self openAsMorph]. topView _ StandardSystemView new. topView model: self. myChangeSet ifNil: [self myChangeSet: Smalltalk changes]. topView label: self labelString. topView borderWidth: 1; minimumSize: 360@360. self openView: topView offsetBy: 0@0. topView controller open. ! ! !ChangeSorter methodsFor: 'creation' stamp: 'sw 3/6/1999 09:34'! openAsMorph "ChangeSorter new openAsMorph" ^ self morphicWindow openInWorld. ! ! !ChangeSorter methodsFor: 'creation' stamp: 'JW 2/2/2001 21:47'! openAsMorphIn: window rect: rect "Add a set of change sorter views to the given top view offset by the given amount. To create a single change sorter, call this once with an offset of 0@0. To create a dual change sorter, call it twice with offsets of 0@0 and 0.5@0." | csListHeight msgListHeight csMsgListHeight | contents _ ''. csListHeight _ 0.25. msgListHeight _ 0.25. csMsgListHeight _ csListHeight + msgListHeight. self addDependent: window. "so it will get changed: #relabel" window addMorph: ((PluggableListMorphByItem on: self list: #changeSetList selected: #currentCngSet changeSelected: #showChangeSetNamed: menu: #changeSetMenu:shifted: keystroke: #changeSetListKey:from:) autoDeselect: false) frame: (((0@0 extent: 0.5@csListHeight) scaleBy: rect extent) translateBy: rect origin). window addMorph: (PluggableListMorphByItem on: self list: #classList selected: #currentClassName changeSelected: #currentClassName: menu: #classMenu:shifted: keystroke: #classListKey:from:) frame: (((0.5@0 extent: 0.5@csListHeight) scaleBy: rect extent) translateBy: rect origin). window addMorph: (PluggableListMorphByItem on: self list: #messageList selected: #currentSelector changeSelected: #currentSelector: menu: #messageMenu:shifted: keystroke: #messageListKey:from:) frame: (((0@csListHeight extent: 1@msgListHeight) scaleBy: rect extent) translateBy: rect origin). self addLowerPanesTo: window at: (((0@csMsgListHeight corner: 1@1) scaleBy: rect extent) translateBy: rect origin) with: nil.! ! !ChangeSorter methodsFor: 'creation' stamp: 'sw 1/24/2001 21:24'! openView: topView offsetBy: offset "Add a set of change sorter views to the given top view offset by the given amount. To create a single change sorter, call this once with an offset of 0@0. To create a dual change sorter, call it twice with offsets of 0@0 and 360@0." | classView messageView codeView cngSetListView basePane annoPane annoHeight | contents _ ''. annoHeight _ 20. self addDependent: topView. "so it will get changed: #relabel" cngSetListView _ PluggableListViewByItem on: self list: #changeSetList selected: #currentCngSet changeSelected: #showChangeSetNamed: menu: #changeSetMenu:shifted: keystroke: #changeSetListKey:from:. cngSetListView window: ((0@0 extent: 180@100) translateBy: offset). topView addSubView: cngSetListView. classView _ PluggableListViewByItem on: self list: #classList selected: #currentClassName changeSelected: #currentClassName: menu: #classMenu:shifted: keystroke: #classListKey:from:. classView window: ((0@0 extent: 180@100) translateBy: offset). topView addSubView: classView toRightOf: cngSetListView. messageView _ PluggableListViewByItem on: self list: #messageList selected: #currentSelector changeSelected: #currentSelector: menu: #messageMenu:shifted: keystroke: #messageListKey:from:. messageView menuTitleSelector: #messageListSelectorTitle. messageView window: ((0@0 extent: 360@100) translateBy: offset). topView addSubView: messageView below: cngSetListView. self wantsAnnotationPane ifFalse: [basePane _ messageView] ifTrue: [annoPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annoPane window: ((0@0 extent: 360@annoHeight) translateBy: offset). topView addSubView: annoPane below: messageView. basePane _ annoPane]. codeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. codeView window: ((0 @ 0 extent: 360 @ 180) translateBy: offset). topView addSubView: codeView below: basePane.! ! !ChangeSorter methodsFor: 'creation' stamp: 'tk 12/7/1999 12:53'! veryDeepFixupWith: deepCopier super veryDeepFixupWith: deepCopier. parent _ deepCopier references at: parent ifAbsent: [parent]. self updateIfNecessary! ! !ChangeSorter methodsFor: 'creation' stamp: 'tk 12/7/1999 12:51'! veryDeepInner: deepCopier "Copy all of my instance variables. Some need to be not copied at all, but shared." super veryDeepInner: deepCopier. "parent _ parent. Weakly copied" "myChangeSet _ myChangeSet. Weakly copied" currentClassName _ currentClassName veryDeepCopyWith: deepCopier. "currentSelector _ currentSelector. Symbol" priorChangeSetList _ priorChangeSetList veryDeepCopyWith: deepCopier. ! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/29/1998 08:22'! changeSet ^ myChangeSet! ! !ChangeSorter methodsFor: 'access' stamp: 'sw 1/27/2000 11:19'! changeSetCurrentlyDisplayed ^ myChangeSet! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/30/1998 13:37'! label ^ self labelString! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/30/1998 14:03'! labelString "The label for my entire window. The large button that displays my name is gotten via mainButtonName" ^ parent ifNil: [Smalltalk changes == myChangeSet ifTrue: ['Changes go to "', myChangeSet name, '"'] ifFalse: ['ChangeSet: ', myChangeSet name]] ifNotNil: ['Changes go to "', (Smalltalk changes name), '"']! ! !ChangeSorter methodsFor: 'access' stamp: 'sma 11/11/2000 23:28'! modelWakeUp "A window with me as model is being entered. Make sure I am up-to-date with the changeSets." self canDiscardEdits ifTrue: [self update]! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/24/1998 08:43'! myChangeSet: anObject myChangeSet _ anObject! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/24/1998 08:42'! parent ^ parent! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/24/1998 08:42'! parent: anObject parent _ anObject! ! !ChangeSorter methodsFor: 'access' stamp: 'sw 1/28/1999 12:31'! showChangeSet: chgSet myChangeSet == chgSet ifFalse: [ myChangeSet _ chgSet. currentClassName _ nil. currentSelector _ nil]. self changed: #relabel. self changed: #currentCngSet. "new -- list of sets" self changed: #mainButtonName. "old, button" self changed: #classList. self changed: #messageList. self setContents. self contentsChanged.! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 6/10/1998 07:38'! showChangeSetNamed: aName aName ifNil: [^ self showChangeSet: nil]. self showChangeSet: (AllChangeSets detect: [:each | each name = aName]) ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/6/1999 23:22'! addPreamble myChangeSet assurePreambleExists. self okToChange ifTrue: [currentClassName _ nil. currentSelector _ nil. self showChangeSet: myChangeSet]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 4/24/1998 13:27'! browseChangeSet "Open a message list browser on the new and changed methods in the current change set" ChangedMessageSet openFor: myChangeSet ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/1/2000 15:48'! browseMethodConflicts "Check to see if any other change set also holds changes to any methods in the selected change set; if so, open a browser on all such." | aList aClass aSelector | aList _ myChangeSet changedMessageListAugmented select: [:aChange | MessageSet parse: aChange toClassAndSelector: [:cls :sel | aClass _ cls. aSelector _ sel]. (ChangeSorter allChangeSetsWithClass: aClass selector: aSelector) size > 1]. aList size == 0 ifTrue: [^ self inform: 'No other change set has changes for any method in this change set.']. MessageSet openMessageList: aList name: 'Methods in "', myChangeSet name, '" that are also in other change sets (', aList size printString, ')' ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sma 11/11/2000 23:32'! changeSetList "Answer a list of ChangeSet names. If there're new ChangeSet instances, create a new list of change sets." ChangeSet instanceCount > AllChangeSets size ifTrue: [self class gatherChangeSets]. ^ AllChangeSets reversed collect: [:each | each name]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/25/2001 07:22'! changeSetListKey: aChar from: view "Respond to a Command key. I am a model with a listView that has a list of changeSets." aChar == $b ifTrue: [^ self browseChangeSet]. aChar == $f ifTrue: [^ self findCngSet]. aChar == $m ifTrue: [^ self newCurrent]. aChar == $n ifTrue: [^ self newSet]. aChar == $o ifTrue: [^ self fileOut]. aChar == $r ifTrue: [^ self rename]. aChar == $x ifTrue: [^ self remove]. aChar == $p ifTrue: [^ self addPreamble]. aChar == $c ifTrue: [^ self copyAllToOther]. aChar == $D ifTrue: [^ self toggleDiffing]. ^ self messageListKey: aChar from: view! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/28/2001 19:18'! changeSetMenu: aMenu shifted: isShifted "Set up aMenu to hold commands for the change-set-list pane. This could be for a single or double changeSorter" isShifted ifTrue: [^ self shiftedChangeSetMenu: aMenu]. Smalltalk isMorphic ifTrue: [aMenu title: 'Change Set'. aMenu addStayUpItemSpecial] ifFalse: [aMenu title: 'Change Set: ' , myChangeSet name]. aMenu add: 'make changes go to me (m)' action: #newCurrent. aMenu addLine. aMenu add: 'new change set... (n)' action: #newSet. aMenu add: 'find...(f)' action: #findCngSet. aMenu add: 'show...' action: #chooseCngSet. aMenu add: 'rename change set (r)' action: #rename. aMenu addLine. aMenu add: 'file out (o)' action: #fileOut. aMenu add: 'mail to list' action: #mailOut. aMenu add: 'browse methods (b)' action: #browseChangeSet. aMenu addLine. parent ifNotNil: [aMenu add: 'copy all to other side (c)' action: #copyAllToOther. aMenu add: 'submerge into other side' action: #submergeIntoOtherSide. aMenu add: 'subtract other side' action: #subtractOtherSide. aMenu addLine]. myChangeSet hasPreamble ifTrue: [aMenu add: 'edit preamble (p)' action: #addPreamble. aMenu add: 'remove preamble' action: #removePreamble] ifFalse: [aMenu add: 'add preamble (p)' action: #addPreamble]. myChangeSet hasPostscript ifTrue: [aMenu add: 'edit postscript...' action: #editPostscript. aMenu add: 'remove postscript' action: #removePostscript] ifFalse: [aMenu add: 'add postscript...' action: #editPostscript]. aMenu addLine. aMenu add: 'destroy change set (x)' action: #remove. aMenu addLine. aMenu add: 'more...' action: #shiftedYellowButtonActivity. ^ aMenu! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 10/30/2000 10:48'! checkForAlienAuthorship "Open a message list browser on all uncommented methods in the current change set that have alien authorship" myChangeSet checkForAlienAuthorship ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 10/30/2000 10:39'! checkForUncommentedMethods "Open a message list browser on all uncommented methods in the current change set" myChangeSet checkForUncommentedMethods ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 10/27/1999 14:20'! checkForUnsentMessages "Open a message list browser on all unsent messages in the current change set" myChangeSet checkForUnsentMessages ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 7/8/1999 13:36'! checkThatSidesDiffer: escapeBlock "If the change sets on both sides of the dual sorter are the same, put up an error message and escape via escapeBlock, else proceed happily" (myChangeSet == (parent other: self) changeSet) ifTrue: [self inform: 'This command requires that the change sets selected on the two sides of the change sorter *not* be the same.'. ^ escapeBlock value] ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 11/3/2000 17:07'! chooseCngSet "Present the user with an alphabetical list of change set names, and let her choose one" | index changeSetsSortedAlphabetically | self okToChange ifFalse: [^ self]. ChangeSet instanceCount > AllChangeSets size ifTrue: [self class gatherChangeSets]. changeSetsSortedAlphabetically _ AllChangeSets asSortedCollection: [:a :b | a name asLowercase withoutLeadingDigits < b name asLowercase withoutLeadingDigits]. index _ (PopUpMenu labels: Smalltalk changes name , ' (active)' , Character cr asString , (changeSetsSortedAlphabetically collect: [:each | each name]) asStringWithCr) startUp. index = 0 ifTrue: [^ self]. index = 1 ifTrue: [^ self showChangeSet: Smalltalk changes]. self showChangeSet: (changeSetsSortedAlphabetically at: index-1). ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/28/1999 12:30'! clearChangeSet "Clear out the current change set, after getting a confirmation." | message | self okToChange ifFalse: [^ self]. myChangeSet isEmpty ifFalse: [message _ 'Are you certain that you want to\forget all the changes in this set?' withCRs. (self confirm: message) ifFalse: [^ self]]. myChangeSet clear. self changed: #classList. self changed: #messageList. self setContents. self contentsChanged. ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/27/2000 11:21'! copyAllToOther "Copy this entire change set into the one on the other side" | companionSorter | self checkThatSidesDiffer: [^ self]. (companionSorter _ parent other: self) changeSetCurrentlyDisplayed assimilateAllChangesFoundIn: myChangeSet. companionSorter changed: #classList. "Later the changeSet itself will notice..." companionSorter changed: #messageList! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 6/5/1998 06:47'! currentCngSet ^ myChangeSet name! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 4/28/1998 08:06'! editPostscript "Allow the user to edit the receiver's change-set's postscript -- in a separate window" myChangeSet editPostscript! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 4/28/1998 08:06'! editPreamble "Allow the user to edit the receiver's change-set's preamble -- in a separate window." myChangeSet editPreamble! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 4/19/2000 16:18'! expungeUniclasses "remove all memory of uniclasses in the receiver" self okToChange ifFalse: [^ self]. myChangeSet expungeUniclasses. self changed: #classList. self changed: #messageList. ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 11/22/1998 23:57'! fileIntoNewChangeSet "Obtain a file designation from the user, and file its contents into a new change set whose name is a function of the filename. Show the new set and leave the current changeSet unaltered." | aFileName aNewChangeSet | self okToChange ifFalse: [^ self]. aFileName _ FillInTheBlank request: 'Name of file to be imported: '. aFileName size == 0 ifTrue: [^ self]. (FileDirectory default fileExists: aFileName) ifFalse: [^ self inform: 'Sorry -- cannot find that file']. aNewChangeSet _ self class newChangesFromStream: (FileStream readOnlyFileNamed: aFileName) named: aFileName. aNewChangeSet ifNotNil: [self showChangeSet: aNewChangeSet]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 6/10/1999 12:44'! fileOut "File out the current change set." myChangeSet fileOut. parent modelWakeUp. "notice object conversion methods created" ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/4/2001 10:04'! fileOutUnnumberedChangeSets "File out all nonempty change sets whose names do not start with numbers, without checking for slips" Utilities fileOutUnnumberedChangeSets! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 2/16/1999 13:30'! findCngSet "Search for a changeSet by name. Pop up a menu of all changeSets whose name contains the string entered by the user. If only one matches, then the pop-up menu is bypassed" | index pattern candidates | self okToChange ifFalse: [^ self]. ChangeSet instanceCount > AllChangeSets size ifTrue: [self class gatherChangeSets]. pattern _ FillInTheBlank request: 'ChangeSet name or fragment?'. pattern isEmpty ifTrue: [^ self]. candidates _ AllChangeSets select: [:c | c name includesSubstring: pattern caseSensitive: false]. candidates size = 0 ifTrue: [^ self beep]. candidates size = 1 ifTrue: [^ self showChangeSet: candidates first]. index _ (PopUpMenu labels: (candidates collect: [:each | each name]) asStringWithCr) startUp. index = 0 ifFalse: [self showChangeSet: (candidates at: index)]. ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 10/26/1999 14:24'! goToChangeSetsProject "Transport the user to a project which bears the selected changeSet as its current changeSet" | aProject | (aProject _ myChangeSet correspondingProject) ifNotNil: [aProject enter: false revert: false saveForRevert: false] ifNil: [self inform: 'Has no project']! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/10/1999 01:01'! inspectChangeSet "Open a message list browser on the new and changed methods in the current change set" myChangeSet inspectWithLabel: 'Change set: ', myChangeSet name ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 2/17/1999 11:05'! lookForSlips "Open a message list browser on the new and changed methods in the current change set" myChangeSet lookForSlips ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'dvf 5/13/2000 05:08'! mailOut "Create a mail with a gzipped attachment holding out the current change set. " myChangeSet mailOut. parent modelWakeUp! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 4/24/1998 13:10'! mainButtonName ^ myChangeSet name! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/1/2000 15:48'! methodConflictsWithOtherSide "Check to see if the change set on the other side shares any methods with the selected change set; if so, open a browser on all such." | aList aClass aSelector other | self checkThatSidesDiffer: [^ self]. other _ (parent other: self) changeSet. aList _ myChangeSet changedMessageListAugmented select: [:aChange | MessageSet parse: aChange toClassAndSelector: [:cls :sel | aClass _ cls. aSelector _ sel]. aClass notNil and: [(other methodChangesAtClass: aClass name) includesKey: aSelector]]. aList size == 0 ifTrue: [^ self inform: 'There are no methods that appear both in this change set and in the one on the other side.']. MessageSet openMessageList: aList name: 'Methods in "', myChangeSet name, '" that are also in ', other name,' (', aList size printString, ')' ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'tk 4/30/1998 13:47'! newCurrent "make my change set be the current one that changes go into" Smalltalk newChanges: myChangeSet. self changed: #relabel.! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 9/4/1998 09:00'! newSet "Create a new changeSet and show it., making it the current one. Reject name if already in use." | aSet | self okToChange ifFalse: [^ self]. aSet _ self class newChangeSet. aSet ifNotNil: [self update. self showChangeSet: aSet. self changed: #relabel]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/27/2000 15:03'! promoteToTopChangeSet self class promoteToTop: myChangeSet! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 6/14/1998 12:00'! remove "Completely destroy my change set. Check if it's OK first" self okToChange ifFalse: [^ self]. self removePrompting: true. self update! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 6/29/1999 20:53'! removePostscript (myChangeSet hasPostscript and: [myChangeSet postscriptHasDependents]) ifTrue: [^ self inform: 'Cannot remove the postscript right now because there is at least one window open on that postscript. Close that window and try again.']. myChangeSet removePostscript. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/5/1999 19:32'! removePreamble myChangeSet removePreamble. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 5/26/2000 22:34'! removePrompting: doPrompt "Completely destroy my change set. Check if it's OK first, and if doPrompt is true, get the user to confirm his intentions first" | message aName | aName _ myChangeSet name. myChangeSet okayToRemove ifFalse: [^ self]. "forms current changes for some project" (myChangeSet isEmpty or: [doPrompt not]) ifFalse: [message _ 'Are you certain that you want to remove (destroy) the change set named "', aName, '" ?'. (self confirm: message) ifFalse: [^ self]]. (doPrompt and: [myChangeSet hasPreamble or: [myChangeSet hasPostscript]]) ifTrue: [(self confirm: 'Caution!! This change set has a preamble and/or a postscript, which will be lost if you destroy the change set. Do you really want to go ahead with this?') ifFalse: [^ self]]. "Go ahead and remove the change set" AllChangeSets remove: myChangeSet. myChangeSet wither. "clear out its contents" self showChangeSet: Smalltalk changes.! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 6/14/1998 11:58'! rename "Store a new name string into the selected ChangeSet. reject duplicate name; allow user to back out" | newName | newName _ FillInTheBlank request: 'New name for this change set' initialAnswer: myChangeSet name. (newName = myChangeSet name or: [newName size == 0]) ifTrue: [^ self inform: 'No change made']. (self class changeSetNamed: newName) ifNotNil: [^ Utilities inform: 'Sorry that name is already used']. myChangeSet name: newName. self update. self changed: #mainButtonName. self changed: #relabel.! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 1/29/2001 02:46'! shiftedChangeSetMenu: aMenu "Set up aMenu to hold items relating to the change-set-list pane when the shift key is down" Smalltalk isMorphic ifTrue: [aMenu title: 'Change set (shifted)'. aMenu addStayUpItemSpecial]. aMenu add: 'conflicts with other change sets' action: #browseMethodConflicts. aMenu balloonTextForLastItem: 'Browse all methods that occur both in this change set and in at least one other change set.'. parent ifNotNil: [aMenu add: 'conflicts with opposite side' action: #methodConflictsWithOtherSide. aMenu balloonTextForLastItem: 'Browse all methods that occur both in this change set and in the one on the opposite side of the change sorter.'. ]. aMenu addLine. aMenu add: 'check for slips' action: #lookForSlips. aMenu balloonTextForLastItem: 'Check this change set for halts and references to Transcript.'. aMenu add: 'check for unsent messages' action: #checkForUnsentMessages. aMenu balloonTextForLastItem: 'Check this change set for messages that are not sent anywhere in the system'. aMenu add: 'check for uncommented methods' action: #checkForUncommentedMethods. aMenu balloonTextForLastItem: 'Check this change set for methods that do not have comments'. Utilities authorInitialsPerSe isEmptyOrNil ifFalse: [aMenu add: 'check for other authors' action: #checkForAlienAuthorship. aMenu balloonTextForLastItem: 'Check this change set for methods whose current authoring stamp does not start with "', Utilities authorInitials, '"']. aMenu addLine. aMenu add: 'inspect change set' action: #inspectChangeSet. aMenu balloonTextForLastItem: 'Open an inspector on this change set. (There are some details in a change set which you don''t see in a change sorter.)'. aMenu add: 'update' action: #update. aMenu balloonTextForLastItem: 'Update the display for this change set. (This is done automatically when you activate this window, so is seldom needed.)'. aMenu add: 'go to change set''s project' action: #goToChangeSetsProject. aMenu balloonTextForLastItem: 'If this change set is currently associated with a Project, go to that project right now.'. aMenu add: 'promote to top of list' action: #promoteToTopChangeSet. aMenu balloonTextForLastItem: 'Make this change set appear first in change-set lists in all change sorters.'. aMenu add: 'trim history' action: #trimHistory. aMenu balloonTextForLastItem: ' Drops any methods added and then removed, as well as renaming and reorganization of newly-added classes. NOTE: can cause confusion if later filed in over an earlier version of these changes'. aMenu add: 'clear this change set' action: #clearChangeSet. aMenu balloonTextForLastItem: 'Reset this change set to a pristine state where it holds no information. CAUTION: this is destructive and irreversible!!'. aMenu add: 'expunge uniclasses' action: #expungeUniclasses. aMenu balloonTextForLastItem: 'Remove from the change set all memory of uniclasses, e.g. classes added on behalf of etoys, fabrik, etc., whose classnames end with a digit.'. aMenu add: 'uninstall this change set' action: #uninstallChangeSet. aMenu balloonTextForLastItem: 'Attempt to uninstall this change set. CAUTION: this may not work completely and is irreversible!!'. aMenu addLine. aMenu add: 'file into new...' action: #fileIntoNewChangeSet. aMenu balloonTextForLastItem: 'Load a fileout from disk and place its changes into a new change set (seldom needed -- much better to do this from a file-list browser these days.)'. aMenu add: 'file out all change sets' action: #fileOutUnnumberedChangeSets. aMenu balloonTextForLastItem: 'File out every change set in the system whose name does not begin with a digit, except those that are empty or whose names start with "Play with me". The usual checks for slips are suppressed when this command is done.'. aMenu addLine. aMenu add: 'more...' action: #unshiftedYellowButtonActivity. aMenu balloonTextForLastItem: 'Takes you back to the primary change-set menu.'. ^ aMenu! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 5/26/2000 18:23'! submergeIntoOtherSide "Copy the contents of the receiver to the other side, then remove the receiver -- all after checking that all is well." | other message nextToView i | self checkThatSidesDiffer: [^ self]. self okToChange ifFalse: [^ self]. other _ (parent other: self) changeSet. other == myChangeSet ifTrue: [^ self inform: 'Both sides are the same!!']. myChangeSet isEmpty ifTrue: [^ self inform: 'Nothing to copy. To remove, simply choose "remove".']. myChangeSet okayToRemove ifFalse: [^ self]. message _ 'Please confirm: copy all changes in "', myChangeSet name, '" into "', other name, '" and then destroy the change set named "', myChangeSet name, '"?'. (self confirm: message) ifFalse: [^ self]. (myChangeSet hasPreamble or: [myChangeSet hasPostscript]) ifTrue: [(self confirm: 'Caution!! This change set has a preamble or a postscript or both. If you submerge it into the other side, these will be lost. Do you really want to go ahead with this?') ifFalse: [^ self]]. other assimilateAllChangesFoundIn: myChangeSet. nextToView _ ((AllChangeSets includes: myChangeSet) and: [(i _ AllChangeSets indexOf: myChangeSet) < AllChangeSets size]) ifTrue: [AllChangeSets at: i+1] ifFalse: [other]. self removePrompting: false. self showChangeSet: nextToView. self class gatherChangeSets. parent modelWakeUp. "(parent other: self) changed: #classList. (parent other: self) changed: #messageList."! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 7/8/1999 12:32'! subtractOtherSide "Subtract the changes found on the other side from the requesting side." self checkThatSidesDiffer: [^ self]. myChangeSet forgetAllChangesFoundIn: ((parent other: self) changeSet). self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 5/12/2000 15:03'! trimHistory "Drop non-essential history (rename, reorg, method removals) from newly-added classes." myChangeSet trimHistory ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 3/8/2000 14:18'! uninstallChangeSet "Attempt to uninstall the current change set, after confirmation." self okToChange ifFalse: [^ self]. (self confirm: 'Uninstalling a changeSet is unreliable at best. It will only work if the changeSet consists only of single changes, additions and removals of methods, and if no subsequent changes have been to any of them. No changes to classes will be undone. The changeSet will be cleared after uninstallation. Do you still wish to attempt to uninstall this changeSet?') ifFalse: [^ self]. myChangeSet uninstall. self changed: #relabel. self changed: #classList. self changed: #messageList. self setContents. self contentsChanged. ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'di 6/21/1998 13:02'! update "recompute all of my panes" self updateIfNecessary. parent ifNotNil: [(parent other: self) updateIfNecessary]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sma 11/11/2000 23:37'! updateIfNecessary "Recompute all of my panes." | newList | self okToChange ifFalse: [^ self]. myChangeSet isMoribund ifTrue: [^ self showChangeSet: Smalltalk changes]. newList _ self changeSetList. (priorChangeSetList == nil or: [priorChangeSetList ~= newList]) ifTrue: [priorChangeSetList _ newList. self changed: #changeSetList]. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'class list' stamp: 'tk 5/7/1998 13:01'! classList "Computed. View should try to preserve selections, even though index changes" ^ myChangeSet changedClassNames ! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 1/19/2001 01:33'! classListKey: aChar from: view "Overridden to obviate spurious StringHolder processing of $f for findClass" ^ self messageListKey: aChar from: view! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 12/6/2000 16:34'! classMenu: aMenu "Set up aMenu for the class-list. Retained for backward compatibility with old change sorters in image segments" ^ self classMenu: aMenu shifted: false! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 2/1/2001 08:42'! classMenu: aMenu shifted: shifted "Fill aMenu with items appropriate for the class list" aMenu title: 'class list'. Smalltalk isMorphic ifTrue: [aMenu addStayUpItemSpecial]. (parent notNil and: [shifted not]) ifTrue: [aMenu addList: #( "These two only apply to dual change sorters" ('copy class chgs to other side' copyClassToOther) ('move class chgs to other side' moveClassToOther))]. ^ aMenu addList: (shifted ifFalse: [#( - ('delete class chgs from this change set' forgetClass) - ('browse full (b)' browseMethodFull) ('browse hierarchy (h)' spawnHierarchy) ('browse protocol (p)' browseFullProtocol) - ('printOut' printOutClass) ('fileOut' fileOutClass) - ('inst var refs...' browseInstVarRefs) ('inst var defs...' browseInstVarDefs) ('class var refs...' browseClassVarRefs) ('class vars' browseClassVariables) ('class refs (N)' browseClassRefs) - ('more...' shiftedYellowButtonActivity))] ifTrue: [#( - ('unsent methods' browseUnusedMethods) ('unreferenced inst vars' showUnreferencedInstVars) - ('sample instance' makeSampleInstance) ('inspect instances' inspectInstances) ('inspect subinstances' inspectSubInstances) - ('more...' unshiftedYellowButtonActivity))])! ! !ChangeSorter methodsFor: 'class list' stamp: 'di 4/4/2000 09:37'! copyClassToOther "Place these changes in the other changeSet also" | otherSorter otherChangeSet | self checkThatSidesDiffer: [^ self]. self okToChange ifFalse: [^ self beep]. currentClassName ifNil: [^ self beep]. otherSorter _ parent other: self. otherChangeSet _ otherSorter changeSet. otherChangeSet absorbClass: self selectedClassOrMetaClass name from: myChangeSet. otherSorter showChangeSet: otherChangeSet.! ! !ChangeSorter methodsFor: 'class list' stamp: 'tk 4/24/1998 09:14'! currentClassName ^ currentClassName! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 1/28/1999 12:30'! currentClassName: aString currentClassName _ aString. currentSelector _ nil. "fix by wod" self changed: #currentClassName. self changed: #messageList. self setContents. self contentsChanged.! ! !ChangeSorter methodsFor: 'class list' stamp: 'kfr 6/16/2000 16:27'! fileOutClass "this is a hack!!!! makes a new change set, called the class name, adds author initials to try to make a unique change set name, files it out and removes it. kfr 16 june 2000" | aSet | "File out the selected class set." aSet _ self class newChangeSet: currentClassName. aSet absorbClass: self selectedClassOrMetaClass name from: myChangeSet. aSet fileOut. self class removeChangeSet: aSet. parent modelWakeUp. "notice object conversion methods created" ! ! !ChangeSorter methodsFor: 'class list' stamp: 'ls 8/12/1998 23:47'! forgetClass "Remove all mention of this class from the changeSet" self okToChange ifFalse: [^ self]. currentClassName ifNotNil: [ myChangeSet removeClassChanges: currentClassName. currentClassName _ nil. currentSelector _ nil. self showChangeSet: myChangeSet]. ! ! !ChangeSorter methodsFor: 'class list' stamp: 'di 4/3/2000 20:31'! moveClassToOther "Place class changes in the other changeSet and remove them from this one" self copyClassToOther. self forgetClass! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 12/7/1998 09:43'! selectedClass "Answer the currently-selected class. If there is no selection, or if the selection refers to a class no longer extant, return nil" | c | ^ currentClassName ifNotNil: [(c _ self selectedClassOrMetaClass) ifNotNil: [c theNonMetaClass]]! ! !ChangeSorter methodsFor: 'class list' stamp: 'tk 5/7/1998 13:48'! selectedClassOrMetaClass "Careful, the class may have been removed!!" | cName | currentClassName ifNil: [^ nil]. (currentClassName endsWith: ' class') ifTrue: [cName _ (currentClassName copyFrom: 1 to: currentClassName size-6) asSymbol. ^ (Smalltalk at: cName ifAbsent: [^nil]) class] ifFalse: [cName _ currentClassName asSymbol. ^ Smalltalk at: cName ifAbsent: [nil]]! ! !ChangeSorter methodsFor: 'message list' stamp: 'di 3/23/2000 13:34'! browseVersions "Create and schedule a changelist browser on the versions of the selected message." | class selector method category pair sourcePointer | (selector _ self selectedMessageName) ifNil: [^ self]. class _ self selectedClassOrMetaClass. (class includesSelector: selector) ifTrue: [method _ class compiledMethodAt: selector. category _ class whichCategoryIncludesSelector: selector. sourcePointer _ nil] ifFalse: [pair _ myChangeSet methodInfoFromRemoval: {class name. selector}. pair ifNil: [^ nil]. sourcePointer _ pair first. method _ CompiledMethod toReturnSelf setSourcePointer: sourcePointer. category _ pair last]. VersionsBrowser browseVersionsOf: method class: self selectedClass meta: class isMeta category: category selector: selector lostMethodPointer: sourcePointer. ! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 7/8/1999 12:31'! copyMethodToOther "Place this change in the other changeSet also" | other cls sel | self checkThatSidesDiffer: [^ self]. currentSelector ifNotNil: [other _ (parent other: self) changeSet. cls _ self selectedClassOrMetaClass. sel _ currentSelector asSymbol. other absorbMethod: sel class: cls from: myChangeSet. (parent other: self) showChangeSet: other] ! ! !ChangeSorter methodsFor: 'message list' stamp: 'tk 4/24/1998 09:15'! currentSelector ^ currentSelector! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 1/28/1999 12:31'! currentSelector: messageName currentSelector _ messageName. self changed: #currentSelector. self setContents. self contentsChanged.! ! !ChangeSorter methodsFor: 'message list' stamp: 'di 6/22/1998 02:08'! forget "Drop this method from the changeSet" self okToChange ifFalse: [^ self]. currentSelector ifNotNil: [ myChangeSet removeSelectorChanges: self selectedMessageName class: self selectedClassOrMetaClass. currentSelector _ nil. self showChangeSet: myChangeSet]! ! !ChangeSorter methodsFor: 'message list' stamp: 'di 4/25/2000 10:33'! messageList | probe newSelectors | currentClassName ifNil: [^ #()]. probe _ (currentClassName endsWith: ' class') ifTrue: [currentClassName] ifFalse: [currentClassName asSymbol]. newSelectors _ myChangeSet selectorsInClass: probe. (newSelectors includes: currentSelector) ifFalse: [currentSelector _ nil]. ^ newSelectors asSortedCollection ! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 2/1/2001 08:40'! messageMenu: aMenu shifted: shifted "Fill aMenu with items appropriate for the message list; could be for a single or double changeSorter" shifted ifTrue: [^ self shiftedMessageMenu: aMenu]. aMenu title: 'message list'. Smalltalk isMorphic ifTrue: [aMenu addStayUpItemSpecial]. parent ifNotNil: [aMenu addList: #( ('copy method to other side' copyMethodToOther) ('move method to other side' moveMethodToOther))]. aMenu addList: #( ('delete method from change set' forget) - ('remove method from system (x)' removeMessage) - ('browse full (b)' browseMethodFull) ('browse hierarchy (h)' spawnHierarchy) ('browse method (O)' openSingleMessageBrowser) ('browse protocol (p)' browseFullProtocol) - ('fileOut' fileOutMessage) ('printOut' printOutMessage) - ('senders of... (n)' browseSendersOfMessages) ('implementors of... (m)' browseMessages) ('inheritance (i)' methodHierarchy) ('versions (v)' browseVersions) - ('more...' shiftedYellowButtonActivity)). ^ aMenu ! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 7/8/1999 12:31'! moveMethodToOther "Place this change in the other changeSet and remove it from this side" | other cls sel | self checkThatSidesDiffer: [^ self]. self okToChange ifFalse: [^ self beep]. currentSelector ifNotNil: [other _ (parent other: self) changeSet. other == myChangeSet ifTrue: [^ self beep]. cls _ self selectedClassOrMetaClass. sel _ currentSelector asSymbol. other absorbMethod: sel class: cls from: myChangeSet. (parent other: self) showChangeSet: other. self forget "removes the method from this side"] ! ! !ChangeSorter methodsFor: 'message list' stamp: 'di 6/21/1998 23:13'! removeFromCurrentChanges "Redisplay after removal in case we are viewing the current changeSet" super removeFromCurrentChanges. currentSelector _ nil. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'message list' stamp: 'di 4/4/2000 10:54'! removeMessage "Remove the selected msg from the system. Real work done by the parent, a ChangeSorter" | confirmation sel | self okToChange ifFalse: [^ self]. currentSelector ifNotNil: [ confirmation _ self selectedClassOrMetaClass confirmRemovalOf: (sel _ self selectedMessageName). confirmation == 3 ifTrue: [^ self]. self selectedClassOrMetaClass removeSelector: sel. self update. confirmation == 2 ifTrue: [Smalltalk browseAllCallsOn: sel]]! ! !ChangeSorter methodsFor: 'message list' stamp: 'tk 2/7/2000 11:01'! selectedMessage "Answer a copy of the source code for the selected message selector." ^ contents ifNil: [''] ifNotNil: [contents copy]! ! !ChangeSorter methodsFor: 'message list' stamp: 'jm 5/4/1998 07:32'! selectedMessageName currentSelector ifNil: [^ nil]. ^ currentSelector asSymbol! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 1/25/2001 07:25'! shiftedMessageMenu: aMenu "Arm the menu so that it holds items appropriate to the message-list while the shift key is down. Answer the menu." ^ aMenu addList: #( - ('method pane' makeIsolatedCodePane) ('toggle diffing (D)' toggleDiffing) ('implementors of sent messages' browseAllMessages) ('change category...' changeCategory) - ('sample instance' makeSampleInstance) ('inspect instances' inspectInstances) ('inspect subinstances' inspectSubInstances) - ('change sets with this method' findMethodInChangeSets) ('revert to previous version' revertToPreviousVersion) ('revert & remove from changes' revertAndForget) - ('more...' unshiftedYellowButtonActivity))! ! !ChangeSorter methodsFor: 'code pane' stamp: 'tk 5/10/1999 17:24'! contents: aString notifying: aController "Compile the code in aString. Notify aController of any syntax errors. Create an error if the category of the selected message is unknown. Answer false if the compilation fails. Otherwise, if the compilation created a new method, deselect the current selection. Then answer true." | category selector class oldSelector | (class _ self selectedClassOrMetaClass) ifNil: [(myChangeSet preambleString == nil or: [aString size == 0]) ifTrue: [ ^ false]. (aString count: [:char | char == $"]) odd ifTrue: [self inform: 'unmatched double quotes in preamble'] ifFalse: [(Scanner new scanTokens: aString) size > 0 ifTrue: [ self inform: 'Part of the preamble is not within double-quotes. To put a double-quote inside a comment, type two double-quotes in a row. (Ignore this warning if you are including a doIt in the preamble.)']]. myChangeSet preambleString: aString. self currentSelector: nil. "forces update with no 'unsubmitted chgs' feedback" ^ true]. oldSelector _ self selectedMessageName. category _ class organization categoryOfElement: oldSelector. selector _ class compile: aString classified: category notifying: aController. selector ifNil: [^ false]. (self messageList includes: selector) ifTrue: [self currentSelector: selector] ifFalse: [self currentSelector: oldSelector]. self update. ^ true! ! !ChangeSorter methodsFor: 'code pane' stamp: 'mas 5/20/2000 22:03'! setContents "return the source code that shows in the bottom pane" | sel class strm changeType | self clearUserEditFlag. currentClassName ifNil: [^ contents _ myChangeSet preambleString ifNil: ['']]. class _ self selectedClassOrMetaClass. (sel _ currentSelector) == nil ifFalse: [changeType _ (myChangeSet atSelector: (sel _ sel asSymbol) class: class). changeType == #remove ifTrue: [^ contents _ 'Method has been removed (see versions)']. changeType == #addedThenRemoved ifTrue: [^ contents _ 'Added then removed (see versions)']. class ifNil: [^ contents _ 'Method was added, but cannot be found!!']. (class includesSelector: sel) ifFalse: [^ contents _ 'Method was added, but cannot be found!!']. contents _ class sourceCodeAt: sel. Preferences browseWithPrettyPrint ifTrue: [contents _ class compilerClass new format: contents in: class notifying: nil decorated: Preferences colorWhenPrettyPrinting]. self showDiffs ifTrue: [contents _ self diffFromPriorSourceFor: contents]. ^ contents _ contents asText makeSelectorBoldIn: class] ifTrue: [strm _ WriteStream on: (String new: 100). (myChangeSet classChangeAt: currentClassName) do: [:each | each = #remove ifTrue: [strm nextPutAll: 'Entire class was removed.'; cr]. each = #addedThenRemoved ifTrue: [strm nextPutAll: 'Class was added then removed.']. each = #rename ifTrue: [strm nextPutAll: 'Class name was changed.'; cr]. each = #add ifTrue: [strm nextPutAll: 'Class definition was added.'; cr]. each = #change ifTrue: [strm nextPutAll: 'Class definition was changed.'; cr]. each = #reorganize ifTrue: [strm nextPutAll: 'Class organization was changed.'; cr]. each = #comment ifTrue: [strm nextPutAll: 'New class comment.'; cr. ]]. ^ contents _ strm contents].! ! !ChangeSorter methodsFor: 'code pane' stamp: 'sw 1/19/2001 15:43'! toggleDiffing "Toggle whether diffs should be shown in the code pane" self okToChange ifTrue: [self showDiffs: self showDiffs not. self changed: #contents. self update] ! ! !ChangeSorter methodsFor: 'code pane' stamp: 'JW 2/2/2001 21:41'! wantsOptionalButtons "No optional buttons for ChangeSorter" ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangeSorter class instanceVariableNames: ''! !ChangeSorter class methodsFor: 'enumerating' stamp: 'tk 5/1/1998 15:26'! allChangeSetNames ^ self gatherChangeSets collect: [:c | c name]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sw 2/21/2000 14:36'! allChangeSetsWithClass: class selector: selector class ifNil: [^ #()]. ^ self gatherChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'tk 4/24/1998 16:42'! changeSetNamed: aName "Return the change set of the given name, or nil if none found. 1/22/96 sw" self gatherChangeSets. AllChangeSets do: [:aChangeSet | aChangeSet name = aName ifTrue: [^ aChangeSet]]. ^ nil! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'di 4/1/2000 17:25'! changeSetsNamedSuchThat: nameBlock "(ChangeSorter changeSetsNamedSuchThat: [:name | name first isDigit and: [name initialInteger >= 373]]) do: [:cs | AllChangeSets remove: cs wither]" self gatherChangeSets. ^ AllChangeSets select: [:aChangeSet | nameBlock value: aChangeSet name]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'RAA 10/19/2000 13:17'! existingOrNewChangeSetNamed: aName | newSet | ^(self changeSetNamed: aName) ifNil: [ newSet _ ChangeSet new initialize name: aName. AllChangeSets add: newSet. newSet ]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'di 4/3/2000 14:51'! gatherChangeSets "ChangeSorter gatherChangeSets" "Collect any change sets created in other projects" | allChangeSets obsolete | allChangeSets _ AllChangeSets asSet. ChangeSet allSubInstances do: [:each | (allChangeSets includes: each) == (obsolete _ each isMoribund) ifTrue:[ obsolete ifTrue: ["Was included and is obsolete." AllChangeSets remove: each] ifFalse: ["Was not included and is not obsolete." AllChangeSets add: each]]]. ^ AllChangeSets ! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sw 8/20/1999 10:27'! highestNumberedChangeSet "ChangeSorter highestNumberedChangeSet" | aList | aList _ (self allChangeSetNames select: [:aString | aString startsWithDigit] thenCollect: [:aString | aString initialIntegerOrNil]). ^ (aList size > 0) ifTrue: [aList max] ifFalse: [nil] ! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sw 2/15/1999 23:02'! mostRecentChangeSetWithChangeForClass: class selector: selector | hits | hits _ self gatherChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]. hits isEmpty ifTrue: [^ 'not in any change set']. ^ 'recent cs: ', hits last name! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sw 1/27/2000 15:04'! promoteToTop: aChangeSet "make aChangeSet the first in the list from now on" self gatherChangeSets. AllChangeSets remove: aChangeSet ifAbsent: [^ self]. AllChangeSets add: aChangeSet. Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'tk 4/24/1998 13:52'! initialize AllChangeSets == nil ifTrue: [AllChangeSets _ OrderedCollection new]. self gatherChangeSets. ! ! !ChangeSorter class methodsFor: 'adding' stamp: 'sma 11/11/2000 23:22'! basicNewChangeSet: newName | newSet | (self changeSetNamed: newName) ifNotNil: [self inform: 'Sorry that name is already used'. ^ nil]. newSet _ ChangeSet new initialize name: newName. AllChangeSets add: newSet. ^ newSet! ! !ChangeSorter class methodsFor: 'adding' stamp: 'sma 11/11/2000 23:22'! newChangeSet "Prompt the user for a name, and establish a new change set of that name (if ok), making it the current changeset. Return nil of not ok, else return the actual changeset." | newName newSet | newName _ FillInTheBlank request: 'Please name the new change set:' initialAnswer: ChangeSet defaultName. newName isEmpty ifTrue: [self inform: 'nothing done'. ^ nil]. newSet _ self basicNewChangeSet: newName. newSet ifNotNil: [Smalltalk newChanges: newSet. Transcript cr; show: newName, ' is now the current change set']. ^ newSet! ! !ChangeSorter class methodsFor: 'adding' stamp: 'sma 11/11/2000 23:23'! newChangeSet: aName "Makes a new change set called aName, add author initials to try to ensure a unique change set name." | newName | newName _ aName , FileDirectory dot , Utilities authorInitials. ^ self basicNewChangeSet: newName! ! !ChangeSorter class methodsFor: 'adding' stamp: 'sma 11/11/2000 23:24'! newChangesFromStream: aStream named: aName "File in the code from the stream into a new change set whose name is derived from aName. Leave the 'current change set' unchanged. Return the new change set or nil on failure." | oldChanges newName newSet | oldChanges _ Smalltalk changes. newName _ aName sansPeriodSuffix. newSet _ self basicNewChangeSet: newName. newSet ifNotNil: [Smalltalk newChanges: newSet. aStream fileInAnnouncing: 'Loading ', newName, '...'. Transcript cr; show: 'File ', aName, ' successfully filed in to change set ', newName]. aStream close. Smalltalk newChanges: oldChanges. ^ newSet! ! !ChangeSorter class methodsFor: 'removing' stamp: 'sw 1/6/2001 06:21'! deleteChangeSetsNumberedLowerThan: anInteger "Delete all changes sets whose names start with integers smaller than anInteger" ChangeSorter removeChangeSetsNamedSuchThat: [:aName | aName first isDigit and: [aName initialIntegerOrNil < anInteger]]. "ChangeSorter deleteChangeSetsNumberedLowerThan: (ChangeSorter highestNumberedChangeSet name initialIntegerOrNil - 500)" ! ! !ChangeSorter class methodsFor: 'removing' stamp: 'sw 8/18/1999 09:44'! removeChangeSet: aChangeSet "Remove the given changeSet. Caller must assure that it's cool to do this" AllChangeSets remove: aChangeSet ifAbsent: []. aChangeSet wither ! ! !ChangeSorter class methodsFor: 'removing' stamp: 'tk 6/24/1999 11:32'! removeChangeSetsBefore: stopName "Remove all change sets before the one with the given name." "ChangeSorter removeChangeSetsBefore: 'Beyond'" | stop | (self confirm: 'Really remove all change sets before "', stopName, '"?') ifFalse: [^ self]. self gatherChangeSets. stop _ false. ChangeSet allSubInstancesDo: [:changeSet | changeSet name = stopName ifTrue: [stop _ true]. stop ifFalse: [ changeSet okayToRemove ifTrue: [ AllChangeSets remove: changeSet ifAbsent: []. changeSet wither]]]. Smalltalk garbageCollect. AllChangeSets _ OrderedCollection new. self gatherChangeSets. ! ! !ChangeSorter class methodsFor: 'removing' stamp: 'di 11/8/1998 16:32'! removeChangeSetsNamedSuchThat: nameBlock (ChangeSorter changeSetsNamedSuchThat: nameBlock) do: [:cs | AllChangeSets remove: cs wither]! ! !ChangeSorter class methodsFor: 'removing' stamp: 'di 11/9/1998 10:23'! removeEmptyUnnamedChangeSets "Remove all change sets that are empty, whose names start with Unnamed, and which are not nailed down by belonging to a Project." "ChangeSorter removeEmptyUnnamedChangeSets" | toGo | (toGo _ (self changeSetsNamedSuchThat: [:csName | csName beginsWith: 'Unnamed']) select: [:cs | cs isEmpty and: [cs okayToRemoveInforming: false]]) do: [:cs | AllChangeSets remove: cs wither]. self inform: toGo size printString, ' change set(s) removed.'! ! !ChangeSorter class methodsFor: 'services' stamp: 'di 5/6/1998 16:40'! browseChangeSetsWithClass: class selector: selector | hits index | hits _ self gatherChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]. hits isEmpty ifTrue: [^ PopUpMenu notify: class name,'.',selector , ' is not in any change set']. index _ hits size == 1 ifTrue: [1] ifFalse: [(PopUpMenu labelArray: (hits collect: [:cs | cs name]) lines: #()) startUp]. index = 0 ifTrue: [^ self]. (ChangeSorter new myChangeSet: (hits at: index)) open. ! ! !ChangeSorter class methodsFor: 'services' stamp: 'sw 1/19/2001 17:06'! buildAggregateChangeSet "Establish a change-set named Aggregate which bears the union of all the changes in all the existing change-sets in the system (other than any pre-existing Aggregate). This can be useful when wishing to discover potential conflicts between a disk-resident change-set and an image. Formerly very useful, now some of its unique contributions have been overtaken by new features" | aggregateChangeSet | aggregateChangeSet _ self existingOrNewChangeSetNamed: 'Aggregate'. aggregateChangeSet clear. (self gatherChangeSets copyWithout: aggregateChangeSet) do: [:aChangeSet | aggregateChangeSet assimilateAllChangesFoundIn: aChangeSet] "ChangeSorter buildAggregateChangeSet" ! ! !ChangeSorter class methodsFor: 'services' stamp: 'sw 1/4/2000 16:31'! reorderChangeSets "ChangeSorter reorderChangeSets" "Change the order of the change sets to something more convenient: First come the unnumbered changesets that come with the release. Next come the numbered updates. Next come all remaining changesets In a ChangeSorter, they will appear in the reversed order." | newHead newMid newTail itsName | self gatherChangeSets. newHead _ OrderedCollection new. newMid _ OrderedCollection new. newTail _ OrderedCollection new. AllChangeSets do: [:aSet | itsName _ aSet name. ((itsName beginsWith: 'Play With Me') or: [#('New Changes' 'MakeInternal') includes: itsName]) ifTrue: [newHead add: aSet] ifFalse: [itsName startsWithDigit ifTrue: [newMid add: aSet] ifFalse: [newTail add: aSet]]]. AllChangeSets _ newHead, newMid, newTail. Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]! ! !ChangeSorter class methodsFor: 'services' stamp: 'tk 4/30/1998 13:43'! secondaryChangeSet "Answer a likely change set to use as the second initial one in a Dual Change Sorter. " | last | self gatherChangeSets. AllChangeSets size == 1 ifTrue: [^ AllChangeSets first]. ^ (last _ AllChangeSets last) == Smalltalk changes ifTrue: [AllChangeSets at: (AllChangeSets size - 1)] ifFalse: [last]! ! MessageSet subclass: #ChangedMessageSet instanceVariableNames: 'changeSet ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser'! !ChangedMessageSet methodsFor: 'everything'! changeSet: aChangeSet changeSet _ aChangeSet! ! !ChangedMessageSet methodsFor: 'everything' stamp: 'sw 8/29/2000 16:30'! contents: aString notifying: aController "Accept the string as new source for the current method, and make certain the annotation pane gets invalidated" | selectedMessageName selector oldMessageList cls | self okayToAccept ifFalse: [^ false]. selectedMessageName _ self selectedMessageName. oldMessageList _ self messageList. contents _ nil. selector _ self selectedClassOrMetaClass compile: aString classified: self selectedMessageCategoryName notifying: aController. selector == nil ifTrue: [^ false]. cls _ self selectedClassOrMetaClass. contents _ aString copy. self changed: #annotation. selector ~~ selectedMessageName ifTrue: [(oldMessageList includes: selector) ifFalse: [self initializeMessageList: changeSet changedMessageListAugmented. self changed: #messageList]. self messageListIndex: (self messageList indexOf: (cls name, ' ', selector))]. ^ true! ! !ChangedMessageSet methodsFor: 'everything' stamp: 'sw 1/28/2001 20:59'! growable "Answer whether the receiver can be changed by manual additions & deletions" ^ false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangedMessageSet class instanceVariableNames: ''! !ChangedMessageSet class methodsFor: 'as yet unclassified' stamp: 'sw 1/27/2001 12:20'! openFor: aChangeSet "Open up a ChangedMessageSet browser on the given change set; this is a conventional message-list browser whose message-list consists of all the methods in aChangeSet. After any method submission, the message list is refigured, making it plausibly dynamic" | messageSet | messageSet _ MessageSet extantMethodsIn: aChangeSet changedMessageListAugmented. self openMessageList: messageSet name: ('Methods in Change Set ', aChangeSet name) autoSelect: nil changeSet: aChangeSet! ! !ChangedMessageSet class methodsFor: 'as yet unclassified' stamp: 'sma 4/30/2000 09:22'! openMessageList: messageList name: labelString autoSelect: autoSelectString changeSet: aChangeSet | messageSet | messageSet _ self messageList: messageList. messageSet changeSet: aChangeSet. messageSet autoSelectString: autoSelectString. Smalltalk isMorphic ifTrue: [self openAsMorph: messageSet name: labelString] ifFalse: [ScheduledControllers scheduleActive: (self open: messageSet name: labelString)]! ! Object subclass: #CharRecog instanceVariableNames: 'mp p sts pts bmin bmax op cPat in dirs ftrs prevFeatures textMorph ' classVariableNames: 'CharacterDictionary ' poolDictionaries: 'TextConstants ' category: 'System-Support'! !CharRecog commentStamp: '' prior: 0! Alan Kay's "one-page" character recognizer. Currently hooked up to text panes and to text morphs, such that you can get it started by hitting cmd-r in such text area that currently has focus. To reinitialize the recognition dictionary, evaluate CharRecog reinitializeCharacterDictionary ! !CharRecog methodsFor: 'recognizer'! extractFeatures | xl xr yl yh reg px py | "get extent bounding box" in _ bmax - bmin. "Look for degenerate forms first: . - |" "look for a dot" in < (3@3) ifTrue: [^' dot... ']. "Feature 5: turns (these are already in ftrs)" "Feature 4: absolute size" in < (10@10) ifTrue: [ftrs _ 'SML ', ftrs] ifFalse: [in <= (70@70) ifTrue: [ftrs _ 'REG ', ftrs] ifFalse: [in > (70@70) ifTrue: [ftrs _ 'LRG ', ftrs]]]. "Feature 3: aspect ratio" "horizontal shape" ((in y = 0) or: [(in x/in y) abs > 3]) ifTrue: [ftrs _ 'HOR ', ftrs] ifFalse: "vertical shape" [((in x = 0) or: [(in y/in x) abs >= 3]) ifTrue: [ftrs _ 'VER ', ftrs] ifFalse: "boxy shape" [((in x/in y) abs <= 3) ifTrue: [ftrs _ 'BOX ', ftrs. "Now only for boxes" "Feature 2: endstroke reg" ftrs _ (self regionOf: (pts last)), ftrs. "Feature 1: startstroke reg" ftrs _ (self regionOf: (pts contents at: 1)), ftrs.]]]. ^ftrs ! ! !CharRecog methodsFor: 'recognizer'! fourDirsFrom: p1 to: p2 | ex | "get the bounding box" ex _ p2 - p1. "unlike bmax-bmin, this can have negatives" "Look for degenerate forms first: . - |" "look for a dot" ex abs < (3@3) ifTrue: [^' dot... ']. "look for hori line" ((ex y = 0) or: [(ex x/ex y) abs > 1]) ifTrue: "look for w-e" [ex x > 0 ifTrue:[^'WE '] "it's an e-w" ifFalse:[^'EW ']]. "look for vertical line" ((ex x = 0) or: [(ex y/ex x) abs >= 1]) ifTrue: "look for n-s" [(ex y > 0) ifTrue:[ ^'NS '] "it's a s-n" ifFalse:[^'SN ']]. "look for a diagonal (ex x/ex y) abs <= 2 ifTrue:" "se or ne [ex x > 0 ifTrue:[ex y > 0 ifTrue:[^' se// ']. ^' ne// ']." "sw or nw ex y > 0 ifTrue:[^' sw// ']. ^' nw// ']." ! ! !CharRecog methodsFor: 'recognizer'! recognizeAndDispatch: charDispatchBlock ifUnrecognized: unrecognizedFeaturesBlock until: terminationBlock "Recognize characters, and dispatch each one found by evaluating charDispatchBlock; proceed until terminationBlock is true. This method derives directly from Alan's 1/96 #recognize method, but factors out the character dispatch and the termination condition from the main body of the method. 2/2/96 sw. 2/5/96 sw: switch to using a class variable for the character dictionary, and don't put vacuous entries in the dictionary if the user gives an empty response to the prompt, and don't send empty characters onward, and use a variant of the FillInTheBlank that keeps the prompt clear of the working window. 8/17/96 tk: Turn cr, tab, bs into strings so they work. 9/18/96 sw: in this variant, the block for handling unrecognized features is handed in as an argument, so that in some circumstances we can avoid putting up a prompt. unrecognizedFeaturesBlock should be a one-argument block, which is handed in the features and which is expected to return a string which indicates the determined translation -- empty if none." | prv cdir features char r s t dir | "Inits" (p _ Pen new) defaultNib: 1; down. "for points" pts _ ReadWriteStream on: #(). "Event Loop" [terminationBlock value] whileFalse: "First-Time" [pts reset. "will hold features" ftrs _ ''. (Sensor anyButtonPressed) ifTrue: [pts nextPut: (bmin _ bmax _ t _ s _ sts _ Sensor mousePoint). p place: sts. cdir _ nil. "Each-Time" [Sensor anyButtonPressed] whileTrue: "ink raw input" [p goto: (r _ Sensor mousePoint). "smooth it" s _ (0.5*s) + (0.5*r). "thin the stream" ((s x - t x) abs > 3 or:[(s y - t y) abs > 3]) ifTrue: [pts nextPut: t. "bounding box" bmin _ bmin min: s. bmax _ bmax max: s. "get current dir" dir _ (self fourDirsFrom: t to: s). t _ s. dir ~= ' dot... ' ifTrue: "store new dirs" [cdir ~= dir ifTrue: [ftrs _ ftrs, (cdir _ dir)]]. "for inked t's" p place: t; go: 1; place: r]]. "End Each-Time Loop" "Last-Time" "save last points" pts nextPut: t; nextPut: r. "find rest of features" features _ self extractFeatures. "find char..." char _ CharacterDictionary at: features ifAbsent: [unrecognizedFeaturesBlock value: features]. "special chars" char size > 0 ifTrue: [char = 'tab' ifTrue: [char _ Tab]. char = 'cr' ifTrue: [char _ CR]. "must be a string" char class == Character ifTrue: [char _ String with: char]. char = 'bs' ifTrue: [char _ BS]. "control the editor" charDispatchBlock value: char]]] ! ! !CharRecog methodsFor: 'recognizer'! recognizeAndDispatch: charDispatchBlock until: terminationBlock "Recognize characters, and dispatch each one found by evaluating charDispatchBlock; proceed until terminationBlock is true. 9/18/96 sw" ^ self recognizeAndDispatch: charDispatchBlock ifUnrecognized: [:features | self stringForUnrecognizedFeatures: features] until: terminationBlock ! ! !CharRecog methodsFor: 'recognizer'! regionOf: pt | px py reg xl yl yh xr rg | "it's some other character" rg _ in/3. xl _ bmin x + rg x. xr _ bmax x - rg x. "divide box into 9 regions" yl _ bmin y + rg y. yh _ bmax y - rg y. px _ pt x. py _ pt y. reg _ (px < xl ifTrue: [py < yl ifTrue: ['NW '] "py >= yl" ifFalse:[ py < yh ifTrue:['W '] ifFalse: ['SW ']]] ifFalse: [px < xr ifTrue: [py < yl ifTrue: ['N '] ifFalse: [py < yh ifTrue: ['C '] ifFalse: ['S ']]] ifFalse: [py < yl ifTrue: ['NE '] ifFalse: [py < yh ifTrue: ['E '] ifFalse: ['SE ']]]]). ^reg. ! ! !CharRecog methodsFor: 'recognizer' stamp: 'di 3/14/1999 16:45'! stringForUnrecognizedFeatures: features "Prompt the user for what string the current features represent, and return the result. 9/18/96 sw" | result | result _ FillInTheBlank request: ('Not recognized. type char, or "tab", "cr" or "bs", or hit return to ignore ', features). textMorph ifNotNil: [textMorph world displayWorld "take down the FillInTheBlank morph"]. ^ (result = '~' | result = '') ifTrue: [''] ifFalse: [CharacterDictionary at: features put: result. result]! ! !CharRecog methodsFor: 'morphic dockup' stamp: 'sw 12/16/1998 13:17'! textMorph: aTextMorph textMorph _ aTextMorph! ! !CharRecog methodsFor: 'historical & disused'! directionFrom: p1 to: p2 | ex | "This does 8 directions and is not used in current recognizer" "get the bounding box" ex _ p2 - p1. "unlike bmax-bmin, this can have negatives" "Look for degenerate forms first: . - |" "look for a dot" ex abs < (3@3) ifTrue: [^' dot... ']. "look for hori line" ((ex y = 0) or: [(ex x/ex y) abs > 2]) ifTrue: "look for w-e" [ex x > 0 ifTrue:[^' we-- '] "it's an e-w" ifFalse:[^' ew-- ']]. "look for vertical line" ((ex x = 0) or: [(ex y/ex x) abs > 2]) ifTrue: "look for n-s" [(ex y > 0) ifTrue:[ ^' ns||'] "it's a s-n" ifFalse:[^' sn|| ']]. "look for a diagonal" (ex x/ex y) abs <= 2 ifTrue: "se or ne" [ex x > 0 ifTrue:[ex y > 0 ifTrue:[^' se// ']. ^' ne// ']. "sw or nw" ex y > 0 ifTrue:[^' sw// ']. ^' nw// ']. ! ! !CharRecog methodsFor: 'historical & disused' stamp: 'jm 4/28/1998 05:37'! learnPrev "The character recognized before this one was wrong. (Got here via the gesture for 'wrong'.) Bring up a dialog box on that char. 8/21/96 tk" | old result | old _ CharacterDictionary at: prevFeatures ifAbsent: [^ '']. "get right char from user" result _ FillInTheBlank request: ('Redefine the gesture we thought was "', old asString, '".', ' (Letter or: tab cr wrong bs select caret) ', prevFeatures). "ignore or..." (result = '~' | result = '') ifTrue: [''] "...enter new char" ifFalse: [ CharacterDictionary at: prevFeatures put: result]. "caller erases bad char" "good char" ^ result! ! !CharRecog methodsFor: 'historical & disused'! recogPar | prv cdir result features char r s t dir | "Inits" (p _ Pen new) defaultNib: 1; down. "for points" pts _ ReadWriteStream on: #(). "Event Loop" [Sensor anyButtonPressed] whileFalse: [(Sensor mousePoint x < 50) ifTrue: [^''].]. "First-Time" pts reset. "will hold features" ftrs _ ''. (Sensor anyButtonPressed) ifTrue: [pts nextPut: (bmin _ bmax _ t _ s _ sts _ Sensor mousePoint). p place: sts. cdir _ nil. "Each-Time" [Sensor anyButtonPressed] whileTrue: [ "ink raw input" p goto: (r _ Sensor mousePoint). "smooth it" s _ (0.5*s) + (0.5*r). "thin the stream" ((s x - t x) abs > 3 or:[(s y - t y) abs > 3]) ifTrue: [ pts nextPut: t. "bounding box" bmin _ bmin min: s. bmax _ bmax max: s. "get current dir" dir _ (self fourDirsFrom: t to: s). t _ s. dir ~= ' dot... ' ifTrue: [ "store new dirs" cdir ~= dir ifTrue: [ftrs _ ftrs, (cdir _ dir)]]. "for inked t's" p place: t; go: 1; place: r. ]. "End Each-Time Loop" ]. "Last-Time" "start a new recog for next point" [CharRecog new recognize] fork. "save last points" pts nextPut: t; nextPut: r. "find rest of features" features _ self extractFeatures. "find char..." char _ CharacterDictionary at: features ifAbsent: "...or get from user" [ result _ FillInTheBlank request: 'Not recognized. type char, or type ~: ', features. "ignore or..." result = '~' ifTrue: [''] "...enter new char" ifFalse: [CharacterDictionary at: features put: result. result]]. "control the editor" (char = 'cr' ifTrue: [Transcript cr] ifFalse: [char = 'bs' ifTrue: [Transcript bs] ifFalse: [char = 'tab' ifTrue:[Transcript tab] ifFalse: [Transcript show: char]]]). "End First-Time Loop" ]. ! ! !CharRecog methodsFor: 'historical & disused'! recognize | prv cdir result features char r s t dir | "Alan Kay's recognizer as of 1/31/96. This version preserved for historical purposes, and also because it's still called by the not-yet-deployed method recogPar. Within the current image, the recognizer is now called via #recognizeAndDispatch:until:" "Inits" (p _ Pen new) defaultNib: 1; down. "for points" pts _ ReadWriteStream on: #(). "Event Loop" [(Sensor mousePoint x) < 50] whileFalse: "First-Time" [pts reset. "will hold features" ftrs _ ''. (Sensor anyButtonPressed) ifTrue: [pts nextPut: (bmin _ bmax _ t _ s _ sts _ Sensor mousePoint). p place: sts. cdir _ nil. "Each-Time" [Sensor anyButtonPressed] whileTrue: [ "ink raw input" p goto: (r _ Sensor mousePoint). "smooth it" s _ (0.5*s) + (0.5*r). "thin the stream" ((s x - t x) abs > 3 or:[(s y - t y) abs > 3]) ifTrue: [ pts nextPut: t. "bounding box" bmin _ bmin min: s. bmax _ bmax max: s. "get current dir" dir _ (self fourDirsFrom: t to: s). t _ s. dir ~= ' dot... ' ifTrue: [ "store new dirs" cdir ~= dir ifTrue: [ftrs _ ftrs, (cdir _ dir)]]. "for inked t's" p place: t; go: 1; place: r. ]. "End Each-Time Loop" ]. "Last-Time" "save last points" pts nextPut: t; nextPut: r. "find rest of features" features _ self extractFeatures. "find char..." char _ CharacterDictionary at: features ifAbsent: "...or get from user" [ result _ FillInTheBlank request: 'Not recognized. type char, or type ~: ', features. "ignore or..." result = '~' ifTrue: [''] "...enter new char" ifFalse: [CharacterDictionary at: features put: result. result]]. "control the editor" (char = 'cr' ifTrue: [Transcript cr] ifFalse: [char = 'bs' ifTrue: [Transcript bs] ifFalse: [char = 'tab' ifTrue:[Transcript tab] ifFalse: [Transcript show: char]]]). "End First-Time Loop" ]. "End Event-Loop" ]. ! ! !CharRecog methodsFor: 'historical & disused'! recognizeAndPutInTranscript "Call Alan's recognizer repeatedly until the mouse is near the left edge of the screen, and dispatch keystrokes inferred to the Trancript. 2/2/96 sw" ^ self recognizeAndDispatch: [:char | (char = 'cr') ifTrue: [Transcript cr] ifFalse: [char = 'bs' ifTrue: [Transcript bs] ifFalse: [char = 'tab' ifTrue:[Transcript tab] ifFalse: [Transcript show: char]]]] until: [Sensor mousePoint x < 50] "CharRecog new recognizeAndPutInTranscript"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CharRecog class instanceVariableNames: ''! !CharRecog class methodsFor: 'initialization'! initialize "Iniitialize the character dictionary if it doesn't exist yet. 2/5/96 sw" CharacterDictionary == nil ifTrue: [CharacterDictionary _ Dictionary new]! ! !CharRecog class methodsFor: 'initialization'! reinitializeCharacterDictionary "Reset the character dictionary to be empty, ready for a fresh start. 2/5/96 sw" CharacterDictionary _ Dictionary new "CharRecog reinitializeCharacterDictionary" ! ! !CharRecog class methodsFor: 'saving dictionary'! readRecognizerDictionaryFrom: aFileName "Read a fresh version of the Recognizer dictionary in from a file of the given name. 7/26/96 sw" "CharRecog readRecognizerDictionaryFrom: 'RecogDictionary.2 fixed'" | aReferenceStream | aReferenceStream _ ReferenceStream fileNamed: aFileName. CharacterDictionary _ aReferenceStream next. aReferenceStream close. ! ! !CharRecog class methodsFor: 'saving dictionary'! saveRecognizerDictionaryTo: aFileName "Save the current state of the Recognizer dictionary to disk. 7/26/96 sw" | aReferenceStream | aReferenceStream _ ReferenceStream fileNamed: aFileName. aReferenceStream nextPut: CharacterDictionary. aReferenceStream close! ! Magnitude subclass: #Character instanceVariableNames: 'value ' classVariableNames: 'CharacterTable ' poolDictionaries: '' category: 'Collections-Text'! !Character commentStamp: '' prior: 0! I represent a character by storing its associated ASCII code (extended to 256 codes). My instances are created uniquely, so that all instances of a character ($R, for example) are identical.! !Character methodsFor: 'accessing'! asciiValue "Answer the value of the receiver that represents its ascii encoding." ^value! ! !Character methodsFor: 'accessing'! digitValue "Answer 0-9 if the receiver is $0-$9, 10-35 if it is $A-$Z, and < 0 otherwise. This is used to parse literal numbers of radix 2-36." value <= $9 asciiValue ifTrue: [^value - $0 asciiValue]. value >= $A asciiValue ifTrue: [value <= $Z asciiValue ifTrue: [^value - $A asciiValue + 10]]. ^ -1! ! !Character methodsFor: 'comparing'! < aCharacter "Answer true if the receiver's value < aCharacter's value." ^self asciiValue < aCharacter asciiValue! ! !Character methodsFor: 'comparing'! = aCharacter "Primitive. Answer true if the receiver and the argument are the same object (have the same object pointer) and false otherwise. Optional. See Object documentation whatIsAPrimitive." ^self == aCharacter! ! !Character methodsFor: 'comparing'! > aCharacter "Answer true if the receiver's value > aCharacter's value." ^self asciiValue > aCharacter asciiValue! ! !Character methodsFor: 'comparing'! hash "Hash is reimplemented because = is implemented." ^value! ! !Character methodsFor: 'testing'! isAlphaNumeric "Answer whether the receiver is a letter or a digit." ^self isLetter or: [self isDigit]! ! !Character methodsFor: 'testing'! isDigit "Answer whether the receiver is a digit." ^value >= 48 and: [value <= 57]! ! !Character methodsFor: 'testing'! isLetter "Answer whether the receiver is a letter." ^(8r141 <= value and: [value <= 8r172]) or: [8r101 <= value and: [value <= 8r132]]! ! !Character methodsFor: 'testing'! isLowercase "Answer whether the receiver is a lowercase letter. (The old implementation answered whether the receiver is not an uppercase letter.)" ^8r141 <= value and: [value <= 8r172]! ! !Character methodsFor: 'testing' stamp: 'ls 7/26/1998 20:27'! isSafeForHTTP "whether a character is 'safe', or needs to be escaped when used, eg, in a URL" ^self isAlphaNumeric or: [ '.~-_' includes: self ]! ! !Character methodsFor: 'testing'! isSeparator "Answer whether the receiver is one of the separator characters--space, cr, tab, line feed, or form feed." value = 32 ifTrue: [^true]. "space" value = 13 ifTrue: [^true]. "cr" value = 9 ifTrue: [^true]. "tab" value = 10 ifTrue: [^true]. "line feed" value = 12 ifTrue: [^true]. "form feed" ^false! ! !Character methodsFor: 'testing' stamp: 'di 4/3/1999 00:38'! isSpecial "Answer whether the receiver is one of the special characters" ^'+-/\*~<>=@,%|&?!!' includes: self! ! !Character methodsFor: 'testing'! isUppercase "Answer whether the receiver is an uppercase letter. (The old implementation answered whether the receiver is not a lowercase letter.)" ^8r101 <= value and: [value <= 8r132]! ! !Character methodsFor: 'testing'! isVowel "Answer whether the receiver is one of the vowels, AEIOU, in upper or lower case." ^'AEIOU' includes: self asUppercase! ! !Character methodsFor: 'testing'! tokenish "Answer whether the receiver is a valid token-character--letter, digit, or colon." ^self isLetter or: [self isDigit or: [self = $:]]! ! !Character methodsFor: 'copying' stamp: 'tk 12/9/2000 11:46'! clone "Answer with the receiver, because Characters are unique."! ! !Character methodsFor: 'copying'! copy "Answer with the receiver because Characters are unique."! ! !Character methodsFor: 'copying'! deepCopy "Answer with the receiver because Characters are unique."! ! !Character methodsFor: 'copying' stamp: 'tk 1/7/1999 16:50'! veryDeepCopyWith: deepCopier "Return self. I can't be copied."! ! !Character methodsFor: 'printing'! hex ^ String with: ('0123456789ABCDEF' at: value//16+1) with: ('0123456789ABCDEF' at: value\\16+1)! ! !Character methodsFor: 'printing'! isLiteral ^true! ! !Character methodsFor: 'printing'! printOn: aStream aStream nextPut: $$. aStream nextPut: self! ! !Character methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:04'! printOnStream: aStream aStream print:'$', (String with:self).! ! !Character methodsFor: 'printing'! storeOn: aStream "Character literals are preceded by '$'." aStream nextPut: $$; nextPut: self! ! !Character methodsFor: 'converting'! asCharacter "Answer the receiver itself." ^self! ! !Character methodsFor: 'converting' stamp: 'ls 9/5/1998 01:18'! asIRCLowercase "convert to lowercase, using IRC's rules" self == $[ ifTrue: [ ^ ${ ]. self == $] ifTrue: [ ^ $} ]. self == $\ ifTrue: [ ^ $| ]. ^self asLowercase! ! !Character methodsFor: 'converting'! asInteger "Answer the value of the receiver." ^value! ! !Character methodsFor: 'converting'! asLowercase "If the receiver is uppercase, answer its matching lowercase Character." (8r101 <= value and: [value <= 8r132]) "self isUppercase" ifTrue: [^ Character value: value + 8r40] ifFalse: [^ self]! ! !Character methodsFor: 'converting' stamp: 'sma 3/11/2000 17:21'! asString ^ String with: self! ! !Character methodsFor: 'converting' stamp: 'SqR 8/23/2000 16:19'! asSymbol "Answer a Symbol consisting of the receiver as the only element." ^Symbol intern: self asString! ! !Character methodsFor: 'converting' stamp: 'tk 9/4/2000 12:05'! asText ^ self asString asText! ! !Character methodsFor: 'converting'! asUppercase "If the receiver is lowercase, answer its matching uppercase Character." (8r141 <= value and: [value <= 8r172]) "self isLowercase" ifTrue: [^ Character value: value - 8r40] ifFalse: [^ self]! ! !Character methodsFor: 'converting' stamp: 'sma 3/15/2000 22:57'! isoToSqueak "Convert receiver from iso8895-1 (actually CP1252) to mac encoding. Does not do lf/cr conversion!! Characters not available in MacRoman encoding have been remapped to their base characters or to $?." value < 128 ifTrue: [^ self]. ^ Character value: (#( 219 63 226 196 227 201 160 224 246 228 83 220 206 63 90 63 "80-8F" 63 212 213 210 211 165 208 209 247 170 115 221 207 63 122 217 "90-9F" 202 193 162 163 63 180 124 164 172 169 187 199 194 45 168 248 "A0-AF" 161 177 50 51 171 181 166 225 252 49 188 200 63 63 63 192 "B0-BF" 203 231 229 204 128 129 174 130 233 131 230 232 237 234 235 236 "C0-CF" 63 132 241 238 239 205 133 42 175 244 242 243 134 89 63 167 "D0-DF" 136 135 137 139 138 140 190 141 143 142 144 145 147 146 148 149 "E0-EF" 63 150 152 151 153 155 154 214 191 157 156 158 159 121 63 216 "F0-FF" ) at: value - 127)! ! !Character methodsFor: 'converting' stamp: 'bf 3/9/2000 16:52'! squeakToIso "Convert from mac to iso8895-1 encoding. Does not do lf/cr conversion!!" value < 128 ifTrue: [^self]. ^ Character value: (#(196 197 199 201 209 214 220 225 224 226 228 227 229 231 233 232 234 235 237 236 238 239 241 243 242 244 246 245 250 249 251 252 185 176 162 163 167 130 182 223 174 169 142 180 168 173 198 216 141 177 178 179 165 181 166 183 184 160 188 170 186 189 230 248 191 161 172 146 128 129 140 171 187 131 190 192 195 213 145 147 208 132 150 148 149 144 247 215 255 221 152 151 134 153 222 164 136 135 137 139 138 194 202 193 203 200 205 206 207 204 211 212 240 210 218 219 217 155 154 133 143 157 156 158 159 253 254 175) at: value - 127)! ! !Character methodsFor: 'converting'! to: other "Answer with a collection in ascii order -- $a to: $z" ^ (self asciiValue to: other asciiValue) collect: [:ascii | Character value: ascii]! ! !Character methodsFor: 'object fileIn' stamp: 'tk 1/17/2000 11:27'! comeFullyUpOnReload: smartRefStream "Use existing an Character. Don't use the new copy." ^ self class value: value! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Character class instanceVariableNames: ''! !Character class methodsFor: 'class initialization'! initialize "Create the table of unique Characters. This code is not shown so that the user can not destroy the system by trying to recreate the table."! ! !Character class methodsFor: 'instance creation' stamp: 'ls 8/15/1998 06:56'! allCharacters ^ (0 to: 255) collect: [:v | Character value: v] ! ! !Character class methodsFor: 'instance creation'! digitValue: x "Answer the Character whose digit value is x. For example, answer $9 for x=9, $0 for x=0, $A for x=10, $Z for x=35." | index | index _ x asInteger. ^CharacterTable at: (index < 10 ifTrue: [48 + index] ifFalse: [55 + index]) + 1! ! !Character class methodsFor: 'instance creation'! new "Creating new characters is not allowed." self error: 'cannot create new characters'! ! !Character class methodsFor: 'instance creation'! separators ^ #(32 "space" 13 "cr" 9 "tab" 10 "line feed" 12 "form feed") collect: [:v | Character value: v] ! ! !Character class methodsFor: 'instance creation'! value: anInteger "Answer the Character whose value is anInteger." ^CharacterTable at: anInteger + 1! ! !Character class methodsFor: 'accessing untypeable characters'! backspace "Answer the Character representing a backspace." ^self value: 8! ! !Character class methodsFor: 'accessing untypeable characters'! cr "Answer the Character representing a carriage return." ^self value: 13! ! !Character class methodsFor: 'accessing untypeable characters'! enter "Answer the Character representing enter." ^self value: 3! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'ls 9/2/1999 08:06'! escape "Answer the ASCII ESC character" ^self value: 27! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'sma 3/15/2000 22:33'! euro "The Euro currency sign, that E with two dashes. The key code is a wild guess" ^ Character value: 219! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'ls 9/8/1998 22:15'! lf "Answer the Character representing a linefeed." ^self value: 10! ! !Character class methodsFor: 'accessing untypeable characters'! linefeed "Answer the Character representing a linefeed." ^self value: 10! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'sma 3/11/2000 20:47'! nbsp "non-breakable space." ^ Character value: 202! ! !Character class methodsFor: 'accessing untypeable characters'! newPage "Answer the Character representing a form feed." ^self value: 12! ! !Character class methodsFor: 'accessing untypeable characters'! space "Answer the Character representing a space." ^self value: 32! ! !Character class methodsFor: 'accessing untypeable characters'! tab "Answer the Character representing a tab." ^self value: 9! ! !Character class methodsFor: 'constants' stamp: 'rhi 9/8/2000 14:57'! alphabet "($a to: $z) as: String" ^ 'abcdefghijklmnopqrstuvwxyz' copy! ! !Character class methodsFor: 'constants'! characterTable "Answer the class variable in which unique Characters are stored." ^CharacterTable! ! Rectangle subclass: #CharacterBlock instanceVariableNames: 'stringIndex text textLine ' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !CharacterBlock commentStamp: '' prior: 0! My instances contain information about displayed characters. They are used to return the results of methods: Paragraph characterBlockAtPoint: aPoint and Paragraph characterBlockForIndex: stringIndex. Any recomposition or movement of a Paragraph can make the instance obsolete.! !CharacterBlock methodsFor: 'accessing' stamp: 'di 6/7/2000 17:33'! copy "Overridden because Rectangle does a deepCopy, which goes nuts with the text" ^ self clone! ! !CharacterBlock methodsFor: 'accessing'! stringIndex "Answer the position of the receiver in the string it indexes." ^stringIndex! ! !CharacterBlock methodsFor: 'accessing' stamp: 'di 12/2/97 14:33'! textLine ^ textLine! ! !CharacterBlock methodsFor: 'accessing' stamp: 'di 12/2/97 14:33'! textLine: aLine textLine _ aLine! ! !CharacterBlock methodsFor: 'comparing'! < aCharacterBlock "Answer whether the string index of the receiver precedes that of aCharacterBlock." ^stringIndex < aCharacterBlock stringIndex! ! !CharacterBlock methodsFor: 'comparing'! <= aCharacterBlock "Answer whether the string index of the receiver does not come after that of aCharacterBlock." ^(self > aCharacterBlock) not! ! !CharacterBlock methodsFor: 'comparing'! = aCharacterBlock self species = aCharacterBlock species ifTrue: [^stringIndex = aCharacterBlock stringIndex] ifFalse: [^false]! ! !CharacterBlock methodsFor: 'comparing'! > aCharacterBlock "Answer whether the string index of the receiver comes after that of aCharacterBlock." ^aCharacterBlock < self! ! !CharacterBlock methodsFor: 'comparing'! >= aCharacterBlock "Answer whether the string index of the receiver does not precede that of aCharacterBlock." ^(self < aCharacterBlock) not! ! !CharacterBlock methodsFor: 'printing' stamp: 'di 12/2/97 19:15'! printOn: aStream aStream nextPutAll: 'a CharacterBlock with index '. stringIndex printOn: aStream. (text ~~ nil and: [text size> 0 and: [stringIndex between: 1 and: text size]]) ifTrue: [aStream nextPutAll: ' and character '. (text at: stringIndex) printOn: aStream]. aStream nextPutAll: ' and rectangle '. super printOn: aStream. textLine ifNotNil: [aStream cr; nextPutAll: ' in '. textLine printOn: aStream]. ! ! !CharacterBlock methodsFor: 'private'! moveBy: aPoint "Change the corner positions of the receiver so that its area translates by the amount defined by the argument, aPoint." origin _ origin + aPoint. corner _ corner + aPoint! ! !CharacterBlock methodsFor: 'private' stamp: 'di 10/23/97 22:33'! stringIndex: anInteger text: aText topLeft: topLeft extent: extent stringIndex _ anInteger. text _ aText. super setOrigin: topLeft corner: topLeft + extent ! ! CharacterScanner subclass: #CharacterBlockScanner instanceVariableNames: 'characterPoint characterIndex lastCharacter lastCharacterExtent lastSpaceOrTabExtent nextLeftMargin specialWidth ' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !CharacterBlockScanner commentStamp: '' prior: 0! My instances are used to scan text to compute the CharacterBlock for a character specified by its index in the text or its proximity to the cursor location.! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:51'! cr "Answer a CharacterBlock that specifies the current location of the mouse relative to a carriage return stop condition that has just been encountered. The ParagraphEditor convention is to denote selections by CharacterBlocks, sometimes including the carriage return (cursor is at the end) and sometimes not (cursor is in the middle of the text)." ((characterIndex ~= nil and: [characterIndex > text size]) or: [(line last = text size) and: [(destY + line lineHeight) < characterPoint y]]) ifTrue: ["When off end of string, give data for next character" destY _ destY + line lineHeight. lastCharacter _ nil. characterPoint _ ((text at: lastIndex) = CR ifTrue: [leftMargin] ifFalse: [nextLeftMargin]) @ destY. lastIndex _ lastIndex + 1. self lastCharacterExtentSetX: 0. ^ true]. lastCharacter _ CR. characterPoint _ destX @ destY. self lastCharacterExtentSetX: rightMargin - destX. ^true! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'di 9/3/2000 13:09'! crossedX "Text display has wrapping. The scanner just found a character past the x location of the cursor. We know that the cursor is pointing at a character or before one." | leadingTab currentX | characterPoint x <= (destX + (lastCharacterExtent x // 2)) ifTrue: [lastCharacter _ (text at: lastIndex). characterPoint _ destX @ destY. ^true]. lastIndex >= line last ifTrue: [lastCharacter _ (text at: line last). characterPoint _ destX @ destY. ^true]. "Pointing past middle of a character, return the next character." lastIndex _ lastIndex + 1. lastCharacter _ text at: lastIndex. currentX _ destX + lastCharacterExtent x + kern. self lastCharacterExtentSetX: (font widthOf: lastCharacter). characterPoint _ currentX @ destY. lastCharacter = Space ifFalse: [^ true]. "Yukky if next character is space or tab." textStyle alignment = Justified ifTrue: [self lastCharacterExtentSetX: (lastCharacterExtent x + (line justifiedPadFor: (spaceCount + 1))). ^ true]. true ifTrue: [^ true]. "NOTE: I find no value to the following code, and so have defeated it - DI" "See tabForDisplay for illumination on the following awfulness." leadingTab _ true. line first to: lastIndex - 1 do: [:index | (text at: index) ~= Tab ifTrue: [leadingTab _ false]]. (textStyle alignment ~= Justified or: [leadingTab]) ifTrue: [self lastCharacterExtentSetX: (textStyle nextTabXFrom: currentX leftMargin: leftMargin rightMargin: rightMargin) - currentX] ifFalse: [self lastCharacterExtentSetX: (((currentX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount))) - currentX) max: 0)]. ^ true! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:50'! endOfRun "Before arriving at the cursor location, the selection has encountered an end of run. Answer false if the selection continues, true otherwise. Set up indexes for building the appropriate CharacterBlock." | runLength lineStop | ((characterIndex ~~ nil and: [runStopIndex < characterIndex and: [runStopIndex < text size]]) or: [characterIndex == nil and: [lastIndex < line last]]) ifTrue: ["We're really at the end of a real run." runLength _ (text runLengthFor: (lastIndex _ lastIndex + 1)). characterIndex ~~ nil ifTrue: [lineStop _ characterIndex "scanning for index"] ifFalse: [lineStop _ line last "scanning for point"]. (runStopIndex _ lastIndex + (runLength - 1)) > lineStop ifTrue: [runStopIndex _ lineStop]. self setStopConditions. ^false]. lastCharacter _ text at: lastIndex. characterPoint _ destX @ destY. ((lastCharacter = Space and: [textStyle alignment = Justified]) or: [lastCharacter = Tab and: [lastSpaceOrTabExtent notNil]]) ifTrue: [lastCharacterExtent _ lastSpaceOrTabExtent]. characterIndex ~~ nil ifTrue: ["If scanning for an index and we've stopped on that index, then we back destX off by the width of the character stopped on (it will be pointing at the right side of the character) and return" runStopIndex = characterIndex ifTrue: [self characterPointSetX: destX - lastCharacterExtent x. ^true]. "Otherwise the requested index was greater than the length of the string. Return string size + 1 as index, indicate further that off the string by setting character to nil and the extent to 0." lastIndex _ lastIndex + 1. lastCharacter _ nil. self lastCharacterExtentSetX: 0. ^true]. "Scanning for a point and either off the end of the line or off the end of the string." runStopIndex = text size ifTrue: ["off end of string" lastIndex _ lastIndex + 1. lastCharacter _ nil. self lastCharacterExtentSetX: 0. ^true]. "just off end of line without crossing x" lastIndex _ lastIndex + 1. ^true! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:51'! paddedSpace "When the line is justified, the spaces will not be the same as the font's space character. A padding of extra space must be considered in trying to find which character the cursor is pointing at. Answer whether the scanning has crossed the cursor." | pad | pad _ 0. spaceCount _ spaceCount + 1. pad _ line justifiedPadFor: spaceCount. lastSpaceOrTabExtent _ lastCharacterExtent copy. self lastSpaceOrTabExtentSetX: spaceWidth + pad. (destX + lastSpaceOrTabExtent x) >= characterPoint x ifTrue: [lastCharacterExtent _ lastSpaceOrTabExtent copy. ^self crossedX]. lastIndex _ lastIndex + 1. destX _ destX + lastSpaceOrTabExtent x. ^ false ! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 1/8/2000 14:32'! setFont specialWidth _ nil. super setFont! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 5/18/2000 16:47'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. textStyle alignment = Justified ifTrue:[ "Make a local copy of stop conditions so we don't modify the default" stopConditions == DefaultStopConditions ifTrue:[stopConditions _ stopConditions copy]. stopConditions at: Space asciiValue + 1 put: #paddedSpace]! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:50'! tab | currentX | currentX _ (textStyle alignment == Justified and: [self leadingTab not]) ifTrue: "imbedded tabs in justified text are weird" [destX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount)) max: destX] ifFalse: [textStyle 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! ! !CharacterBlockScanner methodsFor: 'private' stamp: 'di 9/3/2000 15:44'! buildCharacterBlockIn: para | lineIndex runLength lineStop done stopCondition | "handle nullText" (para numberOfLines = 0 or: [text size = 0]) ifTrue: [^ CharacterBlock new stringIndex: 1 "like being off end of string" text: para text topLeft: (para leftMarginForDisplayForLine: 1) @ para compositionRectangle top extent: 0 @ textStyle lineGrid]. "find the line" lineIndex _ para lineIndexOfTop: characterPoint y. destY _ para topAtLineIndex: lineIndex. line _ para lines at: lineIndex. rightMargin _ para rightMarginForDisplay. (lineIndex = para numberOfLines and: [(destY + line lineHeight) < characterPoint y]) ifTrue: ["if beyond lastLine, force search to last character" self characterPointSetX: rightMargin] ifFalse: [characterPoint y < (para compositionRectangle) top ifTrue: ["force search to first line" characterPoint _ (para compositionRectangle) topLeft]. characterPoint x > rightMargin ifTrue: [self characterPointSetX: rightMargin]]. destX _ (leftMargin _ para leftMarginForDisplayForLine: lineIndex). nextLeftMargin_ para leftMarginForDisplayForLine: lineIndex+1. lastIndex _ line first. self setStopConditions. "also sets font" runLength _ (text runLengthFor: line first). characterIndex == nil ifTrue: [lineStop _ line last "characterBlockAtPoint"] ifFalse: [lineStop _ characterIndex "characterBlockForIndex"]. (runStopIndex _ lastIndex + (runLength - 1)) > lineStop ifTrue: [runStopIndex _ 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: (font widthOf: (text at: lastIndex)). (self perform: stopCondition) ifTrue: [characterIndex == nil ifTrue: ["characterBlockAtPoint" ^ CharacterBlock new stringIndex: lastIndex text: text topLeft: characterPoint + (font descentKern @ 0) extent: lastCharacterExtent] ifFalse: ["characterBlockForIndex" ^ CharacterBlock new stringIndex: lastIndex text: text topLeft: characterPoint + ((font descentKern) - kern @ 0) extent: lastCharacterExtent]]]! ! !CharacterBlockScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:34'! characterPointSetX: xVal characterPoint _ xVal @ characterPoint y! ! !CharacterBlockScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:34'! lastCharacterExtentSetX: xVal lastCharacterExtent _ xVal @ lastCharacterExtent y! ! !CharacterBlockScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:34'! lastSpaceOrTabExtentSetX: xVal lastSpaceOrTabExtent _ xVal @ lastSpaceOrTabExtent y! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 19:14'! characterBlockAtPoint: aPoint in: aParagraph "Answer a CharacterBlock for character in aParagraph at point aPoint. It is assumed that aPoint has been transformed into coordinates appropriate to the text's destination form rectangle and the composition rectangle." self initializeFromParagraph: aParagraph clippedBy: aParagraph clippingRectangle. characterPoint _ aPoint. ^self buildCharacterBlockIn: aParagraph! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'di 9/3/2000 11:39'! 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. 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 @ textStyle lineGrid) textLine: line]. rightMargin _ line rightMargin. destX _ leftMargin _ line leftMarginForAlignment: textStyle alignment. destY _ line top. lastIndex _ line first. self setStopConditions. "also sets font" 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 @ 0) extent: lastCharacterExtent) textLine: line]]]! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 19:14'! characterBlockForIndex: targetIndex in: aParagraph "Answer a CharacterBlock for character in aParagraph at targetIndex. The coordinates in the CharacterBlock will be appropriate to the intersection of the destination form rectangle and the composition rectangle." self initializeFromParagraph: aParagraph clippedBy: aParagraph clippingRectangle. characterIndex _ targetIndex. characterPoint _ aParagraph rightMarginForDisplay @ (aParagraph topAtLineIndex: (aParagraph lineIndexOfCharacterIndex: characterIndex)). ^self buildCharacterBlockIn: aParagraph! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'ar 5/19/2000 14:46'! characterNotInFont "See the note in CharacterScanner>>characterNotInFont. This does not handle character selection nicely, i.e., illegal characters are a little tricky to select. Since the end of a run or line is subverted here by actually having the scanner scan a different string in order to manage the illegal character, things are not in an absolutely correct state for the character location code. If this becomes too odious in use, logic will be added to accurately manage the situation." lastCharacterExtent _ (font widthOf: (font maxAscii + 1) asCharacter) @ line lineHeight. ^super characterNotInFont! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 17:35'! placeEmbeddedObject: anchoredMorph (super placeEmbeddedObject: anchoredMorph) ifFalse: [^ false]. specialWidth _ anchoredMorph width. ^ true! ! Object subclass: #CharacterScanner instanceVariableNames: 'destX lastIndex xTable map destY stopConditions text textStyle alignment leftMargin rightMargin font line runStopIndex spaceCount spaceWidth emphasisCode kern indentationLevel ' classVariableNames: 'DefaultStopConditions ' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !CharacterScanner commentStamp: '' prior: 0! My instances hold the state associated with scanning text. My subclasses scan characters for specified purposes, such as computing a CharacterBlock or placing characters into Forms.! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:27'! addEmphasis: code "Set the bold-ital-under-strike emphasis." emphasisCode _ emphasisCode bitOr: code! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:27'! addKern: kernDelta "Set the current kern amount." kern _ kern + kernDelta! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 5/17/2000 17:13'! initializeFromParagraph: aParagraph clippedBy: clippingRectangle text _ aParagraph text. textStyle _ aParagraph textStyle. ! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:27'! setActualFont: aFont "Set the basal font to an isolated font reference." font _ aFont! ! !CharacterScanner methodsFor: 'private' stamp: 'di 9/3/2000 13:47'! setFont | priorFont | "Set the font and other emphasis." priorFont _ font. text == nil ifFalse:[ emphasisCode _ 0. kern _ 0. indentationLevel _ 0. alignment _ textStyle alignment. font _ nil. (text attributesAt: lastIndex forStyle: textStyle) do: [:att | att emphasizeScanner: self]]. font == nil ifTrue: [self setFont: textStyle defaultFontIndex]. font _ font emphasized: emphasisCode. priorFont ifNotNil: [destX _ destX + priorFont descentKern]. destX _ destX - font descentKern. "NOTE: next statement should be removed when clipping works" leftMargin ifNotNil: [destX _ destX max: leftMargin]. kern _ kern - font baseKern. "Install various parameters from the font." spaceWidth _ font widthOf: Space. xTable _ font xTable. map _ font characterToGlyphMap. stopConditions _ DefaultStopConditions.! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:28'! setFont: fontNumber "Set the font by number from the textStyle." self setActualFont: (textStyle fontAt: fontNumber)! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:28'! text: t textStyle: ts text _ t. textStyle _ ts! ! !CharacterScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:28'! textColor: ignored "Overridden in DisplayScanner"! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 5/19/2000 14:45'! characterNotInFont "Note: All fonts should have some sort of a character to glyph mapping. If a character is not in the font it should be mapped to the appropriate glyph (that is the glyph describing a non-existing character). If done correctly, this method should never be called. It is mainly provided for backward compatibility (and I'd really like to get rid of it - ar). All fonts have an illegal character to be used when a character is not within the font's legal range. When characters out of ranged are encountered in scanning text, then this special character indicates the appropriate behavior. The character is usually treated as a unary message understood by a subclass of CharacterScanner." | illegalAsciiString saveIndex stopCondition | saveIndex _ lastIndex. illegalAsciiString _ String with: (font maxAscii + 1) asCharacter. stopCondition _ self scanCharactersFrom: 1 to: 1 in: illegalAsciiString rightX: rightMargin stopConditions: stopConditions kern: kern. lastIndex _ saveIndex + 1. stopCondition ~= (stopConditions at: EndOfRun) ifTrue: [^self perform: stopCondition] ifFalse: [lastIndex = runStopIndex ifTrue: [^self perform: (stopConditions at: EndOfRun)]. ^false] ! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 18:20'! indentationLevel "return the number of tabs that are currently being placed at the beginning of each line" ^indentationLevel ifNil:[0]! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 1/8/2000 14:23'! indentationLevel: anInteger "set the number of tabs to put at the beginning of each line" indentationLevel _ anInteger! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 1/8/2000 14:23'! leadingTab "return true if only tabs lie to the left" line first to: lastIndex do: [:i | (text at: i) == Tab ifFalse: [^ false]]. ^ true! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 18:20'! placeEmbeddedObject: anchoredMorph "Place the anchoredMorph or return false if it cannot be placed. In any event, advance destX by its width." | w | destX _ destX + (w _ anchoredMorph width). (destX > rightMargin and: [(leftMargin + w) <= rightMargin]) ifTrue: ["Won't fit, but would on next line" ^ false]. lastIndex _ lastIndex + 1. self setFont. "Force recalculation of emphasis for next run" ^ true! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 5/23/2000 12:59'! primScanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta "Primitive. This is the inner loop of text display--but see scanCharactersFrom: to:rightX: which would get the string, stopConditions and displaying from the instance. March through source String from startIndex to stopIndex. If any character is flagged with a non-nil entry in stops, then return the corresponding value. Determine width of each character from xTable, indexed by map. If dextX would exceed rightX, then return stops at: 258. Advance destX by the width of the character. If stopIndex has been reached, then return stops at: 257. Optional. See Object documentation whatIsAPrimitive." | ascii nextDestX char | lastIndex _ startIndex. [lastIndex <= stopIndex] whileTrue: [char _ (sourceString at: lastIndex). ascii _ char asciiValue + 1. (stops at: ascii) == nil ifFalse: [^stops at: ascii]. "Note: The following is querying the font about the width since the primitive may have failed due to a non-trivial mapping of characters to glyphs or a non-existing xTable." nextDestX _ destX + (font widthOf: char). nextDestX > rightX ifTrue: [^stops at: CrossedX]. destX _ nextDestX + kernDelta. lastIndex _ lastIndex + 1]. lastIndex _ stopIndex. ^stops at: EndOfRun! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 5/19/2000 14:42'! scanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta "This method will perform text scanning with kerning." line first = startIndex ifTrue: [ "handle indentation" self indentationLevel timesRepeat: [ self tab ] ]. ^self primScanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta ! ! !CharacterScanner methodsFor: 'initialize' stamp: 'ar 5/17/2000 17:33'! initialize destX _ destY _ 0.! ! !CharacterScanner methodsFor: 'as yet unclassified' stamp: 'RAA 9/6/2000 13:31'! setYFor: anchoredMorph ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CharacterScanner class instanceVariableNames: ''! !CharacterScanner class methodsFor: 'class initialization' stamp: 'ar 5/18/2000 16:50'! initialize "CharacterScanner initialize" "NewCharacterScanner initialize" | stopConditions | stopConditions _ Array new: 258. stopConditions atAllPut: nil. stopConditions at: Space asciiValue + 1 put: nil. stopConditions at: Tab asciiValue + 1 put: #tab. stopConditions at: CR asciiValue + 1 put: #cr. stopConditions at: EndOfRun put: #endOfRun. stopConditions at: CrossedX put: #crossedX. DefaultStopConditions _ stopConditions.! ! !CharacterScanner class methodsFor: 'instance creation' stamp: 'ar 1/8/2000 15:00'! new ^super new initialize! ! Collection subclass: #CharacterSet instanceVariableNames: 'map ' classVariableNames: '' poolDictionaries: '' category: 'Collections-Text'! !CharacterSet commentStamp: '' prior: 0! A set of characters. Lookups for inclusion are very fast.! !CharacterSet methodsFor: 'collection ops' stamp: 'ls 8/17/1998 20:33'! add: aCharacter map at: aCharacter asciiValue+1 put: 1.! ! !CharacterSet methodsFor: 'collection ops' stamp: 'ls 8/17/1998 20:41'! do: aBlock "evaluate aBlock with each character in the set" Character allCharacters do: [ :c | (self includes: c) ifTrue: [ aBlock value: c ] ] ! ! !CharacterSet methodsFor: 'collection ops' stamp: 'ls 8/17/1998 20:31'! includes: aCharacter ^(map at: aCharacter asciiValue + 1) > 0! ! !CharacterSet methodsFor: 'collection ops' stamp: 'ls 8/17/1998 20:34'! remove: aCharacter map at: aCharacter asciiValue + 1 put: 0! ! !CharacterSet methodsFor: 'conversion' stamp: 'ls 8/17/1998 20:39'! complement "return a character set containing precisely the characters the receiver does not" | set | set _ CharacterSet allCharacters. self do: [ :c | set remove: c ]. ^set! ! !CharacterSet methodsFor: 'comparison' stamp: 'ls 8/17/1998 20:46'! = anObject ^self class == anObject class and: [ self byteArrayMap = anObject byteArrayMap ]! ! !CharacterSet methodsFor: 'comparison' stamp: 'ls 8/17/1998 20:46'! hash ^self byteArrayMap hash! ! !CharacterSet methodsFor: 'private' stamp: 'ls 8/17/1998 20:35'! byteArrayMap "return a ByteArray mapping each ascii value to a 1 if that ascii value is in the set, and a 0 if it isn't. Intended for use by primitives only" ^map! ! !CharacterSet methodsFor: 'private' stamp: 'ls 8/17/1998 20:30'! initialize map _ ByteArray new: 256 withAll: 0.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CharacterSet class instanceVariableNames: ''! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 8/17/1998 20:42'! allCharacters "return a set containing all characters" | set | set _ self empty. 0 to: 255 do: [ :ascii | set add: (Character value: ascii) ]. ^set! ! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 8/17/1998 20:36'! empty "return an empty set of characters" ^super new initialize! ! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 8/17/1998 20:31'! new ^super new initialize! ! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 1/3/1999 12:52'! newFrom: aCollection | newCollection | newCollection _ self new. newCollection addAll: aCollection. ^newCollection! ! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 8/18/1998 00:40'! nonSeparators "return a set containing everything but the whitespace characters" ^self separators complement! ! !CharacterSet class methodsFor: 'instance creation' stamp: 'ls 8/18/1998 00:40'! separators "return a set containing just the whitespace characters" | set | set _ self empty. set addAll: Character separators. ^set! ! SimpleButtonMorph subclass: #ChatButtonMorph instanceVariableNames: 'actionDownSelector actionUpSelector labelDown labelUp ' classVariableNames: '' poolDictionaries: '' category: 'Audio-Chat'! !ChatButtonMorph methodsFor: 'initialization' stamp: 'Tbp 4/11/2000 16:13'! setDefaultLabel self label: 'Flash'. ! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'Tbp 4/11/2000 16:25'! actionDownSelector: aSymbolOrString (nil = aSymbolOrString or: ['nil' = aSymbolOrString or: [aSymbolOrString isEmpty]]) ifTrue: [^actionDownSelector _ nil]. actionDownSelector _ aSymbolOrString asSymbol.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'Tbp 4/11/2000 16:27'! actionUpSelector: aSymbolOrString (nil = aSymbolOrString or: ['nil' = aSymbolOrString or: [aSymbolOrString isEmpty]]) ifTrue: [^ actionUpSelector _ nil]. actionUpSelector _ aSymbolOrString asSymbol.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'Tbp 4/11/2000 16:31'! labelDown: aString labelDown _ aString.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'Tbp 4/11/2000 16:32'! labelUp: aString labelUp _ aString! ! !ChatButtonMorph methodsFor: 'events' stamp: 'Tbp 4/11/2000 16:19'! doButtonDownAction (target ~~ nil and: [actionDownSelector ~~ nil]) ifTrue: [ Cursor normal showWhile: [ target perform: actionDownSelector]].! ! !ChatButtonMorph methodsFor: 'events' stamp: 'Tbp 4/11/2000 16:20'! doButtonUpAction (target ~~ nil and: [actionUpSelector ~~ nil]) ifTrue: [ Cursor normal showWhile: [ target perform: actionUpSelector]].! ! !ChatButtonMorph methodsFor: 'events' stamp: 'RAA 8/6/2000 18:30'! mouseDown: evt oldColor _ color. self label: labelDown. self doButtonDownAction. ! ! !ChatButtonMorph methodsFor: 'events' stamp: 'RAA 8/6/2000 18:37'! mouseUp: evt "if oldColor nil, it signals that mouse had not gone DOWN inside me, e.g. because of a cmd-drag; in this case we want to avoid triggering the action!!" oldColor ifNil: [^self]. self color: oldColor. (self containsPoint: evt cursorPoint) ifTrue: [ self label: labelUp. self doButtonUpAction. ]. ! ! StringHolder subclass: #ChatNotes instanceVariableNames: 'name notesIndex names notes recorder player sound isPlaying isRecording isSaving nameTextMorph ' classVariableNames: '' poolDictionaries: '' category: 'Audio-Chat'! !ChatNotes methodsFor: 'accessing' stamp: 'RAA 8/1/2000 17:59'! name ^name ifNil: [name _ '']! ! !ChatNotes methodsFor: 'accessing' stamp: 'TBP 2/23/2000 21:07'! name: aString name _ aString. self changed: #name.! ! !ChatNotes methodsFor: 'accessing' stamp: 'RAA 8/1/2000 18:01'! notesList self flag: #why. ^names copy asArray! ! !ChatNotes methodsFor: 'accessing' stamp: 'RAA 8/1/2000 18:02'! notesListIndex ^notesIndex ifNil: [notesIndex _ 0]! ! !ChatNotes methodsFor: 'accessing' stamp: 'RAA 8/1/2000 18:02'! notesListIndex: index notesIndex _ index = notesIndex ifTrue: [0] ifFalse: [index]. self name: (self notesList at: notesIndex ifAbsent: ['']). self changed: #notesListIndex.! ! !ChatNotes methodsFor: 'accessing' stamp: 'TBP 2/23/2000 21:07'! recorder ^recorder! ! !ChatNotes methodsFor: 'button commands' stamp: 'RAA 8/1/2000 19:05'! record self isRecording: true. notesIndex = 0 ifFalse: [self notesListIndex: 0]. sound _ nil. recorder clearRecordedSound. recorder resumeRecording.! ! !ChatNotes methodsFor: 'button commands' stamp: 'RAA 8/1/2000 18:03'! save self isSaving: true. notesIndex = 0 ifTrue: [self saveSound] ifFalse: [self saveName]. self isSaving: false.! ! !ChatNotes methodsFor: 'button commands' stamp: 'TBP 2/23/2000 21:07'! stop recorder pause. self isRecording: false! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:03'! isPlaying ^isPlaying ifNil: [isPlaying _ false]! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:04'! isPlaying: aBoolean isPlaying = aBoolean ifTrue: [^self]. isPlaying _ aBoolean. self changed: #isPlaying ! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:04'! isRecording ^isRecording ifNil: [isRecording _ false]! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:05'! isRecording: aBoolean isRecording = aBoolean ifTrue: [^self]. isRecording _ aBoolean. self changed: #isRecording ! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:05'! isSaving ^isSaving ifNil: [isSaving _ false]! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:05'! isSaving: aBoolean isSaving = aBoolean ifTrue: [^self]. isSaving _ aBoolean. self changed: #isSaving! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:05'! isStopped ^false! ! !ChatNotes methodsFor: 'initialization' stamp: 'RAA 8/1/2000 18:05'! initialize self loadNotes. notesIndex _ 0. recorder _ ChatRecorder new. recorder initialize.! ! !ChatNotes methodsFor: 'initialization' stamp: 'RAA 8/1/2000 18:09'! loadNotes "Load notes from the files" | dir | names _ OrderedCollection new. notes _ OrderedCollection new. (FileDirectory default directoryExists: 'audio') ifFalse: [FileDirectory default createDirectory: 'audio']. dir _ self audioDirectory. dir fileNames do: [:fname | (fname endsWith: '.name') ifTrue: [ names add: ((dir fileNamed: fname) contentsOfEntireFile). notes add: (fname copyFrom: 1 to: (fname size - 4))]].! ! !ChatNotes methodsFor: 'initialization' stamp: 'RAA 8/2/2000 01:15'! openAsMorph | window aColor recordButton stopButton playButton saveButton | window _ (SystemWindow labelled: 'Audio Notes') model: self. window addMorph: ( (PluggableListMorph on: self list: #notesList selected: #notesListIndex changeSelected: #notesListIndex: menu: #notesMenu: ) autoDeselect: false) frame: (0@0 corner: 0.5@1.0). nameTextMorph _ PluggableTextMorph on: self text: #name accept: nil. nameTextMorph askBeforeDiscardingEdits: false. window addMorph: nameTextMorph frame: (0.5@0 corner: 1.0@0.4). aColor _ Color colorFrom: self defaultBackgroundColor. (recordButton _ PluggableButtonMorph on: self getState: #isRecording action: #record) label: 'record'; askBeforeChanging: true; color: aColor; onColor: aColor darker offColor: aColor. window addMorph: recordButton frame: (0.5@0.4 corner: 0.75@0.7). (stopButton _ PluggableButtonMorph on: self getState: #isStopped action: #stop) label: 'stop'; askBeforeChanging: true; color: aColor; onColor: aColor darker offColor: aColor. window addMorph: stopButton frame: (0.75@0.4 corner: 1.0@0.7). (playButton _ PluggableButtonMorph on: self getState: #isPlaying action: #play) label: 'play'; askBeforeChanging: true; color: aColor; onColor: aColor darker offColor: aColor. window addMorph: playButton frame: (0.5@0.7 corner: 0.75@1.0). (saveButton _ PluggableButtonMorph on: self getState: #isSaving action: #save) label: 'save'; askBeforeChanging: true; color: aColor; onColor: aColor darker offColor: aColor. window addMorph: saveButton frame: (0.75@0.7 corner: 1.0@1.0). window openInWorld.! ! !ChatNotes methodsFor: 'morphic' stamp: 'TBP 2/23/2000 21:07'! defaultBackgroundColor "In a better design, this would be handled by preferences." ^Color r: 1.0 g: 0.7 b: 0.8! ! !ChatNotes methodsFor: 'morphic' stamp: 'TBP 2/23/2000 21:07'! initialExtent "Nice and small--that was the idea. It shouldn't take up much screen real estate." ^200@100! ! !ChatNotes methodsFor: 'morphic' stamp: 'TBP 2/23/2000 21:07'! notesMenu: aMenu "Simple menu to delete notes" ^(notesIndex = 0) ifTrue: [aMenu labels: 'update notes' lines: #() selections: #(updateNotes)] ifFalse: [aMenu labels: ('delete', String cr, 'update notes') lines: #() selections: #(deleteSelection updateNotes)]! ! !ChatNotes methodsFor: 'morphic' stamp: 'RAA 8/2/2000 01:11'! textMorphString ^nameTextMorph text string! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/1/2000 18:08'! audioDirectory ^FileDirectory default directoryNamed: 'audio'! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/1/2000 18:08'! deleteSelection "Delete the selection in the list" | dir | notesIndex <= 0 ifTrue: [^self]. dir _ self audioDirectory. dir deleteFileNamed: ((notes at: notesIndex), 'name') ifAbsent: []. dir deleteFileNamed: ((notes at: notesIndex), 'aiff') ifAbsent: []. names removeAt: notesIndex. notes removeAt: notesIndex. self notesListIndex: 0. self changed: #notesList. self changed: #name.! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/1/2000 18:09'! getNextName "Return the next name available. All names are of the form '#.name' and '#.aiff'." | dir num | dir _ self audioDirectory. num _ 1. [dir fileExists: (num asString, '.name')] whileTrue: [num _ num + 1]. ^(num asString, '.')! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/1/2000 19:05'! play | separator | self isPlaying: true. notesIndex = 0 ifTrue: [ recorder pause. recorder playback. self isPlaying: false. ^self ]. separator _ FileDirectory pathNameDelimiter asString. sound _ (AIFFFileReader new readFromFile: ( FileDirectory default pathName, separator, 'audio', separator, (notes at: notesIndex), 'aiff')) sound. [ sound playAndWaitUntilDone. self isPlaying: false ] fork! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/2/2000 01:09'! saveName "Save the name to the '.name' file." | dir file | self name: self textMorphString. dir _ self audioDirectory. file _ (notes at: notesIndex), 'name'. (dir fileExists: file) ifTrue: [dir deleteFileNamed: file]. file _ dir newFileNamed: file. file nextPutAll: name. file close. names at: notesIndex put: name. self changed: #notesList.! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/2/2000 01:09'! saveSound "Move the sound from the recorder to the files." | fname file | recorder recordedSound ifNil: [^self]. self isSaving: true. fname _ self getNextName. "Create .name file" file _ self audioDirectory newFileNamed: (fname, 'name'). file nextPutAll: self textMorphString. file close. "Create .aiff file" file _ (self audioDirectory newFileNamed: (fname, 'aiff')) binary. self storeAIFFOnFile: file. file close. "Add to names and notes" names add: self textMorphString. notes add: fname. self changed: #notesList. self notesListIndex: (notes size). "Clear Recorder" recorder _ SoundRecorder new. "Stop Button" self isSaving: false! ! !ChatNotes methodsFor: 'file i/o' stamp: 'RAA 8/1/2000 18:12'! storeAIFFOnFile: file "In a better design, this would be handled by SequentialSound, but I figure you will need a new primitive anyway, so it can be implemented at that time." | sampleCount s | sampleCount _ recorder recordedSound sounds inject: 0 into: [ :sum :rsound | sum + rsound samples monoSampleCount ]. file nextPutAll: 'FORM' asByteArray. file nextInt32Put: (2 * sampleCount) + 46. file nextPutAll: 'AIFF' asByteArray. file nextPutAll: 'COMM' asByteArray. file nextInt32Put: 18. file nextNumber: 2 put: 1. "channels" file nextInt32Put: sampleCount. file nextNumber: 2 put: 16. "bits/sample" (AbstractSound new) storeExtendedFloat: (recorder samplingRate) on: file. file nextPutAll: 'SSND' asByteArray. file nextInt32Put: (2 * sampleCount) + 8. file nextInt32Put: 0. file nextInt32Put: 0. (recorder recordedSound sounds) do: [:rsound | 1 to: (rsound samples monoSampleCount) do: [:i | s _ rsound samples at: i. file nextPut: ((s bitShift: -8) bitAnd: 16rFF). file nextPut: (s bitAnd: 16rFF)]].! ! !ChatNotes methodsFor: 'file i/o' stamp: 'TBP 2/23/2000 21:07'! updateNotes "Probably not necessary unless several audio notes are open at the same time" "Clear Notes" self loadNotes. self changed: #notesList. self notesListIndex: 0. self name: ''.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChatNotes class instanceVariableNames: ''! !ChatNotes class methodsFor: 'instance creation' stamp: 'RAA 8/2/2000 01:06'! openAsMorph ^self new openAsMorph! ! ServerAction subclass: #ChatPage instanceVariableNames: 'current ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !ChatPage commentStamp: '' prior: 0! Simple ServerAction that allows a primitive chat session shown as a web page. It maintains a list of the 20 most recent submissions. There is a form on the page for typing your contribution to the session. The default Swiki has a chat page enabled. Get to the page by this URL: machine:80/chat! !ChatPage methodsFor: 'chat processing' stamp: 'mjg 11/25/97 13:33'! add: aMessage current isNil ifTrue: [current _ OrderedCollection new]. current add: aMessage. (current size > 20) ifTrue: [current _ current copyFrom: (current size - 20) to: (current size)]! ! !ChatPage methodsFor: 'chat processing' stamp: 'mjg 11/17/97 13:32'! current ^current ! ! !ChatPage methodsFor: 'URL processing' stamp: 'mjg 11/25/97 13:34'! process: request | author note | request fields isNil ifTrue: [current isNil ifTrue: [current _ OrderedCollection new]. request reply: (HTMLformatter evalEmbedded: (self fileContents: 'chat.html') with: current)] ifFalse: [author _ request fields at: 'author'. note _ request fields at: 'note'. self add: '' , author , ' ' , Time now printString , '-' , Date today printString , '

' , note , '

'. request fields at: 'current' put: current. request reply: (HTMLformatter evalEmbedded: (self fileContents: 'chat.html') with: request)]! ! SoundRecorder subclass: #ChatRecorder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Audio-Chat'! !ChatRecorder methodsFor: 'accessing' stamp: 'TBP 2/23/2000 20:54'! recordedSound: aSound self clearRecordedSound. recordedSound _ aSound.! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'RAA 8/11/2000 09:47'! initialize "setting a higher desired recording rate seemed to fix certain powerbook problems. I'm still trying to understand it all, but there it is for now" super initialize. samplingRate _ 44100. ! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'RAA 8/13/2000 11:34'! pause "Go into pause mode. The record level continues to be updated, but no sound is recorded." paused _ true. ((currentBuffer ~~ nil) and: [nextIndex > 1]) ifTrue: [self emitPartialBuffer. self allocateBuffer]. soundPlaying ifNotNil: [ soundPlaying pause. soundPlaying _ nil]. self stopRecording. "Preferences canRecordWhilePlaying ifFalse: [self stopRecording]." ! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'RAA 8/13/2000 11:34'! playback "Playback the sound that has been recorded." self pause. soundPlaying _ self recordedSound ifNil: [^self]. soundPlaying play. ! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'RAA 8/13/2000 11:38'! resumeRecording "Continue recording from the point at which it was last paused." self startRecording. paused _ false. ! ! EllipseMorph subclass: #ChineseCheckerPiece instanceVariableNames: 'boardLoc myBoard ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !ChineseCheckerPiece commentStamp: '' prior: 0! I represent a player piece for Chinese Checkers. Mostly I act as an ellipse, but my special methods ensure that I cannot be picked up or dropped except in the proper circumstances. Structure: myBoard a ChineseCheckers morph boardLoc my current logical position on the board. ! !ChineseCheckerPiece methodsFor: 'all' stamp: 'di 4/9/2000 08:31'! boardLoc ^ boardLoc! ! !ChineseCheckerPiece methodsFor: 'all' stamp: 'di 4/11/2000 08:36'! handlesMouseDown: evt ^ true! ! !ChineseCheckerPiece methodsFor: 'all' stamp: 'ar 10/5/2000 20:02'! justDroppedInto: newOwner event: evt newOwner == myBoard ifFalse: ["Only allow dropping into my board." ^self rejectDropMorphEvent: evt]. ^super justDroppedInto: newOwner event: evt! ! !ChineseCheckerPiece methodsFor: 'all' stamp: 'di 4/9/2000 09:27'! mouseDown: evt ((owner isKindOf: ChineseCheckers) and: [owner okToPickUpPieceAt: boardLoc]) ifTrue: [evt hand grabMorph: self]! ! !ChineseCheckerPiece methodsFor: 'all' stamp: 'di 4/11/2000 08:34'! setBoard: aBoard loc: aBoardLoc myBoard _ aBoard. boardLoc _ aBoardLoc! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChineseCheckerPiece class instanceVariableNames: ''! !ChineseCheckerPiece class methodsFor: 'as yet unclassified' stamp: 'di 4/9/2000 11:17'! includeInNewMorphMenu ^ false! ! BorderedMorph subclass: #ChineseCheckers instanceVariableNames: 'board sixDeltas teams homes autoPlay whoseMove plannedMove plannedMovePhase colors movePhase animateMoves pathMorphs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !ChineseCheckers commentStamp: '' prior: 0! An implementation of Chinese Checkers by Dan Ingalls. April 9, 2000. board: A 19x19 rhombic array, addressed by row@col points, in which is imbedded the familiar six-pointed layout of cells. A cell outside the board is nil (-). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5 - - - - - - - - - - - - - - - - - 5 5 - - - - - - - - - - - - - - - - 5 5 5 - - - - - - - - - - - - - - - 5 5 5 5 - - - - - - - - - - 6 6 6 6 0 0 0 0 0 4 4 4 4 - - - - - - 6 6 6 0 0 0 0 0 0 4 4 4 - - - - - - - 6 6 0 0 0 0 0 0 0 4 4 - - - - - - - - 6 0 0 0 0 0 0 0 0 4 - - - - - - - - - 0 0 0 0 0 0 0 0 0 - - - - - - - - - 1 0 0 0 0 0 0 0 0 3 - - - - - - - - 1 1 0 0 0 0 0 0 0 3 3 - - - - - - - 1 1 1 0 0 0 0 0 0 3 3 3 - - - - - - 1 1 1 1 0 0 0 0 0 3 3 3 3 - - - - - - - - - - 2 2 2 2 - - - - - - - - - - - - - - - 2 2 2 - - - - - - - - - - - - - - - - 2 2 - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cells within the board contain 0 if empty, or a team number (1..6) if occupied by a piece of that team. An extra border of nils around the whole reduces bounds checking to a nil test. sixDeltas: An array giving the x@y deltas for the 6 valid steps in CCW order from a given cell. For team 1 they are: in fr, fl, l, bl, br, r. To get, eg fl for a given team, use (sixDeltas atWrap: team+1). teams: An array of six teams, each of which is an array of the x@y locations of the 10 pieces. homes: The x@y coordinates of the six home points, namely 14@2, 18@6, 14@14, 6@18, 2@14, 6@6. The goal, or farthest point in destination triangle, is thus (homes atWrap: teamNo+3). autoPlay: An array of booleans, parallel to teams, where true means that Squeak will make the moves for the corresponding team. whoseMove: A team number specifying whose turn it is next. Set to 0 when game is over. plannedMove: If not nil, it means the board is in a state where it is animating the next move to be made so that it can be seen. movePhase: Holds the state of display of the planned move so that, eg, it can appear one jump at a time. Advances from 1 to (plannedMove size * 2). A move is an array of locs which are the path of the move. Once the morph is open, the menu command 'reset...' allows you to reset the board and change the number of players. The circle at turnIndicatorLoc indicates the color of the team whose turn it is. If it is a human, play waits for drag and drop of a piece of that color. The current strategy is very simple: generate all moves, score them and pick the best. Beyond this, it will look ahead a number of moves, but this becomes very expensive without pruning. Pruning would help the speed of play, especially in the end game where we look a little deeper. A more effective strategy would consider opponents' possible moves as well, but this is left as an exercise for the serious programmer.! !ChineseCheckers methodsFor: 'initialization'! board: b teams: t board := b. teams := t! ! !ChineseCheckers methodsFor: 'initialization' stamp: 'di 4/9/2000 20:55'! copyBoard "Return a copy of the board for the purpose of looking ahead one or more moves." ^ self copy board: (board collect: [:row | row copy]) teams: (teams collect: [:team | team copy])! ! !ChineseCheckers methodsFor: 'initialization' stamp: 'di 4/13/2000 13:35'! initialize "Default creation is for one person against Squeak." super initialize. self extent: 382@413. self color: (Color r: 0.6 g: 0.4 b: 0.0). self borderWidth: 2. animateMoves _ true. self teams: #(2 5) autoPlay: {false. true}. ! ! !ChineseCheckers methodsFor: 'initialization' stamp: 'di 4/12/2000 23:44'! teams: teamsPlaying autoPlay: ifAuto "Initialize board, teams, steps, jumps" | p q teamInPlay | colors _ (#(gray) , #(red green blue cyan magenta yellow white) shuffled) collect: [:c | Color perform: c]. "New set of colors each time." self removeAllMorphs. "eg, from previous game." board := (1 to: 19) collect: [:i | Array new: 19]. sixDeltas := {0@1. -1@1. -1@0. 0@-1. 1@-1. 1@0}. homes := {14@2. 18@6. 14@14. 6@18. 2@14. 6@6}. teams := (1 to: 6) collect: [:i | OrderedCollection new]. autoPlay := (1 to: 6) collect: [:i | false]. 1 to: 6 do: [:team | p:= homes at: team. (teamInPlay := teamsPlaying includes: team) ifTrue: [autoPlay at: team put: (ifAuto at: (teamsPlaying indexOf: team))]. "Place empty cells in rhombus extending out from each home, and occupied cells in active home triangles." 1 to: 5 do: [:i | q := p. 1 to: 5 do: [:j | (teamInPlay and: [j <= (5 - i)]) ifTrue: [self at: q put: team. (teams at: team) add: q. self addMorph: ((ChineseCheckerPiece newBounds: ((self cellPointAt: q) extent: self pieceSize) color: (colors at: team+1)) setBoard: self loc: q)] ifFalse: [self at: q put: 0]. q := q + (sixDeltas at: team). "right,forward"]. p := p + (sixDeltas atWrap: team+1). "left,forward"]. teams at: team put: (teams at: team) asArray]. whoseMove _ teamsPlaying first. self addMorph: ((ChineseCheckerPiece newBounds: ((self cellPointAt: self turnIndicatorLoc) extent: self pieceSize) color: (colors at: whoseMove+1)) setBoard: self loc: self turnIndicatorLoc). plannedMove _ nil. self changed! ! !ChineseCheckers methodsFor: 'board geometry'! at: p ^ (board at: p x) at: p y! ! !ChineseCheckers methodsFor: 'board geometry'! at: p put: x ^ (board at: p x) at: p y put: x! ! !ChineseCheckers methodsFor: 'board geometry'! boardCenter ^ 10@10! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 4/9/2000 10:00'! boardLocAt: cellPoint | dx dy row col | dx _ self width/15.0. dy _ dx * 0.8660254037844385 "(Float pi / 3) sin". row _ (cellPoint y - self position y) // dy + 1. col _ (cellPoint x - self position x) / (dx/2.0) + 16 - row // 2. ^ row @ col! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 4/11/2000 17:18'! cellPointAt: boardLoc | dx dy row col | dx _ self width/15.0. dy _ dx * 0.8660254037844385 "(Float pi / 3) sin". row _ boardLoc x. col _ boardLoc y. ^ self position + ((col*2+row-16*dx//2)@(row-1*dy)) asIntegerPoint! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 3/13/2000 19:50'! distFrom: a to: b "The six possible moves are: 1@0, 1@-1, 0@1, 0@-1, -1@0, -1@1." | dx dy | dx _ b x - a x. dy _ b y - a y. dx abs >= dy abs ifTrue: ["Major change is in x-coord..." dx >= 0 ifTrue: [(dy between: (0-dx) and: 0) ifTrue: [^ dx "no lateral motion"]. ^ dx + ((0-dx) - dy max: dy - 0) "added lateral dist"] ifFalse: ["Reverse sign and rerun same code" ^ self distFrom: b to: a]] ifFalse: ["Transpose and re-run same code" ^ self distFrom: a transposed to: b transposed]! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 4/11/2000 09:21'! extent: newExtent | extraY | extraY _ (newExtent x / 15.0 * 1.25) asInteger. super extent: (newExtent x) @ (newExtent x + extraY). self submorphsDo: [:m | (m isKindOf: ChineseCheckerPiece) ifTrue: [m position: (self cellPointAt: m boardLoc); extent: self pieceSize]]! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 4/11/2000 09:20'! pieceSize ^ self width asPoint // 20! ! !ChineseCheckers methodsFor: 'board geometry' stamp: 'di 4/9/2000 09:44'! turnIndicatorLoc ^ 16@11! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/13/2000 14:18'! allMovesFrom: boardLoc "boardLoc must be occupied" | team stepMoves jumpDict | team := self at: boardLoc. stepMoves := (sixDeltas collect: [:d | boardLoc + d]) select: [:p | (self at: p) notNil and: [(self at: p) = 0]]. jumpDict := Dictionary new. jumpDict at: boardLoc put: (Array with: boardLoc). self jumpFor: team from: boardLoc havingVisited: jumpDict. jumpDict removeKey: boardLoc. ^ (stepMoves collect: [:p | {boardLoc. p}]) , jumpDict values reject: [:move | "Don't include any moves that land in other homes." (self distFrom: move last to: self boardCenter) >= 5 "In a home..." and: [(self distFrom: move last to: (homes atWrap: team+3)) > 3 "...not my goal..." and: [(self distFrom: move last to: (homes at: team)) > 3 "...nor my home"]]]! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/12/2000 23:23'! bestMove: ply forTeam: team | score bestScore bestMove | bestScore := -999. (teams at: team) do: [:boardLoc | (self allMovesFrom: boardLoc) do: [:move | score := self score: move for: team. (score > -99 and: [ply > 0]) ifTrue: [score := score "Add 0.7 * score of next move (my guess)" + (0 max: ((self score: ((self copyBoard makeMove: move) bestMove: ply - 1 forTeam: team) for: team) * 0.7))]. score > bestScore ifTrue: [bestScore := score. bestMove := move]]]. ^ bestMove! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/10/2000 08:27'! checkDoneAfter: move | team locsAfterMove | (team := self at: move first) = 0 ifTrue: [^ false]. (locsAfterMove _ (teams at: team) copy) replaceAll: move first with: move last. ^ self testDone: locsAfterMove for: team! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/12/2000 23:40'! endGameFor: team "Return true if we are in the end game (all players within 1 of home triangle)." | goalLoc | goalLoc _ homes atWrap: team+3. "Farthest cell across the board" (teams at: team) do: [:boardLoc | (self distFrom: boardLoc to: goalLoc) > 4 ifTrue: [^ false]]. ^ true! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/12/2000 20:36'! jumpFor: team from: loc havingVisited: dict "Recursively explore all jumps from loc, leaving in dict the prior position from which we got there" "Fasten seatbelts..." ((((sixDeltas collect: [:d | loc + d]) select: [:p | (self at: p) notNil and: [(self at: p) > 0]]) collect: [:p | p + (p - loc)]) select: [:p | (self at: p) notNil and: [(self at: p) = 0]]) do: [:p | (dict includesKey: p) ifFalse: [dict at: p put: ((dict at: loc) copyWith: p). self jumpFor: team from: p havingVisited: dict]]! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/10/2000 08:17'! makeMove: move | team | team := self at: move first. self at: move last put: team. self at: move first put: 0. (teams at: team) replaceAll: move first with: move last! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/13/2000 14:21'! score: move for: team "Return the decrease in distance toward this team's goal" | goal closerToGoal wasBack nowBack | goal _ homes atWrap: team+3. wasBack _ self distFrom: move first to: goal. nowBack _ self distFrom: move last to: goal. closerToGoal _ wasBack - nowBack. closerToGoal < -1 ifTrue: [^ -99]. "Quick rejection if move backward more than 1" (nowBack <= 3 and: [self checkDoneAfter: move]) ifTrue: [^ 999]. "Reward closerToGoal, but add bias to move those left far behind." ^ (closerToGoal*5) + wasBack! ! !ChineseCheckers methodsFor: 'moves' stamp: 'di 4/12/2000 23:40'! testDone: teamLocs for: team "Return true if we are done (all players in home triangle)." | goalLoc | goalLoc _ homes atWrap: team+3. teamLocs do: [:boardLoc | (self distFrom: boardLoc to: goalLoc) > 3 ifTrue: [^ false]]. ^ true! ! !ChineseCheckers methodsFor: 'drag and drop' stamp: 'ar 10/5/2000 19:23'! acceptDroppingMorph: aPiece event: evt | dropLoc | super acceptDroppingMorph: aPiece event: evt. dropLoc _ self boardLocAt: evt cursorPoint. dropLoc = aPiece boardLoc ifTrue: "Null move" [^ aPiece rejectDropMorphEvent: evt]. (plannedMove _ (self allMovesFrom: aPiece boardLoc) detect: [:move | move last = dropLoc] ifNone: [nil]) ifNil: [^ aPiece rejectDropMorphEvent: evt. "Not a valid move"]. movePhase _ 1. "Start the animation if any." ! ! !ChineseCheckers methodsFor: 'drag and drop' stamp: 'di 4/8/2000 23:45'! okToPickUpPieceAt: boardLoc ^ (self at: boardLoc) = whoseMove and: [(autoPlay at: whoseMove) not]! ! !ChineseCheckers methodsFor: 'drag and drop' stamp: 'di 4/9/2000 08:30'! pieceAt: boardLoc self submorphsDo: [:m | ((m isMemberOf: ChineseCheckerPiece) and: [m boardLoc = boardLoc]) ifTrue: [^ m]]. ^ nil! ! !ChineseCheckers methodsFor: 'drag and drop' stamp: 'di 4/9/2000 10:44'! wantsDroppedMorph: aPiece event: evt ^ aPiece isKindOf: ChineseCheckerPiece ! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 13:23'! addCustomMenuItems: aCustomMenu hand: aHandMorph "Include our modest command set in the ctrl-menu" super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. self addMenuItemsTo: aCustomMenu hand: aHandMorph! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 14:01'! addMenuItemsTo: aMenu hand: aHandMorph aMenu add: 'new game' target: self action: #newGame. aMenu add: 'reset...' target: self action: #reset. animateMoves ifTrue: [aMenu add: 'don''t animate moves' target: self action: #dontAnimateMoves] ifFalse: [aMenu add: 'animate moves' target: self action: #animateMoves] ! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 13:36'! animateMoves animateMoves _ true! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 13:36'! dontAnimateMoves animateMoves _ false! ! !ChineseCheckers methodsFor: 'menu' stamp: 'sma 4/30/2000 09:23'! handlesMouseDown: evt "Prevent stray clicks from picking up the whole game in MVC." ^ Smalltalk isMorphic not or: [evt yellowButtonPressed]! ! !ChineseCheckers methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:57'! mouseDown: evt | menu | evt yellowButtonPressed ifFalse: [^ self]. menu _ MenuMorph new defaultTarget: self. self addMenuItemsTo: menu hand: evt hand. menu popUpEvent: evt in: self world. ! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 13:32'! newGame "Reset the board, with same teams." | teamNumbers | teamNumbers _ (1 to: 6) reject: [:i | (teams at: i) isEmpty]. self teams: teamNumbers autoPlay: (teamNumbers collect: [:i | autoPlay at: i]). ! ! !ChineseCheckers methodsFor: 'menu' stamp: 'di 4/13/2000 13:31'! reset "Reset the board, choosing anew how many teams." | nPlayers nHumans | nPlayers _ (SelectionMenu selections: (1 to: 6)) startUpWithCaption: 'How many players?'. nPlayers ifNil: [nPlayers _ 2]. nHumans _ (SelectionMenu selections: (0 to: nPlayers)) startUpWithCaption: 'How many humans?'. nHumans ifNil: [nHumans _ 1]. self teams: (#((1) (2 5) (2 4 6) (1 2 4 5) (1 2 3 4 6) (1 2 3 4 5 6)) at: nPlayers) autoPlay: ((1 to: nPlayers) collect: [:i | i > nHumans]). ! ! !ChineseCheckers methodsFor: 'display' stamp: 'di 8/10/2000 09:40'! drawOn: aCanvas | row1 row2 offset dotExtent | super drawOn: aCanvas. "Draw square board" "Only draw rows in the clipping region" dotExtent _ (self width//25) asPoint. offset _ self pieceSize - dotExtent + 1 // 2. "Offset of smaller dots rel to larger" row1 _ (self boardLocAt: aCanvas clipRect topLeft) x max: 1. row2 _ (self boardLocAt: aCanvas clipRect bottomRight) x min: board size. row1 to: row2 do: [:row | (board at: row) doWithIndex: [:cell :i | cell ifNotNil: [aCanvas fillOval: ((self cellPointAt: (row@i)) + offset extent: dotExtent) color: (colors at: cell+1)]]]! ! !ChineseCheckers methodsFor: 'display' stamp: 'di 4/10/2000 11:29'! printOn: s "For testing only" 1 to: board size do: [:row | s cr; next: row put: $ . (board at: row) do: [:cell | s space; nextPut: (cell == nil ifTrue: [$-] ifFalse: [cell printString last])]]! ! !ChineseCheckers methodsFor: 'game sequence' stamp: 'di 4/13/2000 14:25'! nextTurn (self testDone: (teams at: whoseMove) for: whoseMove) ifTrue: [(self pieceAt: self turnIndicatorLoc) extent: self width asPoint//6; borderWidth: 2. ^ whoseMove _ 0. "Game over."]. [whoseMove _ whoseMove\\6 + 1. (teams at: whoseMove) isEmpty] "Turn passes to the next player" whileTrue: []. (self pieceAt: self turnIndicatorLoc) color: (colors at: whoseMove+1)! ! !ChineseCheckers methodsFor: 'game sequence' stamp: 'di 4/11/2000 08:35'! showNextMoveSegment "Display the current move in progress. Starts with movePhase = 1. Increments movePhase at each tick. Ends by setting movePhase to 0." | dot p1 p2 delta secondPhase line | delta _ self width//40. movePhase <= plannedMove size ifTrue: ["First we trace the move with dots and lines..." movePhase = 1 ifTrue: [pathMorphs _ OrderedCollection new]. p1 _ self cellPointAt: (plannedMove at: movePhase). dot _ (ImageMorph new image: (Form dotOfSize: 7)) position: p1 + delta - (7//2). self addMorph: dot. pathMorphs addLast: dot. movePhase > 1 ifTrue: [p2 _ self cellPointAt: (plannedMove at: movePhase-1). line _ PolygonMorph vertices: {p2 + delta. p1 + delta} color: Color black borderWidth: 3 borderColor: Color black. self addMorph: line. pathMorphs addLast: line]] ifFalse: ["...then we erase the path while moving the piece." secondPhase _ movePhase - plannedMove size. pathMorphs removeFirst delete. secondPhase > 1 ifTrue: [pathMorphs removeFirst delete. self makeMove: {plannedMove at: secondPhase - 1. plannedMove at: secondPhase}. (self pieceAt: (plannedMove at: secondPhase - 1)) position: (self cellPointAt: (plannedMove at: secondPhase)); setBoard: self loc: (plannedMove at: secondPhase). self changed]]. (movePhase _ movePhase + 1) > (plannedMove size * 2) ifTrue: [movePhase _ 0 "End of animated move"]. ! ! !ChineseCheckers methodsFor: 'game sequence' stamp: 'di 4/13/2000 13:52'! step whoseMove = 0 ifTrue: [^ self]. "Game over." plannedMove == nil ifTrue: [(autoPlay at: whoseMove) ifFalse: [^ self]. "Waiting for a human." (self endGameFor: whoseMove) "Look deeper at the end." ifTrue: [plannedMove _ self bestMove: 2 forTeam: whoseMove] ifFalse: [plannedMove _ self bestMove: 1 forTeam: whoseMove]. movePhase _ 1. "Start the animated move"]. animateMoves ifTrue: ["Display the move in phases..." movePhase > 0 ifTrue: [^ self showNextMoveSegment]] ifFalse: ["... or skip the entire animated move if requested." self makeMove: plannedMove. (self pieceAt: plannedMove first) position: (self cellPointAt: plannedMove last); setBoard: self loc: plannedMove last. self changed. movePhase _ 0]. plannedMove _ nil. "End the animated move" self nextTurn! ! !ChineseCheckers methodsFor: 'game sequence' stamp: 'di 4/12/2000 23:43'! stepTime ^ 200! ! WordGamePanelMorph subclass: #CipherPanel instanceVariableNames: 'originalText quote originalMorphs decodingMorphs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !CipherPanel commentStamp: '' prior: 0! The CipherPanel, as its name suggests, is a tool for decoding simple substitution codes, such as are presented on the puzzle pages of many Sunday newspapers. Most of the capability is inherited from the two WordGame classes used. To try it out, choose newMorph/Games/CipherPanel in a morphic project, or execute, in any project: CipherPanel new openInWorld ! !CipherPanel methodsFor: 'initialization' stamp: 'di 5/12/2000 00:53'! encodedQuote: aString "World addMorph: CipherPanel test" | morph prev | haveTypedHere _ false. quote _ aString asUppercase. prev _ nil. originalMorphs _ quote asArray collectWithIndex: [:c :i | WordGameLetterMorph new plain indexInQuote: i id1: nil; setLetter: (quote at: i)]. letterMorphs _ OrderedCollection new. decodingMorphs _ quote asArray collectWithIndex: [:c :i | (quote at: i) isLetter ifTrue: [morph _ WordGameLetterMorph new underlined indexInQuote: i id1: nil. morph on: #mouseDown send: #mouseDownEvent:letterMorph: to: self. morph on: #keyStroke send: #keyStrokeEvent:letterMorph: to: self. letterMorphs addLast: morph. morph predecessor: prev. prev ifNotNil: [prev successor: morph]. prev _ morph] ifFalse: [WordGameLetterMorph new plain indexInQuote: i id1: nil; setLetter: (quote at: i)]]. self color: originalMorphs first color. self extent: 500@500 ! ! !CipherPanel methodsFor: 'initialization' stamp: 'di 11/23/2000 19:36'! extent: newExtent "Lay out with word wrap, alternating bewteen decoded and encoded lines." "Currently not tolerant of narrow (less than a word) margins" | w h relLoc topLeft thisWord i m corner row | self removeAllMorphs. w _ originalMorphs first width - 1. h _ originalMorphs first height * 2 + 10. topLeft _ self position + self borderWidth + (0@10). thisWord _ OrderedCollection new. i _ 1. relLoc _ 0@0. corner _ topLeft. [i <= originalMorphs size] whileTrue: [m _ originalMorphs at: i. thisWord addLast: ((decodingMorphs at: i) position: topLeft + relLoc). thisWord addLast: (m position: topLeft + relLoc + (0@m height)). (m letter = Character space or: [i = originalMorphs size]) ifTrue: [self addAllMorphs: thisWord. corner _ corner max: thisWord last bounds bottomRight. thisWord reset]. relLoc _ relLoc + (w@0). (relLoc x + w) > newExtent x ifTrue: [i _ i - (thisWord size//2) + 1. thisWord reset. relLoc _ 0@(relLoc y + h)] ifFalse: [i _ i + 1]]. row _ self buttonRow. self addMorph: row. super extent: (corner - topLeft) + (self borderWidth * 2) + (0@row height+10). row align: row fullBounds bottomCenter with: self fullBounds bottomCenter - (0@2). ! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/11/2000 08:32'! addMenuItemsTo: aMenu hand: aHandMorph aMenu add: 'show cipher help' target: self action: #showHelpWindow. aMenu add: 'show cipher hints' target: self action: #showHintsWindow. aMenu add: 'clear cipher typing' target: self action: #clearTyping. aMenu add: 'enter a new cipher' target: self action: #enterANewCipher. ! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/8/2000 20:21'! buttonRow | row aButton | row _ AlignmentMorph newRow color: self color; hResizing: #shrinkWrap; vResizing: #shrinkWrap. aButton _ SimpleButtonMorph new target: self. aButton color: Color transparent; borderWidth: 1; borderColor: Color black. #('show help' 'show hints' 'clear typing' 'enter a new cipher') with: #(showHelpWindow showHintsWindow clearTyping enterANewCipher) do: [:label :selector | aButton _ aButton fullCopy. aButton actionSelector: selector. aButton label: label. row addMorphBack: aButton. row addTransparentSpacerOfSize: (3 @ 0)]. ^ row ! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/8/2000 10:46'! cipherStats | letterCounts digraphs d digraphCounts | letterCounts _ (quote copyWithout: Character space) asBag sortedCounts. digraphs _ Bag new. quote withIndexDo: [:c :i | i < quote size ifTrue: [d _ quote at: i+1. (c ~= Character space and: [d ~= Character space]) ifTrue: [digraphs add: (String with: c with: d)]]]. digraphCounts _ digraphs sortedCounts. ^ String streamContents: [:strm | 1 to: 10 do: [:i | strm cr; tab; nextPut: (letterCounts at: i) value. strm tab; print: (letterCounts at: i) key. (digraphCounts at: i) key > 1 ifTrue: [strm tab; tab; tab; nextPutAll: (digraphCounts at: i) value. strm tab; print: (digraphCounts at: i) key]]]! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/12/2000 00:28'! clearTyping self isClean ifTrue: [^ self]. (self confirm: 'Are you sure you want to discard all typing?') ifFalse: [^ self]. super clearTyping. ! ! !CipherPanel methodsFor: 'menu' stamp: 'sma 6/18/2000 10:32'! enterANewCipher self encodedQuote: (FillInTheBlank request: 'Type a cipher text to work on here below...')! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/8/2000 10:53'! showHelpWindow ((StringHolder new contents: 'The Cipher Panel displays an encrypted message. The encryption is a simple substitution code; each letter of the alphabet has been changed to a different one. You can solve the cipher by clicking above any letter in the message, and typing the letter you think it should be. The Cipher Panel automatically makes the same substitution anywhere else that letter occurs in the encoded message. If you are having trouble, you can use the command menu to ''show cipher hints''. That will display how many of each letter occurs, which is often a help in solving ciphers.') embeddedInMorphicWindowLabeled: 'About the Cipher Panel') setWindowColor: (Color r: 1.0 g: 0.6 b: 0.0); openInWorld: self world extent: 389@209! ! !CipherPanel methodsFor: 'menu' stamp: 'di 5/8/2000 12:19'! showHintsWindow ((StringHolder new contents: 'Most bodies of english text follow a general pattern of letter usage. The following are the most common letters, in approximate order of frequency: E T A O N I R S H The following are the most common digraphs: EN ER RE NT TH ON IN The message you are trying to decode has the following specific statistics:' , self cipherStats , ' Good luck!!') embeddedInMorphicWindowLabeled: 'Some Useful Statistics') setWindowColor: (Color r: 1.0 g: 0.6 b: 0.0); openInWorld: self world extent: 318@326! ! !CipherPanel methodsFor: 'events' stamp: 'di 5/12/2000 00:52'! keyCharacter: aLetter atIndex: indexInQuote nextFocus: nextFocus | encodedLetter | encodedLetter _ quote at: indexInQuote. originalMorphs with: decodingMorphs do: [:e :d | e letter = encodedLetter ifTrue: [d setLetter: aLetter color: Color red]]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CipherPanel class instanceVariableNames: ''! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/10/2000 09:52'! includeInNewMorphMenu ^ true! ! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/10/2000 10:08'! new "NOTE: Use newFromQuote: rather than new to create new CipherPanels" ^ self newFromQuote: self sampleString " Here are some other examples... World addMorph: (CipherPanel newFromQuote: 'BPFFXY LZY PK ROY RPBY PG XPAY HOYG EJCM SXJROYK FJG''R APR QCR PR''K EJC HOJ GYYF ROY LXRYMLRPJGK. KJCMSY CGNGJHG') World addMorph: (CipherPanel newFromQuote: 'Y FRV TRK HJRH QVL QS HJL BPLRHLTH WZLRTXPLT YV ZYSL YT OQYVB MJRH WLQWZL TRK KQX FRVVQH OQ.') World addMorph: (CipherPanel newFromQuote: 'XI''H SAZRG: SDCIZCIZT EZDEAZ TD CDI SGZRIZ EGDPGZHH.') World addMorph: (CipherPanel newFromQuote: 'PY MOJ WPMMWJ MZGYR ZL MOJ GZSWH PM''R YZ RZZYJS HZYJ MOBY RBPH.') World addMorph: (CipherPanel newFromQuote: 'PYSLHYA DJP VBHHLXYAA BPY BGNBMA PLUVQ LX AQMGY; QVY HPLXSLHBG LXUPYCLYXQA BPY NBPK BXC DPLYXCGM AKLGYA.') World addMorph: (CipherPanel newFromQuote: 'U HWVS RJ AHOST RLO FOOQOST TJUSM AJIO LOVNC WUXRUSM VST HWVCUSM LVSTZVWW. -- TVNUT WORROEIVS VXROE LUA KGUSRGHWO-ZCHVAA LOVER JHOEVRUJS') "! ! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/10/2000 10:06'! newFromQuote: encodedString "Use this to creat new panels instead of new." ^ super new encodedQuote: encodedString! ! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 9/19/2000 23:23'! quoteToScramble: aString "World addMorph: (CipherPanel quoteToScramble: 'Now is the time for all good men to come to the aid of their country.')" | dict | dict _ Dictionary new. ($A to: $Z) with: ($A to: $Z) shuffled do: [:a :b | dict at: a put: b]. ^ self newFromQuote: (aString asUppercase collect: [:a | dict at: a ifAbsent: [a]])! ! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/8/2000 11:58'! sampleString ^ 'E SGJC OSCVC LICGNV, ENGRCV, JEVEMAV. E SGJC OSEV QGVVEMA XMI [SMWWDHMML] ... EO''V HMALCIXKW OM SGJC VMNCOSEAR OSGO EAVQEICV GAL LIEJCV DMK. -- ZGIZIG VOICEVGAL'! ! !CipherPanel class methodsFor: 'as yet unclassified' stamp: 'di 9/19/2000 23:39'! tedsHack "Generate cryptic puzzles from method comments in the system" | c s | s _ 'none'. [s = 'none'] whileTrue: [s _ ((c _ Smalltalk allClasses atRandom) selectors collect: [:sel | (c firstCommentAt: sel) asString]) detect: [:str | str size between: 100 and: 200] ifNone: ['none']]. (CipherPanel quoteToScramble: s) openInWorld "CipherPanel tedsHack"! ! Arc subclass: #Circle instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Paths'! !Circle commentStamp: '' prior: 0! I represent a full circle. I am made from four Arcs.! !Circle methodsFor: 'displaying'! displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm 1 to: 4 do: [:i | super quadrant: i. super displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm]! ! !Circle methodsFor: 'displaying'! displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm 1 to: 4 do: [:i | super quadrant: i. super displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm]! ! !Circle methodsFor: 'display box access'! computeBoundingBox ^center - radius + form offset extent: form extent + (radius * 2) asPoint! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Circle class instanceVariableNames: ''! !Circle class methodsFor: 'examples'! exampleOne "Click any button somewhere on the screen. The point will be the center of the circcle of radius 150." | aCircle aForm | aForm _ Form extent: 1@30. aForm fillBlack. aCircle _ Circle new. aCircle form: aForm. aCircle radius: 150. aCircle center: Sensor waitButton. aCircle displayOn: Display "Circle exampleOne"! ! !Circle class methodsFor: 'examples'! exampleTwo "Designate a rectangular area that should be used as the brush for displaying the circle. Click any button at a point on the screen which will be the center location for the circle. The curve will be displayed with a long black form." | aCircle aForm | aForm _ Form fromUser. aCircle _ Circle new. aCircle form: aForm. aCircle radius: 150. aCircle center: Sensor waitButton. aCircle displayOn: Display at: 0 @ 0 rule: Form reverse "Circle exampleTwo"! ! ClassDescription subclass: #Class instanceVariableNames: 'subclasses name classPool sharedPools environment category ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !Class commentStamp: '' prior: 0! I add a number of facilities to those in ClassDescription: A set of all my subclasses (defined in ClassDescription, but only used here and below) A name by which I can be found in a SystemDictionary A classPool for class variables shared between this class and its metaclass A list of sharedPools which probably should be supplanted by some better mechanism. My instances describe the representation and behavior of objects. I add more comprehensive programming support facilities to the basic attributes of Behavior and the descriptive facilities of ClassDescription. The slot 'subclasses' is a redundant structure. It is never used during execution, but is used by the development system to simplify or speed certain operations. ! !Class methodsFor: 'initialize-release'! declare: varString "Declare class variables common to all instances. Answer whether recompilation is advisable." | newVars conflicts assoc class | newVars _ (Scanner new scanFieldNames: varString) collect: [:x | x asSymbol]. newVars do: [:var | var first isLowercase ifTrue: [self error: var, ' class variable name should be capitalized; proceed to include anyway.']]. conflicts _ false. classPool == nil ifFalse: [(classPool keys reject: [:x | newVars includes: x]) do: [:var | self removeClassVarName: var]]. (newVars reject: [:var | self classPool includesKey: var]) do: [:var | "adding" "check if new vars defined elsewhere" (self scopeHas: var ifTrue: [:ignored | ignored]) ifTrue: [self error: var , ' is defined elsewhere'. conflicts _ true]]. newVars size > 0 ifTrue: [classPool _ self classPool. "in case it was nil" newVars do: [:var | classPool declare: var from: Undeclared]]. ^conflicts! ! !Class methodsFor: 'initialize-release' stamp: 'ar 9/10/1999 17:34'! obsolete "Change the receiver and all of its subclasses to an obsolete class." self == Object ifTrue:[^self error:'Object is NOT obsolete']. name _ 'AnObsolete' , name. Object class instSize + 1 to: self class instSize do: [:i | self instVarAt: i put: nil]. "Store nil over class instVars." classPool _ nil. sharedPools _ nil. self class obsolete. super obsolete. ! ! !Class methodsFor: 'initialize-release' stamp: 'di 12/20/1999 12:37'! removeFromSystem "Forget the receiver from the Smalltalk global dictionary. Any existing instances will refer to an obsolete version of the receiver." self environment removeClassFromSystem: self. self obsolete! ! !Class methodsFor: 'initialize-release' stamp: 'sw 8/11/1998 13:23'! removeFromSystemUnlogged "Forget the receiver from the Smalltalk global dictionary. Any existing instances will refer to an obsolete version of the receiver. Do not log the removal either to the current change set nor to the system changes log" Smalltalk removeClassFromSystemUnlogged: self. self obsolete! ! !Class methodsFor: 'initialize-release' stamp: 'ar 2/13/1999 21:04'! sharing: poolString "Set up sharedPools. Answer whether recompilation is advisable." | oldPools found | oldPools _ self sharedPools. sharedPools _ OrderedCollection new. (Scanner new scanFieldNames: poolString) do: [:poolName | sharedPools add: (Smalltalk at: poolName asSymbol ifAbsent:[ (self confirm: 'The pool dictionary ', poolName,' does not exist.', '\Do you want it automatically created?' withCRs) ifTrue:[Smalltalk at: poolName asSymbol put: Dictionary new] ifFalse:[^self error: poolName,' does not exist']])]. sharedPools isEmpty ifTrue: [sharedPools _ nil]. oldPools do: [:pool | found _ false. self sharedPools do: [:p | p == pool ifTrue: [found _ true]]. found ifFalse: [^ true "A pool got deleted"]]. ^ false! ! !Class methodsFor: 'initialize-release' stamp: 'ar 7/15/1999 16:39'! superclass: sup methodDict: md format: ft name: nm organization: org instVarNames: nilOrArray classPool: pool sharedPools: poolSet "Answer an instance of me, a new class, using the arguments of the message as the needed information. Must only be sent to a new instance; else we would need Object flushCache." superclass _ sup. methodDict _ md. format _ ft. name _ nm. organization _ org. instanceVariables _ nilOrArray. classPool _ pool. sharedPools _ poolSet! ! !Class methodsFor: 'initialize-release' stamp: 'ar 7/20/1999 11:23'! superclass: aClass methodDictionary: mDict format: fmt "Basic initialization of the receiver" super superclass: aClass methodDictionary: mDict format: fmt. subclasses _ nil. "Important for moving down the subclasses field into Class" ! ! !Class methodsFor: 'accessing'! classPool "Answer the dictionary of class variables." classPool == nil ifTrue: [^Dictionary new] ifFalse: [^classPool]! ! !Class methodsFor: 'accessing'! name "Answer the name of the receiver." name == nil ifTrue: [^super name] ifFalse: [^name]! ! !Class methodsFor: 'testing'! hasMethods "Answer a Boolean according to whether any methods are defined for the receiver (includes whether there are methods defined in the receiver's metaclass)." ^super hasMethods or: [self class hasMethods]! ! !Class methodsFor: 'testing' stamp: 'ar 7/15/1999 15:36'! isObsolete "Return true if the receiver is obsolete." ^(self environment at: name ifAbsent:[nil]) ~~ self! ! !Class methodsFor: 'testing' stamp: 'tk 8/12/1999 15:47'! isSystemDefined "Answer true if the receiver is a system-defined class, and not a UniClass (an instance-specific lightweight class)" ^ self == self officialClass! ! !Class methodsFor: 'testing' stamp: 'tk 8/12/1999 15:49'! officialClass "I am not a UniClass. (See Player officialClass). Return the class you use to make new subclasses." ^ self! ! !Class methodsFor: 'copying' stamp: 'di 2/17/2000 22:43'! copy | newClass | newClass _ self class copy new superclass: superclass methodDict: self methodDict copy format: format name: name organization: self organization copy instVarNames: instanceVariables copy classPool: classPool copy sharedPools: sharedPools. Class instSize+1 to: self class instSize do: [:offset | newClass instVarAt: offset put: (self instVarAt: offset)]. ^ newClass! ! !Class methodsFor: 'class name' stamp: 'bf 5/31/2000 17:24'! rename: aString "The new name of the receiver is the argument, aString." | newName | (newName _ aString asSymbol) ~= self name ifTrue: [(Smalltalk includesKey: newName) ifTrue: [^self error: newName , ' already exists']. (Undeclared includesKey: newName) ifTrue: [SelectionMenu notify: 'There are references to, ' , aString printString , ' from Undeclared. Check them after this change.']. Smalltalk renameClass: self as: newName. name _ newName]! ! !Class methodsFor: 'class name' stamp: 'sw 12/1/2000 20:40'! uniqueNameForReference "Answer a unique name by which the receiver can be referred to from user scripts, for example" ^ name! ! !Class methodsFor: 'instance variables' stamp: 'ar 7/15/1999 18:56'! addInstVarName: aString "Add the argument, aString, as one of the receiver's instance variables." ^(ClassBuilder new) name: self name inEnvironment: self environment subclassOf: superclass type: self typeOfClass instanceVariableNames: self instanceVariablesString , aString classVariableNames: self classVariablesString poolDictionaries: self sharedPoolsString category: self category ! ! !Class methodsFor: 'instance variables' stamp: 'ar 7/15/1999 18:56'! removeInstVarName: aString "Remove the argument, aString, as one of the receiver's instance variables." | newInstVarString | (self instVarNames includes: aString) ifFalse: [self error: aString , ' is not one of my instance variables']. newInstVarString _ ''. (self instVarNames copyWithout: aString) do: [:varName | newInstVarString _ newInstVarString , ' ' , varName]. ^(ClassBuilder new) name: self name inEnvironment: self environment subclassOf: superclass type: self typeOfClass instanceVariableNames: newInstVarString classVariableNames: self classVariablesString poolDictionaries: self sharedPoolsString category: self category! ! !Class methodsFor: 'class variables' stamp: 'di 3/27/2000 21:54'! addClassVarName: aString "Add the argument, aString, as a class variable of the receiver. Signal an error if the first character of aString is not capitalized, or if it is already a variable named in the class." | symbol | aString first isLowercase ifTrue: [^self error: aString, ' class variable name should be capitalized; proceed to include anyway.']. symbol _ aString asSymbol. self withAllSubclasses do: [:subclass | subclass scopeHas: symbol ifTrue: [:temp | ^ self error: aString , ' is already used as a variable name in class ' , subclass name]]. classPool == nil ifTrue: [classPool _ Dictionary new]. (classPool includesKey: symbol) ifFalse: ["Pick up any refs in Undeclared" Smalltalk changes changeClass: self from: self. classPool declare: symbol from: Undeclared]! ! !Class methodsFor: 'class variables'! allClassVarNames "Answer a Set of the names of the receiver's class variables, including those defined in the superclasses of the receiver." | aSet | superclass == nil ifTrue: [^self classVarNames] "This is the keys so it is a new Set." ifFalse: [aSet _ superclass allClassVarNames. aSet addAll: self classVarNames. ^aSet]! ! !Class methodsFor: 'class variables'! classVarNames "Answer a Set of the names of the class variables defined in the receiver." ^self classPool keys! ! !Class methodsFor: 'class variables' stamp: 'tk 3/15/98 20:19'! ensureClassPool classPool ifNil: [classPool _ Dictionary new].! ! !Class methodsFor: 'class variables'! initialize "Typically used for the initialization of class variables and metaclass instance variables. Does nothing, but may be overridden in Metaclasses." ^self! ! !Class methodsFor: 'class variables' stamp: 'jm 7/24/1999 12:58'! removeClassVarName: aString "Remove the class variable whose name is the argument, aString, from the names defined in the receiver, a class. Create an error notification if aString is not a class variable or if it is still being used in the code of the class." | aSymbol | aSymbol _ aString asSymbol. (classPool includesKey: aSymbol) ifFalse: [^self error: aString, ' is not a class variable']. self withAllSubclasses do:[:subclass | (Array with: subclass with: subclass class) do:[:classOrMeta | (classOrMeta whichSelectorsReferTo: (classPool associationAt: aSymbol)) isEmpty ifFalse: [ (self confirm: (aString,' is still used in code of class ', classOrMeta name, '.\Is it okay to move it to Undeclared?') withCRs) ifTrue:[^Undeclared declare: aSymbol from: classPool] ifFalse:[^self]]]]. classPool removeKey: aSymbol. classPool isEmpty ifTrue: [classPool _ nil]. ! ! !Class methodsFor: 'pool variables'! addSharedPool: aDictionary "Add the argument, aDictionary, as one of the receiver's pool dictionaries. Create an error if the dictionary is already one of the pools." (self sharedPools includes: aDictionary) ifTrue: [^self error: 'The dictionary is already in my pool']. sharedPools == nil ifTrue: [sharedPools _ OrderedCollection with: aDictionary] ifFalse: [sharedPools add: aDictionary]! ! !Class methodsFor: 'pool variables'! allSharedPools "Answer a Set of the pools the receiver shares, including those defined in the superclasses of the receiver." | aSet | superclass == nil ifTrue: [^self sharedPools copy] ifFalse: [aSet _ superclass allSharedPools. aSet addAll: self sharedPools. ^aSet]! ! !Class methodsFor: 'pool variables' stamp: 'tk 9/12/96'! removeSharedPool: aDictionary "Remove the pool dictionary, aDictionary, as one of the receiver's pool dictionaries. Create an error notification if the dictionary is not one of the pools. : Note that it removes the wrong one if there are two empty Dictionaries in the list." | satisfiedSet workingSet aSubclass | (self sharedPools includes: aDictionary) ifFalse: [^self error: 'the dictionary is not in my pool']. "first see if it is declared in a superclass in which case we can remove it." (self selectSuperclasses: [:class | class sharedPools includes: aDictionary]) isEmpty ifFalse: [sharedPools remove: aDictionary. sharedPools isEmpty ifTrue: [sharedPools _ nil]. ^self]. "second get all the subclasses that reference aDictionary through me rather than a superclass that is one of my subclasses." workingSet _ self subclasses asOrderedCollection. satisfiedSet _ Set new. [workingSet isEmpty] whileFalse: [aSubclass _ workingSet removeFirst. (aSubclass sharedPools includes: aDictionary) ifFalse: [satisfiedSet add: aSubclass. workingSet addAll: aSubclass subclasses]]. "for each of these, see if they refer to any of the variables in aDictionary because if they do, we can not remove the dictionary." satisfiedSet add: self. satisfiedSet do: [:sub | aDictionary associationsDo: [:aGlobal | (sub whichSelectorsReferTo: aGlobal) isEmpty ifFalse: [^self error: aGlobal key , ' is still used in code of class ' , sub name]]]. sharedPools remove: aDictionary. sharedPools isEmpty ifTrue: [sharedPools _ nil]! ! !Class methodsFor: 'pool variables'! sharedPools "Answer a Set of the pool dictionaries declared in the receiver." sharedPools == nil ifTrue: [^OrderedCollection new] ifFalse: [^sharedPools]! ! !Class methodsFor: 'compiling' stamp: 'di 12/4/1999 07:18'! canFindWithoutEnvironment: varName "This method is used for analysis of system structure -- see senders." "Look up varName, in the context of the receiver. Return true if it can be found without using the declared environment." | assoc | "First look in classVar dictionary." (assoc _ self classPool associationAt: varName ifAbsent: []) == nil ifFalse: [^ true]. "Next look in shared pools." self sharedPools do: [:pool | assoc _ pool associationAt: varName ifAbsent: [ "Hideous string key hack from Hypersqueak now used in Wonderland" pool associationAt: varName asString ifAbsent: []]. assoc == nil ifFalse: [^ true]]. "Finally look higher up the superclass chain and fail at the end." superclass == nil ifTrue: [^ false] ifFalse: [^ superclass scopeHas: varName ifTrue: [:ignored]]. ! ! !Class methodsFor: 'compiling' stamp: 'ar 7/14/1999 04:56'! compileAll super compileAll. self class compileAll.! ! !Class methodsFor: 'compiling'! compileAllFrom: oldClass "Recompile all the methods in the receiver's method dictionary (not the subclasses). Also recompile the methods in the metaclass." super compileAllFrom: oldClass. self class compileAllFrom: oldClass class! ! !Class methodsFor: 'compiling'! possibleVariablesFor: misspelled continuedFrom: oldResults | results | results _ misspelled correctAgainstDictionary: self classPool continuedFrom: oldResults. self sharedPools do: [:pool | results _ misspelled correctAgainstDictionary: pool continuedFrom: results ]. superclass == nil ifTrue: [ ^ misspelled correctAgainstDictionary: Smalltalk continuedFrom: results ] ifFalse: [ ^ superclass possibleVariablesFor: misspelled continuedFrom: results ]! ! !Class methodsFor: 'compiling' stamp: 'di 12/4/1999 16:51'! scopeHas: varName ifTrue: assocBlock "Look up the first argument, varName, in the context of the receiver. If it is there, pass the association to the second argument, assocBlock, and answer true." | assoc | "First look in classVar dictionary." (assoc _ self classPool associationAt: varName ifAbsent: []) == nil ifFalse: [assocBlock value: assoc. ^ true]. "Next look in shared pools." self sharedPools do: [:pool | assoc _ pool associationAt: varName ifAbsent: [ "String key hack from Hypersqueak now used in Wonderland **Eliminate this**" pool associationAt: varName asString ifAbsent: []]. assoc == nil ifFalse: [assocBlock value: assoc. ^true]]. "Next look in declared environment." (assoc _ self environment associationAtOrAbove: varName ifAbsent: [nil]) == nil ifFalse: [assocBlock value: assoc. ^ true]. "Finally look higher up the superclass chain and fail at the end." superclass == nil ifTrue: [^ false] ifFalse: [^ superclass scopeHas: varName ifTrue: assocBlock]. ! ! !Class methodsFor: 'subclass creation' stamp: 'RAA 6/22/2000 14:17'! newSubclass | i className | i _ 1. [className _ (self name , i printString) asSymbol. Smalltalk includesKey: className] whileTrue: [i _ i + 1]. ^ self subclass: className instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: Object categoryForUniclasses "Point newSubclass new"! ! !Class methodsFor: 'subclass creation' stamp: 'ar 7/15/1999 18:57'! subclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver)." ^(ClassBuilder new) superclass: self subclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat ! ! !Class methodsFor: 'subclass creation' stamp: 'ar 7/15/1999 18:57'! variableByteSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver) in which the subclass is to have indexable byte-sized nonpointer variables." ^(ClassBuilder new) superclass: self variableByteSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat ! ! !Class methodsFor: 'subclass creation' stamp: 'ar 7/15/1999 18:56'! variableSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver) in which the subclass is to have indexable pointer variables." ^(ClassBuilder new) superclass: self variableSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat ! ! !Class methodsFor: 'subclass creation' stamp: 'ar 7/15/1999 18:56'! variableWordSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver) in which the subclass is to have indexable word-sized nonpointer variables." ^(ClassBuilder new) superclass: self variableWordSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat ! ! !Class methodsFor: 'subclass creation' stamp: 'dwh 11/20/1999 23:44'! weakSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver) in which the subclass is to have weak indexable pointer variables." ^(ClassBuilder new) superclass: self weakSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! !Class methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 09:58'! fileOut "Create a file whose name is the name of the receiver with '.st' as the extension, and file a description of the receiver onto it." ^ self fileOutAsHtml: false! ! !Class methodsFor: 'fileIn/Out' stamp: 'tk 1/8/1999 08:01'! fileOutAsHtml: useHtml "File a description of the receiver onto a new file whose base name is the name of the receiver." | fileStream | fileStream _ useHtml ifTrue: [(FileStream newFileNamed: self name, FileDirectory dot, 'html') asHtml] ifFalse: [FileStream newFileNamed: self name, FileDirectory dot, 'st']. fileStream header; timeStamp. self sharedPools size > 0 ifTrue: [ self shouldFileOutPools ifTrue: [self fileOutSharedPoolsOn: fileStream]]. self fileOutOn: fileStream moveSource: false toFile: 0. fileStream trailer; close. DeepCopier new checkVariables. ! ! !Class methodsFor: 'fileIn/Out' stamp: 'ar 12/22/1999 17:32'! fileOutInitializerOn: aStream ^self class fileOutInitializerOn: aStream! ! !Class methodsFor: 'fileIn/Out' stamp: 'ar 12/22/1999 17:30'! fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex "File a description of the receiver on aFileStream. If the boolean argument, moveSource, is true, then set the trailing bytes to the position of aFileStream and to fileIndex in order to indicate where to find the source code." ^self fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex initializing: true! ! !Class methodsFor: 'fileIn/Out' stamp: 'ar 12/22/1999 17:29'! fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex initializing: aBool "File a description of the receiver on aFileStream. If the boolean argument, moveSource, is true, then set the trailing bytes to the position of aFileStream and to fileIndex in order to indicate where to find the source code." Transcript cr; show: name. super fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex. self class nonTrivial ifTrue: [aFileStream cr; nextPutAll: '"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!!'; cr; cr. self class fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex initializing: aBool]! ! !Class methodsFor: 'fileIn/Out' stamp: 'dtl 1/15/2000 17:54'! fileOutPool: aPool onFileStream: aFileStream | aPoolName aValue | aPoolName _ Smalltalk keyAtIdentityValue: aPool. Transcript cr; show: aPoolName. aFileStream nextPutAll: 'Transcript show: ''' , aPoolName , '''; cr!!'; cr. aFileStream nextPutAll: 'Smalltalk at: #' , aPoolName , ' put: Dictionary new!!'; cr. aPool keys asSortedCollection do: [ :aKey | aValue _ aPool at: aKey. aFileStream nextPutAll: aPoolName , ' at: #''' , aKey asString , '''', ' put: '. (aValue isKindOf: Number) ifTrue: [aValue printOn: aFileStream] ifFalse: [aFileStream nextPutAll: '('. aValue printOn: aFileStream. aFileStream nextPutAll: ')']. aFileStream nextPutAll: '!!'; cr]. aFileStream cr! ! !Class methodsFor: 'fileIn/Out' stamp: 'ar 2/13/1999 21:17'! fileOutSharedPoolsOn: aFileStream "file out the shared pools of this class after prompting the user about each pool" | poolsToFileOut | poolsToFileOut _ self sharedPools select: [:aPool | (self shouldFileOutPool: (Smalltalk keyAtIdentityValue: aPool))]. poolsToFileOut do: [:aPool | self fileOutPool: aPool onFileStream: aFileStream]. ! ! !Class methodsFor: 'fileIn/Out' stamp: 'tk 11/6/2000 20:58'! objectForDataStream: refStrm | | "I am about to be written on an object file. Write a reference to a class in Smalltalk instead." refStrm insideASegment ifFalse: ["Normal use" ^ DiskProxy global: self theNonMetaClass name selector: #yourself args: #()] ifTrue: ["recording objects to go into an ImageSegment" self isSystemDefined ifFalse: [^ self]. "do trace Player classes" (refStrm rootObject includes: self) ifTrue: [^ self]. "is in roots, intensionally write out, ^ self" "A normal class. remove it from references. Do not trace." refStrm references removeKey: self ifAbsent: []. "already there" ^ nil] ! ! !Class methodsFor: 'fileIn/Out'! reformatAll "Reformat all methods in this class. Leaves old code accessible to version browsing" super reformatAll. "me..." self class reformatAll "...and my metaclass"! ! !Class methodsFor: 'fileIn/Out'! removeFromChanges "References to the receiver, a class, and its metaclass should no longer be included in the system ChangeSet. 7/18/96 sw: call removeClassAndMetaClassChanges:" Smalltalk changes removeClassAndMetaClassChanges: self! ! !Class methodsFor: 'fileIn/Out'! shouldFileOutPool: aPoolName "respond with true if the user wants to file out aPoolName" ^self confirm: ('FileOut the sharedPool ', aPoolName, '?')! ! !Class methodsFor: 'fileIn/Out'! shouldFileOutPools "respond with true if the user wants to file out the shared pools" ^self confirm: 'FileOut selected sharedPools?'! ! !Class methodsFor: 'fileIn/Out' stamp: 'tk 9/27/2000 11:40'! storeDataOn: aDataStream "I don't get stored. Use a DiskProxy" (aDataStream insideASegment and: [self isSystemDefined not]) ifTrue: [ ^ super storeDataOn: aDataStream]. "do trace me" self error: 'use a DiskProxy to store a Class'! ! !Class methodsFor: 'accessing class hierarchy' stamp: 'tk 10/17/1999 13:31'! addSubclass: aSubclass "Make the argument, aSubclass, be one of the subclasses of the receiver. Create an error notification if the argument's superclass is not the receiver." aSubclass superclass ~~ self ifTrue: [^self error: aSubclass name , ' is not my subclass']. subclasses == nil ifTrue: [subclasses _ Array with: aSubclass. ^self]. subclasses do:[:cl| cl == aSubclass ifTrue:[^self]]. "Already my subclass" subclasses _ subclasses copyWith: aSubclass.! ! !Class methodsFor: 'accessing class hierarchy' stamp: 'ar 7/14/1999 10:54'! removeSubclass: aSubclass "If the argument, aSubclass, is one of the receiver's subclasses, remove it." subclasses == nil ifFalse: [subclasses _ subclasses copyWithout: aSubclass. subclasses isEmpty ifTrue: [subclasses _ nil]]. ! ! !Class methodsFor: 'accessing class hierarchy' stamp: 'ar 7/14/1999 11:00'! subclasses "Answer a Set containing the receiver's subclasses." ^subclasses == nil ifTrue: [#()] ifFalse: [subclasses copy]! ! !Class methodsFor: 'accessing class hierarchy' stamp: 'ar 7/14/1999 11:00'! subclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's immediate subclasses." subclasses == nil ifFalse:[subclasses do: aBlock]! ! !Class methodsFor: 'accessing class hierarchy' stamp: 'tk 8/18/1999 17:42'! subclassesDoGently: aBlock "Evaluate the argument, aBlock, for each of the receiver's immediate subclasses." subclasses == nil ifFalse: [subclasses do: aBlock]! ! !Class methodsFor: 'private' stamp: 'ar 7/15/1999 15:37'! setName: aSymbol "Private - set the name of the class" name _ aSymbol.! ! !Class methodsFor: 'organization' stamp: 'di 11/16/1999 16:25'! environment environment == nil ifTrue: [^ super environment]. ^ environment! ! !Class methodsFor: 'organization' stamp: 'di 12/23/1999 11:42'! environment: anEnvironment environment _ anEnvironment! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Class class instanceVariableNames: ''! !Class class methodsFor: 'instance creation' stamp: 'di 6/7/2000 22:01'! template: aSystemCategoryName "Answer an expression that can be edited and evaluated in order to define a new class." ^ self templateForSubclassOf: Object name category: aSystemCategoryName ! ! !Class class methodsFor: 'instance creation' stamp: 'di 6/7/2000 21:57'! templateForSubclassOf: priorClassName category: systemCategoryName "Answer an expression that can be edited and evaluated in order to define a new class, given that the class previously looked at was as given" Preferences printAlternateSyntax ifTrue: [^ priorClassName asString, ' subclass (#NameOfSubclass) instanceVariableNames ('''') classVariableNames ('''') poolDictionaries ('''') category (''' , systemCategoryName asString , ''')'] ifFalse: [^ priorClassName asString, ' subclass: #NameOfSubclass instanceVariableNames: '''' classVariableNames: '''' poolDictionaries: '''' category: ''' , systemCategoryName asString , '''']! ! !Class class methodsFor: 'fileIn/Out'! fileOutPool: aString "file out the global pool named aString" | f | f _ FileStream newFileNamed: aString, '.st'. self new fileOutPool: (Smalltalk at: aString asSymbol) onFileStream: f. f close. ! ! Object subclass: #ClassBuilder instanceVariableNames: 'environ classMap instVarMap progress maxClassIndex currentClassIndex ' classVariableNames: 'QuietMode ' poolDictionaries: '' category: 'Kernel-Classes'! !ClassBuilder commentStamp: '' prior: 0! Responsible for creating a new class or changing the format of an existing class (from a class definition in a browser or a fileIn). This includes validating the definition, computing the format of instances, creating or modifying the accompanying Metaclass, setting up the class and metaclass objects themselves, registering the class as a global, recompiling methods, modifying affected subclasses, mutating existing instances to the new format, and more. You typically only need to use or modify this class, or even know how it works, when making fundamental changes to how the Smalltalk system and language works. ! !ClassBuilder methodsFor: 'initialize' stamp: 'ar 11/22/1999 10:09'! doneCompiling: aClass "The receiver has finished modifying the class hierarchy. Do any necessary cleanup." aClass doneCompiling.! ! !ClassBuilder methodsFor: 'initialize' stamp: 'ar 8/29/1999 12:32'! initialize environ _ Smalltalk. instVarMap _ IdentityDictionary new.! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 11/22/1999 03:21'! class: oldClass instanceVariableNames: instVarString unsafe: unsafe "This is the basic initialization message to change the definition of an existing Metaclass" | instVars newClass | environ _ oldClass environment. instVars _ Scanner new scanFieldNames: instVarString. unsafe ifFalse:[ "Run validation checks so we know that we have a good chance for recompilation" (self validateInstvars: instVars from: oldClass forSuper: oldClass superclass) ifFalse:[^nil]. (self validateSubclassFormat: oldClass typeOfClass from: oldClass forSuper: oldClass superclass extra: instVars size) ifFalse:[^nil]]. "Create a template for the new class (will return oldClass when there is no change)" newClass _ self newSubclassOf: oldClass superclass type: oldClass typeOfClass instanceVariables: instVars from: oldClass unsafe: unsafe. newClass == nil ifTrue:[^nil]. "Some error" newClass _ self recompile: false from: oldClass to: newClass mutate: false. self doneCompiling: newClass. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 8/26/1999 12:54'! mutate: oldClass to: newClass "Mutate oldClass to newClass. Convert all instances of oldClass and recursively update the subclasses." | subs newSubclass oldSubclass | subs _ oldClass subclasses asArray. "Walk down" 1 to: subs size do:[:i| oldSubclass _ subs at: i. self showProgressFor: oldSubclass. "Create the new class" newSubclass _ self reshapeClass: oldSubclass to: nil super: newClass. self mutate: oldSubclass to: newSubclass. ]. oldClass obsolete. newClass isObsolete ifTrue:[newClass obsolete]. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 8/29/1999 15:34'! name: className inEnvironment: env subclassOf: newSuper type: type instanceVariableNames: instVarString classVariableNames: classVarString poolDictionaries: poolString category: category "Define a new class in the given environment" ^self name: className inEnvironment: env subclassOf: newSuper type: type instanceVariableNames: instVarString classVariableNames: classVarString poolDictionaries: poolString category: category unsafe: false! ! !ClassBuilder methodsFor: 'class definition' stamp: 'di 12/23/1999 15:23'! name: className inEnvironment: env subclassOf: newSuper type: type instanceVariableNames: instVarString classVariableNames: classVarString poolDictionaries: poolString category: category unsafe: unsafe "Define a new class in the given environment. If unsafe is true do not run any validation checks. This facility is provided to implement important system changes." | oldClass newClass organization instVars classVars force | environ _ env. instVars _ Scanner new scanFieldNames: instVarString. classVars _ (Scanner new scanFieldNames: classVarString) collect: [:x | x asSymbol]. "Validate the proposed name" unsafe ifFalse:[(self validateClassName: className) ifFalse:[^nil]]. oldClass _ env at: className ifAbsent:[nil]. oldClass isBehavior ifFalse:[oldClass _ nil]. "Already checked in #validateClassName:" unsafe ifFalse:[ "Run validation checks so we know that we have a good chance for recompilation" (self validateSuperclass: newSuper forSubclass: oldClass) ifFalse:[^nil]. (self validateInstvars: instVars from: oldClass forSuper: newSuper) ifFalse:[^nil]. (self validateClassvars: classVars from: oldClass forSuper: newSuper) ifFalse:[^nil]. (self validateSubclassFormat: type from: oldClass forSuper: newSuper extra: instVars size) ifFalse:[^nil]]. "Create a template for the new class (will return oldClass when there is no change)" newClass _ self newSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass unsafe: unsafe. newClass == nil ifTrue:[^nil]. "Some error" newClass == oldClass ifFalse:[newClass setName: className]. "Install the class variables and pool dictionaries... " force _ (newClass declare: classVarString) | (newClass sharing: poolString). "... classify ..." organization _ environ ifNotNil:[environ organization]. organization classify: newClass name under: category asSymbol. newClass environment: environ. "... recompile ..." newClass _ self recompile: force from: oldClass to: newClass mutate: false. "... export if not yet done ..." (environ at: newClass name ifAbsent:[nil]) == newClass ifFalse:[ environ at: newClass name put: newClass. Smalltalk flushClassNameCache. ]. "... and fix eventual references to obsolete globals." oldClass _ nil. "So we have no references to the old class anymore" self fixGlobalReferences. self doneCompiling: newClass. ^newClass ! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 8/29/1999 15:36'! newSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass unsafe: unsafe "Create a new subclass of the given superclass. Note: The new class may be meta." | newFormat newClass meta | "Compute the format of the new class" newFormat _ self computeFormat: type instSize: instVars size forSuper: newSuper ccIndex: (oldClass ifNil:[0] ifNotNil:[oldClass indexIfCompact]). newFormat == nil ifTrue:[^nil]. "Check if we really need a new subclass" (oldClass ~~ nil and:[ newSuper == oldClass superclass and:[ newFormat = oldClass format and:[ instVars = oldClass instVarNames]]]) ifTrue:[^oldClass]. unsafe ifFalse:[ "Make sure we don't redefine any dangerous classes" (self tooDangerousClasses includes: oldClass name) ifTrue:[ self error: oldClass name, ' cannot be changed'. ^nil]. "Check if the receiver should not be redefined" (oldClass ~~ nil and:[oldClass shouldNotBeRedefined]) ifTrue:[ self notify: oldClass name asText allBold, ' should not be redefined!! \Proceed to store over it.' withCRs]]. oldClass == nil ifTrue:["Requires new metaclass" meta _ Metaclass new. meta superclass: (newSuper ifNil:[Class] ifNotNil:[newSuper class]) methodDictionary: MethodDictionary new format: (newSuper ifNil:[Class format] ifNotNil:[newSuper class format]). meta superclass addSubclass: meta. "In case of Class" newClass _ meta new. ] ifFalse:[ newClass _ oldClass shallowCopy ]. newClass superclass: newSuper methodDictionary: MethodDictionary new format: newFormat; setInstVarNames: instVars; organization: (oldClass ifNotNil:[oldClass organization]). ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 9/9/1999 16:00'! recompile: force from: oldClass to: aClass mutate: forceMutation "Do the necessary recompilation after changine oldClass to newClass. If required (e.g., when oldClass ~~ newClass) mutate oldClass to newClass and all its subclasses. If forceMutation is true force a mutation even if oldClass and newClass are the same." | newClass | newClass _ aClass. oldClass == nil ifTrue:[ "newClass has an empty method dictionary so we don't need to recompile" Smalltalk changes addClass: newClass. newClass superclass addSubclass: newClass. ^newClass]. (newClass == oldClass and:[force not and:[forceMutation not]]) ifTrue:[ "No recompilation necessary but we might have added class vars or class pools so record the change" Smalltalk changes changeClass: newClass from: oldClass. ^newClass]. currentClassIndex _ 0. maxClassIndex _ oldClass withAllSubclasses size. (oldClass == newClass and:[forceMutation not]) ifTrue:[ Smalltalk changes changeClass: newClass from: oldClass. "Recompile from newClass without mutating" self informUserDuring:[ newClass isSystemDefined ifFalse:[progress _ nil]. newClass withAllSubclassesDo:[:cl| self showProgressFor: cl. cl compileAll]]. ^newClass]. "Recompile and mutate oldClass to newClass" classMap _ WeakValueDictionary new. self informUserDuring:[ newClass isSystemDefined ifFalse:[progress _ nil]. self showProgressFor: oldClass. newClass _ self reshapeClass: oldClass to: newClass super: newClass superclass. Smalltalk changes changeClass: newClass from: oldClass. self mutate: oldClass to: newClass. ]. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 9/5/1999 15:23'! reshapeClass: aClass to: templateClass super: newSuper "Reshape the given class to the new super class. If templateClass is not nil then it defines the shape of the new class" | fmt newClass newMeta newSuperMeta oldMeta instVars oldClass | templateClass == nil ifTrue:[oldClass _ aClass] ifFalse:[oldClass _ templateClass]. aClass becomeUncompact. "Compute the new format of the class" instVars _ instVarMap at: aClass name ifAbsent:[oldClass instVarNames]. fmt _ self computeFormat: oldClass typeOfClass instSize: instVars size forSuper: newSuper ccIndex: 0."Known to be 0 since we uncompacted aClass first" fmt == nil ifTrue:[^nil]. aClass isMeta ifFalse:["Create a new meta class" oldMeta _ aClass class. newMeta _ oldMeta clone. newSuperMeta _ newSuper ifNil:[Class] ifNotNil:[newSuper class]. newMeta superclass: newSuperMeta methodDictionary: MethodDictionary new format: (self computeFormat: oldMeta typeOfClass instSize: oldMeta instVarNames size forSuper: newSuperMeta ccIndex: 0); setInstVarNames: oldMeta instVarNames; organization: oldMeta organization. "Recompile the meta class" oldMeta hasMethods ifTrue:[newMeta compileAllFrom: oldMeta]. "Fix up meta class structure" oldMeta superclass removeSubclass: oldMeta. newMeta superclass addSubclass: newMeta. "And record the change so we can fix global refs later" self recordClass: oldMeta replacedBy: newMeta. ]. newClass _ newMeta == nil ifTrue:[oldClass clone] ifFalse:[newMeta adoptInstance: oldClass from: oldMeta]. newClass superclass: newSuper methodDictionary: MethodDictionary new format: fmt; setInstVarNames: instVars; organization: aClass organization. "Recompile the new class" aClass hasMethods ifTrue:[newClass compileAllFrom: aClass]. "Export the new class into the environment" aClass isMeta ifFalse:[ "Derefence super sends in the old class" self fixSuperSendsFrom: aClass. "Export the class" environ at: newClass name put: newClass. "And use the ST association in the new class" self fixSuperSendsTo: newClass]. "Fix up the class hierarchy" aClass superclass removeSubclass: aClass. newClass superclass addSubclass: newClass. "Adopt all the instances of the old class" aClass autoMutateInstances ifTrue:[newClass updateInstancesFrom: aClass]. "And record the change" self recordClass: aClass replacedBy: newClass. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 11/22/1999 03:20'! silentlyMoveInstVarNamed: instVarName from: srcClass to: dstClass after: prevInstVarName "Move the instvar from srcClass to dstClass. Do not perform any checks." | srcVars dstVars dstIndex | srcVars _ srcClass instVarNames copyWithout: instVarName. srcClass == dstClass ifTrue:[dstVars _ srcVars] ifFalse:[dstVars _ dstClass instVarNames]. dstIndex _ dstVars indexOf: prevInstVarName. dstVars _ (dstVars copyFrom: 1 to: dstIndex), (Array with: instVarName), (dstVars copyFrom: dstIndex+1 to: dstVars size). instVarMap at: srcClass name put: srcVars. instVarMap at: dstClass name put: dstVars. (srcClass inheritsFrom: dstClass) ifTrue:[ self recompile: false from: dstClass to: dstClass mutate: true. ] ifFalse:[ (dstClass inheritsFrom: srcClass) ifTrue:[ self recompile: false from: srcClass to: srcClass mutate: true. ] ifFalse:[ "Disjunct hierarchies" srcClass == dstClass ifFalse:[ self recompile: false from: dstClass to: dstClass mutate: true. ]. self recompile: false from: srcClass to: srcClass mutate: true. ]. ]. self doneCompiling: srcClass. self doneCompiling: dstClass.! ! !ClassBuilder methodsFor: 'class format' stamp: 'ar 9/10/1999 12:55'! computeFormat: type instSize: newInstSize forSuper: newSuper ccIndex: ccIndex "Compute the new format for making oldClass a subclass of newSuper. Return the format or nil if there is any problem." | instSize isVar isWords isPointers isWeak | instSize _ newInstSize + (newSuper ifNil:[0] ifNotNil:[newSuper instSize]). instSize > 254 ifTrue:[ self error: 'Class has too many instance variables (', instSize printString,')'. ^nil]. type == #compiledMethod ifTrue:[^CompiledMethod instSpec]. type == #normal ifTrue:[isVar _ isWeak _ false. isWords _ isPointers _ true]. type == #bytes ifTrue:[isVar _ true. isWords _ isPointers _ isWeak _ false]. type == #words ifTrue:[isVar _ isWords _ true. isPointers _ isWeak _ false]. type == #variable ifTrue:[isVar _ isPointers _ isWords _ true. isWeak _ false]. type == #weak ifTrue:[isVar _ isWeak _ isWords _ isPointers _ true]. (isPointers not and:[instSize > 0]) ifTrue:[ self error:'A non-pointer class cannot have instance variables'. ^nil]. ^(self format: instSize variable: isVar words: isWords pointers: isPointers weak: isWeak) + (ccIndex bitShift: 11).! ! !ClassBuilder methodsFor: 'class format' stamp: 'ar 7/11/1999 06:39'! format: nInstVars variable: isVar words: isWords pointers: isPointers weak: isWeak "Compute the format for the given instance specfication." | cClass instSpec sizeHiBits fmt | self flag: #instSizeChange. " Smalltalk browseAllCallsOn: #instSizeChange. Smalltalk browseAllImplementorsOf: #fixedFieldsOf:. Smalltalk browseAllImplementorsOf: #instantiateClass:indexableSize:. " " NOTE: This code supports the backward-compatible extension to 8 bits of instSize. For now the format word is... <2 bits=instSize//64><5 bits=cClass><4 bits=instSpec><6 bits=instSize\\64><1 bit=0> But when we revise the image format, it should become... <5 bits=cClass><4 bits=instSpec><8 bits=instSize><1 bit=0> " sizeHiBits _ (nInstVars+1) // 64. cClass _ 0. "for now" instSpec _ isWeak ifTrue:[4] ifFalse:[isPointers ifTrue: [isVar ifTrue: [nInstVars>0 ifTrue: [3] ifFalse: [2]] ifFalse: [nInstVars>0 ifTrue: [1] ifFalse: [0]]] ifFalse: [isWords ifTrue: [6] ifFalse: [8]]]. fmt _ sizeHiBits. fmt _ (fmt bitShift: 5) + cClass. fmt _ (fmt bitShift: 4) + instSpec. fmt _ (fmt bitShift: 6) + ((nInstVars+1)\\64). "+1 since prim size field includes header" fmt _ (fmt bitShift: 1). "This shift plus integer bit lets wordSize work like byteSize" ^fmt! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/20/1999 00:41'! validateClass: srcClass forMoving: iv downTo: dstClass "Make sure that we don't have any accesses to the instVar left" srcClass withAllSubclassesDo:[:cls| (cls == dstClass or:[cls inheritsFrom: dstClass]) ifFalse:[ cls forgetDoIts. (cls whichSelectorsAccess: iv) isEmpty ifFalse:[ self notify: (iv printString asText allBold), ' is still used in ', cls name asText allBold,'. Proceed to move it to Undeclared'. ]. ]. ]. ^true! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/20/1999 00:39'! validateClass: srcClass forMoving: iv upTo: dstClass "Make sure we don't have this instvar already" dstClass withAllSubclassesDo:[:cls| (cls == srcClass or:[cls inheritsFrom: srcClass]) ifFalse:[ cls isPointers ifFalse:[ self error: dstClass name, ' cannot have instance variables'. ^false]. cls instSize >= 254 ifTrue:[ self error: cls name, ' has more than 254 instance variables'. ^false]. (cls instVarNames includes: iv) ifTrue:[ self notify: (iv printString asText allBold),' is defined in ', cls name asText allBold,' Proceed to move it up to ', dstClass name asText allBold,' as well'. instVarMap at: cls name put: (cls instVarNames copyWithout: iv)]. ]. ]. ^true! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/13/1999 05:26'! validateClassName: aString "Validate the new class name" aString first isUppercase ifFalse:[ self error: 'Class names must be capitalized'. ^false]. environ at: aString ifPresent:[:old| (old isKindOf: Behavior) ifFalse:[ self notify: aString asText allBold, ' already exists!!\Proceed will store over it.' withCRs]]. ^true! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/15/1999 13:48'! validateClassvars: classVarArray from: oldClass forSuper: newSuper "Check if any of the classVars of oldClass conflict with the new superclass" | usedNames classVars temp | classVarArray isEmpty ifTrue:[^true]. "Okay" "Validate the class var names" usedNames _ classVarArray asSet. usedNames size = classVarArray size ifFalse:[ classVarArray do:[:var| usedNames remove: var ifAbsent:[temp _ var]]. self error: temp,' is multiply defined'. ^false]. (usedNames includesAnyOf: self reservedNames) ifTrue:[ self reservedNames do:[:var| (usedNames includes: var) ifTrue:[temp _ var]]. self error: temp,' is a reserved name'. ^false]. newSuper == nil ifFalse:[ usedNames _ newSuper allClassVarNames asSet. classVarArray do:[:iv| (usedNames includes: iv) ifTrue:[ newSuper withAllSuperclassesDo:[:cl| (cl classVarNames includes: iv) ifTrue:[temp _ cl]]. self error: iv, ' is already defined in ', temp name. ^false]]]. oldClass == nil ifFalse:[ usedNames _ Set new: 20. oldClass allSubclassesDo:[:cl| usedNames addAll: cl classVarNames]. classVars _ classVarArray. newSuper == nil ifFalse:[classVars _ classVars, newSuper allClassVarNames asArray]. classVars do:[:iv| (usedNames includes: iv) ifTrue:[ self error: iv, ' is already defined in a subclass of ', oldClass name. ^false]]]. ^true! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/15/1999 13:49'! validateInstvars: instVarArray from: oldClass forSuper: newSuper "Check if any of the instVars of oldClass conflict with the new superclass" | instVars usedNames temp | instVarArray isEmpty ifTrue:[^true]. "Okay" "Validate the inst var names" usedNames _ instVarArray asSet. usedNames size = instVarArray size ifFalse:[ instVarArray do:[:var| usedNames remove: var ifAbsent:[temp _ var]]. self error: temp,' is multiply defined'. ^false]. (usedNames includesAnyOf: self reservedNames) ifTrue:[ self reservedNames do:[:var| (usedNames includes: var) ifTrue:[temp _ var]]. self error: temp,' is a reserved name'. ^false]. newSuper == nil ifFalse:[ usedNames _ newSuper allInstVarNames asSet. instVarArray do:[:iv| (usedNames includes: iv) ifTrue:[ newSuper withAllSuperclassesDo:[:cl| (cl instVarNames includes: iv) ifTrue:[temp _ cl]]. self error: iv,' is already defined in ', temp name. ^false]]]. oldClass == nil ifFalse:[ usedNames _ Set new: 20. oldClass allSubclassesDo:[:cl| usedNames addAll: cl instVarNames]. instVars _ instVarArray. newSuper == nil ifFalse:[instVars _ instVars, newSuper allInstVarNames]. instVars do:[:iv| (usedNames includes: iv) ifTrue:[ self error: iv, ' is already defined in a subclass of ', oldClass name. ^false]]]. ^true! ! !ClassBuilder methodsFor: 'validation' stamp: 'di 11/24/1999 13:09'! validateSubclassFormat: newType from: oldClass forSuper: newSuper extra: newInstSize "Validate the # of instVars and the format of the subclasses" | deltaSize oldType | oldClass == nil ifTrue: [^ true]. "No subclasses" "Compute the # of instvars needed for all subclasses" deltaSize _ newInstSize. (oldClass notNil) ifTrue: [deltaSize _ deltaSize - oldClass instVarNames size]. (newSuper notNil) ifTrue: [deltaSize _ deltaSize + newSuper instSize]. (oldClass notNil and: [oldClass superclass notNil]) ifTrue: [deltaSize _ deltaSize - oldClass superclass instSize]. oldClass == nil ifTrue: [ deltaSize > 254 ifTrue: [ self error: 'More than 254 instance variables'. ^ false]. ^ true]. oldClass withAllSubclassesDo: [:sub | sub instSize + deltaSize > 254 ifTrue: [ self error: sub name,' has more than 254 instance variables'. ^ false]]. newType ~~ #normal ifTrue: ["And check if the immediate subclasses of oldClass can keep its layout" oldClass subclassesDo:[:sub| oldType _ sub typeOfClass. oldType == newType ifFalse: [ self error: sub name,' cannot be recompiled'. ^ false]]]. ^ true! ! !ClassBuilder methodsFor: 'validation' stamp: 'ar 7/15/1999 13:50'! validateSuperclass: aSuperClass forSubclass: aClass "Check if it is okay to use aSuperClass as the superclass of aClass" aClass == nil ifTrue:["New class" (aSuperClass == nil or:[aSuperClass isBehavior and:[aSuperClass isMeta not]]) ifFalse:[self error: aSuperClass name,' is not a valid superclass'. ^false]. ^true]. aSuperClass == aClass superclass ifTrue:[^true]. "No change" (aClass isMeta) "Not permitted - meta class hierarchy is derived from class hierarchy" ifTrue:[^self error: aClass name, ' must inherit from ', aClass superclass name]. "Check for circular references" (aSuperClass ~~ nil and:[aSuperClass == aClass or:[aSuperClass inheritsFrom: aClass]]) ifTrue:[self error: aSuperClass name,' inherits from ', aClass name. ^false]. ^true! ! !ClassBuilder methodsFor: 'private' stamp: 'ls 10/23/2000 14:10'! fixGlobalReferences "Fix all the references to globals which are now outdated. Care must be taken that we do not accidentally 'fix' dangerous stuff." | oldClasses newClasses condition any | classMap == nil ifTrue:[^self]. (self retryWithGC: [condition _ classMap anySatisfy: [:any0 | any _ any0. any0 _ nil. any notNil and:[any isObsolete]]. any_nil. condition] until:[:obsRef| obsRef = false]) ifFalse:[^self]. "GC cleaned up the remaining refs" "Collect the old and the new refs" oldClasses _ OrderedCollection new. newClasses _ OrderedCollection new. classMap keysAndValuesDo:[:new :old| old == nil ifFalse:[ newClasses add: new. oldClasses add: old]]. oldClasses isEmpty ifTrue:[^self]. "GC cleaned up the rest" "Now fix all the known dangerous pointers to old classes by creating copies of those still needed. Dangerous pointers should come only from obsolete subclasses (where the superclass must be preserved)." self fixObsoleteReferencesTo: oldClasses. "After this has been done fix the remaining references" progress == nil ifFalse:[progress value: 'Fixing references to globals']. "Forward all old refs to the new ones" (oldClasses asArray) elementsForwardIdentityTo: (newClasses asArray). "Done"! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 7/20/1999 10:45'! fixObsoleteMethodsFrom: oldClasses map: obsoleteClasses "Fix the methods of the obsolete classes" | nLits tempMethod | oldClasses do:[:class| obsoleteClasses at: class ifPresent:[:tempClass| class selectorsAndMethodsDo:[:sel :meth| "Create a clean copy for the temps" tempMethod _ meth copy. "Fix the super sends" tempMethod sendsToSuper ifTrue:[ nLits _ tempMethod numLiterals. "Hack the method class in the temp class" tempMethod literalAt: nLits put: (Association new value: (obsoleteClasses at: class ifAbsent:[class])). ]. "Install in tempClass" tempClass addSelector: sel withMethod: tempMethod. ]. ]. ].! ! !ClassBuilder methodsFor: 'private' stamp: 'tk 2/15/2000 13:58'! fixObsoleteReferencesTo: oldClasses "Fix all obsolete references to the given set of outdated classes" | obsoleteClasses obj | progress == nil ifFalse:[progress value:'Fixing obsolete class references...']. "Prepare a map of obsolete classes" obsoleteClasses _ self mapObsoleteClassesToTemps: oldClasses. "Sanity check for debugging" "oldClasses size = obsoleteClasses size ifFalse:[self error:'Obsolete classes size mismatch']." "Fix the methods" self fixObsoleteMethodsFrom: oldClasses map: obsoleteClasses. "Now search and fix all dangerous objects" obj _ 0 someObject. [0 == obj] whileFalse:[ "Avoid proxies on the disk. See below." obj isInMemory ifTrue:[ (obj isBehavior and:[obsoleteClasses includesKey: obj superclass]) ifTrue:[ (obsoleteClasses includesKey: obj) ifFalse:[ obj superclass: (obsoleteClasses at: obj superclass)]]]. obj _ obj nextObject. ]. "Three kinds of ProtoObjects need to be considered: ObjectTracer and ObjectViewer OK to test. They are not Behaviors, and the object they represent will be found and fixed. Traces may be recorded, but they should be. ObjectOut and MorphObjectOut Skip them. The objects they represent are in SqueakPages, and are not really in this image. Do not want to bring them all in!! ImageSegmentRootStub Skip them. Might stand for a class, or its segment might contain a class. But, that class can't be the one being cleaned up now. If instances of the class being fixed are in a segment, the class is in the outPointers, and will be found normally. " ! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 8/29/1999 12:03'! fixSuperSendsFrom: oldClass "The oldClass is about to be removed from the environment. Fix all references to super so that the association is different from the original ST association." | newSuper nLits lastLiteral | newSuper _ Association key: nil value: oldClass. oldClass methodsDo:[:meth| nLits _ meth numLiterals. nLits > 0 ifTrue:[lastLiteral _ meth literalAt: nLits] ifFalse:[lastLiteral _ nil]. (lastLiteral class == Association and:[meth sendsToSuper]) ifTrue:[ meth literalAt: nLits put: newSuper. ]. ].! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 8/29/1999 12:04'! fixSuperSendsTo: newClass "The newClass has been exported into the environment. Fix all references to super so that the association is the original ST association." | newSuper nLits lastLiteral | newSuper _ Smalltalk associationAt: newClass name ifAbsent:[nil]. newSuper == nil ifTrue:[^self]. newSuper value == newClass ifTrue:[^self]. newClass methodsDo:[:meth| nLits _ meth numLiterals. nLits > 0 ifTrue:[lastLiteral _ meth literalAt: nLits] ifFalse:[lastLiteral _ nil]. (lastLiteral class == Association and:[meth sendsToSuper]) ifTrue:[ meth literalAt: nLits put: newSuper. ]. ].! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 8/29/1999 13:03'! informUserDuring: aBlock self class isSilent ifTrue:[^aBlock value]. Utilities informUserDuring:[:bar| progress _ bar. aBlock value]. progress _ nil.! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 8/29/1999 12:29'! mapObsoleteClassesToTemps: oldClasses "Map the old classes to temporary classes. These temporary classes will survive the #become: operation and be used as the class of any instances or subclasses of the obsolete classes." | oldMeta tempMeta obsoleteClasses | obsoleteClasses _ IdentityDictionary new: oldClasses size. oldClasses do:[:oldClass| "Note: If a class is getting obsolete here so is its metaclass" oldMeta _ oldClass isMeta ifTrue:[oldClass] ifFalse:[oldClass class]. tempMeta _ obsoleteClasses at: oldMeta ifAbsentPut:[oldMeta clone]. oldClass isMeta ifFalse:[ tempMeta adoptInstance: oldClass from: oldMeta. obsoleteClasses at: oldClass put: tempMeta soleInstance. "Note: If we haven't mutated the instances of the old class to the new layout we must do it here." oldClass autoMutateInstances ifFalse:[ tempMeta soleInstance updateInstancesFrom: oldClass]]]. "Fix the superclasses of the clones" obsoleteClasses keysAndValuesDo:[:old :temp| temp superclass: (obsoleteClasses at: temp superclass "Might be a subclass of a live class" ifAbsent:[temp superclass])]. "And install new method dictionaries" obsoleteClasses valuesDo:[:temp| temp methodDictionary: temp methodDictionary copy. ]. ^obsoleteClasses! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 7/20/1999 00:00'! recordClass: oldClass replacedBy: newClass "Record the replacement of oldClass by newClass so that we can fix any references to oldClass later on." classMap at: newClass put: oldClass. (classMap includesKey: oldClass) ifTrue:[ "This will happen if we recompile from Class in which case the metaclass gets recorded twice" classMap at: newClass put: (classMap at: oldClass). classMap removeKey: oldClass. ]. "And keep the changes up to date" (instVarMap includesKey: oldClass name) ifTrue:[ Smalltalk changes changeClass: newClass from: oldClass. ].! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 7/15/1999 13:39'! reservedNames "Return a list of names that must not be used for variables" ^#('self' 'super' 'thisContext' 'true' 'false' 'nil' self super thisContext true false nil).! ! !ClassBuilder methodsFor: 'private' stamp: 'sw 7/31/2000 12:57'! showProgressFor: aClass "Announce that we're processing aClass" progress == nil ifTrue:[^self]. currentClassIndex _ currentClassIndex + 1. (aClass hasMethods and: [aClass wantsRecompilationProgressReported]) ifTrue: [progress value: ('Recompiling ', aClass name,' (', currentClassIndex printString,'/', maxClassIndex printString,')')]! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 8/29/1999 15:43'! tooDangerousClasses "Return a list of class names which will not be modified in the public interface" ^#( "Object will break immediately" Object "Contexts and their superclasses" InstructionStream ContextPart BlockContext MethodContext "Superclasses of basic collections" Collection SequenceableCollection ArrayedCollection "Collections known to the VM" Array Bitmap String Symbol ByteArray CompiledMethod TranslatedMethod "Basic Numbers" Magnitude Number SmallInteger Float "Misc other" LookupKey Association Link Point Rectangle Behavior PositionableStream UndefinedObject ) ! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 8/29/1999 15:38'! class: oldClass instanceVariableNames: instVarString "This is the basic initialization message to change the definition of an existing Metaclass" oldClass isMeta ifFalse:[^self error: oldClass name, 'is not a Metaclass']. ^self class: oldClass instanceVariableNames: instVarString unsafe: false! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:40'! moveInstVarNamed: instVarName from: srcClass to: dstClass after: prevInstVarName "Move the given instVar from srcClass to dstClass" (srcClass instVarNames includes: instVarName) ifFalse:[^self error: instVarName,' is not an instance variable of ', srcClass name]. (prevInstVarName isNil or:[dstClass instVarNames includes: prevInstVarName]) ifFalse:[^self error: prevInstVarName, 'is not an instance variable of', dstClass name]. (srcClass inheritsFrom: dstClass) ifTrue:[ "Move the instvar up the hierarchy." (self validateClass: srcClass forMoving: instVarName upTo: dstClass) ifFalse:[^false]. ]. (dstClass inheritsFrom: srcClass) ifTrue:[ "Move the instvar down the hierarchy" (self validateClass: srcClass forMoving: instVarName downTo: dstClass) ifFalse:[^false]. ]. ^self silentlyMoveInstVarNamed: instVarName from: srcClass to: dstClass after: prevInstVarName! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:29'! superclass: newSuper subclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class." ^self name: t inEnvironment: newSuper environment subclassOf: newSuper type: newSuper typeOfClass instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:29'! superclass: aClass variableByteSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class in which the subclass is to have indexable byte-sized nonpointer variables." (aClass instSize > 0) ifTrue: [^self error: 'cannot make a byte subclass of a class with named fields']. (aClass isVariable and: [aClass isWords]) ifTrue: [^self error: 'cannot make a byte subclass of a class with word fields']. (aClass isVariable and: [aClass isPointers]) ifTrue: [^self error: 'cannot make a byte subclass of a class with pointer fields']. ^self name: t inEnvironment: aClass environment subclassOf: aClass type: #bytes instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:29'! superclass: aClass variableSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class in which the subclass is to have indexable pointer variables." aClass isBits ifTrue: [^self error: 'cannot make a pointer subclass of a class with non-pointer fields']. ^self name: t inEnvironment: aClass environment subclassOf: aClass type: #variable instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:30'! superclass: aClass variableWordSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class in which the subclass is to have indexable word-sized nonpointer variables." (aClass instSize > 0) ifTrue: [^self error: 'cannot make a word subclass of a class with named fields']. (aClass isVariable and: [aClass isBytes]) ifTrue: [^self error: 'cannot make a word subclass of a class with byte fields']. (aClass isVariable and: [aClass isPointers]) ifTrue: [^self error: 'cannot make a word subclass of a class with pointer fields']. ^self name: t inEnvironment: aClass environment subclassOf: aClass type: #words instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! !ClassBuilder methodsFor: 'public' stamp: 'ar 7/19/1999 23:30'! superclass: aClass weakSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat "This is the standard initialization message for creating a new class as a subclass of an existing class (the receiver) in which the subclass is to have weak indexable pointer variables." aClass isBits ifTrue: [^self error: 'cannot make a pointer subclass of a class with non-pointer fields']. ^self name: t inEnvironment: aClass environment subclassOf: aClass type: #weak instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassBuilder class instanceVariableNames: ''! !ClassBuilder class methodsFor: 'testing' stamp: 'ar 7/15/1999 14:04'! autoMutateInstances "Don't mutate me while I'm compiling myself" ^false! ! !ClassBuilder class methodsFor: 'accessing' stamp: 'ar 7/15/1999 18:50'! beSilent: aBool "ClassDefiner beSilent: true" "ClassDefiner beSilent: false" QuietMode _ aBool.! ! !ClassBuilder class methodsFor: 'accessing' stamp: 'ar 7/15/1999 18:53'! beSilentDuring: aBlock "Temporarily suppress information about what is going on" | wasSilent result | wasSilent _ self isSilent. self beSilent: true. result _ aBlock value. self beSilent: wasSilent. ^result! ! !ClassBuilder class methodsFor: 'accessing' stamp: 'ar 7/15/1999 18:48'! isSilent ^QuietMode == true! ! !ClassBuilder class methodsFor: 'instance creation' stamp: 'ar 7/19/1999 23:28'! new ^super new initialize.! ! Object subclass: #ClassCategoryReader instanceVariableNames: 'class category changeStamp ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !ClassCategoryReader commentStamp: '' prior: 0! I represent a mechanism for retrieving class descriptions stored on a file.! !ClassCategoryReader methodsFor: 'fileIn/Out' stamp: 'tk 12/15/97 16:26'! scanFrom: aStream "File in methods from the stream, aStream." | methodText | [methodText _ aStream nextChunkText. methodText size > 0] whileTrue: [class compile: methodText classified: category withStamp: changeStamp notifying: (SyntaxError new category: category)]! ! !ClassCategoryReader methodsFor: 'fileIn/Out' stamp: 'tk 1/27/2000 23:24'! scanFromNoCompile: aStream "Just move the source code for the methods from aStream." | methodText selector | [methodText _ aStream nextChunkText. methodText size > 0] whileTrue: [(SourceFiles at: 2) ifNotNil: [ selector _ class parserClass new parseSelector: methodText. (class compiledMethodAt: selector) putSource: methodText fromParseNode: nil class: class category: category withStamp: changeStamp inFile: 2 priorMethod: nil]]! ! !ClassCategoryReader methodsFor: 'fileIn/Out' stamp: 'RAA 6/22/2000 16:08'! scanFromNoCompile: aStream forSegment: anImageSegment ^self scanFromNoCompile: aStream "subclasses may care about the segment"! ! !ClassCategoryReader methodsFor: 'private' stamp: '6/5/97 di'! setClass: aClass category: aCategory ^ self setClass: aClass category: aCategory changeStamp: String new ! ! !ClassCategoryReader methodsFor: 'private' stamp: '6/5/97 di'! setClass: aClass category: aCategory changeStamp: aString class _ aClass. category _ aCategory. changeStamp _ aString ! ! Object subclass: #ClassChangeRecord instanceVariableNames: 'inForce revertable changeTypes thisDefinition priorDefinition thisName priorName thisOrganization priorOrganization thisComment priorComment thisMD priorMD methodChanges ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ClassChangeRecord commentStamp: '' prior: 0! A ClassChangeRecorder keeps track of most substantive changes premissible in a project, isolated or not. Structure: inForce a boolean Tells whether these changes are in effect. true for all changeSets in and above the current project. It should be sufficient only to record this for the changeSet as a whole, but this redundancy could help in error recovery. classIsLocal a boolean True if and only if this class is defined in this layer of the project structure. changeTypes an identitySet Summarizes which changes have been made in this class. Values include #comment, #reorganize, #rename, and the four more summarized below. thisName a string Retains the class name for this layer. priorName a string Preserves the prior name. thisComment a text Retains the class comment for this layer. priorComment a text Preserves the prior comment. thisOrganization a classOrganizer Retains the class organization for this layer. priorOrganization a classOrganizer Preserves the prior organization. thisMD a methodDictionary Used to prepare changes for nearly atomic invocation of this layer (see below). priorMD a methodDictionary Preserves the state of an altered class as it exists in the next outer layer of the project structure. methodChanges a dictionary of classChangeRecords Retains all the method changes for this layer. Four of the possible changeTypes are maintained in a mutually exclusive set, analogously to MethodChangeRecords. Here is a simple summary of the relationship between these four changeType symbols and the recording of prior state | prior == nil | prior not nil --------- |---------------------------- |-------------------- add | add | change --------- |---------------------------- |-------------------- remove | addedThenRemoved | remove A classChangeRecorder is notified of changes by the method noteMethodChange: . ClassChangeRecorders are designed to invoke a set of changes relative to the definition of a class in an prior layer. It is important that both invocation and revocation of these changes take place in a nearly atomic fashion so that interdependent changes will be adopted as a whole, and so that only one flush of the method cache should be necessary. A further reason for revocation to be simple is that it may be requested as an attempt to recover from an error in a project that is failing.! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 4/2/2000 21:39'! allChangeTypes | chgs | (priorName ~~ nil and: [changeTypes includes: #rename]) ifTrue: [(chgs _ changeTypes copy) add: 'oldName: ' , priorName. ^ chgs]. ^ changeTypes! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 4/2/2000 21:59'! assimilateAllChangesIn: otherRecord | selector changeRecord changeType | otherRecord isClassRemoval ifTrue: [^ self noteChangeType: #remove]. otherRecord allChangeTypes do: [:chg | self noteChangeType: chg fromClass: self realClass]. otherRecord methodChanges associationsDo: [:assn | selector _ assn key. changeRecord _ assn value. changeType _ changeRecord changeType. (changeType == #remove or: [changeType == #addedThenRemoved]) ifTrue: [changeType == #addedThenRemoved ifTrue: [self atSelector: selector put: #add]. self noteRemoveSelector: selector priorMethod: nil lastMethodInfo: changeRecord methodInfoFromRemoval] ifFalse: [self atSelector: selector put: changeType]]. ! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 3/28/2000 10:59'! hasNoChanges ^ changeTypes isEmpty and: [methodChanges isEmpty]! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 3/24/2000 09:36'! includesChangeType: changeType changeType == #new ifTrue: [^ changeTypes includes: #add]. "Backwd compat" ^ changeTypes includes: changeType! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 3/28/2000 15:14'! noteChangeType: changeSymbol ^ self noteChangeType: changeSymbol fromClass: nil! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 4/5/2000 08:05'! noteChangeType: changeSymbol fromClass: class (changeSymbol = #new or: [changeSymbol = #add]) ifTrue: [changeTypes add: #add. changeTypes remove: #change ifAbsent: []. revertable _ false. ^ self]. changeSymbol = #change ifTrue: [(changeTypes includes: #add) ifTrue: [^ self]. ^ changeTypes add: changeSymbol]. changeSymbol = #comment ifTrue: [^ changeTypes add: changeSymbol]. changeSymbol = #reorganize ifTrue: [^ changeTypes add: changeSymbol]. changeSymbol = #rename ifTrue: [^ changeTypes add: changeSymbol]. (changeSymbol beginsWith: 'oldName: ') ifTrue: ["Must only be used when assimilating other changeSets" (changeTypes includes: #add) ifTrue: [^ self]. priorName _ changeSymbol copyFrom: 'oldName: ' size + 1 to: changeSymbol size. ^ changeTypes add: #rename]. changeSymbol = #remove ifTrue: [(changeTypes includes: #add) ifTrue: [changeTypes add: #addedThenRemoved] ifFalse: [changeTypes add: #remove]. ^ changeTypes removeAllFoundIn: #(add change comment reorganize)]. self error: 'Unrecognized changeType'! ! !ClassChangeRecord methodsFor: 'all changes' stamp: 'di 5/16/2000 08:43'! trimHistory "Drop non-essential history." "Forget methods added and later removed" methodChanges keysAndValuesRemove: [:sel :chgRecord | chgRecord changeType == #addedThenRemoved]. "Forget renaming and reorganization of newly-added classes." (changeTypes includes: #add) ifTrue: [changeTypes removeAllFoundIn: #(rename reorganize)]. ! ! !ClassChangeRecord methodsFor: 'isolation layers' stamp: 'di 3/29/2000 22:00'! invokePhase1 | selector changeRecord type elements | revertable ifFalse: [^ self]. inForce ifTrue: [self error: 'Can invoke only when not in force.']. "Do the first part of the invoke operation -- no particular hurry." "Save the outer method dictionary for quick revert of method changes." priorMD _ self realClass methodDict. "Prepare a methodDictionary for switcheroo." thisMD _ self realClass methodDict copy. methodChanges associationsDo: [:assn | selector _ assn key. changeRecord _ assn value. type _ changeRecord changeType. type = #remove ifTrue: [thisMD removeKey: selector]. type = #add ifTrue: [thisMD at: selector put: changeRecord currentMethod]. type = #change ifTrue: [thisMD at: selector put: changeRecord currentMethod]. ]. "Replace the original organization (and comment)." priorOrganization _ self realClass organization. thisOrganization elementArray copy do: [:sel | (thisMD includesKey: sel) ifFalse: [thisOrganization removeElement: sel]]. #(DoIt DoItIn:) do: [:sel | thisMD removeKey: sel ifAbsent: []]. thisOrganization elementArray size = thisMD size ifFalse: [elements _ thisOrganization elementArray asSet. thisMD keysDo: [:sel | (elements includes: sel) ifFalse: [thisOrganization classify: sel under: (priorOrganization categoryOfElement: sel)]]]. self realClass organization: thisOrganization. ! ! !ClassChangeRecord methodsFor: 'isolation layers' stamp: 'di 3/29/2000 14:50'! invokePhase2 revertable ifFalse: [^ self]. "Do the second part of the revert operation. This must be very simple." "Replace original method dicts if there are method changes." self realClass methodDictionary: thisMD. "zap. Must flush Cache in outer loop." inForce _ true. ! ! !ClassChangeRecord methodsFor: 'isolation layers' stamp: 'di 3/30/2000 18:03'! realClass "Return the actual class (or meta), as determined from my name." thisName ifNil: [^ nil]. (thisName endsWith: ' class') ifTrue: [^ (Smalltalk at: (thisName copyFrom: 1 to: thisName size - 6) asSymbol ifAbsent: [^ nil]) class] ifFalse: [^ Smalltalk at: thisName ifAbsent: [^ nil]]! ! !ClassChangeRecord methodsFor: 'isolation layers' stamp: 'di 3/29/2000 14:50'! revokePhase1 revertable ifFalse: [^ self]. inForce ifFalse: [self error: 'Can revoke only when in force.']. "Do the first part of the revoke operation. This must be very simple." "Replace original method dict if there are method changes." self realClass methodDictionary: priorMD "zap. Must flush Cache in outer loop."! ! !ClassChangeRecord methodsFor: 'isolation layers' stamp: 'di 3/29/2000 14:50'! revokePhase2 revertable ifFalse: [^ self]. "Replace the original organization (and comment)." thisOrganization _ self realClass organization. self realClass organization: priorOrganization. inForce _ false. ! ! !ClassChangeRecord methodsFor: 'definition' stamp: 'di 3/27/2000 22:06'! checkCoherence "If I recreate the class then don't remove it" (changeTypes includes: #remove) ifTrue: [changeTypes remove: #remove. changeTypes add: #change]. (changeTypes includes: #addedThenRemoved) ifTrue: [changeTypes remove: #addedThenRemoved. changeTypes add: #add]. ! ! !ClassChangeRecord methodsFor: 'definition' stamp: 'di 3/27/2000 22:08'! notePriorDefinition: oldClass oldClass ifNil: [^ self]. priorDefinition ifNil: [priorDefinition _ oldClass definition]! ! !ClassChangeRecord methodsFor: 'definition' stamp: 'di 3/28/2000 09:12'! priorDefinition ^ priorDefinition! ! !ClassChangeRecord methodsFor: 'rename' stamp: 'di 5/8/2000 20:39'! noteNewName: newName thisName _ newName! ! !ClassChangeRecord methodsFor: 'rename' stamp: 'di 3/24/2000 09:38'! priorName ^ priorName! ! !ClassChangeRecord methodsFor: 'removal' stamp: 'di 4/4/2000 12:49'! forgetChangesIn: otherRecord "See forgetAllChangesFoundIn:. Used in culling changeSets." | cls otherMethodChanges selector actionToSubtract | (cls _ self realClass) == nil ifTrue: [^ self]. "We can do better now, though..." otherMethodChanges _ otherRecord methodChangeTypes. otherMethodChanges associationsDo: [:assoc | selector _ assoc key. actionToSubtract _ assoc value. (cls includesSelector: selector) ifTrue: [(#(add change) includes: actionToSubtract) ifTrue: [methodChanges removeKey: selector ifAbsent: []]] ifFalse: [(#(remove addedThenRemoved) includes: actionToSubtract) ifTrue: [methodChanges removeKey: selector ifAbsent: []]]]. changeTypes isEmpty ifFalse: [changeTypes removeAllFoundIn: otherRecord allChangeTypes. (changeTypes includes: #rename) ifFalse: [changeTypes removeAllSuchThat: [:x | x beginsWith: 'oldName: ']]]! ! !ClassChangeRecord methodsFor: 'removal' stamp: 'di 3/23/2000 12:27'! forgetClassRemoval self halt.! ! !ClassChangeRecord methodsFor: 'removal' stamp: 'di 4/1/2000 23:05'! isClassRemoval "NOTE: there are other removals with changeType #addedThenRemoved, but this message is used to write out removals in fileOut, and those cases should not be written out." ^ (changeTypes includes: #remove) or: [changeTypes includes: #removeClass]! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/28/2000 10:38'! atSelector: selector ifAbsent: absentBlock ^ (methodChanges at: selector ifAbsent: absentBlock) changeType! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/28/2000 11:01'! atSelector: selector put: changeType (self findOrMakeMethodChangeAt: selector priorMethod: nil) noteChangeType: changeType! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/28/2000 10:07'! changedSelectors "Return a set of the changed or removed selectors." ^ methodChanges keys! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 4/1/2000 10:45'! compileAll: newClass from: oldClass "Something about this class has changed. Locally retained methods must be recompiled. NOTE: You might think that if this changeSet is in force, then we can just note the new methods but a lower change set may override and be in force which would mean that only the overriding copies go recompiled. Just do it." | sel changeType changeRecord newMethod | methodChanges associationsDo: [:assn | sel _ assn key. changeRecord _ assn value. changeType _ changeRecord changeType. (changeType == #add or: [changeType == #change]) ifTrue: [newMethod _ newClass recompileNonResidentMethod: changeRecord currentMethod atSelector: sel from: oldClass. changeRecord noteNewMethod: newMethod]]! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/28/2000 11:01'! findOrMakeMethodChangeAt: selector priorMethod: priorMethod ^ methodChanges at: selector ifAbsent: [methodChanges at: selector put: (MethodChangeRecord new priorMethod: priorMethod)]! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/29/2000 16:26'! infoFromRemoval: selector ^ (methodChanges at: selector ifAbsent: [^ nil]) methodInfoFromRemoval ! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/24/2000 09:46'! methodChangeTypes "Return an old-style dictionary of method change types." | dict selector record | dict _ IdentityDictionary new. methodChanges associationsDo: [:assn | selector _ assn key. record _ assn value. dict at: selector put: record changeType]. ^ dict! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 4/1/2000 23:49'! methodChanges ^ methodChanges! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/28/2000 23:28'! noteNewMethod: newMethod selector: selector priorMethod: methodOrNil | methodChange | methodChange _ self findOrMakeMethodChangeAt: selector priorMethod: methodOrNil. methodOrNil == nil ifTrue: [methodChange noteChangeType: #add] ifFalse: [methodChange noteChangeType: #change]. methodChange noteNewMethod: newMethod. ! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/23/2000 23:00'! noteRemoveSelector: selector priorMethod: priorMethod lastMethodInfo: infoOrNil | methodChange | methodChange _ self findOrMakeMethodChangeAt: selector priorMethod: priorMethod. methodChange changeType == #add ifTrue: [methodChange noteChangeType: #addedThenRemoved] ifFalse: [methodChange noteChangeType: #remove]. infoOrNil ifNotNil: ["Save the source code pointer and category so can still browse old versions" methodChange noteMethodInfoFromRemoval: infoOrNil] ! ! !ClassChangeRecord methodsFor: 'method changes' stamp: 'di 3/23/2000 11:58'! removeSelector: selector "Remove all memory of changes associated with the argument, selector, in this class." methodChanges removeKey: selector ifAbsent: []! ! !ClassChangeRecord methodsFor: 'initialization' stamp: 'di 4/5/2000 08:11'! initFor: className revertable: isRevertable inForce _ isRevertable. changeTypes _ IdentitySet new. methodChanges _ IdentityDictionary new. priorName _ thisName _ className. revertable _ isRevertable and: [self realClass notNil]. revertable ifTrue: [priorMD _ self realClass methodDict copy. priorOrganization _ self realClass organization deepCopy]. ! ! !ClassChangeRecord methodsFor: 'initialization' stamp: 'di 9/21/2000 12:34'! zapHistory "Drop all recorded information not needed to simply keep track of what has been changed. Saves a lot of space." methodChanges do: [:r | r noteNewMethod: nil]. "Drop all refes to old methods" thisOrganization _ nil. priorOrganization _ nil. thisComment _ nil. priorComment _ nil. thisMD _ nil. priorMD _ nil.! ! ClassCategoryReader subclass: #ClassCommentReader instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !ClassCommentReader methodsFor: 'as yet unclassified' stamp: 'tk 12/15/97 15:56'! scanFrom: aStream "File in the class comment from aStream. Not string-i-fied, just a text, exactly as it is in the browser. Move to changes file." class theNonMetaClass classComment: (aStream nextChunkText). "Writes it on the disk and saves a RemoteString ref"! ! !ClassCommentReader methodsFor: 'as yet unclassified' stamp: 'tk 1/27/2000 22:56'! scanFromNoCompile: aStream "File in the class comment from aStream. Not string-i-fied, just a text, exactly as it is in the browser. Move to changes file." self scanFrom: aStream. "for comments, the same as usual"! ! Behavior subclass: #ClassDescription instanceVariableNames: 'instanceVariables organization ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !ClassDescription commentStamp: '' prior: 0! I add a number of facilities to basic Behaviors: Named instance variables Category organization for methods The notion of a name of this class (implemented as subclass responsibility) The maintenance of a ChangeSet, and logging changes on a file Most of the mechanism for fileOut. I am an abstract class, in particular, my facilities are intended for inheritance by two subclasses, Class and Metaclass. The slots 'organization' and 'methodDict' should ONLY be accessed by message in order for things to work during ImageSegment>>discoverActiveClasses (q.v.).! !ClassDescription methodsFor: 'initialize-release' stamp: 'ar 11/22/1999 10:09'! doneCompiling "A ClassBuilder has finished the compilation of the receiver. This message is a notification for a class that needs to do some cleanup / reinitialization after it has been recompiled."! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'ar 7/14/1999 04:41'! obsolete "Make the receiver obsolete." superclass removeSubclass: self. organization _ nil. super obsolete.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'di 7/21/1999 11:05'! superclass: aClass methodDictionary: mDict format: fmt "Basic initialization of the receiver" super superclass: aClass methodDictionary: mDict format: fmt. organization _ nil. instanceVariables _ nil.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'tk 10/4/1999 09:43'! updateInstances: oldInstances from: oldClass isMeta: isMeta "Recreate any existing instances of the argument, oldClass, as instances of the receiver, which is a newly changed class. Permute variables as necessary." "If there are any contexts having an old instance as receiver it might crash the system because the layout has changed, and the method only knows about the old layout." | map variable instSize newInstances | oldInstances isEmpty ifTrue:[^self]. "no instances to convert" isMeta ifTrue: [ oldInstances size = 1 ifFalse:[^self error:'Metaclasses can only have one instance']. self soleInstance class == self ifTrue:[ ^self error:'Metaclasses can only have one instance']]. map _ self instVarMappingFrom: oldClass. variable _ self isVariable. instSize _ self instSize. newInstances _ Array new: oldInstances size. 1 to: oldInstances size do:[:i| newInstances at: i put: ( self newInstanceFrom: (oldInstances at: i) variable: variable size: instSize map: map)]. "Now perform a bulk mutation of old instances into new ones" oldInstances elementsExchangeIdentityWith: newInstances.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'RAA 12/20/2000 23:27'! updateInstancesFrom: oldClass "Recreate any existing instances of the argument, oldClass, as instances of the receiver, which is a newly changed class. Permute variables as necessary." "ar 7/15/1999: The updating below is possibly dangerous. If there are any contexts having an old instance as receiver it might crash the system if the new receiver in which the context is executed has a different layout. See bottom below for a simple example:" | oldInstances | Smalltalk garbageCollect. "ensure that allInstances is correct" oldInstances _ oldClass allInstances asArray. self updateInstances: oldInstances from: oldClass isMeta: self isMeta. "Now fix up instances in segments that are out on the disk." ImageSegment allSubInstancesDo: [:seg | seg segUpdateInstancesOf: oldClass toBe: self isMeta: self isMeta]. oldInstances _ nil. Smalltalk garbageCollect. "ensure that old instances are gone" " | crashingBlock class | class _ Object subclass: #CrashTestDummy instanceVariableNames: 'instVar' classVariableNames: '' poolDictionaries: '' category: 'Crash-Test'. class compile:'instVar: value instVar _ value'. class compile:'crashingBlock ^[instVar]'. crashingBlock _ (class new) instVar: 42; crashingBlock. Object subclass: #CrashTestDummy instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Crash-Test'. crashingBlock. crashingBlock value. " ! ! !ClassDescription methodsFor: 'accessing'! classVersion "Default. Any class may return a later version to inform readers that use ReferenceStream. 8/17/96 tk" ^ 0! ! !ClassDescription methodsFor: 'accessing' stamp: 'di 2/9/2000 17:54'! comment "Answer the receiver's comment. (If missing, supply a template) " | aString | aString _ self theNonMetaClass organization classComment. aString isEmpty ifFalse: [^ aString]. ^ 'Main comment stating the purpose of this class and relevant relationship to other classes. Possible useful expressions for doIt or printIt. Structure: instVar1 type -- comment about the purpose of instVar1 instVar2 type -- comment about the purpose of instVar2 Any further useful comments about the general approach of this implementation.'! ! !ClassDescription methodsFor: 'accessing' stamp: 'tk 12/16/97 07:49'! comment: aStringOrText "Set the receiver's comment to be the argument, aStringOrText." self theNonMetaClass classComment: aStringOrText. Smalltalk changes commentClass: self! ! !ClassDescription methodsFor: 'accessing' stamp: 'sw 9/8/1998 14:43'! comment: aStringOrText stamp: aStamp "Set the receiver's comment to be the argument, aStringOrText." self theNonMetaClass classComment: aStringOrText stamp: aStamp. Smalltalk changes commentClass: self theNonMetaClass! ! !ClassDescription methodsFor: 'accessing'! isMeta ^ false! ! !ClassDescription methodsFor: 'accessing'! theNonMetaClass "Sent to a class or metaclass, always return the class" ^self! ! !ClassDescription methodsFor: 'copying'! copy: sel from: class "Install the method associated with the first argument, sel, a message selector, found in the method dictionary of the second argument, class, as one of the receiver's methods. Classify the message under -As yet not classified-." self copy: sel from: class classified: nil! ! !ClassDescription methodsFor: 'copying' stamp: 'di 2/17/2000 22:35'! copy: sel from: class classified: cat "Install the method associated with the first arugment, sel, a message selector, found in the method dictionary of the second argument, class, as one of the receiver's methods. Classify the message under the third argument, cat." | code category | "Useful when modifying an existing class" code _ class sourceMethodAt: sel. code == nil ifFalse: [cat == nil ifTrue: [category _ class organization categoryOfElement: sel] ifFalse: [category _ cat]. (self methodDict includesKey: sel) ifTrue: [code asString = (self sourceMethodAt: sel) asString ifFalse: [self error: self name , ' ' , sel , ' will be redefined if you proceed.']]. self compile: code classified: category]! ! !ClassDescription methodsFor: 'copying'! copyAll: selArray from: class "Install all the methods found in the method dictionary of the second argument, class, as the receiver's methods. Classify the messages under -As yet not classified-." self copyAll: selArray from: class classified: nil! ! !ClassDescription methodsFor: 'copying'! copyAll: selArray from: class classified: cat "Install all the methods found in the method dictionary of the second argument, class, as the receiver's methods. Classify the messages under the third argument, cat." selArray do: [:s | self copy: s from: class classified: cat]! ! !ClassDescription methodsFor: 'copying'! copyAllCategoriesFrom: aClass "Specify that the categories of messages for the receiver include all of those found in the class, aClass. Install each of the messages found in these categories into the method dictionary of the receiver, classified under the appropriate categories." aClass organization categories do: [:cat | self copyCategory: cat from: aClass]! ! !ClassDescription methodsFor: 'copying'! copyCategory: cat from: class "Specify that one of the categories of messages for the receiver is cat, as found in the class, class. Copy each message found in this category." self copyCategory: cat from: class classified: cat! ! !ClassDescription methodsFor: 'copying'! copyCategory: cat from: aClass classified: newCat "Specify that one of the categories of messages for the receiver is the third argument, newCat. Copy each message found in the category cat in class aClass into this new category." self copyAll: (aClass organization listAtCategoryNamed: cat) from: aClass classified: newCat! ! !ClassDescription methodsFor: 'copying' stamp: 'sw 6/16/1998 15:01'! copyMethodDictionaryFrom: donorClass "Copy the method dictionary of the donor class over to the receiver" methodDict _ donorClass copyOfMethodDictionary. organization _ donorClass organization deepCopy! ! !ClassDescription methodsFor: 'printing'! classVariablesString "Answer a string of my class variable names separated by spaces." | aStream | aStream _ WriteStream on: (String new: 100). self classPool keys asSortedCollection do: [:key | aStream nextPutAll: key; space]. ^aStream contents! ! !ClassDescription methodsFor: 'printing' stamp: 'di 6/7/2000 22:23'! instanceVariablesString "Answer a string of my instance variable names separated by spaces." ^ String streamContents: [:strm | self instVarNames do: [:varName | strm nextPutAll: varName; space]]! ! !ClassDescription methodsFor: 'printing'! printOn: aStream aStream nextPutAll: self name! ! !ClassDescription methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:05'! printOnStream: aStream aStream print: self name! ! !ClassDescription methodsFor: 'printing' stamp: 'di 12/19/1999 14:37'! sharedPoolsString "Answer a string of my shared pool names separated by spaces." | aStream | aStream _ WriteStream on: (String new: 100). self sharedPools do: [:x | aStream nextPutAll: (self environment keyAtIdentityValue: x ifAbsent: ['private']); space]. ^ aStream contents! ! !ClassDescription methodsFor: 'printing'! storeOn: aStream "Classes and Metaclasses have global names." aStream nextPutAll: self name! ! !ClassDescription methodsFor: 'instance variables'! addInstVarName: aString "Add the argument, aString, as one of the receiver's instance variables." self subclassResponsibility! ! !ClassDescription methodsFor: 'instance variables' stamp: 'sw 10/23/2000 18:05'! allInstVarNamesEverywhere "Answer the set of inst var names used by the receiver, all superclasses, and all subclasses" | aList | aList _ OrderedCollection new. (self allSuperclasses , self withAllSubclasses asOrderedCollection) do: [:cls | aList addAll: cls instVarNames]. ^ aList asSet "BorderedMorph allInstVarNamesEverywhere"! ! !ClassDescription methodsFor: 'instance variables' stamp: 'RAA 8/9/1999 19:32'! browseClassVarRefs "1/17/96 sw: moved here from Browser so that it could be used from a variety of places." | lines labelStream vars allVars index owningClasses | lines _ OrderedCollection new. allVars _ OrderedCollection new. owningClasses _ OrderedCollection new. labelStream _ WriteStream on: (String new: 200). self withAllSuperclasses reverseDo: [:class | vars _ class classVarNames asSortedCollection. vars do: [:var | labelStream nextPutAll: var; cr. allVars add: var. owningClasses add: class]. vars isEmpty ifFalse: [lines add: allVars size]]. labelStream contents isEmpty ifTrue: [^1 beep]. "handle nil superclass better" labelStream skip: -1 "cut last CR". index _ (PopUpMenu labels: labelStream contents lines: lines) startUp. index = 0 ifTrue: [^ self]. Smalltalk browseAllCallsOn: ((owningClasses at: index) classPool associationAt: (allVars at: index))! ! !ClassDescription methodsFor: 'instance variables'! browseClassVariables "Put up a browser showing the receiver's class variables. 2/1/96 sw" self classPool inspectWithLabel: 'Class Variables in ', self name! ! !ClassDescription methodsFor: 'instance variables'! browseInstVarDefs "Copied from browseInstVarRefs. Should be consolidated some day. 7/29/96 di 7/30/96 sw: did the consolidation" self chooseInstVarThenDo: [:aVar | self browseAllStoresInto: aVar]! ! !ClassDescription methodsFor: 'instance variables'! browseInstVarRefs "1/16/96 sw: moved here from Browser so that it could be used from a variety of places. 7/30/96 sw: call chooseInstVarThenDo: to get the inst var choice" self chooseInstVarThenDo: [:aVar | self browseAllAccessesTo: aVar]! ! !ClassDescription methodsFor: 'instance variables' stamp: 'di 11/9/1998 20:21'! checkForInstVarsOK: instVarString "Return true if instVarString does no include any names used in a subclass" | instVarArray | instVarArray _ Scanner new scanFieldNames: instVarString. self allSubclasses do: [:cl | cl instVarNames do: [:n | (instVarArray includes: n) ifTrue: [self error: n , ' is already used in ' , cl name. ^ false]]]. ^ true! ! !ClassDescription methodsFor: 'instance variables' stamp: 'sw 9/28/1999 17:04'! chooseInstVarAlphabeticallyThenDo: aBlock | allVars index | "Put up a menu of all the instance variables in the receiver, presented in alphabetical order, and when the user chooses one, evaluate aBlock with the chosen variable as its parameter." allVars _ self allInstVarNames asSortedArray. allVars isEmpty ifTrue: [^ self inform: 'There are no instance variables']. index _ (PopUpMenu labelArray: allVars lines: #()) startUpWithCaption: 'Instance variables in ', self name. index = 0 ifTrue: [^ self]. aBlock value: (allVars at: index)! ! !ClassDescription methodsFor: 'instance variables' stamp: 'ls 12/5/1999 13:40'! chooseInstVarThenDo: aBlock "Put up a menu of all the instance variables in the receiver, and when the user chooses one, evaluate aBlock with the chosen variable as its parameter. If the list is 6 or larger, then offer an alphabetical formulation as an alternative. triggered by a 'show alphabetically' item at the top of the list." | lines labelStream vars allVars index count offerAlpha | (count _ self allInstVarNames size) = 0 ifTrue: [^ self inform: 'There are no instance variables.']. allVars _ OrderedCollection new. lines _ OrderedCollection new. labelStream _ WriteStream on: (String new: 200). (offerAlpha _ count > 5) ifTrue: [lines add: 1. allVars add: 'show alphabetically'. labelStream nextPutAll: allVars first; cr]. self withAllSuperclasses reverseDo: [:class | vars _ class instVarNames. vars do: [:var | labelStream nextPutAll: var; cr. allVars add: var]. vars isEmpty ifFalse: [lines add: allVars size]]. labelStream skip: -1 "cut last CR". (lines size > 0 and: [lines last = allVars size]) ifTrue: [lines removeLast]. "dispense with inelegant line beneath last item" index _ (PopUpMenu labels: labelStream contents lines: lines) startUpWithCaption: 'Instance variables in ', self name. index = 0 ifTrue: [^ self]. (index = 1 and: [offerAlpha]) ifTrue: [^ self chooseInstVarAlphabeticallyThenDo: aBlock]. aBlock value: (allVars at: index)! ! !ClassDescription methodsFor: 'instance variables' stamp: 'sw 5/27/1999 16:46'! classThatDefinesInstanceVariable: instVarName (instanceVariables notNil and: [instanceVariables includes: instVarName asString]) ifTrue: [^ self]. ^ superclass ifNotNil: [superclass classThatDefinesInstanceVariable: instVarName]! ! !ClassDescription methodsFor: 'instance variables'! forceNewFrom: anArray "Create a new instance of the class and fill its instance variables up with the array." | object max | object _ self new. max _ self instSize. anArray doWithIndex: [:each :index | index > max ifFalse: [object instVarAt: index put: each]]. ^ object! ! !ClassDescription methodsFor: 'instance variables'! instVarNames "Answer an Array of the receiver's instance variable names." instanceVariables == nil ifTrue: [^#()] ifFalse: [^instanceVariables]! ! !ClassDescription methodsFor: 'instance variables'! removeInstVarName: aString "Remove the argument, aString, as one of the receiver's instance variables. Create an error notification if the argument is not found." self subclassResponsibility! ! !ClassDescription methodsFor: 'instance variables' stamp: 'di 9/14/1998 08:40'! renameInstVar: oldName to: newName (self confirm: 'WARNING: Renaming of instance variables is subject to substitution ambiguities. Do you still wish to attempt it?') ifFalse: [self halt]. "...In other words, this does a dumb text search-and-replace, which might improperly alter, eg, a literal string. As long as the oldName is unique, everything should work jes' fine. - di" ^ self renameSilentlyInstVar: oldName to: newName! ! !ClassDescription methodsFor: 'instance variables' stamp: 'tk 12/12/2000 11:59'! renameSilentlyInstVar: old to: new | i oldName newName | oldName _ old asString. newName _ new asString. (i _ instanceVariables indexOf: oldName) = 0 ifTrue: [self error: oldName , ' is not defined in ', self name]. self allSuperclasses , self withAllSubclasses asOrderedCollection do: [:cls | (cls instVarNames includes: newName) ifTrue: [self error: newName , ' is already used in ', cls name]]. instanceVariables replaceFrom: i to: i with: (Array with: newName). self replaceSilently: oldName to: newName. "replace in text body of all methods"! ! !ClassDescription methodsFor: 'instance variables' stamp: 'tk 12/12/2000 11:58'! replaceSilently: old to: new "text-replace any part of a method. Used for class and pool variables. Don't touch the header. Not guaranteed to work if name appears in odd circumstances" | oldCode newCode parser header body sels oldName newName | oldName _ old asString. newName _ new asString. self withAllSubclasses do: [:cls | sels _ cls selectors. sels removeAllFoundIn: #(DoIt DoItIn:). sels do: [:sel | oldCode _ cls sourceCodeAt: sel. "Don't make changes in the method header" (parser _ cls parserClass new) parseSelector: oldCode. header _ oldCode copyFrom: 1 to: (parser endOfLastToken min: oldCode size). body _ header size > oldCode size ifTrue: [''] ifFalse: [oldCode copyFrom: header size+1 to: oldCode size]. newCode _ header , (body copyReplaceTokens: oldName with: newName). newCode ~= oldCode ifTrue: [cls compile: newCode classified: (cls organization categoryOfElement: sel) notifying: nil]]. cls isMeta ifFalse: [oldCode _ cls comment. newCode _ oldCode copyReplaceTokens: oldName with: newName. newCode ~= oldCode ifTrue: [cls comment: newCode]]]! ! !ClassDescription methodsFor: 'method dictionary' stamp: 'di 2/17/2000 22:17'! induceMDFault "Stache a copy of the methodDict in the organization slot (hack!!), and set the methodDict to nil. This will induce an MD fault on any message send. See: ClassDescription>>recoverFromMDFault and ImageSegment>>discoverActiveClasses." organization _ Array with: methodDict with: organization. methodDict _ nil. self flushCache! ! !ClassDescription methodsFor: 'method dictionary' stamp: 'di 3/27/1999 23:53'! recoverFromMDFault (organization isMemberOf: Array) ifFalse: [^ self error: 'oops']. methodDict _ organization first. organization _ organization second.! ! !ClassDescription methodsFor: 'method dictionary'! removeCategory: aString "Remove each of the messages categorized under aString in the method dictionary of the receiver. Then remove the category aString." | categoryName | categoryName _ aString asSymbol. (self organization listAtCategoryNamed: categoryName) do: [:sel | self removeSelector: sel]. self organization removeCategory: categoryName! ! !ClassDescription methodsFor: 'method dictionary' stamp: 'di 3/23/2000 23:08'! removeSelector: selector | priorMethod | "Remove the message whose selector is given from the method dictionary of the receiver, if it is there. Answer nil otherwise." (self methodDict includesKey: selector) ifFalse: [^ nil]. priorMethod _ self compiledMethodAt: selector. Smalltalk changes removeSelector: selector class: self priorMethod: priorMethod lastMethodInfo: {priorMethod sourcePointer. (self whichCategoryIncludesSelector: selector)}. super removeSelector: selector. self organization removeElement: selector. self acceptsLoggingOfCompilation ifTrue: [Smalltalk logChange: self name , ' removeSelector: #' , selector]! ! !ClassDescription methodsFor: 'method dictionary' stamp: 'di 2/17/2000 22:34'! removeSelectorUnlogged: aSymbol "Remove the message whose selector is aSymbol from the method dictionary of the receiver, if it is there. Answer nil otherwise. Do not log the action either to the current change set or to the changes log" (self methodDict includesKey: aSymbol) ifFalse: [^ nil]. super removeSelector: aSymbol. self organization removeElement: aSymbol! ! !ClassDescription methodsFor: 'method dictionary' stamp: 'sw 5/18/1999 10:11'! ultimateSourceCodeAt: selector ifAbsent: aBlock "Return the source code at selector, deferring to superclass if necessary" ^ self sourceCodeAt: selector ifAbsent: [superclass ifNil: [aBlock value] ifNotNil: [superclass ultimateSourceCodeAt: selector ifAbsent: aBlock]]! ! !ClassDescription methodsFor: 'organization'! category "Answer the system organization category for the receiver." ^SystemOrganization categoryOfElement: self name! ! !ClassDescription methodsFor: 'organization'! category: cat "Categorize the receiver under the system category, cat, removing it from any previous categorization." (cat isKindOf: String) ifTrue: [SystemOrganization classify: self name under: cat asSymbol] ifFalse: [self errorCategoryName]! ! !ClassDescription methodsFor: 'organization' stamp: 'di 2/17/2000 22:36'! organization "Answer the instance of ClassOrganizer that represents the organization of the messages of the receiver." organization ifNil: [organization _ ClassOrganizer defaultList: self methodDict keys asSortedCollection asArray]. (organization isMemberOf: Array) ifTrue: [self recoverFromMDFault]. ^ organization! ! !ClassDescription methodsFor: 'organization' stamp: 'tk 6/21/1999 12:59'! organization: aClassOrg "Install an instance of ClassOrganizer that represents the organization of the messages of the receiver." organization _ aClassOrg! ! !ClassDescription methodsFor: 'organization' stamp: 'di 7/17/97 00:06'! whichCategoryIncludesSelector: aSelector "Answer the category of the argument, aSelector, in the organization of the receiver, or answer nil if the receiver does not inlcude this selector." (self includesSelector: aSelector) ifTrue: [^ self organization categoryOfElement: aSelector] ifFalse: [^nil]! ! !ClassDescription methodsFor: 'organization' stamp: 'di 9/10/1999 10:21'! zapAllMethods "Remove all methods in this class which is assumed to be obsolete" methodDict _ MethodDictionary new. self isMeta ifFalse: [self class zapAllMethods]! ! !ClassDescription methodsFor: 'organization'! zapOrganization "Remove the organization of this class by message categories. This is typically done to save space in small systems. Classes and methods created or filed in subsequently will, nonetheless, be organized" organization _ nil. self isMeta ifFalse: [self class zapOrganization]! ! !ClassDescription methodsFor: 'compiling'! acceptsLoggingOfCompilation "weird name is so that it will come lexically before #compile, so that a clean build can make it through. 7/7/96 sw" ^ true! ! !ClassDescription methodsFor: 'compiling'! compile: code classified: heading "Compile the argument, code, as source code in the context of the receiver and install the result in the receiver's method dictionary under the classification indicated by the second argument, heading. nil is to be notified if an error occurs. The argument code is either a string or an object that converts to a string or a PositionableStream on an object that converts to a string." ^self compile: code classified: heading notifying: (SyntaxError new category: heading)! ! !ClassDescription methodsFor: 'compiling' stamp: 'sw 8/21/97 00:26'! compile: text classified: category notifying: requestor | stamp | stamp _ self acceptsLoggingOfCompilation ifTrue: [Utilities changeStamp] ifFalse: [nil]. ^ self compile: text classified: category withStamp: stamp notifying: requestor ! ! !ClassDescription methodsFor: 'compiling' stamp: 'di 2/17/2000 22:34'! compile: text classified: category withStamp: changeStamp notifying: requestor | selector priorMethod method methodNode newText | method _ self compile: text asString notifying: requestor trailer: #(0 0 0 0) ifFail: [^nil] elseSetSelectorAndNode: [:sel :node | selector _ sel. priorMethod _ self methodDict at: selector ifAbsent: [nil]. methodNode _ node]. self acceptsLoggingOfCompilation ifTrue: [newText _ ((requestor == nil or: [requestor isKindOf: SyntaxError]) not and: [Preferences confirmFirstUseOfStyle]) ifTrue: [text askIfAddStyle: priorMethod req: requestor] ifFalse: [text]. method putSource: newText fromParseNode: methodNode class: self category: category withStamp: changeStamp inFile: 2 priorMethod: priorMethod]. self organization classify: selector under: category. ^selector! ! !ClassDescription methodsFor: 'compiling'! compile: code notifying: requestor "Refer to the comment in Behavior|compile:notifying:." ^self compile: code classified: ClassOrganizer default notifying: requestor! ! !ClassDescription methodsFor: 'compiling' stamp: 'di 3/23/2000 20:47'! compile: code notifying: requestor trailer: bytes ifFail: failBlock elseSetSelectorAndNode: selAndNodeBlock "Intercept this message in order to remember system changes. 5/15/96 sw: modified so that if the class does not wish its methods logged in the changes file, then they also won't be accumulated in the current change set. 7/12/96 sw: use wantsChangeSetLogging to determine whether to put in change set" | methodNode selector newMethod priorMethodOrNil | methodNode _ self compilerClass new compile: code in: self notifying: requestor ifFail: failBlock. selector _ methodNode selector. selAndNodeBlock value: selector value: methodNode. requestor ifNotNil: ["Note this change for recent submissions list" Utilities noteMethodSubmission: selector forClass: self]. methodNode encoder requestor: requestor. "Why was this not preserved?" newMethod _ methodNode generate: bytes. priorMethodOrNil _ (methodDict includesKey: selector) ifTrue: [self compiledMethodAt: selector] ifFalse: [nil]. Smalltalk changes noteNewMethod: newMethod forClass: self selector: selector priorMethod: priorMethodOrNil. self addSelector: selector withMethod: newMethod. ^ newMethod! ! !ClassDescription methodsFor: 'compiling' stamp: 'sw 7/1/1999 23:07'! compileProgrammatically: code classified: cat | oldInitials | oldInitials _ Utilities authorInitialsPerSe. Utilities setAuthorInitials: 'programmatic'. self compile: code classified: cat. Utilities setAuthorInitials: oldInitials. ! ! !ClassDescription methodsFor: 'compiling' stamp: 'sw 8/11/1998 14:40'! compileUnlogged: text classified: category notifying: requestor | selector | self compile: text asString notifying: requestor trailer: #(0 0 0 0) ifFail: [^ nil] elseSetSelectorAndNode: [:sel :node | selector _ sel]. self organization classify: selector under: category. ^ selector! ! !ClassDescription methodsFor: 'compiling' stamp: 'ar 7/20/1999 11:04'! moveInstVarNamed: instVarName to: anotherClass after: prevInstVarName "Move the given instance variable to another class." self == anotherClass ifFalse:[ self notify:'Warning:' asText allBold,' moving ', instVarName printString,' from ', self name,' to ', anotherClass name,' will not be recorded in the change set correctly. Proceed to do it anyways.']. ^(ClassBuilder new) moveInstVarNamed: instVarName from: self to: anotherClass after: prevInstVarName! ! !ClassDescription methodsFor: 'compiling'! wantsChangeSetLogging "Answer whether code submitted for the receiver should be remembered by the changeSet mechanism. 7/12/96 sw" ^ true! ! !ClassDescription methodsFor: 'compiling' stamp: 'sw 7/31/2000 12:55'! wantsRecompilationProgressReported "Answer whether the receiver would like progress of its recompilation reported interactively to the user." ^ true! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sw 9/8/1998 14:44'! classComment: aString "Store the comment, aString or Text or RemoteString, associated with the class we are orgainzing. Empty string gets stored only if had a non-empty one before." ^ self classComment: aString stamp: ''! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 3/28/2000 14:34'! classComment: aString stamp: aStamp "Store the comment, aString or Text or RemoteString, associated with the class we are organizing. Empty string gets stored only if had a non-empty one before." | ptr header file oldCommentRemoteStr | (aString isKindOf: RemoteString) ifTrue: [^ self organization classComment: aString]. oldCommentRemoteStr _ self organization commentRemoteStr. (aString size = 0) & (oldCommentRemoteStr == nil) ifTrue: [^ organization classComment: nil]. "never had a class comment, no need to write empty string out" ptr _ oldCommentRemoteStr ifNil: [0] ifNotNil: [oldCommentRemoteStr sourcePointer]. SourceFiles ifNotNil: [(file _ SourceFiles at: 2) ifNotNil: [ file setToEnd; cr; nextPut: $!!. "directly" "Should be saying (file command: 'H3') for HTML, but ignoring it here" header _ String streamContents: [:strm | strm nextPutAll: self name; nextPutAll: ' commentStamp: '. aStamp storeOn: strm. strm nextPutAll: ' prior: '; nextPutAll: ptr printString]. file nextChunkPut: header]]. Smalltalk changes commentClass: self. aStamp size > 0 ifTrue: [self commentStamp: aStamp]. organization classComment: (RemoteString newString: aString onFileNumber: 2). ! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'tk 12/13/97 14:20'! commentFollows "Answer a ClassCommentReader who will scan in the comment." ^ ClassCommentReader new setClass: self category: #Comment "False commentFollows inspect"! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sw 9/2/1998 14:22'! commentStamp: changeStamp self organization commentStamp: changeStamp. ^ self commentStamp: changeStamp prior: 0! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'tk 12/13/97 14:21'! commentStamp: changeStamp prior: indexAndOffset "Prior source link ignored when filing in." ^ ClassCommentReader new setClass: self category: #Comment changeStamp: changeStamp ! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/7/2000 22:46'! definition "Answer a String that defines the receiver in good old ST-80." ^ self definitionST80! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/7/2000 22:38'! definitionST80 "Answer a String that defines the receiver." | aStream path | aStream _ WriteStream on: (String new: 300). superclass == nil ifTrue: [aStream nextPutAll: 'nil'] ifFalse: [path _ ''. self environment scopeFor: superclass name from: nil envtAndPathIfFound: [:envt :remotePath | path _ remotePath]. aStream nextPutAll: path , superclass name]. aStream nextPutAll: self kindOfSubclass; store: self name. aStream cr; tab; nextPutAll: 'instanceVariableNames: '; store: self instanceVariablesString. aStream cr; tab; nextPutAll: 'classVariableNames: '; store: self classVariablesString. aStream cr; tab; nextPutAll: 'poolDictionaries: '; store: self sharedPoolsString. aStream cr; tab; nextPutAll: 'category: '; store: (SystemOrganization categoryOfElement: self name) asString. ^ aStream contents! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/7/2000 22:42'! definitionST80: isST80 "Answer a String that defines the receiver." | aStream path | isST80 ifTrue: [^ self definitionST80]. aStream _ WriteStream on: (String new: 300). superclass == nil ifTrue: [aStream nextPutAll: 'nil'] ifFalse: [path _ ''. self environment scopeFor: superclass name from: nil envtAndPathIfFound: [:envt :remotePath | path _ remotePath]. aStream nextPutAll: path , superclass name]. aStream nextPutKeyword: self kindOfSubclass withArg: self name. aStream cr; tab; nextPutKeyword: 'instanceVariableNames: ' withArg: self instanceVariablesString. aStream cr; tab; nextPutKeyword: 'classVariableNames: 'withArg: self classVariablesString. aStream cr; tab; nextPutKeyword: 'poolDictionaries: ' withArg: self sharedPoolsString. aStream cr; tab; nextPutKeyword: 'category: ' withArg: (SystemOrganization categoryOfElement: self name) asString. ^ aStream contents! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 10:06'! fileOutCategory: catName ^ self fileOutCategory: catName asHtml: false! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 10:05'! fileOutCategory: catName asHtml: useHtml "FileOut the named category, possibly in Html format." | fileStream | fileStream _ useHtml ifTrue: [(FileStream newFileNamed: self name , '-' , catName , '.html') asHtml] ifFalse: [FileStream newFileNamed: self name , '-' , catName , '.st']. fileStream header; timeStamp. self fileOutCategory: catName on: fileStream moveSource: false toFile: 0. fileStream trailer; close! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 10/15/1999 14:45'! fileOutCategory: aSymbol on: aFileStream moveSource: moveSource toFile: fileIndex "File a description of the receiver's category, aString, onto aFileStream. If moveSource, is true, then set the method source pointer to the new file position. Note when this method is called with moveSource=true, it is condensing the .sources file, and should only write one preamble per method category." | selectors | aFileStream cr. selectors := (aSymbol asString = ClassOrganizer allCategory) ifTrue: [ self organization allMethodSelectors ] ifFalse: [ self organization listAtCategoryNamed: aSymbol ]. "Overridden to preserve author stamps in sources file regardless" selectors do: [:sel | self printMethodChunk: sel withPreamble: true on: aFileStream moveSource: moveSource toFile: fileIndex]. ^ self! ! !ClassDescription methodsFor: 'fileIn/Out'! fileOutChangedMessages: aSet on: aFileStream "File a description of the messages of the receiver that have been changed (i.e., are entered into the argument, aSet) onto aFileStream." self fileOutChangedMessages: aSet on: aFileStream moveSource: false toFile: 0! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/26/97 21:41'! fileOutChangedMessages: aSet on: aFileStream moveSource: moveSource toFile: fileIndex "File a description of the messages of this class that have been changed (i.e., are entered into the argument, aSet) onto aFileStream. If moveSource, is true, then set the method source pointer to the new file position. Note when this method is called with moveSource=true, it is condensing the .changes file, and should only write a preamble for every method." | org sels | (org _ self organization) categories do: [:cat | sels _ (org listAtCategoryNamed: cat) select: [:sel | aSet includes: sel]. sels do: [:sel | self printMethodChunk: sel withPreamble: true on: aFileStream moveSource: moveSource toFile: fileIndex]]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 15:52'! fileOutMethod: selector "Write source code of a single method on a file. Make up a name for the file." self fileOutMethod: selector asHtml: false! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 15:51'! fileOutMethod: selector asHtml: useHtml "Write source code of a single method on a file in .st or .html format" | fileStream nameBody | (self includesSelector: selector) ifFalse: [^ self halt: 'Selector not found']. nameBody _ self name , '-' , (selector copyReplaceAll: ':' with: ''). fileStream _ useHtml ifTrue: [(FileStream newFileNamed: nameBody , '.html') asHtml] ifFalse: [FileStream newFileNamed: nameBody , '.st']. fileStream header; timeStamp. self printMethodChunk: selector withPreamble: true on: fileStream moveSource: false toFile: 0. fileStream close! ! !ClassDescription methodsFor: 'fileIn/Out'! fileOutOn: aFileStream "File a description of the receiver on aFileStream." self fileOutOn: aFileStream moveSource: false toFile: 0! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sw 1/15/98 23:38'! fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex "File a description of the receiver on aFileStream. If the boolean argument, moveSource, is true, then set the trailing bytes to the position of aFileStream and to fileIndex in order to indicate where to find the source code." aFileStream command: 'H3'. aFileStream nextChunkPut: self definition. aFileStream command: '/H3'. self organization putCommentOnFile: aFileStream numbered: fileIndex moveSource: moveSource forClass: self. self organization categories do: [:heading | self fileOutCategory: heading on: aFileStream moveSource: moveSource toFile: fileIndex]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/28/97 20:35'! fileOutOrganizationOn: aFileStream "File a description of the receiver's organization on aFileStream." aFileStream cr; nextPut: $!!. aFileStream nextChunkPut: self name, ' reorganize'; cr. aFileStream nextChunkPut: self organization printString; cr! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 11/13/1998 15:25'! methods "Answer a ClassCategoryReader for compiling messages that are not classified, as in fileouts made with Smalltalk/V" ^ ClassCategoryReader new setClass: self category: ClassOrganizer default! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'tk 12/29/97 13:00'! methodsFor: categoryName "Answer a ClassCategoryReader for compiling the messages in the category, categoryName, of the receiver." ^ ClassCategoryReader new setClass: self category: categoryName asSymbol "(False methodsFor: 'logical operations') inspect"! ! !ClassDescription methodsFor: 'fileIn/Out'! methodsFor: aString priorSource: sourcePosition inFile: fileIndex "Prior source pointer ignored when filing in." ^ self methodsFor: aString! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 6/13/97 13:51'! methodsFor: categoryName stamp: changeStamp ^ self methodsFor: categoryName stamp: (Utilities fixStamp: changeStamp) prior: 0! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'tk 8/15/1998 22:02'! methodsFor: categoryName stamp: changeStamp prior: indexAndOffset "Prior source link ignored when filing in." ^ ClassCategoryReader new setClass: self category: categoryName asSymbol changeStamp: changeStamp "Most importantly, return the new ClassCategoryReader, so a fileIn will let it seize control. So method will be placed in the proper category. See the transfer of control where ReadWriteStream fileIn calls scanFrom:"! ]style[(65 333 22 17)f1b,f1,f1LReadWriteStream fileIn;,f1! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 2/17/2000 22:42'! moveChangesTo: newFile "Used in the process of condensing changes, this message requests that the source code of all methods of the receiver that have been changed should be moved to newFile." | changes | self organization moveChangedCommentToFile: newFile numbered: 2. changes _ self methodDict keys select: [:sel | (self methodDict at: sel) fileIndex > 1]. self fileOutChangedMessages: changes on: newFile moveSource: true toFile: 2! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: '6/5/97 di'! printCategoryChunk: categoryName on: aFileStream ^ self printCategoryChunk: categoryName withStamp: '' on: aFileStream! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: '6/5/97 di'! printCategoryChunk: category on: aFileStream priorMethod: priorMethod ^ self printCategoryChunk: category on: aFileStream withStamp: Utilities changeStamp priorMethod: priorMethod! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'di 4/4/1999 11:43'! printCategoryChunk: category on: aFileStream withStamp: changeStamp priorMethod: priorMethod "Print a method category preamble. This must have a category name. It may have an author/date stamp, and it may have a prior source link. If it has a prior source link, it MUST have a stamp, even if it is empty." "The current design is that changeStamps and prior source links are preserved in the changes file. All fileOuts include changeStamps. Condensing sources, however, eliminates all stamps (and links, natch)." aFileStream cr; command: 'H3'; nextPut: $!!. aFileStream nextChunkPut: (String streamContents: [:strm | strm nextPutAll: self name; nextPutAll: ' methodsFor: '; print: category asString. (changeStamp ~~ nil and: [changeStamp size > 0 or: [priorMethod ~~ nil]]) ifTrue: [strm nextPutAll: ' stamp: '; print: changeStamp]. priorMethod ~~ nil ifTrue: [strm nextPutAll: ' prior: '; print: priorMethod sourcePointer]]). aFileStream command: '/H3'.! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: '6/6/97 di'! printCategoryChunk: categoryName withStamp: changeStamp on: aFileStream ^ self printCategoryChunk: categoryName on: aFileStream withStamp: changeStamp priorMethod: nil! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'RAA 5/5/2000 09:08'! printMethodChunk: selector withPreamble: doPreamble on: outStream moveSource: moveSource toFile: fileIndex "Copy the source code for the method associated with selector onto the fileStream. If moveSource true, then also set the source code pointer of the method." | preamble method oldPos newPos sourceFile endPos | doPreamble ifTrue: [preamble _ self name , ' methodsFor: ' , (self organization categoryOfElement: selector) asString printString] ifFalse: [preamble _ '']. method _ self methodDict at: selector. ((method fileIndex = 0 or: [(SourceFiles at: method fileIndex) == nil]) or: [(oldPos _ method filePosition) = 0]) ifTrue: ["The source code is not accessible. We must decompile..." preamble size > 0 ifTrue: [outStream cr; nextPut: $!!; nextChunkPut: preamble; cr]. outStream nextChunkPut: (self decompilerClass new decompile: selector in: self method: method) decompileString] ifFalse: [sourceFile _ SourceFiles at: method fileIndex. sourceFile position: oldPos. preamble size > 0 ifTrue: "Copy the preamble" [outStream copyPreamble: preamble from: sourceFile]. "Copy the method chunk" newPos _ outStream position. outStream copyMethodChunkFrom: sourceFile. sourceFile skipSeparators. "The following chunk may have ]style[" sourceFile peek == $] ifTrue: [ outStream cr; copyMethodChunkFrom: sourceFile]. moveSource ifTrue: "Set the new method source pointer" [endPos _ outStream position. method checkOKToAdd: endPos - newPos at: newPos. method setSourcePosition: newPos inFile: fileIndex]]. preamble size > 0 ifTrue: [outStream nextChunkPut: ' ']. ^ outStream cr! ! !ClassDescription methodsFor: 'fileIn/Out'! reformatAll "Reformat all methods in this class. Leaves old code accessible to version browsing" self selectorsDo: [:sel | self reformatMethodAt: sel]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sw 11/6/1999 23:08'! reformatMethodAt: selector | newCodeString method | newCodeString _ (self compilerClass new) format: (self sourceCodeAt: selector) in: self notifying: nil decorated: false. method _ self compiledMethodAt: selector. method putSource: newCodeString fromParseNode: nil class: self category: (self organization categoryOfElement: selector) inFile: 2 priorMethod: method! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'tk 8/15/1998 15:26'! reorganize "During fileIn, !!Rectangle reorganize!! allows Rectangle to seize control and treat the next chunk as its organization. See the transfer of control where ReadWriteStream fileIn calls scanFrom:" Smalltalk changes reorganizeClass: self. ^self organization! ]style[(10 156 22 80)f1b,f1,f1LReadWriteStream fileIn;,f1! ! !ClassDescription methodsFor: 'private'! errorCategoryName self error: 'Category name must be a String'! ! !ClassDescription methodsFor: 'private' stamp: 'ar 7/11/1999 11:41'! instVarMappingFrom: oldClass "Return the mapping from instVars of oldClass to new class that is used for converting old instances of oldClass." | oldInstVarNames | oldInstVarNames _ oldClass allInstVarNames. ^self allInstVarNames collect: [:instVarName | oldInstVarNames indexOf: instVarName]. ! ! !ClassDescription methodsFor: 'private' stamp: 'di 4/3/1999 22:29'! linesOfCode "InterpreterSimulator linesOfCode 790" "An approximate measure of lines of code. Includes comments, but excludes blank lines." | lines code strm line | lines _ 0. self selectorsDo: [:sel | code _ self sourceCodeAt: sel. strm _ ReadStream on: code. [strm atEnd] whileFalse: [line _ strm upTo: Character cr. line isEmpty ifFalse: [lines _ lines+1]]]. self isMeta ifTrue: [^ lines] ifFalse: [^ lines + self class linesOfCode] " (SystemOrganization categories select: [:c | 'Fabrik*' match: c]) detectSum: [:c | (SystemOrganization superclassOrder: c) detectSum: [:cl | cl linesOfCode]] 24878 "! ! !ClassDescription methodsFor: 'private' stamp: 'ar 7/10/1999 11:17'! newInstanceFrom: oldInstance variable: variable size: instSize map: map "Create a new instance of the receiver based on the given old instance. The supplied map contains a mapping of the old instVar names into the receiver's instVars" | new | variable ifTrue: [new _ self basicNew: oldInstance basicSize] ifFalse: [new _ self basicNew]. 1 to: instSize do: [:offset | (map at: offset) > 0 ifTrue: [new instVarAt: offset put: (oldInstance instVarAt: (map at: offset))]]. variable ifTrue: [1 to: oldInstance basicSize do: [:offset | new basicAt: offset put: (oldInstance basicAt: offset)]]. ^new! ! !ClassDescription methodsFor: 'private' stamp: 'ar 7/15/1999 17:04'! setInstVarNames: instVarArray "Private - for class initialization only" | required | required _ self instSize. superclass notNil ifTrue:[required _ required - superclass instSize]. instVarArray size = required ifFalse:[^self error: required printString, ' instvar names are required']. instVarArray isEmpty ifTrue:[instanceVariables _ nil] ifFalse:[instanceVariables _ instVarArray asArray].! ! !ClassDescription methodsFor: 'private' stamp: 'jm 11/1/1998 11:47'! spaceUsed "Answer a rough estimate of number of bytes used by this class and its metaclass. Does not include space used by class variables." | space method | space _ 0. self selectorsDo: [:sel | space _ space + 16. "dict and org'n space" method _ self compiledMethodAt: sel. space _ space + (method size + 6 "hdr + avg pad"). method literals do: [:lit | (lit isMemberOf: Array) ifTrue: [space _ space + ((lit size + 1) * 4)]. (lit isMemberOf: Float) ifTrue: [space _ space + 12]. (lit isMemberOf: String) ifTrue: [space _ space + (lit size + 6)]. (lit isMemberOf: LargeNegativeInteger) ifTrue: [space _ space + ((lit size + 1) * 4)]. (lit isMemberOf: LargePositiveInteger) ifTrue: [space _ space + ((lit size + 1) * 4)]]]. (self isMemberOf: Metaclass) ifTrue: [^ space] ifFalse: [^ space + self class spaceUsed]. ! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'sma 6/1/2000 12:22'! allSubclasses "Answer a Set of the receiver's and the receiver's descendent's subclasses." | scan scanTop | scan _ OrderedCollection withAll: self subclasses. scanTop _ 1. [scanTop > scan size] whileFalse: [scan addAll: (scan at: scanTop) subclasses. scanTop _ scanTop + 1]. ^ scan asSet! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'di 2/17/2000 22:36'! classesThatImplementAllOf: selectorSet "Return an array of any classes that implement all the messages in selectorSet." | found remaining | found _ OrderedCollection new. selectorSet do: [:sel | (self methodDict includesKey: sel) ifTrue: [found add: sel]]. found isEmpty ifTrue: [^ self subclasses inject: Array new into: [:subsThatDo :sub | subsThatDo , (sub classesThatImplementAllOf: selectorSet)]] ifFalse: [remaining _ selectorSet copyWithoutAll: found. remaining isEmpty ifTrue: [^ Array with: self]. ^ self subclasses inject: Array new into: [:subsThatDo :sub | subsThatDo , (sub classesThatImplementAllOf: remaining)]]! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'ar 7/14/1999 10:57'! printSubclassesOn: aStream level: level "As part of the algorithm for printing a description of the receiver, print the subclass on the file stream, aStream, indenting level times." | subclassNames | aStream crtab: level. aStream nextPutAll: self name. aStream space; print: self instVarNames. self == Class ifTrue: [aStream crtab: level + 1; nextPutAll: '[ ... all the Metaclasses ... ]'. ^self]. subclassNames _ self subclasses asSortedCollection:[:c1 :c2| c1 name <= c2 name]. "Print subclasses in alphabetical order" subclassNames do: [:subclass | subclass printSubclassesOn: aStream level: level + 1]! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'di 6/10/1999 12:05'! removeUninstantiatedSubclassesSilently "Remove the classes of any subclasses that have neither instances nor subclasses. Answer the number of bytes reclaimed" "Player removeUninstantiatedSubclassesSilently" | candidatesForRemoval oldFree | oldFree _ Smalltalk garbageCollect. candidatesForRemoval _ self subclasses select: [:c | (c instanceCount = 0) and: [c subclasses size = 0]]. candidatesForRemoval do: [:c | c removeFromSystem]. ^ Smalltalk garbageCollect - oldFree! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'di 7/21/1999 11:05'! subclasses ^ Array new! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'ar 7/10/1999 08:22'! subclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's immediate subclasses." ^self subclasses do: aBlock! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'SqR 5/25/2000 16:54'! withAllSubclasses "Answer a Set of the receiver, the receiver's descendent's, and the receiver's descendent's subclasses." ^ self allSubclasses add: self; yourself! ! TextDiffBuilder subclass: #ClassDiffBuilder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-File Contents Browser'! !ClassDiffBuilder methodsFor: 'initialize'! split: aString | lines in out c | lines := OrderedCollection new. in := ReadStream on: aString. out := WriteStream on: String new. [in atEnd] whileFalse:[ (c := in next) isSeparator ifTrue:[ out nextPut: c. lines add: out contents. out reset. ] ifFalse:[ out nextPut: c. ]. ]. out position = 0 ifFalse:[ lines add: out contents. ]. ^lines! ! !ClassDiffBuilder methodsFor: 'printing'! printPatchSequence: ps on: aStream | type line attr | ps do:[:assoc| type := assoc key. line := assoc value. attr := TextEmphasis normal. type == #insert ifTrue:[attr := TextColor red]. type == #remove ifTrue:[attr := TextEmphasis struckOut]. aStream withAttribute: attr do:[aStream nextPutAll: line]. ].! ! Object subclass: #ClassOrganizer instanceVariableNames: 'globalComment categoryArray categoryStops elementArray commentStamp ' classVariableNames: 'Default NullCategory ' poolDictionaries: '' category: 'Kernel-Classes'! !ClassOrganizer commentStamp: '' prior: 0! I represent method categorization information for classes. The handling of class comments has gone through a tortuous evolution. Grandfathered class comments (before late aug 98) have no time stamps, and historically, fileouts of class comments always substituted the timestamp reflecting the author and date/time at the moment of fileout; and historically any timestamps in a filed out class comment were dropped on the floor, with the author & time prevailing at the moment of filein being substituted. Such grandfathered comments now go out on fileouts with '' timestamp; class comments created after the 8/98 changes will have their correct timestamps preserved, though there is not yet a decent ui for reading those stamps other than filing out and looking at the file; nor is there yet any ui for browsing and recovering past versions of such comments. Everything in good time!!! !ClassOrganizer methodsFor: 'accessing'! categories "Answer an Array of categories (names)." (categoryArray size = 1 and: [categoryArray first = Default & (elementArray size = 0)]) ifTrue: [^Array with: NullCategory]. ^categoryArray! ! !ClassOrganizer methodsFor: 'accessing'! categories: anArray "Reorder my categories to be in order of the argument, anArray. If the resulting organization does not include all elements, then give an error." | newCategories newStops newElements catName list runningTotal | newCategories _ Array new: anArray size. newStops _ Array new: anArray size. newElements _ Array new: 0. runningTotal _ 0. 1 to: anArray size do: [:i | catName _ (anArray at: i) asSymbol. list _ self listAtCategoryNamed: catName. newElements _ newElements, list. newCategories at: i put: catName. newStops at: i put: (runningTotal _ runningTotal + list size)]. elementArray do: [:element | "check to be sure all elements are included" (newElements includes: element) ifFalse: [^self error: 'New categories must match old ones']]. "Everything is good, now update my three arrays." categoryArray _ newCategories. categoryStops _ newStops. elementArray _ newElements! ! !ClassOrganizer methodsFor: 'accessing'! categoryOfElement: element "Answer the category associated with the argument, element." | index | index _ self numberOfCategoryOfElement: element. index = 0 ifTrue: [^nil] ifFalse: [^categoryArray at: index]! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'di 12/2/1999 20:36'! changeFromCategorySpecs: categorySpecs "Tokens is an array of categorySpecs as scanned from a browser 'reorganize' pane, or built up by some other process, such as a scan of an environment." | oldElements newElements newCategories newStops currentStop temp ii cc catSpec | oldElements _ elementArray asSet. newCategories _ Array new: categorySpecs size. newStops _ Array new: categorySpecs size. currentStop _ 0. newElements _ WriteStream on: (Array new: 16). 1 to: categorySpecs size do: [:i | catSpec _ categorySpecs at: i. newCategories at: i put: catSpec first asSymbol. catSpec allButFirst asSortedCollection do: [:elem | (oldElements remove: elem ifAbsent: [nil]) notNil ifTrue: [newElements nextPut: elem. currentStop _ currentStop+1]]. newStops at: i put: currentStop]. "Ignore extra elements but don't lose any existing elements!!" oldElements _ oldElements collect: [:elem | Array with: (self categoryOfElement: elem) with: elem]. newElements _ newElements contents. categoryArray _ newCategories. (cc _ categoryArray asSet) size = categoryArray size ifFalse: ["has duplicate element" temp _ categoryArray asOrderedCollection. temp removeAll: categoryArray asSet asOrderedCollection. temp do: [:dup | ii _ categoryArray indexOf: dup. [dup _ (dup,' #2') asSymbol. cc includes: dup] whileTrue. cc add: dup. categoryArray at: ii put: dup]]. categoryStops _ newStops. elementArray _ newElements. oldElements do: [:pair | self classify: pair last under: pair first].! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'di 12/2/1999 10:54'! changeFromString: aString "Parse the argument, aString, and make this be the receiver's structure." | categorySpecs | categorySpecs _ Scanner new scanTokens: aString. "If nothing was scanned and I had no elements before, then default me" (categorySpecs isEmpty and: [elementArray isEmpty]) ifTrue: [^ self setDefaultList: Array new]. ^ self changeFromCategorySpecs: categorySpecs! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'tk 12/12/97 13:32'! classComment "Answer the comment associated with the object that refers to the receiver." globalComment == nil ifTrue: [^'']. ^globalComment text! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'di 5/4/1999 20:14'! classComment: aString "Store the comment, aString, associated with the object that refers to the receiver." (aString isKindOf: RemoteString) ifTrue: [globalComment _ aString] ifFalse: [(aString == nil or: [aString size = 0]) ifTrue: [globalComment _ nil] ifFalse: [ self error: 'use aClass classComment:'. globalComment _ RemoteString newString: aString onFileNumber: 2]] "Later add priorSource and date and initials?"! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'tk 12/15/97 14:41'! commentRemoteStr ^ globalComment! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'sw 8/24/1998 12:29'! commentStamp ^ commentStamp! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'sw 8/24/1998 12:29'! commentStamp: aStamp commentStamp _ aStamp! ! !ClassOrganizer methodsFor: 'accessing'! hasNoComment "Answer whether the class classified by the receiver has a comment." ^globalComment == nil! ! !ClassOrganizer methodsFor: 'accessing'! listAtCategoryNamed: categoryName "Answer the array of elements associated with the name, categoryName." | i | i _ categoryArray indexOf: categoryName ifAbsent: [^Array new]. ^self listAtCategoryNumber: i! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'sw 3/23/1999 15:58'! listAtCategoryNumber: anInteger "Answer the array of elements stored at the position indexed by anInteger. Answer nil if anInteger is larger than the number of categories." | firstIndex lastIndex | firstIndex _ (anInteger > 1 ifTrue: [categoryStops at: anInteger - 1] ifFalse: [0]) + 1. (categoryStops size < anInteger) ifTrue: [^ nil]. "It can happen, if Default category got aggressively removed by some automatic operation" lastIndex _ categoryStops at: anInteger. ^elementArray copyFrom: firstIndex to: lastIndex! ! !ClassOrganizer methodsFor: 'accessing'! numberOfCategoryOfElement: element "Answer the index of the category with which the argument, element, is associated." | categoryIndex elementIndex | categoryIndex _ 1. elementIndex _ 0. [(elementIndex _ elementIndex + 1) <= elementArray size] whileTrue: ["point to correct category" [elementIndex > (categoryStops at: categoryIndex)] whileTrue: [categoryIndex _ categoryIndex + 1]. "see if this is element" element = (elementArray at: elementIndex) ifTrue: [^categoryIndex]]. ^0! ! !ClassOrganizer methodsFor: 'accessing'! removeElement: element "Remove the selector, element, from all categories." | categoryIndex elementIndex nextStop newElements | categoryIndex _ 1. elementIndex _ 0. nextStop _ 0. "nextStop keeps track of the stops in the new element array" newElements _ WriteStream on: (Array new: elementArray size). [(elementIndex _ elementIndex + 1) <= elementArray size] whileTrue: [[elementIndex > (categoryStops at: categoryIndex)] whileTrue: [categoryStops at: categoryIndex put: nextStop. categoryIndex _ categoryIndex + 1]. (elementArray at: elementIndex) = element ifFalse: [nextStop _ nextStop + 1. newElements nextPut: (elementArray at: elementIndex)]]. [categoryIndex <= categoryStops size] whileTrue: [categoryStops at: categoryIndex put: nextStop. categoryIndex _ categoryIndex + 1]. elementArray _ newElements contents! ! !ClassOrganizer methodsFor: 'accessing'! removeEmptyCategories "Remove empty categories." | categoryIndex currentStop keptCategories keptStops | keptCategories _ WriteStream on: (Array new: 16). keptStops _ WriteStream on: (Array new: 16). currentStop _ categoryIndex _ 0. [(categoryIndex _ categoryIndex + 1) <= categoryArray size] whileTrue: [(categoryStops at: categoryIndex) > currentStop ifTrue: [keptCategories nextPut: (categoryArray at: categoryIndex). keptStops nextPut: (currentStop _ categoryStops at: categoryIndex)]]. categoryArray _ keptCategories contents. categoryStops _ keptStops contents. categoryArray size = 0 ifTrue: [categoryArray _ Array with: Default. categoryStops _ Array with: 0] "ClassOrganizer allInstancesDo: [:co | co removeEmptyCategories]."! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'SqR 11/16/2000 13:52'! sortCategories | privateCategories publicCategories newCategories | privateCategories _ self categories select: [:one | (one findString: 'private' startingAt: 1 caseSensitive: false) = 1]. publicCategories _ self categories copyWithoutAll: privateCategories. newCategories _ publicCategories asSortedCollection asOrderedCollection addAll: privateCategories asSortedCollection; asArray. self categories: newCategories! ! !ClassOrganizer methodsFor: 'compiler access' stamp: 'sw 3/23/1999 17:04'! classify: element under: heading self classify: element under: heading suppressIfDefault: true! ! !ClassOrganizer methodsFor: 'compiler access' stamp: 'sw 3/23/1999 17:02'! classify: element under: heading suppressIfDefault: aBoolean "Store the argument, element, in the category named heading. If aBoolean is true, then invoke special logic such that the classification is NOT done if the new heading is the Default and the element already had a non-Default classification -- useful for filein" | catName catIndex elemIndex realHeading | ((heading = NullCategory) or: [heading == nil]) ifTrue: [realHeading _ Default] ifFalse: [realHeading _ heading asSymbol]. (catName _ self categoryOfElement: element) = realHeading ifTrue: [^ self]. "done if already under that category" catName ~~ nil ifTrue: [(aBoolean and: [realHeading = Default]) ifTrue: [^ self]. "return if non-Default category already assigned in memory" self removeElement: element]. "remove if in another category" (categoryArray indexOf: realHeading) = 0 ifTrue: [self addCategory: realHeading]. catIndex _ categoryArray indexOf: realHeading. elemIndex _ catIndex > 1 ifTrue: [categoryStops at: catIndex - 1] ifFalse: [0]. [(elemIndex _ elemIndex + 1) <= (categoryStops at: catIndex) and: [element >= (elementArray at: elemIndex)]] whileTrue. "elemIndex is now the index for inserting the element. Do the insertion before it." elementArray _ elementArray copyReplaceFrom: elemIndex to: elemIndex-1 with: (Array with: element). "add one to stops for this and later categories" catIndex to: categoryArray size do: [:i | categoryStops at: i put: (categoryStops at: i) + 1]. (self listAtCategoryNamed: Default) size = 0 ifTrue: [self removeCategory: Default]! ! !ClassOrganizer methodsFor: 'compiler access'! classifyAll: aCollection under: heading aCollection do: [:element | self classify: element under: heading]! ! !ClassOrganizer methodsFor: 'method dictionary'! addCategory: newCategory ^ self addCategory: newCategory before: nil ! ! !ClassOrganizer methodsFor: 'method dictionary'! addCategory: catString before: nextCategory "Add a new category named heading. If default category exists and is empty, remove it. If nextCategory is nil, then add the new one at the end, otherwise, insert it before nextCategory." | index newCategory | newCategory _ catString asSymbol. (categoryArray indexOf: newCategory) > 0 ifTrue: [^self]. "heading already exists, so done" index _ categoryArray indexOf: nextCategory ifAbsent: [categoryArray size + 1]. categoryArray _ categoryArray copyReplaceFrom: index to: index-1 with: (Array with: newCategory). categoryStops _ categoryStops copyReplaceFrom: index to: index-1 with: (Array with: (index = 1 ifTrue: [0] ifFalse: [categoryStops at: index-1])). "remove empty default category" (newCategory ~= Default and: [(self listAtCategoryNamed: Default) isEmpty]) ifTrue: [self removeCategory: Default]! ! !ClassOrganizer methodsFor: 'method dictionary' stamp: 'di 3/29/2000 21:41'! allMethodSelectors "give a list of all method selectors." ^ elementArray copy sort! ! !ClassOrganizer methodsFor: 'method dictionary' stamp: 'sw 3/23/1999 17:04'! letUserReclassify: anElement "Put up a list of categories and solicit one from the user. Answer true if user indeed made a change, else false" "ClassOrganizer organization letUserReclassify: #letUserReclassify:" | currentCat newCat | currentCat _ self categoryOfElement: anElement. newCat _ self categoryFromUserWithPrompt: 'Choose Category (currently "', currentCat, '")'. (newCat ~~ nil and: [newCat ~= currentCat]) ifTrue: [self classify: anElement under: newCat suppressIfDefault: false. ^ true] ifFalse: [^ false]! ! !ClassOrganizer methodsFor: 'method dictionary'! removeCategory: cat "Remove the category named, cat. Create an error notificiation if the category has any elements in it." | index lastStop | index _ categoryArray indexOf: cat ifAbsent: [^self]. lastStop _ index = 1 ifTrue: [0] ifFalse: [categoryStops at: index - 1]. (categoryStops at: index) - lastStop > 0 ifTrue: [^self error: 'cannot remove non-empty category']. categoryArray _ categoryArray copyReplaceFrom: index to: index with: Array new. categoryStops _ categoryStops copyReplaceFrom: index to: index with: Array new. categoryArray size = 0 ifTrue: [categoryArray _ Array with: Default. categoryStops _ Array with: 0] ! ! !ClassOrganizer methodsFor: 'method dictionary' stamp: 'sw 10/20/1999 16:24'! renameCategory: oldCatString toBe: newCatString "Rename a category. No action if new name already exists, or if old name does not exist." | index oldCategory newCategory | oldCategory _ oldCatString asSymbol. newCategory _ newCatString asSymbol. (categoryArray indexOf: newCategory) > 0 ifTrue: [^ self]. "new name exists, so no action" (index _ categoryArray indexOf: oldCategory) = 0 ifTrue: [^ self]. "old name not found, so no action" categoryArray _ categoryArray copy. "need to change identity so smart list update will notice the change" categoryArray at: index put: newCategory! ! !ClassOrganizer methodsFor: 'printing' stamp: 'di 4/26/2000 20:22'! printOn: aStream "Refer to the comment in Object|printOn:." | elementIndex | elementIndex _ 1. 1 to: categoryArray size do: [:i | aStream nextPut: $(. (categoryArray at: i) asString printOn: aStream. [elementIndex <= (categoryStops at: i)] whileTrue: [aStream space; nextPutAll: (elementArray at: elementIndex). elementIndex _ elementIndex + 1]. aStream nextPut: $); cr]! ! !ClassOrganizer methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:16'! printOnStream: aStream "Refer to the comment in Object|printOn:." | elementIndex | elementIndex _ 1. 1 to: categoryArray size do: [:i | aStream print: '('; write:(categoryArray at:i). " is the asString redundant? " [elementIndex <= (categoryStops at: i)] whileTrue: [aStream print:' '; write:(elementArray at: elementIndex). elementIndex _ elementIndex + 1]. aStream print:')'. aStream cr]! ! !ClassOrganizer methodsFor: 'fileIn/Out'! fileOutCommentOn: aFileStream moveSource: moveSource toFile: fileIndex "Copy the class comment to aFileStream. If moveSource is true (as in compressChanges or compressSources, then update globalComment to point to the new file." | fileComment | globalComment ifNotNil: [aFileStream cr. fileComment _ RemoteString newString: globalComment text onFileNumber: fileIndex toFile: aFileStream. moveSource ifTrue: [globalComment _ fileComment]]! ! !ClassOrganizer methodsFor: 'fileIn/Out'! moveChangedCommentToFile: aFileStream numbered: fileIndex "If the comment is in the changes file, then move it to a new file." (globalComment ~~ nil and: [globalComment sourceFileNumber > 1]) ifTrue: [self fileOutCommentOn: aFileStream moveSource: true toFile: fileIndex]! ! !ClassOrganizer methodsFor: 'fileIn/Out' stamp: 'tk 9/28/2000 15:39'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. Write a path to me in the other system instead." Smalltalk allBehaviorsDo: [:aClass | aClass organization == self ifTrue: [ (refStrm insideASegment and: [aClass isSystemDefined not]) ifTrue: [ ^ self]. "do trace me" (aClass isKindOf: Class) ifTrue: [ dp _ DiskProxy global: aClass name selector: #organization args: #(). refStrm replace: self with: dp. ^ dp]]]. ^ self "in desparation" ! ! !ClassOrganizer methodsFor: 'fileIn/Out' stamp: 'sw 8/24/1998 12:33'! putCommentOnFile: aFileStream numbered: sourceIndex moveSource: moveSource forClass: aClass "Store the comment about the class onto file, aFileStream." | header | globalComment ifNotNil: [aFileStream cr; nextPut: $!!. header _ String streamContents: [:strm | strm nextPutAll: aClass name; nextPutAll: ' commentStamp: '. commentStamp ifNil: [commentStamp _ '']. commentStamp storeOn: strm. strm nextPutAll: ' prior: '; nextPutAll: '0']. aFileStream nextChunkPut: header. aClass organization fileOutCommentOn: aFileStream moveSource: moveSource toFile: sourceIndex. aFileStream cr]! ! !ClassOrganizer methodsFor: 'fileIn/Out' stamp: 'di 1/13/98 16:57'! scanFrom: aStream "Reads in the organization from the next chunk on aStream. Categories or elements not found in the definition are not affected. New elements are ignored." self changeFromString: aStream nextChunk. aStream skipStyleChunk.! ! !ClassOrganizer methodsFor: 'private' stamp: 'sw 2/24/1999 15:26'! categoryFromUserWithPrompt: aPrompt "SystemDictionary organization categoryFromUserWithPrompt: 'testing'" | aMenu | aMenu _ CustomMenu new. self categories do: [:cat | aMenu add: cat asString action: cat]. ^ aMenu startUpWithCaption: aPrompt! ! !ClassOrganizer methodsFor: 'private' stamp: 'di 3/29/2000 21:42'! elementArray ^ elementArray! ! !ClassOrganizer methodsFor: 'private' stamp: 'tk 12/16/97 07:35'! setDefaultList: aSortedCollection self classComment: ''. categoryArray _ Array with: Default. categoryStops _ Array with: aSortedCollection size. elementArray _ aSortedCollection asArray! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassOrganizer class instanceVariableNames: ''! !ClassOrganizer class methodsFor: 'class initialization' stamp: 'ccn 3/22/1999 17:43'! allCategory "Return a symbol that represents the virtual all methods category." ^ '-- all --' asSymbol! ! !ClassOrganizer class methodsFor: 'class initialization'! default ^ Default! ! !ClassOrganizer class methodsFor: 'class initialization'! initialize Default _ 'as yet unclassified' asSymbol. NullCategory _ 'no messages' asSymbol. "ClassOrganizer initialize"! ! !ClassOrganizer class methodsFor: 'class initialization'! nullCategory ^ NullCategory! ! !ClassOrganizer class methodsFor: 'instance creation'! defaultList: aSortedCollection "Answer an instance of me with initial elements from the argument, aSortedCollection." ^self new setDefaultList: aSortedCollection! ! !ClassOrganizer class methodsFor: 'documentation'! documentation "Instances consist of an Array of category names (categoryArray), each of which refers to an Array of elements (elementArray). This association is made through an Array of stop indices (categoryStops), each of which is the index in elementArray of the last element (if any) of the corresponding category. For example: categories _ Array with: 'firstCat' with: 'secondCat' with: 'thirdCat'. stops _ Array with: 1 with: 4 with: 4. elements _ Array with: #a with: #b with: #c with: #d. This means that category firstCat has only #a, secondCat has #b, #c, and #d, and thirdCat has no elements. This means that stops at: stops size must be the same as elements size." ! ! ObjectRepresentativeMorph subclass: #ClassRepresentativeMorph instanceVariableNames: 'classRepresented ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting'! Object subclass: #Clause instanceVariableNames: 'string phrases accent ' classVariableNames: '' poolDictionaries: '' category: 'Speech-TTS'! !Clause commentStamp: '' prior: 0! My instances are clauses. They can carry a phrase accent (applicable to their last phrase) and a boundary tone: 'L- L%' (for declarative sentences in American English), 'H- H%' (for Yes-No questions), etc.! !Clause methodsFor: 'accessing' stamp: 'len 12/12/1999 22:46'! accent ^ accent! ! !Clause methodsFor: 'accessing' stamp: 'len 12/12/1999 22:46'! accent: aString accent _ aString! ! !Clause methodsFor: 'accessing' stamp: 'len 12/13/1999 02:32'! accept: anObject anObject clause: self! ! !Clause methodsFor: 'accessing' stamp: 'len 12/8/1999 17:53'! events | answer | answer _ CompositeEvent new. self phrases do: [ :each | answer addAll: each events]. ^ answer! ! !Clause methodsFor: 'accessing' stamp: 'len 12/8/1999 17:50'! lastSyllable ^ self phrases last lastSyllable! ! !Clause methodsFor: 'accessing' stamp: 'len 12/8/1999 17:49'! phrases ^ phrases! ! !Clause methodsFor: 'accessing' stamp: 'len 12/8/1999 17:50'! phrases: aCollection phrases _ aCollection! ! !Clause methodsFor: 'accessing' stamp: 'len 12/12/1999 22:22'! string ^ string! ! !Clause methodsFor: 'accessing' stamp: 'len 12/12/1999 22:22'! string: aString string _ aString! ! !Clause methodsFor: 'enumarating' stamp: 'len 12/13/1999 01:19'! eventsDo: aBlock self phrases do: [ :phrase | phrase eventsDo: aBlock]! ! !Clause methodsFor: 'enumarating' stamp: 'len 12/14/1999 04:22'! syllablesDo: aBlock self wordsDo: [ :each | each syllables do: aBlock]! ! !Clause methodsFor: 'enumarating' stamp: 'len 12/13/1999 02:40'! wordsDo: aBlock self phrases do: [ :each | each words do: aBlock]! ! !Clause methodsFor: 'printing' stamp: 'len 12/8/1999 18:17'! printOn: aStream self phrases do: [ :each | aStream print: each; nextPutAll: '- ']! ! Object subclass: #Clipboard instanceVariableNames: 'contents recent ' classVariableNames: 'Default ' poolDictionaries: '' category: 'Kernel-ST80 Remnants'! !Clipboard commentStamp: '' prior: 0! The Clipboard class implements a basic buffering scheme for text. The currently selected text is also exported to the OS so that text can be copied from and to other applications. Commonly only a single instance is used (the default clipboard) but applications are free to use other than the default clipboard if necessary.! !Clipboard methodsFor: 'initialize' stamp: 'ar 1/15/2001 18:34'! initialize contents _ '' asText. recent _ OrderedCollection new.! ! !Clipboard methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:32'! chooseRecentClipping "Clipboard chooseRecentClipping" "Choose by menu from among the recent clippings" recent ifNil: [^ nil]. ^ (SelectionMenu labelList: (recent collect: [:txt | ((txt asString contractTo: 50) copyReplaceAll: Character cr asString with: '\') copyReplaceAll: Character tab asString with: '|']) selections: recent) startUp. ! ! !Clipboard methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:31'! clipboardText "Return the text currently in the clipboard. If the system clipboard is empty, or if it differs from the Smalltalk clipboard text, use the Smalltalk clipboard. This is done since (a) the Mac clipboard gives up on very large chunks of text and (b) since not all platforms support the notion of a clipboard." | s | s _ self primitiveClipboardText. (s isEmpty or: [s = contents string]) ifTrue: [^ contents] ifFalse: [^ s asText]! ! !Clipboard methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:40'! clipboardText: text "Set text currently on the clipboard. Also export to OS" contents _ text. self noteRecentClipping: text. self primitiveClipboardText: text string! ! !Clipboard methodsFor: 'primitives' stamp: 'ar 1/15/2001 18:28'! primitiveClipboardText "Get the current clipboard text. Return the empty string if the primitive fails." ^ ''! ! !Clipboard methodsFor: 'primitives' stamp: 'ar 1/15/2001 18:30'! primitiveClipboardText: aString "Set the current clipboard text to the given string." "don't fail if the primitive is not implemented"! ! !Clipboard methodsFor: 'private' stamp: 'ar 1/15/2001 18:34'! noteRecentClipping: text "Keep most recent clippings in a queue for pasteRecent (paste... command)" text isEmpty ifTrue: [^ self]. text size > 50000 ifTrue: [^ self]. (recent includes: text) ifTrue: [^ self]. recent addFirst: text. [recent size > 5] whileTrue: [recent removeLast]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Clipboard class instanceVariableNames: ''! !Clipboard class methodsFor: 'instance creation' stamp: 'ar 1/15/2001 18:33'! new ^super new initialize.! ! !Clipboard class methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:45'! chooseRecentClipping "Clipboard chooseRecentClipping" "Choose by menu from among the recent clippings" ^self default chooseRecentClipping! ! !Clipboard class methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:35'! clipboardText "Clipboard clipboardText" ^self default clipboardText.! ! !Clipboard class methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:35'! clipboardText: aText ^self default clipboardText: aText! ! !Clipboard class methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:33'! default ^Default ifNil:[Default _ self new].! ! !Clipboard class methodsFor: 'accessing' stamp: 'ar 1/15/2001 18:48'! default: aClipboard "So that clients can switch between different default clipboards" Default _ aClipboard.! ! PluggableCanvas subclass: #ClippingCanvas instanceVariableNames: 'canvas clipRect ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !ClippingCanvas commentStamp: '' prior: 0! A modified canvas which clips all drawing commands.! !ClippingCanvas methodsFor: 'private' stamp: 'ls 3/20/2000 20:44'! apply: aBlock "apply the given block to the inner canvas with clipRect as the clipping rectangle" canvas clipBy: clipRect during: aBlock! ! !ClippingCanvas methodsFor: 'initialization' stamp: 'ls 3/20/2000 20:44'! canvas: aCanvas clipRect: aRectangle canvas := aCanvas. clipRect := aRectangle.! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/25/2000 22:56'! clipRect ^clipRect! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/26/2000 14:22'! contentsOfArea: aRectangle into: aForm self flag: #hack. "ignore the clipping specification for this command. This is purely so that CachingCanvas will work properly when clipped. There *has* to be a clean way to do this...." ^canvas contentsOfArea: aRectangle into: aForm! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/20/2000 21:17'! form ^canvas form! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/20/2000 21:17'! isBalloonCanvas ^canvas isBalloonCanvas! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/20/2000 21:18'! isShadowDrawing ^canvas isShadowDrawing! ! !ClippingCanvas methodsFor: 'canvas methods' stamp: 'ls 3/20/2000 21:15'! shadowColor ^canvas shadowColor! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClippingCanvas class instanceVariableNames: ''! !ClippingCanvas class methodsFor: 'instance creation' stamp: 'ls 3/20/2000 20:45'! canvas: aCanvas clipRect: aRectangle ^self new canvas: aCanvas clipRect: aRectangle! ! StringMorph subclass: #ClockMorph instanceVariableNames: 'showSeconds ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !ClockMorph methodsFor: 'as yet unclassified' stamp: 'sw 2/24/1999 12:51'! addCustomMenuItems: aCustomMenu hand: aHandMorph "Note minor loose end here -- if the menu is persistent, then the wording will be wrong half the time" | item | super addCustomMenuItems: aCustomMenu hand: aHandMorph. item _ showSeconds == true ifTrue: ['stop showing seconds'] ifFalse: ['start showing seconds']. aCustomMenu add: item target: self action: #toggleShowingSeconds ! ! !ClockMorph methodsFor: 'as yet unclassified' stamp: 'sw 2/17/1999 14:39'! initialize super initialize. showSeconds _ true. self step! ! !ClockMorph methodsFor: 'as yet unclassified' stamp: 'sw 2/17/1999 14:39'! showSeconds: aBoolean showSeconds _ aBoolean! ! !ClockMorph methodsFor: 'as yet unclassified' stamp: 'sw 2/17/1999 14:49'! step | time | super step. time _ String streamContents: [:aStrm | Time now print24: false showSeconds: (showSeconds == true) on: aStrm]. self contents: time ! ! !ClockMorph methodsFor: 'as yet unclassified'! stepTime "Answer the desired time between steps in milliseconds." ^ 1000! ! !ClockMorph methodsFor: 'as yet unclassified' stamp: 'sw 2/17/1999 14:53'! toggleShowingSeconds showSeconds _ (showSeconds == true) not ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClockMorph class instanceVariableNames: ''! !ClockMorph class methodsFor: 'as yet unclassified' stamp: 'sw 10/16/1998 15:36'! authoringPrototype ^ super authoringPrototype contents: Time now printString! ! StringHolder subclass: #CodeHolder instanceVariableNames: 'showDiffs currentCompiledMethod contentsSymbol ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-ST80 Remnants'! !CodeHolder commentStamp: '' prior: 0! An ancestor class for all models which can show code. Eventually, much of the code that currently resides in StringHolder which only applies to code-holding StringHolders might get moved down here.! !CodeHolder methodsFor: 'annotation' stamp: 'sw 1/25/2001 06:40'! annotation "Provide a line of content for an annotation pane, representing information about the method associated with the selected class and selector in the receiver." | aSelector aClass | ((aSelector _ self selectedMessageName) == nil or: [(aClass _ self selectedClassOrMetaClass) == nil]) ifTrue: [^ '------']. ^ self annotationForSelector: aSelector ofClass: aClass! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 1/25/2001 06:17'! annotationForSelector: aSelector ofClass: aClass "Provide a line of content for an annotation pane, representing information about the given selector and class" | stamp sendersCount implementorsCount toShow aCategory separator aString aList versionsCount aComment | toShow _ ReadWriteStream on: ''. separator _ ' ¥ '. self annotationRequests do: [:aRequest | (aRequest == #firstComment) ifTrue: [aComment _ aClass firstCommentAt: aSelector. aComment isEmptyOrNil ifFalse: [toShow nextPutAll: (aComment, separator)]]. (aRequest == #timeStamp) ifTrue: [stamp _ self timeStamp. toShow nextPutAll: (stamp size > 0 ifTrue: [stamp, separator] ifFalse: ['no timeStamp', separator])]. (aRequest == #messageCategory) ifTrue: [aCategory _ aClass organization categoryOfElement: aSelector. aCategory ifNotNil: "woud be nil for a method no longer present, e.g. in a recent-submissions browser" [toShow nextPutAll: aCategory, separator]]. (aRequest == #sendersCount) ifTrue: [sendersCount _ (Smalltalk allCallsOn: aSelector) size. sendersCount _ sendersCount == 1 ifTrue: ['1 sender'] ifFalse: [sendersCount printString, ' senders']. toShow nextPutAll: sendersCount, separator]. (aRequest == #implementorsCount) ifTrue: [implementorsCount _ Smalltalk numberOfImplementorsOf: aSelector. implementorsCount _ implementorsCount == 1 ifTrue: ['1 implementor'] ifFalse: [implementorsCount printString, ' implementors']. toShow nextPutAll: implementorsCount, separator]. (aRequest == #priorVersionsCount) ifTrue: [versionsCount _ VersionsBrowser versionCountForSelector: aSelector class: aClass. toShow nextPutAll: ((versionsCount > 1 ifTrue: [versionsCount == 2 ifTrue: ['1 prior version'] ifFalse: [versionsCount printString, ' prior versions']] ifFalse: ['no prior versions']), separator)]. (aRequest == #priorTimeStamp) ifTrue: [stamp _ VersionsBrowser timeStampFor: aSelector class: aClass reverseOrdinal: 2. stamp ifNotNil: [toShow nextPutAll: 'prior time stamp: ', stamp, separator]]. (aRequest == #recentChangeSet) ifTrue: [aString _ ChangeSorter mostRecentChangeSetWithChangeForClass: aClass selector: aSelector. aString size > 0 ifTrue: [toShow nextPutAll: aString, separator]]. (aRequest == #allChangeSets) ifTrue: [aList _ ChangeSorter allChangeSetsWithClass: aClass selector: aSelector. aList size > 0 ifTrue: [aList size = 1 ifTrue: [toShow nextPutAll: 'only in change set '] ifFalse: [toShow nextPutAll: 'in change sets: ']. aList do: [:aChangeSet | toShow nextPutAll: aChangeSet name, ' ']] ifFalse: [toShow nextPutAll: 'in no change set']. toShow nextPutAll: separator]]. ^ toShow contents! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 9/27/1999 14:13'! annotationRequests ^ Preferences defaultAnnotationRequests! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 1/24/2001 22:30'! defaultAnnotationPaneHeight "Answer the receiver's preferred default height for new annotation panes." ^ Preferences parameterAt: #defaultAnnotationPaneHeight default: [25]! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 1/24/2001 22:30'! defaultButtonPaneHeight "Answer the user's preferred default height for new button panes." ^ Preferences parameterAt: #defaultButtonPaneHeight default: [25]! ! !CodeHolder methodsFor: 'categories' stamp: 'sw 1/4/2001 12:04'! categoryOfCurrentMethod "Answer the category that owns the current method. If unable to determine a category, answer nil." | aClass aSelector | ^ (aClass _ self selectedClassOrMetaClass) ifNotNil: [(aSelector _ self selectedMessageName) ifNotNil: [aClass whichCategoryIncludesSelector: aSelector]]! ! !CodeHolder methodsFor: 'categories' stamp: 'sw 9/27/1999 14:14'! changeCategory "Present a menu of the categories of messages for the current class, and let the user choose a new category for the current message" | aClass aSelector | (aClass _ self selectedClassOrMetaClass) ifNotNil: [(aSelector _ self selectedMessageName) ifNotNil: [(aClass organization letUserReclassify: aSelector) ifTrue: ["Smalltalk changes reorganizeClass: aClass." "Decided on further review that the above, when present, could cause more unexpected harm than good" self methodCategoryChanged]]]! ! !CodeHolder methodsFor: 'categories' stamp: 'sw 9/27/1999 14:11'! methodCategoryChanged self changed: #annotation! ! !CodeHolder methodsFor: 'categories' stamp: 'sw 3/22/2000 23:04'! selectedMessageCategoryName "Answer the name of the message category of the message of the currently selected context." ^ self selectedClass organization categoryOfElement: self selectedMessageName! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 1/18/2001 13:54'! diffButton | outerButton aButton | "Return a checkbox that lets the user decide whether diffs should be shown or not" outerButton _ AlignmentMorph newRow. outerButton wrapCentering: #center; cellPositioning: #leftCenter. outerButton color: Color transparent. outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap. outerButton addMorph: (aButton _ UpdatingThreePhaseButtonMorph checkBox). aButton target: self; actionSelector: #toggleDiffing; getSelector: #showDiffs. outerButton addMorphBack: (StringMorph contents: 'diffs') lock. outerButton setBalloonText: 'If checked, then code differences from the previous version, if any, will be shown.'. ^ outerButton ! ! !CodeHolder methodsFor: 'diffs' stamp: 'nk 10/29/2000 12:42'! diffFromPriorSourceFor: sourceCode "If there is a prior version of source for the selected method, return a diff, else just return the source code" | prior | ^ (prior _ self priorSourceOrNil) ifNil: [sourceCode] ifNotNil: [TextDiffBuilder buildDisplayPatchFrom: prior to: sourceCode inClass: self selectedClass]! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 9/27/1999 13:56'! showDiffs ^ showDiffs == true ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 9/27/1999 13:56'! showDiffs: aBoolean showDiffs _ aBoolean! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 1/18/2001 13:58'! toggleDiff "Retained for backward compatibility with existing buttons in existing images" self toggleDiffing! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 1/18/2001 13:56'! toggleDiffing "Toggle whether diffs should be shown in the code pane" self okToChange ifTrue: [self showDiffs: self showDiffs not. contents _ nil. self changed: #contents] ! ! !CodeHolder methodsFor: 'contents' stamp: 'sw 12/11/2000 10:42'! commentContents "documentation for the selected method" | poss aClass aSelector | ^ (poss _ (aClass _ self selectedClassOrMetaClass) ifNil: ['----'] ifNotNil: [(aSelector _ self selectedMessageName) ifNil: ['---'] ifNotNil: [(aClass precodeCommentOrInheritedCommentFor: aSelector)", String cr, String cr, self timeStamp" "which however misses comments that are between the temps declaration and the body of the method; those are picked up by ¥aClass commentOrInheritedCommentFor: aSelector¥ but that method will get false positives from comments *anywhere* in the method source"]]) isEmptyOrNil ifTrue: [aSelector ifNotNil: [((aClass methodHeaderFor: aSelector), ' Has no comment') asText makeSelectorBoldIn: aClass] ifNil: ['Hamna']] ifFalse: [aSelector ifNotNil: [((aClass methodHeaderFor: aSelector), ' ', poss) asText makeSelectorBoldIn: aClass] ifNil: [poss]]! ! !CodeHolder methodsFor: 'contents' stamp: 'sw 12/5/2000 12:16'! contents "Answer the source code or documentation for the selected method" ^ self showingDocumentation ifFalse: [super contents] ifTrue: [self commentContents]! ! !CodeHolder methodsFor: 'contents' stamp: 'sw 9/27/1999 14:11'! contentsChanged self changed: #contents. self changed: #annotation! ! !CodeHolder methodsFor: 'contents' stamp: 'sw 11/29/2000 09:50'! contentsSymbol "Answer a symbol indicating what kind of content should be shown for the method; for normal showing of source code, this symbol is #source. A nil value in the contentsSymbol slot will be set to #source by this method" ^ contentsSymbol ifNil: [contentsSymbol _ #source]! ! !CodeHolder methodsFor: 'contents' stamp: 'sw 11/29/2000 09:51'! contentsSymbol: aSymbol "Set the contentsSymbol as indicated. #source means to show source code, #comment means to show the first comment found in the source code" contentsSymbol _ aSymbol! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 12/28/2000 18:09'! adoptMessageInCurrentChangeset "Add the receiver's method to the current change set if not already there" self setClassAndSelectorIn: [:cl :sel | cl ifNotNil: [Smalltalk changes adoptSelector: sel forClass: cl. self changed: #annotation]] ! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 5/8/2000 12:38'! makeSampleInstance | aClass nonMetaClass anInstance | (aClass _ self selectedClassOrMetaClass) ifNil: [^ self]. nonMetaClass _ aClass theNonMetaClass. anInstance _ self sampleInstanceOfSelectedClass. (anInstance isNil and: [nonMetaClass ~~ UndefinedObject]) ifTrue: [^ self inform: 'Sorry, cannot make an instance of ', nonMetaClass name]. (Smalltalk isMorphic and: [anInstance isMorph]) ifTrue: [self currentHand attachMorph: anInstance] ifFalse: [anInstance inspectWithLabel: 'An instance of ', nonMetaClass name]! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 5/5/2000 09:23'! showUnreferencedInstVars "Search for all instance variables known to the selected class, and put up a list of those that have no references anywhere in the system. The search includes superclasses, so that you don't need to navigate your way to the class that defines each inst variable in order to determine whether it is unreferenced" | cls aList aReport | (cls _ self selectedClass) ifNil: [^ self]. aList _ cls allUnreferencedInstanceVariables. aList size == 0 ifTrue: [^ self inform: 'There are no unreferenced instance variables in ', cls name]. aReport _ String streamContents: [:aStream | aStream nextPutAll: 'Unreferenced instance variable(s) in ', cls name; cr. aList do: [:el | aStream tab; nextPutAll: el; cr]]. Transcript cr; show: aReport. (SelectionMenu labels: aList selections: aList) startUpWithCaption: 'Unreferenced instance variables in ', cls name! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 1/4/2001 12:17'! spawn: aString "Create and schedule a spawned message category browser for the currently selected message category. The initial text view contains the characters in aString. In the spawned browser, preselect the current selector (if any) as the going-in assumption, though upon acceptance this will often change" | newBrowser aCategory aClass | (aClass _ self selectedClassOrMetaClass) isNil ifTrue: [^ aString isEmptyOrNil ifFalse: [(Workspace new contents: aString) openLabel: 'spawned workspace']]. (aCategory _ self categoryOfCurrentMethod) ifNil: [self buildClassBrowserEditString: aString] ifNotNil: [newBrowser _ Browser new setClass: aClass selector: self selectedMessageName. newBrowser setOriginalCategoryIndexForCurrentMethod. Browser openBrowserView: (newBrowser openMessageCatEditString: aString) label: 'category "', aCategory, '" in ', newBrowser selectedClassOrMetaClassName]! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 12/4/2000 12:07'! spawnFullProtocol "Create and schedule a new protocol browser on the currently selected class or meta." | aClassOrMetaclass | (aClassOrMetaclass _ self selectedClassOrMetaClass) ifNotNil: [ProtocolBrowser openFullProtocolForClass: aClassOrMetaclass]! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 5/8/2000 03:18'! spawnHierarchy "Create and schedule a new class hierarchy browser on the currently selected class or meta." | newBrowser aSymbol aBehavior messageCatIndex selectedClassOrMetaClass | (selectedClassOrMetaClass _ self selectedClassOrMetaClass) ifNil: [^ self]. newBrowser _ HierarchyBrowser new initHierarchyForClass: selectedClassOrMetaClass. (aSymbol _ self selectedMessageName) ifNotNil: [aBehavior _ selectedClassOrMetaClass. messageCatIndex _ aBehavior organization numberOfCategoryOfElement: aSymbol. newBrowser messageCategoryListIndex: messageCatIndex + 1. newBrowser messageListIndex: ((aBehavior organization listAtCategoryNumber: messageCatIndex) indexOf: aSymbol)]. Browser openBrowserView: (newBrowser openSystemCatEditString: nil) label: self selectedClassName , ' hierarchy'! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 5/8/2000 14:24'! spawnProtocol | aClassOrMetaclass | "Create and schedule a new protocol browser on the currently selected class or meta." (aClassOrMetaclass _ self selectedClassOrMetaClass) ifNotNil: [ProtocolBrowser openSubProtocolForClass: aClassOrMetaclass]! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 11:32'! showComment "Answer whether the receiver should show documentation rather than, say, source code" ^ self contentsSymbol == #documentation ! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 12:25'! showDocumentation: aBoolean "Set the showDocumentation toggle as indicated" self contentsSymbol: (aBoolean ifFalse: [#source] ifTrue: [#documentation])! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 12:12'! showingDocumentation "Answer whether the receiver should show documentation rather than, say, source code" ^ self contentsSymbol == #documentation ! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 11:48'! showingSource "Answer whether the receiver is currently showing source code" ^ self contentsSymbol == #source ! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 11:51'! sourceOrInfoButton "Return a checkbox that lets the user decide whether the full source or just documentation should show in the code pane" | outerButton aButton | outerButton _ AlignmentMorph newRow. outerButton wrapCentering: #center; cellPositioning: #leftCenter. outerButton color: Color transparent. outerButton hResizing: #shrinkWrap; vResizing: #shrinkWrap. outerButton addMorph: (aButton _ UpdatingThreePhaseButtonMorph checkBox). aButton target: self; actionSelector: #toggleShowDocumentation; getSelector: #showingSource. outerButton addMorphBack: (StringMorph contents: 'source') lock. outerButton setBalloonText: 'If checked, then source code is shown in the text pane, if not, then documentation is shown'. ^ outerButton ! ! !CodeHolder methodsFor: 'source vs documentation' stamp: 'sw 12/5/2000 12:17'! toggleShowDocumentation "Toggle the setting of the showingDocumentation flag, unless there are unsubmitted edits that the user declines to discard" self okToChange ifTrue: [self showDocumentation: self showingDocumentation not. contents _ nil. self changed: #contents] ! ! !CodeHolder methodsFor: 'misc' stamp: 'JW 2/3/2001 09:38'! addLowerPanesTo: window at: nominalFractions with: editString | verticalOffset row innerFractions | row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 1; borderColor: Color black; layoutPolicy: ProportionalLayout new. verticalOffset _ 0. innerFractions _ 0@0 corner: 1@0. verticalOffset _ self addOptionalAnnotationsTo: row at: innerFractions plus: verticalOffset. verticalOffset _ self addOptionalButtonsTo: row at: innerFractions plus: verticalOffset. row addMorph: ((self buildMorphicCodePaneWith: editString) borderWidth: 0) fullFrame: ( LayoutFrame fractions: (innerFractions withBottom: 1) offsets: (0@verticalOffset corner: 0@0) ). window addMorph: row frame: nominalFractions. row on: #mouseEnter send: #paneTransition: to: window. row on: #mouseLeave send: #paneTransition: to: window.! ! !CodeHolder methodsFor: 'misc' stamp: 'JW 2/3/2001 10:12'! addOptionalAnnotationsTo: window at: fractions plus: verticalOffset "Add an annotation pane to the window if preferences indicate a desire for it, and return the incoming verticalOffset plus the height of the added pane, if any" | aTextMorph divider delta | self wantsAnnotationPane ifFalse: [^ verticalOffset]. aTextMorph _ PluggableTextMorph on: self text: #annotation accept: nil readSelection: nil menu: #annotationPaneMenu:shifted:. aTextMorph askBeforeDiscardingEdits: false; borderWidth: 0; hideScrollBarIndefinitely. divider _ SubpaneDividerMorph forBottomEdge. delta _ self defaultAnnotationPaneHeight. window addMorph: aTextMorph fullFrame: (LayoutFrame fractions: fractions offsets: (0@verticalOffset corner: 0@(verticalOffset + delta - 1))). window addMorph: divider fullFrame: (LayoutFrame fractions: fractions offsets: (0@(verticalOffset + delta - 1) corner: 0@(verticalOffset + delta))). ^ verticalOffset + delta! ! !CodeHolder methodsFor: 'misc' stamp: 'JW 2/3/2001 09:39'! addOptionalButtonsTo: window at: fractions plus: verticalOffset "If the receiver wishes it, add a button pane to the window, and answer the verticalOffset plus the height added" | delta buttons divider | self wantsOptionalButtons ifFalse: [^verticalOffset]. delta _ self defaultButtonPaneHeight. buttons _ self optionalButtonRow color: (Display depth <= 8 ifTrue: [Color transparent] ifFalse: [Color gray alpha: 0.2]); borderWidth: 0. divider _ SubpaneDividerMorph forBottomEdge. window addMorph: buttons fullFrame: (LayoutFrame fractions: fractions offsets: (0@verticalOffset corner: 0@(verticalOffset + delta - 1))). window addMorph: divider fullFrame: (LayoutFrame fractions: fractions offsets: (0@(verticalOffset + delta - 1) corner: 0@(verticalOffset + delta))). ^ verticalOffset + delta! ! !CodeHolder methodsFor: 'misc' stamp: 'RAA 1/13/2001 07:20'! annotationPaneMenu: aMenu shifted: shifted ^ aMenu labels: 'change pane size' lines: #() selections: #(toggleAnnotationPaneSize)! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/28/2001 00:11'! browseSenders "Create and schedule a message set browser on all senders of the currently selected message selector. Do nothing if no message is selected." | aMessageName | (aMessageName _ self selectedMessageName) ifNotNil: [Smalltalk browseAllCallsOn: aMessageName]! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/4/2001 12:34'! buildClassBrowserEditString: aString "Create and schedule a new class browser for the current selection, with initial textual contents set to aString. This is used specifically in spawning where a class is established but a method-category is not." | newBrowser | newBrowser _ Browser new. newBrowser setClass: self selectedClassOrMetaClass selector: nil. newBrowser editSelection: #newMessage. Browser openBrowserView: (newBrowser openOnClassWithEditString: aString) label: 'Class Browser: ', self selectedClassOrMetaClass name ! ! !CodeHolder methodsFor: 'misc' stamp: 'RAA 1/11/2001 08:54'! buildMorphicCodePaneWith: editString | codePane | codePane _ PluggableTextMorph on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. editString ifNotNil: [ codePane editString: editString. codePane hasUnacceptedEdits: true ]. ^codePane ! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/7/2001 07:15'! buttonWithSelector: aSelector "If receiver has a control button with the given action selector answer it, else answer nil. morphic only at this point" | aWindow aPane | ((aWindow _ self containingWindow) isKindOf: SystemWindow) ifFalse: [^ nil]. (aPane _ aWindow submorphNamed: 'buttonPane') ifNil: [^ nil]. ^ aPane submorphThat: [:m | (m isKindOf: PluggableButtonMorph) and: [m actionSelector == aSelector]] ifNone: [^ nil]! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/5/2001 07:19'! decorateButtons "Change screen feedback for any buttons in the UI of the receiver that may wish it. Initially, it is only the Inheritance button that is decorated, but one can imagine others." self decorateForInheritance ! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/5/2001 07:15'! decorateForInheritance "Check to see if the currently-viewed method has a super send or an override, and if so, change screen feedback, unless the #decorateBrowserButtons says not to." | aColor aButton | (aButton _ self inheritanceButton) ifNil: [^ self]. aColor _ (currentCompiledMethod == nil or: [Preferences decorateBrowserButtons not]) ifTrue: [Color transparent] ifFalse: [currentCompiledMethod sendsToSuper ifTrue: [self isThereAnOverride ifTrue: [Color blue muchLighter] ifFalse: [Color green muchLighter ]] ifFalse: [self isThereAnOverride ifTrue: [Color tan lighter] ifFalse: [Color transparent]]]. aButton offColor: aColor! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/25/2001 14:44'! inheritanceButton "If receiver has an Inheritance button, answer it, else answer nil. morphic only at this point" ^ self buttonWithSelector: #methodHierarchy! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 12/28/2000 16:43'! isThereAnOverride "Answer whether any subclass of my selected class implements my selected selector" | aName aList aClass | (aName _ self selectedMessageName) ifNil: [^ false]. aList _ Smalltalk allImplementorsOf: aName. aClass _ self selectedClassOrMetaClass. aList do: [:element | MessageSet parse: element toClassAndSelector: [:cl :sel | (cl inheritsFrom: aClass) ifTrue: [^ true]]]. ^ false! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 1/5/2001 07:31'! modelWakeUpIn: aWindow "The window has been activated. Respond to possible changes that may have taken place while it was inactive" self updateListsAndCodeIn: aWindow. self decorateButtons. super modelWakeUpIn: aWindow! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 12/5/2000 11:33'! okayToAccept "Answer whether it is okay to accept the receiver's input" self showComment ifTrue: [self inform: 'Sorry, for the moment you can only submit changes here when you are showing source. Later, you will be able to edit the isolated comment here and save it back, but only if YOU implement it!!.'. ^ false]. self showDiffs ifFalse: [^ true]. ^ SelectionMenu confirm: 'Caution!! You are "showing diffs" here, so there is a danger that some of the text in the code pane is contaminated by the "diff" display' trueChoice: 'accept anyway -- I''ll take my chances' falseChoice: 'um, let me reconsider' ! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 9/27/1999 14:09'! priorSourceOrNil "If the currently-selected method has a previous version, return its source, else return nil" | aClass aSelector changeRecords | (aClass _ self selectedClassOrMetaClass) ifNil: [^ nil]. (aSelector _ self selectedMessageName) ifNil: [^ nil]. changeRecords _ aClass changeRecordsAt: aSelector. (changeRecords == nil or: [changeRecords size <= 1]) ifTrue: [^ nil]. ^ (changeRecords at: 2) string ! ! !CodeHolder methodsFor: 'misc' stamp: 'tk 9/9/2000 21:08'! releaseCachedState "Can always be found again. Don't write on a file." currentCompiledMethod _ nil.! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 5/8/2000 12:34'! sampleInstanceOfSelectedClass | aClass | "Return a sample instance of the class currently being pointed at" (aClass _ self selectedClassOrMetaClass) ifNil: [^ nil]. ^ aClass theNonMetaClass initializedInstance! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 12/28/2000 15:32'! setClassAndSelectorIn: csBlock "Evaluate csBlock with my selected class and and selector as its arguments; provide nil arguments if I don't have a method currently selected" | aName | (aName _ self selectedMessageName) ifNil: [csBlock value: nil value: nil] ifNotNil: [csBlock value: self selectedClassOrMetaClass value: aName] ! ! !CodeHolder methodsFor: 'self-updating' stamp: 'di 1/17/2001 16:05'! didCodeChangeElsewhere "Determine whether the code for the currently selected method and class has been changed somewhere else." | aClass aSelector aCompiledMethod | currentCompiledMethod ifNil: [^ false]. (aClass _ self selectedClassOrMetaClass) ifNil: [^ false]. (aSelector _ self selectedMessageName) ifNil: [^ false]. ^ ((aCompiledMethod _ aClass compiledMethodAt: aSelector ifAbsent: [^ false]) ~~ currentCompiledMethod) and: [aCompiledMethod last ~= 0 "either not yet installed" or: [currentCompiledMethod last = 0 "or these methods don't have source pointers"]] ! ! !CodeHolder methodsFor: 'self-updating' stamp: 'sw 10/19/1999 08:37'! stepIn: aSystemWindow self updateListsAndCodeIn: aSystemWindow! ! !CodeHolder methodsFor: 'self-updating' stamp: 'sw 10/19/1999 17:30'! updateCodePaneIfNeeded "If the code for the currently selected method has changed underneath me, then update the contents of my code pane unless it holds unaccepted edits" self didCodeChangeElsewhere ifTrue: [self hasUnacceptedEdits ifFalse: [contents _ nil. self contentsChanged] ifTrue: [self changed: #codeChangedElsewhere]]! ! !CodeHolder methodsFor: 'self-updating' stamp: 'sw 10/19/1999 14:14'! updateListsAndCodeIn: aWindow super updateListsAndCodeIn: aWindow. self updateCodePaneIfNeeded! ! !CodeHolder methodsFor: 'self-updating' stamp: 'sw 10/20/1999 12:22'! wantsStepsIn: aWindow ^ Preferences smartUpdating! ! Object subclass: #CodeLoader instanceVariableNames: 'baseURL sourceFiles segments publicKey ' classVariableNames: 'DefaultBaseURL DefaultKey ' poolDictionaries: '' category: 'Framework-Download'! !CodeLoader commentStamp: '' prior: 0! CodeLoader provides a simple facility for loading code from the network. Examples: | loader | loader _ CodeLoader new. loader baseURL:'http://isgwww.cs.uni-magdeburg.de/~raab/test/'. loader localCache: #('.cache' 'source'). "Sources and segments can be loaded in parallel" loader loadSourceFiles: #('file1.st' 'file2.st.gz'). loader localCache: #('.cache' 'segments'). loader loadSegments: #('is1.extseg' 'is2.extseg.gz'). "Install sources first - will wait until the files are actually loaded" loader installSourceFiles. "And then the segments" loader installSegments.! !CodeLoader methodsFor: 'accessing' stamp: 'ar 12/13/1999 18:19'! baseURL ^baseURL! ! !CodeLoader methodsFor: 'accessing' stamp: 'ar 12/13/1999 18:19'! baseURL: aString baseURL _ aString.! ! !CodeLoader methodsFor: 'accessing' stamp: 'ar 12/22/1999 15:07'! publicKey ^publicKey! ! !CodeLoader methodsFor: 'accessing' stamp: 'ar 12/22/1999 15:07'! publicKey: aPublicKey publicKey _ aPublicKey! ! !CodeLoader methodsFor: 'loading' stamp: 'mir 10/13/2000 12:24'! loadSegments: anArray "Load all the source files in the given array." | loader request reqName | loader _ HTTPLoader default. segments _ anArray collect:[:name | reqName _ (FileDirectory extensionFor: name) isEmpty ifTrue: [FileDirectory fileName: name extension: ImageSegment compressedFileExtension] ifFalse: [name]. request _ self createRequestFor: reqName in: loader. name->request]. ! ! !CodeLoader methodsFor: 'loading' stamp: 'ar 12/14/1999 14:40'! loadSourceFiles: anArray "Load all the source files in the given array." | loader request | loader _ HTTPLoader default. sourceFiles _ anArray collect:[:name| request _ self createRequestFor: name in: loader. request]. ! ! !CodeLoader methodsFor: 'installing' stamp: 'ar 1/19/2001 16:28'! installProject "Assume that we're loading a single file and it's a project" | aStream | aStream _ sourceFiles first contentStream. aStream ifNil:[^self error:'Project was not loaded']. ProjectLoading openFromFile: aStream fromDirectory: nil withProjectView: nil. ! ! !CodeLoader methodsFor: 'installing' stamp: 'mir 2/23/2000 12:58'! installSegment: reqEntry "Install the previously loaded segment" | secured contentStream contents | contentStream _ reqEntry value contentStream. contentStream ifNil:[^self error:'No content to install: ', reqEntry key printString]. secured _ self positionedToSecuredContentsOf: contentStream. secured ifFalse: [ (contentStream respondsTo: #close) ifTrue:[contentStream close]. ^self error:'Insecure content encountered: ', reqEntry key printString]. contents _ contentStream upToEnd unzipped. (contentStream respondsTo: #close) ifTrue:[contentStream close]. ^(RWBinaryOrTextStream with: contents) reset fileInObjectAndCode install.! ! !CodeLoader methodsFor: 'installing' stamp: 'mir 1/20/2000 13:37'! installSegments "Install the previously loaded segments" segments == nil ifTrue:[^self]. segments do:[:req| self installSegment: req]. segments _ nil.! ! !CodeLoader methodsFor: 'installing' stamp: 'mir 2/23/2000 12:59'! installSourceFile: aStream "Install the previously loaded source file" | contents secured | aStream ifNil:[^self error:'No content to install']. secured _ self positionedToSecuredContentsOf: aStream. secured ifFalse: [ (aStream respondsTo: #close) ifTrue:[aStream close]. ^self error:'Insecure content encountered']. contents _ aStream upToEnd unzipped. (aStream respondsTo: #close) ifTrue:[aStream close]. ^(RWBinaryOrTextStream with: contents) reset fileIn! ! !CodeLoader methodsFor: 'installing' stamp: 'ar 12/22/1999 15:02'! installSourceFiles "Install the previously loaded source files" sourceFiles == nil ifTrue:[^self]. sourceFiles do:[:req| self installSourceFile: req contentStream]. sourceFiles _ nil.! ! !CodeLoader methodsFor: 'private' stamp: 'mir 1/10/2000 18:56'! createRequestFor: name in: aLoader "Create a URL request for the given string, which can be cached locally." | request | request _ self httpRequestClass for: self baseURL , name in: aLoader. aLoader addRequest: request. "fetch from URL" ^request! ! !CodeLoader methodsFor: 'private' stamp: 'mir 12/22/1999 14:13'! httpRequestClass ^HTTPRequest! ! !CodeLoader methodsFor: 'private' stamp: 'mir 2/23/2000 12:57'! positionedToSecuredContentsOf: aStream "Private - return the 'secured' part of the given stream, e.g., the actual contents without the verification checksum. If the verification fails return nil (since there is no secured contents)." | hash signature part1 part2 dsa okay | "No key, no security..." publicKey == nil ifTrue: [^true]. aStream position: 0. aStream binary. part1 _ aStream nextInto: (LargePositiveInteger basicNew: 20). part2 _ aStream nextInto: (LargePositiveInteger basicNew: 20). signature _ Array with: part1 with: part2. hash _ SecureHashAlgorithm new hashStream: aStream. dsa _ DigitalSignatureAlgorithm new. okay _ dsa verifySignature: signature ofMessageHash: hash publicKey: publicKey. okay ifFalse: [^false]. aStream ascii. aStream position: 40. ^true! ! !CodeLoader methodsFor: 'initialize-release' stamp: 'mir 1/11/2000 13:47'! initialize publicKey _ DefaultKey. baseURL _ self class defaultBaseURL! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CodeLoader class instanceVariableNames: ''! !CodeLoader class methodsFor: 'instance creation' stamp: 'ar 12/22/1999 15:08'! new ^super new initialize! ! !CodeLoader class methodsFor: 'accessing' stamp: 'mir 1/11/2000 13:45'! defaultBaseURL ^DefaultBaseURL ifNil: ['']! ! !CodeLoader class methodsFor: 'accessing' stamp: 'mir 1/11/2000 13:45'! defaultBaseURL: aURLString DefaultBaseURL _ aURLString! ! !CodeLoader class methodsFor: 'accessing' stamp: 'ar 12/22/1999 15:08'! defaultKey "Return the default key used for verifying signatures of loaded code" ^DefaultKey! ! !CodeLoader class methodsFor: 'accessing' stamp: 'mir 1/10/2000 18:16'! defaultKey: aPublicKey "Store the default key used for verifying signatures of loaded code" DefaultKey _ aPublicKey "CodeLoader defaultKey: DOLPublicKey" "CodeLoader defaultKey: (DigitalSignatureAlgorithm testKeySet at: 2)"! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 9/6/2000 15:03'! compressFileNamed: aFileName self compressFileNamed: aFileName in: FileDirectory default! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 10/13/2000 13:27'! compressFileNamed: aFileName in: aDirectory "Compress the currently selected file" | zipped buffer unzipped zipFileName | unzipped _ aDirectory readOnlyFileNamed: (aDirectory fullNameFor: aFileName). unzipped binary. zipFileName _ aFileName copyUpToLast: $. . zipped _ aDirectory newFileNamed: (zipFileName, FileDirectory dot, ImageSegment compressedFileExtension). zipped binary. zipped _ GZipWriteStream on: zipped. buffer _ ByteArray new: 50000. 'Compressing ', zipFileName displayProgressAt: Sensor cursorPoint from: 0 to: unzipped size during:[:bar| [unzipped atEnd] whileFalse:[ bar value: unzipped position. zipped nextPutAll: (unzipped nextInto: buffer)]. zipped close. unzipped close]. ! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 1/18/2000 16:22'! exportCategories: catList to: aFileName "CodeLoader exportCategories: #( 'Game-Animation' 'Game-Framework' ) to: 'Game-Framework'" | list classList | classList _ OrderedCollection new. catList do: [:catName | list _ SystemOrganization listAtCategoryNamed: catName asSymbol. list do: [:nm | classList add: (Smalltalk at: nm); add: (Smalltalk at: nm) class]]. self exportCodeSegment: aFileName classes: classList keepSource: true! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 1/18/2000 20:53'! exportCategoryNamed: catName "CodeLoader exportCategoryNamed: 'OceanicPanic' " | list | list _ SystemOrganization listAtCategoryNamed: catName asSymbol. self exportClassesNamed: list to: catName! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 1/18/2000 20:53'! exportClassesNamed: classNameList to: aFileName | classList | classList _ OrderedCollection new. classNameList do: [:nm | classList add: (Smalltalk at: nm); add: (Smalltalk at: nm) class]. self exportCodeSegment: aFileName classes: classList keepSource: true! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 10/11/2000 19:12'! exportCodeSegment: exportName classes: aClassList keepSource: keepSources "Code for writing out a specific category of classes as an external image segment. Perhaps this should be a method." | is oldMethods newMethods m oldCodeString argsAndTemps classList symbolHolder fileName | keepSources ifTrue: [ self confirm: 'We are going to abandon sources. Quit without saving after this has run.' orCancel: [^self]]. classList _ aClassList asArray. "Strong pointers to symbols" symbolHolder := Symbol allInstances. oldMethods _ OrderedCollection new: classList size * 150. newMethods _ OrderedCollection new: classList size * 150. keepSources ifTrue: [ classList do: [:cl | cl selectors do: [:selector | m _ cl compiledMethodAt: selector. m fileIndex > 0 ifTrue: [oldCodeString _ cl sourceCodeAt: selector. argsAndTemps _ (cl compilerClass new parse: oldCodeString in: cl notifying: nil) tempNames. oldMethods addLast: m. newMethods addLast: (m copyWithTempNames: argsAndTemps)]]]]. oldMethods asArray elementsExchangeIdentityWith: newMethods asArray. oldMethods _ newMethods _ m _ oldCodeString _ argsAndTemps _ nil. Smalltalk garbageCollect. is _ ImageSegment new copyFromRootsForExport: classList. "Classes and MetaClasses" fileName _ FileDirectory fileName: exportName extension: ImageSegment fileExtension. is writeForExport: fileName. self compressFileNamed: fileName ! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 10/12/2000 17:39'! loadCode: codeSegmentName from: baseURL ifClassNotLoaded: testClass CodeLoader defaultBaseURL: baseURL. (Smalltalk includesKey: testClass) ifFalse: [CodeLoader loadCodeSegment: codeSegmentName]. ! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 10/11/2000 19:14'! loadCodeSegment: segmentName | loader | loader _ PluginCodeLoader new. loader loadSegments: (Array with: segmentName). loader installSegments.! ! !CodeLoader class methodsFor: 'utilities' stamp: 'ar 12/22/1999 15:09'! signFile: fileName renameAs: destFile key: privateKey dsa: dsa "Sign the given file using the private key." | in hash sig out | in _ FileStream readOnlyFileNamed: fileName. in binary. hash _ SecureHashAlgorithm new hashStream: in. sig _ dsa computeSignatureForMessageHash: hash privateKey: privateKey. out _ FileStream newFileNamed: destFile. out binary. out nextPutAll: sig first; nextPutAll: sig last. in position: 0. [in atEnd] whileFalse:[out nextPutAll: (in next: 4096)]. in close. out close.! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 2/14/2000 16:47'! signFiles: fileNames in: dirName key: privateKey "Sign the files in the current directory and put them into a folder signed." | newNames oldNames | oldNames _ fileNames collect:[:fileName | dirName , FileDirectory slash, fileName]. newNames _ fileNames collect:[:fileName | dirName , FileDirectory slash, 'signed', FileDirectory slash, fileName]. CodeLoader signFilesFrom: oldNames to: newNames key: privateKey! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 1/18/2000 18:49'! signFiles: fileNames key: privateKey "Sign the files in the current directory and put them into a folder signed." | newNames | newNames _ fileNames collect:[:fileName | 'signed', FileDirectory slash, fileName]. CodeLoader signFilesFrom: fileNames to: newNames key: privateKey! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 1/18/2000 11:14'! signFilesFrom: sourceNames to: destNames key: privateKey "Sign all the given files using the private key. This will add an 's' to the extension of the file." "| fd oldNames newNames | fd _ FileDirectory default directoryNamed:'unsigned'. oldNames _ fd fileNames. newNames _ oldNames collect:[:name| 'signed', FileDirectory slash, name]. oldNames _ oldNames collect:[:name| 'unsigned', FileDirectory slash, name]. CodeLoader signFilesFrom: oldNames to: newNames key: DOLPrivateKey." | dsa | dsa _ DigitalSignatureAlgorithm new. dsa initRandom: (dsa randomBitsFromSoundInput: 512). 'Signing files...' displayProgressAt: Sensor cursorPoint from: 1 to: sourceNames size during:[:bar| 1 to: sourceNames size do:[:i| bar value: i. self signFile: (sourceNames at: i) renameAs: (destNames at: i) key: privateKey dsa: dsa]]. ! ! !CodeLoader class methodsFor: 'utilities' stamp: 'mir 2/23/2000 12:57'! verifySignedFileNamed: aFileName "CodeLoader verifySignedFileNamed: 'signed\dummy1.dsq' " | secured signedFileStream | signedFileStream _ FileStream fileNamed: aFileName. secured _ CodeLoader new positionedToSecuredContentsOf: signedFileStream. signedFileStream close. Transcript show: aFileName , ' verified: '; show: secured printString; cr. ! ! ServerAction subclass: #CodeServer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !CodeServer commentStamp: '' prior: 0! Return the source code from Smalltalk, as web page text, or as a raw Squeak file chunk. URLs are of the form: machine:80/smtlk.Point|min; <-- NOTE: use ; instead of : machine:80/smtlk.{Class}|{selector} machine:80/chunk.{Class}|{selector} machine:80/smtlk.{Class}|class|{selector} machine:80/chunk.{Class}|class|{selector} machine:80/smtlk.{Class}|Definition machine:80/chunk.{Class}|Definition machine:80/smtlk.{Class}|Hierarchy machine:80/chunk.{Class}|Hierarchy machine:80/smtlk.{Class}|Comment machine:80/chunk.{Class}|Comment NOTE: use ; semicolon instead of : colon in selector names ! !CodeServer methodsFor: 'as yet unclassified' stamp: 'tk 5/7/1998 15:36'! chunk: request "Return Smalltalk source code as a chunk from the changes file. URL = machine:80/chunk.Point|min; included are: Point|at; Point|Comment Point|Hierarchy Point|Definition Point|class|x;y; Meant to be received by a Squeak client, not a browser. Reply not in HTML" | classAndMethod set strm chunk | classAndMethod _ request message atPin: 2. classAndMethod _ classAndMethod copyReplaceAll: '|' with: ' '. classAndMethod _ classAndMethod copyReplaceAll: ';' with: ':'. set _ LinkedMessageSet messageList: (Array with: classAndMethod). strm _ WriteStream on: (String new: 300). strm nextChunkPutWithStyle: (set selectedMessage). "String or text" chunk _ strm contents. request reply: 'content-length: ', chunk size printString, PWS crlfcrlf. request reply: chunk. ! ! !CodeServer methodsFor: 'as yet unclassified' stamp: 'tk 5/7/1998 15:35'! process: request "Return the source code from Smalltalk, as text or as a chunk. URLs are of this form. Each may have 'chunk' or 'smtlk' as the thing after the slash machine:80/smtlk.Point|min; machine:80/chunk.{Class}|{selector} machine:80/smtlk.{Class}|{selector} machine:80/smtlk.{Class}|class|{selector} machine:80/smtlk.{Class}|Definition machine:80/smtlk.{Class}|Hierarchy machine:80/smtlk.{Class}|Comment NOTE: use ; semicolon instead of : colon in selector names!!!!!!" | coreRef | coreRef _ (request message at: 1) asLowercase. request reply: PWS success; reply: PWS contentHTML. Transcript show: 'In process: ', request message printString; cr. coreRef = 'smtlk' ifTrue: [^ self smtlk: request]. coreRef = 'chunk' ifTrue: [^ self chunk: request]. request reply: ( 'HTTP/1.0 400 Bad Request', PWS crlfcrlf, 'expected smtlk.{Class}|{selector} or chunk.{Class}|{selector}'). "failure"! ! !CodeServer methodsFor: 'as yet unclassified' stamp: 'tk 5/7/1998 15:36'! smtlk: request "Return Smalltalk sourcecode in HTML. URL = machine:80/myswiki.smtlk.Point|min; included are: Point|min; Point|Comment Point|Hierarchy Point|Definition Point|class|x;y; NOTE: use ; instead of : in selector names!!!!!!" | classAndMethod set | classAndMethod _ request message atPin: 2. classAndMethod _ classAndMethod copyReplaceAll: '|' with: ' '. classAndMethod _ classAndMethod copyReplaceAll: ';' with: ':'. set _ LinkedMessageSet messageList: (Array with: classAndMethod). request reply: PWS crlf, (HTMLformatter evalEmbedded: (self fileContents: 'swiki',(ServerAction pathSeparator),'smtlk.html') with: set).! ! RectangleMorph subclass: #CodecDemoMorph instanceVariableNames: 'codecClassName ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Interface'! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:37'! acceptDroppingMorph: aMorph event: evt | codecClass | 'None' = codecClassName ifTrue: [aMorph sound play] ifFalse: [ codecClass _ Smalltalk at: codecClassName ifAbsent: [^ self]. (codecClass new compressAndDecompress: aMorph sound) play]. aMorph position: self topRight + (10@0). ! ! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:33'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'select codec' action: #selectCodec. ! ! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:36'! codecClassName: aStringOrSymbol | label | codecClassName _ aStringOrSymbol asSymbol. self removeAllMorphs. label _ StringMorph contents: aStringOrSymbol. label position: self position + (5@5). self addMorph: label. label lock: true. self extent: label extent + (10@10). ! ! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:18'! initialize super initialize. self borderWidth: 2. self color: (Color r: 1.0 g: 0.806 b: 0.677). self codecClassName: 'MuLawCodec'. ! ! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:33'! selectCodec | aMenu codecs newCodec | aMenu _ CustomMenu new title: 'Codec:'. codecs _ (SoundCodec allSubclasses collect: [:c | c name]) asSortedCollection. codecs add: 'None'. codecs do:[:cName | aMenu add: cName action: cName]. newCodec _ aMenu startUp. newCodec ifNil: [^ self]. self codecClassName: newCodec. ! ! !CodecDemoMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/4/1999 12:19'! wantsDroppedMorph: aMorph event: evt ^ aMorph isMemberOf: SoundTile ! ! SystemWindow subclass: #CollapsedMorph instanceVariableNames: 'uncollapsedMorph ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !CollapsedMorph methodsFor: 'as yet unclassified' stamp: 'sw 5/9/2000 00:18'! beReplacementFor: aMorph | itsWorld priorPosition | (itsWorld _ aMorph world) ifNil: [^self]. uncollapsedMorph _ aMorph. self setLabel: aMorph externalName. aMorph delete. itsWorld addMorphFront: self. self collapseOrExpand. (priorPosition _ aMorph valueOfProperty: #collapsedPosition ifAbsent: [nil]) ifNotNil: [self position: priorPosition]. ! ! !CollapsedMorph methodsFor: 'as yet unclassified' stamp: 'sw 9/1/2000 11:06'! buildWindowMenu "Answer the menu to be put up in response to the user's clicking on the window-menu control in the window title. Specialized for CollapsedMorphs." | aMenu | aMenu _ MenuMorph new defaultTarget: self. aMenu add: 'change name...' action: #relabel. aMenu addLine. aMenu add: 'send to back' action: #sendToBack. aMenu add: 'make next-to-topmost' action: #makeSecondTopmost. aMenu addLine. self mustNotClose ifFalse: [aMenu add: 'make unclosable' action: #makeUnclosable] ifTrue: [aMenu add: 'make closable' action: #makeClosable]. aMenu add: (self isSticky ifTrue: ['make draggable'] ifFalse: ['make undraggable']) action: #toggleStickiness. ^aMenu! ! !CollapsedMorph methodsFor: 'as yet unclassified' stamp: 'sw 9/1/2000 11:07'! collapseOrExpand "Toggle the expand/collapsd state of the receiver. If expanding, copy the window title back to the name of the expanded morph" | aWorld | isCollapsed ifTrue: [uncollapsedMorph setProperty: #collapsedPosition toValue: self position. labelString ifNotNil: [uncollapsedMorph setNameTo: labelString]. mustNotClose _ false. "We're not closing but expanding" self delete. (aWorld _ self currentWorld) addMorphFront: uncollapsedMorph. aWorld startSteppingSubmorphsOf: uncollapsedMorph] ifFalse: [super collapseOrExpand]! ! Object subclass: #Collection instanceVariableNames: '' classVariableNames: 'RandomForPicking ' poolDictionaries: '' category: 'Collections-Abstract'! !Collection commentStamp: '' prior: 0! I am the abstract superclass of all classes that represent a group of elements.! !Collection methodsFor: 'accessing' stamp: 'sma 5/12/2000 11:33'! anyOne "Answer a representative sample of the receiver. This method can be helpful when needing to preinfer the nature of the contents of semi-homogeneous collections." self emptyCheck. self do: [:each | ^ each]! ! !Collection methodsFor: 'accessing' stamp: 'sma 5/12/2000 11:41'! capacity "Answer the current capacity of the receiver." ^ self size! ! !Collection methodsFor: 'accessing' stamp: 'sma 5/12/2000 11:34'! size "Answer how many elements the receiver contains." | tally | tally _ 0. self do: [:each | tally _ tally + 1]. ^ tally! ! !Collection methodsFor: 'adapting' stamp: 'di 11/6/1998 13:34'! adaptToCollection: rcvr andSend: selector "If I am involved in arithmetic with another Collection, return a Collection of the results of each element combined with the scalar in that expression." rcvr isSequenceable & self isSequenceable ifFalse: [self error: 'Only sequenceable collections may be combined arithmetically']. ^ rcvr with: self collect: [:rcvrElement :myElement | rcvrElement perform: selector with: myElement]! ! !Collection methodsFor: 'adapting' stamp: 'di 11/9/1998 12:16'! adaptToNumber: rcvr andSend: selector "If I am involved in arithmetic with a scalar, return a Collection of the results of each element combined with the scalar in that expression." ^ self collect: [:element | rcvr perform: selector with: element]! ! !Collection methodsFor: 'adapting' stamp: 'di 11/6/1998 13:37'! adaptToPoint: rcvr andSend: selector "If I am involved in arithmetic with a scalar, return a Collection of the results of each element combined with the scalar in that expression." ^ self collect: [:element | rcvr perform: selector with: element]! ! !Collection methodsFor: 'adapting' stamp: 'di 11/6/1998 13:37'! adaptToString: rcvr andSend: selector "If I am involved in arithmetic with a String, convert it to a Number." ^ rcvr asNumber perform: selector with: self! ! !Collection methodsFor: 'adding'! add: newObject "Include newObject as one of the receiver's elements. Answer newObject. ArrayedCollections cannot respond to this message." self subclassResponsibility! ! !Collection methodsFor: 'adding' stamp: 'sma 5/12/2000 17:21'! add: newObject withOccurrences: anInteger "Add newObject anInteger times to the receiver. Answer newObject." anInteger timesRepeat: [self add: newObject]. ^ newObject! ! !Collection methodsFor: 'adding' stamp: 'sma 5/12/2000 17:26'! addAll: aCollection "Include all the elements of aCollection as the receiver's elements. Answer aCollection. Actually, any object responding to #do: can be used as argument." aCollection do: [:each | self add: each]. ^ aCollection! ! !Collection methodsFor: 'adding' stamp: 'sma 5/12/2000 17:23'! addIfNotPresent: anObject "Include anObject as one of the receiver's elements, but only if there is no such element already. Anwser anObject." (self includes: anObject) ifFalse: [self add: anObject]. ^ anObject! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:53'! * arg ^ arg adaptToCollection: self andSend: #*! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:53'! + arg ^ arg adaptToCollection: self andSend: #+! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:53'! - arg ^ arg adaptToCollection: self andSend: #-! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:53'! / arg ^ arg adaptToCollection: self andSend: #/! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:54'! // arg ^ arg adaptToCollection: self andSend: #//! ! !Collection methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:54'! \\ arg ^ arg adaptToCollection: self andSend: #\\! ! !Collection methodsFor: 'comparing' stamp: 'SqR 8/3/2000 13:36'! hash "Answer an integer hash value for the receiver such that, -- the hash value of an unchanged object is constant over time, and -- two equal objects have equal hash values" | hash | hash _ self species hash. self size <= 10 ifTrue: [self do: [:elem | hash _ hash bitXor: elem hash]]. ^hash bitXor: self size hash! ! !Collection methodsFor: 'converting' stamp: 'sma 5/6/2000 20:22'! asArray "Answer an Array whose elements are the elements of the receiver. Implementation note: Cannot use ''Array withAll: self'' as that only works for SequenceableCollections which support the replacement primitive." | array index | array _ Array new: self size. index _ 0. self do: [:each | array at: (index _ index + 1) put: each]. ^ array! ! !Collection methodsFor: 'converting' stamp: 'sma 5/6/2000 20:10'! asBag "Answer a Bag whose elements are the elements of the receiver." ^ Bag withAll: self! ! !Collection methodsFor: 'converting' stamp: 'sma 5/6/2000 20:22'! asByteArray "Answer a ByteArray whose elements are the elements of the receiver. Implementation note: Cannot use ''ByteArray withAll: self'' as that only works for SequenceableCollections which support the replacement primitive." | array index | array _ ByteArray new: self size. index _ 0. self do: [:each | array at: (index _ index + 1) put: each]. ^ array! ! !Collection methodsFor: 'converting' stamp: 'sma 5/6/2000 20:26'! asCharacterSet "Answer a CharacterSet whose elements are the unique elements of the receiver. The reciever should only contain characters." ^ CharacterSet newFrom: self! ! !Collection methodsFor: 'converting' stamp: 'ar 9/22/2000 10:12'! asIdentitySet ^(IdentitySet new: self size) addAll: self; yourself! ! !Collection methodsFor: 'converting' stamp: 'sma 5/12/2000 17:43'! asOrderedCollection "Answer an OrderedCollection whose elements are the elements of the receiver. The order in which elements are added depends on the order in which the receiver enumerates its elements. In the case of unordered collections, the ordering is not necessarily the same for multiple requests for the conversion." ^ self as: OrderedCollection! ! !Collection methodsFor: 'converting' stamp: 'sma 5/6/2000 20:29'! asSet "Answer a Set whose elements are the unique elements of the receiver." ^ Set withAll: self! ! !Collection methodsFor: 'converting'! asSortedArray "Return a copy of the receiver in sorted order, as an Array. 6/10/96 sw" ^ self asSortedCollection asArray! ! !Collection methodsFor: 'converting' stamp: 'sma 5/12/2000 17:44'! asSortedCollection "Answer a SortedCollection whose elements are the elements of the receiver. The sort order is the default less than or equal." ^ self as: SortedCollection! ! !Collection methodsFor: 'converting' stamp: 'sma 5/12/2000 17:46'! asSortedCollection: aSortBlock "Answer a SortedCollection whose elements are the elements of the receiver. The sort order is defined by the argument, aSortBlock." | aSortedCollection | aSortedCollection _ SortedCollection new: self size. aSortedCollection sortBlock: aSortBlock. aSortedCollection addAll: self. ^ aSortedCollection! ! !Collection methodsFor: 'copying' stamp: 'sma 5/12/2000 14:41'! copyWith: newElement "Answer a new collection with newElement added (as last element if sequenceable)." ^ self copy add: newElement; yourself! ! !Collection methodsFor: 'copying' stamp: 'sma 5/12/2000 14:43'! copyWithout: oldElement "Answer a copy of the receiver that does not contain any elements equal to oldElement." ^ self reject: [:each | each = oldElement] "Examples: 'fred the bear' copyWithout: $e #(2 3 4 5 5 6) copyWithout: 5 "! ! !Collection methodsFor: 'copying' stamp: 'sma 5/12/2000 18:08'! copyWithoutAll: aCollection "Answer a copy of the receiver that does not contain any elements equal to those in aCollection." ^ self reject: [:each | aCollection includes: each]! ! !Collection methodsFor: 'enumerating' stamp: 'sma 4/30/2000 11:17'! allSatisfy: aBlock "Evaluate aBlock with the elements of the receiver. If aBlock returns false for any element return false. Otherwise return true." self do: [:each | (aBlock value: each) ifFalse: [^ false]]. ^ true! ! !Collection methodsFor: 'enumerating' stamp: 'sma 4/30/2000 11:17'! anySatisfy: aBlock "Evaluate aBlock with the elements of the receiver. If aBlock returns true for any element return true. Otherwise return false." self do: [:each | (aBlock value: each) ifTrue: [^ true]]. ^ false! ! !Collection methodsFor: 'enumerating'! associationsDo: aBlock "Evaluate aBlock for each of the receiver's elements (key/value associations). If any non-association is within, the error is not caught now, but later, when a key or value message is sent to it." self do: aBlock! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:45'! collect: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Collect the resulting values into a collection like the receiver. Answer the new collection." | newCollection | newCollection _ self species new. self do: [:each | newCollection add: (aBlock value: each)]. ^ newCollection! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:51'! collect: collectBlock thenSelect: selectBlock "Utility method to improve readability." ^ (self collect: collectBlock) select: selectBlock! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:52'! count: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Answer the number of elements that answered true." | sum | sum _ 0. self do: [:each | (aBlock value: each) ifTrue: [sum _ sum + 1]]. ^ sum! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:20'! detect: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Answer the first element for which aBlock evaluates to true." ^ self detect: aBlock ifNone: [self errorNotFound: aBlock]! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:52'! detect: aBlock ifNone: exceptionBlock "Evaluate aBlock with each of the receiver's elements as the argument. Answer the first element for which aBlock evaluates to true. If none evaluate to true, then evaluate the argument, exceptionBlock." self do: [:each | (aBlock value: each) ifTrue: [^ each]]. ^ exceptionBlock value! ! !Collection methodsFor: 'enumerating'! detectMax: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Answer the element for which aBlock evaluates to the highest magnitude. If collection empty, return nil. This method might also be called elect:." | maxElement maxValue val | self do: [:each | maxValue == nil ifFalse: [ (val _ aBlock value: each) > maxValue ifTrue: [ maxElement _ each. maxValue _ val]] ifTrue: ["first element" maxElement _ each. maxValue _ aBlock value: each]. "Note that there is no way to get the first element that works for all kinds of Collections. Must test every one."]. ^ maxElement! ! !Collection methodsFor: 'enumerating'! detectMin: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Answer the element for which aBlock evaluates to the lowest number. If collection empty, return nil." | minElement minValue val | self do: [:each | minValue == nil ifFalse: [ (val _ aBlock value: each) < minValue ifTrue: [ minElement _ each. minValue _ val]] ifTrue: ["first element" minElement _ each. minValue _ aBlock value: each]. "Note that there is no way to get the first element that works for all kinds of Collections. Must test every one."]. ^ minElement! ! !Collection methodsFor: 'enumerating'! detectSum: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Return the sum of the answers." | sum | sum _ 0. self do: [:each | sum _ (aBlock value: each) + sum]. ^ sum! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 17:52'! difference: aCollection "Answer the set theoretic difference of two collections." ^ self reject: [:each | aCollection includes: each]! ! !Collection methodsFor: 'enumerating'! do: aBlock "Evaluate aBlock with each of the receiver's elements as the argument." self subclassResponsibility! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:57'! do: elementBlock separatedBy: separatorBlock "Evaluate the elementBlock for all elements in the receiver, and evaluate the separatorBlock between." | beforeFirst | beforeFirst _ true. self do: [:each | beforeFirst ifTrue: [beforeFirst _ false] ifFalse: [separatorBlock value]. elementBlock value: each]! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:59'! do: aBlock without: anItem "Enumerate all elements in the receiver. Execute aBlock for those elements that are not equal to the given item" ^ self do: [:each | anItem = each ifFalse: [aBlock value: each]]! ! !Collection methodsFor: 'enumerating' stamp: 'dvf 6/10/2000 18:32'! groupBy: keyBlock having: selectBlock "Like in SQL operation - Split the recievers contents into collections of elements for which keyBlock returns the same results, and return those collections allowed by selectBlock. keyBlock should return an Integer." | result key | result _ PluggableDictionary integerDictionary. self do: [:e | key _ keyBlock value: e. (result includesKey: key) ifFalse: [result at: key put: OrderedCollection new]. (result at: key) add: e]. ^ result _ result select: selectBlock! ! !Collection methodsFor: 'enumerating'! inject: thisValue into: binaryBlock "Accumulate a running value associated with evaluating the argument, binaryBlock, with the current value of the argument, thisValue, and the receiver as block arguments. For instance, to sum the numeric elements of a collection, aCollection inject: 0 into: [:subTotal :next | subTotal + next]." | nextValue | nextValue _ thisValue. self do: [:each | nextValue _ binaryBlock value: nextValue value: each]. ^nextValue! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 17:52'! intersection: aCollection "Answer the set theoretic intersection of two collections." ^ self select: [:each | aCollection includes: each]! ! !Collection methodsFor: 'enumerating' stamp: 'bf 3/10/2000 08:51'! noneSatisfy: aBlock "Evaluate aBlock with the elements of the receiver. If aBlock returns false for all elements return true. Otherwise return false" self do: [:item | (aBlock value: item) ifTrue: [^ false]]. ^ false! ! !Collection methodsFor: 'enumerating'! reject: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Collect into a new collection like the receiver only those elements for which aBlock evaluates to false. Answer the new collection." ^self select: [:element | (aBlock value: element) == false]! ! !Collection methodsFor: 'enumerating'! select: aBlock "Evaluate aBlock with each of the receiver's elements as the argument. Collect into a new collection like the receiver, only those elements for which aBlock evaluates to true. Answer the new collection." | newCollection | newCollection _ self species new. self do: [:each | (aBlock value: each) ifTrue: [newCollection add: each]]. ^newCollection! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 11:59'! select: selectBlock thenCollect: collectBlock "Utility method to improve readability." ^ (self select: selectBlock) collect: collectBlock! ! !Collection methodsFor: 'enumerating' stamp: 'sma 5/12/2000 17:54'! union: aCollection "Answer the set theoretic union of two collections." ^ self asSet addAll: aCollection; yourself! ! !Collection methodsFor: 'filter streaming' stamp: 'sma 5/12/2000 12:07'! contents ^ self! ! !Collection methodsFor: 'filter streaming' stamp: 'sma 5/12/2000 12:08'! flattenOnStream: aStream ^ aStream writeCollection: self! ! !Collection methodsFor: 'filter streaming' stamp: 'sma 5/12/2000 12:07'! write: anObject ^ self add: anObject! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:51'! abs "Absolute value of all elements in the collection" ^ self collect: [:a | a abs]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:57'! average ^ self sum / self size! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:51'! ceiling ^ self collect: [:a | a ceiling]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:51'! floor ^ self collect: [:a | a floor]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:52'! log ^ self collect: [:each | each log]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:58'! max ^ self inject: self anyOne into: [:max :each | max max: each]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 16:00'! median ^ self asSortedCollection median! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 16:00'! min ^ self inject: self anyOne into: [:min :each | min min: each]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:52'! negated "Negated value of all elements in the collection" ^ self collect: [:a | a negated]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 16:00'! range ^ self max - self min! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:53'! reciprocal "Return the reciever full of reciprocated elements" ^ self collect: [:a | a reciprocal]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:53'! rounded ^ self collect: [:a | a rounded]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:53'! sqrt ^ self collect: [:each | each sqrt]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:53'! squared ^ self collect: [:each | each * each]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 16:02'! sum "This is implemented using a variant of the normal inject:into: pattern. The reason for this is that it is not known whether we're in the normal number line, i.e. whether 0 is a good initial value for the sum. Consider a collection of measurement objects, 0 would be the unitless value and would not be appropriate to add with the unit-ed objects." | sum sample | sample _ self anyOne. sum _ self inject: sample into: [:accum :each | accum + each]. ^ sum - sample! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:54'! truncated ^ self collect: [:a | a truncated]! ! !Collection methodsFor: 'printing' stamp: 'sma 6/1/2000 09:39'! printElementsOn: aStream aStream nextPut: $(. self do: [:element | aStream print: element; space]. self isEmpty ifFalse: [aStream skip: -1]. aStream nextPut: $)! ! !Collection methodsFor: 'printing' stamp: 'sma 6/1/2000 09:41'! printNameOn: aStream super printOn: aStream! ! !Collection methodsFor: 'printing' stamp: 'sma 6/1/2000 09:41'! printOn: aStream "Append a sequence of characters that identify the receiver to aStream." self printNameOn: aStream. self printElementsOn: aStream! ! !Collection methodsFor: 'printing'! storeOn: aStream "Refer to the comment in Object|storeOn:." | noneYet | aStream nextPutAll: '(('. aStream nextPutAll: self class name. aStream nextPutAll: ' new)'. noneYet _ true. self do: [:each | noneYet ifTrue: [noneYet _ false] ifFalse: [aStream nextPut: $;]. aStream nextPutAll: ' add: '. aStream store: each]. noneYet ifFalse: [aStream nextPutAll: '; yourself']. aStream nextPut: $)! ! !Collection methodsFor: 'private'! emptyCheck self isEmpty ifTrue: [self errorEmptyCollection]! ! !Collection methodsFor: 'private'! errorEmptyCollection self error: 'this collection is empty'! ! !Collection methodsFor: 'private'! errorNoMatch self error: 'collection sizes do not match'! ! !Collection methodsFor: 'private' stamp: 'sma 5/12/2000 11:22'! errorNotFound: anObject "Actually, this should raise a special Exception not just an error." self error: 'Object is not in the collection.'! ! !Collection methodsFor: 'private'! errorNotKeyed self error: self class name, 's do not respond to keyed accessing messages.'! ! !Collection methodsFor: 'private'! toBraceStack: itsSize "Push receiver's elements onto the stack of thisContext sender. Error if receiver does not have itsSize elements or if receiver is unordered. Do not call directly: this is called by {a. b} _ ... constructs." self size ~= itsSize ifTrue: [self error: 'Trying to store ', self size printString, ' values into ', itsSize printString, ' variables.']. thisContext sender push: itsSize fromIndexable: self! ! !Collection methodsFor: 'removing' stamp: 'sma 5/12/2000 11:22'! remove: oldObject "Remove oldObject from the receiver's elements. Answer oldObject unless no element is equal to oldObject, in which case, raise an error. ArrayedCollections cannot respond to this message." ^ self remove: oldObject ifAbsent: [self errorNotFound: oldObject]! ! !Collection methodsFor: 'removing' stamp: 'sma 5/12/2000 11:14'! remove: oldObject ifAbsent: anExceptionBlock "Remove oldObject from the receiver's elements. If several of the elements are equal to oldObject, only one is removed. If no element is equal to oldObject, answer the result of evaluating anExceptionBlock. Otherwise, answer the argument, oldObject. ArrayedCollections cannot respond to this message." self subclassResponsibility! ! !Collection methodsFor: 'removing' stamp: 'sma 5/12/2000 11:14'! removeAll: aCollection "Remove each element of aCollection from the receiver. If successful for each, answer aCollection. Otherwise create an error notification. ArrayedCollections cannot respond to this message." aCollection do: [:each | self remove: each]. ^ aCollection! ! !Collection methodsFor: 'removing' stamp: 'sma 5/12/2000 11:16'! removeAllFoundIn: aCollection "Remove each element of aCollection which is present in the receiver from the receiver. Answer aCollection. No error is raised if an element isn't found. ArrayedCollections cannot respond to this message." aCollection do: [:each | self remove: each ifAbsent: []]. ^ aCollection! ! !Collection methodsFor: 'removing' stamp: 'sma 5/12/2000 11:19'! removeAllSuchThat: aBlock "Evaluate aBlock for each element and remove all that elements from the receiver for that aBlock evaluates to true. Use a copy to enumerate collections whose order changes when an element is removed (i.e. Sets)." self copy do: [:each | (aBlock value: each) ifTrue: [self remove: each]]! ! !Collection methodsFor: 'testing' stamp: 'ls 3/27/2000 17:25'! identityIncludes: anObject "Answer whether anObject is one of the receiver's elements." self do: [:each | anObject == each ifTrue: [^true]]. ^false! ! !Collection methodsFor: 'testing' stamp: 'sma 5/12/2000 14:07'! includes: anObject "Answer whether anObject is one of the receiver's elements." ^ self anySatisfy: [:each | each = anObject]! ! !Collection methodsFor: 'testing'! includesAllOf: aCollection "Answer whether all the elements of aCollection are in the receiver." aCollection do: [:elem | (self includes: elem) ifFalse: [^ false]]. ^ true! ! !Collection methodsFor: 'testing'! includesAnyOf: aCollection "Answer whether any element of aCollection is one of the receiver's elements." aCollection do: [:elem | (self includes: elem) ifTrue: [^ true]]. ^ false! ! !Collection methodsFor: 'testing' stamp: 'sw 8/12/97 20:59'! includesSubstringAnywhere: testString "Answer whether the receiver includes, anywhere in its nested structure, a string that has testString as a substring" self do: [:element | (element isKindOf: String) ifTrue: [(element includesSubString: testString) ifTrue: [^ true]]. (element isKindOf: Collection) ifTrue: [(element includesSubstringAnywhere: testString) ifTrue: [^ true]]]. ^ false "#(first (second third) ((allSentMessages ('Elvis' includes:)))) includesSubstringAnywhere: 'lvi'"! ! !Collection methodsFor: 'testing' stamp: 'ar 8/17/1999 19:43'! isCollection "Return true if the receiver is some sort of Collection and responds to basic collection messages such as #size and #do:" ^true! ! !Collection methodsFor: 'testing'! isEmpty "Answer whether the receiver contains any elements." ^self size = 0! ! !Collection methodsFor: 'testing' stamp: 'bf 3/10/2000 09:29'! isEmptyOrNil "Answer whether the receiver contains any elements, or is nil. Useful in numerous situations where one wishes the same reaction to an empty collection or to nil" ^ self isEmpty! ! !Collection methodsFor: 'testing' stamp: 'di 11/6/1998 09:16'! isSequenceable ^ false! ! !Collection methodsFor: 'testing' stamp: 'sma 5/12/2000 17:49'! notEmpty "Answer whether the receiver contains any elements." ^ self isEmpty not! ! !Collection methodsFor: 'testing'! occurrencesOf: anObject "Answer how many of the receiver's elements are equal to anObject." | tally | tally _ 0. self do: [:each | anObject = each ifTrue: [tally _ tally + 1]]. ^tally! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Collection class instanceVariableNames: ''! !Collection class methodsFor: 'instance creation' stamp: 'apb 10/15/2000 22:05'! ofSize: n "Create a new collection of size n with nil as its elements. This method exists because OrderedCollection new: n creates an empty collection, not one of size n." ^ self new: n! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 19:58'! with: anObject "Answer an instance of me containing anObject." ^ self new add: anObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:01'! with: firstObject with: secondObject "Answer an instance of me containing the two arguments as elements." ^ self new add: firstObject; add: secondObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:03'! with: firstObject with: secondObject with: thirdObject "Answer an instance of me containing the three arguments as elements." ^ self new add: firstObject; add: secondObject; add: thirdObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:06'! with: firstObject with: secondObject with: thirdObject with: fourthObject "Answer an instance of me, containing the four arguments as the elements." ^ self new add: firstObject; add: secondObject; add: thirdObject; add: fourthObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:06'! with: firstObject with: secondObject with: thirdObject with: fourthObject with: fifthObject "Answer an instance of me, containing the five arguments as the elements." ^ self new add: firstObject; add: secondObject; add: thirdObject; add: fourthObject; add: fifthObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:06'! with: firstObject with: secondObject with: thirdObject with: fourthObject with: fifthObject with: sixthObject "Answer an instance of me, containing the six arguments as the elements." ^ self new add: firstObject; add: secondObject; add: thirdObject; add: fourthObject; add: fifthObject; add: sixthObject; yourself! ! !Collection class methodsFor: 'instance creation' stamp: 'sma 5/6/2000 20:07'! withAll: aCollection "Create a new collection containing all the elements from aCollection." ^ (self new: aCollection size) addAll: aCollection; yourself! ! !Collection class methodsFor: 'private' stamp: 'sma 3/3/2000 10:45'! initialize "Set up a Random number generator to be used by atRandom when the user does not feel like creating his own Random generator." RandomForPicking _ Random new! ! !Collection class methodsFor: 'private' stamp: 'sma 5/12/2000 12:31'! randomForPicking ^ RandomForPicking! ! Object subclass: #Color instanceVariableNames: 'rgb cachedDepth cachedBitPattern ' classVariableNames: 'Black Blue BlueShift Brown CachedColormaps ColorChart ColorNames ComponentMask ComponentMax Cyan DarkGray Gray GrayToIndexMap Green GreenShift HalfComponentMask HighLightBitmaps IndexedColors LightBlue LightBrown LightCyan LightGray LightGreen LightMagenta LightOrange LightRed LightYellow Magenta MaskingMap Orange PaleBlue PaleBuff PaleGreen PaleMagenta PaleOrange PalePeach PaleRed PaleTan PaleYellow PureBlue PureCyan PureGreen PureMagenta PureRed PureYellow RandomStream Red RedShift TranslucentPatterns Transparent VeryDarkGray VeryLightGray VeryPaleRed VeryVeryDarkGray VeryVeryLightGray White Yellow ' poolDictionaries: '' category: 'Graphics-Primitives'! !Color commentStamp: '' prior: 0! This class represents abstract color, regardless of the depth of bitmap it will be shown in. At the very last moment a Color is converted to a pixelValue that depends on the depth of the actual Bitmap inside the Form it will be used with. The supported depths (in bits) are 1, 2, 4, 8, 16, and 32. The number of actual colors at these depths are: 2, 4, 16, 256, 32768, and 16 million. (See comment in BitBlt.) To change the depth of the Display and set how many colors you can see, execute: (Display newDepth: 8). (See comment in DisplayMedium) Color is represented as the amount of light in red, green, and blue. White is (1.0, 1.0, 1.0) and black is (0, 0, 0). Pure red is (1.0, 0, 0). These colors are "additive". Think of Color's instance variables as: r amount of red, a Float between 0.0 and 1.0. g amount of green, a Float between 0.0 and 1.0. b amount of blue, a Float between 0.0 and 1.0. (But, in fact, the three are encoded as values from 0 to 1023 and combined in a single integer, rgb. The user does not need to know this.) Many colors are named. You find a color by name by sending a message to class Color, for example (Color lightBlue). Also, (Color red: 0.2 green: 0.6 blue: 1.0) or (Color r: 0.2 g: 0.6 b: 1.0) creates a color. (see below) A color is essentially immutable. Once you set red, green, and blue, you cannot change them. Instead, create a new Color and use it. Applications such as contour maps and bar graphs will want to display one of a set of shades based on a number. Convert the range of this number to an integer from 1 to N. Then call (Color green lightShades: N) to get an Array of colors from white to green. Use the Array messages at:, atPin:, or atWrap: to pull out the correct color from the array. atPin: gives the first (or last) color if the index is out of range. atWrap: wraps around to the other end if the index is out of range. Here are some fun things to run in when your screen has color: Pen new mandala: 30 diameter: Display height-100. Pen new web "Draw with the mouse, opt-click to end" Display fillWhite. Pen new hilberts: 5. Form toothpaste: 30 "Draw with mouse, opt-click to end" You might also want to try the comment in Form>class>examples>tinyText... Messages: mixed: proportion with: aColor Answer this color mixed with the given color additively. The proportion, a number between 0.0 and 1.0, determines what what fraction of the receiver to use in the mix. + add two colors - subtract two colors * multiply the values of r, g, b by a number or an Array of factors. ((Color named: #white) * 0.3) gives a darkish gray. (aColor * #(0 0 0.9)) gives a color with slightly less blue. / divide a color by a factor or an array of three factors. errorForDepth: d How close the nearest color at this depth is to this abstract color. Sum of the squares of the RGB differences, square rooted and normalized to 1.0. Multiply by 100 to get percent. hue Returns the hue of the color. On a wheel from 0 to 360 with pure red at 0 and again at 360. saturation Returns the saturation of the color. 0.0 to 1.0 brightness Returns the brightness of the color. 0.0 to 1.0 name Look to see if this Color has a name. display Show a swatch of this color tracking the cursor. lightShades: thisMany An array of thisMany colors from white to the receiver. darkShades: thisMany An array of thisMany colors from black to the receiver. Array is of length num. mix: color2 shades: thisMany An array of thisMany colors from the receiver to color2. wheel: thisMany An array of thisMany colors around the color wheel starting and ending at the receiver. pixelValueForDepth: d Returns the bits that appear be in a Bitmap of this depth for this color. Represents the nearest available color at this depth. Normal users do not need to know which pixelValue is used for which color. Messages to Class Color. red: r green: g blue: b Return a color with the given r, g, and b components. r: g: b: Same as above, for fast typing. hue: h saturation: s brightness: b Create a color with the given hue, saturation, and brightness. pink blue red ... Many colors have messages that return an instance of Color. canUnderstand: #brown Returns true if #brown is a defined color. names An OrderedCollection of the names of the colors. named: #notAllThatGray put: aColor Add a new color to the list and create an access message and a class variable for it. fromUser Shows the palette of colors available at this display depth. Click anywhere to return the color you clicked on. hotColdShades: thisMany An array of thisMany colors showing temperature from blue to red to white hot. stdColorsForDepth: d An Array of colors available at this depth. For 16 bit and 32 bits, returns a ColorGenerator. It responds to at: with a Color for that index, simulating a very big Array. colorFromPixelValue: value depth: d Returns a Color whose bit pattern (inside a Bitmap) at this depth is the number specified. Normal users do not need to use this. (See also comments in these classes: Form, Bitmap, BitBlt, Pattern, MaskedForm.)! !Color methodsFor: 'access'! alpha "Return the opacity ('alpha') value of opaque so that normal colors can be compared to TransparentColors." ^ 1.0 ! ! !Color methodsFor: 'access'! blue "Return the blue component of this color, a float in the range [0.0..1.0]." ^ self privateBlue asFloat / ComponentMax! ! !Color methodsFor: 'access'! brightness "Return the brightness of this color, a float in the range [0.0..1.0]." ^ ((self privateRed max: self privateGreen) max: self privateBlue) asFloat / ComponentMax! ! !Color methodsFor: 'access'! green "Return the green component of this color, a float in the range [0.0..1.0]." ^ self privateGreen asFloat / ComponentMax! ! !Color methodsFor: 'access'! hue "Return the hue of this color, an angle in the range [0.0..360.0]." | r g b max min span h | r _ self privateRed. g _ self privateGreen. b _ self privateBlue. max _ ((r max: g) max: b). min _ ((r min: g) min: b). span _ (max - min) asFloat. span = 0.0 ifTrue: [ ^ 0.0 ]. r = max ifTrue: [ h _ ((g - b) asFloat / span) * 60.0. ] ifFalse: [ g = max ifTrue: [ h _ 120.0 + (((b - r) asFloat / span) * 60.0). ] ifFalse: [ h _ 240.0 + (((r - g) asFloat / span) * 60.0). ]. ]. h < 0.0 ifTrue: [ h _ 360.0 + h ]. ^ h! ! !Color methodsFor: 'access'! luminance "Return the luminance of this color, a brightness value weighted by the human eye's color sensitivity." ^ ((299 * self privateRed) + (587 * self privateGreen) + (114 * self privateBlue)) / (1000 * ComponentMax) ! ! !Color methodsFor: 'access'! red "Return the red component of this color, a float in the range [0.0..1.0]." ^ self privateRed asFloat / ComponentMax! ! !Color methodsFor: 'access'! saturation "Return the saturation of this color, a value between 0.0 and 1.0." | r g b max min | r _ self privateRed. g _ self privateGreen. b _ self privateBlue. max _ min _ r. g > max ifTrue: [max _ g]. b > max ifTrue: [max _ b]. g < min ifTrue: [min _ g]. b < min ifTrue: [min _ b]. max = 0 ifTrue: [ ^ 0.0 ] ifFalse: [ ^ (max - min) asFloat / max asFloat ]. ! ! !Color methodsFor: 'equality' stamp: 'di 1/6/1999 20:26'! = aColor "Return true if the receiver equals the given color. This method handles TranslucentColors, too." aColor isColor ifFalse: [^ false]. ^ aColor privateRGB = rgb and: [aColor privateAlpha = self privateAlpha] ! ! !Color methodsFor: 'equality' stamp: 'di 9/27/2000 08:07'! diff: theOther "Returns a number between 0.0 and 1.0" ^ ((self privateRed - theOther privateRed) abs + (self privateGreen - theOther privateGreen) abs + (self privateBlue - theOther privateBlue) abs) / 3.0 / ComponentMax! ! !Color methodsFor: 'equality'! hash ^ rgb! ! !Color methodsFor: 'queries' stamp: 'sw 4/25/1998 12:51'! basicType ^ #color! ! !Color methodsFor: 'queries' stamp: 'ar 1/14/1999 15:27'! isBitmapFill ^false! ! !Color methodsFor: 'queries' stamp: 'ar 11/12/1998 19:43'! isBlack "Return true if the receiver represents black" ^rgb = 0! ! !Color methodsFor: 'queries'! isColor ^ true ! ! !Color methodsFor: 'queries' stamp: 'ar 6/18/1999 06:58'! isGradientFill ^false! ! !Color methodsFor: 'queries' stamp: 'ar 11/12/1998 19:44'! isGray "Return true if the receiver represents a shade of gray" ^(self privateRed = self privateGreen) and:[self privateRed = self privateBlue]! ! !Color methodsFor: 'queries' stamp: 'ar 6/18/1999 07:57'! isOrientedFill "Return true if the receiver keeps an orientation (e.g., origin, direction, and normal)" ^false! ! !Color methodsFor: 'queries' stamp: 'ar 11/7/1998 20:20'! isSolidFill ^true! ! !Color methodsFor: 'queries' stamp: 'di 12/30/1998 14:33'! isTranslucent ^ false ! ! !Color methodsFor: 'queries' stamp: 'di 1/3/1999 12:23'! isTranslucentColor "This means: self isTranslucent, but isTransparent not" ^ false! ! !Color methodsFor: 'queries'! isTransparent ^ false ! ! !Color methodsFor: 'transformations' stamp: 'di 11/2/97 14:05'! * aNumber "Answer this color with its RGB multiplied by the given number. " "(Color brown * 2) display" ^ Color basicNew setPrivateRed: (self privateRed * aNumber) asInteger green: (self privateGreen * aNumber) asInteger blue: (self privateBlue * aNumber) asInteger ! ! !Color methodsFor: 'transformations' stamp: 'di 11/2/97 14:05'! + aColor "Answer this color mixed with the given color in an additive color space. " "(Color blue + Color green) display" ^ Color basicNew setPrivateRed: self privateRed + aColor privateRed green: self privateGreen + aColor privateGreen blue: self privateBlue + aColor privateBlue ! ! !Color methodsFor: 'transformations' stamp: 'di 11/2/97 14:05'! - aColor "Answer aColor is subtracted from the given color in an additive color space. " "(Color white - Color red) display" ^ Color basicNew setPrivateRed: self privateRed - aColor privateRed green: self privateGreen - aColor privateGreen blue: self privateBlue - aColor privateBlue ! ! !Color methodsFor: 'transformations' stamp: 'di 11/2/97 14:07'! / aNumber "Answer this color with its RGB divided by the given number. " "(Color red / 2) display" ^ Color basicNew setPrivateRed: (self privateRed / aNumber) asInteger green: (self privateGreen / aNumber) asInteger blue: (self privateBlue / aNumber) asInteger ! ! !Color methodsFor: 'transformations' stamp: 'sma 6/25/2000 15:36'! alpha: alphaValue "Answer a new Color with the given amount of opacity ('alpha')." alphaValue = 1.0 ifFalse: [^ TranslucentColor basicNew setRgb: rgb alpha: alphaValue]! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 11:55'! alphaMixed: proportion with: aColor "Answer this color mixed with the given color. The proportion, a number between 0.0 and 1.0, determines what what fraction of the receiver to use in the mix. For example, 0.9 would yield a color close to the receiver. This method uses RGB interpolation; HSV interpolation can lead to surprises. Mixes the alphas (for transparency) also." | frac1 frac2 | frac1 _ proportion asFloat min: 1.0 max: 0.0. frac2 _ 1.0 - frac1. ^ Color r: self red * frac1 + (aColor red * frac2) g: self green * frac1 + (aColor green * frac2) b: self blue * frac1 + (aColor blue * frac2) alpha: self alpha * frac1 + (aColor alpha * frac2)! ! !Color methodsFor: 'transformations' stamp: 'RAA 6/2/2000 08:47'! atLeastAsLuminentAs: aFloat | revisedColor | revisedColor _ self. [revisedColor luminance < aFloat] whileTrue: [revisedColor _ revisedColor slightlyLighter]. ^revisedColor ! ! !Color methodsFor: 'transformations' stamp: 'di 5/15/1998 21:54'! dansDarker "Return a darker shade of the same color. An attempt to do better than the current darker method." ^ Color h: self hue s: self saturation v: (self brightness - 0.16 max: 0.0)! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! darker "Answer a darker shade of this color." ^ self alphaMixed: 0.8333 with: Color black ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! lighter "Answer a lighter shade of this color." ^ self alphaMixed: 0.8333 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:00'! mixed: proportion with: aColor "Mix with another color and do not preserve transpareny. Only use this for extracting the RGB value and mixing it. All other callers should use instead: aColor alphaMixed: proportion with: anotherColor " | frac1 frac2 | frac1 _ proportion asFloat min: 1.0 max: 0.0. frac2 _ 1.0 - frac1. ^ Color r: self red * frac1 + (aColor red * frac2) g: self green * frac1 + (aColor green * frac2) b: self blue * frac1 + (aColor blue * frac2)! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! muchLighter ^ self alphaMixed: 0.233 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'ar 6/19/1999 00:36'! negated "Return an RGB inverted color" ^Color r: 1.0 - self red g: 1.0 - self green b: 1.0 - self blue! ! !Color methodsFor: 'transformations' stamp: 'di 9/27/2000 08:14'! orColorUnlike: theOther "If this color is a lot like theOther, then return its complement, otherwide, return self" (self diff: theOther) < 0.3 ifTrue: [^ theOther negated] ifFalse: [^ self]! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! slightlyDarker "Answer a slightly darker shade of this color." ^ self alphaMixed: 0.93 with: Color black ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! slightlyLighter "Answer a slightly lighter shade of this color." ^ self alphaMixed: 0.93 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! twiceDarker "Answer a significantly darker shade of this color." ^ self alphaMixed: 0.5 with: Color black ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! twiceLighter "Answer a significantly lighter shade of this color." ^ self alphaMixed: 0.5 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! veryMuchLighter ^ self alphaMixed: 0.1165 with: Color white ! ! !Color methodsFor: 'groups of shades' stamp: 'tk 6/18/96'! darkShades: thisMany "An array of thisMany colors from black to the receiver. Array is of length num. Very useful for displaying color based on a variable in your program. " "Color showColors: (Color red darkShades: 12)" ^ self class black mix: self shades: thisMany ! ! !Color methodsFor: 'groups of shades' stamp: 'tk 6/18/96'! lightShades: thisMany "An array of thisMany colors from white to self. Very useful for displaying color based on a variable in your program. " "Color showColors: (Color red lightShades: 12)" ^ self class white mix: self shades: thisMany ! ! !Color methodsFor: 'groups of shades' stamp: 'tk 6/18/96'! mix: color2 shades: thisMany "Return an array of thisMany colors from self to color2. Very useful for displaying color based on a variable in your program. " "Color showColors: (Color red mix: Color green shades: 12)" | redInc greenInc blueInc rr gg bb c out | thisMany = 1 ifTrue: [^ Array with: color2]. redInc _ color2 red - self red / (thisMany-1). greenInc _ color2 green - self green / (thisMany-1). blueInc _ color2 blue - self blue / (thisMany-1). rr _ self red. gg _ self green. bb _ self blue. out _ (1 to: thisMany) collect: [:num | c _ Color r: rr g: gg b: bb. rr _ rr + redInc. gg _ gg + greenInc. bb _ bb + blueInc. c]. out at: out size put: color2. "hide roundoff errors" ^ out ! ! !Color methodsFor: 'groups of shades' stamp: 'di 10/23/2000 09:45'! wheel: thisMany "An array of thisMany colors around the color wheel starting at self and ending all the way around the hue space just before self. Array is of length thisMany. Very useful for displaying color based on a variable in your program. " | sat bri hue step c | sat _ self saturation. bri _ self brightness. hue _ self hue. step _ 360.0 / (thisMany max: 1). ^ (1 to: thisMany) collect: [:num | c _ Color h: hue s: sat v: bri. "hue is taken mod 360" hue _ hue + step. c]. " (Color wheel: 8) withIndexDo: [:c :i | Display fill: (i*10@20 extent: 10@20) fillColor: c] "! ! !Color methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:14'! byteEncode: aStream aStream print: '('; print: self class name; print: ' r: '; write: (self red roundTo: 0.001); print: ' g: '; write: (self green roundTo: 0.001); print: ' b: '; write: (self blue roundTo: 0.001) ; print: ')'. ! ! !Color methodsFor: 'printing' stamp: 'RAA 7/31/2000 17:25'! encodeForRemoteCanvas | encoded | CanvasEncoder at: 4 count: 1. (encoded := String new: 12) putInteger32: (rgb bitAnd: 16rFFFF) at: 1; putInteger32: (rgb >> 16) at: 5; putInteger32: self privateAlpha at: 9. ^encoded! ! !Color methodsFor: 'printing'! encodePostscriptOn: aStream aStream setrgbcolor:self. ! ! !Color methodsFor: 'printing' stamp: 'bf 5/25/2000 16:52'! printOn: aStream | name | (name _ self name) ifNotNil: [^ aStream nextPutAll: 'Color '; nextPutAll: name]. self storeOn: aStream. ! ! !Color methodsFor: 'printing'! shortPrintString "Return a short (but less precise) print string for use where space is tight." | s | s _ WriteStream on: ''. s nextPutAll: '(' , self class name; nextPutAll: ' r: '; nextPutAll: (self red roundTo: 0.01) printString; nextPutAll: ' g: '; nextPutAll: (self green roundTo: 0.01) printString; nextPutAll: ' b: '; nextPutAll: (self blue roundTo: 0.01) printString; nextPutAll: ')'. ^ s contents ! ! !Color methodsFor: 'printing' stamp: 'mir 7/21/1999 11:41'! storeArrayOn: aStream aStream nextPutAll: '#('. self storeArrayValuesOn: aStream. aStream nextPutAll: ') ' ! ! !Color methodsFor: 'printing' stamp: 'mir 7/21/1999 11:41'! storeArrayValuesOn: aStream (self red roundTo: 0.001) storeOn: aStream. aStream space. (self green roundTo: 0.001) storeOn: aStream. aStream space. (self blue roundTo: 0.001) storeOn: aStream. ! ! !Color methodsFor: 'printing' stamp: 'di 9/27/2000 13:34'! storeOn: aStream aStream nextPutAll: '(' , self class name; nextPutAll: ' r: '; print: (self red roundTo: 0.001); nextPutAll: ' g: '; print: (self green roundTo: 0.001); nextPutAll: ' b: '; print: (self blue roundTo: 0.001); nextPutAll: ')'. ! ! !Color methodsFor: 'other' stamp: 'sw 2/16/98 03:42'! colorForInsets ^ self! ! !Color methodsFor: 'other' stamp: 'tk 6/14/96'! display "Show a swatch of this color tracking the cursor until the next mouseClick. " "Color red display" | f | f _ Form extent: 40@20 depth: Display depth. f fillColor: self. Cursor blank showWhile: [f follow: [Sensor cursorPoint] while: [Sensor noButtonPressed]]! ! !Color methodsFor: 'other' stamp: 'jm 12/4/97 10:24'! name "Return this color's name, or nil if it has no name. Only returns a name if it exactly matches the named color." ColorNames do: [:name | (Color perform: name) = self ifTrue: [^ name]]. ^ nil ! ! !Color methodsFor: 'other' stamp: 'sw 6/10/1998 17:50'! newTileMorphRepresentative ^ ColorTileMorph new colorSwatchColor: self! ! !Color methodsFor: 'other' stamp: 'jm 12/4/97 10:27'! rgbTriplet "Color fromUser rgbTriplet" ^ Array with: (self red roundTo: 0.01) with: (self green roundTo: 0.01) with: (self blue roundTo: 0.01) ! ! !Color methodsFor: 'conversions' stamp: 'ar 2/5/1999 19:30'! asB3DColor "Convert the receiver into a color" ^B3DColor4 new loadFrom: self! ! !Color methodsFor: 'conversions' stamp: 'ar 11/2/1998 12:19'! asColor "Convert the receiver into a color" ^self! ! !Color methodsFor: 'conversions' stamp: 'TBn 6/15/2000 20:37'! asColorref "Convert the receiver into a colorref" ^(self red * 255) asInteger + ((self green * 255) asInteger << 8) + ((self green * 255) asInteger << 16)! ! !Color methodsFor: 'conversions' stamp: 'sw 10/27/1999 10:51'! asNontranslucentColor ^ self! ! !Color methodsFor: 'conversions' stamp: 'di 3/25/2000 10:13'! balancedPatternForDepth: depth "A generalization of bitPatternForDepth: as it exists. Generates a 2x2 stipple of color. The topLeft and bottomRight pixel are closest approx to this color" | pv1 pv2 mask1 mask2 pv3 c | (depth == cachedDepth and:[cachedBitPattern size = 2]) ifTrue: [^ cachedBitPattern]. (depth between: 4 and: 16) ifFalse: [^ self bitPatternForDepth: depth]. cachedDepth _ depth. pv1 _ self pixelValueForDepth: depth. " Subtract error due to pv1 to get pv2. pv2 _ (self - (err1 _ (Color colorFromPixelValue: pv1 depth: depth) - self)) pixelValueForDepth: depth. Subtract error due to 2 pv1's and pv2 to get pv3. pv3 _ (self - err1 - err1 - ((Color colorFromPixelValue: pv2 depth: depth) - self)) pixelValueForDepth: depth. " "Above two statements computed faster by the following..." pv2 _ (c _ self - ((Color colorFromPixelValue: pv1 depth: depth) - self)) pixelValueForDepth: depth. pv3 _ (c + (c - (Color colorFromPixelValue: pv2 depth: depth))) pixelValueForDepth: depth. "Return to a 2-word bitmap that encodes a 2x2 stipple of the given pixelValues." mask1 _ (#(- - - 16r01010101 - - - "replicates every other 4 bits" 16r00010001 - - - - - - - "replicates every other 8 bits" 16r00000001) at: depth). "replicates every other 16 bits" mask2 _ (#(- - - 16r10101010 - - - "replicates the other 4 bits" 16r01000100 - - - - - - - "replicates the other 8 bits" 16r00010000) at: depth). "replicates the other 16 bits" ^ cachedBitPattern _ Bitmap with: (mask1*pv1) + (mask2*pv2) with: (mask1*pv3) + (mask2*pv1)! ! !Color methodsFor: 'conversions' stamp: 'hmm 4/25/2000 09:40'! bitPatternForDepth: depth "Return a Bitmap, possibly containing a stipple pattern, that best represents this color at the given depth. BitBlt calls this method to convert colors into Bitmaps. The resulting Bitmap may be multiple words to represent a stipple pattern of several lines. " "See also: pixelValueAtDepth: -- value for single pixel pixelWordAtDepth: -- a 32-bit word filled with the pixel value" "Details: The pattern for the most recently requested depth is cached." "Note for depths > 2, there are stippled and non-stippled versions (generated with #balancedPatternForDepth: and #bitPatternForDepth:, respectively). The stippled versions don't work with the window bit caching of StandardSystemView, so we make sure that for these depths, only unstippled patterns are returned" (depth == cachedDepth and: [depth <= 2 or: [cachedBitPattern size = 1]]) ifTrue: [^ cachedBitPattern]. cachedDepth _ depth. depth > 2 ifTrue: [^ cachedBitPattern _ Bitmap with: (self pixelWordForDepth: depth)]. depth = 1 ifTrue: [^ cachedBitPattern _ self halfTonePattern1]. depth = 2 ifTrue: [^ cachedBitPattern _ self halfTonePattern2]. ! ! !Color methodsFor: 'conversions'! closestPixelValue1 "Return the nearest approximation to this color for a monochrome Form." "fast special cases" rgb = 0 ifTrue: [^ 1]. "black" rgb = 16r3FFFFFFF ifTrue: [^ 0]. "white" self luminance > 0.5 ifTrue: [^ 0] "white" ifFalse: [^ 1]. "black" ! ! !Color methodsFor: 'conversions'! closestPixelValue2 "Return the nearest approximation to this color for a 2-bit deep Form." | lum | "fast special cases" rgb = 0 ifTrue: [^ 1]. "black" rgb = 16r3FFFFFFF ifTrue: [^ 2]. "opaque white" lum _ self luminance. lum < 0.2 ifTrue: [^ 1]. "black" lum > 0.6 ifTrue: [^ 2]. "opaque white" ^ 3 "50% gray" ! ! !Color methodsFor: 'conversions'! closestPixelValue4 "Return the nearest approximation to this color for a 4-bit deep Form." | bIndex | "fast special cases" rgb = 0 ifTrue: [^ 1]. "black" rgb = 16r3FFFFFFF ifTrue: [^ 2]. "opaque white" rgb = PureRed privateRGB ifTrue: [^ 4]. rgb = PureGreen privateRGB ifTrue: [^ 5]. rgb = PureBlue privateRGB ifTrue: [^ 6]. rgb = PureCyan privateRGB ifTrue: [^ 7]. rgb = PureYellow privateRGB ifTrue: [^ 8]. rgb = PureMagenta privateRGB ifTrue: [^ 9]. bIndex _ (self luminance * 8.0) rounded. "bIndex in [0..8]" ^ #( 1 "black" 10 "1/8 gray" 11 "2/8 gray" 12 "3/8 gray" 3 "4/8 gray" 13 "5/8 gray" 14 "6/8 gray" 15 "7/8 gray" 2 "opaque white" ) at: bIndex + 1. ! ! !Color methodsFor: 'conversions'! closestPixelValue8 "Return the nearest approximation to this color for an 8-bit deep Form." "fast special cases" rgb = 0 ifTrue: [^ 1]. "black" rgb = 16r3FFFFFFF ifTrue: [^ 255]. "white" self saturation < 0.2 ifTrue: [ ^ GrayToIndexMap at: (self privateGreen >> 2) + 1. "nearest gray" ] ifFalse: [ "compute nearest entry in the color cube" ^ 40 + ((((self privateRed * 5) + HalfComponentMask) // ComponentMask) * 36) + ((((self privateBlue * 5) + HalfComponentMask) // ComponentMask) * 6) + (((self privateGreen * 5) + HalfComponentMask) // ComponentMask)]. ! ! !Color methodsFor: 'conversions' stamp: 'di 9/2/97 20:21'! dominantColor ^ self! ! !Color methodsFor: 'conversions' stamp: 'di 6/23/97 23:27'! halfTonePattern1 "Return a halftone-pattern to approximate luminance levels on 1-bit deep Forms." | lum | lum _ self luminance. lum < 0.1 ifTrue: [^ Bitmap with: 16rFFFFFFFF]. "black" lum < 0.4 ifTrue: [^ Bitmap with: 16rBBBBBBBB with: 16rEEEEEEEE]. "dark gray" lum < 0.6 ifTrue: [^ Bitmap with: 16r55555555 with: 16rAAAAAAAA]. "medium gray" lum < 0.9 ifTrue: [^ Bitmap with: 16r44444444 with: 16r11111111]. "light gray" ^ Bitmap with: 0 "1-bit white" ! ! !Color methodsFor: 'conversions'! halfTonePattern2 "Return a halftone-pattern to approximate luminance levels on 2-bit deep Forms." | lum | lum _ self luminance. lum < 0.125 ifTrue: [^ Bitmap with: 16r55555555]. "black" lum < 0.25 ifTrue: [^ Bitmap with: 16r55555555 with: 16rDDDDDDDD]. "1/8 gray" lum < 0.375 ifTrue: [^ Bitmap with: 16rDDDDDDDD with: 16r77777777]. "2/8 gray" lum < 0.5 ifTrue: [^ Bitmap with: 16rFFFFFFFF with: 16r77777777]. "3/8 gray" lum < 0.625 ifTrue: [^ Bitmap with: 16rFFFFFFFF]. "4/8 gray" lum < 0.75 ifTrue: [^ Bitmap with: 16rFFFFFFFF with: 16rBBBBBBBB]. "5/8 gray" lum < 0.875 ifTrue: [^ Bitmap with: 16rEEEEEEEE with: 16rBBBBBBBB]. "6/8 gray" lum < 1.0 ifTrue: [^ Bitmap with: 16rAAAAAAAA with: 16rBBBBBBBB]. "7/8 gray" ^ Bitmap with: 16rAAAAAAAA "opaque white" "handy expression for computing patterns for 2x2 tiles; set p to a string of 4 letters (e.g., 'wggw' for a gray-and- white checkerboard) and print the result of evaluating: | p d w1 w2 | p _ 'wggw'. d _ Dictionary new. d at: $b put: '01'. d at: $w put: '10'. d at: $g put: '11'. w1 _ (d at: (p at: 1)), (d at: (p at: 2)). w1 _ '2r', w1, w1, w1, w1, w1, w1, w1, w1, ' hex'. w2 _ (d at: (p at: 3)), (d at: (p at: 4)). w2 _ '2r', w2, w2, w2, w2, w2, w2, w2, w2, ' hex'. Array with: (Compiler evaluate: w1) with: (Compiler evaluate: w2) "! ! !Color methodsFor: 'conversions' stamp: 'tk 4/24/97'! indexInMap: aColorMap "Return the index corresponding to this color in the given color map. RGB colors are truncated to 3-, 4-, or 5-bits per color component when indexing into such a colorMap. " aColorMap size = 2 ifTrue: [^ (self pixelValueForDepth: 1) + 1]. aColorMap size = 4 ifTrue: [^ (self pixelValueForDepth: 2) + 1]. aColorMap size = 16 ifTrue: [^ (self pixelValueForDepth: 4) + 1]. aColorMap size = 256 ifTrue: [^ (self pixelValueForDepth: 8) + 1]. aColorMap size = 512 ifTrue: [^ (self pixelValueForDepth: 9) + 1]. aColorMap size = 4096 ifTrue: [^ (self pixelValueForDepth: 12) + 1]. aColorMap size = 32768 ifTrue: [^ (self pixelValueForDepth: 16) + 1]. self error: 'unknown pixel depth'. ! ! !Color methodsFor: 'conversions' stamp: 'bf 10/13/1999 14:22'! makeForegroundColor "Make a foreground color contrasting with me" ^self luminance >= "Color red luminance" 0.299 ifTrue: [Color black] ifFalse: [Color white]! ! !Color methodsFor: 'conversions' stamp: 'jm 6/2/1998 14:56'! pixelValueForDepth: d "Returns an integer representing the bits that appear in a single pixel of this color in a Form of the given depth. The depth must be one of 1, 2, 4, 8, 16, or 32. Contrast with pixelWordForDepth: and bitPatternForDepth:, which return either a 32-bit word packed with the given pixel value or a multiple-word Bitmap containing a pattern. The inverse is the class message colorFromPixelValue:depth:" "Details: For depths of 8 or less, the result is a colorMap index. For depths of 16 and 32, it is a direct color value with 5 or 8 bits per color component." "Transparency: The pixel value zero is reserved for transparent. For depths greater than 8, black maps to the darkest possible blue." | rgbBlack val rgbBlack32 | d = 8 ifTrue: [^ self closestPixelValue8]. "common case" d < 8 ifTrue: [ d = 4 ifTrue: [^ self closestPixelValue4]. d = 2 ifTrue: [^ self closestPixelValue2]. d = 1 ifTrue: [^ self closestPixelValue1]]. rgbBlack _ 1. "closest black that is not transparent in RGB" d = 16 ifTrue: [ "five bits per component; top bits ignored" val _ (((rgb bitShift: -15) bitAnd: 16r7C00) bitOr: ((rgb bitShift: -10) bitAnd: 16r03E0)) bitOr: ((rgb bitShift: -5) bitAnd: 16r001F). ^ val = 0 ifTrue: [rgbBlack] ifFalse: [val]]. d = 32 ifTrue: [ rgbBlack32 _ 16rFF000001. "closest black for 32-bit depth, with opaque alpha" "eight bits per component; top 8 bits set to all ones (opaque)" val _ (((rgb bitShift: -6) bitAnd: 16rFF0000) bitOr: ((rgb bitShift: -4) bitAnd: 16r00FF00)) bitOr: ((rgb bitShift: -2) bitAnd: 16r0000FF). ^ val = 0 ifTrue: [rgbBlack32] ifFalse: [16rFF000000 + val]]. d = 12 ifTrue: [ "for indexing a color map with 4 bits per color component" val _ (((rgb bitShift: -18) bitAnd: 16r0F00) bitOr: ((rgb bitShift: -12) bitAnd: 16r00F0)) bitOr: ((rgb bitShift: -6) bitAnd: 16r000F). ^ val = 0 ifTrue: [rgbBlack] ifFalse: [val]]. d = 9 ifTrue: [ "for indexing a color map with 3 bits per color component" val _ (((rgb bitShift: -21) bitAnd: 16r01C0) bitOr: ((rgb bitShift: -14) bitAnd: 16r0038)) bitOr: ((rgb bitShift: -7) bitAnd: 16r0007). ^ val = 0 ifTrue: [rgbBlack] ifFalse: [val]]. self error: 'unknown pixel depth: ', d printString ! ! !Color methodsFor: 'conversions' stamp: 'di 11/30/1998 09:03'! pixelWordFor: depth filledWith: pixelValue "Return to a 32-bit word that concatenates enough copies of the given pixel value to fill the word (i.e., 32/depth copies). Depth should be one of 1, 2, 4, 8, 16, or 32. The pixel value should be an integer in 0..2^depth-1." | halfword | depth = 32 ifTrue: [^ pixelValue]. depth = 16 ifTrue: [halfword _ pixelValue] ifFalse: [halfword _ pixelValue * (#(16rFFFF "replicates at every bit" 16r5555 - "replicates every 2 bits" 16r1111 - - - "replicates every 4 bits" 16r0101) at: depth) "replicates every 8 bits"]. ^ halfword bitOr: (halfword bitShift: 16)! ! !Color methodsFor: 'conversions'! pixelWordForDepth: depth "Return to a 32-bit word that concatenates enough copies of the receiver's pixel value to fill the word (i.e., 32/depth copies). Depth should be one of 1, 2, 4, 8, 16, or 32. The pixel value should be an integer in 0..2^depth-1." | pixelValue | pixelValue _ self pixelValueForDepth: depth. ^ self pixelWordFor: depth filledWith: pixelValue ! ! !Color methodsFor: 'conversions' stamp: 'ar 1/14/1999 15:28'! scaledPixelValue32 "Return the alpha scaled pixel value for depth 32" ^self pixelWordForDepth: 32! ! !Color methodsFor: 'private'! attemptToMutateError "A color is immutable. Once a color's red, green, and blue have been initialized, you cannot change them. Instead, create a new Color and use it." self error: 'Color objects are immutable once created' ! ! !Color methodsFor: 'private'! flushCache "Flush my cached bit pattern." cachedDepth _ nil. cachedBitPattern _ nil. ! ! !Color methodsFor: 'private'! privateAlpha "Private!! Return the raw alpha value for opaque. Used only for equality testing." ^ 255! ! !Color methodsFor: 'private'! privateBlue "Private!! Return the internal representation of my blue component." ^ rgb bitAnd: ComponentMask! ! !Color methodsFor: 'private'! privateGreen "Private!! Return the internal representation of my green component. Replaced >> by bitShift: 0 -. SqR!! 2/25/1999 23:08" ^ (rgb bitShift: 0 - GreenShift) bitAnd: ComponentMask! ! !Color methodsFor: 'private'! privateRGB "Private!! Return the internal representation of my RGB components." ^ rgb ! ! !Color methodsFor: 'private'! privateRed "Private!! Return the internal representation of my red component." ^ (rgb bitShift: 0 - RedShift) bitAnd: ComponentMask! ! !Color methodsFor: 'private'! setHue: hue saturation: saturation brightness: brightness "Initialize this color to the given hue, saturation, and brightness. See the comment in the instance creation method for details." | s v hf i f p q t | s _ (saturation asFloat max: 0.0) min: 1.0. v _ (brightness asFloat max: 0.0) min: 1.0. "zero saturation yields gray with the given brightness" s = 0.0 ifTrue: [ ^ self setRed: v green: v blue: v ]. hf _ hue asFloat. (hf < 0.0 or: [hf >= 360.0]) ifTrue: [hf _ hf - ((hf quo: 360.0) asFloat * 360.0)]. hf _ hf / 60.0. i _ hf asInteger. "integer part of hue" f _ hf fractionPart. "fractional part of hue" p _ (1.0 - s) * v. q _ (1.0 - (s * f)) * v. t _ (1.0 - (s * (1.0 - f))) * v. 0 = i ifTrue: [ ^ self setRed: v green: t blue: p ]. 1 = i ifTrue: [ ^ self setRed: q green: v blue: p ]. 2 = i ifTrue: [ ^ self setRed: p green: v blue: t ]. 3 = i ifTrue: [ ^ self setRed: p green: q blue: v ]. 4 = i ifTrue: [ ^ self setRed: t green: p blue: v ]. 5 = i ifTrue: [ ^ self setRed: v green: p blue: q ]. self error: 'implementation error'. ! ! !Color methodsFor: 'private' stamp: 'di 11/2/97 12:19'! setPrivateRed: r green: g blue: b "Initialize this color's r, g, and b components to the given values in the range [0..ComponentMax]. Encoded in a single variable as 3 integers in [0..1023]." rgb == nil ifFalse: [self attemptToMutateError]. rgb _ ((r min: ComponentMask max: 0) bitShift: RedShift) + ((g min: ComponentMask max: 0) bitShift: GreenShift) + (b min: ComponentMask max: 0). cachedDepth _ nil. cachedBitPattern _ nil. ! ! !Color methodsFor: 'private' stamp: 'ls 9/24/1999 20:04'! setRGB: rgb0 rgb == nil ifFalse: [self attemptToMutateError]. rgb _ rgb0! ! !Color methodsFor: 'private'! setRed: r green: g blue: b "Initialize this color's r, g, and b components to the given values in the range [0.0..1.0]. Encoded in a single variable as 3 integers in [0..1023]." rgb == nil ifFalse: [self attemptToMutateError]. rgb _ (((r * ComponentMax) rounded bitAnd: ComponentMask) bitShift: RedShift) + (((g * ComponentMax) rounded bitAnd: ComponentMask) bitShift: GreenShift) + ((b * ComponentMax) rounded bitAnd: ComponentMask). cachedDepth _ nil. cachedBitPattern _ nil. ! ! !Color methodsFor: 'private'! setRed: r green: g blue: b range: range "Initialize this color's r, g, and b components to the given values in the range [0..r]." rgb == nil ifFalse: [self attemptToMutateError]. rgb _ ((((r * ComponentMask) // range) bitAnd: ComponentMask) bitShift: RedShift) + ((((g * ComponentMask) // range) bitAnd: ComponentMask) bitShift: GreenShift) + (((b * ComponentMask) // range) bitAnd: ComponentMask). cachedDepth _ nil. cachedBitPattern _ nil. ! ! !Color methodsFor: 'copying' stamp: 'tk 8/19/1998 16:12'! veryDeepCopyWith: deepCopier "Return self. I am immutable in the Morphic world. Do not record me."! ! !Color methodsFor: 'Morphic menu' stamp: 'ar 6/18/1999 08:35'! addFillStyleMenuItems: aMenu hand: aHand from: aMorph "Add the items for changing the current fill style of the receiver" aMenu add: 'change color...' target: self selector: #changeColorIn:event: argument: aMorph! ! !Color methodsFor: 'Morphic menu' stamp: 'ar 10/5/2000 18:50'! changeColorIn: aMorph event: evt "Note: This is just a workaround to make sure we don't use the old color inst var" aMorph changeColorTarget: aMorph selector: #fillStyle: originalColor: self hand: evt hand! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Color class instanceVariableNames: ''! !Color class methodsFor: 'instance creation' stamp: 'sw 11/9/1998 19:25'! colorFrom: parm "Return an instantiated color from parm. If parm is already a color, return it, else return the result of my performing it if it's a symbol, else just return the thing" (parm isKindOf: Color) ifTrue: [^ parm]. (parm isKindOf: Symbol) ifTrue: [^ self perform: parm]. ^ parm! ! !Color class methodsFor: 'instance creation' stamp: 'RAA 9/22/2000 15:19'! colorFromPixelValue: p depth: d "Convert a pixel value for the given display depth into a color." "Details: For depths of 8 or less, the pixel value is simply looked up in a table. For greater depths, the color components are extracted and converted into a color." | r g b alpha | d = 8 ifTrue: [^ IndexedColors at: (p bitAnd: 16rFF) + 1]. d = 4 ifTrue: [^ IndexedColors at: (p bitAnd: 16r0F) + 1]. d = 2 ifTrue: [^ IndexedColors at: (p bitAnd: 16r03) + 1]. d = 1 ifTrue: [^ IndexedColors at: (p bitAnd: 16r01) + 1]. (d = 16) | (d = 15) ifTrue: [ "five bits per component" r _ (p bitShift: -10) bitAnd: 16r1F. g _ (p bitShift: -5) bitAnd: 16r1F. b _ p bitAnd: 16r1F. (r = 0 and: [g = 0 and: [b = 0]]) ifTrue: [^Color transparent]. ^ Color r: r g: g b: b range: 31]. d = 32 ifTrue: [ "eight bits per component; 8 bits of alpha" r _ (p bitShift: -16) bitAnd: 16rFF. g _ (p bitShift: -8) bitAnd: 16rFF. b _ p bitAnd: 16rFF. alpha _ p bitShift: -24. alpha = 0 ifTrue: [^Color transparent]. (r = 0 and: [g = 0 and: [b = 0]]) ifTrue: [^Color transparent]. alpha < 255 ifTrue: [^ (Color r: r g: g b: b range: 255) alpha: (alpha asFloat / 255.0)] ifFalse: [^ (Color r: r g: g b: b range: 255)]]. d = 12 ifTrue: [ "four bits per component" r _ (p bitShift: -8) bitAnd: 16rF. g _ (p bitShift: -4) bitAnd: 16rF. b _ p bitAnd: 16rF. ^ Color r: r g: g b: b range: 15]. d = 9 ifTrue: [ "three bits per component" r _ (p bitShift: -6) bitAnd: 16r7. g _ (p bitShift: -3) bitAnd: 16r7. b _ p bitAnd: 16r7. ^ Color r: r g: g b: b range: 7]. self error: 'unknown pixel depth: ', d printString ! ! !Color class methodsFor: 'instance creation' stamp: 'mir 7/21/1999 11:54'! fromArray: colorDef colorDef size == 3 ifTrue: [^self r: (colorDef at: 1) g: (colorDef at: 2) b: (colorDef at: 3)]. colorDef size == 0 ifTrue: [^Color transparent]. colorDef size == 4 ifTrue: [^(TranslucentColor r: (colorDef at: 1) g: (colorDef at: 2) b: (colorDef at: 3)) alpha: (colorDef at: 4)]. self error: 'Undefined color definition'! ! !Color class methodsFor: 'instance creation' stamp: 'sw 8/8/97 22:03'! fromRgbTriplet: list ^ self r: list first g: list second b: list last! ! !Color class methodsFor: 'instance creation' stamp: 'dvf 6/16/2000 17:48'! fromString: aString "for HTML color spec: #FFCCAA or white/black" "Color fromString: '#FFCCAA'. Color fromString: 'white'. Color fromString: 'orange'" | aColorHex red green blue | aString isEmptyOrNil ifTrue: [^ Color white]. aString first = $# ifTrue: [aColorHex _ aString copyFrom: 2 to: aString size] ifFalse: [aColorHex _ aString]. [aColorHex size = 6 ifTrue: [aColorHex _ aColorHex asUppercase. red _ ('16r', (aColorHex copyFrom: 1 to: 2)) asNumber/255. green _ ('16r', (aColorHex copyFrom: 3 to: 4)) asNumber/255. blue _ ('16r', (aColorHex copyFrom: 5 to: 6)) asNumber/255. ^ self r: red g: green b: blue]] ifError: [:err :rcvr | "not a hex color triplet" ]. "try to match aColorHex with known named colors" aColorHex _ aColorHex asLowercase. ^self perform: (ColorNames detect: [:i | i asString asLowercase = aColorHex] ifNone: [#white])! ! !Color class methodsFor: 'instance creation' stamp: 'jm 12/4/97 13:05'! gray: brightness "Return a gray shade with the given brightness in the range [0.0..1.0]." ^ self basicNew setRed: brightness green: brightness blue: brightness ! ! !Color class methodsFor: 'instance creation'! h: hue s: saturation v: brightness "Create a color with the given hue, saturation, and brightness. Hue is given as the angle in degrees of the color on the color circle where red is zero degrees. Saturation and brightness are numbers in [0.0..1.0] where larger values are more saturated or brighter colors. For example, (Color h: 0 s: 1 v: 1) is pure red." "Note: By convention, brightness is abbreviated 'v' to to avoid confusion with blue." ^ self basicNew setHue: hue saturation: saturation brightness: brightness! ! !Color class methodsFor: 'instance creation'! new ^ self r: 0.0 g: 0.0 b: 0.0! ! !Color class methodsFor: 'instance creation' stamp: 'jm 12/4/97 13:04'! r: r g: g b: b "Return a color with the given r, g, and b components in the range [0.0..1.0]." ^ self basicNew setRed: r green: g blue: b ! ! !Color class methodsFor: 'instance creation'! r: r g: g b: b alpha: alpha ^ (self r: r g: g b: b) alpha: alpha! ! !Color class methodsFor: 'instance creation'! r: r g: g b: b range: range "Return a color with the given r, g, and b components specified as integers in the range [0..r]. This avoids the floating point arithmetic in the red:green:blue: message and is thus a bit faster for certain applications (such as computing a sequence of colors for a palette)." ^ self basicNew setRed: r green: g blue: b range: range! ! !Color class methodsFor: 'instance creation'! random "Return a random color that isn't too dark or under-saturated." ^ self basicNew setHue: (360.0 * RandomStream next) saturation: (0.3 + (RandomStream next * 0.7)) brightness: (0.4 + (RandomStream next * 0.6))! ! !Color class methodsFor: 'class initialization'! initialize "Color initialize" "Details: Externally, the red, green, and blue components of color are floats in the range [0.0..1.0]. Internally, they are represented as integers in the range [0..ComponentMask] packing into a small integer to save space and to allow fast hashing and equality testing. For a general description of color representations for computer graphics, including the relationship between the RGB and HSV color models used here, see Chapter 17 of Foley and van Dam, Fundamentals of Interactive Computer Graphics, Addison-Wesley, 1982." ComponentMask _ 1023. HalfComponentMask _ 512. "used to round up in integer calculations" ComponentMax _ 1023.0. "a Float used to normalize components" RedShift _ 20. GreenShift _ 10. BlueShift _ 0. PureRed _ self r: 1 g: 0 b: 0. PureGreen _ self r: 0 g: 1 b: 0. PureBlue _ self r: 0 g: 0 b: 1. PureYellow _ self r: 1 g: 1 b: 0. PureCyan _ self r: 0 g: 1 b: 1. PureMagenta _ self r: 1 g: 0 b: 1. RandomStream _ Random new. self initializeIndexedColors. self initializeGrayToIndexMap. self initializeNames. self initializeHighLights. ! ! !Color class methodsFor: 'class initialization'! initializeGrayToIndexMap "Build an array of gray values available in the 8-bit colormap. This array is indexed by a gray level between black (1) and white (256) and returns the pixel value for the corresponding gray level." "Note: This method must be called after initializeIndexedColors, since it uses IndexedColors." "Color initializeGrayToIndexMap" | grayLevels grayIndices c distToClosest dist indexOfClosest | "record the level and index of each gray in the 8-bit color table" grayLevels _ OrderedCollection new. grayIndices _ OrderedCollection new. "Note: skip the first entry, which is reserved for transparent" 2 to: IndexedColors size do: [:i | c _ IndexedColors at: i. c saturation = 0.0 ifTrue: [ "c is a gray" grayLevels add: (c privateBlue) >> 2. "top 8 bits; R, G, and B are the same" grayIndices add: i - 1]]. "pixel values are zero-based" grayLevels _ grayLevels asArray. grayIndices _ grayIndices asArray. "for each gray level in [0..255], select the closest match" GrayToIndexMap _ ByteArray new: 256. 0 to: 255 do: [:level | distToClosest _ 10000. "greater than distance to any real gray" 1 to: grayLevels size do: [:i | dist _ (level - (grayLevels at: i)) abs. dist < distToClosest ifTrue: [ distToClosest _ dist. indexOfClosest _ grayIndices at: i]]. GrayToIndexMap at: (level + 1) put: indexOfClosest]. ! ! !Color class methodsFor: 'class initialization' stamp: 'tk 6/22/96'! initializeHighLights "Create a set of Bitmaps for quickly reversing areas of the screen without converting colors. " "Color initializeHighLights" | t | t _ Array new: 32. t at: 1 put: (Bitmap with: 16rFFFFFFFF). t at: 2 put: (Bitmap with: 16rFFFFFFFF). t at: 4 put: (Bitmap with: 16r55555555). t at: 8 put: (Bitmap with: 16r7070707). t at: 16 put: (Bitmap with: 16rFFFFFFFF). t at: 32 put: (Bitmap with: 16rFFFFFFFF). HighLightBitmaps _ t. ! ! !Color class methodsFor: 'class initialization'! initializeIndexedColors "Build an array of colors corresponding to the fixed colormap used for display depths of 1, 2, 4, or 8 bits." "Color initializeIndexedColors" | a index grayVal | a _ Array new: 256. "1-bit colors (monochrome)" a at: 1 put: (Color r: 1.0 g: 1.0 b: 1.0). "white or transparent" a at: 2 put: (Color r: 0.0 g: 0.0 b: 0.0). "black" "additional colors for 2-bit color" a at: 3 put: (Color r: 1.0 g: 1.0 b: 1.0). "opaque white" a at: 4 put: (Color r: 0.5 g: 0.5 b: 0.5). "1/2 gray" "additional colors for 4-bit color" a at: 5 put: (Color r: 1.0 g: 0.0 b: 0.0). "red" a at: 6 put: (Color r: 0.0 g: 1.0 b: 0.0). "green" a at: 7 put: (Color r: 0.0 g: 0.0 b: 1.0). "blue" a at: 8 put: (Color r: 0.0 g: 1.0 b: 1.0). "cyan" a at: 9 put: (Color r: 1.0 g: 1.0 b: 0.0). "yellow" a at: 10 put: (Color r: 1.0 g: 0.0 b: 1.0). "magenta" a at: 11 put: (Color r: 0.125 g: 0.125 b: 0.125). "1/8 gray" a at: 12 put: (Color r: 0.25 g: 0.25 b: 0.25). "2/8 gray" a at: 13 put: (Color r: 0.375 g: 0.375 b: 0.375). "3/8 gray" a at: 14 put: (Color r: 0.625 g: 0.625 b: 0.625). "5/8 gray" a at: 15 put: (Color r: 0.75 g: 0.75 b: 0.75). "6/8 gray" a at: 16 put: (Color r: 0.875 g: 0.875 b: 0.875). "7/8 gray" "additional colors for 8-bit color" "24 more shades of gray (1/32 increments but not repeating 1/8 increments)" index _ 17. 1 to: 31 do: [:v | (v \\ 4) = 0 ifFalse: [ grayVal _ v / 32.0. a at: index put: (Color r: grayVal g: grayVal b: grayVal). index _ index + 1]]. "The remainder of color table defines a color cube with six steps for each primary color. Note that the corners of this cube repeat previous colors, but this simplifies the mapping between RGB colors and color map indices. This color cube spans indices 40 through 255 (indices 41-256 in this 1-based array)." 0 to: 5 do: [:r | 0 to: 5 do: [:g | 0 to: 5 do: [:b | index _ 41 + ((36 * r) + (6 * b) + g). index > 256 ifTrue: [ self error: 'index out of range in color table compuation']. a at: index put: (Color r: r g: g b: b range: 5)]]]. IndexedColors _ a. ! ! !Color class methodsFor: 'class initialization' stamp: 'dwh 7/7/1999 23:57'! initializeNames "Name some colors." "Color initializeNames" ColorNames _ OrderedCollection new. self named: #black put: (Color r: 0 g: 0 b: 0). self named: #veryVeryDarkGray put: (Color r: 0.125 g: 0.125 b: 0.125). self named: #veryDarkGray put: (Color r: 0.25 g: 0.25 b: 0.25). self named: #darkGray put: (Color r: 0.375 g: 0.375 b: 0.375). self named: #gray put: (Color r: 0.5 g: 0.5 b: 0.5). self named: #lightGray put: (Color r: 0.625 g: 0.625 b: 0.625). self named: #veryLightGray put: (Color r: 0.75 g: 0.75 b: 0.75). self named: #veryVeryLightGray put: (Color r: 0.875 g: 0.875 b: 0.875). self named: #white put: (Color r: 1.0 g: 1.0 b: 1.0). self named: #red put: (Color r: 1.0 g: 0 b: 0). self named: #yellow put: (Color r: 1.0 g: 1.0 b: 0). self named: #green put: (Color r: 0 g: 1.0 b: 0). self named: #cyan put: (Color r: 0 g: 1.0 b: 1.0). self named: #blue put: (Color r: 0 g: 0 b: 1.0). self named: #magenta put: (Color r: 1.0 g: 0 b: 1.0). self named: #brown put: (Color r: 0.6 g: 0.2 b: 0). self named: #orange put: (Color r: 1.0 g: 0.6 b: 0). self named: #lightRed put: (Color r: 1.0 g: 0.8 b: 0.8). self named: #lightYellow put: (Color r: 1.0 g: 1.0 b: 0.8). self named: #lightGreen put: (Color r: 0.8 g: 1.0 b: 0.6). self named: #lightCyan put: (Color r: 0.4 g: 1.0 b: 1.0). self named: #lightBlue put: (Color r: 0.8 g: 1.0 b: 1.0). self named: #lightMagenta put: (Color r: 1.0 g: 0.8 b: 1.0). self named: #lightBrown put: (Color r: 1.0 g: 0.6 b: 0.2). self named: #lightOrange put: (Color r: 1.0 g: 0.8 b: 0.4). self named: #transparent put: (TranslucentColor new alpha: 0.0). self named: #paleBuff put: (Color r: 254 g: 250 b: 235 range: 255). self named: #paleBlue put: (Color r: 222 g: 249 b: 254 range: 255). self named: #paleYellow put: (Color r: 255 g: 255 b: 217 range: 255). self named: #paleGreen put: (Color r: 223 g: 255 b: 213 range: 255). self named: #paleRed put: (Color r: 255 g: 230 b: 230 range: 255). self named: #veryPaleRed put: (Color r: 255 g: 242 b: 242 range: 255). self named: #paleTan put: (Color r: 235 g: 224 b: 199 range: 255). self named: #paleMagenta put: (Color r: 255 g: 230 b: 255 range: 255). self named: #paleOrange put: (Color r: 253 g: 237 b: 215 range: 255). self named: #palePeach put: (Color r: 255 g: 237 b: 213 range: 255). ! ! !Color class methodsFor: 'class initialization' stamp: 'ar 2/16/2000 21:56'! initializeTranslucentPatterns "Color initializeTranslucentPatterns" | mask bits pattern patternList | TranslucentPatterns _ Array new: 8. #(1 2 4 8) do:[:d| patternList _ Array new: 5. mask _ (1 bitShift: d) - 1. bits _ 2 * d. [bits >= 32] whileFalse: [ mask _ mask bitOr: (mask bitShift: bits). "double the length of mask" bits _ bits + bits]. "0% pattern" pattern _ Bitmap with: 0 with: 0. patternList at: 1 put: pattern. "25% pattern" pattern _ Bitmap with: mask with: 0. patternList at: 2 put: pattern. "50% pattern" pattern _ Bitmap with: mask with: mask bitInvert32. patternList at: 3 put: pattern. "75% pattern" pattern _ Bitmap with: mask with: 16rFFFFFFFF. patternList at: 4 put: pattern. "100% pattern" pattern _ Bitmap with: 16rFFFFFFFF with: 16rFFFFFFFF. patternList at: 5 put: pattern. TranslucentPatterns at: d put: patternList. ].! ! !Color class methodsFor: 'class initialization' stamp: 'tk 6/13/96'! named: newName put: aColor "Add a new color to the list and create an access message and a class variable for it. The name should start with a lowercase letter. (The class variable will start with an uppercase letter.) (Color colorNames) returns a list of all color names. " | str cap sym accessor csym | (aColor isKindOf: self) ifFalse: [^ self error: 'not a Color']. str _ newName asString. sym _ str asSymbol. cap _ str capitalized. csym _ cap asSymbol. (self class canUnderstand: sym) ifFalse: [ "define access message" accessor _ str, (String with: Character cr with: Character tab), '^', cap. self class compile: accessor classified: 'named colors']. (self classPool includesKey: csym) ifFalse: [ self addClassVarName: cap]. (ColorNames includes: sym) ifFalse: [ ColorNames add: sym]. ^ self classPool at: csym put: aColor! ! !Color class methodsFor: 'examples'! colorRampForDepth: depth extent: aPoint "Returns a form of the given size showing R, G, B, and gray ramps for the given depth. Useful for testing color conversions between different depths." "(Color colorRampForDepth: Display depth extent: 256@80) display" "(Color colorRampForDepth: 32 extent: 256@80) displayOn: Display at: 0@0 rule: Form paint" | f dx dy r | f _ Form extent: aPoint depth: depth. dx _ aPoint x // 256. dy _ aPoint y // 4. 0 to: 255 do: [:i | r _ (dx * i)@0 extent: dx@dy. f fill: r fillColor: (Color r: i g: 0 b: 0 range: 255). r _ r translateBy: 0@dy. f fill: r fillColor: (Color r: 0 g: i b: 0 range: 255). r _ r translateBy: 0@dy. f fill: r fillColor: (Color r: 0 g: 0 b: i range: 255). r _ r translateBy: 0@dy. f fill: r fillColor: (Color r: i g: i b: i range: 255)]. ^ f ! ! !Color class methodsFor: 'examples' stamp: 'tk 6/19/96'! hotColdShades: thisMany "An array of thisMany colors showing temperature from blue to red to white hot. (Later improve this by swinging in hue.) " "Color showColors: (Color hotColdShades: 25)" | n s1 s2 s3 s4 s5 | thisMany < 5 ifTrue: [^ self error: 'must be at least 5 shades']. n _ thisMany // 5. s1 _ self white mix: self yellow shades: (thisMany - (n*4)). s2 _ self yellow mix: self red shades: n+1. s2 _ s2 copyFrom: 2 to: n+1. s3 _ self red mix: self green darker shades: n+1. s3 _ s3 copyFrom: 2 to: n+1. s4 _ self green darker mix: self blue shades: n+1. s4 _ s4 copyFrom: 2 to: n+1. s5 _ self blue mix: self black shades: n+1. s5 _ s5 copyFrom: 2 to: n+1. ^ s1, s2, s3, s4, s5 ! ! !Color class methodsFor: 'examples'! showColorCube "Show a 12x12x12 color cube." "Color showColorCube" 0 to: 11 do: [:r | 0 to: 11 do: [:g | 0 to: 11 do: [:b | Display fill: (((r*60) + (b*5)) @ (g*5) extent: 5@5) fillColor: (Color r: r g: g b: b range: 11)]]]. ! ! !Color class methodsFor: 'examples'! showColors: colorList "Display the given collection of colors across the top of the Display." | w r | w _ Display width // colorList size. r _ 0@0 extent: w@((w min: 30) max: 10). colorList do: [:c | Display fill: r fillColor: c. r _ r translateBy: w@0]. ! ! !Color class methodsFor: 'examples'! showHSVPalettes "Shows a palette of hues, varying the saturation and brightness for each one. Best results are with depths 16 and 32." "Color showHSVPalettes" | left top c | left _ top _ 0. 0 to: 179 by: 15 do: [:h | 0 to: 10 do: [:s | left _ (h * 4) + (s * 4). 0 to: 10 do: [:v | c _ Color h: h s: s asFloat / 10.0 v: v asFloat / 10.0. top _ (v * 4). Display fill: (left@top extent: 4@4) fillColor: c. c _ Color h: h + 180 s: s asFloat / 10.0 v: v asFloat / 10.0. top _ (v * 4) + 50. Display fill: (left@top extent: 4@4) fillColor: c]]]. ! ! !Color class methodsFor: 'examples'! showHuesInteractively "Shows a palette of hues at a (saturation, brightness) point determined by the mouse position. Click the mouse button to exit and return the selected (saturation, brightness) point." "Color showHuesInteractively" | p s v | [Sensor anyButtonPressed] whileFalse: [ p _ Sensor cursorPoint. s _ p x asFloat / 300.0. v _ p y asFloat / 300.0. self showColors: (self wheel: 12 saturation: s brightness: v)]. ^ (s min: 1.0) @ (v min: 1.0)! ! !Color class methodsFor: 'examples'! wheel: thisMany "Return a collection of thisMany colors evenly spaced around the color wheel." "Color showColors: (Color wheel: 12)" ^ Color wheel: thisMany saturation: 0.9 brightness: 0.7 ! ! !Color class methodsFor: 'examples'! wheel: thisMany saturation: s brightness: v "Return a collection of thisMany colors evenly spaced around the color wheel, all of the given saturation and brightness." "Color showColors: (Color wheel: 12 saturation: 0.4 brightness: 1.0)" "Color showColors: (Color wheel: 12 saturation: 0.8 brightness: 0.5)" ^ (Color h: 0.0 s: s v: v) wheel: thisMany ! ! !Color class methodsFor: 'named colors'! black ^Black! ! !Color class methodsFor: 'named colors'! blue ^Blue! ! !Color class methodsFor: 'named colors'! brown ^Brown! ! !Color class methodsFor: 'named colors'! cyan ^Cyan! ! !Color class methodsFor: 'named colors'! darkGray ^DarkGray! ! !Color class methodsFor: 'named colors'! gray ^Gray! ! !Color class methodsFor: 'named colors'! green ^Green! ! !Color class methodsFor: 'named colors'! lightBlue ^LightBlue! ! !Color class methodsFor: 'named colors'! lightBrown ^LightBrown! ! !Color class methodsFor: 'named colors'! lightCyan ^LightCyan! ! !Color class methodsFor: 'named colors'! lightGray ^LightGray! ! !Color class methodsFor: 'named colors'! lightGreen ^LightGreen! ! !Color class methodsFor: 'named colors'! lightMagenta ^LightMagenta! ! !Color class methodsFor: 'named colors'! lightOrange ^LightOrange! ! !Color class methodsFor: 'named colors'! lightRed ^LightRed! ! !Color class methodsFor: 'named colors'! lightYellow ^LightYellow! ! !Color class methodsFor: 'named colors'! magenta ^Magenta! ! !Color class methodsFor: 'named colors'! orange ^Orange! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleBlue ^PaleBlue! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleBuff ^PaleBuff! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleGreen ^PaleGreen! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleMagenta ^PaleMagenta! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleOrange ^PaleOrange! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! palePeach ^PalePeach! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleRed ^PaleRed! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleTan ^PaleTan! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! paleYellow ^PaleYellow! ! !Color class methodsFor: 'named colors'! red ^Red! ! !Color class methodsFor: 'named colors' stamp: 'wod 5/24/1998 01:56'! tan ^ Color r: 0.8 g: 0.8 b: 0.5! ! !Color class methodsFor: 'named colors'! transparent ^Transparent! ! !Color class methodsFor: 'named colors'! veryDarkGray ^VeryDarkGray! ! !Color class methodsFor: 'named colors'! veryLightGray ^VeryLightGray! ! !Color class methodsFor: 'named colors' stamp: 'dwh 7/7/1999 23:56'! veryPaleRed ^VeryPaleRed! ! !Color class methodsFor: 'named colors'! veryVeryDarkGray ^VeryVeryDarkGray! ! !Color class methodsFor: 'named colors'! veryVeryLightGray ^VeryVeryLightGray! ! !Color class methodsFor: 'named colors'! white ^White! ! !Color class methodsFor: 'named colors'! yellow ^Yellow! ! !Color class methodsFor: 'colormaps' stamp: 'jm 5/2/1999 07:24'! cachedColormapFrom: sourceDepth to: destDepth "Return a cached colormap for mapping between the given depths. Always return a real colormap, not nil; this allows the client to get an identity colormap that can then be copied and modified to do color transformations." "Note: This method returns a shared, cached colormap to save time and space. Clients that need to modify a colormap returned by this method should make a copy and modify that!!" "Note: The colormap cache may be cleared by evaluating 'Color shutDown'." | srcIndex map | CachedColormaps class == Array ifFalse: [CachedColormaps _ (1 to: 9) collect: [:i | Array new: 32]]. srcIndex _ sourceDepth. sourceDepth > 8 ifTrue: [srcIndex _ 9]. (map _ (CachedColormaps at: srcIndex) at: destDepth) ~~ nil ifTrue: [^ map]. map _ self computeColormapFrom: sourceDepth to: destDepth. (CachedColormaps at: srcIndex) at: destDepth put: map. ^ map ! ! !Color class methodsFor: 'colormaps'! colorMapIfNeededFrom: sourceDepth to: destDepth "Return a colormap for mapping between the given depths, or nil if no colormap is needed." "Note: This method returns a shared, cached colormap to save time and space. Clients that need to modify a colormap returned by this method should make a copy and modify that!!" sourceDepth = destDepth ifTrue: [^ nil]. "not needed if depths are the same" (sourceDepth >= 16) & (destDepth >= 16) ifTrue: [ "mapping is done in BitBlt by zero-filling or truncating each color component" ^ nil]. ^ Color cachedColormapFrom: sourceDepth to: destDepth ! ! !Color class methodsFor: 'colormaps' stamp: 'jm 3/25/1999 19:48'! computeColormapFrom: sourceDepth to: destDepth "Compute a colorMap for translating between the given depths. A colormap is a Bitmap whose entries contain the pixel values for the destination depth. Typical clients use cachedColormapFrom:to: instead." | map bitsPerColor | sourceDepth < 16 ifTrue: [ "source is 1-, 2-, 4-, or 8-bit indexed color" map _ (IndexedColors copyFrom: 1 to: (1 bitShift: sourceDepth)) collect: [:c | c pixelValueForDepth: destDepth]. map _ map as: Bitmap. ] ifFalse: [ "source is 16-bit or 32-bit RGB" destDepth > 8 ifTrue: [bitsPerColor _ 5] "retain maximum color resolution" ifFalse: [bitsPerColor _ 4]. map _ self computeRGBColormapFor: destDepth bitsPerColor: bitsPerColor]. "Note: zero is transparent except when source depth is one-bit deep" sourceDepth > 1 ifTrue: [map at: 1 put: 0]. ^ map ! ! !Color class methodsFor: 'colormaps' stamp: 'jm 12/4/97 15:25'! computeRGBColormapFor: destDepth bitsPerColor: bitsPerColor "Compute a colorMap for translating from 16-bit or 32-bit RGB color to the given depth, using the given number of of bits per color component." | mask map c | (#(3 4 5) includes: bitsPerColor) ifFalse: [self error: 'BitBlt only supports 3, 4, or 5 bits per color component']. mask _ (1 bitShift: bitsPerColor) - 1. map _ Bitmap new: (1 bitShift: (3 * bitsPerColor)). 0 to: map size - 1 do: [:i | c _ Color r: ((i bitShift: 0 - (2 * bitsPerColor)) bitAnd: mask) g: ((i bitShift: 0 - bitsPerColor) bitAnd: mask) b: ((i bitShift: 0) bitAnd: mask) range: mask. map at: i + 1 put: (c pixelValueForDepth: destDepth)]. map at: 1 put: (Color transparent pixelWordForDepth: destDepth). "zero always transparent" ^ map ! ! !Color class methodsFor: 'other'! colorNames "Return a collection of color names." ^ ColorNames! ! !Color class methodsFor: 'other'! indexedColors ^ IndexedColors! ! !Color class methodsFor: 'other' stamp: 'di 3/29/1999 13:33'! maskingMap: depth "Return a color map that maps all colors except transparent to words of all ones. Used to create a mask for a Form whose transparent pixel value is zero. Cache the most recently used map." | sizeNeeded | depth <= 8 ifTrue: [sizeNeeded _ 1 bitShift: depth] ifFalse: [sizeNeeded _ 4096]. (MaskingMap == nil or: [MaskingMap size ~= sizeNeeded]) ifTrue: [MaskingMap _ Bitmap new: sizeNeeded withAll: 16rFFFFFFFF. MaskingMap at: 1 put: 0. "transparent"]. ^ MaskingMap ! ! !Color class methodsFor: 'other'! pixelScreenForDepth: depth "Return a 50% stipple containing alternating pixels of all-zeros and all-ones to be used as a mask at the given depth." | mask bits | mask _ (1 bitShift: depth) - 1. bits _ 2 * depth. [bits >= 32] whileFalse: [ mask _ mask bitOr: (mask bitShift: bits). "double the length of mask" bits _ bits + bits]. ^ Bitmap with: mask with: mask bitInvert32 ! ! !Color class methodsFor: 'other'! quickHighLight: depth "Quickly return a Bitblt-ready raw colorValue for highlighting areas. 6/22/96 tk" ^ HighLightBitmaps at: depth! ! !Color class methodsFor: 'other'! shutDown "Color shutDown" ColorChart _ nil. "Palette of colors for the user to pick from" CachedColormaps _ nil. "Maps to translate between color depths" MaskingMap _ nil. "Maps all colors except transparent to black for creating a mask" ! ! !Color class methodsFor: 'other' stamp: 'ar 2/16/2000 21:56'! translucentMaskFor: alphaValue depth: d "Return a pattern representing a mask usable for stipple transparency" ^(TranslucentPatterns at: d) at: ((alphaValue min: 1.0 max: 0.0) * 4) rounded + 1! ! !Color class methodsFor: 'color from user' stamp: 'jm 12/5/97 18:35'! colorPaletteForDepth: depth extent: chartExtent "Display a palette of colors sorted horizontally by hue and vertically by lightness. Useful for eyeballing the color gamut of the display, or for choosing a color interactively." "Note: It is slow to build this palette, so it should be cached for quick access." "(Color colorPaletteForDepth: 16 extent: 190@60) display" | basicHue x y c startHue palette transHt vSteps transCaption grayWidth hSteps | palette _ Form extent: chartExtent depth: depth. transCaption _ "(DisplayText text: 'no color' asText textStyle: (TextConstants at: #ComicPlain)) form storeString" (Form extent: 34@9 depth: 1 fromArray: #(0 0 256 0 256 0 3808663859 2147483648 2491688266 2147483648 2491688266 0 2491688266 0 2466486578 0 0 0) offset: 0@0). transHt _ transCaption height. palette fillWhite: (0@0 extent: palette width@transHt). palette fillBlack: (0@transHt extent: palette width@1). transCaption displayOn: palette at: palette boundingBox topCenter - ((transCaption width // 2)@0). grayWidth _ 10. startHue _ 338.0. vSteps _ palette height - transHt // 2. hSteps _ palette width - grayWidth. x _ 0. startHue to: startHue + 360.0 by: 360.0/hSteps do: [:h | basicHue _ Color h: h asFloat s: 1.0 v: 1.0. y _ transHt+1. 0 to: vSteps do: [:n | c _ basicHue mixed: (n asFloat / vSteps asFloat) with: Color white. palette fill: (x@y extent: 1@1) fillColor: c. y _ y + 1]. 1 to: vSteps do: [:n | c _ Color black mixed: (n asFloat / vSteps asFloat) with: basicHue. palette fill: (x@y extent: 1@1) fillColor: c. y _ y + 1]. x _ x + 1]. y _ transHt + 1. 1 to: vSteps * 2 do: [:n | c _ Color black mixed: (n asFloat / (vSteps*2) asFloat) with: Color white. palette fill: (x@y extent: 10@1) fillColor: c. y _ y + 1]. ^ palette ! ! !Color class methodsFor: 'color from user' stamp: 'jm 1/19/1999 11:33'! colorTest: depth extent: chartExtent colorMapper: colorMapper "Create a palette of colors sorted horizontally by hue and vertically by lightness. Useful for eyeballing the color gamut of the display, or for choosing a color interactively." "Note: It is slow to build this palette, so it should be cached for quick access." "(Color colorTest: 32 extent: 570@180 colorMapper: [:c | c]) display" "(Color colorTest: 32 extent: 570@180 colorMapper: [:c | Color r: (c red * 7) asInteger / 7 g: (c green * 7) asInteger / 7 b: (c blue * 3) asInteger / 3]) display" "(Color colorTest: 32 extent: 570@180 colorMapper: [:c | Color r: (c red * 5) asInteger / 5 g: (c green * 5) asInteger / 5 b: (c blue * 5) asInteger / 5]) display" "(Color colorTest: 32 extent: 570@180 colorMapper: [:c | Color r: (c red * 15) asInteger / 15 g: (c green * 15) asInteger / 15 b: (c blue * 15) asInteger / 15]) display" "(Color colorTest: 32 extent: 570@180 colorMapper: [:c | Color r: (c red * 31) asInteger / 31 g: (c green * 31) asInteger / 31 b: (c blue * 31) asInteger / 31]) display" | basicHue x y c startHue palette transHt vSteps transCaption grayWidth hSteps | palette _ Form extent: chartExtent depth: depth. transCaption _ "(DisplayText text: 'no color' asText textStyle: (TextConstants at: #ComicPlain)) form storeString" (Form extent: 34@9 depth: 1 fromArray: #(0 0 256 0 256 0 3808663859 2147483648 2491688266 2147483648 2491688266 0 2491688266 0 2466486578 0 0 0) offset: 0@0). transHt _ transCaption height. palette fillWhite: (0@0 extent: palette width@transHt). palette fillBlack: (0@transHt extent: palette width@1). transCaption displayOn: palette at: palette boundingBox topCenter - ((transCaption width // 2)@0). grayWidth _ 10. startHue _ 338.0. vSteps _ palette height - transHt // 2. hSteps _ palette width - grayWidth. x _ 0. startHue to: startHue + 360.0 by: 360.0/hSteps do: [:h | basicHue _ Color h: h asFloat s: 1.0 v: 1.0. y _ transHt+1. 0 to: vSteps do: [:n | c _ basicHue mixed: (n asFloat / vSteps asFloat) with: Color white. c _ colorMapper value: c. palette fill: (x@y extent: 1@1) fillColor: c. y _ y + 1]. 1 to: vSteps do: [:n | c _ Color black mixed: (n asFloat / vSteps asFloat) with: basicHue. c _ colorMapper value: c. palette fill: (x@y extent: 1@1) fillColor: c. y _ y + 1]. x _ x + 1]. y _ transHt + 1. 1 to: vSteps * 2 do: [:n | c _ Color black mixed: (n asFloat / (vSteps*2) asFloat) with: Color white. c _ colorMapper value: c. palette fill: (x@y extent: 10@1) fillColor: c. y _ y + 1]. ^ palette ! ! !Color class methodsFor: 'color from user' stamp: 'di 4/13/1999 14:30'! fromUser "Displays a color palette of colors, waits for a mouse click, and returns the selected color. Any pixel on the Display can be chosen, not just those in the color palette." "Note: Since the color chart is cached, you may need to do 'ColorChart _ nil' after changing the oldColorPaletteForDepth:extent: method." "Color fromUser" | d startPt save tr oldColor c here s | d _ Display depth. ((ColorChart == nil) or: [ColorChart depth ~= Display depth]) ifTrue: [ColorChart _ self oldColorPaletteForDepth: d extent: (2 * 144)@80]. Sensor cursorPoint y < Display center y ifTrue: [startPt _ 0@(Display boundingBox bottom - ColorChart height)] ifFalse: [startPt _ 0@0]. save _ Form fromDisplay: (startPt extent: ColorChart extent). ColorChart displayAt: startPt. tr _ ColorChart extent - (50@19) corner: ColorChart extent. tr _ tr translateBy: startPt. oldColor _ nil. [Sensor anyButtonPressed] whileFalse: [ c _ Display colorAt: (here _ Sensor cursorPoint). (tr containsPoint: here) ifFalse: [Display fill: (0@61+startPt extent: 20@19) fillColor: c] ifTrue: [ c _ Color transparent. Display fill: (0@61+startPt extent: 20@19) fillColor: Color white]. c = oldColor ifFalse: [ Display fillWhite: (20@61 + startPt extent: 135@19). c isTransparent ifTrue: [s _ 'transparent'] ifFalse: [s _ c shortPrintString. s _ s copyFrom: 7 to: s size - 1]. s displayAt: 20@61 + startPt. oldColor _ c]]. save displayAt: startPt. Sensor waitNoButton. ^ c ! ! !Color class methodsFor: 'color from user' stamp: 'di 4/13/1999 14:28'! oldColorPaletteForDepth: depth extent: paletteExtent "Returns a form of the given size showing a color palette for the given depth." "(Color oldColorPaletteForDepth: Display depth extent: 720@100) display" | c p f nSteps rect w h q | f _ Form extent: paletteExtent depth: depth. f fill: f boundingBox fillColor: Color white. nSteps _ depth > 8 ifTrue: [12] ifFalse: [6]. w _ paletteExtent x // (nSteps * nSteps). h _ paletteExtent y - 20 // nSteps. 0 to: nSteps-1 do: [:r | 0 to: nSteps-1 do: [:g | 0 to: nSteps-1 do: [:b | c _ Color r: r g: g b: b range: nSteps - 1. rect _ ((r * nSteps * w) + (b * w)) @ (g * h) extent: w@(h + 1). f fill: rect fillColor: c]]]. q _ Quadrangle origin: paletteExtent - (50@19) corner: paletteExtent. q displayOn: f. 'Trans.' displayOn: f at: q origin + (9@1). w _ ((paletteExtent x - q width - 130) // 64) max: 1. p _ paletteExtent x - q width - (64 * w) - 1 @ (paletteExtent y - 19). 0 to: 63 do: [:v | c _ Color r: v g: v b: v range: 63. f fill: ((v * w)@0 + p extent: (w + 1)@19) fillColor: c]. ^ f ! ! Form subclass: #ColorForm instanceVariableNames: 'colors cachedDepth cachedColormap ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !ColorForm commentStamp: '' prior: 0! ColorForm is a normal Form plus a color map of up to 2^depth Colors. Typically, one reserves one entry in the color map for transparent. This allows 1, 3, 15, or 255 non-transparent colors in ColorForms of depths 1, 2, 4, and 8 bits per pixel. ColorForms don't support depths greater than 8 bits because that would require excessively large color maps with little real benefit, since 16-bit and 32-bit depths already support thousands and millions of colors. ColorForms have several uses: 1) Precise colors. You can have up to 256 true colors, instead being limited to the 8-bit color palette. 2) Easy transparency. Just store (Color transparent) at the desired position in the color map. 3) Cheap color remapping by changing the color map. A color map is an Array of up to 2^depth Color objects. A Bitmap colorMap is automatically computed and cached for rapid display. Note that if you change the color map, you must resubmit it via the colors: method to flush this cache. ColorForms can be a bit tricky. Note that: a) When you BitBlt from one ColorForm to another, you must remember to copy the color map of the source ColorForm to the destination ColorForm. b) A ColorForm's color map is an array of depth-independent Color objects. BitBlt requires a BitMap of actual pixel values, adjusted to the destination depth. These are different things!! ColorForms automatically maintain a cache of the BitBlt-style color map corresponding to the colors array for the last depth on which the ColorForm was displayed, so there should be little need for clients to work with BitBlt-style color maps. c) The default map for 8 bit depth has black in the first entry, not transparent. Say (cform colors at: 1 put: Color transparent). ! !ColorForm methodsFor: 'accessing' stamp: 'jm 11/14/97 17:39'! colors "Return my color palette." self ensureColorArrayExists. ^ colors ! ! !ColorForm methodsFor: 'accessing'! colors: colorList "Set my color palette to the given collection." | colorArray colorCount newColors | colorList ifNil: [ colors _ cachedDepth _ cachedColormap _ nil. ^ self]. colorArray _ colorList asArray. colorCount _ colorArray size. newColors _ Array new: (1 bitShift: depth). 1 to: newColors size do: [:i | i <= colorCount ifTrue: [newColors at: i put: (colorArray at: i)] ifFalse: [newColors at: i put: Color transparent]]. colors _ newColors. cachedDepth _ nil. cachedColormap _ nil. ! ! !ColorForm methodsFor: 'accessing' stamp: 'mir 7/21/1999 11:51'! colorsFromArray: colorArray | colorList | colorList _ colorArray collect: [:colorDef | Color fromArray: colorDef]. self colors: colorList! ! !ColorForm methodsFor: 'displaying'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: rule fillColor: aForm aDisplayMedium copyBits: self boundingBox from: self at: aDisplayPoint + self offset clippingBox: clipRectangle rule: rule fillColor: aForm map: (self colormapIfNeededForDepth: aDisplayMedium depth). ! ! !ColorForm methodsFor: 'displaying' stamp: 'di 7/17/97 10:04'! displayOnPort: port at: location port copyForm: self to: location rule: Form paint! ! !ColorForm methodsFor: 'pixel accessing' stamp: 'jm 11/14/97 17:25'! colorAt: aPoint "Return the color of the pixel at aPoint." ^ self colors at: (self pixelValueAt: aPoint) + 1 ! ! !ColorForm methodsFor: 'pixel accessing' stamp: 'jm 11/14/97 17:25'! colorAt: aPoint put: aColor "Store the given color into the pixel at aPoint. The given color must match one of the colors in the receiver's colormap." | i | i _ self colors indexOf: aColor ifAbsent: [^ self error: 'trying to use a color that is not in my colormap']. self pixelValueAt: aPoint put: i - 1. ! ! !ColorForm methodsFor: 'pixel accessing' stamp: 'tk 10/21/97 12:27'! isTransparentAt: aPoint "Return true if the receiver is transparent at the given point." ^ (self colorAt: aPoint) isTransparent ! ! !ColorForm methodsFor: 'pixel accessing' stamp: 'ar 5/28/2000 12:06'! pixelValueAt: aPoint "Return the raw pixel value at the given point. Typical clients use colorAt: to get a Color." "Details: To get the raw pixel value, be sure the peeker's colorMap is nil." ^ (BitBlt current bitPeekerFromForm: self) colorMap: nil; pixelAt: aPoint ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'di 11/11/1998 13:20'! asGrayScale "Return a grayscale ColorForm computed by mapping each color into its grayscale equivalent" ^ self copy colors: (colors collect: [:c | c isTransparent ifTrue: [c] ifFalse: [Color gray: c luminance]])! ! !ColorForm methodsFor: 'color manipulation' stamp: 'ar 5/25/2000 19:51'! colormapIfNeededForDepth: destDepth "Return a colormap for displaying the receiver at the given depth, or nil if no colormap is needed." | newMap | colors == nil ifTrue: [ "use the standard colormap" ^ Color colorMapIfNeededFrom: depth to: destDepth]. (destDepth = cachedDepth and:[cachedColormap isColormap not]) ifTrue: [^ cachedColormap]. newMap _ Bitmap new: colors size. 1 to: colors size do: [:i | newMap at: i put: ((colors at: i) pixelValueForDepth: destDepth)]. cachedDepth _ destDepth. ^ cachedColormap _ newMap. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'jm 4/18/98 20:34'! colorsUsed "Return a list of the colors actually used by this ColorForm." | myColor list | myColor _ self colors. list _ OrderedCollection new. self tallyPixelValues doWithIndex: [:count :i | count > 0 ifTrue: [list add: (myColor at: i)]]. ^ list asArray ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'jm 11/16/97 11:18'! ensureTransparentColor "Ensure that the receiver (a) includes Color transparent in its color map and (b) that the entry for Color transparent is the first entry in its color map." | i | self error: 'not yet implemented'. (colors includes: Color transparent) ifTrue: [ (colors indexOf: Color transparent) = 1 ifTrue: [^ self]. "shift the entry for color transparent"] ifFalse: [ i _ self unusedColormapEntry. i = 0 ifTrue: [self error: 'no color map entry is available']. colors at: i put: Color transparent. "shift the entry for color transparent"]. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'di 8/28/1998 15:48'! indexOfColor: aColor "Return the index of aColor in my color array" self ensureColorArrayExists. ^ colors indexOf: aColor ifAbsent: [0]! ! !ColorForm methodsFor: 'color manipulation' stamp: 'jm 10/19/1998 10:52'! mapColor: oldColor to: newColor "Replace all occurances of the given color with the given new color in my color map." self ensureColorArrayExists. 1 to: colors size do: [:i | (colors at: i) = oldColor ifTrue: [colors at: i put: newColor]]. self clearColormapCache. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'jm 3/27/98 13:24'! readFrom: aBinaryStream self error: 'not yet implemented'. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'jm 11/16/97 09:08'! replaceColor: oldColor with: newColor "Replace all occurances of the given color with the given new color in my color map." self ensureColorArrayExists. 1 to: colors size do: [:i | (colors at: i) = oldColor ifTrue: [colors at: i put: newColor]]. self clearColormapCache. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'tk 3/2/98 15:42'! replaceColorAt: aPoint with: newColor "Replace a color map entry with newColor. The entry replaced is the one used by aPoint. If there are are two entries in the colorMap for the oldColor, just replace ONE!!!! There are often two whites or two blacks, and this is what you want, when replacing one." | oldIndex | self ensureColorArrayExists. oldIndex _ self pixelValueAt: aPoint. colors at: oldIndex+1 put: newColor. self clearColormapCache. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'di 8/28/1998 15:49'! replaceColorAtIndex: index with: newColor "Replace a color map entry with newColor." self ensureColorArrayExists. colors at: index put: newColor. cachedColormap == nil ifFalse: [cachedColormap at: index put: (newColor pixelValueForDepth: cachedDepth)]! ! !ColorForm methodsFor: 'color manipulation' stamp: 'tk 3/2/98 11:26'! transparentAllPixelsLike: aPoint "Make all occurances of the given pixel value transparent. Very useful when two entries in the colorMap have the same value. This only changes ONE." self replaceColorAt: aPoint with: Color transparent. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'tk 3/2/98 11:27'! transparentColor: aColor "Make all occurances of the given color transparent. Note: for colors like black and white, which have two entries in the colorMap, this changes BOTH of them. Not always what you want." self replaceColor: aColor with: Color transparent. ! ! !ColorForm methodsFor: 'color manipulation' stamp: 'ar 5/28/2000 12:06'! twoToneFromDisplay: aRectangle backgroundColor: bgColor "Copy one-bit deep ColorForm from the Display using a color map that maps all colors except the background color to black. Used for caching the contents of inactive MVC windows." | map | (width = aRectangle width and: [height = aRectangle height]) ifFalse: [self setExtent: aRectangle extent depth: depth]. "make a color map mapping the background color to zero and all other colors to one" map _ Bitmap new: (1 bitShift: (Display depth min: 9)). 1 to: map size do: [:i | map at: i put: 16rFFFFFFFF]. map at: (bgColor indexInMap: map) put: 0. (BitBlt current toForm: self) destOrigin: 0@0; sourceForm: Display; sourceRect: aRectangle; combinationRule: Form over; colorMap: map; copyBits. ! ! !ColorForm methodsFor: 'copying' stamp: 'RAA 8/14/2000 10:45'! asCursorForm ^ (self asFormOfDepth: 32) offset: offset; as: StaticForm! ! !ColorForm methodsFor: 'copying' stamp: 'RAA 9/28/1999 11:22'! blankCopyOf: aRectangle scaledBy: scale | newForm | newForm _ self class extent: (aRectangle extent * scale) truncated depth: depth. colors ifNotNil: [newForm colors: colors copy]. ^ newForm! ! !ColorForm methodsFor: 'copying' stamp: 'ar 5/28/2000 12:06'! copy: aRect "Return a new ColorForm containing the portion of the receiver delineated by aRect." | newForm | newForm _ self class extent: aRect extent depth: depth. ((BitBlt current destForm: newForm sourceForm: self fillColor: nil combinationRule: Form over destOrigin: 0@0 sourceOrigin: aRect origin extent: aRect extent clipRect: newForm boundingBox) colorMap: nil) copyBits. colors ifNotNil: [newForm colors: colors copy]. ^ newForm ! ! !ColorForm methodsFor: 'copying' stamp: 'jm 2/27/98 09:38'! deepCopy ^ self shallowCopy bits: bits copy; offset: offset copy; colors: colors ! ! !ColorForm methodsFor: 'private' stamp: 'jm 11/16/97 09:07'! clearColormapCache cachedDepth _ nil. cachedColormap _ nil. ! ! !ColorForm methodsFor: 'private' stamp: 'jm 11/16/97 09:12'! depth: bitsPerPixel bitsPerPixel > 8 ifTrue: [self error: 'ColorForms only support depths up to 8 bits']. super depth: bitsPerPixel. ! ! !ColorForm methodsFor: 'private' stamp: 'jm 11/16/97 08:37'! ensureColorArrayExists "Return my color palette." colors ifNil: [ depth > 8 ifTrue: [^ self error: 'ColorForms only support depths up to 8 bits']. self colors: (Color indexedColors copyFrom: 1 to: (1 bitShift: depth))]. ! ! !ColorForm methodsFor: 'private' stamp: 'jm 4/5/1999 10:11'! setColors: colorArray cachedColormap: aBitmap depth: anInteger "Semi-private. Set the color array, cached colormap, and cached colormap depth to avoid having to recompute the colormap when switching color palettes in animations." colors _ colorArray. cachedDepth _ anInteger. cachedColormap _ aBitmap. ! ! !ColorForm methodsFor: 'private' stamp: 'jm 11/16/97 08:37'! setExtent: extent depth: bitsPerPixel "Create a virtual bit map with the given extent and bitsPerPixel." bitsPerPixel > 8 ifTrue: [self error: 'ColorForms only support depths up to 8 bits']. super setExtent: extent depth: bitsPerPixel. ! ! !ColorForm methodsFor: 'private' stamp: 'jm 2/24/98 18:53'! unusedColormapEntry "Return the index of an unused color map entry, or zero if there isn't one." | tallies | tallies _ self tallyPixelValues. 1 to: tallies size do: [:i | (tallies at: i) = 0 ifTrue: [^ i]]. ^ 0 ! ! !ColorForm methodsFor: 'scaling, rotation' stamp: 'ar 3/15/1999 14:28'! flipBy: direction centerAt: aPoint | oldColors newForm | oldColors _ colors. self colors: nil. newForm _ super flipBy: direction centerAt: aPoint. self colors: oldColors. newForm colors: oldColors. ^newForm ! ! !ColorForm methodsFor: 'scaling, rotation' stamp: 'RAA 8/5/2000 18:12'! scaledToSize: newExtent "super method did not seem to work so well on ColorForms" ^(self asFormOfDepth: 16) scaledToSize: newExtent! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'RAA 7/24/2000 13:32'! encodeForRemoteCanvas "encode into a bitstream for use with RemoteCanvas." | colorsToSend | colorsToSend _ self colors. ^String streamContents: [ :str | str nextPut: $C; "indicates color form" nextPutAll: colorsToSend size printString; nextPut: $,. colorsToSend do: [ :each | str nextPutAll: each encodeForRemoteCanvas ]. str nextPutAll: super encodeForRemoteCanvas ]. ! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'jm 7/23/1999 20:42'! hibernate "Make myself take up less space. See comment in Form>hibernate." super hibernate. self clearColormapCache. ! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'bf 5/25/2000 16:31'! storeOn: aStream aStream nextPut: $(. super storeOn: aStream. aStream cr; tab; nextPutAll: 'colorsFromArray: #('. self colors do: [:color | color storeArrayOn: aStream]. aStream nextPutAll: ' ))'.! ! !ColorForm methodsFor: 'postscript generation'! asFormWithSingleTransparentColors | transparentIndexes | transparentIndexes _ self transparentColorIndexes. transparentIndexes size <= 1 ifTrue:[^self] ifFalse:[^self mapTransparencies:transparentIndexes].! ! !ColorForm methodsFor: 'postscript generation'! decodeArray ^self depth = 1 ifTrue:['[1 0]'] ifFalse:['[0 255]'].! ! !ColorForm methodsFor: 'postscript generation' stamp: 'sma 6/14/2000 14:20'! encodePostscriptOn: aStream self unhibernate. aStream print: '% form contains '; write: (colors select: [:c | c = Color transparent]) size; print: ' transparent colors'; cr. ^ self asFormWithSingleTransparentColors printPostscript: aStream operator: (self depth = 1 ifTrue: ['imagemask'] ifFalse: [(self indexOfColor: Color transparent) printString , ' transparentimage'])! ! !ColorForm methodsFor: 'postscript generation'! getTransparencyUnificationLUT | lut transparentIndex | lut _ Array new:colors size. transparentIndex _ self indexOfColor:Color transparent. 1 to: colors size do: [ :i | lut at:i put:(((colors at:i) = Color transparent) ifTrue:[transparentIndex] ifFalse:[i])]. ! ! !ColorForm methodsFor: 'postscript generation'! mapTransparencies:transparentIndexes ^self deepCopy mapColors:transparentIndexes to:(transparentIndexes at:1).! ! !ColorForm methodsFor: 'postscript generation'! printPostscript:aStream aStream nextPutAll:'% form contains '; print:((colors select:[:c| c=Color transparent]) size); nextPutAll:' transparent colors'; cr. ^self asFormWithSingleTransparentColors printPostscript:aStream operator:(self depth=1 ifTrue:['imagemask'] ifFalse:[ (self indexOfColor:Color transparent) printString ,' transparentimage']) . ! ! !ColorForm methodsFor: 'postscript generation'! setColorspaceOn:aStream self depth = 1 ifTrue:[ aStream print:'/DeviceRGB setcolorspace 0 setgray'; cr. ] ifFalse:[ aStream print:'[ /Indexed /DeviceRGB '; write:self colors size-1; print:' <'. (self colormapIfNeededForDepth: 32 ) storeBits:20 to:0 on:aStream. aStream print:'> ] setcolorspace'; cr.]. ! ! !ColorForm methodsFor: 'postscript generation'! transparentColorIndexes ^(1 to: colors size) select: [ :index | (colors at:index) isTransparent ]. ! ! !ColorForm methodsFor: 'color mapping' stamp: 'ar 6/8/2000 20:39'! colormapIfNeededFor: destForm | newMap color pv | colors == nil ifTrue: [ "use the standard colormap" ^ super colormapIfNeededFor: destForm]. (destForm depth = cachedDepth and:[cachedColormap isColormap]) ifTrue: [^ cachedColormap]. newMap _ WordArray new: (1 bitShift: depth). 1 to: colors size do: [:i | color _ colors at: i. pv _ destForm pixelValueFor: color. (pv = 0 and:[color isTransparent not]) ifTrue:[pv _ 1]. newMap at: i put: pv]. cachedDepth _ destForm depth. ^cachedColormap _ ColorMap shifts: nil masks: nil colors: newMap.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorForm class instanceVariableNames: ''! !ColorForm class methodsFor: 'as yet unclassified' stamp: 'jm 11/16/97 09:17'! mappingWhiteToTransparentFrom: aFormOrCursor "Return a ColorForm copied from the given Form or Cursor with white mapped to transparent." | f map | aFormOrCursor depth <= 8 ifFalse: [ ^ self error: 'argument depth must be 8-bits per pixel or less']. (aFormOrCursor isKindOf: ColorForm) ifTrue: [ f _ aFormOrCursor deepCopy. map _ aFormOrCursor colors. ] ifFalse: [ f _ ColorForm extent: aFormOrCursor extent depth: aFormOrCursor depth. f copyBits: aFormOrCursor boundingBox from: aFormOrCursor at: 0@0 clippingBox: aFormOrCursor boundingBox rule: Form over fillColor: nil. map _ Color indexedColors copyFrom: 1 to: (1 bitShift: aFormOrCursor depth)]. map _ map collect: [:c | c = Color white ifTrue: [Color transparent] ifFalse: [c]]. f colors: map. ^ f ! ! !ColorForm class methodsFor: 'as yet unclassified'! twoToneFromDisplay: aRectangle using: oldForm backgroundColor: bgColor "Return a 1-bit deep ColorForm copied from the given rectangle of the display. All colors except the background color will be mapped to black." | f | ((oldForm ~~ nil) and: [oldForm extent = aRectangle extent]) ifTrue: [ f _ oldForm fromDisplay: aRectangle. ] ifFalse: [ f _ ColorForm extent: aRectangle extent depth: 1. f twoToneFromDisplay: aRectangle backgroundColor: bgColor. f colors: (Array with: bgColor with: Color black)]. ^ f ! ! Object subclass: #ColorMap instanceVariableNames: 'shifts masks colors ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Primitives'! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:54'! alphaMask ^masks at: 4! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:55'! alphaMask: value masks at: 4 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:31'! alphaShift ^shifts at: 4! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:31'! alphaShift: value shifts at: 4 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:39'! at: index ^colors at: index! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:39'! at: index put: value ^colors at: index put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:31'! blueMask ^masks at: 3! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:31'! blueMask: value masks at: 3 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:31'! blueShift ^shifts at: 3! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:32'! blueShift: value shifts at: 3 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 2/10/2000 17:12'! colors ^colors! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:32'! greenMask ^masks at: 2! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:32'! greenMask: value masks at: 2 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:32'! greenShift ^shifts at: 2! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:36'! greenShift: value shifts at: 2 put: value.! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 5/28/2000 22:08'! inverseMap "Return the inverse map of the receiver" | newMasks newShifts | colors ifNotNil:[^self error:'Not yet implemented']. newMasks _ WriteStream on: (Array new: 4). newShifts _ WriteStream on: (Array new: 4). masks with: shifts do:[:mask :shift| newMasks nextPut: (mask bitShift: shift). newShifts nextPut: shift negated]. ^ColorMap shifts: newShifts contents masks: newMasks contents! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 5/27/2000 19:16'! masks ^masks! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:36'! redMask ^masks at: 1! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:37'! redMask: value masks at: 1 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:37'! redShift ^shifts at: 1! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 1/16/2000 15:37'! redShift: value shifts at: 1 put: value! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:48'! rgbaBitMasks "Return the rgba bit masks for the receiver" ^masks asArray with: shifts collect:[:m :s| m bitShift: s]! ! !ColorMap methodsFor: 'accessing' stamp: 'ar 5/27/2000 19:16'! shifts ^shifts! ! !ColorMap methodsFor: 'pixel mapping' stamp: 'ar 1/16/2000 20:52'! mapPixel: pixelValue "Perform a forward pixel mapping operation" | pv | (shifts == nil and:[masks == nil]) ifFalse:[ pv _ (((pixelValue bitAnd: self redMask) bitShift: self redShift) bitOr: ((pixelValue bitAnd: self greenMask) bitShift: self greenShift)) bitOr: (((pixelValue bitAnd: self blueMask) bitShift: self blueShift) bitOr: ((pixelValue bitAnd: self alphaMask) bitShift: self alphaShift)). ] ifTrue:[pv _ pixelValue]. colors == nil ifTrue:[^pv] ifFalse:[^colors at: pv].! ! !ColorMap methodsFor: 'pixel mapping' stamp: 'ar 6/8/2000 20:36'! mappingTo: aColorMap "Compute a new color map through the receiver and aColorMap. Both maps are assumed to be mappings into canonical ARGB space" | fixedMap | self = aColorMap ifTrue:[^nil]. "No mapping needed" aColorMap isIndexed ifTrue:[^nil]. "We can't compute mappings to an indexed map yet" fixedMap _ self class mappingFrom: self rgbaBitMasks to: aColorMap rgbaBitMasks. self isIndexed ifFalse:[^fixedMap]. "If the receiver is indexed then we need to map the colors as well" self flag: #untested. ^ColorMap shifts: fixedMap shifts masks: fixedMap masks colors: (colors collect:[:pv| aColorMap pixelMap: pv]). ! ! !ColorMap methodsFor: 'pixel mapping' stamp: 'ar 1/16/2000 20:52'! pixelMap: pixelValue "Perform a reverse pixel mapping operation" | pv | colors == nil ifTrue:[pv _ pixelValue] ifFalse:[pv _ colors at: pixelValue]. (shifts == nil and:[masks == nil]) ifTrue:[^pv] ifFalse:[^(((pv bitAnd: self redMask) bitShift: self redShift) bitOr: ((pv bitAnd: self greenMask) bitShift: self greenShift)) bitOr: (((pv bitAnd: self blueMask) bitShift: self blueShift) bitOr: ((pv bitAnd: self alphaMask) bitShift: self alphaShift))]! ! !ColorMap methodsFor: 'private' stamp: 'ar 2/22/2000 16:47'! setShifts: shiftArray masks: maskArray colors: colorArray shiftArray ifNotNil:[shifts _ shiftArray asIntegerArray]. maskArray ifNotNil:[masks _ maskArray asWordArray]. colorArray ifNotNil:[colors _ colorArray asWordArray].! ! !ColorMap methodsFor: 'testing' stamp: 'ar 5/25/2000 19:41'! isColormap ^true! ! !ColorMap methodsFor: 'testing' stamp: 'ar 5/27/2000 19:06'! isFixed "Return true if the receiver does not use a lookup mechanism for pixel mapping" ^self isIndexed not! ! !ColorMap methodsFor: 'testing' stamp: 'ar 5/27/2000 19:06'! isIndexed "Return true if the receiver uses a lookup mechanism for pixel mapping" ^colors notNil! ! !ColorMap methodsFor: 'comparing' stamp: 'ar 5/27/2000 19:28'! = aColorMap "Return true if the receiver is equal to aColorMap" self species = aColorMap species ifFalse:[^false]. self isIndexed = aColorMap isIndexed ifFalse:[^false]. ^self colors = aColorMap colors and:[ self shifts = aColorMap shifts and:[ self masks = aColorMap masks]]! ! !ColorMap methodsFor: 'comparing' stamp: 'ar 5/27/2000 19:29'! hash "Hash is re-implemented because #= is re-implemented" ^colors hash bitXor: (shifts hash bitXor: masks hash)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorMap class instanceVariableNames: ''! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 2/22/2000 14:08'! colors: colorArray ^self new setShifts: nil masks: nil colors: colorArray! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 5/27/2000 20:09'! mapBitsFrom: srcBitMask to: dstBitMask "Return an array consisting of the shift and the mask for mapping component values out of srcBitMask and into dstBitMask. While this computation is somewhat complicated it eases the batch conversion of all the pixels in BitBlt." | srcBits dstBits srcLow srcHigh dstLow dstHigh bits mask shift | (srcBitMask = 0 or:[dstBitMask = 0]) ifTrue:[^#(0 0)]. "Zero mask and shift" "Compute low and high bit position for source and dest bit mask" srcLow _ srcBitMask lowBit - 1. srcHigh _ srcBitMask highBit. dstLow _ dstBitMask lowBit - 1. dstHigh _ dstBitMask highBit. "Compute the number of bits in source and dest bit mask" srcBits _ srcHigh - srcLow. dstBits _ dstHigh - dstLow. "Compute the maximum number of bits we can transfer inbetween" bits _ srcBits min: dstBits. "Compute the (unshifted) transfer mask" mask _ (1 bitShift: bits) - 1. "Shift the transfer mask to the mask the highest n bits of srcBitMask" mask _ mask bitShift: (srcHigh - bits). "Compute the delta shift so that the most significant bit of the source bit mask falls on the most significant bit of the dest bit mask. Note that delta is used for #bitShift: so shift > 0 : shift right shift < 0 : shift left e.g., if dstHigh > srcHigh we need to shift left and if dstHigh < srcHigh we need to shift right. This leads to:" shift _ dstHigh - srcHigh. "And that's all we need" ^Array with: shift with: mask! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 5/27/2000 19:41'! mappingFrom: srcBitMasks to: dstBitMasks "Return a color map mapping from the array of source bit masks to the array of dest bit masks." | shifts masks shiftAndMask | shifts _ IntegerArray new: 4. masks _ WordArray new: 4. 1 to: 4 do:[:i| shiftAndMask _ self mapBitsFrom: (srcBitMasks at: i) to: (dstBitMasks at: i). shifts at: i put: (shiftAndMask at: 1). masks at: i put: (shiftAndMask at: 2). ]. ^self shifts: shifts masks: masks! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 5/27/2000 20:08'! mappingFromARGB: dstBitMasks "Return a ColorMap mapping from canonical ARGB space into dstBitMasks" ^self mappingFrom: #(16rFF0000 16rFF00 16rFF 16rFF000000) to: dstBitMasks! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 5/27/2000 20:08'! mappingToARGB: srcBitMasks "Return a ColorMap mapping from srcBitMasks into canonical ARGB space" ^self mappingFrom: srcBitMasks to: #(16rFF0000 16rFF00 16rFF 16rFF000000)! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 1/16/2000 16:02'! shifts: shiftArray masks: maskArray ^self shifts: shiftArray masks: maskArray colors: nil.! ! !ColorMap class methodsFor: 'instance creation' stamp: 'ar 1/16/2000 16:02'! shifts: shiftArray masks: maskArray colors: colorArray ^self new setShifts: shiftArray masks: maskArray colors: colorArray! ! FormCanvas subclass: #ColorPatchCanvas instanceVariableNames: 'stopMorph foundMorph doStop ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !ColorPatchCanvas commentStamp: '' prior: 0! I generate patches of Morphic worlds that views below certain Morphs. This facility is used for the end-user scripting system.! !ColorPatchCanvas methodsFor: 'initialize-release' stamp: 'ar 6/22/1999 16:18'! reset "Initialize the receiver to act just as a FormCanvas" super reset. foundMorph _ false. doStop _ false. stopMorph _ nil.! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:15'! doStop ^doStop! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:15'! doStop: aBoolean doStop _ aBoolean! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:15'! foundMorph ^foundMorph! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:38'! foundMorph: aBoolean foundMorph _ aBoolean! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:14'! stopMorph ^stopMorph! ! !ColorPatchCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 16:14'! stopMorph: aMorph stopMorph _ aMorph! ! !ColorPatchCanvas methodsFor: 'drawing-general' stamp: 'ar 6/22/1999 16:14'! fullDrawMorph: aMorph (foundMorph and:[doStop]) ifTrue:[^self]. "Found it and should stop" aMorph == stopMorph ifTrue:[ "Never draw the stopMorph" foundMorph _ true. ^self]. ^super fullDrawMorph: aMorph.! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 16:34'! clipBy: aRectangle during: aBlock "Set a clipping rectangle active only during the execution of aBlock. Note: In the future we may want to have more general clip shapes - not just rectangles" | tempCanvas | tempCanvas _ (self copyClipRect: aRectangle). aBlock value: tempCanvas. foundMorph _ tempCanvas foundMorph.! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 16:42'! preserveStateDuring: aBlock "Preserve the full canvas state during the execution of aBlock. Note: This does *not* include the state in the receiver (e.g., foundMorph)." | tempCanvas | tempCanvas _ self copy. aBlock value: tempCanvas. foundMorph _ tempCanvas foundMorph.! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 2/17/2000 00:15'! transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize "Note: This method has been originally copied from TransformationMorph." | innerRect patchRect sourceQuad warp start subCanvas | (aDisplayTransform isPureTranslation) ifTrue:[ subCanvas _ self copyOffset: aDisplayTransform offset negated truncated clipRect: aClipRect. aBlock value: subCanvas. foundMorph _ subCanvas foundMorph. ^self ]. "Prepare an appropriate warp from patch to innerRect" innerRect _ aClipRect. patchRect _ aDisplayTransform globalBoundsToLocal: (self clipRect intersect: innerRect). sourceQuad _ (aDisplayTransform sourceQuadFor: innerRect) collect: [:p | p - patchRect topLeft]. warp _ self warpFrom: sourceQuad toRect: innerRect. warp cellSize: cellSize. "Render the submorphs visible in the clipping rectangle, as patchForm" start _ (self depth = 1 and: [self isShadowDrawing not]) "If this is true B&W, then we need a first pass for erasure." ifTrue: [1] ifFalse: [2]. start to: 2 do: [:i | "If i=1 we first make a shadow and erase it for opaque whites in B&W" subCanvas _ ColorPatchCanvas extent: patchRect extent depth: self depth. subCanvas stopMorph: stopMorph. subCanvas foundMorph: foundMorph. subCanvas doStop: doStop. i=1 ifTrue: [subCanvas shadowColor: Color black. warp combinationRule: Form erase] ifFalse: [self isShadowDrawing ifTrue: [subCanvas shadowColor: self shadowColor]. warp combinationRule: Form paint]. subCanvas translateBy: patchRect topLeft negated during:[:offsetCanvas| aBlock value: offsetCanvas]. i = 2 ifTrue:[foundMorph _ subCanvas foundMorph]. warp sourceForm: subCanvas form; warpBits. warp sourceForm: nil. subCanvas _ nil "release space for next loop"] ! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 16:39'! translateBy: delta clippingTo: aRectangle during: aBlock "Set a translation and clipping rectangle only during the execution of aBlock." | tempCanvas | tempCanvas _ self copyOffset: delta clipRect: aRectangle. aBlock value: tempCanvas. foundMorph _ tempCanvas foundMorph.! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 16:39'! translateBy: delta during: aBlock "Set a translation only during the execution of aBlock." | tempCanvas | tempCanvas _ self copyOffset: delta. aBlock value: tempCanvas. foundMorph _ tempCanvas foundMorph.! ! !ColorPatchCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 16:40'! translateTo: newOrigin clippingTo: aRectangle during: aBlock "Set a new origin and clipping rectangle only during the execution of aBlock." | tempCanvas | tempCanvas _ self copyOrigin: newOrigin clipRect: aRectangle. aBlock value: tempCanvas. foundMorph _ tempCanvas foundMorph.! ! !ColorPatchCanvas methodsFor: 'private' stamp: 'ar 6/22/1999 16:18'! setForm: aForm "Initialize the receiver to act just as a FormCanvas" super setForm: aForm. stopMorph _ nil. doStop _ false. foundMorph _ false.! ! SketchMorph subclass: #ColorPickerMorph instanceVariableNames: 'selectedColor sourceHand deleteOnMouseUp updateContinuously target selector argument originalColor theSelectorDisplayMorph command isModal clickedTranslucency ' classVariableNames: 'ColorChart DragBox FeedbackBox RevertBox TransText TransparentBox ' poolDictionaries: '' category: 'Morphic-Widgets'! !ColorPickerMorph methodsFor: 'initialization' stamp: 'di 9/28/2000 12:05'! buildChartForm | chartForm | chartForm _ ColorChart deepCopy asFormOfDepth: Display depth. chartForm fill: ((TransparentBox left + 9)@0 extent: 1@9) fillColor: Color lightGray. chartForm fill: ((TransparentBox right - 10)@0 extent: 1@9) fillColor: Color lightGray. TransText displayOn: chartForm at: 62@0. Display depth = 32 ifTrue: ["Set opaque bits for 32-bit display" chartForm fill: chartForm boundingBox rule: Form under fillColor: (Color r: 0.0 g: 0.0 b: 0.0 alpha: 1.0)]. chartForm borderWidth: 1. self form: chartForm. selectedColor ifNotNil: [self updateAlpha: selectedColor alpha]. self updateSelectorDisplay. ! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'sw 9/8/2000 18:14'! choseModalityFromPreference "Decide whether to be modal or not by consulting the prevailing preference" self initializeModal: Preferences modalColorPickers! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'RAA 2/1/2001 13:43'! initialize "Initialize the receiver. Obey the modalColorPickers preference when deciding how to configure myself. This is not quite satisfactory -- we'd like to have explicit calls tell us things like whether whether to be modal, whether to allow transparency, but for the moment, in grand Morphic fashion, this is rather inflexibly all housed right here" super initialize. self buildChartForm. selectedColor _ Color white. sourceHand _ nil. deleteOnMouseUp _ false. clickedTranslucency _ false. updateContinuously _ true. selector _ nil. target _ nil! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'di 9/27/2000 13:29'! initializeModal: beModal "Initialize the receiver. If beModal is true, it will be a modal color picker, else not" isModal _ beModal. self removeAllMorphs. isModal ifFalse: [theSelectorDisplayMorph _ AlignmentMorph newRow color: Color white; borderWidth: 1; borderColor: Color red; hResizing: #shrinkWrap; vResizing: #shrinkWrap; addMorph: (StringMorph contents: 'theSelector'). self addMorph: theSelectorDisplayMorph. self addMorph: (SimpleButtonMorph new borderWidth: 0; label: 'x' font: nil; color: Color transparent; actionSelector: #delete; target: self; useSquareCorners; position: self topLeft - (0@3); extent: 10@12; setCenteredBalloonText: 'dismiss color picker')]. self addMorph: ((Morph newBounds: (DragBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'put me somewhere'). self addMorph: ((Morph newBounds: (RevertBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'restore original color'). self addMorph: ((Morph newBounds: (FeedbackBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'shows selected color'). self addMorph: ((Morph newBounds: (TransparentBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'adjust translucency'). self buildChartForm. selectedColor ifNil: [selectedColor _ Color white]. sourceHand _ nil. deleteOnMouseUp _ false. updateContinuously _ true. ! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'sma 4/22/2000 19:39'! updateSelectorDisplay theSelectorDisplayMorph ifNil: [^self]. theSelectorDisplayMorph position: self bottomLeft. theSelectorDisplayMorph firstSubmorph contents: selector asString , ' ' , selectedColor printString! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'ar 6/25/1999 11:33'! argument ^argument! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'ar 6/25/1999 11:33'! argument: anObject argument _ anObject! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! deleteOnMouseUp ^ deleteOnMouseUp ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! deleteOnMouseUp: aBoolean deleteOnMouseUp _ aBoolean. ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'di 9/28/2000 11:47'! originalColor: colorOrSymbol "Set the receiver's original color. It is at this point that a command is launched to represent the action of the picker, in support of Undo." originalColor _ (colorOrSymbol isKindOf: Color) ifTrue: [colorOrSymbol] ifFalse: [Color lightGreen]. originalForm fill: RevertBox fillColor: originalColor. selectedColor _ originalColor. ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! selectedColor ^ selectedColor ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! selector ^ selector ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'di 8/30/2000 13:40'! selector: aSymbol "Set the selector to be associated with the receiver. Store it in the receiver's command, if appropriate" selector _ aSymbol. self updateSelectorDisplay! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! sourceHand ^ sourceHand ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! sourceHand: aHand sourceHand _ aHand. ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:15'! target ^ target ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'sw 3/8/1999 14:28'! target: anObject target _ anObject. (target respondsTo: #color) ifTrue: [selectedColor _ target color] ifFalse: [selectedColor _ Color white]. ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:46'! updateContinuously ^ updateContinuously ! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'jm 11/4/97 07:46'! updateContinuously: aBoolean updateContinuously _ aBoolean. ! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'jm 11/4/97 07:15'! handlesMouseDown: evt ^ true ! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'di 9/27/2000 13:19'! mouseDown: evt | localPt | localPt _ evt cursorPoint - self topLeft. self deleteAllBalloons. clickedTranslucency _ TransparentBox containsPoint: localPt. (DragBox containsPoint: localPt) ifTrue: [^ evt hand grabMorph: self]. (RevertBox containsPoint: localPt) ifTrue: [^ self updateColor: originalColor feedbackColor: originalColor]. self comeToFront. sourceHand _ evt hand. self startStepping. ! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'jm 11/4/97 07:46'! mouseUp: evt self stopStepping. sourceHand _ nil. deleteOnMouseUp ifTrue: [self delete]. self updateTargetColor. ! ! !ColorPickerMorph methodsFor: 'stepping' stamp: 'jm 11/4/97 07:15'! step sourceHand ifNotNil: [self pickColorAt: sourceHand position]. ! ! !ColorPickerMorph methodsFor: 'stepping' stamp: 'jm 11/4/97 07:15'! stepTime ^ 50 ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 9/27/2000 10:36'! anchorAndRunModeless: aHand "If user clicks on the drag-dot of a modal picker, anchor it, and change to modeless operation." aHand showTemporaryCursor: nil. "revert to normal cursor" self initializeModal: false; originalColor: originalColor. "reset as modeless" aHand flushEvents. "Drop any events gathered during modal loop" aHand position: Sensor cursorPoint; grabMorph: self. "Slip into drag operation" ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 8/30/2000 13:47'! argumentsWith: aColor "Return an argument array appropriate to this action selector" | nArgs | nArgs _ selector numArgs. nArgs = 1 ifTrue:[^ {aColor}]. nArgs = 2 ifTrue:[^ {aColor. sourceHand}]. nArgs = 3 ifTrue:[^ {aColor. argument. sourceHand}]. ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 9/27/2000 12:55'! deleteAllBalloons self submorphsDo: [:m | m deleteBalloon]. ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'ar 10/3/2000 17:05'! modalBalloonHelpAtPoint: cursorPoint self flag: #arNote. "Throw this away. There needs to be another way." self submorphsDo: [:m | m wantsBalloon ifTrue: [(m valueOfProperty: #balloon) == nil ifTrue: [(m containsPoint: cursorPoint) ifTrue: [m showBalloon: m balloonText]] ifFalse: [(m containsPoint: cursorPoint) ifFalse: [m deleteBalloon]]]]! ! !ColorPickerMorph methodsFor: 'private' stamp: 'RAA 2/1/2001 13:43'! pickColorAt: aGlobalPoint | alpha selfRelativePoint pickedColor | clickedTranslucency ifNil: [clickedTranslucency _ false]. selfRelativePoint _ (self globalPointToLocal: aGlobalPoint) - self topLeft. (FeedbackBox containsPoint: selfRelativePoint) ifTrue: [^ self]. (RevertBox containsPoint: selfRelativePoint) ifTrue: [^ self updateColor: originalColor feedbackColor: originalColor]. "check for transparent color and update using appropriate feedback color " (TransparentBox containsPoint: selfRelativePoint) ifTrue: [clickedTranslucency ifFalse: [^ self]. "Can't wander into translucency control" alpha _ (selfRelativePoint x - TransparentBox left - 10) asFloat / (TransparentBox width - 20) min: 1.0 max: 0.0. "(alpha roundTo: 0.01) printString , ' ' displayAt: 0@0." " -- debug" self updateColor: (selectedColor alpha: alpha) feedbackColor: (selectedColor alpha: alpha). ^ self]. "pick up color, either inside or outside this world" clickedTranslucency ifTrue: [^ self]. "Can't wander out of translucency control" pickedColor _ Display colorAt: aGlobalPoint. self updateColor: ( (selectedColor isColor and: [selectedColor isTranslucentColor]) ifTrue: [pickedColor alpha: selectedColor alpha] ifFalse: [pickedColor] ) feedbackColor: pickedColor! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 9/30/2000 10:07'! trackColorAt: aGlobalPoint "Before the mouse comes down in a modal color picker, track the color under the cursor, and show it in the feedback box, but do not make transparency changes" | selfRelativePoint pickedColor | selfRelativePoint _ (self globalPointToLocal: aGlobalPoint) - self topLeft. (FeedbackBox containsPoint: selfRelativePoint) ifTrue: [^ self]. (RevertBox containsPoint: selfRelativePoint) ifTrue: [^ self updateColor: originalColor feedbackColor: originalColor]. "check for transparent color and update using appropriate feedback color " (TransparentBox containsPoint: selfRelativePoint) ifTrue: [^ self]. "pick up color, either inside or outside this world" pickedColor _ Display colorAt: aGlobalPoint. self updateColor: (pickedColor alpha: originalColor alpha) feedbackColor: pickedColor! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 9/28/2000 11:10'! updateAlpha: alpha | sliderRect | sliderRect _ (TransparentBox left + 10)@1 corner: (TransparentBox right - 9)@9. originalForm fill: (sliderRect withRight: sliderRect left + (alpha*sliderRect width)) fillColor: Color lightGray. originalForm fillWhite: (sliderRect withLeft: sliderRect left + (alpha*sliderRect width)). originalForm fill: ((TransparentBox right - 9)@1 extent: 8@8) fillColor: (alpha < 1.0 ifTrue: [Color white] ifFalse: [Color lightGray]). TransText displayOn: originalForm at: 62@1 rule: Form paint. ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 9/28/2000 10:38'! updateColor: aColor feedbackColor: feedbackColor "Set my selected color to the given color if it is different. Give user feedback. Inform the target of the change if the target and selector are not nil." selectedColor = aColor ifTrue: [^ self]. "do nothing if color doesn't change" self updateAlpha: aColor alpha. originalForm fill: FeedbackBox fillColor: feedbackColor. self form: originalForm. selectedColor _ aColor. updateContinuously ifTrue: [self updateTargetColor]. ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'LC 2/2/2000 03:17'! updateTargetColor | nArgs | (target ~~ nil and: [selector ~~ nil]) ifTrue: [self updateSelectorDisplay. nArgs _ selector numArgs. nArgs = 1 ifTrue:[^target perform: selector with: selectedColor]. nArgs = 2 ifTrue:[^target perform: selector with: selectedColor with: sourceHand]. nArgs = 3 ifTrue:[^target perform: selector with: selectedColor with: argument with: sourceHand]]. ! ! !ColorPickerMorph methodsFor: 'private' stamp: 'di 8/30/2000 13:47'! updateTargetColorWith: aColor "Update the target so that it reflects aColor as the color choice" (target ~~ nil and: [selector ~~ nil]) ifTrue: [self updateSelectorDisplay. ^ target perform: selector withArguments: (self argumentsWith: aColor)] ! ! !ColorPickerMorph methodsFor: 'menu' stamp: 'jm 11/4/97 07:46'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. deleteOnMouseUp ifTrue: [aCustomMenu add: 'stay up' action: #toggleDeleteOnMouseUp] ifFalse: [aCustomMenu add: 'do not stay up' action: #toggleDeleteOnMouseUp]. updateContinuously ifTrue: [aCustomMenu add: 'update only at end' action: #toggleUpdateContinuously] ifFalse: [aCustomMenu add: 'update continuously' action: #toggleUpdateContinuously]. ! ! !ColorPickerMorph methodsFor: 'menu' stamp: 'di 9/28/2000 11:44'! pickUpColorFor: aMorph "Show the eyedropper cursor, and modally track the mouse through a mouse-down and mouse-up cycle" | aHand localPt | aHand _ aMorph ifNil: [self activeHand] ifNotNil: [aMorph activeHand]. aHand ifNil: [aHand _ self currentHand]. self addToWorld: aHand world near: (aMorph ifNil: [aHand world]) fullBounds. self owner ifNil: [^ self]. aHand showTemporaryCursor: (ScriptingSystem formAtKey: #Eyedropper) hotSpotOffset: 6 negated @ 4 negated. "<<<< the form was changed a bit??" self updateContinuously: false. [Sensor anyButtonPressed] whileFalse: [self trackColorUnderMouse]. self deleteAllBalloons. (DragBox containsPoint: (localPt _ Sensor cursorPoint - self topLeft)) ifTrue: ["Click or drag the drag-dot means to anchor as a modeless picker" ^ self anchorAndRunModeless: aHand]. (clickedTranslucency _ TransparentBox containsPoint: localPt) ifTrue: [selectedColor _ originalColor]. self updateContinuously: true. [Sensor anyButtonPressed] whileTrue: [self updateTargetColorWith: self indicateColorUnderMouse]. aHand newMouseFocus: nil; showTemporaryCursor: nil; flushEvents. self delete. ! ! !ColorPickerMorph methodsFor: 'menu' stamp: 'jm 11/4/97 07:46'! toggleDeleteOnMouseUp deleteOnMouseUp _ deleteOnMouseUp not. ! ! !ColorPickerMorph methodsFor: 'menu' stamp: 'jm 11/4/97 07:46'! toggleUpdateContinuously updateContinuously _ updateContinuously not. ! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 11/27/1999 09:12'! addToWorld: world near: box | goodLocation | goodLocation _ self bestPositionNear: box inWorld: world. world allMorphsDo: [:p | (p isMemberOf: ColorPickerMorph) ifTrue: [(p ~~ self and: [p owner notNil and: [p target == target]]) ifTrue: [(p selector == selector and: [p argument == argument]) ifTrue: [^ p comeToFront "uncover existing picker"] ifFalse: ["place second picker relative to first" goodLocation _ self bestPositionNear: p bounds inWorld: world]]]]. self position: goodLocation. world addMorphFront: self. self changed ! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 11/27/1999 08:51'! bestPositionNear: box inWorld: world | points b | points _ #(topCenter rightCenter bottomCenter leftCenter). "possible anchors" 1 to: 4 do: [:i | "Try the four obvious anchor points" b _ self bounds align: (self bounds perform: (points at: i)) with: (box perform: (points atWrap: i + 2)). (world viewBox containsRect: b) ifTrue: [^ b topLeft" Yes, it fits"]]. ^ 20@20 "when all else fails" ! ! !ColorPickerMorph methodsFor: 'other' stamp: 'LC 2/2/2000 04:28'! containsPoint: aPoint ^ (super containsPoint: aPoint) or: [RevertBox containsPoint: aPoint - self topLeft]! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 9/12/2000 10:00'! delete "The moment of departure has come. If the receiver has an affiliated command, finalize it and have the system remember it. In any case, delete the receiver" self rememberCommand: (Command new cmdWording: 'color change'; undoTarget: target selector: selector arguments: (self argumentsWith: originalColor); redoTarget: target selector: selector arguments: (self argumentsWith: selectedColor)). super delete! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 9/3/1999 13:34'! drawOn: aCanvas aCanvas depth = 1 ifTrue: [aCanvas fillRectangle: self bounds color: Color white]. Display depth = originalForm depth ifFalse: [self buildChartForm]. super drawOn: aCanvas! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 9/25/2000 15:38'! indicateColorUnderMouse "Track the mouse with the special eyedropper cursor, and accept whatever color is under the mouse as the currently-chosen color; reflect that choice in the feedback box, and return that color." | pt | self pickColorAt: (pt _ Sensor cursorPoint). isModal ifTrue: [self activeHand position: pt. self world displayWorldSafely; runStepMethods]. ^ selectedColor ! ! !ColorPickerMorph methodsFor: 'other' stamp: 'sw 7/6/1999 09:00'! isCandidateForAutomaticViewing ^ false! ! !ColorPickerMorph methodsFor: 'other' stamp: 'sw 7/6/1999 09:07'! isLikelyRecipientForMouseOverHalos ^ false! ! !ColorPickerMorph methodsFor: 'other' stamp: 'ar 12/8/2000 15:32'! putUpFor: aMorph near: aRectangle "Put the receiver up on the screen. Note highly variant behavior depending on the setting of the #modalColorPickers preference" | layerNumber | aMorph isMorph ifTrue: [ layerNumber _ aMorph morphicLayerNumber. aMorph allOwnersDo:[:m| layerNumber _ layerNumber min: m morphicLayerNumber]. self setProperty: #morphicLayerNumber toValue: layerNumber - 0.1 ]. isModal == true "backward compatibility" ifTrue: [self pickUpColorFor: aMorph] ifFalse: [self addToWorld: ((aMorph notNil and: [aMorph world notNil]) ifTrue: [aMorph world] ifFalse: [self currentWorld]) near: (aRectangle ifNil: [aMorph ifNil: [100@100 extent: 1@1] ifNotNil: [aMorph fullBoundsInWorld]])]! ! !ColorPickerMorph methodsFor: 'other' stamp: 'di 9/27/2000 11:48'! trackColorUnderMouse "Track the mouse with the special eyedropper cursor, and accept whatever color is under the mouse as the currently-chosen color; reflect that choice in the feedback box, and return that color." | pt | selectedColor _ originalColor. self trackColorAt: (pt _ Sensor cursorPoint). isModal ifTrue: [self activeHand position: pt. self world displayWorldSafely; runStepMethods. self modalBalloonHelpAtPoint: pt]. ^ selectedColor ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorPickerMorph class instanceVariableNames: ''! !ColorPickerMorph class methodsFor: 'as yet unclassified' stamp: 'di 9/28/2000 11:09'! initialize "ColorPickerMorph initialize" ColorChart _ Color colorPaletteForDepth: 16 extent: 190@60. DragBox _ (11@0) extent: 9@8. RevertBox _ (ColorChart width - 20)@1 extent: 9@8. FeedbackBox _ (ColorChart width - 10)@1 extent: 9@8. TransparentBox _ DragBox topRight corner: RevertBox bottomLeft. ColorChart fillBlack: ((DragBox left - 1)@0 extent: 1@9). ColorChart fillBlack: ((TransparentBox left)@0 extent: 1@9). ColorChart fillBlack: ((FeedbackBox left - 1)@0 extent: 1@9). ColorChart fillBlack: ((RevertBox left - 1)@0 extent: 1@9). (Form dotOfSize: 5) displayOn: ColorChart at: DragBox center + (0@1). TransText _ (Form extent: 63@8 depth: 1 "Where there's a will there's a way..." fromArray: #(4194306 1024 4194306 1024 15628058 2476592640 4887714 2485462016 1883804850 2486772764 4756618 2485462016 4748474 1939416064 0 0) offset: 0@0). TransText _ ColorForm mappingWhiteToTransparentFrom: TransText ! ! !ColorPickerMorph class methodsFor: 'as yet unclassified' stamp: 'sw 10/27/1999 11:40'! perniciousBorderColor "Answer the color of the border lines of a color picker; this color gets reported as you drag the mouse through from the translucent box to the true color area, for example, and can cause some difficulties in some special cases, so it is faithfully reported here in this hard-coded fashion in order that energetic clients wishing to handle it as special-case it can do so." ^ Color r: 0.0 g: 0.0 b: 0.032! ! ColorTileMorph subclass: #ColorSeerTile instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !ColorSeerTile methodsFor: 'initialization' stamp: 'sw 9/17/1999 08:02'! initialize | m1 m2 desiredW | super initialize. self removeAllMorphs. "get rid of the parts of a regular Color tile" type _ #operator. operatorOrExpression _ #color:sees:. m1 _ StringMorph contents: 'color sees' font: ScriptingSystem fontForTiles. m2 _ Morph new extent: 12@8; color: (Color r: 0.8 g: 0 b: 0). desiredW _ m1 width + 6. self extent: (desiredW max: self basicWidth) @ self class defaultH. m1 position: (bounds center x - (m1 width // 2)) @ (bounds top + 5). m2 position: (bounds center x - (m2 width // 2) + 3) @ (bounds top + 8). self addMorph: m1; addMorphFront: m2. colorSwatch _ m2. ! ! !ColorSeerTile methodsFor: 'code generation' stamp: 'jm 5/28/1998 19:19'! storeCodeOn: aStream indent: tabCount "We have a hidden arg. Output two keywords with interspersed arguments." | parts | parts _ operatorOrExpression keywords. "color:sees:" ^ aStream nextPutAll: (parts at: 1); space; nextPutAll: colorSwatch color printString; space; nextPutAll: (parts at: 2). ! ! !ColorSeerTile methodsFor: 'display' stamp: 'tk 12/3/97 09:51'! updateLiteralLabel "Do nothing"! ! UpdatingRectangleMorph subclass: #ColorSwatch instanceVariableNames: 'argument ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Support'! !ColorSwatch methodsFor: 'as yet unclassified' stamp: 'sw 7/13/1999 13:39'! argument: arg argument _ arg! ! !ColorSwatch methodsFor: 'as yet unclassified' stamp: 'sw 7/13/1999 15:09'! readFromTarget | v | ((target == nil) or: [getSelector == nil]) ifTrue: [^ contents]. v _ target scriptPerformer perform: getSelector with: argument. lastValue _ v. ^ v ! ! !ColorSwatch methodsFor: 'as yet unclassified' stamp: 'sw 7/13/1999 13:48'! setTargetColor: aColor putSelector ifNotNil: [self color: aColor. contents _ aColor. target scriptPerformer perform: self putSelector withArguments: (Array with: argument with: aColor)] ! ! !ColorSwatch methodsFor: 'as yet unclassified' stamp: 'sw 7/13/1999 18:39'! stepTime ^ 1000! ! StandardSystemView subclass: #ColorSystemView instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Support'! !ColorSystemView methodsFor: 'as yet unclassified'! cacheBitsAsTwoTone ^ false! ! !ColorSystemView methodsFor: 'as yet unclassified' stamp: 'di 2/26/98 08:58'! displayDeEmphasized "Display this view with emphasis off. If windowBits is not nil, then simply BLT if possible." bitsValid ifTrue: [self lock. windowBits displayAt: self windowOrigin] ifFalse: [super displayDeEmphasized] ! ! TileMorph subclass: #ColorTileMorph instanceVariableNames: 'colorSwatch ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !ColorTileMorph methodsFor: 'initialization'! initialize super initialize. type _ #literal. self addColorSwatch. ! ! !ColorTileMorph methodsFor: 'events'! handlesMouseDown: evt (colorSwatch containsPoint: evt cursorPoint) ifTrue: [^ true] ifFalse: [^ super handlesMouseDown: evt]. ! ! !ColorTileMorph methodsFor: 'events'! mouseDown: evt (colorSwatch containsPoint: evt cursorPoint) ifFalse: [super mouseDown: evt]. ! ! !ColorTileMorph methodsFor: 'events' stamp: 'ar 10/5/2000 18:51'! mouseUp: evt self changeColorTarget: colorSwatch selector: #userSelectedColor: originalColor: colorSwatch color hand: evt hand! ! !ColorTileMorph methodsFor: 'other' stamp: 'sw 12/7/1999 18:51'! addColorSwatch | m1 m2 desiredW | m1 _ StringMorph contents: 'color' font: (StrikeFont familyName: #NewYork size: 12). m2 _ Morph new extent: 12@8; color: (Color r: 0.8 g: 0 b: 0). desiredW _ m1 width + 6. self extent: (desiredW max: self basicWidth) @ self class defaultH. m1 position: (bounds center x - (m1 width // 2)) @ (bounds top + 1). m2 position: (bounds center x - (m2 width // 2)) @ (m1 bottom - 1). self addMorph: m1; addMorph: m2. colorSwatch _ m2! ! !ColorTileMorph methodsFor: 'other' stamp: 'sw 10/25/1998 00:25'! colorSwatch ^ colorSwatch! ! !ColorTileMorph methodsFor: 'other' stamp: 'sw 6/10/1998 17:49'! colorSwatchColor: aColor colorSwatch color: aColor! ! !ColorTileMorph methodsFor: 'other' stamp: 'jm 6/25/97 17:38'! resultType ^ #color! ! !ColorTileMorph methodsFor: 'other' stamp: 'sw 1/6/1999 10:41'! setLiteral: aLiteral colorSwatch color: aLiteral! ! !ColorTileMorph methodsFor: 'other' stamp: 'jm 5/28/1998 19:02'! storeCodeOn: aStream indent: tabCount aStream nextPutAll: colorSwatch color printString. ! ! !ColorTileMorph methodsFor: 'other' stamp: 'sw 1/6/1999 10:43'! updateLiteralLabel "Do nothing"! ! StringHolder subclass: #ColumnsTester instanceVariableNames: 'mainIndex listMorph theList smallTest ' classVariableNames: '' poolDictionaries: '' category: 'sbw-experiments'! !ColumnsTester commentStamp: '' prior: 0! A lot of code in here was cloned from ChatNotes. This class is a tester for the PluggableMultiColumnListMorph. See the instance method #listArray for two ways you can exercise this widget. To use it just evaluate the #test class method.! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:17'! addToTestList | entry | entry _ self queryForNewEntry. entry isNil ifTrue: [^ nil]. 1 to: entry size do: [:index | (theList at: index) add: (entry at: index)]. self changed: #listArray! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:18'! bigList ^ Array with: Smalltalk classNames asOrderedCollection with: Smalltalk classNames reversed asOrderedCollection with: Smalltalk classNames asSet asArray asOrderedCollection with: (Smalltalk classNames collect: [:each | each size printString]) asOrderedCollection! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 11/30/2000 22:14'! defaultBackgroundColor "In a better design, this would be handled by preferences." ^ Color r: 1.0 g: 0.9 b: 0.8! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 11/30/2000 22:15'! initialExtent ^ 300 @ 200! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:01'! inspectList theList inspect! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:01'! inspectListMorph listMorph inspect! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:02'! inspectMe self inspect! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:01'! listArray "Use #bigList here for a large four column list amd #smallList for a short three column test." theList == nil ifTrue: [self populateInitialList]. ^ theList! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:02'! listMenu: aMenu ^ aMenu labels: 'add remove inspect me inspect list inspect list morph' lines: #(2 ) selections: #(#addToTestList #removeFromTestList #inspectMe #inspectList inspectListMorph)! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 12/25/2000 22:29'! mainList ^ #('One' 'Two' 'Three Little Pigs Went To Market' 'Fourteen' 'Linux')! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 11/30/2000 22:21'! mainListIndex mainIndex == nil ifTrue: [mainIndex _ 0]. ^mainIndex! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 11/30/2000 22:23'! mainListIndex: index mainIndex _ index. self changed: #mainListIndex! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 12/25/2000 22:29'! minorList ^ #(1.2 3.4 5.6 7.8 0.006) collect: [:n | n printString]! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 06:56'! openAsMorph | window | window _ (SystemWindow labelled: 'Columns Tester') model: self. listMorph _ PluggableMultiColumnListMorph on: self list: #listArray selected: #mainListIndex changeSelected: #mainListIndex: menu: #listMenu:. window addMorph: (listMorph autoDeselect: false) frame: (0 @ 0 corner: 1.0 @ 1.0). window openInWorld! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:25'! populateInitialList "Use #bigList here for a large four column list amd #smallList for a short three column test." smallTest == nil ifTrue: [smallTest _ true]. theList _ (smallTest ifTrue: [self smallList] ifFalse: [self bigList]) asOrderedCollection! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:06'! queryColumnEntry: column default: defaultString ^FillInTheBlank request: 'New entry for column ', column printString, '?' initialAnswer: defaultString! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:10'! queryForNewEntry | array entry | array _ Array new: theList size. 1 to: array size do: [:column | entry _ self queryColumnEntry: column default: column printString. entry isEmpty ifTrue: [^nil]. array at: column put: entry]. ^array ! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 2/1/2001 10:59'! removeFromTestList mainIndex = 0 ifTrue: [^ self]. (self confirm: 'Really delete row ' , mainIndex printString , '?') ifFalse: [^ self]. 1 to: theList size do: [:index | (theList at: index) removeAt: mainIndex]. self changed: #listArray. self mainListIndex: 0! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 12/25/2000 22:29'! secondList ^ #('aaaa' 'bbbb' 'cccc' 'dddd' 'linux')! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:22'! setBigTest smallTest _ false! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:21'! setSmallTest smallTest _ true! ! !ColumnsTester methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:18'! smallList ^ Array with: self mainList asOrderedCollection with: self secondList asOrderedCollection with: self minorList asOrderedCollection! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColumnsTester class instanceVariableNames: ''! !ColumnsTester class methodsFor: 'as yet unclassified' stamp: 'sbw 11/30/2000 22:13'! openAsMorph ^ self new openAsMorph! ! !ColumnsTester class methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:24'! test "(Smalltalk at: #ColumnsTester) test" self testSmall! ! !ColumnsTester class methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:23'! testBig "(Smalltalk at: #ColumnsTester) testBig" | model | model _ self new. model setBigTest. model openAsMorph! ! !ColumnsTester class methodsFor: 'as yet unclassified' stamp: 'sbw 1/31/2001 07:23'! testSmall "(Smalltalk at: #ColumnsTester) testSmall" | model | model _ self new. model setSmallTest. model openAsMorph! ! Object subclass: #Command instanceVariableNames: 'phase cmdWording undoTarget undoSelector undoArguments redoTarget redoSelector redoArguments parameters ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Undo'! !Command commentStamp: '' prior: 0! An object representing an undoable command to be done in the environment. Structure: phase indicates whether the cmd is current in undone or redone mode cmdWording The wording of the command (used in arming the "undo"/"redo" menu items parameters an IdentityDictionary /NOT USED/ undoTarget Receiver, selector and arguments to accomplish undo undoSelector undoArguments redoTarget Receiver, selector and arguments to accomplish redo redoSelector redoArguments To use this, for any command you wish to use, you * Create an instance of Command, as follows... cmd _ Command new cmdWording: 'resizing'. * Give the the command undo state and redo state, as follows... cmd undoTarget: target selector: #extent: argument: oldExtent. cmd redoTarget: target selector: #extent: argument: newExtent. * Send a message of the form Command rememberCommand: cmd LastCommand is the last command that was actually done or undone. CommandHistory, applicable only when infiniteUndo is set, holds a 'tape' of the complete history of commands, as far back as it's possible to go. CommandExcursions, also applicable only in the infiniteUndo case, and rather at the fringe even then, holds segments of former CommandHistory that have been lopped off because of variant paths taken.! !Command methodsFor: 'initialization' stamp: 'sw 8/29/2000 14:12'! cmdWording: wrd "Set the wording to be used in a menu item referring to the receiver" cmdWording _ wrd! ! !Command methodsFor: 'initialization' stamp: 'sw 8/29/2000 14:13'! phase: aPhase "Set the phase of the command to the supplied symbol" phase _ aPhase! ! !Command methodsFor: 'initialization' stamp: 'di 8/30/2000 14:09'! printOn: aStream "Provide more detailed info about the receiver, put in for debugging, maybe should be removed" super printOn: aStream. aStream nextPutAll: ' phase: ', phase printString. cmdWording ifNotNil: [aStream nextPutAll: '; ', cmdWording asString]. parameters ifNotNil: [parameters associationsDo: [:assoc | aStream nextPutAll: ': ', assoc printString]]! ! !Command methodsFor: 'initialization' stamp: 'di 8/30/2000 13:04'! redoTarget: target selector: aSymbol argument: argument ^ self redoTarget: target selector: aSymbol arguments: {argument}! ! !Command methodsFor: 'initialization' stamp: 'di 8/30/2000 20:53'! redoTarget: target selector: selector arguments: arguments "Give target morph a chance to refine its undo operation" target refineRedoTarget: target selector: selector arguments: arguments in: [:rTarget :rSelector :rArguments | redoTarget _ rTarget. redoSelector _ rSelector. redoArguments _ rArguments]! ! !Command methodsFor: 'initialization' stamp: 'di 8/30/2000 13:04'! undoTarget: target selector: aSymbol argument: argument ^ self undoTarget: target selector: aSymbol arguments: {argument}! ! !Command methodsFor: 'initialization' stamp: 'di 8/30/2000 20:53'! undoTarget: target selector: selector arguments: arguments "Give target morph a chance to refine its undo operation" target refineUndoTarget: target selector: selector arguments: arguments in: [:rTarget :rSelector :rArguments | undoTarget _ rTarget. undoSelector _ rSelector. undoArguments _ rArguments]! ! !Command methodsFor: 'parameters' stamp: 'sw 8/29/2000 14:12'! parameterAt: aSymbol "Answer the parameter stored at the given symbol, or nil if none" ^ self parameterAt: aSymbol ifAbsent: [nil]! ! !Command methodsFor: 'parameters' stamp: 'sw 8/29/2000 14:12'! parameterAt: aSymbol ifAbsent: aBlock "Answer the parameter stored at the aSymbol, but if none, return the result of evaluating aBlock" ^ self assuredParameterDictionary at: aSymbol ifAbsent: [aBlock value]! ! !Command methodsFor: 'parameters' stamp: 'sw 8/29/2000 14:12'! parameterAt: aSymbol put: aValue "Place aValue in the parameters dictionary using aSymbol as key" ^ self assuredParameterDictionary at: aSymbol put: aValue! ! !Command methodsFor: 'command execution' stamp: 'di 8/30/2000 16:04'! doCommand "Do the command represented by the receiver. Not actually called by active current code, but reachable by the not-yet-unsealed promoteToCurrent: action." redoTarget ifNotNil: [redoTarget perform: redoSelector withArguments: redoArguments]! ! !Command methodsFor: 'command execution' stamp: 'di 8/30/2000 16:04'! redoCommand "Perform the 'redo' operation" redoTarget ifNotNil: [redoTarget perform: redoSelector withArguments: redoArguments]! ! !Command methodsFor: 'command execution' stamp: 'di 8/30/2000 16:02'! undoCommand "Perform the 'undo' operation" undoTarget ifNotNil: [undoTarget perform: undoSelector withArguments: undoArguments]! ! !Command methodsFor: 'private' stamp: 'sw 8/29/2000 14:09'! assuredParameterDictionary "Private!! Answer the parameters dictionary, creating it if necessary" ^ parameters ifNil: [parameters _ IdentityDictionary new]! ! !Command methodsFor: 'private' stamp: 'sw 8/29/2000 14:12'! cmdWording "Answer the wording to be used to refer to the command in a menu" ^ cmdWording ifNil: ['last command']! ! !Command methodsFor: 'private' stamp: 'sw 8/29/2000 14:13'! phase "Answer the phase of the command" ^ phase! ! !Command methodsFor: 'private' stamp: 'di 12/12/2000 12:36'! undoTarget ^ undoTarget! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Command class instanceVariableNames: ''! !Command class methodsFor: 'class initialization' stamp: 'RAA 9/21/2000 14:02'! zapObsolete "Command zapObsolete" "kill some obsolete stuff still retained by the CompiledMethods in change records" | before after histories lastCmd histCount lastCount | Smalltalk garbageCollect. before _ Command allInstances size. histories _ Association allInstances select: [ :each | each key == #CommandHistory and: [ (each value isKindOf: OrderedCollection) and: [ each value isEmpty not and: [ each value first isKindOf: Command]]] ]. histCount _ histories size. lastCmd _ Association allInstances select: [ :each | each key == #LastCommand and: [each value isKindOf: Command] ]. lastCount _ lastCmd size. histories do: [ :each | each value: OrderedCollection new]. lastCmd do: [ :each | each value: Command new]. Smalltalk garbageCollect. Smalltalk garbageCollect. after _ Command allInstances size. Transcript show: {before. after. histCount. histories. lastCount. lastCmd} printString; cr; cr. ! ! !Command class methodsFor: 'dog simple ui' stamp: 'ar 8/31/2000 23:13'! redoEnabled | w | ^(w _ self currentWorld) == nil ifTrue:[false] ifFalse:[w commandHistory redoEnabled]! ! !Command class methodsFor: 'dog simple ui' stamp: 'ar 8/31/2000 23:13'! redoNextCommand | w | ^(w _ self currentWorld) == nil ifFalse:[w commandHistory redoNextCommand]! ! !Command class methodsFor: 'dog simple ui' stamp: 'ar 8/31/2000 23:13'! undoEnabled | w | ^(w _ self currentWorld) == nil ifTrue:[false] ifFalse:[w commandHistory undoEnabled]! ! !Command class methodsFor: 'dog simple ui' stamp: 'ar 8/31/2000 23:14'! undoLastCommand | w | ^(w _ self currentWorld) == nil ifFalse:[w commandHistory undoLastCommand]! ! !Command class methodsFor: 'dog simple ui' stamp: 'ar 11/9/2000 20:38'! undoRedoButtons "Answer a morph that offers undo and redo buttons" | aButton wrapper | "self currentHand attachMorph: Command undoRedoButtons" wrapper _ AlignmentMorph newColumn. wrapper color: Color veryVeryLightGray lighter; borderWidth: 0; layoutInset: 0; vResizing: #shrinkWrap; hResizing: #shrinkWrap. #((CrudeUndo undoLastCommand 'undo last command done' undoEnabled CrudeUndoDisabled CrudeUndoDisabled) (CrudeRedo redoNextCommand 'redo last undone command' redoEnabled CrudeRedoDisabled CrudeRedoDisabled)) do: [:tuple | wrapper addTransparentSpacerOfSize: (8@0). aButton _ UpdatingThreePhaseButtonMorph new. aButton onImage: (ScriptingSystem formAtKey: tuple first); offImage: (ScriptingSystem formAtKey: tuple fifth); pressedImage: (ScriptingSystem formAtKey: tuple sixth); getSelector: tuple fourth; color: Color transparent; target: self; actionSelector: tuple second; setNameTo: tuple second; setBalloonText: tuple third; extent: aButton onImage extent. wrapper addMorphBack: aButton. wrapper addTransparentSpacerOfSize: (8@0)]. ^ wrapper! ! Object subclass: #CommandHistory instanceVariableNames: 'lastCommand history excursions ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Undo'! !CommandHistory methodsFor: 'initialize' stamp: 'ar 8/31/2000 22:50'! initialize lastCommand _ nil. history _ OrderedCollection new. excursions _ OrderedCollection new.! ! !CommandHistory methodsFor: 'menu' stamp: 'ar 8/31/2000 22:41'! nextCommandToUndo | anIndex | lastCommand ifNil: [^ nil]. lastCommand phase == #done ifTrue: [^ lastCommand]. (lastCommand phase == #undone and: [(anIndex _ history indexOf: lastCommand) > 1]) ifTrue: [^ history at: anIndex - 1] ifFalse: [^ nil]! ! !CommandHistory methodsFor: 'menu' stamp: 'ar 8/31/2000 22:39'! redoEnabled "Answer whether the redo command is currently available" ^ self nextCommand notNil! ! !CommandHistory methodsFor: 'menu' stamp: 'ar 8/31/2000 22:39'! redoMenuWording "Answer the wording to be used in a menu offering the current Redo command" | nextCommand | ((nextCommand _ self nextCommand) == nil or: [Preferences useUndo not]) ifTrue: [^ 'can''t redo']. ^ String streamContents: [:aStream | aStream nextPutAll: 'redo "'. aStream nextPutAll: (nextCommand cmdWording truncateTo: 12). aStream nextPut: $". lastCommand phase == #done ifFalse: [aStream nextPutAll: ' (z)']]! ! !CommandHistory methodsFor: 'menu' stamp: 'ar 8/31/2000 22:40'! undoEnabled "Answer whether there is an undoable command at the ready" ^ lastCommand notNil! ! !CommandHistory methodsFor: 'menu' stamp: 'sw 9/1/2000 11:00'! undoMenuWording "Answer the wording to be used in an 'undo' menu item" (((lastCommand == nil or: [Preferences useUndo not]) or: [Preferences infiniteUndo not and: [lastCommand phase == #undone]]) or: [self nextCommandToUndo == nil]) ifTrue: [^ 'can''t undo']. ^ String streamContents: [:aStream | aStream nextPutAll: 'undo "'. aStream nextPutAll: (self nextCommandToUndo cmdWording truncateTo: 12). aStream nextPut: $". lastCommand phase == #done ifTrue: [aStream nextPutAll: ' (z)']]! ! !CommandHistory methodsFor: 'menu' stamp: 'ar 8/31/2000 22:40'! undoOrRedoMenuWording "Answer the wording to be used in a menu item offering undo/redo" | pre | lastCommand ifNil: [^ 'can''t undo']. pre _ lastCommand phase == #done ifTrue: ['undo'] ifFalse: ['redo']. ^ ((pre, ' ', lastCommand cmdWording) truncateTo: 30), ' (z)'! ! !CommandHistory methodsFor: 'command history' stamp: 'ar 8/31/2000 22:44'! historyIndexOfLastCommand "Answer which position of the CommandHistory list is occupied by the LastCommand" ^ history indexOf: lastCommand ifAbsent: [0]! ! !CommandHistory methodsFor: 'command history' stamp: 'ar 8/31/2000 22:45'! lastCommand "Answer the last command done or undone" ^ lastCommand! ! !CommandHistory methodsFor: 'command history' stamp: 'ar 8/31/2000 22:45'! nextCommand "Answer the command object that would be sent the #redoCommand message if the user were to request Redo, or nil if none" | anIndex | lastCommand ifNil: [^ nil]. lastCommand phase == #undone ifTrue: [^ lastCommand]. anIndex _ history indexOf: lastCommand ifAbsent: [^ nil]. ^ anIndex = history size ifTrue: [nil] ifFalse: [history at: (anIndex + 1)]! ! !CommandHistory methodsFor: 'command history' stamp: 'di 12/12/2000 13:46'! resetCommandHistory "CommandHistory allInstancesDo: [:ch | ch resetCommandHistory]" "Clear out the command history so that no commands are held" lastCommand _ nil. history _ OrderedCollection new.! ! !CommandHistory methodsFor: 'called by programmer' stamp: 'ar 8/31/2000 22:46'! cantUndo "Called by client to indicate that the prior undoable command is no longer undoable" lastCommand _ nil. history _ OrderedCollection new.! ! !CommandHistory methodsFor: 'called by programmer' stamp: 'ar 8/31/2000 22:47'! promoteToCurrent: aCommand "Very unusual and speculative and unfinished!!. Not currently reachable. For the real thing, we presumably march forward or backward from the current command pointer to the target command in an orderly fashion, doing or undoing each command in turn." | itsIndex | Preferences useUndo ifFalse: [^ self]. itsIndex _ history indexOf: aCommand ifAbsent: [nil]. itsIndex ifNotNil: [history remove: aCommand ifAbsent: []]. history add: (lastCommand _ aCommand). itsIndex < history size ifTrue: [excursions add: (history copyFrom: (itsIndex to: history size))]. history _ (history copyFrom: 1 to: itsIndex) copyWith: aCommand. lastCommand _ aCommand. aCommand doCommand. lastCommand phase: #done.! ! !CommandHistory methodsFor: 'called by programmer' stamp: 'di 12/12/2000 13:08'! purgeAllCommandsSuchThat: cmdBlock "Remove a bunch of commands, as in [:cmd | cmd undoTarget == zort]" Preferences useUndo ifFalse: [^ self]. history _ history reject: cmdBlock. history isEmpty ifTrue: [lastCommand _ nil] ifFalse: [lastCommand _ history last]! ! !CommandHistory methodsFor: 'called by programmer' stamp: 'di 12/12/2000 10:16'! rememberCommand: aCommand "Make the supplied command be the 'LastCommand', and mark it 'done'" | currentCommandIndex | Preferences useUndo ifFalse: [^ self]. "Command initialize" Preferences infiniteUndo ifTrue: [currentCommandIndex _ history indexOf: lastCommand. ((currentCommandIndex < history size) and: [Preferences preserveCommandExcursions]) ifTrue: [excursions add: (history copyFrom: (currentCommandIndex to: history size)). history _ history copyFrom: 1 to: currentCommandIndex]. history addLast: aCommand]. lastCommand _ aCommand. lastCommand phase: #done.! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'ar 8/31/2000 22:49'! commandToUndo "Undo the last command, i.e. move backward in the recent-commands tape, if possible." | anIndex | lastCommand ifNil: [^ nil]. lastCommand phase == #done ifTrue: [^ lastCommand]. (lastCommand phase == #undone and: [(anIndex _ history indexOf: lastCommand) > 1]) ifTrue: [^ history at: anIndex - 1] ifFalse: [^ nil] ! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'ar 8/31/2000 22:49'! redoNextCommand "If there is a way to 'redo' (move FORWARD) in the undo/redo history tape, do it." | anIndex | lastCommand ifNil: [^ self beep]. lastCommand phase == #undone ifFalse: [anIndex _ history indexOf: lastCommand. (anIndex < history size) ifTrue: [lastCommand _ history at: anIndex + 1] ifFalse: [^ self beep]]. lastCommand redoCommand. lastCommand phase: #done ! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'ar 8/31/2000 22:49'! undoLastCommand "Undo the last command, i.e. move backward in the recent-commands tape, if possible." | aPhase anIndex | lastCommand ifNil: [^ self beep]. (aPhase _ lastCommand phase) == #done ifFalse: [aPhase == #undone ifTrue: [anIndex _ history indexOf: lastCommand. anIndex > 1 ifTrue: [lastCommand _ history at: anIndex - 1]]]. lastCommand undoCommand. lastCommand phase: #undone "Command undoLastCommand" ! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'ar 8/31/2000 22:49'! undoOrRedoCommand "This gives a feature comparable to standard Mac undo/redo. If the undo/redo action taken was a simple do or a redo, then undo it. But if the last undo/redo action taken was an undo, then redo it." "Command undoOrRedoCommand" | aPhase | lastCommand ifNil: [^ self beep]. (aPhase _ lastCommand phase) == #done ifTrue: [lastCommand undoCommand. lastCommand phase: #undone] ifFalse: [aPhase == #undone ifTrue: [lastCommand redoCommand. lastCommand phase: #done]]! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'ar 8/31/2000 22:49'! undoTo "Incomplete. Allow the user to choose a point somewhere in the undo/redo tape, and undo his way to there. Applicable only if infiniteUndo is set. Not yet functional." | anIndex commandList aMenu reply | (anIndex _ self historyIndexOfLastCommand) ifNil: [^ self beep]. commandList _ history copyFrom: ((anIndex - 10) max: 1) to: ((anIndex + 10) min: history size). aMenu _ SelectionMenu labels: (commandList collect: [:cmd | cmd cmdWording]) selections: commandList. reply _ aMenu startUpWithCaption: 'undo or redo to...'. reply ifNotNil: [self halt: 'now for the rest...!!'] "Command undoTo" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CommandHistory class instanceVariableNames: ''! !CommandHistory class methodsFor: 'instance creation' stamp: 'ar 8/31/2000 22:50'! new ^super new initialize! ! !CommandHistory class methodsFor: 'system startup' stamp: 'di 12/12/2000 13:41'! initialize "CommandHistory initialize" Smalltalk addToShutDownList: self. ! ! !CommandHistory class methodsFor: 'system startup' stamp: 'di 12/12/2000 13:48'! resetAllHistory CommandHistory allInstancesDo: [:c | c resetCommandHistory]. ! ! !CommandHistory class methodsFor: 'system startup' stamp: 'di 12/12/2000 13:48'! shutDown: aboutToQuit aboutToQuit ifTrue: [self resetAllHistory]. ! ! AbstractLauncher subclass: #CommandLineLauncherExample instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Framework-Download'! !CommandLineLauncherExample commentStamp: '' prior: 0! CommandLineLauncherExample provides an example for a command line application. if you start squeak with a command line 'class Integer' it will launch a class browser on class Integer. To enable this execute CommandLineLauncherExample activate before you save the image. To disable execute CommandLineLauncherExample deactivate! !CommandLineLauncherExample methodsFor: 'running' stamp: 'mir 7/26/2000 15:56'! startUp | className | className _ self parameterAt: 'class'. (Smalltalk at: className asSymbol ifAbsent: [Object]) browse! ! TileLikeMorph subclass: #CommandTilesMorph instanceVariableNames: 'morph playerScripted ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !CommandTilesMorph commentStamp: '' prior: 0! An entire Smalltalk statement in tiles. A line of code.! !CommandTilesMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:13'! initialize super initialize. self wrapCentering: #center; cellPositioning: #leftCenter. self hResizing: #shrinkWrap. borderWidth _ 0. self layoutInset: 0. self extent: 5@5. "will grow to fit" ! ! !CommandTilesMorph methodsFor: 'initialization' stamp: 'sw 1/29/98 18:32'! setMorph: aMorph playerScripted _ aMorph playerScripted ! ! !CommandTilesMorph methodsFor: 'miscellaneous'! tileRows ^ Array with: self submorphs! ! Object subclass: #Comment instanceVariableNames: '' classVariableNames: 'CommentsTable ' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !Comment commentStamp: '' prior: 0! A Comment space is like a bulletin board. It is a web page with a list statements from many different people. At the bottom there is a form for you to add your own statement. Anyone may start a new comment page, just by asking for a page with a new key, and there can be any number of pages. The default Swiki has a page called 'pws' already created. The administrator must take special action to save the accumulated comments (Comment saveTo: 'aFileName'). Comments are not automatically stored on the disk like regular Swiki pages are. So, for the moment, it is likely that Comments will get lost when the server is restarted. URLs are of the form machine:80/Comment.{commentKey} machine:80/Comment.{commentKey}.{number} machine:80/Comment.{commentKey}.note -- Does this really work??? -tk machine:80/Comment.{commentKey}.gif! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Comment class instanceVariableNames: ''! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/10/97 10:44'! initialize CommentsTable := Dictionary new.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/24/97 15:20'! readIn: filename |f| f _ ReferenceStream fileNamed: filename. CommentsTable _ f next. f close.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/24/97 15:20'! saveTo: filename |f| f _ ReferenceStream fileNamed: filename. f nextPut: CommentsTable. f close.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/17/97 14:52'! setUpExample | newDiscussion | newDiscussion _ Discussion new. newDiscussion title: 'pws'. newDiscussion description: 'Here is a space for talking about the Pluggable Web Server.'. CommentsTable at: 'pws' put: newDiscussion. ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/10/97 11:18'! comments ^CommentsTable! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 12/19/97 13:44'! createComment: request "Create a new comment from a Web request" | newNote newMap | request fields isNil ifTrue: [self error: 'No request to create a comment from!!']. newNote := Note new. newMap := URLmap new. newNote author: (request fields at: 'author' ifAbsent: ['Anonymous']). newNote title: (request fields at: 'title' ifAbsent: ['Untitled']). newNote text: (HTMLformatter swikify: (request fields at: 'text' ifAbsent: ['Nothing much to say']) linkhandler: [:phrase | newMap linkFor: phrase from: (request peerName) storingTo: OrderedCollection new]). newNote timestamp: (Date today printString),' ',(Time now printString). newNote children: OrderedCollection new. "For later addition of threaded comments" ^newNote ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 9/1/1998 12:58'! process: request "URLs are of the form Comment.commentKey or Comment.commentKey.note of Comment.commentKey.gif. If commentKey is accessed but not created, create an empty one. If note is accessed, display it." | commentKey noteIndex newNote | (request message size > 1) ifTrue: [commentKey _ request message at: 2] ifFalse: [^request reply: (self showAllComments: CommentsTable)]. (CommentsTable includesKey: commentKey) ifFalse: [CommentsTable at: commentKey put: Discussion new. (CommentsTable at: commentKey) title: commentKey. (CommentsTable at: commentKey) description: 'Discussion on ' , commentKey]. request fields isNil ifFalse: ["Are there input fields?" newNote _ self createComment: request. newNote parent: commentKey. (CommentsTable at: commentKey) addNote: newNote. newNote url: ('Comment.',commentKey,'.', (CommentsTable at: commentKey) notes size printString)]. request message size > 2 ifTrue: ["There's a note reference or a request for a status image" noteIndex _ request message at: 3. noteIndex asUppercase = 'GIF' ifTrue: [ request reply: (PWS success),(PWS content: 'image/gif'). request reply: (HTMLformatter textToGIF: (CommentsTable at: commentKey) status)] ifFalse: [request reply: (self showNote: ((CommentsTable at: commentKey) at: noteIndex asNumber))]] ifFalse: [request reply: (self showComment: (CommentsTable at: commentKey))]! ! !Comment class methodsFor: 'URL processing' stamp: 'TPR 7/20/1998 18:07'! showAllComments: aDictionaryOfCommentSpaces | fileName | fileName := (ServerAction serverDirectory) , 'ShowAllComments.html'. ^HTMLformatter evalEmbedded: (FileStream fileNamed: fileName) contentsOfEntireFile with: aDictionaryOfCommentSpaces ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/18/97 10:28'! showComment: aComment | fileName | fileName := (ServerAction serverDirectory) , 'ShowComment.html'. ^HTMLformatter evalEmbedded: (FileStream fileNamed: fileName) contentsOfEntireFile with: aComment. ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/18/97 10:28'! showNote: aNote | fileName | fileName := (ServerAction serverDirectory) , 'ShowNote.html'. ^HTMLformatter evalEmbedded: (FileStream fileNamed: fileName) contentsOfEntireFile with: aNote. ! ! ParseNode subclass: #CommentNode instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! ByteArray variableByteSubclass: #CompiledApplescript instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !CompiledApplescript commentStamp: '' prior: 0! I represent a stored persistent representation of a compiled Applescript. Ultimately, Squeak interfaces should endeavor to coerce Applescript information to me in one form or the other as soon as possible, as I require no special treatment with respect to memory management. I *WILL*, however, retain state information between executions of applescripts if I was compiled using modes with bits 2 or 3 set. I am generally kept as an internal value inside of Applescript or Context objects. To operate on my instances, they are first generally converted to an AEDesc of type 'scpt', and then "loaded" into an Applescript scripting component (represented by an OSAID handle). The OSAID is used to "work on" the instance. If changed state information is desired, the modified OSAID must be "stored" from Applescript back to an AEDesc, and then ultimately converted back into another persistent CompiledScript, which can then replace the first instance.! !CompiledApplescript methodsFor: 'accessing' stamp: 'acg 9/26/1999 22:19'! source ^self sourceWith: Applescript generic! ! !CompiledApplescript methodsFor: 'accessing' stamp: 'acg 9/26/1999 22:18'! sourceWith: anApplescriptInstance | theOSAID theSource| (theOSAID _ self asOSAIDWith: anApplescriptInstance) ifNil: [^'']. theSource _ anApplescriptInstance sourceOfOSAID: theOSAID. theOSAID disposeWith: anApplescriptInstance. ^theSource ifNil: [^''].! ! !CompiledApplescript methodsFor: 'processing' stamp: 'acg 9/25/1999 23:27'! value ^self valueWith: Applescript generic in: (OSAID new) mode: 0 ! ! !CompiledApplescript methodsFor: 'processing' stamp: 'acg 9/25/1999 23:27'! valueIn: contextOSAID ^self valueWith: Applescript generic in: contextOSAID mode: 0 ! ! !CompiledApplescript methodsFor: 'processing' stamp: 'acg 9/22/1999 08:51'! valueWith: anApplescriptInstance in: contextOSAID mode: anInteger ^anApplescriptInstance doCompiledScript: self in: contextOSAID mode: anInteger ! ! !CompiledApplescript methodsFor: 'converting' stamp: 'acg 9/22/1999 08:07'! asAEDesc ^AEDesc scptTypeOn: self! ! !CompiledApplescript methodsFor: 'converting' stamp: 'acg 9/26/1999 18:33'! asOSAIDWith: anApplescriptInstance ^self asAEDesc asOSAIDThenDisposeAEDescWith: anApplescriptInstance! ! !CompiledApplescript methodsFor: 'intermediate actions' stamp: 'acg 9/26/1999 01:06'! loadWith: anApplescriptInstance ^self loadWith: anApplescriptInstance mode: 0! ! !CompiledApplescript methodsFor: 'intermediate actions' stamp: 'acg 9/26/1999 01:05'! loadWith: anApplescriptInstance mode: anInteger ^anApplescriptInstance loadAndDisposeAEDesc: (self asAEDesc) mode: anInteger! ! !CompiledApplescript methodsFor: 'printing' stamp: 'sma 6/1/2000 09:44'! printOn: aStream self printNameOn: aStream. aStream nextPutAll: ' ('; print: self size; nextPutAll: ' bytes)'! ! ByteArray variableByteSubclass: #CompiledMethod instanceVariableNames: '' classVariableNames: 'LargeFrame SmallFrame SpecialConstants TempNameCache ' poolDictionaries: '' category: 'Kernel-Methods'! !CompiledMethod commentStamp: '' prior: 0! I represent a method suitable for interpretation by the virtual machine. My instances have pointer fields, including a header and some literals, followed by non-pointer fields comprising the byte encoded instructions for the method. The header encodes the number of arguments, the number of literals, and the amount of temporary space needed (for context allocation). An extra three bytes are added after the executable code. These contain an external file address to the source code for the method.! !CompiledMethod methodsFor: 'initialize-release'! copyWithTrailerBytes: bytes "Testing: (CompiledMethod compiledMethodAt: #copyWithTrailerBytes:) tempNamesPut: 'copy end ' " | copy end start | start _ self initialPC. end _ self endPC. copy _ CompiledMethod newMethod: end - start + 1 + bytes size header: self header. 1 to: self numLiterals do: [:i | copy literalAt: i put: (self literalAt: i)]. start to: end do: [:i | copy at: i put: (self at: i)]. 1 to: bytes size do: [:i | copy at: end + i put: (bytes at: i)]. ^ copy! ! !CompiledMethod methodsFor: 'initialize-release' stamp: 'di 10/22/1999 13:14'! needsFrameSize: newFrameSize "Set the largeFrameBit to accomodate the newFrameSize" | largeFrameBit header | largeFrameBit _ 16r20000. (self numTemps + newFrameSize) > LargeFrame ifTrue: [^ self error: 'Cannot compile -- stack including temps is too deep']. header _ self objectAt: 1. (header bitAnd: largeFrameBit) ~= 0 ifTrue: [header _ header - largeFrameBit]. self objectAt: 1 put: header + ((self numTemps + newFrameSize) > SmallFrame ifTrue: [largeFrameBit] ifFalse: [0])! ! !CompiledMethod methodsFor: 'accessing'! endPC "Answer the index of the last bytecode." | flagByte | flagByte _ self last. flagByte = 0 ifTrue: ["If last byte = 0, may be either 0, 0, 0, 0 or just 0" 1 to: 4 do: [:i | (self at: self size - i) = 0 ifFalse: [^ self size - i]]]. flagByte < 252 ifTrue: ["Magic sources (tempnames encoded in last few bytes)" ^ self size - self last - 1]. "Normal 4-byte source pointer" ^ self size - 4! ! !CompiledMethod methodsFor: 'accessing' stamp: 'di 1/2/1999 17:00'! flushCache "Tell the interpreter to remove all references to this method from its method lookup cache, if it has one. This primitive must be called whenever a method is defined or removed. NOTE: Only one of two selective flush methods needs to be used. Squeak 2.2 and earlier uses 119 (See Symbol flushCache). Squeak 2.3 and later uses 116 (See CompiledMethod flushCache)." ! ! !CompiledMethod methodsFor: 'accessing' stamp: 'di 10/23/1999 22:00'! frameSize "Answer the size of temporary frame needed to run the receiver." "NOTE: Versions 2.7 and later use two sizes of contexts." (self header noMask: 16r20000) ifTrue: [^ SmallFrame] ifFalse: [^ LargeFrame] ! ! !CompiledMethod methodsFor: 'accessing'! initialPC "Answer the program counter for the receiver's first bytecode." ^ (self numLiterals + 1) * 4 + 1! ! !CompiledMethod methodsFor: 'accessing' stamp: 'ar 6/2/1998 16:26'! numArgs "Answer the number of arguments the receiver takes." ^ (self header bitShift: -24) bitAnd: 16r0F! ! !CompiledMethod methodsFor: 'accessing'! numLiterals "Answer the number of literals used by the receiver." ^ (self header bitShift: -9) bitAnd: 16rFF! ! !CompiledMethod methodsFor: 'accessing'! numTemps "Answer the number of temporary variables used by the receiver." ^ (self header bitShift: -18) bitAnd: 16r3F! ! !CompiledMethod methodsFor: 'accessing' stamp: 'jm 9/18/97 21:06'! primitive "Answer the primitive index associated with the receiver. Zero indicates that this is not a primitive method. We currently allow 11 bits of primitive index, but they are in two places for backward compatibility. The time to unpack is negligible, since the reconstituted full index is stored in the method cache." | primBits | primBits _ self header bitAnd: 16r300001FF. primBits > 16r1FF ifTrue: [^ (primBits bitAnd: 16r1FF) + (primBits bitShift: -19)] ifFalse: [^ primBits]! ! !CompiledMethod methodsFor: 'accessing'! returnField "Answer the index of the instance variable returned by a quick return method." | prim | prim _ self primitive. prim < 264 ifTrue: [self error: 'only meaningful for quick-return'] ifFalse: [^ prim - 264]! ! !CompiledMethod methodsFor: 'accessing' stamp: 'sw 8/15/97 16:17'! selector "This is slow, so don't call it frivolously" ^ self who last! ! !CompiledMethod methodsFor: 'comparing' stamp: 'di 6/6/2000 17:53'! = method | myLits otherLits | "Answer whether the receiver implements the same code as the argument, method." (method isKindOf: CompiledMethod) ifFalse: [^false]. self size = method size ifFalse: [^false]. self header = method header ifFalse: [^false]. self initialPC to: self endPC do: [:i | (self at: i) = (method at: i) ifFalse: [^false]]. (myLits _ self literals) = (otherLits _ method literals) ifFalse: [myLits size = otherLits size ifFalse: [^ false]. "Dont bother checking FFI and named primitives" (#(117 120) includes: self primitive) ifTrue: [^ true]. myLits with: otherLits do: [:lit1 :lit2 | lit1 = lit2 ifFalse: [(lit1 isMemberOf: Association) ifTrue: ["Associations match if value is equal, since associations used for super may have key = nil or name of class." lit1 value == lit2 value ifFalse: [^ false]] ifFalse: [(lit1 isMemberOf: Float) ifTrue: ["Floats match if values are close, due to roundoff error." (lit1 closeTo: lit2) ifFalse: [^ false]] ifFalse: ["any other discrepancy is a failure" ^ false]]]]]. ^ true! ! !CompiledMethod methodsFor: 'testing' stamp: 'sw 8/20/1998 09:31'! hasReportableSlip "Answer whether the receiver contains anything that might be brought to the attention of the author when filing out. Customize the lists to suit your preferences. If slips do not get reported in spite of your best efforts here, make certain that the Preference 'suppressCheckForSlips' has not been hard-coded to true." | assoc | #(halt halt: urgent hottest) do: [:aLit | (self hasLiteral: aLit) ifTrue: [^ true]]. #(Transcript AA BB CC DD EE) do: [:aSymbol | (assoc _ (Smalltalk associationAt: aSymbol ifAbsent: [nil])) ifNotNil: [(self hasLiteral: assoc) ifTrue: [^ true]]]. ^ false! ! !CompiledMethod methodsFor: 'testing' stamp: 'di 12/26/1998 21:31'! isQuick "Answer whether the receiver is a quick return (of self or of an instance variable)." ^ self primitive between: 256 and: 519! ! !CompiledMethod methodsFor: 'testing' stamp: 'ar 6/2/1998 16:11'! isReturnField "Answer whether the receiver is a quick return of an instance variable." ^ self primitive between: 264 and: 519! ! !CompiledMethod methodsFor: 'testing'! isReturnSelf "Answer whether the receiver is a quick return of self." ^ self primitive = 256! ! !CompiledMethod methodsFor: 'testing'! isReturnSpecial "Answer whether the receiver is a quick return of self or constant." ^ self primitive between: 256 and: 263! ! !CompiledMethod methodsFor: 'printing' stamp: 'sma 2/12/2000 14:01'! decompileString | clAndSel cl sel | clAndSel _ self who. clAndSel = #(unknown unknown) ifTrue: [cl _ Object. sel _ #xxxUnknown. self numArgs >= 1 ifTrue: [sel _ sel , ':'. 2 to: self numArgs do: [:i | sel _ sel , 'with:']. sel _ sel asSymbol]] ifFalse: [cl _ clAndSel first. sel _ clAndSel last]. ^ (cl decompilerClass new decompile: sel in: cl method: self) decompileString! ! !CompiledMethod methodsFor: 'printing' stamp: 'sma 6/1/2000 09:45'! printOn: aStream "Overrides method inherited from the byte arrayed collection." self printNameOn: aStream. aStream space; nextPutAll: self identityHashPrintString! ! !CompiledMethod methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:09'! printOnStream: aStream "Overrides method inherited from the byte arrayed collection." aStream print: 'a CompiledMethod'! ! !CompiledMethod methodsFor: 'printing' stamp: 'ar 11/28/1999 19:37'! printPrimitiveOn: aStream "Print the primitive on aStream" | primIndex primDecl | primIndex _ self primitive. primIndex = 0 ifTrue:[^self]. primIndex = 120 "External call spec" ifTrue:[^aStream print: (self literalAt: 1); cr]. aStream nextPutAll: '; cr! ! !CompiledMethod methodsFor: 'printing'! storeLiteralsOn: aStream forClass: aBehavior "Store the literals referenced by the receiver on aStream, each terminated by a space." | literal | 2 to: self numLiterals + 1 do: [:index | aBehavior storeLiteral: (self objectAt: index) on: aStream. aStream space]! ! !CompiledMethod methodsFor: 'printing'! storeOn: aStream | noneYet | aStream nextPutAll: '(('. aStream nextPutAll: self class name. aStream nextPutAll: ' newMethod: '. aStream store: self size - self initialPC + 1. aStream nextPutAll: ' header: '. aStream store: self header. aStream nextPut: $). noneYet _ self storeElementsFrom: self initialPC to: self endPC on: aStream. 1 to: self numLiterals do: [:index | noneYet ifTrue: [noneYet _ false] ifFalse: [aStream nextPut: $;]. aStream nextPutAll: ' literalAt: '. aStream store: index. aStream nextPutAll: ' put: '. aStream store: (self literalAt: index)]. noneYet ifFalse: [aStream nextPutAll: '; yourself']. aStream nextPut: $)! ! !CompiledMethod methodsFor: 'printing' stamp: 'di 12/26/1998 21:30'! symbolic "Answer a String that contains a list of all the byte codes in a method with a short description of each." | aStream | self isQuick ifTrue: [self isReturnSpecial ifTrue: [^ 'Quick return ' , (#('self' 'true' 'false' 'nil' '-1' '0' '1' '2') at: self primitive - 255)]. ^ 'Quick return field ' , self returnField printString , ' (0-based)']. aStream _ WriteStream on: (String new: 1000). self printPrimitiveOn: aStream. (InstructionPrinter on: self) printInstructionsOn: aStream. ^aStream contents! ! !CompiledMethod methodsFor: 'printing' stamp: 'di 2/4/2000 21:13'! who "Answer an Array of the class in which the receiver is defined and the selector to which it corresponds." | sel | Smalltalk allBehaviorsDo: [:class | (sel _ class methodDict keyAtIdentityValue: self ifAbsent: [nil]) ifNotNil: [^Array with: class with: sel]]. ^ Array with: #unknown with: #unknown ! ! !CompiledMethod methodsFor: 'literals' stamp: 'di 10/17/97 22:38'! hasLiteral: literal "Answer whether the receiver references the argument, literal." "a fast primitive operation equivalent to..." 2 to: self numLiterals + 1 do: [:index | literal == (self objectAt: index) ifTrue: [^ true]]. ^ false! ! !CompiledMethod methodsFor: 'literals' stamp: 'di 8/15/97 09:51'! hasLiteralSuchThat: litBlock "Answer true if litBlock returns true for any literal in this method, even if imbedded in array structure." | lit | 2 to: self numLiterals + 1 do: [:index | lit _ self objectAt: index. (litBlock value: lit) ifTrue: [^ true]. (lit class == Array and: [lit hasLiteralSuchThat: litBlock]) ifTrue: [^ true]]. ^false! ! !CompiledMethod methodsFor: 'literals' stamp: 'sma 6/3/2000 21:39'! hasLiteralThorough: literal "Answer true if any literal in this method is literal, even if embedded in array structure." | lit | 2 to: self numLiterals + 1 do: [:index | (lit _ self objectAt: index) == literal ifTrue: [^ true]. (lit class == Array and: [lit hasLiteral: literal]) ifTrue: [^ true]]. ^ false! ! !CompiledMethod methodsFor: 'literals'! header "Answer the word containing the information about the form of the receiver and the form of the context needed to run the receiver." ^self objectAt: 1! ! !CompiledMethod methodsFor: 'literals'! literalAt: index "Answer the literal indexed by the argument." ^self objectAt: index + 1! ! !CompiledMethod methodsFor: 'literals'! literalAt: index put: value "Replace the literal indexed by the first argument with the second argument. Answer the second argument." ^self objectAt: index + 1 put: value! ! !CompiledMethod methodsFor: 'literals'! literalStrings | lits litStrs | lits _ self literals. litStrs _ OrderedCollection new: lits size * 3. self literals do: [:lit | (lit isMemberOf: Association) ifTrue: [litStrs addLast: lit key] ifFalse: [(lit isMemberOf: Symbol) ifTrue: [litStrs addAll: lit keywords] ifFalse: [litStrs addLast: lit printString]]]. ^ litStrs! ! !CompiledMethod methodsFor: 'literals'! literals "Answer an Array of the literals referenced by the receiver." | literals numberLiterals | literals _ Array new: (numberLiterals _ self numLiterals). 1 to: numberLiterals do: [:index | literals at: index put: (self objectAt: index + 1)]. ^literals! ! !CompiledMethod methodsFor: 'literals'! objectAt: index "Primitive. Answer the method header (if index=1) or a literal (if index >1) from the receiver. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !CompiledMethod methodsFor: 'literals'! objectAt: index put: value "Primitive. Store the value argument into a literal in the receiver. An index of 2 corresponds to the first literal. Fails if the index is less than 2 or greater than the number of literals. Answer the value as the result. Normally only the compiler sends this message, because only the compiler stores values in CompiledMethods. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !CompiledMethod methodsFor: 'scanning'! messages "Answer a Set of all the message selectors sent by this method." | scanner aSet | aSet _ Set new. scanner _ InstructionStream on: self. scanner scanFor: [:x | scanner addSelectorTo: aSet. false "keep scanning"]. ^aSet! ! !CompiledMethod methodsFor: 'scanning'! readsField: varIndex "Answer whether the receiver loads the instance variable indexed by the argument." self isReturnField ifTrue: [^self returnField + 1 = varIndex]. varIndex <= 16 ifTrue: [^ self scanFor: varIndex - 1]. varIndex <= 64 ifTrue: [^ self scanLongLoad: varIndex - 1]. ^ self scanVeryLongLoad: 64 offset: varIndex - 1! ! !CompiledMethod methodsFor: 'scanning'! readsRef: literalAssociation "Answer whether the receiver loads the argument." | lit | lit _ self literals indexOf: literalAssociation ifAbsent: [^false]. lit <= 32 ifTrue: [^self scanFor: 64 + lit - 1]. lit <= 64 ifTrue: [^self scanLongLoad: 192 + lit - 1]. ^ self scanVeryLongLoad: 128 offset: lit - 1! ! !CompiledMethod methodsFor: 'scanning'! scanFor: byte "Answer whether the receiver contains the argument as a bytecode." ^ (InstructionStream on: self) scanFor: [:instr | instr = byte] " Smalltalk browseAllSelect: [:m | m scanFor: 134] "! ! !CompiledMethod methodsFor: 'scanning'! scanLongLoad: extension "Answer whether the receiver contains a long load whose extension is the argument." | scanner | scanner _ InstructionStream on: self. ^scanner scanFor: [:instr | instr = 128 and: [scanner followingByte = extension]]! ! !CompiledMethod methodsFor: 'scanning'! scanLongStore: extension "Answer whether the receiver contains a long store whose extension is the argument." | scanner | scanner _ InstructionStream on: self. ^scanner scanFor: [:instr | (instr = 129 or: [instr = 130]) and: [scanner followingByte = extension]]! ! !CompiledMethod methodsFor: 'scanning'! scanVeryLongLoad: extension offset: offset "Answer whether the receiver contains a long load whose extension is the argument." | scanner | scanner _ InstructionStream on: self. ^ scanner scanFor: [:instr | (instr = 132 and: [scanner followingByte = extension]) and: [scanner thirdByte = offset]]! ! !CompiledMethod methodsFor: 'scanning' stamp: 'di 6/25/97 19:08'! scanVeryLongStore: extension offset: offset "Answer whether the receiver contains a long load with the given offset. Note that the constant +32 is the known difference between a store and a storePop for instVars, and it will always fail on literal variables, but these only use store (followed by pop) anyway." | scanner ext | scanner _ InstructionStream on: self. ^ scanner scanFor: [:instr | (instr = 132 and: [(ext _ scanner followingByte) = extension or: ["might be a store/pop into rcvr" ext = (extension+32)]]) and: [scanner thirdByte = offset]]! ! !CompiledMethod methodsFor: 'scanning'! sendsToSuper "Answer whether the receiver sends any message to super." | scanner | scanner _ InstructionStream on: self. ^ scanner scanFor: [:instr | instr = 16r85 or: [instr = 16r84 and: [scanner followingByte between: 16r20 and: 16r3F]]]! ! !CompiledMethod methodsFor: 'scanning' stamp: 'di 12/26/1998 21:30'! writesField: field "Answer whether the receiver stores into the instance variable indexed by the argument." self isQuick ifTrue: [^ false]. field <= 8 ifTrue: [^ (self scanFor: 96 + field - 1) or: [self scanLongStore: field - 1]]. field <= 64 ifTrue: [^ self scanLongStore: field - 1]. ^ self scanVeryLongStore: 160 offset: field - 1! ! !CompiledMethod methodsFor: 'scanning'! writesRef: ref "Answer whether the receiver stores the argument." | lit | lit _ self literals indexOf: ref ifAbsent: [^false]. lit <= 64 ifTrue: [^ self scanLongStore: 192 + lit - 1]. ^ self scanVeryLongStore: 224 offset: lit - 1! ! !CompiledMethod methodsFor: 'source code management'! cacheTempNames: names TempNameCache _ Association key: self value: names! ! !CompiledMethod methodsFor: 'source code management' stamp: 'tk 12/7/2000 12:28'! checkOKToAdd: size at: filePosition "Issue several warnings as the end of the changes file approaches its limit, and finally halt with an error when the end is reached." | fileSizeLimit margin | fileSizeLimit _ 16r2000000. 3 to: 1 by: -1 do: [:i | margin _ i*100000. (filePosition + size + margin) > fileSizeLimit ifTrue: [(filePosition + margin) > fileSizeLimit ifFalse: [self inform: 'WARNING: your changes file is within ' , margin printString , ' characters of its size limit. You should take action soon to reduce its size. You may proceed.']] ifFalse: [^ self]]. (filePosition + size > fileSizeLimit) ifFalse: [^ self]. self error: 'You have reached the size limit of the changes file. You must take action now to reduce it. Close this error. Do not attempt to proceed.'! ! !CompiledMethod methodsFor: 'source code management'! copyWithTempNames: tempNames | tempStr | tempStr _ String streamContents: [:strm | tempNames do: [:n | strm nextPutAll: n; space]]. ^ self copyWithTrailerBytes: (self qCompress: tempStr)! ! !CompiledMethod methodsFor: 'source code management' stamp: 'hmm 4/26/2000 20:44'! fileIndex ^SourceFiles fileIndexFromSourcePointer: self sourcePointer! ! !CompiledMethod methodsFor: 'source code management' stamp: 'hmm 4/26/2000 20:45'! filePosition ^SourceFiles filePositionFromSourcePointer: self sourcePointer! ! !CompiledMethod methodsFor: 'source code management' stamp: 'di 12/26/1998 22:34'! getSourceFor: selector in: class "Retrieve or reconstruct the source code for this method." | source flagByte | flagByte _ self last. (flagByte = 0 or: [flagByte = 251 "some source-less methods have flag = 251, rest = 0" and: [((1 to: 3) collect: [:i | self at: self size - i]) = #(0 0 0)]]) ifTrue: ["No source pointer -- decompile without temp names" ^ (class decompilerClass new decompile: selector in: class method: self) decompileString]. flagByte < 252 ifTrue: ["Magic sources -- decompile with temp names" ^ ((class decompilerClass new withTempNames: self tempNames) decompile: selector in: class method: self) decompileString]. "Situation normal; read the sourceCode from the file" (source _ self getSourceFromFile) == nil ifFalse: [^ source]. "Something really wrong -- decompile blind (no temps)" ^ (class decompilerClass new decompile: selector in: class method: self) decompileString! ! !CompiledMethod methodsFor: 'source code management' stamp: 'tk 12/12/97 13:03'! getSourceFromFile "Read the source code from file, determining source file index and file position from the last 3 bytes of this method." | position | (position _ self filePosition) = 0 ifTrue: [^ nil]. ^ (RemoteString newFileNumber: self fileIndex position: position) text! ! !CompiledMethod methodsFor: 'source code management'! putSource: sourceStr fromParseNode: methodNode class: class category: catName inFile: fileIndex priorMethod: priorMethod ^ self putSource: sourceStr fromParseNode: methodNode inFile: fileIndex withPreamble: [:file | class printCategoryChunk: catName on: file priorMethod: priorMethod. file cr]! ! !CompiledMethod methodsFor: 'source code management' stamp: '6/5/97 di'! putSource: sourceStr fromParseNode: methodNode class: class category: catName withStamp: changeStamp inFile: fileIndex priorMethod: priorMethod ^ self putSource: sourceStr fromParseNode: methodNode inFile: fileIndex withPreamble: [:file | class printCategoryChunk: catName on: file withStamp: changeStamp priorMethod: priorMethod. file cr]! ! !CompiledMethod methodsFor: 'source code management' stamp: 'di 5/30/2000 21:26'! putSource: sourceStr fromParseNode: methodNode inFile: fileIndex withPreamble: preambleBlock "Store the source code for the receiver on an external file. If no sources are available, i.e., SourceFile is nil, then store temp names for decompilation at the end of the method. If the fileIndex is 1, print on *.sources; if it is 2, print on *.changes, in each case, storing a 4-byte source code pointer at the method end." | file remoteString st80str | (SourceFiles == nil or: [(file _ SourceFiles at: fileIndex) == nil]) ifTrue: [^ self become: (self copyWithTempNames: methodNode tempNames)]. Smalltalk assureStartupStampLogged. file setToEnd. preambleBlock value: file. "Write the preamble" (methodNode isKindOf: DialectMethodNode) ifTrue: ["This source was parsed from an alternate syntax. We must convert to ST80 before logging it." st80str _ (DialectStream dialect: #ST80 contents: [:strm | methodNode printOn: strm]) asString. remoteString _ RemoteString newString: st80str onFileNumber: fileIndex toFile: file] ifFalse: [remoteString _ RemoteString newString: sourceStr onFileNumber: fileIndex toFile: file]. file nextChunkPut: ' '; flush. self checkOKToAdd: sourceStr size at: remoteString position. self setSourcePosition: remoteString position inFile: fileIndex! ! !CompiledMethod methodsFor: 'source code management' stamp: 'di 9/23/1998 19:22'! qCompress: str "A very simple text compression routine designed for method temp names. Most common 12 chars get values 0-11 packed in one 4-bit nibble; others get values 12-15 (2 bits) * 16 plus next nibble. Last char of str must be a space so it may be dropped without consequence if output ends on odd nibble." | charTable odd ix oddNibble | charTable _ "Character encoding table must match qDecompress:" ' eatrnoislcm bdfghjkpquvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'. ^ ByteArray streamContents: [:strm | odd _ true. "Flag for odd or even nibble out" str do: [:char | ix _ (charTable indexOf: char) - 1. (ix <= 12 ifTrue: [Array with: ix] ifFalse: [Array with: ix//16+12 with: ix\\16]) do: [:nibble | (odd _ odd not) ifTrue: [strm nextPut: oddNibble*16 + nibble] ifFalse: [oddNibble _ nibble]]]. strm nextPut: strm position] " | m s | m _ CompiledMethod new. s _ 'charTable odd ix oddNibble '. ^ Array with: s size with: (m qCompress: s) size with: (m qDecompress: (m qCompress: s)) " ! ! !CompiledMethod methodsFor: 'source code management'! qDecompress: byteArray "Decompress strings compressed by qCompress:. Most common 12 chars get values 0-11 packed in one 4-bit nibble; others get values 12-15 (2 bits) * 16 plus next nibble" | charTable extended ext | charTable _ "Character encoding table must match qCompress:" ' eatrnoislcm bdfghjkpquvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'. ^ String streamContents: [:strm | extended _ false. "Flag for 2-nibble characters" byteArray do: [:byte | (Array with: byte//16 with: byte\\16) do: [:nibble | extended ifTrue: [strm nextPut: (charTable at: ext*16+nibble + 1). extended _ false] ifFalse: [nibble < 12 ifTrue: [strm nextPut: (charTable at: nibble + 1)] ifFalse: [ext _ nibble-12. extended _ true]]]]]! ! !CompiledMethod methodsFor: 'source code management' stamp: 'hmm 4/26/2000 21:00'! setSourcePointer: srcPointer srcPointer = 0 ifTrue: [ self at: self size put: 0. ^self]. (srcPointer between: 16r1000000 and: 16r4FFFFFF) ifFalse: [self error: 'Source pointer out of range']. self at: self size put: (srcPointer bitShift: -24) + 251. 1 to: 3 do: [:i | self at: self size-i put: ((srcPointer bitShift: (i-3)*8) bitAnd: 16rFF)]! ! !CompiledMethod methodsFor: 'source code management' stamp: 'hmm 4/26/2000 21:02'! setSourcePosition: position inFile: fileIndex self setSourcePointer: (SourceFiles sourcePointerFromFileIndex: fileIndex andPosition: position)! ! !CompiledMethod methodsFor: 'source code management'! setTempNamesIfCached: aBlock "This is a cache used by the debugger, independent of the storage of temp names when the system is converted to decompilation with temps." TempNameCache == nil ifTrue: [^self]. TempNameCache key == self ifTrue: [aBlock value: TempNameCache value]! ! !CompiledMethod methodsFor: 'source code management' stamp: 'hmm 4/26/2000 20:44'! sourcePointer "Answer the integer which can be used to find the source file and position for this method. The returned value is either 0 (if no source is stored) or a number between 16r1000000 and 16r4FFFFFF. The actual interpretation of this number is up to the SourceFileArray stored in the global variable SourceFiles." | pos | self last < 252 ifTrue: [^ 0 "no source"]. pos _ self last - 251. self size - 1 to: self size - 3 by: -1 do: [:i | pos _ pos * 256 + (self at: i)]. ^pos! ! !CompiledMethod methodsFor: 'source code management'! tempNames | byteCount bytes | byteCount _ self at: self size. byteCount = 0 ifTrue: [^ Array new]. bytes _ (ByteArray new: byteCount) replaceFrom: 1 to: byteCount with: self startingAt: self size - byteCount. ^ (self qDecompress: bytes) findTokens: ' '! ! !CompiledMethod methodsFor: 'file in/out' stamp: 'tk 10/6/2000 14:22'! readDataFrom: aDataStream size: varsOnDisk "Fill in my fields. My header and number of literals are already installed. Must read both objects for the literals and bytes for the bytecodes." self error: 'Must use readMethod'.! ! !CompiledMethod methodsFor: 'file in/out' stamp: 'tk 3/26/98 09:10'! storeDataOn: aDataStream "Store myself on a DataStream. I am a mixture of objects and raw data bytes. Only use this for blocks. Normal methodDictionaries should not be put out using ReferenceStreams. Their fileOut should be attached to the beginning of the file." | byteLength lits | "No inst vars of the normal type" byteLength _ self basicSize. aDataStream beginInstance: self class size: byteLength. lits _ self numLiterals + 1. "counting header" 1 to: lits do: [:ii | aDataStream nextPut: (self objectAt: ii)]. lits*4+1 to: byteLength do: [:ii | aDataStream byteStream nextPut: (self basicAt: ii)]. "write bytes straight through to the file"! ! !CompiledMethod methodsFor: 'file in/out' stamp: 'tk 8/19/1998 16:20'! veryDeepCopyWith: deepCopier "Return self. I am always shared. Do not record me. Only use this for blocks. Normally methodDictionaries should not be copied this way."! ! !CompiledMethod methodsFor: 'evaluating' stamp: 'sma 6/12/2000 14:29'! valueWithReceiver: aReceiver arguments: anArray | selector | selector _ Symbol new. aReceiver class addSelector: selector withMethod: self. ^ [aReceiver perform: selector withArguments: anArray] ensure: [aReceiver class removeSelectorSimply: selector]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompiledMethod class instanceVariableNames: ''! !CompiledMethod class methodsFor: 'class initialization' stamp: 'di 1/11/1999 22:13'! fullFrameSize "CompiledMethod fullFrameSize" ^ LargeFrame! ! !CompiledMethod class methodsFor: 'class initialization' stamp: 'di 10/22/1999 09:56'! initialize "CompiledMethod initialize" "Initialize class variables specifying the size of the temporary frame needed to run instances of me." SmallFrame _ 16. "Context range for temps+stack" LargeFrame _ 56.! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'tk 9/9/2000 20:36'! basicNew: size self error: 'CompiledMethods may only be created with newMethod:header:' ! ! !CompiledMethod class methodsFor: 'instance creation'! new "This will not make a meaningful method, but it could be used to invoke some otherwise useful method in this class." ^ self newMethod: 0 header: 0! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'tk 1/21/2000 15:25'! new: size self error: 'CompiledMethods may only be created with newMethod:header:'! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'di 5/25/2000 06:37'! newBytes: numberOfBytes trailerBytes: trailer nArgs: nArgs nTemps: nTemps nStack: stackSize nLits: nLits primitive: primitiveIndex "Answer an instance of me. The header is specified by the message arguments. The remaining parts are not as yet determined." | largeBit primBits method | nTemps > 64 ifTrue: [^ self error: 'Cannot compile -- too many temporary variables']. largeBit _ (nTemps + stackSize) > SmallFrame ifTrue: [1] ifFalse: [0]. primBits _ primitiveIndex <= 16r1FF ifTrue: [primitiveIndex] ifFalse: ["For now the high 2 bits of primitive no. are in high bits of header" (primitiveIndex bitAnd: 16r1FF) + ((primitiveIndex bitAnd: 16r600) bitShift: 19)]. method _ self newMethod: numberOfBytes + trailer size header: (nArgs bitShift: 24) + (nTemps bitShift: 18) + (largeBit bitShift: 17) + (nLits bitShift: 9) + primBits. 1 to: trailer size do: "Copy the source code trailer to the end" [:i | method at: method size - trailer size + i put: (trailer at: i)]. ^ method! ! !CompiledMethod class methodsFor: 'instance creation'! newMethod: numberOfBytes header: headerWord "Primitive. Answer an instance of me. The number of literals (and other information) is specified the headerWord. The first argument specifies the number of fields for bytecodes in the method. Fail if either argument is not a SmallInteger, or if numberOfBytes is negative. Once the header of a method is set by this primitive, it cannot be changed in any way. Essential. See Object documentation whatIsAPrimitive." (numberOfBytes isInteger and: [headerWord isInteger and: [numberOfBytes >= 0]]) ifTrue: [ "args okay; space must be low" Smalltalk signalLowSpace. "retry if user proceeds" ^ self newMethod: numberOfBytes header: headerWord ]. ^self primitiveFailed! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'di 5/25/2000 06:43'! toReturnConstant: index trailerBytes: trailer "Answer an instance of me that is a quick return of the constant indexed in (true false nil -1 0 1 2)." ^ self newBytes: 0 trailerBytes: trailer nArgs: 0 nTemps: 0 nStack: 0 nLits: 0 primitive: 256 + index ! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'di 5/25/2000 06:44'! toReturnField: field trailerBytes: trailer "Answer an instance of me that is a quick return of the instance variable indexed by the argument, field." ^ self newBytes: 0 trailerBytes: trailer nArgs: 0 nTemps: 0 nStack: 0 nLits: 0 primitive: 264 + field ! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'di 5/25/2000 06:51'! toReturnSelf "Answer an instance of me that is a quick return of the instance (^self)." ^ self toReturnSelfTrailerBytes: #(0 0 0 0)! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'di 5/25/2000 06:44'! toReturnSelfTrailerBytes: trailer "Answer an instance of me that is a quick return of the instance (^self)." ^ self newBytes: 0 trailerBytes: trailer nArgs: 0 nTemps: 0 nStack: 0 nLits: 0 primitive: 256 ! ! Object subclass: #Compiler instanceVariableNames: 'sourceStream requestor class context ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !Compiler commentStamp: '' prior: 0! The compiler accepts Smalltalk source code and compiles it with respect to a given class. The user of the compiler supplies a context so that temporary variables are accessible during compilation. If there is an error, a requestor (usually a kind of StringHolderController) is sent the message notify:at:in: so that the error message can be displayed. If there is no error, then the result of compilation is a MethodNode, which is the root of a parse tree whose nodes are kinds of ParseNodes. The parse tree can be sent messages to (1) generate code for a CompiledMethod (this is done for compiling methods or evaluating expressions); (2) pretty-print the code (for formatting); or (3) produce a map from object code back to source code (used by debugger program-counter selection). See also Parser, Encoder, ParseNode.! !Compiler methodsFor: 'error handling'! interactive "Answer whether there is a requestor of the compiler who should be informed that an error occurred." ^ (requestor == nil or: [requestor isKindOf: SyntaxError]) not! ! !Compiler methodsFor: 'error handling'! notify: aString "Refer to the comment in Object|notify:." ^self notify: aString at: sourceStream position + 1! ! !Compiler methodsFor: 'error handling' stamp: 'di 10/9/1998 16:50'! notify: aString at: location "Refer to the comment in Object|notify:." requestor == nil ifTrue: [^SyntaxError errorInClass: class withCode: (sourceStream contents copyReplaceFrom: location to: location - 1 with: aString) doitFlag: false] ifFalse: [^requestor notify: aString at: location in: sourceStream]! ! !Compiler methodsFor: 'public access'! compile: textOrStream in: aClass notifying: aRequestor ifFail: failBlock "Answer a MethodNode for the argument, textOrStream. If the MethodNode can not be created, notify the argument, aRequestor; if aRequestor is nil, evaluate failBlock instead. The MethodNode is the root of a parse tree. It can be told to generate a CompiledMethod to be installed in the method dictionary of the argument, aClass." self from: textOrStream class: aClass context: nil notifying: aRequestor. ^self translate: sourceStream noPattern: false ifFail: failBlock! ! !Compiler methodsFor: 'public access' stamp: 'bf 10/14/1999 19:55'! evaluate: textOrStream in: aContext to: receiver notifying: aRequestor ifFail: failBlock "Compiles the sourceStream into a parse tree, then generates code into a method. This method is then installed in the receiver's class so that it can be invoked. In other words, if receiver is not nil, then the text can refer to instance variables of that receiver (the Inspector uses this). If aContext is not nil, the text can refer to temporaries in that context (the Debugger uses this). If aRequestor is not nil, then it will receive a notify:at: message before the attempt to evaluate is aborted. Finally, the compiled method is invoked from here as DoIt or (in the case of evaluation in aContext) DoItIn:. The method is subsequently removed from the class, but this will not get done if the invocation causes an error which is terminated. Such garbage can be removed by executing: Smalltalk allBehaviorsDo: [:cl | cl removeSelector: #DoIt; removeSelector: #DoItIn:]." | methodNode method value | class _ (aContext == nil ifTrue: [receiver] ifFalse: [aContext receiver]) class. self from: textOrStream class: class context: aContext notifying: aRequestor. methodNode _ self translate: sourceStream noPattern: true ifFail: [^failBlock value]. method _ methodNode generate: #(0 0 0 0). self interactive ifTrue: [method _ method copyWithTempNames: methodNode tempNames]. context == nil ifTrue: [class addSelector: #DoIt withMethod: method. value _ receiver DoIt. class removeSelectorSimply: #DoIt. ^value] ifFalse: [class addSelector: #DoItIn: withMethod: method. value _ receiver DoItIn: context. class removeSelectorSimply: #DoItIn:. ^value]! ! !Compiler methodsFor: 'public access' stamp: 'sw 11/7/1999 00:11'! format: textOrStream in: aClass notifying: aRequestor decorated: aBoolean "Compile a parse tree from the argument, textOrStream. Answer a string containing the original code, formatted nicely. If aBoolean is true, then decorate the resulting text with color and hypertext actions" | aNode | self from: textOrStream class: aClass context: nil notifying: aRequestor. aNode _ self format: sourceStream noPattern: false ifFail: [^ nil]. ^ aBoolean ifTrue: [aNode decompileText] ifFalse: [aNode decompileString]! ! !Compiler methodsFor: 'public access' stamp: 'di 4/24/2000 07:46'! parse: textOrStream in: aClass notifying: req "Compile the argument, textOrStream, with respect to the class, aClass, and answer the MethodNode that is the root of the resulting parse tree. Notify the argument, req, if an error occurs. The failBlock is defaulted to an empty block." ^ self parse: textOrStream in: aClass notifying: req dialect: false! ! !Compiler methodsFor: 'public access' stamp: 'RAA 6/14/2000 13:49'! parse: textOrStream in: aClass notifying: req dialect: useDialect "Compile the argument, textOrStream, with respect to the class, aClass, and answer the MethodNode that is the root of the resulting parse tree. Notify the argument, req, if an error occurs. The failBlock is defaulted to an empty block." self from: textOrStream class: aClass context: nil notifying: req. ^ ((useDialect and: [RequestAlternateSyntaxSetting signal]) ifTrue: [DialectParser] ifFalse: [Parser]) new parse: sourceStream class: class noPattern: false context: context notifying: requestor ifFail: []! ! !Compiler methodsFor: 'private' stamp: 'mn 5/25/2000 07:36'! format: aStream noPattern: noPattern ifFail: failBlock | tree | tree _ self class parserClass new parse: aStream class: class noPattern: noPattern context: context notifying: requestor ifFail: [^ failBlock value]. ^ tree! ! !Compiler methodsFor: 'private'! from: textOrStream class: aClass context: aContext notifying: req (textOrStream isKindOf: PositionableStream) ifTrue: [sourceStream _ textOrStream] ifFalse: [sourceStream _ ReadStream on: textOrStream asString]. class _ aClass. context _ aContext. requestor _ req! ! !Compiler methodsFor: 'private' stamp: 'mn 5/25/2000 07:37'! translate: aStream noPattern: noPattern ifFail: failBlock | tree | tree _ self class parserClass new parse: aStream class: class noPattern: noPattern context: context notifying: requestor ifFail: [^ failBlock value]. ^ tree! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Compiler class instanceVariableNames: ''! !Compiler class methodsFor: 'accessing'! parserClass "Return a parser class to use for parsing method headers." ^Parser! ! !Compiler class methodsFor: 'evaluating'! evaluate: textOrString "See Compiler|evaluate:for:notifying:logged:. If a compilation error occurs, a Syntax Error view is created rather than notifying any requestor. Compilation is carried out with respect to nil, i.e., no object, and the invocation is not logged." ^self evaluate: textOrString for: nil logged: false! ! !Compiler class methodsFor: 'evaluating'! evaluate: textOrString for: anObject logged: logFlag "See Compiler|evaluate:for:notifying:logged:. If a compilation error occurs, a Syntax Error view is created rather than notifying any requestor." ^self evaluate: textOrString for: anObject notifying: nil logged: logFlag! ! !Compiler class methodsFor: 'evaluating'! evaluate: textOrString for: anObject notifying: aController logged: logFlag "Compile and execute the argument, textOrString with respect to the class of anObject. If a compilation error occurs, notify aController. If both compilation and execution are successful then, if logFlag is true, log (write) the text onto a system changes file so that it can be replayed if necessary." | val | val _ self new evaluate: textOrString in: nil to: anObject notifying: aController ifFail: [^nil]. logFlag ifTrue: [Smalltalk logChange: textOrString]. ^val! ! !Compiler class methodsFor: 'evaluating'! evaluate: textOrString logged: logFlag "See Compiler|evaluate:for:notifying:logged:. If a compilation error occurs, a Syntax Error view is created rather than notifying any requestor. Compilation is carried out with respect to nil, i.e., no object." ^self evaluate: textOrString for: nil logged: logFlag! ! !Compiler class methodsFor: 'evaluating'! evaluate: textOrString notifying: aController logged: logFlag "See Compiler|evaluate:for:notifying:logged:. Compilation is carried out with respect to nil, i.e., no object." ^self evaluate: textOrString for: nil notifying: aController logged: logFlag! ! Object subclass: #ComplexProgressIndicator instanceVariableNames: 'formerWorld targetMorph estimate prevData formerProcess translucentMorph userSuppliedMorph specificHistory historyCategory cumulativeStageTime formerProject newRatio stageCompleted start ' classVariableNames: 'History ' poolDictionaries: '' category: 'Morphic-Windows'! !ComplexProgressIndicator commentStamp: '' prior: 0! Note: in an effort to remove the progress indicator if a walkback occurs, #withProgressDo: must be sent from the current uiProcess. Hopefully we can relax this restriction in the future. ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 15:31'! backgroundWorldDisplay | f | self flag: #bob. "really need a better way to do this" "World displayWorldSafely." "ugliness to try to track down a possible error" [World displayWorld] ifError: [ :a :b | stageCompleted _ 999. f _ FileDirectory default fileNamed: 'bob.errors'. f nextPutAll: a printString,' ',b printString; cr; cr. f nextPutAll: 'worlds equal ',(formerWorld == World) printString; cr; cr. f nextPutAll: thisContext longStack; cr; cr. f nextPutAll: formerProcess suspendedContext longStack; cr; cr. f close. 1 beep. ]. ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 7/23/2000 17:29'! forkProgressWatcher | killTarget | [ [stageCompleted < 999 and: [formerProject == Project current and: [formerWorld == World and: [translucentMorph world notNil and: [formerProcess suspendedContext notNil and: [Project uiProcess == formerProcess]]]]]] whileTrue: [ translucentMorph setProperty: #revealTimes toValue: {(Time millisecondClockValue - start max: 1). (estimate * newRatio max: 1)}. translucentMorph changed. translucentMorph owner addMorphInLayer: translucentMorph. (Time millisecondClockValue - WorldState lastCycleTime) abs > 500 ifTrue: [ self backgroundWorldDisplay ]. (Delay forMilliseconds: 100) wait. ]. translucentMorph removeProperty: #revealTimes. self loadingHistoryAt: 'total' add: (Time millisecondClockValue - start max: 1). killTarget _ targetMorph ifNotNil: [ targetMorph valueOfProperty: #deleteOnProgressCompletion ]. formerWorld == World ifTrue: [ translucentMorph delete. killTarget ifNotNil: [killTarget delete]. ] ifFalse: [ translucentMorph privateDeleteWithAbsolutelyNoSideEffects. killTarget ifNotNil: [killTarget privateDeleteWithAbsolutelyNoSideEffects]. ]. ] forkAt: 6. ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 5/23/2000 10:00'! historyCategory: aKey History ifNil: [History _ Dictionary new]. specificHistory _ History at: aKey ifAbsentPut: [Dictionary new]. ^specificHistory ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 5/23/2000 09:55'! loadingHistoryAt: aKey add: aNumber (self loadingHistoryDataForKey: aKey) add: aNumber. ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 5/23/2000 10:02'! loadingHistoryDataForKey: anObject | answer | answer _ specificHistory at: anObject ifAbsentPut: [OrderedCollection new]. answer size > 50 ifTrue: [ answer _ answer copyFrom: 25 to: answer size. specificHistory at: anObject put: answer. ]. ^answer ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 6/29/2000 11:31'! targetMorph: aMorph targetMorph _ aMorph! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'RAA 2/1/2001 17:00'! withProgressDo: aBlock | safetyFactor totals trialRect delta stageCompletedString | Smalltalk isMorphic ifFalse: [^aBlock value]. formerProject _ Project current. formerWorld _ World. formerProcess _ Processor activeProcess. targetMorph ifNil: [targetMorph _ ProgressTargetRequestNotification signal]. targetMorph ifNil: [ trialRect _ Rectangle center: Sensor cursorPoint extent: 80@80. delta _ trialRect amountToTranslateWithin: formerWorld bounds. trialRect _ trialRect translateBy: delta. translucentMorph _ TranslucentProgessMorph new opaqueBackgroundColor: Color white; bounds: trialRect; openInWorld: formerWorld. ] ifNotNil: [ translucentMorph _ TranslucentProgessMorph new setProperty: #morphicLayerNumber toValue: targetMorph morphicLayerNumber - 0.1; bounds: targetMorph boundsInWorld; openInWorld: targetMorph world. ]. stageCompleted _ 0. safetyFactor _ 1.1. "better to guess high than low" translucentMorph setProperty: #progressStageNumber toValue: 1. totals _ self loadingHistoryDataForKey: 'total'. newRatio _ 1.0. estimate _ totals size < 2 ifTrue: [ 15000 "be a pessimist" ] ifFalse: [ (totals sum - totals max) / (totals size - 1 max: 1) * safetyFactor. ]. start _ Time millisecondClockValue. self forkProgressWatcher. [ aBlock on: ProgressInitiationException do: [ :ex | ex sendNotificationsTo: [ :min :max :curr | "ignore this as it is inaccurate" ]. ]. ] on: ProgressNotification do: [ :note | stageCompletedString _ (note messageText findTokens: ' ') first. stageCompleted _ (stageCompletedString copyUpTo: $:) asNumber. cumulativeStageTime _ Time millisecondClockValue - start max: 1. prevData _ self loadingHistoryDataForKey: stageCompletedString. prevData isEmpty ifFalse: [ newRatio _ (cumulativeStageTime / (prevData average max: 1)) asFloat. ]. self loadingHistoryAt: stageCompletedString add: cumulativeStageTime. translucentMorph setProperty: #progressStageNumber toValue: stageCompleted + 1. note resume. ]. stageCompleted _ 999. "we may or may not get here" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ComplexProgressIndicator class instanceVariableNames: ''! !ComplexProgressIndicator class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 11:40'! historyReport " ComplexProgressIndicator historyReport " | answer data | History ifNil: [^1 beep]. answer _ String streamContents: [ :strm | (History keys asSortedCollection: [ :a :b | a asString <= b asString]) do: [ :k | strm nextPutAll: k printString; cr. data _ History at: k. (data keys asSortedCollection: [ :a :b | a asString <= b asString]) do: [ :dataKey | strm tab; nextPutAll: dataKey printString,' ', (data at: dataKey) asArray printString; cr. ]. strm cr. ]. ]. StringHolder new contents: answer contents; openLabel: 'Progress History'! ! Player subclass: #Component instanceVariableNames: 'model pinSpecs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Components'! !Component methodsFor: 'initialize' stamp: 'di 5/3/1998 20:23'! initComponentIn: aLayout model _ aLayout model. self nameMeIn: aLayout world. self color: Color lightCyan. self showPins. model addDependent: self! ! !Component methodsFor: 'naming' stamp: 'di 5/3/1998 19:48'! chooseNameLike: someName | stem otherNames i partName | stem _ someName. (stem size > 5 and: [stem endsWith: 'Morph']) ifTrue: [stem _ stem copyFrom: 1 to: stem size - 5]. stem _ stem first asLowercase asString , stem allButFirst. otherNames _ self class allInstVarNames asSet. "otherNames addAll: self world allKnownNames." i _ 1. [otherNames includes: (partName _ stem , i printString)] whileTrue: [i _ i + 1]. partName _ FillInTheBlank request: 'Please give this part a name' initialAnswer: partName. partName isEmpty ifTrue: [^ nil]. (otherNames includes: partName) ifTrue: [self inform: 'Sorry, that name is already used'. ^ nil]. ^ partName! ! !Component methodsFor: 'naming' stamp: 'di 5/3/1998 19:58'! externalName ^ self class name! ! !Component methodsFor: 'naming' stamp: 'di 5/3/1998 19:50'! nameMeIn: aWorld | stem otherNames i partName className | className _ self class name. stem _ className. (stem size > 5 and: [stem endsWith: 'Morph']) ifTrue: [stem _ stem copyFrom: 1 to: stem size - 5]. stem _ stem first asLowercase asString , stem allButFirst. otherNames _ Set newFrom: aWorld allKnownNames. i _ 1. [otherNames includes: (partName _ stem , i printString)] whileTrue: [i _ i + 1]. self setNamePropertyTo: partName! ! !Component methodsFor: 'naming' stamp: 'di 5/3/1998 19:51'! renameMe | newName | newName _ self chooseNameLike: self knownName. newName ifNil: [^ nil]. self setNamePropertyTo: newName! ! !Component methodsFor: 'drag and drop' stamp: 'ar 10/5/2000 20:03'! justDroppedInto: aMorph event: anEvent | theModel | theModel _ aMorph model. ((aMorph isKindOf: ComponentLayout) and: [theModel isKindOf: Component]) ifFalse: ["Disconnect prior to removal by move" (theModel isKindOf: Component) ifTrue: [self unwire. model _ nil]. ^ super justDroppedInto: aMorph event: anEvent]. theModel == model ifTrue: [^ self "Presumably just a move"]. self initComponentIn: aMorph. super justDroppedInto: aMorph event: anEvent.! ! !Component methodsFor: 'variables' stamp: 'di 1/18/2000 16:10'! addVariableNamed: varName | otherNames i partName | "Adjust name if necessary and add it" otherNames _ self class allInstVarNames. i _ nil. [i == nil ifTrue: [partName _ varName] ifFalse: [partName _ varName, i printString]. otherNames includes: partName] whileTrue: [i == nil ifTrue: [i _ 1] ifFalse: [i _ i + 1]]. self class addInstVarName: partName. "Now compile read method and write-with-change method" self class compile: (String streamContents: [:s | s nextPutAll: partName; cr; tab; nextPutAll: '^', partName]) classified: 'view access' notifying: nil. self class compile: (String streamContents: [:s | s nextPutAll: partName, 'Set: newValue'; cr; tab; nextPutAll: partName, ' _ newValue.'; cr; tab; nextPutAll: 'self changed: #', partName, '.'; cr; tab; nextPutAll: '^ true' "for components that expect a boolean for accept"]) classified: 'view access' notifying: nil. ^ Array with: partName asSymbol with: (partName , 'Set:') asSymbol! ! !Component methodsFor: 'variables' stamp: 'di 5/3/1998 19:58'! removeVariableNamed: varName self class removeSelector: varName. self class removeSelector: (varName , 'Set:') asSymbol. self class removeInstVarName: varName asString! ! !Component methodsFor: 'misc' stamp: 'ar 10/5/2000 17:30'! addAddHandMenuItemsForHalo: aMenu hand: aHandMorph super addAddHandMenuItemsForHalo: aMenu hand: aHandMorph. aMenu add: 'delete' target: self action: #dismissMorph:! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Component class instanceVariableNames: ''! !Component class methodsFor: 'as yet unclassified' stamp: 'di 4/17/1998 14:02'! acceptsLoggingOfCompilation "Log everything for now" ^ true! ! !Component class methodsFor: 'as yet unclassified' stamp: 'di 4/18/1998 11:08'! addSlotNamed: aName (self allInstVarNames includes: aName) ifTrue: [self error: 'Duplicate slot name']. self addInstVarName: aName. ! ! !Component class methodsFor: 'as yet unclassified' stamp: 'di 4/13/98 12:15'! includeInNewMorphMenu "Only include instances of subclasses of me" ^ self ~~ Component! ! !Component class methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 19:55'! wantsChangeSetLogging "Log changes for Component itself, but not for automatically-created subclasses like Component1, Component2" "^ self == Component or: [(self class name beginsWith: 'Component') not]" "Log everything for now" false ifTrue: [self halt "DONT FORGET TO REORDER FILEOUT"]. ^ true! ! Component subclass: #Component1 instanceVariableNames: 'printComponent1value listComponent1selectedItem functionComponent1output functionComponent3output listComponent3selectedItem functionComponent4output listComponent4selectedItem functionComponent5output listComponent2selectedItem functionComponent2output ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Components Built'! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:31'! functionComponent1output ^functionComponent1output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:31'! functionComponent1outputSet: newValue functionComponent1output _ newValue. self changed: #functionComponent1output! ! !Component1 methodsFor: 'view access' stamp: 'di 9/15/1998 17:13'! functionComponent2output ^functionComponent2output! ! !Component1 methodsFor: 'view access' stamp: 'di 9/15/1998 17:13'! functionComponent2outputSet: newValue functionComponent2output _ newValue. self changed: #functionComponent2output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:35'! functionComponent3output ^functionComponent3output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:35'! functionComponent3outputSet: newValue functionComponent3output _ newValue. self changed: #functionComponent3output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:42'! functionComponent4output ^functionComponent4output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:42'! functionComponent4outputSet: newValue functionComponent4output _ newValue. self changed: #functionComponent4output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:49'! functionComponent5output ^functionComponent5output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:49'! functionComponent5outputSet: newValue functionComponent5output _ newValue. self changed: #functionComponent5output! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:30'! listComponent1selectedItem ^listComponent1selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:30'! listComponent1selectedItemSet: newValue listComponent1selectedItem _ newValue. self changed: #listComponent1selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 9/15/1998 17:12'! listComponent2selectedItem ^listComponent2selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 9/15/1998 17:12'! listComponent2selectedItemSet: newValue listComponent2selectedItem _ newValue. self changed: #listComponent2selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:41'! listComponent3selectedItem ^listComponent3selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:41'! listComponent3selectedItemSet: newValue listComponent3selectedItem _ newValue. self changed: #listComponent3selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:48'! listComponent4selectedItem ^listComponent4selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:48'! listComponent4selectedItemSet: newValue listComponent4selectedItem _ newValue. self changed: #listComponent4selectedItem! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:29'! printComponent1value ^printComponent1value! ! !Component1 methodsFor: 'view access' stamp: 'di 5/8/1998 23:29'! printComponent1valueSet: newValue printComponent1value _ newValue. self changed: #printComponent1value! ! !Component1 methodsFor: 'functions' stamp: 'di 5/8/1998 23:31'! functionComponent1a: a ^ SystemOrganization listAtCategoryNamed: a! ! !Component1 methodsFor: 'functions' stamp: 'di 9/15/1998 17:10'! functionComponent2a: a ^ Smalltalk at: a! ! !Component1 methodsFor: 'functions' stamp: 'di 5/8/1998 23:36'! functionComponent3a: a ^ a organization categories! ! !Component1 methodsFor: 'functions' stamp: 'di 5/8/1998 23:43'! functionComponent4a: a b: b ^ a organization listAtCategoryNamed: b! ! !Component1 methodsFor: 'functions' stamp: 'di 5/8/1998 23:51'! functionComponent5a: a b: b ^ a sourceCodeAt: b! ! MacExternalData variableWordSubclass: #ComponentInstance instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !ComponentInstance commentStamp: '' prior: 0! I am an ExternalData representation of a MacOS Scripting Component. I am typically subclassed to generate the relevant scripting component, for example, by ApplescriptInstance.! !ComponentInstance methodsFor: 'private' stamp: 'acg 9/21/1999 21:14'! primOpenDefaultConfiguration: type subtype: subtype ^TestOSAPlugin doPrimitive: 'primOpenDefaultConfiguration:subtype:' withArguments: {type. subtype}! ! !ComponentInstance methodsFor: 'private' stamp: 'acg 9/21/1999 21:18'! type: typeString subtype: subtypeString "Associate this object instance with an instance of the generic scripting component. Answer self." self primOpenDefaultConfiguration: (DescType of: typeString) subtype: (DescType of: subtypeString)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ComponentInstance class instanceVariableNames: ''! !ComponentInstance class methodsFor: 'instance creation' stamp: 'acg 9/21/1999 21:24'! new ^super new: 1! ! !ComponentInstance class methodsFor: 'instance creation' stamp: 'acg 9/21/1999 21:24'! new: anInteger ^self error: 'Create ComponentInstances with #new or #type:subtype'! ! !ComponentInstance class methodsFor: 'instance creation' stamp: 'acg 9/21/1999 21:20'! type: typeString subtype: subtypeString ^(super new: 1) type: typeString subtype: subtypeString! ! PasteUpMorph subclass: #ComponentLayout instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Components'! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 10:17'! acceptDroppingMorph: aMorph event: evt "Eschew all of PasteUp's mechanism for now" self addMorph: aMorph. ! ! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 09:44'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. menu addLine. menu add: 'inspect model in morphic' action: #inspectModelInMorphic! ! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'ar 10/6/2000 19:20'! allKnownNames ^ super allKnownNames, (self submorphs collect: [:m | m knownName] thenSelect: [:m | m ~~ nil])! ! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'di 5/2/1998 21:36'! createCustomModel "Create a model object for this world if it does not yet have one. The default model for an EditView is a Component." model == nil ifFalse: [^ self]. "already has a model" model _ Component newSubclass new. ! ! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'di 1/17/2000 16:36'! initialize super initialize. self createCustomModel. self extent: 384@256! ! !ComponentLayout methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 09:41'! inspectModelInMorphic | insp | insp _ InspectorBrowser openAsMorphOn: self model. self world addMorph: insp; startStepping: insp! ! MorphicModel subclass: #ComponentLikeModel instanceVariableNames: 'pinSpecs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Components'! !ComponentLikeModel methodsFor: 'initialization' stamp: 'di 5/3/1998 09:24'! duplicate: newGuy from: oldGuy "oldGuy has just been duplicated and will stay in this world. Make sure all the ComponentLikeModel requirements are carried out for the copy. Ask user to rename it. " newGuy installModelIn: oldGuy pasteUpMorph. newGuy copySlotMethodsFrom: oldGuy slotName.! ! !ComponentLikeModel methodsFor: 'compilation' stamp: 'di 5/3/1998 09:25'! choosePartName "When I am renamed, get a slot, make default methods, move any existing methods." | old | (self pasteUpMorph model isKindOf: Component) ifTrue: [self knownName ifNil: [^ self nameMeIn: self pasteUpMorph] ifNotNil: [^ self renameMe]]. old _ slotName. super choosePartName. slotName ifNil: [^ self]. "user chose bad slot name" self model: self world model slotName: slotName. old == nil ifTrue: [self compilePropagationMethods] ifFalse: [self copySlotMethodsFrom: old]. "old ones not erased!!"! ! !ComponentLikeModel methodsFor: 'components' stamp: 'ar 10/5/2000 17:25'! addAddHandMenuItemsForHalo: aMenu hand: aHandMorph super addAddHandMenuItemsForHalo: aMenu hand: aHandMorph. aMenu addLine. aMenu add: 'inspect' target: self action: #inspectInMorphic. aMenu add: 'delete' target: self action: #dismissMorph:.! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/1/1998 16:14'! addPinFromSpec: pinSpec | pin | pin _ PinMorph new component: self pinSpec: pinSpec. self addMorph: pin. pin placeFromSpec. ^ pin! ! !ComponentLikeModel methodsFor: 'components' stamp: 'sw 10/23/2000 18:13'! delete "Delete the receiver. Possibly put up confirming dialog. Abort if user changes mind" (model isKindOf: Component) ifTrue: [^ self deleteComponent]. (model isKindOf: MorphicModel) ifFalse: [^ super delete]. slotName ifNotNil: [(PopUpMenu confirm: 'Shall I remove the slot ', slotName, ' along with all associated methods?') ifTrue: [(model class selectors select: [:s | s beginsWith: slotName]) do: [:s | model class removeSelector: s]. (model class instVarNames includes: slotName) ifTrue: [model class removeInstVarName: slotName]] ifFalse: [(PopUpMenu confirm: '...but should I at least dismiss this morph? [choose no to leave everything unchanged]') ifFalse: [^ self]]]. super delete. ! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/2/1998 15:07'! deleteComponent model removeDependent: self. self pinsDo: [:pin | pin delete]. ^ super delete! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 4/29/1998 09:49'! extent: newExtent super extent: newExtent. self submorphsDo: [:m | (m isKindOf: PinMorph) ifTrue: [m placeFromSpec]]! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/5/1998 00:57'! initComponentIn: aLayout model _ aLayout model. self nameMeIn: aLayout. self color: Color lightCyan. self initPinSpecs. self initFromPinSpecs. self showPins. model addDependent: self! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/1/1998 16:31'! initFromPinSpecs "no-op for default"! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/3/1998 20:11'! initPinSpecs "no-op for default" pinSpecs _ Array new. ! ! !ComponentLikeModel methodsFor: 'components' stamp: 'ar 10/5/2000 20:03'! justDroppedInto: aMorph event: anEvent | theModel | theModel _ aMorph modelOrNil. ((aMorph isKindOf: ComponentLayout) and: [theModel isKindOf: Component]) ifFalse: ["Disconnect prior to removal by move" (theModel isKindOf: Component) ifTrue: [self unwire. model _ nil]. ^ super justDroppedInto: aMorph event: anEvent]. theModel == model ifTrue: [^ self "Presumably just a move"]. self initComponentIn: aMorph. super justDroppedInto: aMorph event: anEvent! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 4/26/1998 10:40'! nameMeIn: aWorld | stem otherNames i partName className | className _ self class name. stem _ className. (stem size > 5 and: [stem endsWith: 'Morph']) ifTrue: [stem _ stem copyFrom: 1 to: stem size - 5]. stem _ stem first asLowercase asString , stem allButFirst. otherNames _ Set newFrom: aWorld allKnownNames. i _ 1. [otherNames includes: (partName _ stem , i printString)] whileTrue: [i _ i + 1]. self setNamePropertyTo: partName! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/3/1998 20:18'! pinSpecs ^ pinSpecs! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/2/1998 15:09'! pinsDo: pinBlock self submorphsDo: [:m | (m isKindOf: PinMorph) ifTrue: [pinBlock value: m]]! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 5/3/1998 09:26'! renameMe | otherNames newName | otherNames _ Set newFrom: self pasteUpMorph allKnownNames. newName _ FillInTheBlank request: 'Please give this new a name' initialAnswer: self knownName. newName isEmpty ifTrue: [^ nil]. (otherNames includes: newName) ifTrue: [self inform: 'Sorry, that name is already used'. ^ nil]. self setNamePropertyTo: newName! ! !ComponentLikeModel methodsFor: 'components' stamp: 'di 4/29/1998 15:16'! showPins "Make up sensitized pinMorphs for each of my interface variables" self pinSpecs do: [:pinSpec | self addPinFromSpec: pinSpec]! ! AbstractAnimation subclass: #CompositeAnimation instanceVariableNames: 'children childLoopCounts ' classVariableNames: '' poolDictionaries: '' category: 'Balloon3D-Wonderland Time'! !CompositeAnimation commentStamp: '' prior: 0! This class extends the basic Animation class for composite animations (animations built of other animations). It adds an instance variable for the component child animations, and a method for adding them to this collection. ! !CompositeAnimation methodsFor: 'initialization' stamp: 'jsp 2/26/1999 13:53'! append: child "Adds a child animation to the composite animation." child stop. children addLast: child. childLoopCounts addLast: (child getLoopCount). ! ! !CompositeAnimation methodsFor: 'initialization' stamp: 'jsp 2/26/1999 12:39'! initialize "Creates an empty OrderedCollection for the child animations." children _ OrderedCollection new. childLoopCounts _ OrderedCollection new. ! ! !CompositeAnimation methodsFor: 'management' stamp: 'jsp 2/26/1999 13:55'! prologue: currentTime "comment stating purpose of message" | index | super prologue: currentTime. index _ 1. children do: [:child | child setLoopCount: (childLoopCounts at: index). index _ index + 1. ]. ! ! !CompositeAnimation methodsFor: 'private' stamp: 'jsp 2/26/1999 14:35'! getComponents "Return the component animations of the sequential animation" ^ children. ! ! !CompositeAnimation methodsFor: 'private' stamp: 'jsp 2/26/1999 14:28'! scaleDuration: scaleAmount "Scales the animation's duration by the specified amount" | i | super scaleDuration: scaleAmount. i _ 1. children do: [:child | child scaleDuration: (scaleAmount / (childLoopCounts at: i)). i _ i + 1 ]. ! ! !CompositeAnimation methodsFor: 'reversing' stamp: 'jsp 2/26/1999 14:41'! reverseDirection "Changes the direction an animation runs in (forward or in reverse)" super reverseDirection. children do: [:child | child reverseDirection ]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompositeAnimation class instanceVariableNames: ''! !CompositeAnimation class methodsFor: 'initialization' stamp: 'jsp 2/11/1999 14:52'! new "Creates a new Composite animation and makes sure it gets initialized." ^ super new initialize. ! ! VoiceEvent subclass: #CompositeEvent instanceVariableNames: 'timedEvents ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !CompositeEvent methodsFor: 'initialization' stamp: 'len 8/28/1999 23:15'! initialize: anInteger timedEvents _ SortedCollection new: anInteger! ! !CompositeEvent methodsFor: 'accessing-private' stamp: 'len 8/28/1999 22:54'! timedEvents ^ timedEvents! ! !CompositeEvent methodsFor: 'accessing-private' stamp: 'len 8/28/1999 22:54'! timedEvents: aCollection timedEvents _ aCollection! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 22:57'! add: aVoiceEvent ^ self add: aVoiceEvent at: self lastTime! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 22:57'! add: aVoiceEvent at: time ^ self timedEvents add: time -> aVoiceEvent! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 22:56'! add: aVoiceEvent delayed: time ^ self add: aVoiceEvent at: self lastTime + time! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/29/1999 02:16'! addAll: aCollection aCollection do: [ :each | self add: each]. ^ aCollection! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/29/1999 03:08'! at: anInteger ^ (self timedEvents at: anInteger) value! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 23:18'! duration "Answer the duration (in seconds) of the receiver." ^ self lastTime / 1000.0! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/29/1999 03:09'! first ^ self at: 1! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/29/1999 03:09'! last ^ self at: self size! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 23:17'! lastTime | last | self isEmpty ifTrue: [^ 0]. last _ self timedEvents last. ^ last key + (last value duration * 1000) rounded! ! !CompositeEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 23:16'! size ^ self timedEvents size! ! !CompositeEvent methodsFor: 'converting' stamp: 'len 9/29/1999 02:52'! asArray ^ (1 to: self size) collect: [ :each | self at: each]! ! !CompositeEvent methodsFor: 'converting' stamp: 'len 9/27/1999 22:48'! asPHOString | stream | stream _ WriteStream on: String new. self do: [ :each | stream nextPutAll: each asPHOString; nextPut: Character cr]. ^ stream contents! ! !CompositeEvent methodsFor: 'copying' stamp: 'len 8/28/1999 23:16'! copy | answer | answer _ self class new: self size. self timedEvents do: [ :each | answer add: each value copy at: each key]. ^ answer! ! !CompositeEvent methodsFor: 'enumerating' stamp: 'len 12/14/1999 05:49'! detect: aBlock self detect: aBlock ifNone: [self error: 'event not found']! ! !CompositeEvent methodsFor: 'enumerating' stamp: 'len 12/14/1999 05:49'! detect: aBlock ifNone: exceptionBlock self do: [ :each | (aBlock value: each) ifTrue: [^ each]]. ^ exceptionBlock value! ! !CompositeEvent methodsFor: 'enumerating' stamp: 'len 8/28/1999 23:09'! do: aBlock self timedEvents do: [ :each | aBlock value: each value]! ! !CompositeEvent methodsFor: 'playing' stamp: 'len 12/22/1999 03:32'! playOn: aVoice at: time self timedEvents do: [ :each | each value playOn: aVoice at: each key + time]. aVoice flush! ! !CompositeEvent methodsFor: 'testing' stamp: 'len 8/28/1999 22:56'! isEmpty ^ self timedEvents isEmpty! ! !CompositeEvent methodsFor: 'transforming' stamp: 'len 8/29/1999 03:12'! compress: aNumber self stretch: aNumber reciprocal! ! !CompositeEvent methodsFor: 'transforming' stamp: 'len 8/29/1999 03:11'! delay: time self timedEvents do: [ :each | each key: each key + time]! ! !CompositeEvent methodsFor: 'transforming' stamp: 'len 9/29/1999 05:16'! pitchBy: aNumber self do: [ :each | each pitchBy: aNumber]! ! !CompositeEvent methodsFor: 'transforming' stamp: 'len 8/29/1999 03:13'! stretch: aNumber self do: [ :each | each stretch: aNumber]. self timedEvents do: [ :each | each key: (each key * aNumber) rounded]! ! !CompositeEvent methodsFor: 'editing' stamp: 'len 9/28/1999 01:08'! inspect "Open a OrderedCollectionInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." OrderedCollectionInspector openOn: self withEvalPane: true! ! !CompositeEvent methodsFor: 'editing' stamp: 'len 9/28/1999 01:08'! inspectWithLabel: aLabel "Open a OrderedCollectionInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." OrderedCollectionInspector openOn: self withEvalPane: true withLabel: aLabel! ! !CompositeEvent methodsFor: 'private' stamp: 'len 12/13/1999 02:47'! recomputeTimes | oldTimedEvents | oldTimedEvents _ timedEvents. timedEvents _ SortedCollection new: oldTimedEvents size. oldTimedEvents do: [ :each | self add: each value]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompositeEvent class instanceVariableNames: ''! !CompositeEvent class methodsFor: 'instance creation' stamp: 'len 8/28/1999 23:14'! new ^ self new: 10! ! !CompositeEvent class methodsFor: 'instance creation' stamp: 'len 8/28/1999 23:15'! new: anInteger ^ self basicNew initialize: anInteger! ! DisplayTransform subclass: #CompositeTransform instanceVariableNames: 'globalTransform localTransform ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Transformations'! !CompositeTransform commentStamp: '' prior: 0! A composite transform provides the effect of several levels of coordinate transformations.! !CompositeTransform methodsFor: 'initialization' stamp: 'di 10/26/1999 17:08'! composedWith: aTransform "Return a new transform that has the effect of transforming points first by the receiver and then by the argument." self isIdentity ifTrue: [^ aTransform]. aTransform isIdentity ifTrue: [^ self]. ^ CompositeTransform new globalTransform: self localTransform: aTransform! ! !CompositeTransform methodsFor: 'initialization' stamp: 'di 3/4/98 19:17'! globalTransform: gt localTransform: lt globalTransform _ gt. localTransform _ lt! ! !CompositeTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 20:00'! isCompositeTransform ^true! ! !CompositeTransform methodsFor: 'testing' stamp: 'di 3/4/98 19:18'! isIdentity ^ globalTransform isIdentity and: [localTransform isIdentity]! ! !CompositeTransform methodsFor: 'testing' stamp: 'di 3/4/98 19:18'! isPureTranslation ^ globalTransform isPureTranslation and: [localTransform isPureTranslation]! ! !CompositeTransform methodsFor: 'transformations' stamp: 'di 10/1/1998 13:51'! invert: aPoint ^ globalTransform invert: (localTransform invert: aPoint)! ! !CompositeTransform methodsFor: 'transformations' stamp: 'di 3/4/98 19:20'! transform: aPoint ^ localTransform transform: (globalTransform transform: aPoint)! ! !CompositeTransform methodsFor: 'accessing' stamp: 'di 10/26/1999 17:06'! angle ^ localTransform angle + globalTransform angle! ! !CompositeTransform methodsFor: 'accessing' stamp: 'ar 11/2/1998 19:45'! inverseTransformation "Return the inverse transformation of the receiver" ^self species new globalTransform: localTransform inverseTransformation localTransform: globalTransform inverseTransformation! ! !CompositeTransform methodsFor: 'accessing' stamp: 'di 10/26/1999 15:40'! offset ^ (self localPointToGlobal: 0@0) negated! ! !CompositeTransform methodsFor: 'accessing' stamp: 'di 10/26/1999 15:39'! scale ^ localTransform scale * globalTransform scale! ! !CompositeTransform methodsFor: 'transforming points' stamp: 'ar 11/2/1998 16:39'! globalPointToLocal: aPoint "Transform aPoint from global coordinates into local coordinates" ^localTransform globalPointToLocal: (globalTransform globalPointToLocal: aPoint)! ! !CompositeTransform methodsFor: 'transforming points' stamp: 'ar 11/2/1998 16:39'! localPointToGlobal: aPoint "Transform aPoint from global coordinates into local coordinates" ^globalTransform localPointToGlobal: (localTransform localPointToGlobal: aPoint)! ! !CompositeTransform methodsFor: 'converting' stamp: 'ar 11/2/1998 20:00'! asCompositeTransform ^self! ! !CompositeTransform methodsFor: 'converting' stamp: 'ar 11/2/1998 19:56'! asMatrixTransform2x3 ^globalTransform asMatrixTransform2x3 composedWithLocal: localTransform asMatrixTransform2x3! ! !CompositeTransform methodsFor: 'converting' stamp: 'di 10/26/1999 17:03'! asMorphicTransform "Squash a composite transform down to a simple one" ^ MorphicTransform offset: self offset angle: self angle scale: self scale! ! !CompositeTransform methodsFor: 'encoding' stamp: 'ls 3/19/2000 16:28'! encodeForRemoteCanvas ^String streamContents: [ :str | str nextPutAll: 'Composite,'; nextPutAll: '('; nextPutAll: globalTransform encodeForRemoteCanvas; nextPutAll: ')('; nextPutAll: localTransform encodeForRemoteCanvas; nextPutAll: ')' ]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompositeTransform class instanceVariableNames: ''! !CompositeTransform class methodsFor: 'instance creation' stamp: 'ls 3/19/2000 16:49'! fromRemoteCanvasEncoding: encoding | firstStart firstEnd firstEncoding firstTransform secondStart secondEnd secondEncoding secondTransform | "format: Composite,(enc1)(enc2)" "decode the first encoding" firstStart := encoding indexOf: $(. firstStart = 0 ifTrue: [ self error: 'invalid encoding' ]. firstEnd := encoding findCloseParenthesisFor: firstStart. firstEncoding := encoding copyFrom: firstStart+1 to: firstEnd-1. firstTransform := DisplayTransform fromRemoteCanvasEncoding: firstEncoding. "decode the second encoding" secondStart := firstEnd + 1. (encoding at: secondStart) = $( ifFalse: [ ^self error: 'invalid encoding' ]. secondEnd := encoding findCloseParenthesisFor: secondStart. secondEncoding := encoding copyFrom: secondStart+1 to: secondEnd-1. secondTransform := DisplayTransform fromRemoteCanvasEncoding: secondEncoding. "put it together" ^self globalTransform: firstTransform localTransform: secondTransform! ! !CompositeTransform class methodsFor: 'instance creation' stamp: 'ls 3/19/2000 16:44'! globalTransform: gt localTransform: lt ^self new globalTransform: gt localTransform: lt! ! Voice subclass: #CompositeVoice instanceVariableNames: 'voices ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !CompositeVoice methodsFor: 'initialization' stamp: 'len 8/28/1999 04:00'! initialize super initialize. self voices: OrderedCollection new! ! !CompositeVoice methodsFor: 'accessing' stamp: 'len 9/13/1999 00:00'! add: aVoice ^ self voices add: aVoice! ! !CompositeVoice methodsFor: 'accessing' stamp: 'len 8/28/1999 04:01'! voices ^ voices! ! !CompositeVoice methodsFor: 'accessing' stamp: 'len 8/28/1999 04:00'! voices: aCollection voices _ aCollection! ! !CompositeVoice methodsFor: 'enumerating' stamp: 'len 8/29/1999 02:21'! do: aBlock self voices do: aBlock! ! !CompositeVoice methodsFor: 'playing' stamp: 'len 12/22/1999 03:51'! flush "Play all the events in the queue." super flush. self do: [ :each | each flush]! ! !CompositeVoice methodsFor: 'playing' stamp: 'len 9/26/1999 17:21'! playGesturalEvent: event at: time self do: [ :each | each playGesturalEvent: event at: time]! ! !CompositeVoice methodsFor: 'playing' stamp: 'len 9/26/1999 17:21'! playPhoneticEvent: event at: time self do: [ :each | each playPhoneticEvent: event at: time]! ! !CompositeVoice methodsFor: 'playing' stamp: 'len 9/26/1999 17:22'! reset "Reset the state of the receiver." super reset. self do: [ :each | each reset]! ! CharacterScanner subclass: #CompositionScanner instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace ' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !CompositionScanner commentStamp: '' prior: 0! CompositionScanners are used to measure text and determine where line breaks and space padding should occur.! !CompositionScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 17:21'! composeFrom: startIndex inRectangle: lineRectangle firstLine: firstLine leftSide: leftSide rightSide: rightSide "Answer an instance of TextLineInterval that represents the next line in the paragraph." | runLength done stopCondition | "Set up margins" leftMargin _ lineRectangle left. leftSide ifTrue: [leftMargin _ leftMargin + (firstLine ifTrue: [textStyle firstIndent] ifFalse: [textStyle restIndent])]. destX _ spaceX _ leftMargin. rightMargin _ lineRectangle right. rightSide ifTrue: [rightMargin _ rightMargin - textStyle rightIndent]. lastIndex _ startIndex. "scanning sets last index" destY _ lineRectangle top. lineHeight _ baseline _ 0. "Will be increased by setFont" self setStopConditions. "also sets font" runLength _ text runLengthFor: startIndex. runStopIndex _ (lastIndex _ startIndex) + (runLength - 1). line _ (TextLine start: lastIndex stop: 0 internalSpaces: 0 paddingWidth: 0) rectangle: lineRectangle; leftMargin: leftMargin. spaceCount _ 0. done _ false. [done] whileFalse: [stopCondition _ self scanCharactersFrom: lastIndex to: runStopIndex in: text string rightX: rightMargin stopConditions: stopConditions kern: kern. "See setStopConditions for stopping conditions for composing." (self perform: stopCondition) ifTrue: [^ line lineHeight: lineHeight + textStyle leading baseline: baseline + textStyle leading]]! ! !CompositionScanner methodsFor: 'scanning' stamp: 'ar 5/17/2000 17:21'! composeLine: lineIndex fromCharacterIndex: startIndex inParagraph: aParagraph "Answer an instance of TextLineInterval that represents the next line in the paragraph." | runLength done stopCondition | destX _ spaceX _ leftMargin _ aParagraph leftMarginForCompositionForLine: lineIndex. destY _ 0. rightMargin _ aParagraph rightMarginForComposition. leftMargin >= rightMargin ifTrue: [self error: 'No room between margins to compose']. lastIndex _ startIndex. "scanning sets last index" lineHeight _ textStyle lineGrid. "may be increased by setFont:..." baseline _ textStyle baseline. self setStopConditions. "also sets font" runLength _ text runLengthFor: startIndex. runStopIndex _ (lastIndex _ startIndex) + (runLength - 1). line _ TextLineInterval start: lastIndex stop: 0 internalSpaces: 0 paddingWidth: 0. spaceCount _ 0. done _ false. [done] whileFalse: [stopCondition _ self scanCharactersFrom: lastIndex to: runStopIndex in: text string rightX: rightMargin stopConditions: stopConditions kern: kern. "See setStopConditions for stopping conditions for composing." (self perform: stopCondition) ifTrue: [^line lineHeight: lineHeight + textStyle leading baseline: baseline + textStyle leading]]! ! !CompositionScanner methodsFor: 'scanning' stamp: 'ar 1/8/2000 14:36'! setActualFont: aFont "Keep track of max height and ascent for auto lineheight" | descent | super setActualFont: aFont. lineHeight == nil ifTrue: [descent _ font descent. baseline _ font ascent. lineHeight _ baseline + descent] ifFalse: [descent _ lineHeight - baseline max: font descent. baseline _ baseline max: font ascent. lineHeight _ lineHeight max: baseline + descent]! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:54'! cr "Answer true. Set up values for the text line interval currently being composed." line stop: lastIndex. spaceX _ destX. line paddingWidth: rightMargin - spaceX. ^true! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:54'! crossedX "There is a word that has fallen across the right edge of the composition rectangle. This signals the need for wrapping which is done to the last space that was encountered, as recorded by the space stop condition." spaceCount >= 1 ifTrue: ["The common case. First back off to the space at which we wrap." line stop: spaceIndex. lineHeight _ lineHeightAtSpace. baseline _ baselineAtSpace. spaceCount _ spaceCount - 1. spaceIndex _ spaceIndex - 1. "Check to see if any spaces preceding the one at which we wrap. Double space after punctuation, most likely." [(spaceCount > 1 and: [(text at: spaceIndex) = Space])] whileTrue: [spaceCount _ spaceCount - 1. "Account for backing over a run which might change width of space." font _ text fontAt: spaceIndex withStyle: textStyle. spaceIndex _ spaceIndex - 1. spaceX _ spaceX - (font widthOf: Space)]. line paddingWidth: rightMargin - spaceX. line internalSpaces: spaceCount] ifFalse: ["Neither internal nor trailing spaces -- almost never happens." lastIndex _ lastIndex - 1. [destX <= rightMargin] whileFalse: [destX _ destX - (font widthOf: (text at: lastIndex)). lastIndex _ lastIndex - 1]. spaceX _ destX. line paddingWidth: rightMargin - destX. line stop: (lastIndex max: line first)]. ^true! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:54'! endOfRun "Answer true if scanning has reached the end of the paragraph. Otherwise step conditions (mostly install potential new font) and answer false." | runLength | lastIndex = text size ifTrue: [line stop: lastIndex. spaceX _ destX. line paddingWidth: rightMargin - destX. ^true] ifFalse: [runLength _ (text runLengthFor: (lastIndex _ lastIndex + 1)). runStopIndex _ lastIndex + (runLength - 1). self setStopConditions. ^false] ! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/8/2000 14:37'! placeEmbeddedObject: anchoredMorph | descent | (super placeEmbeddedObject: anchoredMorph) ifFalse: [^ false]. descent _ lineHeight - baseline. lineHeight _ lineHeight max: anchoredMorph height. baseline _ lineHeight - descent. ^ true! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 5/18/2000 16:48'! setFont super setFont. stopConditions == DefaultStopConditions ifTrue:[stopConditions _ stopConditions copy]. stopConditions at: Space asciiValue + 1 put: #space.! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/8/2000 14:37'! setStopConditions "Set the font and the stop conditions for the current run." self setFont! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:55'! space "Record left x and character index of the space character just encounted. Used for wrap-around. Answer whether the character has crossed the right edge of the composition rectangle of the paragraph." spaceX _ destX. destX _ spaceX + spaceWidth. spaceIndex _ lastIndex. lineHeightAtSpace _ lineHeight. baselineAtSpace _ baseline. lastIndex _ lastIndex + 1. spaceCount _ spaceCount + 1. destX > rightMargin ifTrue: [^self crossedX]. ^false ! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'ar 1/9/2000 13:59'! tab "Advance destination x according to tab settings in the paragraph's textStyle. Answer whether the character has crossed the right edge of the composition rectangle of the paragraph." destX _ textStyle nextTabXFrom: destX leftMargin: leftMargin rightMargin: rightMargin. destX > rightMargin ifTrue: [^self crossedX]. lastIndex _ lastIndex + 1. ^false ! ! !CompositionScanner methodsFor: 'accessing' stamp: 'ar 1/8/2000 14:35'! rightX "Meaningful only when a line has just been composed -- refers to the line most recently composed. This is a subtrefuge to allow for easy resizing of a composition rectangle to the width of the maximum line. Useful only when there is only one line in the form or when each line is terminated by a carriage return. Handy for sizing menus and lists." ^spaceX! ! !CompositionScanner methodsFor: 'intialize-release' stamp: 'ar 5/17/2000 19:14'! forParagraph: aParagraph "Initialize the receiver for scanning the given paragraph." self initializeFromParagraph: aParagraph clippedBy: aParagraph clippingRectangle. ! ! TileLikeMorph subclass: #CompoundTileMorph instanceVariableNames: 'type testPart yesPart noPart ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Scripting Tiles'! !CompoundTileMorph commentStamp: '' prior: 0! A statement with other whole statements inside it. If-Then. Test.! !CompoundTileMorph methodsFor: 'initialization' stamp: 'sw 2/1/2001 16:36'! initialize | r | super initialize. self color: Color orange muchLighter. self borderWidth: 1. self layoutInset: 2. self listDirection: #topToBottom. self hResizing: #shrinkWrap; vResizing: #shrinkWrap. r _ AlignmentMorph newRow color: color; layoutInset: 0. r setProperty: #demandsBoolean toValue: true. r addMorphBack: (Morph new color: color; extent: 2@5). "spacer" r addMorphBack: (StringMorph new contents: 'Test'). r addMorphBack: (Morph new color: color; extent: 5@5). "spacer" r addMorphBack: (testPart _ BooleanScriptEditor new borderWidth: 0; layoutInset: 1). testPart color: Color transparent. self addMorphBack: r. r _ AlignmentMorph newRow color: color; layoutInset: 0. r addMorphBack: (Morph new color: color; extent: 30@5). "spacer" r addMorphBack: (StringMorph new contents: 'Yes'). r addMorphBack: (Morph new color: color; extent: 5@5). "spacer" r addMorphBack: (yesPart _ ScriptEditorMorph new borderWidth: 0; layoutInset: 2). yesPart color: Color transparent. self addMorphBack: r. r _ AlignmentMorph newRow color: color; layoutInset: 0. r addMorphBack: (Morph new color: color; extent: 35@5). "spacer" r addMorphBack: (StringMorph new contents: 'No'). r addMorphBack: (Morph new color: color; extent: 5@5). "spacer" r addMorphBack: (noPart _ ScriptEditorMorph new borderWidth: 0; layoutInset: 2). noPart color: Color transparent. self addMorphBack: r. self extent: 5@5. "will grow to fit" ! ! !CompoundTileMorph methodsFor: 'access' stamp: 'sw 8/11/1998 16:42'! associatedPlayer ^ nil! ! !CompoundTileMorph methodsFor: 'access' stamp: 'di 11/12/2000 15:31'! enclosingEditor "Return the next scriptor outward in the containment hierarchy" ^ self firstOwnerSuchThat: [:m | (m isKindOf: ScriptEditorMorph) or: [m isKindOf: CompoundTileMorph]]! ! !CompoundTileMorph methodsFor: 'access' stamp: 'sw 10/13/97 21:23'! scriptee "Pertains only when the test is outside a script?!!" ^ nil! ! !CompoundTileMorph methodsFor: 'code generation' stamp: 'sw 9/2/1999 15:22'! codeString ^ String streamContents: [:aStream | self storeCodeOn: aStream indent: 1] ! ! !CompoundTileMorph methodsFor: 'code generation' stamp: 'jm 5/29/1998 10:26'! storeCodeBlockFor: scriptPart on: aStream indent: tabCount | rows r | rows _ scriptPart tileRows. 1 to: rows size do: [:i | tabCount timesRepeat: [aStream tab]. r _ rows at: i. r do: [:t | t storeCodeOn: aStream indent: tabCount]. i < rows size ifTrue: [aStream nextPut: $.; cr]]. ! ! !CompoundTileMorph methodsFor: 'code generation' stamp: 'jm 5/29/1998 10:31'! storeCodeOn: aStream indent: tabCount aStream nextPut: $(. testPart storeCodeOn: aStream indent: 0. aStream nextPut: $); cr. tabCount + 1 timesRepeat: [aStream tab]. aStream nextPutAll: 'ifTrue: ['; cr. self storeCodeBlockFor: yesPart on: aStream indent: tabCount + 2. aStream nextPut: $]; cr. tabCount + 1 timesRepeat: [aStream tab]. aStream nextPutAll: 'ifFalse: ['; cr. self storeCodeBlockFor: noPart on: aStream indent: tabCount + 2. aStream nextPut: $]. ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 10/18/97 18:03'! install "Backstop for obscure cases"! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 10/13/97 21:25'! markEdited "Pertains only when the test is outside a script?!!" ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'di 10/17/97 16:31'! resultType ^ #command! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 5/13/1998 15:19'! rowOfRightTypeFor: aLayoutMorph forActor: anActor aLayoutMorph demandsBoolean ifTrue: [^ self error: 'oops, cannot do that, please close this']. ^ self! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 10/13/97 21:23'! scriptEdited "Pertains only when the test is outside a script?!!"! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'di 5/6/1998 21:10'! tile: tile isOnLineAfter: previousTile "Return true if the given tile is not on the same line at the previous tile or if the previous tile is nil." | tileRow previousRow | previousTile ifNil: [^ true]. tileRow _ tile owner. [tileRow isMemberOf: AlignmentMorph] whileFalse: [tileRow _ tileRow owner]. "find the owning row" previousRow _ previousTile owner. [previousRow isMemberOf: AlignmentMorph] whileFalse: [previousRow _ previousRow owner]. "find the owning row" ^ tileRow ~~ previousRow ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 1/20/2001 00:30'! tileRows "Answer a list of tile rows, in this case just one though it's compound" ^ Array with: (Array with: self fullCopyWithoutFormerOwner)! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'tk 8/4/1999 23:03'! tilesFrom: msgNode in: aScriptor "Construct an if-then from a parseTree." | sel | testPart playerScripted: aScriptor playerScripted. yesPart playerScripted: aScriptor playerScripted. noPart playerScripted: aScriptor playerScripted. testPart tilesFrom: msgNode receiver receiver. "strip off (test ~~ false)" sel _ msgNode selector key. sel == #ifTrue:ifFalse: ifTrue: [ yesPart tilesFrom: msgNode arguments first. noPart tilesFrom: msgNode arguments second]. sel == #ifTrue: ifTrue: [ yesPart tilesFrom: msgNode arguments first]. sel == #ifFalse: ifTrue: [ noPart tilesFrom: msgNode arguments first]. ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 10/13/97 20:25'! topEditor | editor | editor _ self outermostMorphThat: [:m | (m isKindOf: ScriptEditorMorph) or: [m isKindOf: CompoundTileMorph]]. ^ editor ifNil: [self] ifNotNil: [editor]! ! !CompoundTileMorph methodsFor: 'miscellaneous'! type ^ #compound ! ! !CompoundTileMorph methodsFor: 'mouse'! acceptDroppingMorph: aMorph event: evt "Forward the dropped morph to the appropriate part." (self targetPartFor: aMorph) acceptDroppingMorph: aMorph event: evt. ! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'di 7/3/1998 14:25'! handlesDragOver: evt ^ true ! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'di 10/17/97 21:36'! handlesMouseOver: evt ^ true ! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'di 9/14/1998 07:50'! handlesMouseOverDragging: evt ^ true ! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'jm 10/18/97 21:03'! mouseEnter: evt "Resume drop-tracking in enclosing editor" | ed | (ed _ self enclosingEditor) ifNotNil: [ed mouseLeave: evt]! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'di 9/14/1998 08:07'! mouseEnterDragging: evt "Test button state elsewhere if at all" ^ self mouseEnter: evt! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'jm 10/18/97 21:02'! mouseLeave: evt "Resume drop-tracking in enclosing editor" | ed | (ed _ self enclosingEditor) ifNotNil: [ed mouseEnter: evt]! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'di 9/14/1998 08:08'! mouseLeaveDragging: evt "Test button state elsewhere if at all" ^ self mouseLeave: evt! ! !CompoundTileMorph methodsFor: 'mouse' stamp: 'sw 2/1/98 16:40'! prepareToUndoDropOf: aMorph "needs to be here, as a no-op, owing to being hit obscurely on occasion"! ! !CompoundTileMorph methodsFor: 'mouse'! targetPartFor: aMorph "Return the row into which the given morph should be inserted." | centerY | centerY _ aMorph fullBounds center y. (Array with: testPart with: yesPart with: noPart) do: [:m | (centerY <= m bounds bottom) ifTrue: [^ m]]. ^ noPart ! ! !CompoundTileMorph methodsFor: 'mouse'! wantsDroppedMorph: aMorph event: evt ^ (aMorph isKindOf: TileMorph) or: [(aMorph isKindOf: ScriptEditorMorph) or: [(aMorph isKindOf: CompoundTileMorph) or: [aMorph isKindOf: CommandTilesMorph]]] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompoundTileMorph class instanceVariableNames: ''! !CompoundTileMorph class methodsFor: 'instance creation' stamp: 'di 6/22/97 09:07'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! Object subclass: #CompressedBoundaryShape instanceVariableNames: 'points leftFills rightFills lineWidths lineFills fillStyles ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Geometry'! !CompressedBoundaryShape commentStamp: '' prior: 0! This class represents a very compact representation of a boundary shape. It consists of a number of compressed arrays that can be handled by the balloon engine directly. Due to this, there are certain restrictions (see below). Boundaries are always represented by three subsequent points that define a quadratic bezier segment. It is recommended that for straight line segments the control point is set either to the previous or the next point. Instance variables: points Point storage area leftFills Containing the "left" fill index of each segment rightFills Containing the "right" fill index of each segment lineWidths Containing the line width of each segment lineFills Containing the line fill (e.g., line color) of each segment fillStyles Contains the actual fill styles referenced by the indexes RESTRICTIONS: None of the ShortRunArrays may contain a run of length Zero. Also, due to the use of ShortRunArrays a) you cannot have more than 32768 different fill styles b) you cannot have a line width that exceeds 32768 In case you have trouble with a), try to merge some of the fills into one. You might do so by converting colors to 32bit pixel values. In case you have trouble with b) you might change the general resolution of the compressed shape to have less accuracy. ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ls 10/10/1999 13:52'! bounds | min max width | points isEmpty ifTrue:[^0@0 corner: 1@1]. min _ max _ points first. points do:[:pt| min _ min min: pt. max _ max max: pt ]. width _ 0. lineWidths valuesDo:[:w| width _ width max: w]. ^(min corner: max) insetBy: (width negated asPoint)! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 21:55'! fillStyles ^fillStyles! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 21:55'! leftFills ^leftFills! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 21:55'! lineFills ^lineFills! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 21:55'! lineWidths ^lineWidths! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/4/1998 13:50'! numSegments ^points size // 3! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 20:42'! points ^points! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/3/1998 21:55'! rightFills ^rightFills! ! !CompressedBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/9/1998 14:09'! segments "Return all the segments in the receiver" | out | out _ WriteStream on: Array new. self segmentsDo:[:seg| out nextPut: seg]. ^out contents! ! !CompressedBoundaryShape methodsFor: 'editing' stamp: 'ar 11/12/1998 21:12'! collectFills: aBlock fillStyles _ fillStyles collect: aBlock.! ! !CompressedBoundaryShape methodsFor: 'editing' stamp: 'ar 11/12/1998 21:11'! copyAndCollectFills: aBlock ^self copy collectFills: aBlock! ! !CompressedBoundaryShape methodsFor: 'enumerating' stamp: 'ar 11/9/1998 14:10'! segmentsDo: aBlock "Enumerate all segments in the receiver and execute aBlock" | p1 p2 p3 | 1 to: points size by: 3 do:[:i| p1 _ points at: i. p2 _ points at: i+1. p3 _ points at: i+2. (p1 = p2 or:[p2 = p3]) ifTrue:[ aBlock value: (LineSegment from: p1 to: p3). ] ifFalse:[ aBlock value: (Bezier2Segment from: p1 via: p2 to: p3). ]. ].! ! !CompressedBoundaryShape methodsFor: 'private' stamp: 'ar 11/3/1998 18:03'! setPoints: pointList leftFills: leftFillList rightFills: rightFillList fillStyles: fillStyleList lineWidths: lineWidthList lineFills: lineFillList points _ pointList. leftFills _ leftFillList. rightFills _ rightFillList. lineWidths _ lineWidthList. lineFills _ lineFillList. fillStyles _ fillStyleList.! ! !CompressedBoundaryShape methodsFor: 'morphing' stamp: 'ar 9/3/1999 17:19'! morphFrom: srcShape to: dstShape at: ratio | scale unscale srcPoints dstPoints pt1 pt2 x y | scale _ (ratio * 1024) asInteger. scale < 0 ifTrue:[scale _ 0]. scale > 1024 ifTrue:[scale _ 1024]. unscale _ 1024 - scale. srcPoints _ srcShape points. dstPoints _ dstShape points. 1 to: points size do:[:i| pt1 _ srcPoints at: i. pt2 _ dstPoints at: i. x _ ((pt1 x * unscale) + (pt2 x * scale)) bitShift: -10. y _ ((pt1 y * unscale) + (pt2 y * scale)) bitShift: -10. points at: i put: x@y].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompressedBoundaryShape class instanceVariableNames: ''! !CompressedBoundaryShape class methodsFor: 'instance creation' stamp: 'ar 11/3/1998 16:28'! points: pointList leftFills: leftFillList rightFills: rightFillList fillStyles: fillStyleList ^self new setPoints: pointList leftFills: leftFillList rightFills: rightFillList fillStyles: fillStyleList! ! !CompressedBoundaryShape class methodsFor: 'instance creation' stamp: 'ar 11/3/1998 18:02'! points: pointList leftFills: leftFillList rightFills: rightFillList fillStyles: fillStyleList lineWidths: lineWidthList lineFills: lineFillList ^self new setPoints: pointList leftFills: leftFillList rightFills: rightFillList fillStyles: fillStyleList lineWidths: lineWidthList lineFills: lineFillList! ! Object subclass: #CompressedSoundData instanceVariableNames: 'channels soundClassName codecName loopEnd loopLength perceivedPitch samplingRate gain firstSample cachedSound ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !CompressedSoundData commentStamp: '' prior: 0! Instances of this class hold the data resulting from compressing a sound. Each carries a reference to the codec class that created it, so that it can reconstruct a sound similar to the original in response to the message asSound. In order to facilitate integration with existing sounds, a CompressedSoundData instance can masquerade as a sound by caching a copy of its original sound and delegating the essential sound-playing protocol to that cached copy. It should probably be made a subclass of AbstractSound to complete the illusion.! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 08:10'! channels "Answer an array of ByteArrays containing the compressed sound data for each channel." ^ channels ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:34'! channels: anArray channels _ anArray. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:34'! codecName "Answer the name of the sound codec used to compress this sound. Typically, this is the name of a class that can be used to decode the sound, but it is possible that the codec has not yet been implemented or is not filed into this image." ^ codecName ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:45'! codecName: aStringOrSymbol codecName _ aStringOrSymbol asSymbol. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:56'! firstSample "Answer the firstSample of the original sound." ^ firstSample ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:56'! firstSample: anInteger firstSample _ anInteger. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:55'! gain "Answer the gain of the original sound." ^ gain ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:56'! gain: aNumber gain _ aNumber. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 08:11'! loopEnd "Answer index of the last sample of the loop, or nil if the original sound was not looped." ^ loopEnd ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:35'! loopEnd: anInteger loopEnd _ anInteger. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 08:11'! loopLength "Answer length of the loop, or nil if the original sound was not looped." ^ loopLength ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:35'! loopLength: anInteger loopLength _ anInteger. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:39'! perceivedPitch "Answer the perceived pitch of the original sound. By convention, unpitched sounds (like drum hits) are given an arbitrary pitch of 100.0." ^ perceivedPitch ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:39'! perceivedPitch: aNumber perceivedPitch _ aNumber. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 08:13'! samplingRate "Answer the samplingRate of the original sound." ^ samplingRate ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:36'! samplingRate: aNumber samplingRate _ aNumber. ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:46'! soundClassName "Answer the class name of the uncompressed sound." ^ soundClassName ! ! !CompressedSoundData methodsFor: 'accessing' stamp: 'jm 2/2/1999 09:46'! soundClassName: aStringOrSymbol soundClassName _ aStringOrSymbol asSymbol. ! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 08:15'! asSound "Answer the result of decompressing the receiver." | codecClass | codecClass _ Smalltalk at: codecName ifAbsent: [^ self error: 'The codec for decompressing this sound is not available']. ^ (codecClass new decompressSound: self) reset ! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 08:49'! doControl cachedSound doControl ! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 08:49'! mixSampleCount: n into: aSoundBuffer startingAt: startIndex leftVol: leftVol rightVol: rightVol cachedSound mixSampleCount: n into: aSoundBuffer startingAt: startIndex leftVol: leftVol rightVol: rightVol ! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 20:49'! reset "This message is the cue to start behaving like a real sound in order to be played. We do this by caching a decompressed version of this sound. See also samplesRemaining." cachedSound == nil ifTrue: [cachedSound _ self asSound]. cachedSound reset ! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 20:44'! samples ^ self asSound samples! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'di 2/17/1999 20:49'! samplesRemaining "This message is the cue that the cached sound may no longer be needed. We know it is done playing when samplesRemaining=0." | samplesRemaining | samplesRemaining _ cachedSound samplesRemaining. samplesRemaining <= 0 ifTrue: [cachedSound _ nil]. ^ samplesRemaining! ! !CompressedSoundData methodsFor: 'as yet unclassified' stamp: 'RAA 12/8/2000 09:50'! compressWith: codecClass codecName == codecClass name asSymbol ifTrue: [^self]. ^self asSound compressWith: codecClass! ! !CompressedSoundData methodsFor: 'as yet unclassified' stamp: 'RAA 12/24/2000 08:53'! compressWith: codecClass atRate: aSamplingRate (codecName == codecClass name asSymbol and: [samplingRate = aSamplingRate]) ifTrue: [^self]. ^self asSound compressWith: codecClass atRate: aSamplingRate! ! !CompressedSoundData methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 19:25'! withEToySound: aByteArray samplingRate: anInteger soundClassName _ #SampledSound. channels _ {aByteArray}. codecName _ #GSMCodec. loopEnd _ nil. "???" loopLength _ nil. perceivedPitch _ 100.0. samplingRate _ anInteger. gain _ 1.0. "???" firstSample _ 1. cachedSound _ nil. "???"! ! Object subclass: #ConnectionQueue instanceVariableNames: 'portNumber maxQueueLength connections accessSema socket process ' classVariableNames: '' poolDictionaries: '' category: 'Network-Kernel'! !ConnectionQueue commentStamp: '' prior: 0! A ConnectionQueue listens on a given port number and collects a queue of client connections. In order to handle state changes quickly, a ConnectionQueue has its own process that: (a) tries to keep a socket listening on the port whenever the queue isn't already full of connections and (b) prunes stale connections out of the queue to make room for fresh ones. ! !ConnectionQueue methodsFor: 'public' stamp: 'jm 3/10/98 17:31'! connectionCount "Return an estimate of the number of currently queued connections. This is only an estimate since a new connection could be made, or an existing one aborted, at any moment." | count | self pruneStaleConnections. accessSema critical: [count _ connections size]. ^ count ! ! !ConnectionQueue methodsFor: 'public' stamp: 'jm 3/9/98 14:34'! destroy "Terminate the listener process and destroy all sockets in my possesion." process ifNotNil: [ process terminate. process _ nil]. socket ifNotNil: [ socket destroy. socket _ nil]. connections do: [:s | s destroy]. connections _ OrderedCollection new. ! ! !ConnectionQueue methodsFor: 'public' stamp: 'jm 3/10/98 09:18'! getConnectionOrNil "Return a connected socket, or nil if no connection has been established." | result | accessSema critical: [ connections isEmpty ifTrue: [result _ nil] ifFalse: [ result _ connections removeFirst. ((result isValid) and: [result isConnected]) ifFalse: [ "stale connection" result destroy. result _ nil]]]. ^ result ! ! !ConnectionQueue methodsFor: 'public' stamp: 'RAA 7/15/2000 12:36'! getConnectionOrNilLenient "Return a connected socket, or nil if no connection has been established." | result | accessSema critical: [ connections isEmpty ifTrue: [ result _ nil ] ifFalse: [ result _ connections removeFirst. (result isValid and: [result isConnected or: [result isOtherEndClosed]]) ifFalse: [ "stale connection" result destroy. result _ nil ] ] ]. ^ result ! ! !ConnectionQueue methodsFor: 'public' stamp: 'ls 9/26/1999 15:34'! isValid ^process notNil! ! !ConnectionQueue methodsFor: 'private' stamp: 'jm 3/10/98 11:07'! initPortNumber: anInteger queueLength: queueLength "Private!! Initialize the receiver to listen on the given port number. Up to queueLength connections will be queued." portNumber _ anInteger. maxQueueLength _ queueLength. connections _ OrderedCollection new. accessSema _ Semaphore forMutualExclusion. socket _ nil. process _ [self listenLoop] newProcess. process priority: Processor highIOPriority. process resume. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'ls 4/8/2000 20:39'! listenLoop "Private!! This loop is run in a separate process. It will establish up to maxQueueLength connections on the given port." "Details: When out of sockets or queue is full, retry more frequently, since a socket may become available, space may open in the queue, or a previously queued connection may be aborted by the client, making it available for a fresh connection." "Note: If the machine is disconnected from the network while the server is running, the currently waiting socket will go from 'isWaitingForConnection' to 'unconnected', and attempts to create new sockets will fail. When this happens, delete the broken socket and keep trying to create a socket in case the network connection is re-established. Connecting and disconnecting was tested under PPP on Mac system 8.1. It is not if this will work on other platforms." | newConnection | Socket initializeNetwork. socket _ Socket newTCP. "We'll accept four simultanous connections at the same time" socket listenOn: portNumber backlogSize: 4. "If the listener is not valid then the we cannot use the BSD style accept() mechanism." socket isValid ifFalse: [^self oldStyleListenLoop]. [true] whileTrue: [ socket isValid ifFalse: [ "socket has stopped listening for some reason" socket destroy. (Delay forMilliseconds: 10) wait. ^self listenLoop ]. newConnection _ socket waitForAcceptUntil: (Socket deadlineSecs: 10). (newConnection notNil and:[newConnection isConnected]) ifTrue: [accessSema critical: [connections addLast: newConnection]. newConnection _ nil]. self pruneStaleConnections]. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'bolot 7/16/1999 14:27'! oldStyleListenLoop "Private!! This loop is run in a separate process. It will establish up to maxQueueLength connections on the given port." "Details: When out of sockets or queue is full, retry more frequently, since a socket may become available, space may open in the queue, or a previously queued connection may be aborted by the client, making it available for a fresh connection." "Note: If the machine is disconnected from the network while the server is running, the currently waiting socket will go from 'isWaitingForConnection' to 'unconnected', and attempts to create new sockets will fail. When this happens, delete the broken socket and keep trying to create a socket in case the network connection is re-established. Connecting and disconnecting was tested under PPP on Mac system 8.1. It is not if this will work on other platforms." [true] whileTrue: [ ((socket == nil) and: [connections size < maxQueueLength]) ifTrue: [ "try to create a new socket for listening" socket _ Socket createIfFail: [nil]]. socket == nil ifTrue: [(Delay forMilliseconds: 100) wait] ifFalse: [ socket isUnconnected ifTrue: [socket listenOn: portNumber]. socket waitForConnectionUntil: (Socket deadlineSecs: 10). socket isConnected ifTrue: [ "connection established" accessSema critical: [connections addLast: socket]. socket _ nil] ifFalse: [ socket isWaitingForConnection ifFalse: [socket destroy. socket _ nil]]]. "broken socket; start over" self pruneStaleConnections]. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'jm 3/10/98 17:30'! pruneStaleConnections "Private!! The client may establish a connection and then disconnect while it is still in the connection queue. This method is called periodically to prune such sockets out of the connection queue and make room for fresh connections." | foundStaleConnection | accessSema critical: [ foundStaleConnection _ false. connections do: [:s | s isUnconnected ifTrue: [ s destroy. foundStaleConnection _ true]]. foundStaleConnection ifTrue: [ connections _ connections select: [:s | s isValid]]]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ConnectionQueue class instanceVariableNames: ''! !ConnectionQueue class methodsFor: 'instance creation' stamp: 'jm 3/9/98 14:09'! portNumber: anInteger queueLength: queueLength ^ self new initPortNumber: anInteger queueLength: queueLength ! ! InstructionStream subclass: #ContextPart instanceVariableNames: 'stackp ' classVariableNames: 'PrimitiveFailToken ' poolDictionaries: '' category: 'Kernel-Methods'! !ContextPart commentStamp: '' prior: 0! To the instruction parsing ability of InstructionStream I add the actual semantics for execution. The execution state is stored in the indexable fields of my subclasses. This includes temporary variables and a stack of values used in evaluating expressions. The actual semantics of execution can be found in my category "system simulation" and "instruction decode". These methods exactly parallel the operation of the Smalltalk machine itself. The simulator is a group of my methods that do what the Smalltalk interpreter does: execute Smalltalk bytecodes. By adding code to the simulator, you may take statistics on the running of Smalltalk methods. For example, Transcript show: (ContextPart runSimulated: [3 factorial]) printString.! !ContextPart methodsFor: 'accessing'! client "Answer the client, that is, the object that sent the message that created this context." ^sender receiver! ! !ContextPart methodsFor: 'accessing'! home "Answer the context in which the receiver was defined." self subclassResponsibility! ! !ContextPart methodsFor: 'accessing'! method "Answer the method of this context." self subclassResponsibility! ! !ContextPart methodsFor: 'accessing'! receiver "Answer the receiver of the message that created this context." self subclassResponsibility! ! !ContextPart methodsFor: 'accessing'! tempAt: index "Answer the value of the temporary variable whose index is the argument, index." self subclassResponsibility! ! !ContextPart methodsFor: 'accessing'! tempAt: index put: value "Store the argument, value, as the temporary variable whose index is the argument, index." self subclassResponsibility! ! !ContextPart methodsFor: 'instruction decoding'! doDup "Simulate the action of a 'duplicate top of stack' bytecode." self push: self top! ! !ContextPart methodsFor: 'instruction decoding'! doPop "Simulate the action of a 'remove top of stack' bytecode." self pop! ! !ContextPart methodsFor: 'instruction decoding'! jump: distance "Simulate the action of a 'unconditional jump' bytecode whose offset is the argument, distance." pc _ pc + distance! ! !ContextPart methodsFor: 'instruction decoding'! jump: distance if: condition "Simulate the action of a 'conditional jump' bytecode whose offset is the argument, distance, and whose condition is the argument, condition." (self pop eqv: condition) ifTrue: [self jump: distance]! ! !ContextPart methodsFor: 'instruction decoding'! methodReturnConstant: value "Simulate the action of a 'return constant' bytecode whose value is the argument, value. This corresponds to a source expression like '^0'." ^self return: value to: self home sender! ! !ContextPart methodsFor: 'instruction decoding'! methodReturnReceiver "Simulate the action of a 'return receiver' bytecode. This corresponds to the source expression '^self'." ^self return: self receiver to: self home sender! ! !ContextPart methodsFor: 'instruction decoding'! methodReturnTop "Simulate the action of a 'return top of stack' bytecode. This corresponds to source expressions like '^something'." ^self return: self pop to: self home sender! ! !ContextPart methodsFor: 'instruction decoding'! popIntoLiteralVariable: value "Simulate the action of bytecode that removes the top of the stack and stores it into a literal variable of my method." value value: self pop! ! !ContextPart methodsFor: 'instruction decoding'! popIntoReceiverVariable: offset "Simulate the action of bytecode that removes the top of the stack and stores it into an instance variable of my receiver." self receiver instVarAt: offset + 1 put: self pop! ! !ContextPart methodsFor: 'instruction decoding'! popIntoTemporaryVariable: offset "Simulate the action of bytecode that removes the top of the stack and stores it into one of my temporary variables." self home at: offset + 1 put: self pop! ! !ContextPart methodsFor: 'instruction decoding'! pushActiveContext "Simulate the action of bytecode that pushes the the active context on the top of its own stack." self push: self! ! !ContextPart methodsFor: 'instruction decoding'! pushConstant: value "Simulate the action of bytecode that pushes the constant, value, on the top of the stack." self push: value! ! !ContextPart methodsFor: 'instruction decoding'! pushLiteralVariable: value "Simulate the action of bytecode that pushes the contents of the literal variable whose index is the argument, index, on the top of the stack." self push: value value! ! !ContextPart methodsFor: 'instruction decoding'! pushReceiver "Simulate the action of bytecode that pushes the active context's receiver on the top of the stack." self push: self receiver! ! !ContextPart methodsFor: 'instruction decoding'! pushReceiverVariable: offset "Simulate the action of bytecode that pushes the contents of the receiver's instance variable whose index is the argument, index, on the top of the stack." self push: (self receiver instVarAt: offset + 1)! ! !ContextPart methodsFor: 'instruction decoding'! pushTemporaryVariable: offset "Simulate the action of bytecode that pushes the contents of the temporary variable whose index is the argument, index, on the top of the stack." self push: (self home at: offset + 1)! ! !ContextPart methodsFor: 'instruction decoding' stamp: 'ar 5/25/2000 20:45'! send: selector super: superFlag numArgs: numArgs "Simulate the action of bytecodes that send a message with selector, selector. The argument, superFlag, tells whether the receiver of the message was specified with 'super' in the source method. The arguments of the message are found in the top numArgs locations on the stack and the receiver just below them." | receiver arguments answer | arguments _ Array new: numArgs. numArgs to: 1 by: -1 do: [ :i | arguments at: i put: self pop]. receiver _ self pop. (selector == #halt or: [selector == #halt:]) ifTrue: [self error: 'Cant simulate halt. Proceed to bypass it.'. self push: nil. ^self]. selector == #doPrimitive:method:receiver:args: ifTrue: [answer _ receiver doPrimitive: (arguments at: 1) method: (arguments at: 2) receiver: (arguments at: 3) args: (arguments at: 4). self push: answer. ^self]. ^self send: selector to: receiver with: arguments super: superFlag! ! !ContextPart methodsFor: 'instruction decoding'! storeIntoLiteralVariable: value "Simulate the action of bytecode that stores the top of the stack into a literal variable of my method." value value: self top! ! !ContextPart methodsFor: 'instruction decoding'! storeIntoReceiverVariable: offset "Simulate the action of bytecode that stores the top of the stack into an instance variable of my receiver." self receiver instVarAt: offset + 1 put: self top! ! !ContextPart methodsFor: 'instruction decoding'! storeIntoTemporaryVariable: offset "Simulate the action of bytecode that stores the top of the stack into one of my temporary variables." self home at: offset + 1 put: self top! ! !ContextPart methodsFor: 'debugger access'! depthBelow: aContext "Answer how many calls there are between this and aContext." | this depth | this _ self. depth _ 0. [this == aContext or: [this == nil]] whileFalse: [this _ this sender. depth _ depth + 1]. ^depth! ! !ContextPart methodsFor: 'debugger access' stamp: 'RAA 5/16/2000 12:14'! longStack "Answer a String showing the top 100 contexts on my sender chain." ^ String streamContents: [:strm | (self stackOfSize: 100) do: [:item | strm print: item; cr]]! ! !ContextPart methodsFor: 'debugger access' stamp: 'ls 12/5/1999 13:43'! mclass "Answer the class in which the receiver's method was found." | mclass | self receiver class selectorAtMethod: self method setClass: [:mc | mclass _ mc ]. ^mclass! ! !ContextPart methodsFor: 'debugger access'! pc "Answer the index of the next bytecode to be executed." ^pc! ! !ContextPart methodsFor: 'debugger access'! release "Remove information from the receiver and all of the contexts on its sender chain in order to break circularities." self releaseTo: nil! ! !ContextPart methodsFor: 'debugger access'! releaseTo: caller "Remove information from the receiver and the contexts on its sender chain up to caller in order to break circularities." | c s | c _ self. [c == nil or: [c == caller]] whileFalse: [s _ c sender. c singleRelease. c _ s]! ! !ContextPart methodsFor: 'debugger access'! selector "Answer the selector of the method that created the receiver." ^self receiver class selectorAtMethod: self method setClass: [:ignored]! ! !ContextPart methodsFor: 'debugger access'! sender "Answer the context that sent the message that created the receiver." ^sender! ! !ContextPart methodsFor: 'debugger access' stamp: 'di 8/31/1999 09:42'! shortStack "Answer a String showing the top ten contexts on my sender chain." ^ String streamContents: [:strm | (self stackOfSize: 10) do: [:item | strm print: item; cr]]! ! !ContextPart methodsFor: 'debugger access'! singleRelease "Remove information from the receiver in order to break circularities." stackp == nil ifFalse: [1 to: stackp do: [:i | self at: i put: nil]]. sender _ nil! ! !ContextPart methodsFor: 'debugger access' stamp: 'ar 7/9/1999 19:01'! sourceCode | selector methodClass | selector _ self receiver class selectorAtMethod: self method setClass: [:mclass | methodClass _ mclass]. ^self method getSourceFor: selector in: methodClass "Note: The above is a bit safer than ^ methodClass sourceCodeAt: selector which may fail if the receiver's method has been changed in the debugger (e.g., the method is no longer in the methodDict and thus the above selector is something like #Doit:with:with:with:) but the source code is still available."! ! !ContextPart methodsFor: 'debugger access'! stack "Answer an Array of the contexts on the receiver's sender chain." ^self stackOfSize: 9999! ! !ContextPart methodsFor: 'debugger access' stamp: 'tfei 3/20/2000 00:51'! stackOfSize: limit "Answer an OrderedCollection of the top 'limit' contexts on the receiver's sender chain." | a stack cachedStackTop newLimit | stack _ OrderedCollection new. stack addLast: (a _ self). [(a _ a sender) ~~ nil and: [stack size < limit]] whileTrue: [a hideFromDebugger ifFalse: [stack addLast: a]. a cachesStack ifTrue: [cachedStackTop := a cachedStackTop]]. ^cachedStackTop == nil ifTrue: [stack] ifFalse: [newLimit := limit - stack size. newLimit > 0 ifTrue: [stack addAllLast: (cachedStackTop stackOfSize: newLimit); yourself] ifFalse: [stack]]! ! !ContextPart methodsFor: 'debugger access'! swapSender: coroutine "Replace the receiver's sender with coroutine and answer the receiver's previous sender. For use in coroutining." | oldSender | oldSender _ sender. sender _ coroutine. ^oldSender! ! !ContextPart methodsFor: 'debugger access' stamp: 'ls 10/10/1999 13:53'! tempNames "Answer an OrderedCollection of the names of the receiver's temporary variables, which are strings." | names | self method setTempNamesIfCached: [:n | ^n]. names _ (self mclass compilerClass new parse: self sourceCode in: self mclass notifying: nil) tempNames. self method cacheTempNames: names. ^names! ! !ContextPart methodsFor: 'debugger access'! tempsAndValues "Return a string of the temporary variabls and their current values" | aStream | aStream _ WriteStream on: (String new: 100). self tempNames doWithIndex: [:title :index | aStream nextPutAll: title; nextPut: $:; space; tab. (self tempAt: index) printOn: aStream. aStream cr]. ^aStream contents! ! !ContextPart methodsFor: 'controlling'! activateMethod: newMethod withArgs: args receiver: rcvr class: class "Answer a ContextPart initialized with the arguments." ^MethodContext sender: self receiver: rcvr method: newMethod arguments: args! ! !ContextPart methodsFor: 'controlling' stamp: 'di 10/23/1999 17:03'! blockCopy: numArgs "Primitive. Distinguish a block of code from its enclosing method by creating a new BlockContext for that block. The compiler inserts into all methods that contain blocks the bytecodes to send the message blockCopy:. Do not use blockCopy: in code that you write!! Only the compiler can decide to send the message blockCopy:. Fail if numArgs is not a SmallInteger. Optional. No Lookup. See Object documentation whatIsAPrimitive." ^ (BlockContext newForMethod: self home method) home: self home startpc: pc + 2 nargs: numArgs! ! !ContextPart methodsFor: 'controlling'! hasSender: context "Answer whether the receiver is strictly above context on the stack." | s | self == context ifTrue: [^false]. s _ sender. [s == nil] whileFalse: [s == context ifTrue: [^true]. s _ s sender]. ^false! ! !ContextPart methodsFor: 'controlling' stamp: 'di 1/11/1999 22:40'! pop "Answer the top of the receiver's stack and remove the top of the stack." | val | val _ self at: stackp. self stackp: stackp - 1. ^ val! ! !ContextPart methodsFor: 'controlling' stamp: 'di 1/11/1999 22:39'! push: val "Push val on the receiver's stack." self stackp: stackp + 1. self at: stackp put: val! ! !ContextPart methodsFor: 'controlling'! return: value to: sendr "Simulate the return of value to sendr." self releaseTo: sendr. ^sendr push: value! ! !ContextPart methodsFor: 'controlling' stamp: 'di 11/26/1999 19:34'! send: selector to: rcvr with: args super: superFlag "Simulate the action of sending a message with selector, selector, and arguments, args, to receiver. The argument, superFlag, tells whether the receiver of the message was specified with 'super' in the source method." | class meth val | class _ superFlag ifTrue: [(self method literalAt: self method numLiterals) value superclass] ifFalse: [rcvr class]. meth _ class lookupSelector: selector. meth == nil ifTrue: [^ self send: #doesNotUnderstand: to: rcvr with: (Array with: (Message selector: selector arguments: args)) super: superFlag] ifFalse: [val _ self tryPrimitiveFor: meth receiver: rcvr args: args. val == PrimitiveFailToken ifFalse: [^ val]. (selector == #doesNotUnderstand: and: [class == ProtoObject]) ifTrue: [^ self error: 'Simulated message ' , (args at: 1) selector , ' not understood']. ^ self activateMethod: meth withArgs: args receiver: rcvr class: class]! ! !ContextPart methodsFor: 'controlling' stamp: 'crl 2/26/1999 15:34'! terminate "Make myself unresumable." sender _ nil! ! !ContextPart methodsFor: 'controlling' stamp: 'crl 2/26/1999 15:34'! terminateTo: previousContext "Terminate all the Contexts between me and previousContext, if previousContext is on my Context stack. Make previousContext my sender." | currentContext sendingContext | (self hasSender: previousContext) ifTrue: [ currentContext _ sender. [currentContext == previousContext] whileFalse: [ sendingContext _ currentContext sender. currentContext terminate. currentContext _ sendingContext]]. sender _ previousContext! ! !ContextPart methodsFor: 'controlling'! top "Answer the top of the receiver's stack." ^self at: stackp! ! !ContextPart methodsFor: 'printing' stamp: 'ls 10/10/1999 11:57'! printOn: aStream | selector class mclass | self method == nil ifTrue: [^ super printOn: aStream]. selector _ (class _ self receiver class) selectorAtMethod: self method setClass: [:c | mclass _ c]. selector == #? ifTrue: [aStream nextPut: $?; print: self method who. ^self]. aStream nextPutAll: class name. mclass == class ifFalse: [aStream nextPut: $(. aStream nextPutAll: mclass name. aStream nextPut: $)]. aStream nextPutAll: '>>'. aStream nextPutAll: selector! ! !ContextPart methodsFor: 'system simulation' stamp: 'di 1/5/98 11:20'! completeCallee: aContext "Simulate the execution of bytecodes until a return to the receiver." | ctxt current | ctxt _ aContext. [ctxt == current or: [ctxt hasSender: self]] whileTrue: [current _ ctxt. ctxt _ ctxt step]. self stepToSendOrReturn! ! !ContextPart methodsFor: 'system simulation' stamp: 'di 1/5/98 11:20'! runSimulated: aBlock contextAtEachStep: block2 "Simulate the execution of the argument, aBlock, until it ends. aBlock MUST NOT contain an '^'. Evaluate block2 with the current context prior to each instruction executed. Answer the simulated value of aBlock." | current | aBlock hasMethodReturn ifTrue: [self error: 'simulation of blocks with ^ can run loose']. current _ aBlock. current pushArgs: Array new from: self. [current == self] whileFalse: [block2 value: current. current _ current step]. ^self pop! ! !ContextPart methodsFor: 'system simulation'! step "Simulate the execution of the receiver's next bytecode. Answer the context that would be the active context after this bytecode." ^self interpretNextInstructionFor: self! ! !ContextPart methodsFor: 'system simulation' stamp: 'sn 8/22/97 21:55'! stepToSendOrReturn "Simulate the execution of bytecodes until either sending a message or returning a value to the receiver (that is, until switching contexts)." [self willReallySend | self willReturn] whileFalse: [self step]! ! !ContextPart methodsFor: 'private' stamp: 'ar 5/25/2000 20:47'! doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments "Simulate a primitive method whose index is primitiveIndex. The simulated receiver and arguments are given as arguments to this message." | value | "Simulation guard" "If successful, push result and return resuming context, else ^ PrimitiveFailToken" (primitiveIndex = 19) ifTrue:[ Debugger openContext: self label:'Code simulation error' contents: self shortStack]. (primitiveIndex = 80 and: [receiver isKindOf: ContextPart]) ifTrue: [^self push: ((BlockContext newForMethod: receiver home method) home: receiver home startpc: pc + 2 nargs: (arguments at: 1))]. (primitiveIndex = 81 and: [receiver isMemberOf: BlockContext]) ifTrue: [^receiver pushArgs: arguments from: self]. primitiveIndex = 83 "afr 9/11/1998 19:50" ifTrue: [^ self send: arguments first to: receiver with: arguments allButFirst super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50" ifTrue: [^ self send: arguments first to: receiver with: (arguments at: 2) super: false]. arguments size > 6 ifTrue: [^ PrimitiveFailToken]. primitiveIndex = 117 ifTrue:[value _ self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] ifFalse:[value _ receiver tryPrimitive: primitiveIndex withArgs: arguments]. value == PrimitiveFailToken ifTrue: [^ PrimitiveFailToken] ifFalse: [^ self push: value]! ! !ContextPart methodsFor: 'private' stamp: 'di 1/11/1999 10:12'! push: numObjects fromIndexable: anIndexableCollection "Push the elements of anIndexableCollection onto the receiver's stack. Do not call directly. Called indirectly by {1. 2. 3} constructs." 1 to: numObjects do: [:i | self push: (anIndexableCollection at: i)]! ! !ContextPart methodsFor: 'private'! stackPtr "For use only by the SystemTracer" ^ stackp! ! !ContextPart methodsFor: 'private' stamp: 'di 10/23/1999 17:31'! stackp: newStackp "Storing into the stack pointer is a potentially dangerous thing. This primitive stores nil into any cells that become accessible as a result, and it performs the entire operation atomically." "Once this primitive is implemented, failure code should cause an error" self error: 'stackp store failure'. " stackp == nil ifTrue: [stackp _ 0]. newStackp > stackp 'effectively checks that it is a number' ifTrue: [oldStackp _ stackp. stackp _ newStackp. 'Nil any newly accessible cells' oldStackp + 1 to: stackp do: [:i | self at: i put: nil]] ifFalse: [stackp _ newStackp] "! ! !ContextPart methodsFor: 'private' stamp: 'ar 5/25/2000 20:41'! tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments "Hack. Attempt to execute the named primitive from the given compiled method" | selector theMethod spec | arguments size > 8 ifTrue:[^PrimitiveFailToken]. selector _ #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. theMethod _ aReceiver class lookupSelector: selector. theMethod == nil ifTrue:[^PrimitiveFailToken]. spec _ theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt: 1) startingAt: 1. ^aReceiver perform: selector withArguments: arguments! ! !ContextPart methodsFor: 'private' stamp: 'ar 5/25/2000 20:45'! tryPrimitiveFor: method receiver: receiver args: arguments "If this method has a primitive index, then run the primitive and return its result. Otherwise (and also if the primitive fails) return PrimitiveFailToken, as an indication that the method should be activated and run as bytecodes." | primIndex | (primIndex _ method primitive) = 0 ifTrue: [^ PrimitiveFailToken]. ^ self doPrimitive: primIndex method: method receiver: receiver args: arguments! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'tfei 4/1/1999 18:02'! isHandlerContext ^self method == (BlockContext compiledMethodAt: #on:do:)! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'tfei 4/1/1999 18:17'! isUnwindContext | m | ^(m := self method) == (BlockContext compiledMethodAt: #ensure:) or: [m == (BlockContext compiledMethodAt: #ifCurtailed:)]! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'tfei 4/13/1999 19:02'! unwindTo: aContext | ctx returnValue aContextSender unwindBlock | ctx := self. returnValue := nil. aContext == nil ifTrue: [aContextSender := nil] ifFalse: [aContextSender := aContext sender]. "if aContext itself is marked for unwind, then need to use sender for whileFalse: loop check" [ctx == aContextSender or: [ctx == nil]] whileFalse: [ctx isUnwindContext ifTrue: [unwindBlock := ctx tempAt: 1. ctx tempAt: 1 put: nil. "see comment in #ensure:" unwindBlock == nil ifFalse: [returnValue := unwindBlock value]]. ctx := ctx sender]. ^returnValue! ! !ContextPart methodsFor: 'objects from disk' stamp: 'tk 9/28/2000 22:54'! storeDataOn: aDataStream "Contexts are not allowed go to out in DataStreams. They must be included inside an ImageSegment." aDataStream insideASegment ifTrue: [^ super storeDataOn: aDataStream]. self error: 'This Context was not included in the ImageSegment'. "or perhaps ImageSegments were not used at all" ^ nil! ! !ContextPart methodsFor: 'private-debugger' stamp: 'tfei 3/19/2000 23:24'! cachesStack ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ContextPart class instanceVariableNames: ''! !ContextPart class methodsFor: 'examples'! tallyInstructions: aBlock "This method uses the simulator to count the number of occurrences of each of the Smalltalk instructions executed during evaluation of aBlock. Results appear in order of the byteCode set." | tallies | tallies _ Bag new. thisContext sender runSimulated: aBlock contextAtEachStep: [:current | tallies add: current nextByte]. ^tallies sortedElements "ContextPart tallyInstructions: [3.14159 printString]"! ! !ContextPart class methodsFor: 'examples'! tallyMethods: aBlock "This method uses the simulator to count the number of calls on each method invoked in evaluating aBlock. Results are given in order of decreasing counts." | prev tallies | tallies _ Bag new. prev _ aBlock. thisContext sender runSimulated: aBlock contextAtEachStep: [:current | current == prev ifFalse: "call or return" [prev sender == nil ifFalse: "call only" [tallies add: current printString]. prev _ current]]. ^tallies sortedCounts "ContextPart tallyMethods: [3.14159 printString]"! ! !ContextPart class methodsFor: 'examples' stamp: 'sma 4/22/2000 17:03'! trace: aBlock "ContextPart trace: [3 factorial]" "This method uses the simulator to print calls and returned values in the Transcript." Transcript clear. ^ self trace: aBlock on: Transcript! ! !ContextPart class methodsFor: 'examples' stamp: 'sma 4/22/2000 17:03'! trace: aBlock on: aStream "ContextPart trace: [3 factorial]" "This method uses the simulator to print calls to a file." | prev | prev _ aBlock. ^ thisContext sender runSimulated: aBlock contextAtEachStep: [:current | Sensor anyButtonPressed ifTrue: [^ nil]. current == prev ifFalse: [prev sender ifNil: [aStream space; nextPut: $^. self carefullyPrint: current top on: aStream]. aStream cr. (current depthBelow: aBlock) timesRepeat: [aStream space]. self carefullyPrint: current receiver on: aStream. aStream space; nextPutAll: current selector; flush. prev _ current]]! ! !ContextPart class methodsFor: 'examples' stamp: 'sma 4/22/2000 17:05'! trace: aBlock onFileNamed: fileName "ContextPart trace: [3 factorial] onFileNamed: 'trace'" "This method uses the simulator to print calls to a file." | aStream | ^ [aStream _ FileStream fileNamed: fileName. self trace: aBlock on: aStream] ensure: [aStream close]! ! !ContextPart class methodsFor: 'simulation' stamp: 'di 2/10/1999 22:15'! initialize "A unique object to be returned when a primitive fails during simulation" PrimitiveFailToken _ Object new ! ! !ContextPart class methodsFor: 'simulation' stamp: 'di 2/10/1999 22:15'! primitiveFailToken ^ PrimitiveFailToken! ! !ContextPart class methodsFor: 'simulation'! runSimulated: aBlock "Simulate the execution of the argument, current. Answer the result it returns." ^ thisContext sender runSimulated: aBlock contextAtEachStep: [:ignored] "ContextPart runSimulated: [Pen new defaultNib: 5; go: 100]"! ! !ContextPart class methodsFor: 'instance creation' stamp: 'di 10/23/1999 17:09'! basicNew: size self error: 'Contexts must only be created with newForMethod:'! ! !ContextPart class methodsFor: 'instance creation' stamp: 'sw 5/5/2000 00:30'! initializedInstance ^ nil! ! !ContextPart class methodsFor: 'instance creation' stamp: 'di 10/23/1999 17:09'! new self error: 'Contexts must only be created with newForMethod:'! ! !ContextPart class methodsFor: 'instance creation' stamp: 'di 10/23/1999 17:09'! new: size self error: 'Contexts must only be created with newForMethod:'! ! !ContextPart class methodsFor: 'instance creation' stamp: 'di 10/23/1999 17:55'! newForMethod: aMethod "This is the only method for creating new contexts, other than primitive cloning. Any other attempts, such as inherited methods like shallowCopy, should be avoided or must at least be rewritten to determine the proper size from the method being activated. This is because asking a context its size (even basicSize!!) will not return the real object size but only the number of fields currently accessible, as determined by stackp." ^ super basicNew: aMethod frameSize! ! !ContextPart class methodsFor: 'private' stamp: 'sma 4/22/2000 17:01'! carefullyPrint: anObject on: aStream aStream nextPutAll: ([anObject printString] on: Error do: ['unprintable ' , anObject class name])! ! Inspector subclass: #ContextVariablesInspector instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Debugger'! !ContextVariablesInspector commentStamp: '' prior: 0! I represent a query path into the internal representation of a ContextPart. Typically this is a context at a point in the query path of a Debugger. As a StringHolder, the string I represent is the value of the currently selected variable of the observed temporary variable of the context.! !ContextVariablesInspector methodsFor: 'accessing'! fieldList "Refer to the comment in Inspector|fieldList." object == nil ifTrue: [^Array with: 'thisContext']. ^(Array with: 'thisContext' with: 'all temp vars') , object tempNames! ! !ContextVariablesInspector methodsFor: 'selecting'! replaceSelectionValue: anObject "Refer to the comment in Inspector|replaceSelectionValue:." selectionIndex = 1 ifTrue: [^object] ifFalse: [^object tempAt: selectionIndex - 2 put: anObject]! ! !ContextVariablesInspector methodsFor: 'selecting' stamp: 'ar 5/29/1998 18:32'! selection "Refer to the comment in Inspector|selection." selectionIndex = 0 ifTrue:[^'']. selectionIndex = 1 ifTrue: [^object]. selectionIndex = 2 ifTrue: [^object tempsAndValues] ifFalse: [^object tempAt: selectionIndex - 2]! ! !ContextVariablesInspector methodsFor: 'code'! doItContext ^object! ! !ContextVariablesInspector methodsFor: 'code'! doItReceiver ^object receiver! ! AbstractScoreEvent subclass: #ControlChangeEvent instanceVariableNames: 'control value channel ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Scores'! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 07:47'! channel ^ channel ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 07:47'! channel: midiChannel channel _ midiChannel. ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 07:49'! control ^ control ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 07:48'! control: midiControl control _ midiControl. ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 08:03'! control: midiControl value: midiControlValue channel: midiChannel control _ midiControl. value _ midiControlValue. channel _ midiChannel. ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 08:02'! value ^ value ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 08:03'! value: midiControlValue value _ midiControlValue. ! ! !ControlChangeEvent methodsFor: 'classification' stamp: 'jm 9/10/1998 09:45'! isControlChange ^ true ! ! !ControlChangeEvent methodsFor: 'midi' stamp: 'jm 9/10/1998 18:31'! outputOnMidiPort: aMidiPort "Output this event to the given MIDI port." aMidiPort midiCmd: 16rB0 channel: channel byte: control byte: value. ! ! !ControlChangeEvent methodsFor: 'printing' stamp: 'sma 6/1/2000 09:34'! printOn: aStream aStream nextPut: $(; print: time; nextPutAll: ': ctrl['; print: control; nextPutAll: ']='; print: value; nextPut: $)! ! !ControlChangeEvent methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:27'! printOnStream: aStream aStream print:'('; write:time; print:': ctrl['; write:control; print:']=';write:value; print:')'. ! ! Object subclass: #ControlManager instanceVariableNames: 'scheduledControllers activeController activeControllerProcess screenController newTopClicked ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-ST80 Remnants'! !ControlManager commentStamp: '' prior: 0! I represent the top level control over scheduling which controller of a view on the screen the user is actively using. ScheduledControllers is the global reference to an instance of me, the one attached to the Project currently being used.! !ControlManager methodsFor: 'initialize-release'! initialize "Initialize the receiver to refer to only the background controller." | screenView | screenController _ ScreenController new. screenView _ FormView new. screenView model: (InfiniteForm with: Color gray) controller: screenController. screenView window: Display boundingBox. scheduledControllers _ OrderedCollection with: screenController! ! !ControlManager methodsFor: 'initialize-release'! release "Refer to the comment in Object|release." scheduledControllers == nil ifFalse: [scheduledControllers do: [:controller | (controller isKindOf: Controller) ifTrue: [controller view release] ifFalse: [controller release]]. scheduledControllers _ nil]! ! !ControlManager methodsFor: 'accessing'! activeController "Answer the currently active controller." ^activeController! ! !ControlManager methodsFor: 'accessing' stamp: 'ar 6/5/1998 21:49'! activeController: aController "Set aController to be the currently active controller. Give the user control in it." "Simulation guard" activeController _ aController. (activeController == screenController) ifFalse: [self promote: activeController]. activeControllerProcess _ [activeController startUp. self searchForActiveController] newProcess. activeControllerProcess priority: Processor userSchedulingPriority. activeControllerProcess resume! ! !ControlManager methodsFor: 'accessing'! activeControllerNoTerminate: aController andProcess: aProcess "Set aController to be the currently active controller and aProcess to be the the process that handles controller scheduling activities in the system. This message differs from activeController:andProcess: in that it does not send controlTerminate to the currently active controller." self inActiveControllerProcess ifTrue: [aController~~nil ifTrue: [(scheduledControllers includes: aController) ifTrue: [self promote: aController] ifFalse: [self error: 'Old controller not scheduled']]. activeController _ aController. activeController == nil ifFalse: [activeController controlInitialize]. activeControllerProcess _ aProcess. activeControllerProcess resume] ifFalse: [self error: 'New active controller process must be set from old one'] ! ! !ControlManager methodsFor: 'accessing'! activeControllerProcess "Answer the process that is currently handling controller scheduling activities in the system." ^activeControllerProcess! ! !ControlManager methodsFor: 'accessing'! controllerSatisfying: aBlock "Return the first scheduled controller which satisfies the 1-argument boolean-valued block, or nil if none. 7/25/96 sw" scheduledControllers do: [:aController | (aBlock value: aController) == true ifTrue: [^ aController]]. ^ nil! ! !ControlManager methodsFor: 'accessing'! controllerWhoseModelSatisfies: aBlock "Return the first scheduled controller whose model satisfies the 1-argument boolean-valued block, or nil if none. 5/6/96 sw" scheduledControllers do: [:aController | (aBlock value: aController model) == true ifTrue: [^ aController]]. ^ nil! ! !ControlManager methodsFor: 'accessing' stamp: 'sw 9/27/96'! controllersSatisfying: aBlock "Return a list of scheduled controllers satisfying aBlock. " ^ scheduledControllers select: [:aController | (aBlock value: aController) == true]! ! !ControlManager methodsFor: 'accessing'! includes: aController ^ scheduledControllers includes: aController! ! !ControlManager methodsFor: 'accessing'! noteNewTop newTopClicked _ true! ! !ControlManager methodsFor: 'accessing' stamp: 'sw 10/9/96'! removeAllControllersSatisfying: aBlock "Unschedule and delete all controllers satisfying aBlock. May not leave the screen exactly right sometimes. " (self controllersSatisfying: aBlock) do: [:aController | aController closeAndUnschedule]! ! !ControlManager methodsFor: 'accessing'! scheduledControllers "Answer a copy of the ordered collection of scheduled controllers." ^scheduledControllers copy! ! !ControlManager methodsFor: 'accessing' stamp: 'di 10/4/97 09:05'! scheduledWindowControllers "Same as scheduled controllers, but without ScreenController. Avoids null views just after closing, eg, a debugger." ^ scheduledControllers select: [:c | c ~~ screenController and: [c view ~~ nil]]! ! !ControlManager methodsFor: 'accessing'! screenController ^ screenController! ! !ControlManager methodsFor: 'accessing'! windowOriginsInUse "Answer a collection of the origins of windows currently on the screen in the current project. 5/21/96 sw" ^ self scheduledWindowControllers collect: [:aController | aController view displayBox origin].! ! !ControlManager methodsFor: 'scheduling'! activateController: aController "Make aController, which must already be a scheduled controller, the active window. 5/8/96 sw" self activeController: aController. (activeController view labelDisplayBox intersect: Display boundingBox) area < 200 ifTrue: [activeController move]. Processor terminateActive! ! !ControlManager methodsFor: 'scheduling'! activateTranscript "There is known to be a Transcript open in the current project; activate it. 2/5/96 sw" | itsController | itsController _ scheduledControllers detect: [:controller | controller model == Transcript] ifNone: [^ self]. self activeController: itsController. (activeController view labelDisplayBox intersect: Display boundingBox) area < 200 ifTrue: [activeController move]. Processor terminateActive! ! !ControlManager methodsFor: 'scheduling' stamp: 'di 5/19/1998 09:03'! findWindow "Present a menu of window titles, and activate the one that gets chosen." ^ self findWindowSatisfying: [:c | true]! ! !ControlManager methodsFor: 'scheduling' stamp: 'wod 6/17/1998 15:46'! findWindowSatisfying: aBlock "Present a menu of window titles, and activate the one that gets chosen" | sortAlphabetically controllers listToUse labels index | sortAlphabetically _ Sensor shiftPressed. controllers _ OrderedCollection new. scheduledControllers do: [:controller | controller == screenController ifFalse: [(aBlock value: controller) ifTrue: [controllers addLast: controller]]]. controllers size == 0 ifTrue: [^ self]. listToUse _ sortAlphabetically ifTrue: [controllers asSortedCollection: [:a :b | a view label < b view label]] ifFalse: [controllers]. labels _ String streamContents: [:strm | listToUse do: [:controller | strm nextPutAll: (controller view label contractTo: 40); cr]. strm skip: -1 "drop last cr"]. index _ (PopUpMenu labels: labels) startUp. index > 0 ifTrue: [self activateController: (listToUse at: index)]. ! ! !ControlManager methodsFor: 'scheduling'! inActiveControllerProcess "Answer whether the active scheduling process is the actual active process in the system." ^activeControllerProcess == Processor activeProcess! ! !ControlManager methodsFor: 'scheduling' stamp: 'ar 11/19/1998 18:31'! interruptName: labelString "Create a Notifier on the active scheduling process with the given label. Make the Notifier the active controller." | suspendingList newActiveController | (suspendingList _ activeControllerProcess suspendingList) == nil ifTrue: [activeControllerProcess == Processor activeProcess ifTrue: [activeControllerProcess suspend]] ifFalse: [suspendingList remove: activeControllerProcess ifAbsent:[]. activeControllerProcess offList]. activeController ~~ nil ifTrue: [ "Carefully de-emphasis the current window." activeController view topView deEmphasizeForDebugger]. newActiveController _ (Debugger openInterrupt: labelString onProcess: activeControllerProcess) controller. newActiveController centerCursorInView. self activeController: newActiveController. ! ! !ControlManager methodsFor: 'scheduling'! promote: aController "Make aController be the first scheduled controller in the ordered collection." scheduledControllers remove: aController. scheduledControllers addFirst: aController! ! !ControlManager methodsFor: 'scheduling' stamp: 'RAA 7/7/2000 09:22'! resetActiveController "When saving a morphic project whose parent is mvc, we need to set this up first" activeController _ nil. activeControllerProcess _ Processor activeProcess. ! ! !ControlManager methodsFor: 'scheduling' stamp: 'ar 6/5/1998 21:48'! scheduleActive: aController "Make aController be scheduled as the active controller. Presumably the active scheduling process asked to schedule this controller and that a new process associated this controller takes control. So this is the last act of the active scheduling process." "Simulation guard" self scheduleActiveNoTerminate: aController. Processor terminateActive! ! !ControlManager methodsFor: 'scheduling'! scheduleActiveNoTerminate: aController "Make aController be the active controller. Presumably the process that requested the new active controller wants to keep control to do more activites before the new controller can take control. Therefore, do not terminate the currently active process." self schedulePassive: aController. self scheduled: aController from: Processor activeProcess! ! !ControlManager methodsFor: 'scheduling'! scheduleOnBottom: aController "Make aController be scheduled as a scheduled controller, but not the active one. Put it at the end of the ordered collection of controllers." scheduledControllers addLast: aController! ! !ControlManager methodsFor: 'scheduling'! schedulePassive: aController "Make aController be scheduled as a scheduled controller, but not the active one. Put it at the beginning of the ordered collection of controllers." scheduledControllers addFirst: aController! ! !ControlManager methodsFor: 'scheduling'! searchForActiveController "Find a scheduled controller that wants control and give control to it. If none wants control, then see if the System Menu has been requested." | aController | activeController _ nil. activeControllerProcess _ Processor activeProcess. self activeController: self nextActiveController. Processor terminateActive! ! !ControlManager methodsFor: 'scheduling'! unschedule: aController "Remove the view, aController, from the collection of scheduled controllers." scheduledControllers remove: aController ifAbsent: []! ! !ControlManager methodsFor: 'scheduling'! windowFromUser "Present a menu of window titles, and returns the StandardSystemController belonging to the one that gets chosen, or nil if none" | controllers labels index | controllers _ OrderedCollection new. labels _ String streamContents: [:strm | scheduledControllers do: [:controller | controller == screenController ifFalse: [controllers addLast: controller. strm nextPutAll: (controller view label contractTo: 40); cr]]. strm skip: -1 "drop last cr"]. index _ (PopUpMenu labels: labels) startUp. ^ index > 0 ifTrue: [controllers at: index] ifFalse: [nil]! ! !ControlManager methodsFor: 'displaying'! backgroundForm: aForm screenController view model: aForm. ScheduledControllers restore " QDPen new mandala: 30 diameter: 640. ScheduledControllers backgroundForm: (Form fromDisplay: Display boundingBox). ScheduledControllers backgroundForm: (InfiniteForm with: Form gray). "! ! !ControlManager methodsFor: 'displaying' stamp: 'di 2/26/98 08:58'! restore "Clear the screen to gray and then redisplay all the scheduled views. Try to be a bit intelligent about the view that wants control and not display it twice if possible." scheduledControllers first view uncacheBits. "assure refresh" self unschedule: screenController; scheduleOnBottom: screenController. screenController view window: Display boundingBox; displayDeEmphasized. self scheduledWindowControllers reverseDo: [:aController | aController view displayDeEmphasized]. ! ! !ControlManager methodsFor: 'displaying' stamp: 'hmm 1/5/2000 07:00'! restore: aRectangle "Restore all windows visible in aRectangle" ^ self restore: aRectangle without: nil! ! !ControlManager methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:06'! restore: aRectangle below: index without: aView "Restore all windows visible in aRectangle, but without aView" | view | view _ (scheduledControllers at: index) view. view == aView ifTrue: [index >= scheduledControllers size ifTrue: [^ self]. ^ self restore: aRectangle below: index+1 without: aView]. view displayOn: ((BitBlt current toForm: Display) clipRect: aRectangle). index >= scheduledControllers size ifTrue: [^ self]. (aRectangle areasOutside: view windowBox) do: [:rect | self restore: rect below: index + 1 without: aView]! ! !ControlManager methodsFor: 'displaying' stamp: 'hmm 12/30/1999 19:35'! restore: aRectangle without: aView "Restore all windows visible in aRectangle" Display deferUpdates: true. self restore: aRectangle below: 1 without: aView. Display deferUpdates: false; forceToScreen: aRectangle! ! !ControlManager methodsFor: 'displaying'! updateGray "From Georg Gollmann - 11/96. tell the Screen Controller's model to use the currently-preferred desktop color." "ScheduledControllers updateGray" (screenController view model isMemberOf: InfiniteForm) ifTrue: [screenController view model: (InfiniteForm with: Preferences desktopColor)]! ! !ControlManager methodsFor: 'private'! nextActiveController "Answer the controller that would like control. If there was a click outside the active window, it's the top window that now has the mouse, otherwise it's just the top window." (newTopClicked notNil and: [newTopClicked]) ifTrue: [newTopClicked _ false. ^ scheduledControllers detect: [:aController | aController isControlWanted] ifNone: [scheduledControllers first]] ifFalse: [^ scheduledControllers first]! ! !ControlManager methodsFor: 'private'! scheduled: aController from: aProcess activeControllerProcess==aProcess ifTrue: [activeController ~~ nil ifTrue: [activeController controlTerminate]. aController centerCursorInView. self activeController: aController]! ! !ControlManager methodsFor: 'private' stamp: 'sw 12/6/1999 23:40'! unCacheWindows scheduledControllers ifNotNil: [scheduledControllers do: [:aController | aController view uncacheBits]]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ControlManager class instanceVariableNames: ''! !ControlManager class methodsFor: 'instance creation'! new ^super new initialize! ! !ControlManager class methodsFor: 'exchange'! newScheduler: controlManager "When switching projects, the control scheduler has to be exchanged. The active one is the one associated with the current project." Smalltalk at: #ScheduledControllers put: controlManager. ScheduledControllers restore. controlManager searchForActiveController! ! !ControlManager class methodsFor: 'snapshots' stamp: 'di 2/4/1999 15:16'! shutDown "Saves space in snapshots" Smalltalk isMorphic ifFalse: [ScheduledControllers unCacheWindows]! ! !ControlManager class methodsFor: 'snapshots' stamp: 'di 2/4/1999 09:00'! startUp Smalltalk isMorphic ifFalse: [ScheduledControllers restore]! ! Object subclass: #Controller instanceVariableNames: 'model view sensor lastActivityTime ' classVariableNames: 'MinActivityLapse ' poolDictionaries: '' category: 'Kernel-ST80 Remnants'! !Controller commentStamp: '' prior: 0! A Controller coordinates a View, its model, and user actions. It provides scheduling (control) behavior to determine when the user wants to communicate with the model or view.! !Controller methodsFor: 'initialize-release'! initialize "Initialize the state of the receiver. Subclasses should include 'super initialize' when redefining this message to insure proper initialization." sensor _ InputSensor default! ! !Controller methodsFor: 'initialize-release'! release "Breaks the cycle between the receiver and its view. It is usually not necessary to send release provided the receiver's view has been properly released independently." model _ nil. view ~~ nil ifTrue: [view controller: nil. view _ nil]! ! !Controller methodsFor: 'model access'! model "Answer the receiver's model which is the same as the model of the receiver's view." ^model! ! !Controller methodsFor: 'model access'! model: aModel "Controller|model: and Controller|view: are sent by View|controller: in order to coordinate the links between the model, view, and controller. In ordinary usage, the receiver is created and passed as the parameter to View|controller: so that the receiver's model and view links can be set up by the view." model _ aModel! ! !Controller methodsFor: 'view access'! inspectView view notNil ifTrue: [^ view inspect]! ! !Controller methodsFor: 'view access'! view "Answer the receiver's view." ^view! ! !Controller methodsFor: 'view access'! view: aView "Controller|view: and Controller|model: are sent by View|controller: in order to coordinate the links between the model, view, and controller. In ordinary usage, the receiver is created and passed as the parameter to View|controller: and the receiver's model and view links are set up automatically by the view." view _ aView! ! !Controller methodsFor: 'sensor access'! sensor "Answer the receiver's sensor. Subclasses may use other objects that are not instances of Sensor or its subclasses if more general kinds of input/output functions are required." ^sensor! ! !Controller methodsFor: 'sensor access'! sensor: aSensor "Set the receiver's sensor to aSensor." sensor _ aSensor! ! !Controller methodsFor: 'basic control sequence'! controlInitialize "Sent by Controller|startUp as part of the standard control sequence, it provides a place in the standard control sequence for initializing the receiver (taking into account the current state of its model and view). It should be redefined in subclasses to perform some specific action." ^self! ! !Controller methodsFor: 'basic control sequence' stamp: 'ls 7/11/1998 06:33'! controlLoop "Sent by Controller|startUp as part of the standard control sequence. Controller|controlLoop sends the message Controller|isControlActive to test for loop termination. As long as true is returned, the loop continues. When false is returned, the loop ends. Each time through the loop, the message Controller|controlActivity is sent." [self isControlActive] whileTrue: [ self interActivityPause. self controlActivity. Processor yield]! ! !Controller methodsFor: 'basic control sequence'! controlTerminate "Provide a place in the standard control sequence for terminating the receiver (taking into account the current state of its model and view). It should be redefined in subclasses to perform some specific action." ^self! ! !Controller methodsFor: 'basic control sequence' stamp: 'RAA 1/30/2001 19:06'! interActivityPause "if we are looping quickly, insert a short delay. Thus if we are just doing UI stuff, we won't take up much CPU" | currentTime wait | MinActivityLapse ifNotNil: [ lastActivityTime ifNotNil: [ currentTime _ Time millisecondClockValue. wait _ lastActivityTime + MinActivityLapse - currentTime. wait > 0 ifTrue: [ wait <= MinActivityLapse "big waits happen after a snapshot" ifTrue: [DisplayScreen checkForNewScreenSize. (Delay forMilliseconds: wait) wait ]. ]. ]. ]. lastActivityTime _ Time millisecondClockValue.! ! !Controller methodsFor: 'basic control sequence'! startUp "Give control to the receiver. The default control sequence is to initialize (see Controller|controlInitialize), to loop (see Controller|controlLoop), and then to terminate (see Controller|controlTerminate). After this sequence, control is returned to the sender of Control|startUp. The receiver's control sequence is used to coordinate the interaction of its view and model. In general, this consists of polling the sensor for user input, testing the input with respect to the current display of the view, and updating the model to reflect intended changes." self controlInitialize. self controlLoop. self controlTerminate! ! !Controller methodsFor: 'basic control sequence'! terminateAndInitializeAround: aBlock "1/12/96 sw" self controlTerminate. aBlock value. self controlInitialize! ! !Controller methodsFor: 'control defaults'! controlActivity "Pass control to the next control level (that is, to the Controller of a subView of the receiver's view) if possible. It is sent by Controller|controlLoop each time through the main control loop. It should be redefined in a subclass if some other action is needed." self controlToNextLevel! ! !Controller methodsFor: 'control defaults'! controlToNextLevel "Pass control to the next control level (that is, to the Controller of a subView of the receiver's view) if possible. The receiver finds the subView (if any) of its view whose inset display box (see View|insetDisplayBox) contains the sensor's cursor point. The Controller of this subView is then given control if it answers true in response to the message Controller|isControlWanted." | aView | aView _ view subViewWantingControl. aView ~~ nil ifTrue: [aView controller startUp]! ! !Controller methodsFor: 'control defaults' stamp: 'sma 3/11/2000 15:23'! isControlActive "Answer whether receiver wishes to continue evaluating its controlLoop method. It is sent by Controller|controlLoop in order to determine when the receiver's control loop should terminate, and should be redefined in a subclass if some special condition for terminating the main control loop is needed." ^ self viewHasCursor and: [sensor blueButtonPressed not and: [sensor yellowButtonPressed not]]! ! !Controller methodsFor: 'control defaults'! isControlWanted "Answer whether the cursor is inside the inset display box (see View|insetDisplayBox) of the receiver's view. It is sent by Controller|controlNextLevel in order to determine whether or not control should be passed to this receiver from the Controller of the superView of this receiver's view." ^self viewHasCursor! ! !Controller methodsFor: 'cursor'! centerCursorInView "Position sensor's mousePoint (which is assumed to be connected to the cursor) to the center of its view's inset display box (see Sensor|mousePoint: and View|insetDisplayBox)." ^sensor cursorPoint: view insetDisplayBox center! ! !Controller methodsFor: 'cursor' stamp: 'sw 7/13/1999 18:42'! viewHasCursor "Answer whether the cursor point of the receiver's sensor lies within the inset display box of the receiver's view (see View|insetDisplayBox). Controller|viewHasCursor is normally used in internal methods." ^ view ifNotNil: [view containsPoint: sensor cursorPoint] ifNil: [false]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Controller class instanceVariableNames: ''! !Controller class methodsFor: 'instance creation'! new ^super new initialize! ! !Controller class methodsFor: 'initialization' stamp: 'ls 7/13/1998 00:47'! MinActivityLapse: milliseconds "minimum time to delay between calls to controlActivity" MinActivityLapse _ milliseconds ifNotNil: [ milliseconds rounded ].! ! !Controller class methodsFor: 'initialization' stamp: 'ls 7/13/1998 00:47'! initialize "Controller initialize" self MinActivityLapse: 10.! ! Object subclass: #CornerRounder instanceVariableNames: 'cornerMasks cornerOverlays underBits ' classVariableNames: 'CR0 CR1 CR2 ' poolDictionaries: '' category: 'Graphics-Display Objects'! !CornerRounder commentStamp: '' prior: 0! This class is a quick hack to support rounded corners in morphic. Rather than produce rounded rectangles, it tweaks the display of corners. Rather than work for any radius, it only supports a radius of 6. Rather than work for any border width, it only supports widths 0, 1 and 2. The corners, while apparently transparent, still behave opaquely to mouse clicks. Worse than this, the approach relies on the ability to extract underlying bits from the canvas prior to display. This ran afoul of top-down display, it seems, in SystemWindow spawnReframeHandle: (qv). It will also make a postscript printer very unhappy. But, hey, it's cute.! !CornerRounder methodsFor: 'all' stamp: 'di 6/24/1999 09:35'! masterMask: maskForm masterOverlay: overlayForm cornerMasks _ #(none left pi right) collect: [:dir | (maskForm rotateBy: dir centerAt: 0@0) offset: 0@0]. cornerOverlays _ #(none left pi right) collect: [:dir | (overlayForm rotateBy: dir centerAt: 0@0) offset: 0@0]. ! ! !CornerRounder methodsFor: 'all' stamp: 'ar 2/12/2000 18:33'! saveBitsUnderCornersOf: aMorph on: aCanvas | offset corner mask form | underBits _ (1 to: 4) collect: [:i | mask _ cornerMasks at: i. corner _ aMorph bounds corners at: i. i = 1 ifTrue: [offset _ 0@0]. i = 2 ifTrue: [offset _ 0@mask height negated]. i = 3 ifTrue: [offset _ mask extent negated]. i = 4 ifTrue: [offset _ mask width negated@0]. form _ aCanvas contentsOfArea: (corner + offset extent: mask extent). form copyBits: form boundingBox from: mask at: 0@0 clippingBox: form boundingBox rule: Form and fillColor: nil map: (Bitmap with: 16rFFFFFFFF with: 0)]. ! ! !CornerRounder methodsFor: 'all' stamp: 'ar 3/24/2000 01:10'! tweakCornersOf: aMorph on: aCanvas borderWidth: w | offset corner saveBits c fourColors c14 c23 mask nonShadowCanvas outBits | nonShadowCanvas _ aCanvas copy shadowColor: nil. w > 0 ifTrue: [c _ aMorph borderColor. fourColors _ Array new: 4 withAll: c. c == #raised ifTrue: [c _ aMorph color. w = 1 ifTrue: [c14 _ c twiceLighter. c23 _ c twiceDarker] ifFalse: [c14 _ c lighter. c23 _ c darker]. fourColors _ Array with: c14 with: c with: c23 with: c]. (c == #inset and: [aMorph owner notNil]) ifTrue: [c _ aMorph owner colorForInsets. w = 1 ifTrue: [c14 _ c twiceLighter. c23 _ c twiceDarker] ifFalse: [c14 _ c lighter. c23 _ c darker]. fourColors _ Array with: c23 with: c with: c14 with: c]]. mask _ Form extent: cornerMasks first extent depth: aCanvas depth. (1 to: 4) do: [:i | corner _ aMorph bounds corners at: i. saveBits _ underBits at: i. i = 1 ifTrue: [offset _ 0@0]. i = 2 ifTrue: [offset _ 0@saveBits height negated]. i = 3 ifTrue: [offset _ saveBits extent negated]. i = 4 ifTrue: [offset _ saveBits width negated@0]. "Mask out corner area (painting saveBits won't clear if transparent)." mask copyBits: mask boundingBox from: (cornerMasks at: i) at: 0@0 clippingBox: mask boundingBox rule: Form over fillColor: nil map: (Bitmap with: 0 with: 16rFFFFFFFF). outBits _ aCanvas contentsOfArea: (corner + offset extent: mask extent). mask displayOn: outBits at: 0@0 rule: Form and. "Paint back corner bits." saveBits displayOn: outBits at: 0@0 rule: Form paint. nonShadowCanvas drawImage: outBits at: corner + offset. w > 0 ifTrue: ["Paint over with border if any" aCanvas stencil: (cornerOverlays at: i) at: corner + offset color: (fourColors at: i)]]. ! ! !CornerRounder methodsFor: 'all' stamp: 'ar 10/26/2000 20:03'! tweakCornersOf: aMorph on: aCanvas borderWidth: w corners: cornerList "This variant has a cornerList argument, to allow some corners to be rounded and others not" | offset corner saveBits c fourColors c14 c23 insetColor mask outBits shadowColor | shadowColor _ aCanvas shadowColor. aCanvas shadowColor: nil. "for tweaking it's essential" w > 0 ifTrue: [c _ shadowColor ifNil:[aMorph borderColor]. fourColors _ Array new: 4 withAll: c. c == #raised ifTrue: [c14 _ aMorph color lighter. c23 _ aMorph color darker. fourColors _ Array with: c14 with: c23 with: c23 with: c14]. (c == #inset and: [aMorph owner notNil]) ifTrue: [insetColor _ aMorph owner colorForInsets. c14 _ insetColor lighter. c23 _ insetColor darker. fourColors _ Array with: c14 with: c23 with: c23 with: c14]]. mask _ Form extent: cornerMasks first extent depth: aCanvas depth. 1 to: 4 do:[:i | (cornerList includes: i) ifTrue: [corner _ aMorph bounds corners at: i. saveBits _ underBits at: i. i = 1 ifTrue: [offset _ 0@0]. i = 2 ifTrue: [offset _ 0@saveBits height negated]. i = 3 ifTrue: [offset _ saveBits extent negated]. i = 4 ifTrue: [offset _ saveBits width negated@0]. "Mask out corner area (painting saveBits won't clear if transparent)." mask copyBits: mask boundingBox from: (cornerMasks at: i) at: 0@0 clippingBox: mask boundingBox rule: Form over fillColor: nil map: (Bitmap with: 0 with: 16rFFFFFFFF). outBits _ aCanvas contentsOfArea: (corner + offset extent: mask extent). mask displayOn: outBits at: 0@0 rule: Form and. "Paint back corner bits." saveBits displayOn: outBits at: 0@0 rule: Form paint. "Paint back corner bits." aCanvas drawImage: outBits at: corner + offset. w > 0 ifTrue: ["Paint over with border if any" aCanvas stencil: (cornerOverlays at: i) at: corner + offset color: (fourColors at: i)]]]. aCanvas shadowColor: shadowColor. "restore shadow color" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CornerRounder class instanceVariableNames: ''! !CornerRounder class methodsFor: 'all' stamp: 'di 6/28/1999 15:51'! initialize "CornerRounder initialize" CR0 _ CR1 _ self new masterMask: (Form extent: 6@6 fromArray: #(2r1e26 2r111e26 2r1111e26 2r11111e26 2r11111e26 2r111111e26) offset: 0@0) masterOverlay: (Form extent: 6@6 fromArray: #(2r1e26 2r110e26 2r1000e26 2r10000e26 2r10000e26 2r100000e26) offset: 0@0). CR2 _ self new masterMask: (Form extent: 6@6 fromArray: #(2r1e26 2r111e26 2r1111e26 2r11111e26 2r11111e26 2r111111e26) offset: 0@0) masterOverlay: (Form extent: 6@6 fromArray: #(2r1e26 2r111e26 2r1111e26 2r11100e26 2r11000e26 2r111000e26) offset: 0@0). ! ! !CornerRounder class methodsFor: 'all' stamp: 'di 3/25/2000 11:12'! rectWithinCornersOf: aRectangle "Return a single sub-rectangle that lies entirely inside corners that are made by me. Used to identify large regions of window that do not need to be redrawn." ^ aRectangle insetBy: 0@6! ! !CornerRounder class methodsFor: 'all' stamp: 'hmm 3/8/2000 17:42'! roundCornersOf: aMorph on: aCanvas displayBlock: displayBlock borderWidth: w | rounder | rounder _ CR0. w = 1 ifTrue: [rounder _ CR1]. w = 2 ifTrue: [rounder _ CR2]. rounder _ rounder copy. rounder saveBitsUnderCornersOf: aMorph on: aCanvas. displayBlock value. rounder tweakCornersOf: aMorph on: aCanvas borderWidth: w! ! !CornerRounder class methodsFor: 'all' stamp: 'hmm 3/8/2000 17:42'! roundCornersOf: aMorph on: aCanvas displayBlock: displayBlock borderWidth: w corners: aList | rounder | rounder _ CR0. w = 1 ifTrue: [rounder _ CR1]. w = 2 ifTrue: [rounder _ CR2]. rounder _ rounder copy. rounder saveBitsUnderCornersOf: aMorph on: aCanvas. displayBlock value. rounder tweakCornersOf: aMorph on: aCanvas borderWidth: w corners: aList! ! Object subclass: #CosineInterpolator instanceVariableNames: 'origin points stack ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Support'! !CosineInterpolator methodsFor: 'initialization' stamp: 'len 12/13/1999 02:42'! initialize points _ SortedCollection new. stack _ SortedCollection new. origin _ 0! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 12/14/1999 00:49'! at: time "Answer the value of the receiver at a given time. (Do linear interpolation.)" ^ self cosineAt: time + self origin! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 11/24/1999 01:59'! at: time put: value self points add: time + self origin -> value. ^ value! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 12/4/1999 17:30'! commit self cleanBetween: stack first key and: stack last key. self points addAll: stack. stack _ SortedCollection new! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 12/4/1999 17:22'! duration ^ self points last key - self points first key! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 11/24/1999 01:59'! origin ^ origin! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 11/24/1999 01:59'! origin: aNumber origin _ aNumber! ! !CosineInterpolator methodsFor: 'accessing' stamp: 'len 12/4/1999 17:20'! x: x y: y stack add: x + self origin -> y! ! !CosineInterpolator methodsFor: 'private' stamp: 'len 11/23/1999 01:08'! cleanBetween: start and: end self points: (self points reject: [ :each | each key between: start and: end])! ! !CosineInterpolator methodsFor: 'private' stamp: 'len 11/23/1999 00:42'! cosineAt: time "Answer the value of the receiver at a given time. (Do cosine interpolation.)" | xVal count x1 x2 y1 y2 | points isNil ifTrue: [^ nil]. xVal _ points first key. count _ 1. [xVal < time] whileTrue: [count _ count + 1. count > points size ifTrue: [^ points last value]. xVal _ (points at: count) key]. xVal = time ifTrue: [^ (points at: count) value]. count = 1 ifTrue: [^ points first value]. x1 _ (points at: count - 1) key. x2 _ (points at: count) key. y1 _ (points at: count - 1) value. y2 _ (points at: count) value. ^ ((time - x1 / (x2 - x1) * Float pi) cos - 1 / -2.0) * (y2 - y1) + y1! ! !CosineInterpolator methodsFor: 'private' stamp: 'len 12/4/1999 15:54'! linearAt: time "Answer the value of the receiver at a given time. (Do linear interpolation.)" | xVal count x1 x2 y1 y2 | points isNil ifTrue: [^ nil]. xVal _ points first key. count _ 1. [xVal < time] whileTrue: [count _ count + 1. count > points size ifTrue: [^ points last value]. xVal _ (points at: count) key]. xVal = time ifTrue: [^ (points at: count) value]. count = 1 ifTrue: [^ points first value]. x1 _ (points at: count - 1) key. x2 _ (points at: count) key. y1 _ (points at: count - 1) value. y2 _ (points at: count) value. ^ (time - x1) / (x2 - x1) * (y2 - y1) + y1! ! !CosineInterpolator methodsFor: 'private' stamp: 'len 12/4/1999 17:29'! points ^ points! ! !CosineInterpolator methodsFor: 'private' stamp: 'len 11/23/1999 00:41'! points: aCollection points _ aCollection! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CosineInterpolator class instanceVariableNames: ''! !CosineInterpolator class methodsFor: 'instance creation' stamp: 'len 12/13/1999 02:15'! fromArray: anArray | answer | answer _ self new. 1 to: anArray size by: 2 do: [ :each | answer at: (anArray at: each) put: (anArray at: each + 1)]. ^ answer! ! !CosineInterpolator class methodsFor: 'instance creation' stamp: 'len 12/4/1999 17:29'! new ^ super new initialize! ! StandardFileStream subclass: #CrLfFileStream instanceVariableNames: 'lineEndConvention ' classVariableNames: 'Cr CrLf Lf LineEndDefault LineEndStrings LookAheadCount ' poolDictionaries: '' category: 'System-Files'! !CrLfFileStream methodsFor: 'open/close' stamp: 'ar 1/20/98 16:15'! open: aFileName forWrite: writeMode "Open the receiver. If writeMode is true, allow write, else access will be read-only. " | result | result _ super open: aFileName forWrite: writeMode. result ifNotNil: [self detectLineEndConvention]. ^ result! ! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:16'! ascii super ascii. self detectLineEndConvention! ! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:16'! binary super binary. lineEndConvention _ nil! ! !CrLfFileStream methodsFor: 'access' stamp: 'ls 7/10/1998 23:35'! detectLineEndConvention "Detect the line end convention used in this stream. The result may be either #cr, #lf or #crlf." | char numRead pos | self isBinary ifTrue: [^ self error: 'Line end conventions are not used on binary streams']. lineEndConvention _ LineEndDefault. "Default if nothing else found" numRead _ 0. pos _ super position. [super atEnd not and: [numRead < LookAheadCount]] whileTrue: [char _ super next. char = Lf ifTrue: [super position: pos. ^ lineEndConvention _ #lf]. char = Cr ifTrue: [super peek = Lf ifTrue: [lineEndConvention _ #crlf] ifFalse: [lineEndConvention _ #cr]. super position: pos. ^ lineEndConvention]. numRead _ numRead + 1]. super position: pos. ^ lineEndConvention! ! !CrLfFileStream methodsFor: 'access' stamp: 'ls 11/5/1998 23:37'! next | char secondChar | char _ super next. self isBinary ifTrue: [^char]. char == Cr ifTrue: [secondChar _ super next. secondChar ifNotNil: [secondChar == Lf ifFalse: [self skip: -1]]. ^Cr]. char == Lf ifTrue: [^Cr]. ^char! ! !CrLfFileStream methodsFor: 'access' stamp: 'ls 12/29/1998 17:15'! next: n | string peekChar | string _ super next: n. string size = 0 ifTrue: [ ^string ]. self isBinary ifTrue: [ ^string ]. "if we just read a CR, and the next character is an LF, then skip the LF" ( string last = Character cr ) ifTrue: [ peekChar _ super next. "super peek doesn't work because it relies on #next" peekChar ~= Character lf ifTrue: [ super position: (super position - 1) ]. ]. string _ string withSqueakLineEndings. string size = n ifTrue: [ ^string ]. "string shrunk due to embedded crlfs; make up the difference" ^string, (self next: n - string size)! ! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:18'! nextPut: char (lineEndConvention notNil and: [char = Cr]) ifTrue: [super nextPutAll: (LineEndStrings at: lineEndConvention)] ifFalse: [super nextPut: char]. ^ char! ! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:18'! nextPutAll: aString super nextPutAll: (self convertStringFromCr: aString). ^ aString ! ! !CrLfFileStream methodsFor: 'access' stamp: 'wod 6/18/1998 13:52'! peek "Answer what would be returned if the message next were sent to the receiver. If the receiver is at the end, answer nil. " | next pos | self atEnd ifTrue: [^ nil]. pos _ self position. next _ self next. self position: pos. ^ next! ! !CrLfFileStream methodsFor: 'access' stamp: 'wod 11/5/1998 14:15'! upTo: aCharacter | newStream char | newStream _ WriteStream on: (String new: 100). [(char _ self next) isNil or: [char == aCharacter]] whileFalse: [newStream nextPut: char]. ^ newStream contents ! ! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:18'! verbatim: aString super verbatim: (self convertStringFromCr: aString). ^ aString! ! !CrLfFileStream methodsFor: 'private' stamp: 'ar 1/20/98 16:21'! convertStringFromCr: aString | inStream outStream | lineEndConvention ifNil: [^ aString]. lineEndConvention == #cr ifTrue: [^ aString]. lineEndConvention == #lf ifTrue: [^ aString copy replaceAll: Cr with: Lf]. "lineEndConvention == #crlf" inStream _ ReadStream on: aString. outStream _ WriteStream on: (String new: aString size). [inStream atEnd] whileFalse: [outStream nextPutAll: (inStream upTo: Cr). (inStream atEnd not or: [aString last = Cr]) ifTrue: [outStream nextPutAll: CrLf]]. ^ outStream contents! ! !CrLfFileStream methodsFor: 'private' stamp: 'ar 1/20/98 16:21'! convertStringToCr: aString | inStream outStream | lineEndConvention ifNil: [^ aString]. lineEndConvention == #cr ifTrue: [^ aString]. lineEndConvention == #lf ifTrue: [^ aString copy replaceAll: Lf with: Cr]. "lineEndConvention == #crlf" inStream _ ReadStream on: aString. outStream _ WriteStream on: (String new: aString size). [inStream atEnd] whileFalse: [outStream nextPutAll: (inStream upTo: Cr). (inStream atEnd not or: [aString last = Cr]) ifTrue: [outStream nextPut: Cr. inStream peek = Lf ifTrue: [inStream next]]]. ^ outStream contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CrLfFileStream class instanceVariableNames: ''! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'ar 1/20/98 16:10'! defaultToCR "CrLfFileStream defaultToCR" LineEndDefault := #cr.! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'ar 1/20/98 16:10'! defaultToCRLF "CrLfFileStream defaultToCRLF" LineEndDefault := #crlf.! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'ar 1/20/98 16:10'! defaultToLF "CrLfFileStream defaultToLF" LineEndDefault := #lf.! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'ar 1/20/98 16:13'! guessDefaultLineEndConvention "Lets try to guess the line end convention from what we know about the path name delimiter from FileDirectory." FileDirectory pathNameDelimiter = $: ifTrue:[^self defaultToCR]. FileDirectory pathNameDelimiter = $/ ifTrue:[^self defaultToLF]. FileDirectory pathNameDelimiter = $\ ifTrue:[^self defaultToCRLF]. "in case we don't know" ^self defaultToCR! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'di 2/4/1999 09:16'! initialize "CrLfFileStream initialize" Cr := Character cr. Lf := Character lf. CrLf := String with: Cr with: Lf. LineEndStrings := Dictionary new. LineEndStrings at: #cr put: (String with: Character cr). LineEndStrings at: #lf put: (String with: Character lf). LineEndStrings at: #crlf put: (String with: Character cr with: Character lf). LookAheadCount := 2048. Smalltalk addToStartUpList: self. self startUp.! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'djp 1/28/1999 22:08'! startUp self guessDefaultLineEndConvention! ! WordGamePanelMorph subclass: #CrosticPanel instanceVariableNames: 'crosticPanel quotePanel cluesCol2 answers quote clues cluesPanel ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !CrosticPanel commentStamp: '' prior: 0! The CrosticPanel, as its name suggests, is a tool for decoding acrostic puzzles, such as are presented on the puzzle pages of some Sunday newspapers. Much of the capability is inherited from the two WordGame classes used. To try it out, choose newMorph/Games/CrosticPanel in a morphic project, or execute, in any project: CrosticPanel new openInWorld The instance variables of this class include... letterMorphs (in superclass) a collection of all the letterMorphs in this panel quote a string, being the entire quote in uppercase with no blanks clues a collection of the clue strings answers a collection of the answer indices. For each answer, this is an array of the indices into the quote string. The final structure of a CrosticPanel is as follows self a CrosticPanel the overall holder quotePanel a CrosticQuotePanel holds the grid of letters from the quote cluesPanel an AlignmentMorph holds most of the clue rows cluesCol2 an AlignmentMorph holds the rest of the clue rows Each clue row is a horizontal AlignmentMorph with a textMorph and another alignmentMorph full of the letterMorphs for the answer. ! !CrosticPanel methodsFor: 'initialization' stamp: 'di 11/25/2000 19:17'! breakColumnAndResizeWithButtons: buttonRow | indexToSplit yToSplit | "The column of clues has been laid out, and the crostic panel has been resized to that width and embedded as a submorph. This method breaks the clues in two, placing the long part to the left of the crostic and the short one below it." yToSplit _ cluesPanel height + quotePanel height // 2 + self top. indexToSplit _ cluesPanel submorphs findFirst: [:m | m bottom > yToSplit]. cluesCol2 _ AlignmentMorph newColumn color: self color; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 0; cellPositioning: #topLeft. cluesCol2 addAllMorphs: (cluesPanel submorphs copyFrom: indexToSplit + 1 to: cluesPanel submorphs size). cluesPanel position: self position + self borderWidth + (0 @ 4). quotePanel position: self position + (quotePanel width @ 0). cluesCol2 position: self position + quotePanel extent + (0 @ 4). self addMorph: cluesCol2. self addMorph: buttonRow. buttonRow align: buttonRow topLeft with: cluesCol2 bottomLeft. self extent: 100@100; bounds: ((self fullBounds topLeft - self borderWidth asPoint) corner: (self fullBounds bottomRight - (2@0))). ! ! !CrosticPanel methodsFor: 'initialization' stamp: 'di 11/28/2000 10:40'! quote: indexableQuote clues: clueStrings answers: answerIndices quotePanel: panel | row clue answer answerMorph letterMorph prev clueText clueStyle | quote _ indexableQuote. quotePanel _ panel. clues _ clueStrings. answers _ answerIndices. cluesPanel _ AlignmentMorph newColumn color: self color; hResizing: #shrinkWrap; vResizing: #shrinkWrap; cellPositioning: #topLeft; layoutInset: 1. letterMorphs _ Array new: quotePanel letterMorphs size. clueStyle _ nil. 1 to: clues size do: [:i | clue _ clues at: i. answer _ answers at: i. row _ AlignmentMorph newRow cellPositioning: #bottomLeft. clueText _ (TextMorph newBounds: (0@0 extent: 120@20) color: Color black) string: (CrosticPanel oldStyle ifTrue: [(($A to: $Z) at: i) asString , '. ' , clue] ifFalse: [clue]) fontName: 'ComicPlain' size: 13. clueStyle ifNil: ["Make up a special style with decreased leading" clueStyle _ clueText textStyle copy. clueStyle gridForFont: 1 withLead: -2]. clueText text: clueText asText textStyle: clueStyle. "All clues share same style" clueText composeToBounds. row addMorphBack: clueText. answerMorph _ AlignmentMorph newRow layoutInset: 0. prev _ nil. answer do: [:n | letterMorph _ WordGameLetterMorph new underlined indexInQuote: n id1: (CrosticPanel oldStyle ifTrue: [n printString] ifFalse: [nil]); setLetter: Character space. letterMorph on: #mouseDown send: #mouseDownEvent:letterMorph: to: self. letterMorph on: #keyStroke send: #keyStrokeEvent:letterMorph: to: self. letterMorph predecessor: prev. prev ifNotNil: [prev successor: letterMorph]. prev _ letterMorph. letterMorphs at: n put: letterMorph. answerMorph addMorphBack: letterMorph]. answerMorph color: answerMorph firstSubmorph color. row addMorphBack: answerMorph. row fullBounds. row color: answerMorph firstSubmorph color. cluesPanel addMorphBack: row]. self addMorph: cluesPanel. self bounds: cluesPanel fullBounds. ! ! !CrosticPanel methodsFor: 'events' stamp: 'di 5/11/2000 20:44'! highlight: morph self unhighlight. quotePanel unhighlight. morph startOfWord morphsInWordDo: [:m | m color: Color lightGreen. (quotePanel letterMorphs at: m indexInQuote) color: Color lightMagenta]. morph color: Color green. (quotePanel letterMorphs at: morph indexInQuote) color: Color magenta. ! ! !CrosticPanel methodsFor: 'events' stamp: 'di 5/11/2000 20:44'! keyCharacter: aLetter atIndex: indexInQuote nextFocus: nextFocus (self letterMorphs at: indexInQuote) setLetter: aLetter. (quotePanel letterMorphs at: indexInQuote) setLetter: aLetter. self highlight: nextFocus ! ! !CrosticPanel methodsFor: 'events' stamp: 'di 5/11/2000 20:44'! lostFocus self unhighlight. quotePanel unhighlight! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/11/2000 23:38'! addMenuItemsTo: aMenu hand: aHandMorph aMenu add: 'show crostic help' target: self action: #showHelpWindow. aMenu add: 'show crostic hints' target: self action: #showHintsWindow. aMenu add: 'show crostic errors' target: self action: #showErrors. aMenu add: 'clear crostic typing' target: self action: #clearTyping. aMenu add: 'open crostic file...' target: self action: #openFile. ! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/12/2000 00:17'! buttonRow | row aButton | row _ AlignmentMorph newRow color: self color; hResizing: #shrinkWrap; vResizing: #shrinkWrap. aButton _ SimpleButtonMorph new target: self. aButton color: Color transparent; borderWidth: 1; borderColor: Color black. #('show help' 'show errors' 'show hints' 'clear' 'open...') with: #(showHelpWindow showErrors showHintsWindow clearTyping openFile) do: [:label :selector | aButton _ aButton fullCopy. aButton actionSelector: selector. aButton label: label. row addMorphBack: aButton. row addTransparentSpacerOfSize: (3 @ 0)]. ^ row ! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/12/2000 00:25'! clearTyping self isClean ifTrue: [^ self]. (self confirm: 'Are you sure you want to discard all typing?') ifFalse: [^ self]. super clearTyping. quotePanel clearTyping! ! !CrosticPanel methodsFor: 'menu' stamp: 'mdr 8/31/2000 18:43'! openFile | stdFileMenuResult crostic file | stdFileMenuResult _ ((StandardFileMenu new) pattern: '*.crostic'; oldFileFrom: FileDirectory default ) startUpWithCaption: 'Select a Crostic File...'. stdFileMenuResult ifNil: [^ nil]. file _ stdFileMenuResult directory readOnlyFileNamed: stdFileMenuResult name. crostic _ CrosticPanel newFromFile: file. file close. (self isClean or: [self confirm: 'Is it OK to discard this crostic?']) ifTrue: [self world addMorphFront: (crostic position: self position). self delete] ifFalse: [self world addMorphFront: crostic]. ! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/12/2000 15:09'! showErrors letterMorphs do: [:m | (m letter ~= Character space and: [m letter ~= (quote at: m indexInQuote)]) ifTrue: [m color: Color red. (quotePanel letterMorphs at: m indexInQuote) color: Color red]]! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/11/2000 16:44'! showHelpWindow ((StringHolder new contents: 'The Crostic Panel presents an acrostic puzzle for solution. As you type in answers for the clues, the letters also get entered in the text of the hidden quote. Conversely, as you guess words in the quote, those letters will fill in missing places in your answers. In addition, the first letters of all the answers together form the author''s name and title of the work from which the quote is taken. If you wish to make up other acrostic puzzles, follow the obvious file format in the sampleFile method. If you wish to print an acrostic to work it on paper, then change the oldStyle method to return true, and it will properly cross-index all the cells. Have fun.') embeddedInMorphicWindowLabeled: 'About the Crostic Panel') setWindowColor: (Color r: 1.0 g: 0.6 b: 0.0); openInWorld: self world extent: 409@207! ! !CrosticPanel methodsFor: 'menu' stamp: 'di 5/11/2000 21:07'! showHintsWindow | hints | (self confirm: 'As hints, you will be given the five longest answers. Do you really want to do this?') ifFalse: [^ self]. hints _ (answers asSortedCollection: [:x :y | x size > y size]) asArray copyFrom: 1 to: 5. ((StringHolder new contents: 'The five longest answers are... ' , (String streamContents: [:strm | hints do: [:hint | strm cr; nextPutAll: (hint collect: [:i | quote at: i])]. strm cr; cr]) , 'Good luck!!') embeddedInMorphicWindowLabeled: 'Crostic Hints') setWindowColor: (Color r: 1.0 g: 0.6 b: 0.0); openInWorld: self world extent: 198@154! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CrosticPanel class instanceVariableNames: ''! !CrosticPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/11/2000 20:37'! includeInNewMorphMenu ^ true! ! !CrosticPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/11/2000 20:37'! new "NOTE: Use newFromFile: rather than new to create new CrosticPanels" ^ self newFromFile: (ReadStream on: self sampleFile)! ! !CrosticPanel class methodsFor: 'as yet unclassified' stamp: 'di 11/30/2000 09:51'! newFromFile: aStream "World addMorph: CrosticPanel new" "World addMorph: (CrosticPanel newFromFile: (FileStream readOnlyFileNamed: 'first.crostic'))" | quoteWithBlanks citation clue numberLine numbers clues answers indexableQuote quotePanel crosticPanel buttonRow quoteWidth | (aStream next asciiValue = 16r1F) & (aStream next asciiValue = 16r8B) ifTrue: ["It's gzipped..." aStream skip: -2. ^ self newFromFile: aStream asUnZippedStream ascii]. aStream skip: -2. quoteWithBlanks _ aStream nextLine. quoteWithBlanks _ quoteWithBlanks asUppercase select: [:c | c isLetter or: [' -' includes: c]]. indexableQuote _ quoteWithBlanks select: [:c | c isLetter]. citation _ aStream nextLine. aStream nextLine. clues _ OrderedCollection new. answers _ OrderedCollection new. [aStream atEnd] whileFalse: [clue _ aStream nextLine. "Transcript cr; show: clue." clues addLast: clue. numberLine _ aStream nextLine. numbers _ Scanner new scanTokens: numberLine. answers addLast: numbers]. aStream close. "Consistency check: " (citation asUppercase select: [:c | c isLetter]) = (String withAll: (answers collect: [:a | indexableQuote at: a first])) ifFalse: [self error: 'mal-formed crostic file']. crosticPanel _ super new. quotePanel _ CrosticQuotePanel new quote: quoteWithBlanks answers: answers cluesPanel: crosticPanel. crosticPanel color: quotePanel firstSubmorph color; quote: indexableQuote clues: clues answers: answers quotePanel: quotePanel. buttonRow _ crosticPanel buttonRow. quoteWidth _ (crosticPanel width + quotePanel firstSubmorph width) max: buttonRow width. quotePanel extent: quoteWidth @ 9999. crosticPanel addMorph: quotePanel. ^ crosticPanel breakColumnAndResizeWithButtons: buttonRow ! ! !CrosticPanel class methodsFor: 'as yet unclassified' stamp: 'di 5/12/2000 15:12'! oldStyle "return true if we should cross-index all the cells (takes more space)." ^ false! ! !CrosticPanel class methodsFor: 'as yet unclassified' stamp: 'di 11/30/2000 10:15'! sampleFile "If you want to enter a new acrostic, follow this format exactly with regard to CRs and the like, and store it in a file. Do not double the string quotes as here -- that is only because they are embedded in a string. Finally, compress the file in the fileList (so it will be easy to transport and hard to read), and name it 'yourName.crostic' so that the 'open' button on the panel will recognize it." ^ 'Men and women do not feel the same way about dirt. Women for some hormonal reason can see individual dirt molecules, whereas men tend not to notice them until they join together into clumps large enough to support commercial agriculture. Dave Barry''s Guide to Marriage Boccaccio''s collection of tales 74 19 175 156 9 122 84 113 104 Wooden instrument of Swiss herders 67 184 153 103 14 142 148 54 3 Evening service 76 99 154 171 89 194 69 Russian-born American anarchist (2 wds) 159 102 177 25 186 134 128 82 50 62 11 Apple-polish (2 wds) 32 190 129 126 179 157 79 170 Visual-gesture means of communication 4 178 27 168 150 185 114 Postponed contest 173 58 77 65 8 124 85 Groundbreaking invention 98 15 116 162 112 37 92 155 70 187 Material used to make English longbows 132 195 28 Gracile 48 191 145 152 Have the effrontery; experience a high (2 wds) 164 61 137 33 17 45 Florentine painter who experimented with perspective 91 181 189 2 20 81 167 Sondheim opus (3 wds) 72 109 147 13 192 165 93 40 115 138 6 63 Spanish rake 108 56 44 133 193 29 125 Emergence as of an adult butterfly 106 149 59 41 24 135 87 68 Type of rifle (hyph) 111 7 143 73 39 30 105 95 53 Free of charge (3 wds) 176 107 120 130 160 22 46 34 94 71 Pie filling 86 75 136 118 43 Master filmmaker 31 151 174 51 163 144 Longtime sportswriter for the NY Herald tribune (2 wds) 60 140 12 101 55 188 166 121 Birthplace of Erasmus 47 64 141 21 10 180 36 80 1 Mae West classic (3 wds) 127 123 161 110 183 5 139 97 88 Element that glows blue in the dark 100 90 35 182 146 117 169 26 Sturm und Drang writer 158 172 119 16 52 23 Starfish or sea cucumber 18 66 96 83 57 49 78 131 38 42 '! ! WordGamePanelMorph subclass: #CrosticQuotePanel instanceVariableNames: 'cluesPanel ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !CrosticQuotePanel methodsFor: 'initialization' stamp: 'di 5/7/2000 11:59'! extent: newExtent | w h nAcross relLoc topLeft | w _ self firstSubmorph width - 1. h _ self firstSubmorph height - 1. nAcross _ newExtent x - (self borderWidth-1*2)-1 // w. topLeft _ self position + self borderWidth - 1. submorphs withIndexDo: [:m :i | relLoc _ (i-1 \\ nAcross * w) @ (i-1 // nAcross * h). m position: topLeft + relLoc]. super extent: ((w * nAcross + 1) @ (submorphs size - 1 // nAcross + 1 * h+1)) + (self borderWidth - 1 * 2). ! ! !CrosticQuotePanel methodsFor: 'initialization' stamp: 'di 5/12/2000 00:07'! quote: quoteWithBlanks answers: theAnswers cluesPanel: panel | n morph prev clueIxs | cluesPanel _ panel. self color: Color gray. clueIxs _ Array new: quoteWithBlanks size. theAnswers withIndexDo: [:a :i | a do: [:j | clueIxs at: j put: i]]. letterMorphs _ OrderedCollection new. prev _ nil. self addAllMorphs: (quoteWithBlanks asArray collect: [:c | c isLetter ifTrue: [n _ letterMorphs size + 1. morph _ WordGameLetterMorph new boxed. CrosticPanel oldStyle ifTrue: [morph indexInQuote: n id1: n printString. morph id2: (($A to: $Z) at: (clueIxs at: n)) asString] ifFalse: [morph indexInQuote: n id1: nil]. morph setLetter: Character space. morph on: #mouseDown send: #mouseDownEvent:letterMorph: to: self. morph on: #keyStroke send: #keyStrokeEvent:letterMorph: to: self. letterMorphs addLast: morph] ifFalse: [morph _ WordGameLetterMorph new boxed indexInQuote: nil id1: nil. CrosticPanel oldStyle ifTrue: [morph extent: 26@24 "Oops"]]. morph predecessor: prev. prev ifNotNil: [prev successor: morph]. prev _ morph]). ! ! !CrosticQuotePanel methodsFor: 'events' stamp: 'di 5/11/2000 09:37'! highlight: morph self unhighlight. cluesPanel unhighlight. morph startOfWord morphsInWordDo: [:m | m color: Color lightGreen. (cluesPanel letterMorphs at: m indexInQuote) color: Color lightMagenta]. morph color: Color green. (cluesPanel letterMorphs at: morph indexInQuote) color: Color magenta. ! ! !CrosticQuotePanel methodsFor: 'events' stamp: 'di 5/10/2000 09:25'! keyCharacter: aLetter atIndex: indexInQuote nextFocus: nextFocus (self letterMorphs at: indexInQuote) setLetter: aLetter. (cluesPanel letterMorphs at: indexInQuote) setLetter: aLetter. self highlight: nextFocus ! ! !CrosticQuotePanel methodsFor: 'events' stamp: 'di 5/10/2000 08:49'! lostFocus self unhighlight. cluesPanel unhighlight! ! Object subclass: #CurrentProjectRefactoring instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CurrentProjectRefactoring class instanceVariableNames: ''! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 10:55'! currentAddGuard: anObject " CurrentProjectRefactoring currentAddGuard: " ^self xxxCurrent addGuard: anObject! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 11:00'! currentBeIsolated " CurrentProjectRefactoring currentBeIsolated " ^self xxxCurrent beIsolated! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:50'! currentBeParentTo: anotherProject " CurrentProjectRefactoring currentBeParentTo: " ^anotherProject setParent: self xxxCurrent! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:50'! currentBeParentToCurrent " CurrentProjectRefactoring currentBeParentToCurrent " ^self xxxCurrent setParent: self xxxCurrent! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:50'! currentFlapsSuppressed " CurrentProjectRefactoring currentFlapsSuppressed " ^self xxxCurrent flapsSuppressed! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 11:02'! currentFromMyServerLoad: aProjectName " CurrentProjectRefactoring currentFromMyServerLoad: " ^self xxxCurrent fromMyServerLoad: aProjectName! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 18:58'! currentInterruptName: aString " CurrentProjectRefactoring currentInterruptName: " ^Project interruptName: aString! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:50'! currentIsolationHead " CurrentProjectRefactoring currentIsolationHead " ^self xxxCurrent isolationHead! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:50'! currentProjectName " CurrentProjectRefactoring currentProjectName " ^self xxxCurrent name! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 11:01'! currentPropagateChanges " CurrentProjectRefactoring currentPropagateChanges " ^self xxxCurrent propagateChanges! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 18:51'! currentSpawnNewProcessAndTerminateOld: aBoolean " CurrentProjectRefactoring currentSpawnNewProcessAndTerminateOld: " ^Project spawnNewProcessAndTerminateOld: aBoolean ! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 11:18'! currentToggleFlapsSuppressed " CurrentProjectRefactoring currentToggleFlapsSuppressed " ^self xxxCurrent flapsSuppressed: self xxxCurrent flapsSuppressed not. ! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:51'! exitCurrentProject " CurrentProjectRefactoring exitCurrentProject " ^self xxxCurrent exit ! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'sw 11/6/2000 15:37'! isFlapEnabled: aFlapTab "Answer whether the given flap tab is enabled in the current project" ^ self xxxCurrent isFlapEnabled: aFlapTab! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 18:50'! newProcessIfUI: aDeadOrDyingProcess " CurrentProjectRefactoring newProcessIfUI: used ONLY for Morphic " ^Project spawnNewProcessIfThisIsUI: aDeadOrDyingProcess! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:51'! projectWithNameOrCurrent: aString " CurrentProjectRefactoring projectWithNameOrCurrent: " ^(Project named: aString) ifNil: [self xxxCurrent]! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:51'! updateProjectFillsIn: aFlashPlayerMorph " CurrentProjectRefactoring updateProjectFillsIn: " ^aFlashPlayerMorph updateProjectFillsFrom: self xxxCurrent ! ! !CurrentProjectRefactoring class methodsFor: 'as yet unclassified' stamp: 'RAA 6/3/2000 09:49'! xxxCurrent ^Project current! ! Form subclass: #Cursor instanceVariableNames: '' classVariableNames: 'BlankCursor CornerCursor CrossHairCursor CurrentCursor DownCursor MarkerCursor MenuCursor MoveCursor NormalCursor OriginCursor ReadCursor RightArrowCursor SquareCursor UpCursor WaitCursor WriteCursor XeqCursor ' poolDictionaries: '' category: 'Graphics-Display Objects'! !Cursor commentStamp: '' prior: 0! I am a 16 x 16 dot matrix suitable for use as the Alto hardware cursor.! !Cursor methodsFor: 'updating'! changed: aParameter self == CurrentCursor ifTrue: [self beCursor]. super changed: aParameter! ! !Cursor methodsFor: 'displaying'! beCursor "Primitive. Tell the interpreter to use the receiver as the current cursor image. Fail if the receiver does not match the size expected by the hardware. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !Cursor methodsFor: 'displaying' stamp: 'jm 9/22/1998 23:33'! beCursorWithMask: maskForm "Primitive. Tell the interpreter to use the receiver as the current cursor image with the given mask Form. Both the receiver and the mask should have extent 16@16 and a depth of one. The mask and cursor bits are combined as follow: mask cursor effect 0 0 transparent (underlying pixel shows through) 1 1 opaque black 1 0 opaque white 0 1 invert the underlying pixel" "Essential. See Object documentation whatIsAPrimitive." self primitiveFailed ! ! !Cursor methodsFor: 'displaying'! show "Make the current cursor shape be the receiver." Sensor currentCursor: self! ! !Cursor methodsFor: 'displaying'! showGridded: gridPoint "Make the current cursor shape be the receiver, forcing the location of the cursor to the point nearest gridPoint." Sensor cursorPoint: (Sensor cursorPoint grid: gridPoint). Sensor currentCursor: self! ! !Cursor methodsFor: 'displaying' stamp: 'bf 10/13/1999 13:05'! showWhile: aBlock "While evaluating the argument, aBlock, make the receiver be the cursor shape." | oldcursor | oldcursor _ Sensor currentCursor. self show. ^aBlock ensure: [oldcursor show] ! ! !Cursor methodsFor: 'printing'! printOn: aStream self storeOn: aStream base: 2! ! !Cursor methodsFor: 'testing' stamp: 'bf 2/2/1999 19:34'! hasMask ^false! ! !Cursor methodsFor: 'converting' stamp: 'RAA 8/14/2000 10:14'! asCursorForm | form | form _ StaticForm extent: self extent depth: 8. form fillShape: self fillColor: Color black at: offset negated. ^ form offset: offset! ! !Cursor methodsFor: 'converting' stamp: 'bf 2/2/1999 19:32'! withMask ^CursorWithMask derivedFrom: self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Cursor class instanceVariableNames: ''! !Cursor class methodsFor: 'class initialization'! initCorner CornerCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r1111111111111111 2r1111111111111111) offset: -16@-16). ! ! !Cursor class methodsFor: 'class initialization'! initCrossHair CrossHairCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r1111111111111110 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0) offset: -7@-7). ! ! !Cursor class methodsFor: 'class initialization'! initDown DownCursor _ (Cursor extent: 16@16 fromArray: #( 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r1111110000000000 2r111100000000000 2r11000000000000 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initMarker MarkerCursor _ Cursor extent: 16@16 fromArray: #( 2r0111000000000000 2r1111100000000000 2r1111100000000000 2r0111000000000000 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0. ! ! !Cursor class methodsFor: 'class initialization'! initMenu MenuCursor _ (Cursor extent: 16@16 fromArray: #( 2r1111111111100000 2r1000000000100000 2r1010011000100000 2r1000000000100000 2r1011001010100000 2r1000000000100000 2r1010110010100000 2r1000000000100000 2r1010010100100000 2r1000000000100000 2r1111111111100000 2r1101001101100000 2r1111111111100000 2r1000000000100000 2r1010101100100000 2r1111111111100000) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initMove MoveCursor _ Cursor extent: 16@16 fromArray: #( 2r1111111111111111 2r1111111111111111 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1111111111111111 2r1111111111111111 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1100000110000011 2r1111111111111111 2r1111111111111111) offset: 0@0. ! ! !Cursor class methodsFor: 'class initialization'! initNormal NormalCursor _ (Cursor extent: 16@16 fromArray: #( 2r1000000000000000 2r1100000000000000 2r1110000000000000 2r1111000000000000 2r1111100000000000 2r1111110000000000 2r1111111000000000 2r1111100000000000 2r1111100000000000 2r1001100000000000 2r0000110000000000 2r0000110000000000 2r0000011000000000 2r0000011000000000 2r0000001100000000 2r0000001100000000) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization' stamp: 'di 10/8/1998 17:04'! initNormalWithMask "Cursor initNormalWithMask. Cursor normal show" "Next two lines work simply for any cursor..." self initNormal. NormalCursor _ CursorWithMask derivedFrom: NormalCursor. "But for a good looking cursor, you have to tweak things..." NormalCursor _ (CursorWithMask extent: 16@16 depth: 1 fromArray: #( 0 1073741824 1610612736 1879048192 2013265920 2080374784 2113929216 2130706432 2080374784 2080374784 1275068416 100663296 100663296 50331648 50331648 0) offset: -1@-1) setMaskForm: (Form extent: 16@16 depth: 1 fromArray: #( 3221225472 3758096384 4026531840 4160749568 4227858432 4261412864 4278190080 4286578688 4278190080 4261412864 4261412864 3472883712 251658240 125829120 125829120 50331648) offset: 0@0).! ! !Cursor class methodsFor: 'class initialization'! initOrigin OriginCursor _ (Cursor extent: 16@16 fromArray: #( 2r1111111111111111 2r1111111111111111 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initRead ReadCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000110000000110 2r0001001000001001 2r0001001000001001 2r0010000000010000 2r0100000000100000 2r1111101111100000 2r1000010000100000 2r1000010000100000 2r1011010110100000 2r0111101111000000 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initRightArrow RightArrowCursor _ (Cursor extent: 16@16 fromArray: #( 2r100000000000 2r111000000000 2r1111111110000000 2r111000000000 2r100000000000 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0). "Cursor initRightArrow"! ! !Cursor class methodsFor: 'class initialization'! initSquare SquareCursor _ (Cursor extent: 16@16 fromArray: #( 2r0 2r0 2r0 2r0 2r0 2r0000001111000000 2r0000001111000000 2r0000001111000000 2r0000001111000000 2r0 2r0 2r0 2r0 2r0 2r0 2r0) offset: -8@-8). ! ! !Cursor class methodsFor: 'class initialization'! initUp UpCursor _ (Cursor extent: 16@16 fromArray: #( 2r11000000000000 2r111100000000000 2r1111110000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r11000000000000 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initWait WaitCursor _ (Cursor extent: 16@16 fromArray: #( 2r1111111111111111 2r1000000000000001 2r0100000000000010 2r0010000000000100 2r0001110000111000 2r0000111101110000 2r0000011011100000 2r0000001111000000 2r0000001111000000 2r0000010110100000 2r0000100010010000 2r0001000110001000 2r0010001101000100 2r0100111111110010 2r1011111111111101 2r1111111111111111) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initWrite WriteCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000110 2r0000000000001111 2r0000000000010110 2r0000000000100100 2r0000000001001000 2r0000000010010000 2r0000000100100000 2r0000001001000011 2r0000010010000010 2r0000100100000110 2r0001001000001000 2r0010010000001000 2r0111100001001000 2r0101000010111000 2r0110000110000000 2r1111111100000000) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization'! initXeq XeqCursor _ (Cursor extent: 16@16 fromArray: #( 2r1000000000010000 2r1100000000010000 2r1110000000111000 2r1111000111111111 2r1111100011000110 2r1111110001000100 2r1111111001111100 2r1111000001101100 2r1101100011000110 2r1001100010000010 2r0000110000000000 2r0000110000000000 2r0000011000000000 2r0000011000000000 2r0000001100000000 2r0000001100000000) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization' stamp: 'di 3/6/1999 21:27'! initialize "Create all the standard cursors..." self initOrigin. self initRightArrow. self initMenu. self initCorner. self initRead. self initWrite. self initWait. BlankCursor _ Cursor new. self initXeq. self initSquare. self initNormalWithMask. self initCrossHair. self initMarker. self initUp. self initDown. self initMove. self makeCursorsWithMask. "Cursor initialize" ! ! !Cursor class methodsFor: 'class initialization' stamp: 'bf 2/2/1999 19:33'! makeCursorsWithMask "Cursor initialize;makeCursorsWithMask" self classPool associationsDo: [:var | var value hasMask ifFalse: [var value: var value withMask]] ! ! !Cursor class methodsFor: 'class initialization'! startUp self currentCursor: self currentCursor! ! !Cursor class methodsFor: 'instance creation'! extent: extentPoint fromArray: anArray offset: offsetPoint "Answer a new instance of me with width and height specified by extentPoint, offset by offsetPoint, and bits from anArray. NOTE: This has been kluged to take an array of 16-bit constants, and shift them over so they are left-justified in a 32-bit bitmap" extentPoint = (16 @ 16) ifTrue: [^ super extent: extentPoint fromArray: (anArray collect: [:bits | bits bitShift: 16]) offset: offsetPoint] ifFalse: [self error: 'cursors must be 16@16']! ! !Cursor class methodsFor: 'instance creation' stamp: 'di 10/6/1998 13:53'! new ^ self extent: 16 @ 16 fromArray: (Array new: 16 withAll: 0) offset: 0 @ 0 "Cursor new bitEdit show"! ! !Cursor class methodsFor: 'current cursor'! currentCursor "Answer the instance of Cursor that is the one currently displayed." ^CurrentCursor! ! !Cursor class methodsFor: 'current cursor' stamp: 'di 10/6/1998 13:57'! currentCursor: aCursor "Make the instance of cursor, aCursor, be the current cursor. Display it. Create an error if the argument is not a Cursor." (aCursor isKindOf: self) ifTrue: [CurrentCursor _ aCursor. aCursor beCursor] ifFalse: [self error: 'The new cursor must be an instance of class Cursor']! ! !Cursor class methodsFor: 'constants'! blank "Answer the instance of me that is all white." ^BlankCursor! ! !Cursor class methodsFor: 'constants'! bottomLeft "Cursor bottomLeft showWhile: [Sensor waitButton]" ^ (Cursor extent: 16@16 fromArray: #( 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1111111111111111 2r1111111111111111) offset: 0@-16). ! ! !Cursor class methodsFor: 'constants'! bottomRight "Cursor bottomRight showWhile: [Sensor waitButton]" ^ (Cursor extent: 16@16 fromArray: #( 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r1111111111111111 2r1111111111111111) offset: -16@-16). ! ! !Cursor class methodsFor: 'constants'! corner "Answer the instance of me that is the shape of the bottom right corner of a rectangle." ^CornerCursor! ! !Cursor class methodsFor: 'constants'! crossHair "Answer the instance of me that is the shape of a cross." ^CrossHairCursor! ! !Cursor class methodsFor: 'constants'! down "Answer the instance of me that is the shape of an arrow facing downward." ^DownCursor! ! !Cursor class methodsFor: 'constants'! execute "Answer the instance of me that is the shape of an arrow slanted left with a star next to it." ^XeqCursor! ! !Cursor class methodsFor: 'constants'! marker "Answer the instance of me that is the shape of a small ball." ^MarkerCursor! ! !Cursor class methodsFor: 'constants'! menu "Answer the instance of me that is the shape of a menu." ^MenuCursor! ! !Cursor class methodsFor: 'constants'! move "Answer the instance of me that is the shape of a cross inside a square." ^MoveCursor! ! !Cursor class methodsFor: 'constants'! normal "Answer the instance of me that is the shape of an arrow slanted left." ^NormalCursor! ! !Cursor class methodsFor: 'constants'! origin "Answer the instance of me that is the shape of the top left corner of a rectangle." ^OriginCursor! ! !Cursor class methodsFor: 'constants'! read "Answer the instance of me that is the shape of eyeglasses." ^ReadCursor! ! !Cursor class methodsFor: 'constants'! rightArrow "Answer the instance of me that is the shape of an arrow pointing to the right." ^RightArrowCursor! ! !Cursor class methodsFor: 'constants'! square "Answer the instance of me that is the shape of a square." ^SquareCursor! ! !Cursor class methodsFor: 'constants'! topLeft "Cursor topLeft showWhile: [Sensor waitButton]" ^ (Cursor extent: 16@16 fromArray: #( 2r1111111111111111 2r1111111111111111 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000 2r1100000000000000) offset: 0@0). ! ! !Cursor class methodsFor: 'constants'! topRight "Cursor topRight showWhile: [Sensor waitButton]" ^ (Cursor extent: 16@16 fromArray: #( 2r1111111111111111 2r1111111111111111 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011 2r0000000000000011) offset: -16@0). ! ! !Cursor class methodsFor: 'constants'! up "Answer the instance of me that is the shape of an arrow facing upward." ^UpCursor! ! !Cursor class methodsFor: 'constants' stamp: 'sw 8/15/97 13:28'! wait "Answer the instance of me that is the shape of an Hourglass (was in the shape of three small balls)." ^WaitCursor! ! !Cursor class methodsFor: 'constants' stamp: 'ar 11/18/1998 15:33'! webLink "Return a cursor that can be used for emphasizing web links" "Cursor webLink showWhile: [Sensor waitButton]" ^ (CursorWithMask extent: 16@16 fromArray: #(3072 4608 4608 4608 4608 5046 4681 29257 37449 37449 32769 32769 49155 16386 24582 16380 ) offset: -5@0) setMaskForm: (Form extent: 16@16 fromArray: (#(3072 7680 7680 7680 7680 8118 8191 32767 65535 65535 65535 65535 65535 32766 32766 16380 ) collect: [:bits | bits bitShift: 16]) offset: 0@0).! ! !Cursor class methodsFor: 'constants'! write "Answer the instance of me that is the shape of a pen writing." ^WriteCursor! ! Cursor subclass: #CursorWithMask instanceVariableNames: 'maskForm ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'RAA 8/14/2000 10:14'! asCursorForm | form | form _ StaticForm extent: self extent depth: 8. form fillShape: maskForm fillColor: Color white. form fillShape: self fillColor: Color black at: offset negated. ^ form offset: offset! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'di 10/6/1998 15:16'! beCursor maskForm unhibernate. ^ self beCursorWithMask: maskForm! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'bf 2/2/1999 19:34'! hasMask ^true! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'di 10/8/1998 16:46'! maskForm ^ maskForm! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'di 10/8/1998 16:46'! setMaskForm: aForm maskForm _ aForm! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'bf 2/2/1999 19:30'! storeOn: aStream base: anInteger aStream nextPut: $(. super storeOn: aStream base: anInteger. aStream nextPutAll: ' setMaskForm: '. maskForm storeOn: aStream base: anInteger. aStream nextPut: $)! ! !CursorWithMask methodsFor: 'as yet unclassified' stamp: 'bf 2/2/1999 19:31'! withMask ^self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CursorWithMask class instanceVariableNames: ''! !CursorWithMask class methodsFor: 'as yet unclassified' stamp: 'di 2/18/1999 08:56'! derivedFrom: aForm "Cursor initNormalWithMask. Cursor normal show" "aForm is presumably a cursor" | cursor mask ext | ext _ aForm extent. cursor _ self extent: ext. cursor copy: (1@1 extent: ext) from: 0@0 in: aForm rule: Form over. mask _ Form extent: ext. (1@1) eightNeighbors do: [:p | mask copy: (p extent: ext) from: 0@0 in: aForm rule: Form under]. cursor setMaskForm: mask. cursor offset: ((aForm offset - (1@1)) max: ext negated). ^ cursor! ! Path subclass: #CurveFitter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Paths'! !CurveFitter commentStamp: '' prior: 0! I represent a conic section determined by three points p1, p2 and p3. I interpolate p1 and p3 and am tangent to line p1, p2 at p1 and line p3, p2 at p3.! !CurveFitter methodsFor: 'displaying'! displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm | pa pb k s p1 p2 p3 line | line _ Line new. line form: self form. collectionOfPoints size < 3 ifTrue: [self error: 'Curve must have three points']. p1 _ self firstPoint. p2 _ self secondPoint. p3 _ self thirdPoint. s _ Path new. s add: p1. pa _ p2 - p1. pb _ p3 - p2. k _ 5 max: pa x abs + pa y abs + pb x abs + pb y abs // 20. "k is a guess as to how many line segments to use to approximate the curve." 1 to: k do: [:i | s add: pa * i // k + p1 * (k - i) + (pb * (i - 1) // k + p2 * (i - 1)) // (k - 1)]. s add: p3. 1 to: s size - 1 do: [:i | line beginPoint: (s at: i). line endPoint: (s at: i + 1). line displayOn: aDisplayMedium at: aPoint clippingBox: clipRect rule: anInteger fillColor: aForm]! ! !CurveFitter methodsFor: 'displaying' stamp: '6/9/97 10:16 di'! displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm | transformedPath newCurveFitter | transformedPath _ aTransformation applyTo: self. newCurveFitter _ CurveFitter new. newCurveFitter firstPoint: transformedPath firstPoint. newCurveFitter secondPoint: transformedPath secondPoint. newCurveFitter thirdPoint: transformedPath thirdPoint. newCurveFitter form: self form. newCurveFitter displayOn: aDisplayMedium at: 0 @ 0 clippingBox: clipRect rule: anInteger fillColor: aForm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CurveFitter class instanceVariableNames: ''! !CurveFitter class methodsFor: 'instance creation'! new | newSelf | newSelf _ super new: 3. newSelf add: 0@0. newSelf add: 0@0. newSelf add: 0@0. ^newSelf! ! !CurveFitter class methodsFor: 'examples' stamp: '6/9/97 10:16 di'! example "Designate three locations on the screen by clicking any button. The curve determined by the points will be displayed with a long black form." | aCurveFitter aForm | aForm _ Form extent: 1@30. "make a long thin Form for display " aForm fillBlack. "turn it black" aCurveFitter _ CurveFitter new. aCurveFitter form: aForm. "set the form for display" "collect three Points and show them on the dispaly" aCurveFitter firstPoint: Sensor waitButton. Sensor waitNoButton. aForm displayOn: Display at: aCurveFitter firstPoint. aCurveFitter secondPoint: Sensor waitButton. Sensor waitNoButton. aForm displayOn: Display at: aCurveFitter secondPoint. aCurveFitter thirdPoint: Sensor waitButton. Sensor waitNoButton. aForm displayOn: Display at: aCurveFitter thirdPoint. aCurveFitter displayOn: Display "display the CurveFitter" "CurveFitter example"! ! PolygonMorph subclass: #CurveMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !CurveMorph commentStamp: '' prior: 0! This is really only a shell for creating Shapes with smooth outlines.! !CurveMorph methodsFor: 'initialization' stamp: 'di 9/10/2000 14:28'! initialize super initialize. self beSmoothCurve. ! ! SelectionMenu subclass: #CustomMenu instanceVariableNames: 'labels dividers lastDivider title ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Menus'! !CustomMenu commentStamp: '' prior: 0! I am used to build menus on-the-fly. I maintain lists of menu items, actions (usually symbols to be sent as messages), and menu section dividing lines to which my clients may append new entries and lines by sending me the messages: add: aString action: anAction addLine After the menu is constructed, it may be invoked with one of the following messages: startUp: initialSelection startUp I am a subclass of ActionMenu, so I inherit a number of instance variables. The ones I am particularly concerned with are: items _ an OrderedCollection of strings to appear in the menu selectors _ an OrderedCollection of Symbols to be used as message selectors lineArray _ an OrderedCollection of line positions lastLine _ used to keep track of the last line to avoid making duplicate entries in lineArray! !CustomMenu methodsFor: 'initialize-release'! initialize labels _ OrderedCollection new. selections _ OrderedCollection new. dividers _ OrderedCollection new. lastDivider _ 0.! ! !CustomMenu methodsFor: 'initialize-release' stamp: 'sw 8/18/1998 12:01'! title: aTitle title _ aTitle! ! !CustomMenu methodsFor: 'construction' stamp: 'dhhi 9/14/2000 22:39'! add: aString action: actionItem "Add the given string as the next menu item. If it is selected, the given action (usually but not necessarily a symbol) will be returned to the client." | s | aString ifNil: [^ self addLine]. s _ String new: aString size + 2. s at: 1 put: Character space. s replaceFrom: 2 to: s size - 1 with: aString. s at: s size put: Character space. labels addLast: s. selections addLast: actionItem.! ! !CustomMenu methodsFor: 'construction'! addLine "Append a line to the menu after the last entry. Suppress duplicate lines." (lastDivider ~= selections size) ifTrue: [ lastDivider _ selections size. dividers addLast: lastDivider].! ! !CustomMenu methodsFor: 'construction' stamp: 'jm 3/29/98 07:09'! addList: listOfPairs "Add a menu item to the receiver for each pair in the given list of the form ( ). Add a line for each dash (-) in the list." "CustomMenu new addList: #( ('apples' buyApples) ('oranges' buyOranges) - ('milk' buyMilk)); startUp" listOfPairs do: [:pair | #- = pair ifTrue: [self addLine] ifFalse: [self add: pair first action: pair last]]. ! ! !CustomMenu methodsFor: 'construction' stamp: 'sw 7/20/1999 18:47'! balloonTextForLastItem: aString "Vacuous backstop provided for compatibility with MorphicMenu"! ! !CustomMenu methodsFor: 'construction' stamp: 'jm 8/20/1998 08:34'! labels: aString font: aFont lines: anArrayOrNil "This method allows the receiver to accept old-style SelectionMenu creation messages. It should be used only for backward compatibility during the MVC-to-Morphic transition. New code should be written using the other menu construction protocol such as addList:." | labelList linesArray | labelList _ (aString findTokens: String cr) asArray. anArrayOrNil ifNil: [linesArray _ #()] ifNotNil: [linesArray _ anArrayOrNil]. 1 to: labelList size do: [:i | self add: (labelList at: i) action: (labelList at: i). (linesArray includes: i) ifTrue: [self addLine]]. font ifNotNil: [font _ aFont]. ! ! !CustomMenu methodsFor: 'construction' stamp: 'di 8/20/1998 09:24'! labels: labelList lines: linesArray selections: selectionsArray "This method allows the receiver to accept old-style SelectionMenu creation messages. It should be used only for backward compatibility during the MVC-to-Morphic transition. New code should be written using the other menu construction protocol such as addList:." "Labels can be either a sting with embedded crs, or a collection of strings." | labelArray | (labelList isMemberOf: String) ifTrue: [labelArray _ labelList findTokens: String cr] ifFalse: [labelArray _ labelList]. 1 to: labelArray size do: [:i | self add: (labelArray at: i) action: (selectionsArray at: i). (linesArray includes: i) ifTrue: [self addLine]]. ! ! !CustomMenu methodsFor: 'invocation' stamp: 'jm 11/17/97 16:54'! invokeOn: targetObject defaultSelection: defaultSelection "Invoke the menu with the given default selection (i.e. one of my 'action' symbols). Answer the 'action' selector associated with the menu item chosen by the user or nil if none is chosen." | sel | sel _ self startUp: defaultSelection. sel = nil ifFalse: [ sel numArgs = 0 ifTrue: [^ targetObject perform: sel] ifFalse: [^ targetObject perform: sel with: nil]]. ^ nil ! ! !CustomMenu methodsFor: 'invocation'! startUp "Build and invoke this menu with no initial selection. Answer the selection associated with the menu item chosen by the user or nil if none is chosen." ^ self startUp: nil! ! !CustomMenu methodsFor: 'invocation' stamp: 'sw 8/18/1998 12:01'! startUp: initialSelection "Build and invoke this menu with the given initial selection. Answer the selection associated with the menu item chosen by the user or nil if none is chosen." ^ self startUp: initialSelection withCaption: title! ! !CustomMenu methodsFor: 'invocation'! startUp: initialSelection withCaption: caption "Build and invoke this menu with the given initial selection and caption. Answer the selection associated with the menu item chosen by the user or nil if none is chosen." self build. (initialSelection notNil) ifTrue: [self preSelect: initialSelection]. ^ super startUpWithCaption: caption! ! !CustomMenu methodsFor: 'invocation' stamp: 'sw 7/31/97 19:31'! startUpWithCaption: caption "Build and invoke this menu with no initial selection. Answer the selection associated with the menu item chosen by the user or nil if none is chosen; use the provided caption" ^ self startUp: nil withCaption: caption! ! !CustomMenu methodsFor: 'private' stamp: 'sw 12/10/1999 11:21'! build "Turn myself into an invokable ActionMenu." | stream | stream _ WriteStream on: (String new). labels do: [:label | stream nextPutAll: label; cr]. (labels isEmpty) ifFalse: [stream skip: -1]. "remove final cr" super labels: stream contents font: MenuStyle defaultFont lines: dividers! ! !CustomMenu methodsFor: 'private' stamp: 'di 4/14/1999 21:28'! preSelect: action "Pre-select and highlight the menu item associated with the given action." | i | i _ selections indexOf: action ifAbsent: [^ self]. marker ifNil: [self computeForm]. marker _ marker align: marker topLeft with: (marker left)@(frame inside top + (marker height * (i - 1))). selection _ i.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CustomMenu class instanceVariableNames: ''! !CustomMenu class methodsFor: 'instance creation'! new ^ super new initialize! ! !CustomMenu class methodsFor: 'example' stamp: 'sw 11/8/1999 17:27'! example "CustomMenu example" | menu | menu _ CustomMenu new. menu add: 'apples' action: #apples. menu add: 'oranges' action: #oranges. menu addLine. menu addLine. "extra lines ignored" menu add: 'peaches' action: #peaches. menu addLine. menu add: 'pears' action: #pears. menu addLine. ^ menu startUp: #apples "NB: The following is equivalent to the above, but uses the compact #fromArray: consruct: (CustomMenu fromArray: #( ('apples' apples) ('oranges' oranges) - - ('peaches' peaches) - ('pears' pears) -)) startUp: #apples"! ! Object subclass: #DECTalkReader instanceVariableNames: 'stream phonemes durations events currentDuration currentPitch f0Contour ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Support'! !DECTalkReader methodsFor: 'initialization' stamp: 'len 12/24/1999 03:20'! initialize phonemes _ PhonemeSet dectalkToArpabet. events _ CompositeEvent new. currentDuration _ 80. currentPitch _ 100. f0Contour _ CosineInterpolator new. durations _ Dictionary new. #( ('ae' 230.0 80.0) ('aa' 240.0 100.0) ('ax' 120.0 60.0) ('er' 180.0 80.0) ('ay' 250.0 150.0) ('aw' 240.0 100.0) ('b' 85.0 60.0) ('ch' 70.0 50.0) ('d' 75.0 50.0) ('dh' 50.0 30.0) ('eh' 150.0 70.0) ('ea' 270.0 130.0) ('ey' 180.0 100.0) ('f' 100.0 80.0) ('g' 80.0 60.0) ('hh' 80.0 20.0) ('ih' 135.0 40.0) ('ia' 230.0 100.0) ('iy' 155.0 55.0) ('jh' 70.0 50.0) ('k' 80.0 60.0) ('l' 80.0 40.0) ('m' 70.0 60.0) ('n' 60.0 50.0) ('ng' 95.0 60.0) " ('oh' 240.0 130.0)" ('oy' 280.0 150.0) ('ao' 240.0 130.0) ('ow' 220.0 80.0) ('p' 90.0 50.0) ('r' 80.0 30.0) ('s' 105.0 60.0) ('sh' 105.0 80.0) ('t' 75.0 50.0) ('th' 90.0 60.0) ('uh' 210.0 70.0) ('ua' 230.0 110.0) ('ah' 160.0 60.0) ('uw' 230.0 150.0) ('v' 60.0 40.0) ('w' 80.0 60.0) ('y' 80.0 40.0) ('z' 75.0 40.0) ('zh' 70.0 40.0) ('sil' 100.0 100.0)) do: [ :each | durations at: (PhonemeSet arpabet at: each first) put: each second / 1000.0]! ! !DECTalkReader methodsFor: 'accessing' stamp: 'len 12/20/1999 03:28'! defaultDurationFor: aPhoneme ^ durations at: aPhoneme ifAbsent: [0.080]! ! !DECTalkReader methodsFor: 'accessing' stamp: 'len 12/20/1999 03:04'! events ^ events! ! !DECTalkReader methodsFor: 'accessing' stamp: 'len 12/20/1999 03:04'! phonemes ^ phonemes! ! !DECTalkReader methodsFor: 'accessing' stamp: 'len 12/20/1999 03:04'! stream: aStream stream _ aStream! ! !DECTalkReader methodsFor: 'reading' stamp: 'len 12/20/1999 03:37'! addPitches | offset | offset _ 0.0. events do: [ :each | each pitchPoints: (self pitchesBetween: offset and: offset + each duration). offset _ offset + each duration].! ! !DECTalkReader methodsFor: 'reading' stamp: 'len 12/20/1999 04:31'! nextPhoneme | try try2 phon | try _ stream next asString. (',.;-' includes: try first) ifTrue: [^ phonemes at: 'sil']. try2 _ try, stream peek asString. (phon _ phonemes at: try2 ifAbsent: []) notNil ifTrue: [stream next. ^ phon]. ^ phonemes at: try! ! !DECTalkReader methodsFor: 'reading' stamp: 'len 12/20/1999 03:37'! pitchesBetween: t1 and: t2 | step | step _ (t2 - t1 / 0.035) asInteger + 1. "step small enough" ^ (t1 to: t2 by: t2 - t1 / step) collect: [ :each | each - t1 @ (f0Contour at: each)]! ! !DECTalkReader methodsFor: 'reading' stamp: 'len 12/24/1999 05:35'! read | phoneme time | time _ 0. [stream skipSeparators; atEnd] whileFalse: [phoneme _ self nextPhoneme. currentDuration _ self defaultDurationFor: phoneme. stream peek = $< ifTrue: [self readPitchAndDuration]. f0Contour at: time + (currentDuration / 2.0 min: 0.1) put: currentPitch. time _ time + currentDuration. f0Contour at: time put: currentPitch. events add: (PhoneticEvent new phoneme: phoneme; duration: currentDuration; loudness: 1.0)]. self addPitches! ! !DECTalkReader methodsFor: 'reading' stamp: 'len 12/24/1999 03:17'! readPitchAndDuration | tokens code | stream next. tokens _ (stream upTo: $>) findTokens: ','. currentDuration _ tokens first asNumber / 1000.0. tokens size > 1 ifFalse: [^ self]. code _ tokens last asNumber. currentPitch _ code > "37" 64 ifTrue: [code] ifFalse: [AbstractSound pitchForMIDIKey: 35 + code]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DECTalkReader class instanceVariableNames: ''! !DECTalkReader class methodsFor: 'instance creation' stamp: 'len 12/20/1999 03:52'! eventsFromStream: aStream ^ self new stream: aStream; read; events! ! !DECTalkReader class methodsFor: 'instance creation' stamp: 'len 12/20/1999 03:52'! eventsFromString: aString ^ self eventsFromStream: (ReadStream on: aString)! ! !DECTalkReader class methodsFor: 'instance creation' stamp: 'len 12/20/1999 03:52'! new ^ super new initialize! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:14'! daisy " DECTalkReader daisy playOn: KlattVoice new delayed: 10000 " ^ self eventsFromString: '_<50,22>dey<400,22>ziy<400,19>dey<400,15>ziy<400,10> gih<200,12>vmiy<200,14>yurr<200,15>ae<400,12>nsax<200,15> rduw<400,10>. ay<400,17>mhxae<400,22>fkrey<400,19>ziy<400,15>ao<200,12> lfao<200,14>rdhax<200,15>lah<400,17>vao<200,19>vyu<400,17>. ih<200,19>twow<200,20>ntbiy<200,19>ax<200,17>stay<400,22> lih<200,19>shmae<200,17>rih<400,15>jh<50,15>. ay<200,17>kae<400,19>ntax<200,15>fow<400,12>rdax<200,15> kae<200,12>rih<400,10>jh<50,10>. bah<200,10>tyu<400,15>lluh<200,19>kswiy<400,17>tah<200,10> pao<400,15>ndhax<200,19>siy<400,17>t<50,17>. ao<200,17>vax<200,19>bay<200,22>six<200,19>kel<200,15> bih<400,17>ltfao<200,10>rtuw<800,15>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:48'! flower " DECTalkReader flower playOn: KlattVoice new delayed: 15000 " ^ self eventsFromString: '_<25,22>ow<200,22>flaw<400,22>rax<200,20>vskao<400,18> ao<200,18>tlae<800,13>nd<200,13> weh<200,13>nwih<400,18>lwiy<200,22>siy<800,20>yu<200,20> rlay<400,18>kax<200,20>geh<1600,22>n<25,22> dhax<200,22>tfao<300,23>ao<100,22>tae<200,23>nday<400,25> d<200,25>fao<800,18>r<25,18> yu<200,13>rwiy<400,20>bih<200,20>t hxih<200,20>ih<200,18> lae<200,20>ndgleh<400,22>nae<200,23>ndstuh<400,22>dax<200,20> geh<600,18>nst hxih<800,13>m<200,13> praw<200,22>deh<300,23>eh<100,22>dwax<200,23>rdzaa<400,25> aa<200,25>rmih<800,18>ih<200,18> ae<200,22>ndseh<200,23>eh<200,22>nt hxih<200,20>mhxow<300,22> ow<100,20>ow<200,18>mwax<800,18>ax<200,18>rdtey<200,18> thih<400,16>nxkax<200,20>geh<800,18>eh<400,18>n<200,18> _<600,22>dhax<200,22>hxih<400,22>lzax<200,20>rbey<400,18> rr<200,18>naw<800,13> ae<200,13>ndao<400,18>tah<200,22>mliy<800,20>vzlay<200,20> thih<400,18>kax<200,20>ndstih<800,22>ih<800,22>l<25,22> ow<200,22>rlae<300,23>nddhax<100,22>tih<200,23>zlao<400,25> ao<200,25>stnaw<800,18> wih<200,13>chdhow<400,20>zsow<200,20>diy<200,20>ax<200,18> lih<200,20>hxeh<400,22>ldhax<200,23>tstuh<400,22>dax<200,20> geh<400,18>eh<200,18>nst hxih<800,13>m<200,13> praw<200,22>deh<300,23>eh<100,22>dwax<200,23>rdzaa<400,25> aa<200,25>rmih<800,18>ih<200,18> ae<200,22>ndseh<200,23>eh<200,22>nt hxih<200,20>mhxow<300,22> ow<100,20>ow<200,18>mwax<800,18>ax<200,18>rdtey<200,18> thih<400,16>nxkax<200,20>geh<1200,18>n<200,18> _<600,22>dhow<200,22>zdey<400,22>zax<200,20>rpae<400,18> ae<200,18>stnaw<800,13> ae<200,13>ndih<400,18>ndhax<200,22>pae<800,20>stdhey<200,20> mah<400,18>strix<200,20>mey<800,22>ey<800,22>n<25,22> bah<200,22>twiy<300,23>kae<100,22>nstih<200,23>lray<600,25> znaw<800,18> ae<200,13>ndbiy<400,20>dhax<200,20>ney<200,20>shax<200,18> nax<200,20>geh<400,22>ndhax<200,23>tstuh<400,22>dax<200,20> geh<600,18>nst hxih<800,13>m<200,13> praw<200,22>deh<300,23>eh<100,22>dwax<200,23>rdzaa<400,25> aa<200,25>rmih<800,18>ih<200,18> ae<200,22>ndseh<200,23>eh<200,22>nt hxih<200,20>mhxow<300,22> ow<100,20>ow<200,18>mwax<800,18>ax<200,18>rdtey<200,18> thih<400,16>nxkax<200,20>geh<1200,18>n<200,18>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:43'! great " (DECTalkReader great pitchBy: 0.5) playOn: (KlattVoice new tract: 19; flutter: 0.5) delayed: 10000 " ^ self eventsFromString: '_<50,20>ax<200,20>_<1>ax<500,22>_<10>ow<300,20>yxeh<1000,17> say<200,15>mdhax<80,13>grey<1000,15>t_priy<200,17>iy<200,18> teh<200,17>eh<200,15>ndrr<1600,13> _<200,13>priy<200,13>teh<1000,22>ndih<200,22>nxdhae<100,22> day<1000,18>mduh<200,20>ix<200,22>nweh<1600,20>l<600,20> _<60,25>may<300,25>niy<1200,22>dix<200,22>zsah<1000,24>chay<200,22> priy<200,24>teh<1000,25>ndtuh<200,22>_<10>uw<200,25>mah<1000,20>ch<100,20> _<20,20>ay<300,20>mlow<300,20>neliy<800,17>bah<200,13> tnow<1000,15>wah<200,13>nkae<200,15>nteh<1800,13>l<400,13> _<50,20>ax<200,20>_<1>ax<500,22>_<1>ow<300,20>yxeh<1000,17> say<200,15>mdhax<80,13>grey<1000,15>t_priy<200,17>iy<200,18> teh<200,17>eh<200,15>ndrr<1800,13> _<10,13>ah<200,13>drih<1000,22>ftih<50,22>nax<200,22> wrr<1000,18>ldax<200,20>vmay<200,22>ax<200,22>_<1>ow<1400,20>n<600,20> _<60,25>ay<300,25>pley<1100,22>dhax<200,22>gey<1000,24> m<100,24>bah<200,22>tuh<200,24>may<1000,25>riy<200,22>ax<200,25> lshey<600,20>m<400,20> _<20,20>yu<200,20>vleh<200,20>ftmiy<800,17>tuw<200,13> driy<800,15>mao<200,13>lah<200,15>low<1600,13>n<400,13>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:25'! hawaii " DECTalkReader hawaii playOn: (KlattVoice new tract: 14.4) delayed: 10000 " ^ self eventsFromString: '_<300> naa<600,23> ay<300,23>t ae<300,22>nd yuw<1200,23> ,<600> ae<600,24>nd bluw<900,25> hxah<300,24> waa<940,23> aa<240,22> aa<240,21> iy<1200,20> ,<600> dhah<600,32> naa<600,33> ay<300,33>t ih<300,32>z hxeh<900,30> veh<300,22>n liy<1200,25> ,<600> ae<300,30> ae<300,31>nd yu<900,32> aa<300,30>rx hxeh<440,28> veh<440,28>n tuw<440,25> miy<2400,23> ,<600> lah<900,23>v liy<300,22> yuw<1200,23> ,<600> ae<600,24>nd bluw<900,25> hxah<300,24> waa<940,23>-aa<240,22>-aa<240,21>-iy<1200,20> ,<600> wih<600,32>dh ao<900,33>lx dhih<300,32>s lah<880,30> v<40,30> liy<300,22> neh<1200,25>s ,<600> dheh<300,30> eh<300,31>rx shuh<900,32>d biy<300,27> lah<4140,28> v<60,28> ,<600> kah<900,25>m wih<300,32>dh miy<1800,30> ,<600> waa<400,28> ay<200,28>lx dhah<600,25> muw<300,28>n ih<300,25>z aa<300,28>n dhah<300,25> siy<2400,23> ,<600> dhah<600,24> naa<600,25> ay<300,25>t ih<300,32>z yxah<1200,30>nx ,<600> ae<600,28>nd sow<600,25> aa<600,28>rx-wiy<4200,30> ,<600> driy<900,23>mz kah<300,22>m truw<1000,23> uw<200,23> ,<600> ih<600,24>n bluw<900,25> hxah<300,24> waa<940,23> aa<240,22> aa<240,21> iy<1200,20> ,<600> ae<600,32>nd maa<600,33> iy<300>n kuh<300,32>d ao<900,30>lx kah<300,22>m truw<1200,25> ,<600> dhih<300,30> ih<300,31>s mae<900,32> jhih<330,27>k naa<600,28> ay<350,28>t ah<350,27>v naa<600,28> ay<350,28>ts ,<40> wih<380,27>dh yuw<1000,28>-uw<600,455>-uw<1800,35>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 05:14'! silentNightDuetExample " DECTalkReader silentNightDuetExample " | song1 song2 voice1 voice2 time | song1 _ DECTalkReader silentNightVoice1. song2 _ DECTalkReader silentNightVoice2. voice1 _ KlattVoice new tract: 14.4. voice2 _ KlattVoice new tract: 18.5; turbulence: 59. time _ Time millisecondClockValue + 30000. "give it 30 secounds for precomputing" song1 playOn: voice1 at: time. song2 playOn: voice2 at: time! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 05:14'! silentNightDuetExample2 " DECTalkReader silentNightDuetExample2 " | song1 song2 voice1 voice2 time | song1 _ DECTalkReader silentNightVoice1 pitchBy: 0.5. song2 _ DECTalkReader silentNightVoice2 pitchBy: 0.5. voice1 _ KlattVoice new tract: 14.4. voice2 _ KlattVoice new tract: 18.5; turbulence: 59. time _ Time millisecondClockValue + 30000. "give it 30 secounds for precomputing" song1 playOn: voice1 at: time. song2 playOn: voice2 at: time! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 05:14'! silentNightDuetExample3 " DECTalkReader silentNightDuetExample3 " | song1 song2 voice1 voice2 time | song1 _ DECTalkReader silentNightVoice1 pitchBy: 0.25. song2 _ DECTalkReader silentNightVoice2 pitchBy: 0.25. voice1 _ KlattVoice new tract: 18.5; turbulence: 59. voice2 _ KlattVoice new tract: 20; flutter: 0.5. time _ Time millisecondClockValue + 30000. "give it 30 secounds for precomputing" song1 playOn: voice1 at: time. song2 playOn: voice2 at: time! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 05:18'! silentNightDuetExample4 " DECTalkReader silentNightDuetExample4 " | song1 song2 voice1 voice2 gestural1 gestural2 time | song1 _ DECTalkReader silentNightVoice1 pitchBy: 0.5. song2 _ DECTalkReader silentNightVoice2 pitchBy: 0.5. gestural1 _ GesturalVoice new. gestural1 newHead position: 1 @ 50. voice1 _ (KlattVoice new tract: 14.4) + gestural1. gestural2 _ GesturalVoice new. gestural2 newHead position: 150 @ 50. voice2 _ (KlattVoice new tract: 18.5; turbulence: 59) + gestural2. time _ Time millisecondClockValue + 30000. "give it 30 secounds for precomputing" song1 playOn: voice1 at: time. song2 playOn: voice2 at: time! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 04:58'! silentNightVoice1 " (DECTalkReader silentNightVoice1 pitchBy: 0.5) playOn: KlattVoice new delayed: 1000 " ^ self eventsFromString: 'sae<600,32>ay<200,34>leh<400,32>nt nae<600,29>ay<400>t. hxow<600,32>ow<200,34>liy<400,32> nae<600,29>ay<400>t. ao<600,39>l ih<200>z kaa<800,36>lm. ao<600,37>l ih<200>z bray<800,32>t. raw<600,34>nd yah<400>ng ver<600,37>er<200,36>jhah<400,34>n mah<600,32>dher<200,32> ae<400>nd chah<600,29>ay<200>ld. hxow<800,34>liy<400> ih<600,37>nfah<200,36>nt sow<400,34> teh<600,32>nder<400,34> ae<400,32>nd may<600,29>ld. sliy<600,39>p ah<400>n hxeh<400,42>vah<400,39>nliy<400,36> piy<1000,37>iy<800,41>s. sliy<400,37>iy<400,32>p ah<400,29>n hxeh<400,32>vah<400,30>nliy<600,27> piy<1800,25>s.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/24/1999 05:06'! silentNightVoice2 " (DECTalkReader silentNightVoice2 pitchBy: 0.5) playOn: KlattVoice new delayed: 1000 " ^ self eventsFromString: 'sae<600,29>ay<200,30>leh<400,29>nt nae<600,25>ay<400>t. hxow<600,29>ow<200,30>liy<400,29> nae<600,25>ay<400>t. ao<600,30>l ih<200>z kaa<800,27>lm. ao<600,29>l ih<200>z bray<800,29>t. raw<600,30>nd yah<400>ng ver<600,34>er<200,32>jhah<400,30>n mah<600,29>dher<200,30> ae<400,29>nd chah<600,25>ay<200>ld. hxow<800,30>liy<400> ih<600,34>nfah<200,32>nt sow<400,30> teh<600,29>nder<400,30> ae<400,29>nd may<600,25>ld. sliy<600,30>p ah<400>n hxeh<400,27>vah<400,30>nliy<400,27> piy<1000,29>iy<800,32>s. sliy<400,29>iy<400,29>p ah<400,25>n hxeh<400,24>vah<400,24>nliy<600,24> piy<1800,25>s.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:52'! startrek " DECTalkReader startrek playOn: KlattVoice new delayed: 15000 " ^ self eventsFromString: '_<50,17>dhey<400,17>rsklih<100,17>nxao<100,17>nzao<100,17> ndhax<100,17>staa<100,20>rbao<100,20>rdbaw<200,17>staa<100,18> rbao<100,18>rdbaw<200,15>staa<100,17>rbao<100,17>rdbaw<200,13>. dhey<100,17>rsklih<100,17>nxao<100,17>nzao<100,17>ndhax<100,17> staa<100,20>rbao<100,20>rdbaw<200,17>staa<100,18>rbao<100,18> rdbaw<200,15>jhih<400,13>m<50,13>. _<50,17>ih<400,17>tslay<100,17>fjhih<100,17>mbah<100,17> tnao<50,20>tax<50,20>zwiy<100,20>now<100,17>ih<200,17> tnao<50,18>tax<50,18>zwiy<100,18>now<100,15>ih<200,15> tnao<50,17>tax<50,17>zwiy<100,17>now<100,13>ih<200,13> t<50,13>. ih<100,17>tslay<100,17>fjhih<100,17>mbah<100,17>tnao<50,20> tax<50,20>zwiy<100,20>now<100,17>ih<200,17>tnao<50,18> tax<50,18>zwiy<100,18>now<100,15>ih<200,15>tkae<200,13> ptix<200,13>n<50,13>. _<50,17>ih<400,17>tswah<100,17>rsdhae<100,17>ndhae<100,17> t_hxiy<100,20>zdeh<200,20>djhih<200,17>mdeh<200,18>djhih<200,15> mdeh<200,17>djhih<200,13>m<50,13>. ih<100,17>tswah<100,17>rsdhae<100,17>ndhae<100,17>t_hxiy<100,20> zdeh<200,20>djhih<200,17>mdeh<200,18>djhih<200,15>mdeh<400,13> d<50,13>. _<50,17>wiy<400,17>kah<100,17>mih<100,17>npiy<200,17> sshuh<100,20>tuh<100,20>kih<200,17>lshuh<100,18>tuh<100,18> kih<200,15>lshuh<100,17>tuh<100,17>kih<200,13>l<50,13>. wiy<100,17>kah<100,17>mih<100,17>npiy<200,17>sshuh<100,20> tuh<100,20>kih<200,17>lshuh<100,18>tuh<100,18>kih<200,15> lmeh<400,13>n<50,13>. _<50,17>yxih<400,17>kaa<100,17>naa<100,17>chey<100,17> njhdhax<50,17>lao<50,20>zax<100,20>fih<100,17>zih<100,17> kslao<50,18>zax<100,18>fih<100,15>zih<100,15>kslao<50,17> zax<100,17>fih<100,13>zih<100,13>ks<50,13>. yxih<400,17>kaa<100,17>naa<100,17>chey<100,17>njhdhax<50,17> lao<50,20>zax<100,20>fih<100,17>zih<100,17>kslao<50,18> zax<100,18>fih<100,15>zih<100,15>kskaa<200,13>ptix<200,13> n<50,13>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:52'! startrek1 " DECTalkReader startrek1 playOn: KlattVoice new delayed: 5000 " ^ self eventsFromString: '_<50,17>dhey<400,17>rsklih<100,17>nxao<100,17>nzao<100,17> ndhax<100,17>staa<100,20>rbao<100,20>rdbaw<200,17>staa<100,18> rbao<100,18>rdbaw<200,15>staa<100,17>rbao<100,17>rdbaw<200,13>.'! ! !DECTalkReader class methodsFor: 'examples' stamp: 'len 12/20/1999 04:38'! vermont " (DECTalkReader vermont pitchBy: 0.5) playOn: (KlattVoice new tract: 18.5; turbulence: 59) delayed: 15000 " ^ self eventsFromString: 'peh<400,25> niy<400,23>z ih<400,20>n ah<400,18> striy<1200,20>m ,<400> fao<400,25> lih<400,23>nx liy<500,20>vz ,<100> ah<200,16>v sih<200,18> kah<200,20> mao<1000,12>rx ,<200> muw<400,20>n lay<400,18>t ih<400,16>n vrr<400,13> maa<1200,16>nt ,<400> ay<400,25> siy<400,23> fih<400,20>nx grr<400,18> wey<1200,20>vz ,<400> skiy<400,25> trae<300,23>lxz ,<100> aa<600,20>n ah<200,16> maw<200,18>n tih<200,20>n saa<800,12>-ay<200,12>d ,<200> snow<400,20> lay<400,18>t ih<400,16>n vrr<400,13> maa<1200,16>nt ,<400> teh<200,15> lah<200,15> grae<200,15>f key<400,15> bah<300,15>lxz ,<100> dhey<200,15> sih<200,15>nx daw<400,15>n dhah<200,15> hxay<200,15> wey<300,15> ,<100> ae<200,15>nd trae<200,15> vuh<200,15>lx iy<200,15>ch beh<500,27>nd,<100> ih<200,25>n dhah<200,27> row<1200,24>d ,<400> piy<200,16> pah<200,16>lx hxuw<200,16> miy<200,16>t ,<200> ih<400,16>n-dhih<200,16>s-row<200,16>-mae<400,16>n-tih<160,16>k ,<40> seh<200,16> tih<300,16>nx ,<100> aa<200,16>rx sow<200,16> hxih<200,16>p nah<200,16> tay<400,28>zd ,<200> bay<200,26> dhah<200,28> lah<900,25>v liy<700,24> ,<200> iy<400,25>v nih<400,23>nx sah<400,20> mrr<400,18> briy<1200,20>z ,<400> wao<400,25>rx blih<400,23>nx ah<400,20>v ,<200> ah<200,16> meh<200,18> dow<200,20> laa<800,12>rxk ,<400> muw<400,20>n lay<400,18>t ih<400,16>n vrr<400,13> maa<1300,16>nt ,<400> iy<40,12>-yuw<280,12> ae<350,13>n day<420,16> ,<60> ae<340,20>nd muw<380,25>n lay<340,27>t ,<100> ih<500,24>n vrr<540,26> maa<2000,23>nt.'! ! HtmlFormatter subclass: #DHtmlFormatter instanceVariableNames: 'fontSpecs ' classVariableNames: '' poolDictionaries: '' category: 'Network-HTML Formatter'! !DHtmlFormatter commentStamp: '' prior: 0! an attempt to improve HtmlFormatter... make it a bit more DOMish (eventually) roadmap -1- support for font specs (color, size) -2- support for tabless! !DHtmlFormatter methodsFor: 'private-formatting' stamp: 'bolot 5/18/2000 11:26'! setAttributes "set attributes on the output stream" | attribs | attribs _ OrderedCollection new. indentLevel > 0 ifTrue: [ attribs add: (TextIndent tabs: indentLevel) ]. boldLevel > 0 ifTrue: [ attribs add: TextEmphasis bold ]. italicsLevel > 0 ifTrue: [ attribs add: TextEmphasis italic ]. underlineLevel > 0 ifTrue: [ attribs add: TextEmphasis underlined ]. strikeLevel > 0 ifTrue: [ attribs add: TextEmphasis struckOut ]. urlLink isNil ifFalse: [ attribs add: (TextURL new url: urlLink) ]. fontSpecs isEmptyOrNil ifFalse: [attribs addAll: fontSpecs last] ifTrue: [attribs add: (TextFontChange defaultFontChange)]. outputStream currentAttributes: attribs! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:55'! decreaseFontBy: relSize self startFont: (TextFontChange fontNumber: ((self lastFontSize - relSize) min: 4))! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:23'! endFont: aFont fontSpecs isEmptyOrNil ifFalse: [fontSpecs removeLast]. self setAttributes! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 12:01'! endHeader: level boldLevel _ boldLevel - 1. "self decreaseBold" self ensureNewlines: 2. self endFont: nil.! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 12:00'! headerFont: level ^{TextFontChange fontNumber: ((5 - level) max: 1)}! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:50'! increaseFontBy: relSize self startFont: (TextFontChange fontNumber: ((self lastFontSize + relSize) min: 4))! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:55'! lastFontSize | textAttrib | fontSpecs isEmptyOrNil ifTrue: [^1]. fontSpecs reverseDo: [:specs | textAttrib _ specs detect: [:attrib | attrib isKindOf: TextFontChange] ifNone: []. textAttrib ifNotNil: [^textAttrib fontNumber]]. ^1 "default font size in Squeak (1) corresponds to HTML's default 4"! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:57'! resetFont "probably need use document defaults" self startFont: {TextColor black. TextFontChange fontNumber: 1}! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 11:57'! startFont: aTextAttribList "aTextAttribList is a collection of TextAttributes" fontSpecs ifNil: [fontSpecs _ OrderedCollection new]. fontSpecs add: aTextAttribList. self setAttributes! ! !DHtmlFormatter methodsFor: 'formatting commands' stamp: 'bolot 5/18/2000 12:00'! startHeader: level self ensureNewlines: 3. boldLevel _ boldLevel + 1. "self increaseBold" self startFont: (self headerFont: level).! ! InterpreterPlugin subclass: #DSAPlugin instanceVariableNames: 'dsaRemainder dsaDivisor dsaQuotient remainderDigitCount divisorDigitCount ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !DSAPlugin commentStamp: '' prior: 0! This plugin defines primitives that support the DigitalSignatureAlgorithm class. Three of these primitives support fast multiplication and division of very large integers, three others support the SecureHashAlgorithm. ! !DSAPlugin methodsFor: 'primitives-SHA' stamp: 'jm 12/21/1999 22:52'! primitiveExpandBlock "Expand a 64 byte ByteArray (the first argument) into and an Bitmap of 80 32-bit words (the second argument). When reading a 32-bit integer from the ByteArray, consider the first byte to contain the most significant bits of the word (i.e., use big-endian byte ordering)." | expanded buf wordPtr bytePtr src v | self export: true. self var: #wordPtr declareC: 'unsigned int *wordPtr'. self var: #bytePtr declareC: 'unsigned char *bytePtr'. expanded _ interpreterProxy stackObjectValue: 0. buf _ interpreterProxy stackObjectValue: 1. interpreterProxy success: (interpreterProxy isWords: expanded). interpreterProxy success: (interpreterProxy isBytes: buf). interpreterProxy failed ifTrue: [^ nil]. interpreterProxy success: ((interpreterProxy stSizeOf: expanded) = 80). interpreterProxy success: ((interpreterProxy stSizeOf: buf) = 64). interpreterProxy failed ifTrue: [^ nil]. wordPtr _ interpreterProxy firstIndexableField: expanded. bytePtr _ interpreterProxy firstIndexableField: buf. src _ 0. 0 to: 15 do: [:i | v _ ((bytePtr at: src) << 24) + ((bytePtr at: src + 1) << 16) + ((bytePtr at: src + 2) << 8) + (bytePtr at: src + 3). wordPtr at: i put: v. src _ src + 4]. 16 to: 79 do: [:i | v _ (((wordPtr at: i - 3) bitXor: (wordPtr at: i - 8)) bitXor: (wordPtr at: i - 14)) bitXor: (wordPtr at: i - 16). v _ self leftRotate: v by: 1. wordPtr at: i put: v]. interpreterProxy pop: 2. ! ! !DSAPlugin methodsFor: 'primitives-SHA' stamp: 'jm 12/21/1999 20:43'! primitiveHasSecureHashPrimitive "Answer true if the secure hash primitive is implemented." self export: true. interpreterProxy pop: 1. interpreterProxy pushBool: true. ! ! !DSAPlugin methodsFor: 'primitives-SHA' stamp: 'jm 12/21/1999 23:35'! primitiveHashBlock "Hash a Bitmap of 80 32-bit words (the first argument), using the given state (the second argument)." | state buf statePtr bufPtr a b c d e tmp | self export: true. self var: #statePtr declareC: 'unsigned int *statePtr'. self var: #bufPtr declareC: 'unsigned int *bufPtr'. state _ interpreterProxy stackObjectValue: 0. buf _ interpreterProxy stackObjectValue: 1. interpreterProxy success: (interpreterProxy isWords: state). interpreterProxy success: (interpreterProxy isWords: buf). interpreterProxy failed ifTrue: [^ nil]. interpreterProxy success: ((interpreterProxy stSizeOf: state) = 5). interpreterProxy success: ((interpreterProxy stSizeOf: buf) = 80). interpreterProxy failed ifTrue: [^ nil]. statePtr _ interpreterProxy firstIndexableField: state. bufPtr _ interpreterProxy firstIndexableField: buf. a _ statePtr at: 0. b _ statePtr at: 1. c _ statePtr at: 2. d _ statePtr at: 3. e _ statePtr at: 4. 0 to: 19 do: [:i | tmp _ 16r5A827999 + ((b bitAnd: c) bitOr: (b bitInvert32 bitAnd: d)) + (self leftRotate: a by: 5) + e + (bufPtr at: i). e _ d. d _ c. c _ self leftRotate: b by: 30. b _ a. a _ tmp]. 20 to: 39 do: [:i | tmp _ 16r6ED9EBA1 + ((b bitXor: c) bitXor: d) + (self leftRotate: a by: 5) + e + (bufPtr at: i). e _ d. d _ c. c _ self leftRotate: b by: 30. b _ a. a _ tmp]. 40 to: 59 do: [:i | tmp _ 16r8F1BBCDC + (((b bitAnd: c) bitOr: (b bitAnd: d)) bitOr: (c bitAnd: d)) + (self leftRotate: a by: 5) + e + (bufPtr at: i). e _ d. d _ c. c _ self leftRotate: b by: 30. b _ a. a _ tmp]. 60 to: 79 do: [:i | tmp _ 16rCA62C1D6 + ((b bitXor: c) bitXor: d) + (self leftRotate: a by: 5) + e + (bufPtr at: i). e _ d. d _ c. c _ self leftRotate: b by: 30. b _ a. a _ tmp]. statePtr at: 0 put: (statePtr at: 0) + a. statePtr at: 1 put: (statePtr at: 1) + b. statePtr at: 2 put: (statePtr at: 2) + c. statePtr at: 3 put: (statePtr at: 3) + d. statePtr at: 4 put: (statePtr at: 4) + e. interpreterProxy pop: 2. ! ! !DSAPlugin methodsFor: 'primitives-integers' stamp: 'jm 12/21/1999 18:48'! primitiveBigDivide "Called with three LargePositiveInteger arguments, rem, div, quo. Divide div into rem and store the quotient into quo, leaving the remainder in rem." "Assume: quo starts out filled with zeros." | rem div quo | self export: true. quo _ interpreterProxy stackObjectValue: 0. div _ interpreterProxy stackObjectValue: 1. rem _ interpreterProxy stackObjectValue: 2. interpreterProxy success: (interpreterProxy fetchClassOf: rem) = interpreterProxy classLargePositiveInteger. interpreterProxy success: (interpreterProxy fetchClassOf: div) = interpreterProxy classLargePositiveInteger. interpreterProxy success: (interpreterProxy fetchClassOf: quo) = interpreterProxy classLargePositiveInteger. interpreterProxy failed ifTrue:[^ nil]. dsaRemainder _ interpreterProxy firstIndexableField: rem. dsaDivisor _ interpreterProxy firstIndexableField: div. dsaQuotient _ interpreterProxy firstIndexableField: quo. divisorDigitCount _ interpreterProxy stSizeOf: div. remainderDigitCount _ interpreterProxy stSizeOf: rem. "adjust pointers for base-1 indexing" dsaRemainder _ dsaRemainder - 1. dsaDivisor _ dsaDivisor - 1. dsaQuotient _ dsaQuotient - 1. self bigDivideLoop. interpreterProxy pop: 3. ! ! !DSAPlugin methodsFor: 'primitives-integers' stamp: 'jm 12/21/1999 08:09'! primitiveBigMultiply "Multiple f1 by f2, placing the result into prod. f1, f2, and prod must be LargePositiveIntegers, and the length of prod must be the sum of the lengths of f1 and f2." "Assume: prod starts out filled with zeros" | prod f2 f1 prodLen f1Len f2Len prodPtr f2Ptr f1Ptr digit carry k sum | self export: true. self var: #prodPtr declareC: 'unsigned char *prodPtr'. self var: #f2Ptr declareC: 'unsigned char *f2Ptr'. self var: #f1Ptr declareC: 'unsigned char *f1Ptr'. prod _ interpreterProxy stackObjectValue: 0. f2 _ interpreterProxy stackObjectValue: 1. f1 _ interpreterProxy stackObjectValue: 2. interpreterProxy success: (interpreterProxy isBytes: prod). interpreterProxy success: (interpreterProxy isBytes: f2). interpreterProxy success: (interpreterProxy isBytes: f1). interpreterProxy success: (interpreterProxy fetchClassOf: prod) = interpreterProxy classLargePositiveInteger. interpreterProxy success: (interpreterProxy fetchClassOf: f2) = interpreterProxy classLargePositiveInteger. interpreterProxy success: (interpreterProxy fetchClassOf: f1) = interpreterProxy classLargePositiveInteger. interpreterProxy failed ifTrue:[^ nil]. prodLen _ interpreterProxy stSizeOf: prod. f1Len _ interpreterProxy stSizeOf: f1. f2Len _ interpreterProxy stSizeOf: f2. interpreterProxy success: (prodLen = (f1Len + f2Len)). interpreterProxy failed ifTrue:[^ nil]. prodPtr _ interpreterProxy firstIndexableField: prod. f2Ptr _ interpreterProxy firstIndexableField: f2. f1Ptr _ interpreterProxy firstIndexableField: f1. 0 to: f1Len-1 do: [:i | (digit _ f1Ptr at: i) ~= 0 ifTrue: [ carry _ 0. k _ i. "Loop invariants: 0 <= carry <= 16rFF, k = i + j - 1" 0 to: f2Len-1 do: [:j | sum _ ((f2Ptr at: j) * digit) + (prodPtr at: k) + carry. carry _ sum bitShift: -8. prodPtr at: k put: (sum bitAnd: 255). k _ k + 1]. prodPtr at: k put: carry]]. interpreterProxy pop: 3. ! ! !DSAPlugin methodsFor: 'primitives-integers' stamp: 'jm 12/21/1999 09:31'! primitiveHighestNonZeroDigitIndex "Called with one LargePositiveInteger argument. Answer the index of the top-most non-zero digit." | arg bigIntPtr i | self export: true. self var: #bigIntPtr declareC: 'unsigned char *bigIntPtr'. arg _ interpreterProxy stackObjectValue: 0. interpreterProxy success: (interpreterProxy fetchClassOf: arg) = interpreterProxy classLargePositiveInteger. interpreterProxy failed ifTrue: [^ nil]. bigIntPtr _ interpreterProxy firstIndexableField: arg. i _ interpreterProxy stSizeOf: arg. [(i > 0) and: [(bigIntPtr at: (i _ i - 1)) = 0]] whileTrue: ["scan down from end to first non-zero digit"]. interpreterProxy pop: 1. interpreterProxy pushInteger: i + 1. ! ! !DSAPlugin methodsFor: 'private' stamp: 'jm 12/21/1999 08:10'! addBackDivisorDigitShift: digitShift "Add back the divisor shifted left by the given number of digits. This is done only when the estimate of quotient digit was one larger than the correct value." | carry rIndex sum | carry _ 0. rIndex _ digitShift + 1. 1 to: divisorDigitCount do: [:i | sum _ (dsaRemainder at: rIndex) + (dsaDivisor at: i) + carry. dsaRemainder at: rIndex put: (sum bitAnd: 16rFF). carry _ sum bitShift: -8. rIndex _ rIndex + 1]. "do final carry" sum _ (dsaRemainder at: rIndex) + carry. dsaRemainder at: rIndex put: (sum bitAnd: 16rFF). "Note: There should be a final carry that cancels out the excess borrow." "Assert: (sum bitShift: -8) ~= 1 ifTrue: [self halt: 'no carry!!']." ! ! !DSAPlugin methodsFor: 'private' stamp: 'jm 12/21/1999 18:48'! bigDivideLoop "This is the core of the divide algorithm. This loop steps through the digit positions of the quotient, each time estimating the right quotient digit, subtracting from the remainder the divisor times the quotient digit shifted left by the appropriate number of digits. When the loop terminates, all digits of the quotient have been filled in and the remainder contains a value less than the divisor. The tricky bit is estimating the next quotient digit. Knuth shows that the digit estimate computed here will never be less than it should be and cannot be more than one over what it should be. Furthermore, the case where the estimate is one too large is extremely rare. For example, in a typical test of 100000 random 60-bit division problems, the rare case only occured five times. See Knuth, volume 2 ('Semi-Numerical Algorithms') 2nd edition, pp. 257-260" | d1 d2 firstDigit firstTwoDigits thirdDigit q digitShift qTooBig | "extract the top two digits of the divisor" d1 _ dsaDivisor at: divisorDigitCount. d2 _ dsaDivisor at: divisorDigitCount - 1. remainderDigitCount to: divisorDigitCount + 1 by: -1 do: [:j | "extract the top several digits of remainder." firstDigit _ dsaRemainder at: j. firstTwoDigits _ (firstDigit bitShift: 8) + (dsaRemainder at: j - 1). thirdDigit _ dsaRemainder at: j - 2. "estimate q, the next digit of the quotient" firstDigit = d1 ifTrue: [q _ 255] ifFalse: [q _ firstTwoDigits // d1]. "adjust the estimate of q if necessary" (d2 * q) > (((firstTwoDigits - (q * d1)) bitShift: 8) + thirdDigit) ifTrue: [ q _ q - 1. (d2 * q) > (((firstTwoDigits - (q * d1)) bitShift: 8) + thirdDigit) ifTrue: [ q _ q - 1]]. digitShift _ j - divisorDigitCount - 1. q > 0 ifTrue: [ qTooBig _ self subtractDivisorMultipliedByDigit: q digitShift: digitShift. qTooBig ifTrue: [ "this case is extremely rare" self addBackDivisorDigitShift: digitShift. q _ q - 1]]. "record this digit of the quotient" dsaQuotient at: digitShift + 1 put: q]. ! ! !DSAPlugin methodsFor: 'private' stamp: 'jm 12/21/1999 21:54'! leftRotate: anInteger by: bits "Rotate the given 32-bit integer left by the given number of bits and answer the result." self var: #anInteger declareC: 'unsigned int anInteger'. ^ (anInteger << bits) bitOr: (anInteger >> (32 - bits)) ! ! !DSAPlugin methodsFor: 'private' stamp: 'jm 12/21/1999 08:13'! subtractDivisorMultipliedByDigit: digit digitShift: digitShift "Multiply the divisor by the given digit (an integer in the range 0..255), shift it left by the given number of digits, and subtract the result from the current remainder. Answer true if there is an excess borrow, indicating that digit was one too large. (This case is quite rare.)" | borrow rIndex prod resultDigit | borrow _ 0. rIndex _ digitShift + 1. 1 to: divisorDigitCount do: [:i | prod _ ((dsaDivisor at: i) * digit) + borrow. borrow _ prod bitShift: -8. resultDigit _ (dsaRemainder at: rIndex) - (prod bitAnd: 16rFF). resultDigit < 0 ifTrue: [ "borrow from the next digit" resultDigit _ resultDigit + 256. borrow _ borrow + 1]. dsaRemainder at: rIndex put: resultDigit. rIndex _ rIndex + 1]. "propagate the final borrow if necessary" borrow = 0 ifTrue: [^ false]. resultDigit _ (dsaRemainder at: rIndex) - borrow. resultDigit < 0 ifTrue: [ "digit was too large (this case is quite rare)" dsaRemainder at: rIndex put: resultDigit + 256. ^ true] ifFalse: [ dsaRemainder at: rIndex put: resultDigit. ^ false]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DSAPlugin class instanceVariableNames: ''! !DSAPlugin class methodsFor: 'plugin translation' stamp: 'sma 3/3/2000 12:44'! declareCVarsIn: cg cg var: #dsaRemainder type: #'unsigned char*'. cg var: #dsaDivisor type: #'unsigned char*'. cg var: #dsaQuotient type: #'unsigned char*'! ! !DSAPlugin class methodsFor: 'plugin translation' stamp: 'ar 5/15/2000 22:51'! moduleName "Time millisecondsToRun: [ DSAPlugin translateDoInlining: true]" ^ 'DSAPrims' "Yes - it needs to be named this way or else we'll not find it" ! ! PostscriptCanvas subclass: #DSCPostscriptCanvas instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Postscript Canvases'! !DSCPostscriptCanvas commentStamp: '' prior: 0! I generate multi-page Postscript files, for example of Book morphs. The goal is to crete Adobe Document Structuring Conventions compliant, but this is currently not the case. ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'mpw 9/15/1999 20:14'! defaultImageableArea ^ self defaultPageSize insetBy:self defaultMargin. ! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'di 8/3/2000 14:18'! defaultMargin "In Points" ^ (0.25 * 72) asInteger. ! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'di 8/5/2000 22:56'! defaultPageSize " This is Letter size in points. European A4 is 595 @ 842 " ^ 0 @ 0 extent: ((8.5 @ 11.0) * 72) asIntegerPoint. ! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 2/1/2001 17:43'! fullDraw: aMorph super fullDraw: aMorph. (morphLevel = 0 and: [aMorph pagesHandledAutomatically not]) ifTrue: [ target showpage. ].! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'mpw 9/12/1999 21:53'! fullDrawBookMorph:aBookMorph " draw all the pages in a book morph, but only if it is the top-level morph " morphLevel == 1 ifTrue:[ self drawPages:aBookMorph pages. ] ifFalse:[ ^super fullDrawBookMorph:aBookMorph. ]. target print:'%%EOF'; cr. ! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 12:23'! pageBBox | pageSize offset bbox trueExtent | EPSCanvas bobsPostScriptHacks ifTrue: [ trueExtent _ savedMorphExtent "this one has been rotated" ] ifFalse: [ trueExtent _ psBounds extent ]. pageSize _ self defaultImageableArea. offset _ ((pageSize extent - trueExtent) / 2 max: 0@0) + self defaultMargin. bbox _ offset extent: psBounds extent. ^bbox! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 11:51'! pageOffset | offset | EPSCanvas bobsPostScriptHacks ifTrue: [^0@0]. "seems like we were adding it twice" offset _ self pageBBox origin. ^ (offset x @ offset y).! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 2/1/2001 18:08'! setupGStateForMorph: aMorph " position the morph on the page " morphLevel == (topLevelMorph pagesHandledAutomatically ifTrue: [2] ifFalse: [1]) ifTrue:[ target print: '% pageOffset'; cr. target translate: self pageOffset. self writeSetupForRect: aMorph bounds. target print: '% negate morph offset';cr. target translate: aMorph bounds origin negated. ]. ! ! !DSCPostscriptCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 16:01'! writePSIdentifierRotated: rotateFlag | morphExtent pageExtent scaledBox | target print:'%!!PS-Adobe-2.0'; cr; print:'%%Pages: (atend)'; cr. "Define initialScale so that the morph will fit the page rotated or not" savedMorphExtent _ morphExtent _ rotateFlag ifTrue: [psBounds extent transposed] ifFalse: [psBounds extent]. pageExtent _ self defaultImageableArea extent asFloatPoint. initialScale _ pageExtent x/morphExtent x min: pageExtent y/morphExtent y. target print:'% initialScale: '; write:initialScale; cr. scaledBox _ self pageBBox rounded. target print: '%%BoundingBox: '; write: scaledBox rounded; cr. rotateFlag ifTrue: [ target print: '90 rotate'; cr; write: self defaultMargin * initialScale; space; write: (self defaultMargin + scaledBox height * initialScale) negated; print: ' translate'; cr ] ifFalse: [ target write: self defaultMargin * initialScale; space; write: (self defaultMargin * initialScale); print: ' translate'; cr ]. target print: '%%EndComments'; cr. ! ! DSCPostscriptCanvas subclass: #DSCPostscriptCanvasToDisk instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Postscript Canvases'! !DSCPostscriptCanvasToDisk methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 16:19'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset self reset. psBounds _ offset extent: aMorph bounds extent. topLevelMorph _ aMorph. self writeHeaderRotated: rotateFlag. self fullDrawMorph: aMorph. ^self close ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DSCPostscriptCanvasToDisk class instanceVariableNames: ''! !DSCPostscriptCanvasToDisk class methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 22:14'! defaultTarget ^PostscriptEncoderToDisk stream. ! ! !DSCPostscriptCanvasToDisk class methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 09:30'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset | newFileName stream | ^[ (self new morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset) close ] on: PickAFileToWriteNotification do: [ :ex | newFileName _ FillInTheBlank request: 'Name of file to write:' initialAnswer: 'xxx',Time millisecondClockValue printString,'.eps'. newFileName isEmptyOrNil ifFalse: [ stream _ FileStream fileNamed: newFileName. stream ifNotNil: [ex resume: stream]. ]. ]. ! ! Object subclass: #DamageRecorder instanceVariableNames: 'invalidRects totalRepaint ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !DamageRecorder methodsFor: 'initialization' stamp: 'sma 6/5/2000 11:55'! reset "Clear the damage list." invalidRects _ OrderedCollection new: 15. totalRepaint _ false ! ! !DamageRecorder methodsFor: 'recording'! doFullRepaint "Record that a full redisplay is needed. No further damage rectangles will be recorded until after the next reset." ^ totalRepaint _ true. ! ! !DamageRecorder methodsFor: 'recording'! invalidRectsFullBounds: aRectangle "Return a collection of damaged rectangles for the given canvas. If a total repaint has been requested, return the given rectangle." totalRepaint ifTrue: [^ Array with: aRectangle] ifFalse: [^ invalidRects copy]. ! ! !DamageRecorder methodsFor: 'recording' stamp: 'tk 9/25/2000 23:06'! recordInvalidRect: aRectangle "Record the given rectangle in my damage list, a list of rectangular areas of the display that should be redraw on the next display cycle." "Details: Damaged rectangles are often identical or overlap significantly. In these cases, we merge them to reduce the number of damage rectangles that must be processed when the display is updated. Moreover, above a certain threshold, we ignore the individual rectangles completely, and simply do a complete repaint on the next cycle." | mergeRect | totalRepaint ifTrue: [^ self]. "planning full repaint; don't bother collecting damage" invalidRects do: [:rect | (rect intersects: aRectangle) ifTrue: [ "merge rectangle in place (see note below) if there is any overlap" rect setOrigin: (rect origin min: aRectangle origin) truncated corner: (rect corner max: aRectangle corner) truncated. ^ self]]. invalidRects size >= 15 ifTrue: [ "if there are too many separate areas, just repaint all" "totalRepaint _ true." "Note: The totalRepaint policy has poor behavior when many local rectangles (such as parts of a text selection) force repaint of the entire screen. As an alternative, this code performs a simple merge of all rects whenever there are more than 10." mergeRect _ Rectangle merging: invalidRects. self reset. invalidRects addLast: mergeRect]. "add the given rectangle to the damage list" "Note: We make a deep copy of all rectangles added to the damage list, since rectangles in this list may be extended in place." invalidRects addLast: (aRectangle topLeft truncated corner: aRectangle bottomRight truncated). ! ! !DamageRecorder methodsFor: 'testing'! updateIsNeeded "Return true if the display needs to be updated." ^ totalRepaint or: [invalidRects size > 0] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DamageRecorder class instanceVariableNames: ''! !DamageRecorder class methodsFor: 'instance creation'! new ^ super new reset ! ! Stream subclass: #DataStream instanceVariableNames: 'byteStream topCall basePos ' classVariableNames: 'TypeMap ' poolDictionaries: '' category: 'System-Object Storage'! !DataStream commentStamp: '' prior: 0! This is the save-to-disk facility. A DataStream can store one or more objects in a persistent form. To handle objects with sharing and cycles, you must use a ReferenceStream instead of a DataStream. (Or SmartRefStream.) ReferenceStream is typically faster and produces smaller files because it doesn't repeatedly write the same Symbols. Here is the way to use DataStream and ReferenceStream: rr _ ReferenceStream fileNamed: 'test.obj'. rr nextPut: . rr close. To get it back: rr _ ReferenceStream fileNamed: 'test.obj'. _ rr next. rr close. Each object to be stored has two opportunities to control what gets stored. On the high level, objectToStoreOnDataStream allows you to substitute another object on the way out. The low level hook is storeDataOn:. The read-in counterparts to these messages are comeFullyUpOnReload and (class) readDataFrom:size:. See these methods, and the class DiskProxy, for more information about externalizing and internalizing. NOTE: A DataStream should be treated as a write-stream for writing. It is a read-stream for reading. It is not a ReadWriteStream. ! !DataStream methodsFor: 'write and read' stamp: '6/9/97 08:14 tk'! beginInstance: aClass size: anInteger "This is for use by storeDataOn: methods. Cf. Object>>storeDataOn:." "Addition of 1 seems to make extra work, since readInstance has to compensate. Here for historical reasons dating back to Kent Beck's original implementation in late 1988. In ReferenceStream, class is just 5 bytes for shared symbol. SmartRefStream puts out the names and number of class's instances variables for checking." byteStream nextNumber: 4 put: anInteger + 1. self nextPut: aClass name! ! !DataStream methodsFor: 'write and read'! beginReference: anObject "WeÕre starting to read anObject. Remember it and its reference position (if we care; ReferenceStream cares). Answer the reference position." ^ 0! ! !DataStream methodsFor: 'write and read'! getCurrentReference "PRIVATE -- Return the currentReference posn. Overridden by ReferenceStream." ^ 0! ! !DataStream methodsFor: 'write and read' stamp: 'tk 4/8/1999 13:11'! maybeBeginReference: internalObject "Do nothing. See ReferenceStream|maybeBeginReference:" ^ internalObject! ! !DataStream methodsFor: 'write and read' stamp: 'tk 10/4/2000 10:33'! next "Answer the next object in the stream." | type selector anObject isARefType pos internalObject | type _ byteStream next. type ifNil: [pos _ byteStream position. "absolute!!!!" byteStream close. "clean up" byteStream position = 0 ifTrue: [self error: 'The file did not exist in this directory'] ifFalse: [self error: 'Unexpected end of object file']. pos. "so can see it in debugger" ^ nil]. type = 0 ifTrue: [pos _ byteStream position. "absolute!!!!" byteStream close. "clean up" self error: 'Expected start of object, but found 0'. ^ nil]. isARefType _ self noteCurrentReference: type. selector _ #(readNil readTrue readFalse readInteger "<-4" readStringOld readSymbol readByteArray "<-7" readArray readInstance readReference readBitmap "<-11" readClass readUser readFloat readRectangle readShortInst "<-16" readString readWordArray readWordArrayForSegment "<-19" readWordLike readMethod "<-21") at: type. selector == 0 ifTrue: [pos _ byteStream position. "absolute!!!!" byteStream close. self error: 'file is more recent than this system'. ^ nil]. anObject _ self perform: selector. "A method that recursively calls next (readArray, readInstance, objectAt:) must save & restore the current reference position." isARefType ifTrue: [self beginReference: anObject]. "After reading the externalObject, internalize it. #readReference is a special case. Either: (1) We actually have to read the object, recursively calling next, which internalizes the object. (2) We just read a reference to an object already read and thus already interalized. Either way, we must not re-internalize the object here." selector == #readReference ifTrue: [^ anObject]. internalObject _ anObject comeFullyUpOnReload: self. ^ self maybeBeginReference: internalObject! ! !DataStream methodsFor: 'write and read' stamp: 'tk 10/4/2000 10:35'! nextPut: anObject "Write anObject to the receiver stream. Answer anObject." | typeID selector objectToStore | typeID _ self typeIDFor: anObject. (self tryToPutReference: anObject typeID: typeID) ifTrue: [^ anObject]. objectToStore _ (self objectIfBlocked: anObject) objectForDataStream: self. objectToStore == anObject ifFalse: [typeID _ self typeIDFor: objectToStore]. byteStream nextPut: typeID. selector _ #(writeNil: writeTrue: writeFalse: writeInteger: writeStringOld: writeSymbol: writeByteArray: writeArray: writeInstance: errorWriteReference: writeBitmap: writeClass: writeUser: writeFloat: writeRectangle: == "<-16 short inst" writeString: writeBitmap: writeBitmap: writeWordLike: writeInstance: "CompiledMethod") at: typeID. self perform: selector with: objectToStore. ^ anObject "NOTE: If anObject is a reference type (one that we write cross-references to) but its externalized form (result of objectForDataStream:) isn't (e.g. CompiledMethod and ViewState), then we should remember its externalized form but not add to 'references'. Putting that object again should just put its external form again. That's more compact and avoids seeks when reading. But we just do the simple thing here, allowing backward-references for non-reference types like nil. So objectAt: has to compensate. Objects that externalize nicely won't contain the likes of ViewStates, so this shouldn't hurt much. writeReference: -> errorWriteReference:."! ! !DataStream methodsFor: 'write and read'! nextPutAll: aCollection "Write each of the objects in aCollection to the receiver stream. Answer aCollection." ^ aCollection do: [:each | self nextPut: each]! ! !DataStream methodsFor: 'write and read'! noteCurrentReference: typeID "PRIVATE -- If we support references for type typeID, remember the current byteStream position so we can add the next object to the ÔobjectsÕ dictionary, and return true. Else return false. This method is here to be overridden by ReferenceStream" ^ false! ! !DataStream methodsFor: 'write and read' stamp: ' 6/9/97'! objectAt: anInteger "PRIVATE -- Read & return the object at a given stream position. 08:18 tk anInteger is a relative file position. " | savedPosn anObject refPosn | savedPosn _ byteStream position. "absolute" refPosn _ self getCurrentReference. "relative position" byteStream position: anInteger + basePos. "was relative" anObject _ self next. self setCurrentReference: refPosn. "relative position" byteStream position: savedPosn. "absolute" ^ anObject! ! !DataStream methodsFor: 'write and read' stamp: 'tk 3/13/98 22:16'! objectIfBlocked: anObject "We don't do any blocking" ^ anObject! ! !DataStream methodsFor: 'write and read' stamp: '6/9/97 08:46 tk'! outputReference: referencePosn "PRIVATE -- Output a reference to the object at integer stream position referencePosn (relative to basePos). To output a weak reference to an object not yet written, supply (self vacantRef) for referencePosn." byteStream nextPut: 10. "reference typeID" byteStream nextNumber: 4 put: referencePosn "relative position"! ! !DataStream methodsFor: 'write and read' stamp: '6/9/97 08:32 tk'! readArray "PRIVATE -- Read the contents of an Array. We must do beginReference: here after instantiating the Array but before reading its contents, in case the contents reference the Array. beginReference: will be sent again when we return to next, but that's ok as long as we save and restore the current reference position over recursive calls to next." | count array refPosn | count _ byteStream nextNumber: 4. refPosn _ self beginReference: (array _ Array new: count). "relative pos" 1 to: count do: [:i | array at: i put: self next]. self setCurrentReference: refPosn. "relative pos" ^ array! ! !DataStream methodsFor: 'write and read'! readBitmap "PRIVATE -- Read the contents of a Bitmap." ^ Bitmap newFromStream: byteStream "Note that the reader knows that the size is in long words, but the data is in bytes."! ! !DataStream methodsFor: 'write and read'! readBoolean "PRIVATE -- Read the contents of a Boolean. This is here only for compatibility with old data files." ^ byteStream next ~= 0! ! !DataStream methodsFor: 'write and read' stamp: 'jm 8/19/1998 17:00'! readByteArray "PRIVATE -- Read the contents of a ByteArray." | count | count _ byteStream nextNumber: 4. ^ byteStream next: count "assume stream is in binary mode" ! ! !DataStream methodsFor: 'write and read' stamp: 'tk 3/24/98 10:29'! readClass "Should never be executed because a DiskProxy, not a clas comes in." ^ self error: 'Classes should be filed in'! ! !DataStream methodsFor: 'write and read'! readFalse "PRIVATE -- Read the contents of a False." ^ false! ! !DataStream methodsFor: 'write and read'! readFloat "PRIVATE -- Read the contents of a Float. This is the fast way to read a Float. We support 8-byte Floats here. Non-IEEE" | new | new _ Float new: 2. "To get an instance" new at: 1 put: (byteStream nextNumber: 4). new at: 2 put: (byteStream nextNumber: 4). ^ new! ! !DataStream methodsFor: 'write and read'! readFloatString "PRIVATE -- Read the contents of a Float string. This is the slow way to read a Float--via its string repÕn. It's here for compatibility with old data files." ^ Float readFrom: (byteStream next: (byteStream nextNumber: 4))! ! !DataStream methodsFor: 'write and read' stamp: 'tk 1/8/97'! readInstance "PRIVATE -- Read the contents of an arbitrary instance. ASSUMES: readDataFrom:size: sends me beginReference: after it instantiates the new object but before reading nested objects. NOTE: We must restore the current reference position after recursive calls to next. Let the instance, not the class read the data. " | instSize aSymbol refPosn anObject newClass | instSize _ (byteStream nextNumber: 4) - 1. refPosn _ self getCurrentReference. aSymbol _ self next. newClass _ Smalltalk at: aSymbol asSymbol. anObject _ newClass isVariable "Create object here" ifFalse: [newClass basicNew] ifTrue: [newClass basicNew: instSize - (newClass instSize)]. self setCurrentReference: refPosn. "before readDataFrom:size:" anObject _ anObject readDataFrom: self size: instSize. self setCurrentReference: refPosn. "before returning to next" ^ anObject! ! !DataStream methodsFor: 'write and read'! readInteger "PRIVATE -- Read the contents of a SmallInteger." ^ byteStream nextInt32 "signed!!!!!!"! ! !DataStream methodsFor: 'write and read' stamp: 'tk 10/6/2000 14:36'! readMethod "PRIVATE -- Read the contents of an arbitrary instance. ASSUMES: readDataFrom:size: sends me beginReference: after it instantiates the new object but before reading nested objects. NOTE: We must restore the current reference position after recursive calls to next. Let the instance, not the class read the data. " | instSize refPosn newClass className xxHeader nLits byteCodeSizePlusTrailer newMethod lits | instSize _ (byteStream nextNumber: 4) - 1. refPosn _ self getCurrentReference. className _ self next. newClass _ Smalltalk at: className asSymbol. xxHeader _ self next. "nArgs _ (xxHeader >> 24) bitAnd: 16rF." "nTemps _ (xxHeader >> 18) bitAnd: 16r3F." "largeBit _ (xxHeader >> 17) bitAnd: 1." nLits _ (xxHeader >> 9) bitAnd: 16rFF. "primBits _ ((xxHeader >> 19) bitAnd: 16r600) + (xxHeader bitAnd: 16r1FF)." byteCodeSizePlusTrailer _ instSize - (newClass instSize "0") - (nLits + 1 * 4). newMethod _ newClass newMethod: byteCodeSizePlusTrailer header: xxHeader. self setCurrentReference: refPosn. "before readDataFrom:size:" self beginReference: newMethod. lits _ newMethod numLiterals + 1. "counting header" 2 to: lits do: [:ii | newMethod objectAt: ii put: self next]. lits*4+1 to: newMethod basicSize do: [:ii | newMethod basicAt: ii put: byteStream next]. "Get raw bytes directly from the file" self setCurrentReference: refPosn. "before returning to next" ^ newMethod! ! !DataStream methodsFor: 'write and read'! readNil "PRIVATE -- Read the contents of an UndefinedObject." ^ nil! ! !DataStream methodsFor: 'write and read' stamp: ' 6/9/97'! readRectangle "Read a compact Rectangle. Rectangles with values outside +/- 2047 were stored as normal objects (type=9). They will not come here. 17:22 tk" "Encoding is four 12-bit signed numbers. 48 bits in next 6 bytes. 17:24 tk" | acc left top right bottom | acc _ byteStream nextNumber: 3. left _ acc bitShift: -12. (left bitAnd: 16r800) ~= 0 ifTrue: [left _ left - 16r1000]. "sign" top _ acc bitAnd: 16rFFF. (top bitAnd: 16r800) ~= 0 ifTrue: [top _ top - 16r1000]. "sign" acc _ byteStream nextNumber: 3. right _ acc bitShift: -12. (right bitAnd: 16r800) ~= 0 ifTrue: [right _ right - 16r1000]. "sign" bottom _ acc bitAnd: 16rFFF. (bottom bitAnd: 16r800) ~= 0 ifTrue: [bottom _ bottom - 16r1000]. "sign" ^ Rectangle left: left right: right top: top bottom: bottom ! ! !DataStream methodsFor: 'write and read' stamp: 'tk 1/5/2000 11:47'! readReference "Read the contents of an object reference. (Cf. outputReference:) File is not now positioned at this object." | referencePosition | ^ (referencePosition _ (byteStream nextNumber: 4)) = self vacantRef "relative" ifTrue: [nil] ifFalse: [self objectAt: referencePosition] "relative pos"! ! !DataStream methodsFor: 'write and read' stamp: 'tk 1/8/97'! readShortInst "Read the contents of an arbitrary instance that has a short header. ASSUMES: readDataFrom:size: sends me beginReference: after it instantiates the new object but before reading nested objects. NOTE: We must restore the current reference position after recursive calls to next. Let the instance, not the class read the data. " | instSize aSymbol refPosn anObject newClass | instSize _ (byteStream next) - 1. "one byte of size" refPosn _ self getCurrentReference. aSymbol _ self readShortRef. "class symbol in two bytes of file pos" newClass _ Smalltalk at: aSymbol asSymbol. anObject _ newClass isVariable "Create object here" ifFalse: [newClass basicNew] ifTrue: [newClass basicNew: instSize - (newClass instSize)]. self setCurrentReference: refPosn. "before readDataFrom:size:" anObject _ anObject readDataFrom: self size: instSize. self setCurrentReference: refPosn. "before returning to next" ^ anObject! ! !DataStream methodsFor: 'write and read' stamp: 'tk 7/12/1998 13:32'! readShortRef "Read an object reference from two bytes only. Original object must be in first 65536 bytes of the file. Relative to start of data. vacantRef not a possibility." ^ self objectAt: (byteStream nextNumber: 2)! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 21:03'! readString | str | byteStream ascii. str _ byteStream nextString. byteStream binary. ^ str ! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 21:27'! readStringOld ^ byteStream nextStringOld! ! !DataStream methodsFor: 'write and read'! readSymbol "PRIVATE -- Read the contents of a Symbol." ^ self readString asSymbol! ! !DataStream methodsFor: 'write and read'! readTrue "PRIVATE -- Read the contents of a True." ^ true! ! !DataStream methodsFor: 'write and read' stamp: 'tk 3/4/1999 22:58'! readUser "Reconstruct both the private class and the instance. Still used??" ^ self readInstance. "Will create new unique class" ! ! !DataStream methodsFor: 'write and read' stamp: 'tk 1/24/2000 23:20'! readWordArray "PRIVATE -- Read the contents of a WordArray." ^ WordArray newFromStream: byteStream "Size is number of long words."! ! !DataStream methodsFor: 'write and read' stamp: 'tk 1/24/2000 23:23'! readWordArrayForSegment "Read the contents of a WordArray ignoring endianness." ^ WordArrayForSegment newFromStream: byteStream "Size is number of long words."! ! !DataStream methodsFor: 'write and read' stamp: 'tk 2/3/2000 21:11'! readWordLike | refPosn aSymbol newClass anObject | "Can be used by any class that is bits and not bytes (WordArray, Bitmap, SoundBuffer, etc)." refPosn _ self getCurrentReference. aSymbol _ self next. newClass _ Smalltalk at: aSymbol asSymbol. anObject _ newClass newFromStream: byteStream. "Size is number of long words." self setCurrentReference: refPosn. "before returning to next" ^ anObject ! ! !DataStream methodsFor: 'write and read' stamp: 'tk 9/24/2000 15:39'! replace: original with: proxy "We may wish to remember that in some field, the original object is being replaced by the proxy. For the hybred scheme that collects with a DummyStream and writes an ImageSegment, it needs to hold onto the originals so they will appear in outPointers, and be replaced." "do nothing"! ! !DataStream methodsFor: 'write and read'! setCurrentReference: refPosn "PRIVATE -- Set currentReference to refPosn. Noop here. Cf. ReferenceStream."! ! !DataStream methodsFor: 'write and read'! tryToPutReference: anObject typeID: typeID "PRIVATE -- If we support references for type typeID, and if anObject already appears in my output stream, then put a reference to the place where anObject already appears. If we support references for typeID but didnÕt already put anObject, then associate the current stream position with anObject in case one wants to nextPut: it again. Return true after putting a reference; false if the object still needs to be put. For DataStream this is trivial. ReferenceStream overrides this." ^ false! ! !DataStream methodsFor: 'write and read' stamp: 'tk 2/20/1999 23:02'! typeIDFor: anObject "Return the typeID for anObject's class. This is where the tangle of objects is clipped to stop everything from going out. Classes can control their instance variables by defining objectToStoreOnDataStream. Any object in blockers is not written out. See ReferenceStream.objectIfBlocked: and DataStream nextPut:. Morphs do not write their owners. See Morph.storeDataOn: Each morph tells itself to 'prepareToBeSaved' before writing out." ^ TypeMap at: anObject class ifAbsent: [9 "instance of any normal class"] "See DataStream initialize. nil=1. true=2. false=3. a SmallInteger=4. (a String was 5). a Symbol=6. a ByteArray=7. an Array=8. other = 9. a Bitmap=11. a Metaclass=12. a Float=14. a Rectangle=15. any instance that can have a short header=16. a String=17 (new format). a WordArray=18."! ! !DataStream methodsFor: 'write and read'! writeArray: anArray "PRIVATE -- Write the contents of an Array." byteStream nextNumber: 4 put: anArray size. self nextPutAll: anArray.! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 21:07'! writeBitmap: aBitmap "PRIVATE -- Write the contents of a Bitmap." aBitmap writeOn: byteStream "Note that this calls (byteStream nextPutAll: aBitmap) which knows enough to put 4-byte quantities on the stream!! Reader must know that size is in long words."! ! !DataStream methodsFor: 'write and read'! writeBoolean: aBoolean "PRIVATE -- Write the contents of a Boolean. This method is now obsolete." byteStream nextPut: (aBoolean ifTrue: [1] ifFalse: [0])! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 21:06'! writeByteArray: aByteArray "PRIVATE -- Write the contents of a ByteArray." byteStream nextNumber: 4 put: aByteArray size. "May have to convert types here..." byteStream nextPutAll: aByteArray.! ! !DataStream methodsFor: 'write and read' stamp: 'tk 3/24/98 10:27'! writeClass: aClass "Write out a DiskProxy for the class. It will look up the class's name in Smalltalk in the new sustem. Never write classes or methodDictionaries as objects. For novel classes, front part of file is a fileIn of the new class." "This method never executed because objectToStoreOnDataStream returns a DiskProxy. See DataStream.nextPut:" ^ self error: 'Write a DiskProxy instead'! ! !DataStream methodsFor: 'write and read'! writeFalse: aFalse "PRIVATE -- Write the contents of a False."! ! !DataStream methodsFor: 'write and read'! writeFloat: aFloat "PRIVATE -- Write the contents of a Float. We support 8-byte Floats here." byteStream nextNumber: 4 put: (aFloat at: 1). byteStream nextNumber: 4 put: (aFloat at: 2). ! ! !DataStream methodsFor: 'write and read'! writeFloatString: aFloat "PRIVATE -- Write the contents of a Float string. This is the slow way to write a Float--via its string repÕn." self writeByteArray: (aFloat printString)! ! !DataStream methodsFor: 'write and read'! writeInstance: anObject "PRIVATE -- Write the contents of an arbitrary instance." ^ anObject storeDataOn: self! ! !DataStream methodsFor: 'write and read'! writeInteger: anInteger "PRIVATE -- Write the contents of a SmallInteger." byteStream nextInt32Put: anInteger "signed!!!!!!!!!!"! ! !DataStream methodsFor: 'write and read'! writeNil: anUndefinedObject "PRIVATE -- Write the contents of an UndefinedObject."! ! !DataStream methodsFor: 'write and read' stamp: 'jm 7/31/97 16:16'! writeRectangle: anObject "Write the contents of a Rectangle. See if it can be a compact Rectangle (type=15). Rectangles with values outside +/- 2047 were stored as normal objects (type=9). 17:22 tk" | ok right bottom top left acc | ok _ true. (right _ anObject right) > 2047 ifTrue: [ok _ false]. right < -2048 ifTrue: [ok _ false]. (bottom _ anObject bottom) > 2047 ifTrue: [ok _ false]. bottom < -2048 ifTrue: [ok _ false]. (top _ anObject top) > 2047 ifTrue: [ok _ false]. top < -2048 ifTrue: [ok _ false]. (left _ anObject left) > 2047 ifTrue: [ok _ false]. left < -2048 ifTrue: [ok _ false]. ok _ ok & left isInteger & right isInteger & top isInteger & bottom isInteger. ok ifFalse: [ byteStream skip: -1; nextPut: 9; skip: 0. "rewrite type to be normal instance" ^ anObject storeDataOn: self]. acc _ ((left bitAnd: 16rFFF) bitShift: 12) + (top bitAnd: 16rFFF). byteStream nextNumber: 3 put: acc. acc _ ((right bitAnd: 16rFFF) bitShift: 12) + (bottom bitAnd: 16rFFF). byteStream nextNumber: 3 put: acc.! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 20:57'! writeString: aString "PRIVATE -- Write the contents of a String." byteStream nextStringPut: aString.! ! !DataStream methodsFor: 'write and read' stamp: 'tk 6/8/1998 21:23'! writeStringOld: aString "PRIVATE -- Write the contents of a String." | length | aString size < 16384 ifTrue: [ (length _ aString size) < 192 ifTrue: [byteStream nextPut: length] ifFalse: [byteStream nextPut: (length // 256 + 192). byteStream nextPut: (length \\ 256)]. aString do: [:char | byteStream nextPut: char asciiValue]] ifFalse: [self writeByteArray: aString]. "takes more space"! ! !DataStream methodsFor: 'write and read'! writeSymbol: aSymbol "PRIVATE -- Write the contents of a Symbol." self writeString: aSymbol! ! !DataStream methodsFor: 'write and read'! writeTrue: aTrue "PRIVATE -- Write the contents of a True."! ! !DataStream methodsFor: 'write and read'! writeUser: anObject "Write the contents of an arbitrary User instance (and its devoted class)." " 7/29/96 tk" "If anObject is an instance of a unique user class, will lie and say it has a generic class" ^ anObject storeDataOn: self! ! !DataStream methodsFor: 'write and read' stamp: 'tk 2/5/2000 21:53'! writeWordLike: aWordArray "Note that we put the class name before the size." self nextPut: aWordArray class name. aWordArray writeOn: byteStream "Note that this calls (byteStream nextPutAll: aBitmap) which knows enough to put 4-byte quantities on the stream!! Reader must know that size is in long words or double-bytes."! ! !DataStream methodsFor: 'other'! atEnd "Answer true if the stream is at the end." ^ byteStream atEnd! ! !DataStream methodsFor: 'other'! byteStream ^ byteStream! ! !DataStream methodsFor: 'other'! close "Close the stream." | bytes | byteStream closed ifFalse: [ bytes _ byteStream position. byteStream close] ifTrue: [bytes _ 'unknown']. ^ bytes! ! !DataStream methodsFor: 'other'! errorWriteReference: anInteger "PRIVATE -- Raise an error because this case of nextPut:Õs perform: shouldn't be called. -- 11/15/92 jhm" self error: 'This should never be called'! ! !DataStream methodsFor: 'other'! flush "Guarantee that any writes to me are actually recorded on disk. -- 11/17/92 jhm" ^ byteStream flush! ! !DataStream methodsFor: 'other'! next: anInteger "Answer an Array of the next anInteger objects in the stream." | array | array _ Array new: anInteger. 1 to: anInteger do: [:i | array at: i put: self next]. ^ array! ! !DataStream methodsFor: 'other' stamp: 'tk 11/24/97 16:31'! nextAndClose "Speedy way to grab one object. Only use when we are inside an object binary file. Do not use for the start of a SmartRefStream mixed code-and-object file." | obj | byteStream peek = 4 ifFalse: ["Try to fix the user's sins..." self inform: 'Should be using fileInObjectAndCode'. byteStream ascii. byteStream fileIn. obj _ SmartRefStream scannedObject. SmartRefStream scannedObject: nil. ^ obj]. obj _ self next. self close. ^ obj! ! !DataStream methodsFor: 'other'! reset "Reset the stream." byteStream reset! ! !DataStream methodsFor: 'other' stamp: 'tk 5/29/97'! rootObject "Return the object at the root of the tree we are filing out. " ^ topCall! ! !DataStream methodsFor: 'other' stamp: 'tk 5/29/97'! rootObject: anObject "Return the object at the root of the tree we are filing out. " topCall _ anObject! ! !DataStream methodsFor: 'other' stamp: '6/9/97 08:03 di'! setStream: aStream "PRIVATE -- Initialization method." aStream binary. basePos _ aStream position. "Remember where we start. Earlier part of file contains a class or method file-in. Allow that to be edited. We don't deal in absolute file locations." byteStream _ aStream.! ! !DataStream methodsFor: 'other' stamp: 'tk 8/18/1998 08:59'! setStream: aStream reading: isReading "PRIVATE -- Initialization method." aStream binary. basePos _ aStream position. "Remember where we start. Earlier part of file contains a class or method file-in. Allow that to be edited. We don't deal in absolute file locations." byteStream _ aStream.! ! !DataStream methodsFor: 'other'! size "Answer the stream's size." ^ byteStream size! ! !DataStream methodsFor: 'other' stamp: 'tk 7/12/1998 13:16'! vacantRef "Answer the magic 32-bit constant we use ***ON DISK*** as a stream 'reference position' to identify a reference that's not yet filled in. This must be a value that won't be used as an ordinary reference. Cf. outputReference: and readReference. -- NOTE: We could use a different type ID for vacant-refs rather than writing object-references with a magic value. (The type ID and value are overwritten by ordinary object-references when weak refs are fullfilled.)" ^ SmallInteger maxVal! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DataStream class instanceVariableNames: ''! !DataStream class methodsFor: 'as yet unclassified'! example "An example and test of DataStream/ReferenceStream. 11/19/92 jhm: Use self testWith:." "DataStream example" "ReferenceStream example" | input sharedPoint | "Construct the test data." input _ Array new: 9. input at: 1 put: nil. input at: 2 put: true. input at: 3 put: (Form extent: 63 @ 50 depth: 8). (input at: 3) fillWithColor: Color lightBlue. input at: 4 put: #(3 3.0 'three'). input at: 5 put: false. input at: 6 put: 1024 @ -2048. input at: 7 put: #x. input at: 8 put: (Array with: (sharedPoint _ 0 @ -30000)). input at: 9 put: sharedPoint. "Write it out, read it back, and return it for inspection." ^ self testWith: input! ! !DataStream class methodsFor: 'as yet unclassified'! exampleWithPictures "DataStream exampleWithPictures" | file result | file _ FileStream fileNamed: 'Test-Picture'. file binary. (DataStream on: file) nextPut: (Form fromUser). file close. file _ FileStream fileNamed: 'Test-Picture'. file binary. result _ (DataStream on: file) next. file close. result display. ^ result! ! !DataStream class methodsFor: 'as yet unclassified'! fileNamed: aString "Here is the way to use DataStream and ReferenceStream: rr _ ReferenceStream fileNamed: 'test.obj'. rr nextPut: . rr close. " | strm | strm _ self on: (FileStream fileNamed: aString). "will be binary" strm byteStream setFileTypeToObject. "Type and Creator not to be text, so can attach correctly to an email msg" ^ strm! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'tk 10/4/2000 10:09'! initialize "TypeMap maps Smalltalk classes to type ID numbers which identify the data stream primitive formats. nextPut: writes these IDs to the data stream. NOTE: Changing these type ID numbers will invalidate all extant data stream files. Adding new ones is OK. Classes named here have special formats in the file. If such a class has a subclass, it will use type 9 and write correctly. It will just be slow. (Later write the class name in the special format, then subclasses can use the type also.) See nextPut:, next, typeIDFor:, & ReferenceStream>>isAReferenceType:" "DataStream initialize" | refTypes t | refTypes _ OrderedCollection new. t _ TypeMap _ Dictionary new: 80. "sparse for fast hashing" t at: UndefinedObject put: 1. refTypes add: 0. t at: True put: 2. refTypes add: 0. t at: False put: 3. refTypes add: 0. t at: SmallInteger put: 4. refTypes add: 0. t at: String put: 5. refTypes add: 1. t at: Symbol put: 6. refTypes add: 1. t at: ByteArray put: 7. refTypes add: 1. t at: Array put: 8. refTypes add: 1. "(type ID 9 is for arbitrary instances of any class, cf. typeIDFor:)" refTypes add: 1. "(type ID 10 is for references, cf. ReferenceStream>>tryToPutReference:)" refTypes add: 0. t at: Bitmap put: 11. refTypes add: 1. t at: Metaclass put: 12. refTypes add: 0. "Type ID 13 is used for HyperSqueak User classes that must be reconstructed." refTypes add: 1. t at: Float put: 14. refTypes add: 1. t at: Rectangle put: 15. refTypes add: 1. "Allow compact Rects." "type ID 16 is an instance with short header. See beginInstance:size:" refTypes add: 1. t at: String put: 17. refTypes add: 1. "new String format, 1 or 4 bytes of length" t at: WordArray put: 18. refTypes add: 1. "bitmap-like" t at: WordArrayForSegment put: 19. refTypes add: 1. "bitmap-like" t at: SoundBuffer put: 20. refTypes add: 1. "And all other word arrays" t at: CompiledMethod put: 21. refTypes add: 1. "special creation method" "t at: put: 22. refTypes add: 0." ReferenceStream refTypes: refTypes. "save it"! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'di 2/15/98 14:03'! new ^ self basicNew! ! !DataStream class methodsFor: 'as yet unclassified'! newFileNamed: aString "Here is the way to use DataStream and ReferenceStream: rr _ ReferenceStream fileNamed: 'test.obj'. rr nextPut: . rr close. " | strm | strm _ self on: (FileStream newFileNamed: aString). "will be binary" strm byteStream setFileTypeToObject. "Type and Creator not to be text, so can attach correctly to an email msg" ^ strm! ! !DataStream class methodsFor: 'as yet unclassified'! oldFileNamed: aString "Here is the way to use DataStream and ReferenceStream: rr _ ReferenceStream oldFileNamed: 'test.obj'. ^ rr nextAndClose. " | strm ff | ff _ FileStream oldFileOrNoneNamed: aString. ff ifNil: [^ nil]. strm _ self on: (ff binary). ^ strm! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'di 6/24/97 00:18'! on: aStream "Open a new DataStream onto a low-level I/O stream." ^ self basicNew setStream: aStream "aStream binary is in setStream:" ! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 08:38'! streamedRepresentationOf: anObject | file | file _ (RWBinaryOrTextStream on: (ByteArray new: 5000)). file binary. (self on: file) nextPut: anObject. ^file contents! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'jm 12/3/97 19:36'! testWith: anObject "As a test of DataStream/ReferenceStream, write out anObject and read it back. 11/19/92 jhm: Set the file type. More informative file name." "DataStream testWith: 'hi'" "ReferenceStream testWith: 'hi'" | file result | file _ FileStream fileNamed: (self name, ' test'). file binary. (self on: file) nextPut: anObject. file close. file _ FileStream fileNamed: (self name, ' test'). file binary. result _ (self on: file) next. file close. ^ result! ! !DataStream class methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 08:33'! unStream: aString ^(self on: ((RWBinaryOrTextStream with: aString) reset; binary)) next! ! Magnitude subclass: #Date instanceVariableNames: 'julianDayNumber ' classVariableNames: 'DaysInMonth FirstDayOfMonth MonthNames SecondsInDay WeekDayNames ' poolDictionaries: '' category: 'Kernel-Magnitudes'! !Date commentStamp: '' prior: 0! Refactored to use Julian Day Numbers internally. Julian Day Numbers are the number of days which have elapsed since 24 November -4713 Gregorian. The algorithm was published in the Communications of the ACM, volume 11, Number 10, October 1968. See also: http://www.magnet.ch/serendipity/hermetic/cal_stud/jdn.htm http://www.cs.ubc.ca/spider/flinn/docs/scham/primitives/time.html ! !Date methodsFor: 'accessing' stamp: 'BP 5/21/2000 19:17'! day "Answer the day of the year represented by the receiver." ^self dayOfYear! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 18:28'! julianDayNumber "Answer the number of days (or part of a day) elapsed since noon GMT on January 1st, 4713 B.C." ^julianDayNumber ! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 18:29'! julianDayNumber: anInteger "Set the number of days (or part of a day) elapsed since noon GMT on January 1st, 4713 B.C." julianDayNumber _ anInteger. ! ! !Date methodsFor: 'accessing' stamp: 'BP 5/21/2000 16:39'! leap "Answer whether the receiver's year is a leap year." ^Date leapYear: self year! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 19:34'! monthIndex "Answer the index of the month in which the receiver falls." ^self asGregorian middle! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 18:05'! monthName "Answer the name of the month in which the receiver falls." ^MonthNames at: self monthIndex! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 18:05'! weekday "Answer the name of the day of the week on which the receiver falls." ^WeekDayNames at: self weekdayIndex! ! !Date methodsFor: 'accessing' stamp: 'rca 6/15/2000 14:51'! weekdayIndex "Monday=1, ... , Sunday=7" ^ (self julianDayNumber rem: 7) + 1! ! !Date methodsFor: 'accessing' stamp: 'BP 5/18/2000 19:34'! year "Answer the year in which the receiver falls." ^self asGregorian last! ! !Date methodsFor: 'arithmetic' stamp: 'BP 5/18/2000 18:46'! addDays: dayCount "Answer a Date that is dayCount days after the receiver." ^self class fromJulianDayNumber: self julianDayNumber + dayCount.! ! !Date methodsFor: 'arithmetic' stamp: 'RAH 5/23/2000 11:32'! subtractDate: aDate "Answer the number of days between the receiver and aDate." ^self julianDayNumber - aDate asJulianDayNumber! ! !Date methodsFor: 'arithmetic' stamp: 'BP 5/18/2000 18:48'! subtractDays: dayCount "Answer a Date that is dayCount days before the receiver." ^self addDays: dayCount negated.! ! !Date methodsFor: 'comparing' stamp: 'RAH 5/23/2000 11:04'! < aDate "Answer whether aDate precedes the date of the receiver." ^julianDayNumber < aDate asJulianDayNumber! ! !Date methodsFor: 'comparing' stamp: 'BP 5/18/2000 18:31'! = aDate "Answer whether aDate is the same day as the receiver." ^julianDayNumber = aDate asJulianDayNumber. ! ! !Date methodsFor: 'comparing' stamp: 'BP 5/18/2000 19:09'! hash "Hash is reimplemented because = is implemented." ^julianDayNumber hash! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/21/2000 19:18'! dayOfMonth "Answer which day of the month is represented by the receiver." ^self asGregorian first! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/21/2000 19:18'! dayOfYear ^self firstDayOfMonth + self dayOfMonth - 1! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/18/2000 18:05'! daysInMonth "Answer the number of days in the month represented by the receiver." ^(DaysInMonth at: self monthIndex) + (self monthIndex = 2 ifTrue: [self leap] ifFalse: [0])! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/21/2000 16:40'! daysInYear "Answer the number of days in the year represented by the receiver." ^Date daysInYear: self year! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/21/2000 17:28'! daysLeftInYear "Answer the number of days in the year after the date of the receiver." ^self daysInYear - self dayOfYear! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/21/2000 17:31'! firstDayOfMonth "Answer the index of the day of the year that is the first day of the receiver's month." ^(FirstDayOfMonth at: self monthIndex) + (self monthIndex > 2 ifTrue: [self leap] ifFalse: [0])! ! !Date methodsFor: 'inquiries' stamp: 'BP 5/18/2000 19:10'! previous: dayName "Answer the previous date whose weekday name is dayName." ^self subtractDays: 7 + self weekdayIndex - (self class dayOfWeek: dayName) \\ 7! ! !Date methodsFor: 'converting' stamp: 'BP 5/18/2000 18:21'! asDate ^self! ! !Date methodsFor: 'converting' stamp: 'BP 5/21/2000 17:32'! asGregorian "Return an array of integers #(dd mm yyyy)" | l n i j dd mm yyyy | l _ self julianDayNumber + 68569. n _ (4 * l) // 146097. l _ l - ( (146097 * n + 3) // 4 ). i _ (4000 * (l + 1) ) // 1461001. l _ l - ( (1461 * i) // 4 ) + 31. j _ (80 *l) // 2447. dd _ l - ( (2447 * j) // 80 ). l _ j // 11. mm _ j + 2 - (12 * l). yyyy _ 100 * (n -49) + i + l. ^Array with: dd with: mm with: yyyy.! ! !Date methodsFor: 'converting' stamp: 'BP 5/21/2000 19:19'! asJulianDayNumber ^self julianDayNumber ! ! !Date methodsFor: 'converting' stamp: 'BP 5/18/2000 18:05'! asSeconds "Answer the seconds between a time on 1 January 1901 and the same time in the receiver's day." ^SecondsInDay * (self subtractDate: (Date newDay: 1 year: 1901))! ! !Date methodsFor: 'converting' stamp: 'BP 5/18/2000 18:05'! month ^ Month fromDate: self! ! !Date methodsFor: 'converting' stamp: 'BP 5/18/2000 18:05'! week ^ Week fromDate: self! ! !Date methodsFor: 'printing' stamp: 'BP 5/18/2000 18:05'! mmddyyyy "Answer the receiver rendered in standard fmt mm/dd/yyyy. Good for avoiding year 2000 bugs. Note that the name here is slightly misleading -- the month and day numbers don't show leading zeros, so that for example feb 1 1996 is 2/1/96" "Date today mmddyyyy" ^ self printFormat: #(2 1 3 $/ 1 1)! ! !Date methodsFor: 'printing' stamp: 'BP 5/18/2000 18:22'! printFormat: formatArray "Answer a String describing the receiver using the argument formatArray." | aStream | aStream _ WriteStream on: (String new: 16). self printOn: aStream format: formatArray. ^aStream contents! ! !Date methodsFor: 'printing' stamp: 'BP 5/18/2000 18:05'! printOn: aStream self printOn: aStream format: #(1 2 3 $ 3 1 )! ! !Date methodsFor: 'printing' stamp: 'rca 6/13/2000 12:31'! printOn: aStream format: formatArray "Print a description of the receiver on aStream using the format denoted the argument, formatArray: #(item item item sep monthfmt yearfmt twoDigits) items: 1=day 2=month 3=year will appear in the order given, separated by sep which is eaither an ascii code or character. monthFmt: 1=09 2=Sep 3=September yearFmt: 1=1996 2=96 digits: (missing or)1=9 2=09. See the examples in printOn: and mmddyy" | gregorian twoDigits element monthFormat | gregorian _ self asGregorian. twoDigits _ formatArray size > 6 and: [ (formatArray at: 7) > 1 ]. 1 to: 3 do: [ :i | element _ formatArray at: i. element = 1 ifTrue: [ twoDigits ifTrue: [ aStream nextPutAll: (gregorian first asString padded: #left to: 2 with: $0) ] ifFalse: [ gregorian first printOn: aStream ] ]. element = 2 ifTrue: [ monthFormat _ formatArray at: 5. monthFormat = 1 ifTrue: [ twoDigits ifTrue: [ aStream nextPutAll: (gregorian middle asString padded: #left to: 2 with: $0) ] ifFalse: [ gregorian middle printOn: aStream ]. ]. monthFormat = 2 ifTrue: [ aStream nextPutAll: ((MonthNames at: gregorian middle) copyFrom: 1 to: 3) ]. monthFormat = 3 ifTrue: [ aStream nextPutAll: (MonthNames at: gregorian middle) ]. ]. element = 3 ifTrue: [ (formatArray at: 6) = 1 ifTrue: [ gregorian last printOn: aStream ] ifFalse: [ aStream nextPutAll: ((gregorian last \\ 100) asString padded: #left to: 2 with: $0) ]. ]. i < 3 ifTrue: [ (formatArray at: 4) ~= 0 ifTrue: [ aStream nextPut: (formatArray at: 4) asCharacter ] ]. ].! ! !Date methodsFor: 'printing' stamp: 'di 9/22/2000 12:47'! storeOn: aStream aStream print: self printString; nextPutAll: ' asDate'! ! !Date methodsFor: 'obsolete' stamp: 'BP 5/21/2000 17:24'! day: dayInteger year: yearInteger self error: 'obsolete' ! ! !Date methodsFor: 'obsolete' stamp: 'BP 5/21/2000 17:30'! firstDayOfMonthIndex: monthIndex "Answer the day of the year (an Integer) that is the first day of my month" self error: 'obsolete'! ! !Date methodsFor: 'obsolete' stamp: 'BP 5/18/2000 18:23'! mmddyy "Please use mmddyyyy instead, so dates in 2000 will be unambiguous" ^ self printFormat: #(2 1 3 $/ 1 2)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Date class instanceVariableNames: ''! !Date class methodsFor: 'class initialization' stamp: 'BP 5/18/2000 18:59'! initialize "Initialize class variables representing the names of the months and days and the number of seconds, days in each month, and first day of each month." MonthNames _ #(January February March April May June July August September October November December ). SecondsInDay _ 24 * 60 * 60. DaysInMonth _ #(31 28 31 30 31 30 31 31 30 31 30 31 ). FirstDayOfMonth _ #(1 32 60 91 121 152 182 213 244 274 305 335 ). WeekDayNames _ #(Monday Tuesday Wednesday Thursday Friday Saturday Sunday ). "Date initialize." ! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/21/2000 17:36'! fromDays: dayCount "Answer an instance of me which is dayCount days after January 1, 1901. Works for negative days before 1901. Works over a huge range, both BC and AD." ^self fromJulianDayNumber: dayCount + 2415386 "Julian Day Number of 1 Jan 1901" ! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/18/2000 18:38'! fromJulianDayNumber: aJulianDayNumber ^self new julianDayNumber: aJulianDayNumber.! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/21/2000 19:26'! fromSeconds: seconds "Answer an instance of me which is 'seconds' seconds after January 1, 1901." ^self fromDays: seconds // SecondsInDay! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/18/2000 18:57'! fromString: aString "Answer an instance of created from a string with format DD.MM.YYYY." ^self readFrom: (ReadStream on: aString). ! ! !Date class methodsFor: 'instance creation' stamp: 'sma 11/11/2000 13:57'! newDay: day month: month year: year "Arguments day, month and year are all integers, except month may be a string. For compatibility, two digit dates in the range 00..49 are from 2000, 50..99 from 1900. Please use ALWAYS four digits year numbers." | monthIndex daysInMonth p q r s | year < 100 ifTrue: [^self newDay: day month: month year: (year < 50 ifTrue: [2000] ifFalse: [1900]) + year]. monthIndex _ month isInteger ifTrue: [month] ifFalse: [self indexOfMonth: month]. monthIndex = 2 ifTrue: [ daysInMonth _ (DaysInMonth at: monthIndex) + (self leapYear: year) ] ifFalse: [ daysInMonth _ DaysInMonth at: monthIndex ]. (day < 1 or: [day > daysInMonth]) ifTrue: [ self error: 'illegal day in month' ]. p _ (monthIndex - 14) quo: 12. q _ year + 4800 + p. r _ monthIndex - 2 - (12 * p). s _ (year + 4900 + p) quo: 100. ^self fromJulianDayNumber: ( (1461 * q) quo: 4 ) + ( (367 * r) quo: 12 ) - ( (3 * s) quo: 4 ) + ( day - 32075 )! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/22/2000 16:46'! newDay: dayCount year: referenceYear "Answer an instance of me which is dayCount days after the beginning of the year referenceYear." | day year daysInYear date | day _ dayCount. year _ referenceYear. [ day > (daysInYear _ self daysInYear: year) ] whileTrue: [ year _ year + 1. day _ day - daysInYear ]. [ day <= 0 ] whileTrue: [ year _ year - 1. day _ day + (self daysInYear: year) ]. date _ self newDay: 1 month: 1 year: year. ^date addDays: (day - 1). ! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/21/2000 16:48'! readFrom: aStream "Read a Date from the stream in any of the forms: (5 April 1982; 5-APR-82) (April 5, 1982) (4/5/82)" | day month | aStream peek isDigit ifTrue: [day _ Integer readFrom: aStream]. [aStream peek isAlphaNumeric] whileFalse: [aStream skip: 1]. aStream peek isLetter ifTrue: "number/name... or name..." [month _ WriteStream on: (String new: 10). [aStream peek isLetter] whileTrue: [month nextPut: aStream next]. month _ month contents. day isNil ifTrue: "name/number..." [[aStream peek isAlphaNumeric] whileFalse: [aStream skip: 1]. day _ Integer readFrom: aStream]] ifFalse: "number/number..." [month _ Date nameOfMonth: day. day _ Integer readFrom: aStream]. [aStream peek isAlphaNumeric] whileFalse: [aStream skip: 1]. ^self newDay: day month: month year: (Integer readFrom: aStream) "Date readFrom: (ReadStream on: '5APR82')" ! ! !Date class methodsFor: 'instance creation' stamp: 'BP 5/21/2000 19:26'! today "Answer an instance of me representing the day and year right now." ^self dateAndTimeNow first! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:05'! dateAndTimeNow "Answer an Array whose first element is Date today and second element is Time now." ^Time dateAndTimeNow! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:59'! dayOfWeek: dayName "Answer the index in a week, 1-7, of the day named dayName. Create an error notification if no such day exists." 1 to: 7 do: [ :index | (WeekDayNames at: index) = dayName ifTrue: [^index] ]. self error: dayName asString , ' is not a day of the week'! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 19:00'! daysInMonth: monthName forYear: yearInteger "Answer the number of days in the month named monthName in the year yearInteger." ^(self newDay: 1 month: monthName year: yearInteger) daysInMonth! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:05'! daysInYear: yearInteger "Answer the number of days in the year, yearInteger." ^365 + (self leapYear: yearInteger)! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:05'! firstWeekdayOfMonth: mn year: yr "Answer the weekday index (Sunday=1, etc) of the first day in the month named mn in the year yr." ^(self newDay: 1 month: mn year: yr) weekdayIndex + 7 \\ 7 + 1! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:05'! indexOfMonth: monthName "Answer the index, 1-12, of the month monthName. Create an error notification if no such month exists." 1 to: 12 do: [ :index | (monthName , '*' match: (MonthNames at: index)) ifTrue: [^index]]. self error: monthName , ' is not a recognized month name'! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 18:05'! leapYear: yearInteger "Answer 1 if the year yearInteger is a leap year; answer 0 if it is not." | adjustedYear | adjustedYear := yearInteger > 0 ifTrue: [yearInteger] ifFalse: [(yearInteger + 1) negated "There is no year 0!!!!"]. (adjustedYear \\ 4 ~= 0 or: [adjustedYear \\ 100 = 0 and: [adjustedYear \\ 400 ~= 0]]) ifTrue: [^0] ifFalse: [^1]! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 19:01'! nameOfDay: dayIndex "Answer a symbol representing the name of the day indexed by dayIndex, 1-7." ^WeekDayNames at: dayIndex! ! !Date class methodsFor: 'general inquiries' stamp: 'BP 5/18/2000 19:01'! nameOfMonth: monthIndex "Answer a String representing the name of the month indexed by monthIndex, 1-12." ^MonthNames at: monthIndex! ! !Date class methodsFor: 'obsolete' stamp: 'BP 5/18/2000 18:05'! absoluteDaysToYear: gregorianYear "Computes the number of days from (or until) January 1 of the year 1 A.D. upto (or since) January 1 of a given year. [Alan Lovejoy]" | days yearDelta quadCenturies centuries quadYears years isInADEra | days := 0. isInADEra := gregorianYear > 0. gregorianYear = 0 ifTrue: [gregorianYear = -1]. "There is no year 0" isInADEra ifTrue: [yearDelta := gregorianYear - 1] ifFalse: [yearDelta := (gregorianYear + 1) negated]. quadCenturies := yearDelta // 400. yearDelta := yearDelta rem: 400. centuries := yearDelta // 100. yearDelta := yearDelta rem: 100. quadYears := yearDelta // 4. years := yearDelta rem: 4. days := (quadCenturies * 146097 "days per quad century") + (centuries * 36524 "days per century") + (quadYears * 1461 "days per quad year") + (years * 365). isInADEra ifFalse: [days := days + 366. "1 B.C. is a leap year" days := days negated]. ^ days! ! !Date class methodsFor: 'obsolete' stamp: 'BP 5/18/2000 18:05'! yearAndDaysFromDays: days into: aTwoArgBlock "Compute the Gregorian year, and the day of the year, from the number of days since (or until) January 1 of the year 1 A.D. Return the values in a block. [Alan Lovejoy]" | quadCentury year dayInYear isInADEra century quadYear | dayInYear := days. isInADEra := days >= 0. isInADEra ifTrue: [year := 0] ifFalse: [dayInYear := dayInYear abs. dayInYear >= 366 "days per leap year" ifTrue: [year := 1. dayInYear := dayInYear - 366] "Subtract the year 1 B.C." ifFalse: [year := 0]]. quadCentury := dayInYear // 146097 "days per quad century". dayInYear := dayInYear \\ 146097 "days per quad century". century := dayInYear // 36524 "days per century". dayInYear := dayInYear \\ 36524 "days per century". quadYear := dayInYear // 1461 "days per quad year". dayInYear := dayInYear \\ 1461 "days per quad year". dayInYear >= 365 "days per standard year" ifTrue: ["e.g., 1 AD or 2 BC" dayInYear := dayInYear - 365 "days per standard year". year := year + 1. dayInYear >= 365 "days per standard year" ifTrue: ["e.g., 2 AD or 3 BC" dayInYear := dayInYear - 365 "days per standard year". year := year + 1. dayInYear >= 365 "days per standard year" ifTrue: ["e.g., 3 AD or 4 BC" dayInYear := dayInYear - 365 "days per standard year". year := year + 1. dayInYear >= 366 "days per leap year" ifTrue: [ "e.g., 4 AD or 5 BC (although this won't occur in the AD case)" dayInYear := dayInYear - 366 "days per leap year". year := year + 1]]]]. year := year + (quadCentury * 400) + (century * 100) + (quadYear * 4) + 1. isInADEra ifFalse: [ year := year negated. dayInYear > 0 ifTrue: [ (Date leapYear: year) = 1 ifTrue: [dayInYear := 366 "days per leap year" - dayInYear] ifFalse: [dayInYear := 365 "days per standard year" - dayInYear]]]. ^ aTwoArgBlock value: year value: dayInYear+1 "the way Dates do it"! ! CodeHolder subclass: #Debugger instanceVariableNames: 'interruptedProcess interruptedController contextStack contextStackTop contextStackIndex contextStackList receiverInspector contextVariablesInspector externalInterrupt proceedValue selectingPC sourceMap tempNames savedCursor isolationHead failedProject errorWasInUIProcess ' classVariableNames: 'ContextStackKeystrokes ErrorRecursion ' poolDictionaries: '' category: 'Tools-Debugger'! !Debugger commentStamp: '' prior: 0! I represent the machine state at the time of an interrupted process. I also represent a query path into the state of the process. The debugger is typically viewed through a window that views the stack of suspended contexts, the code for, and execution point in, the currently selected message, and inspectors on both the receiver of the currently selected message, and the variables in the current context. Special note on recursive errors: Some errors affect Squeak's ability to present a debugger. This is normally an unrecoverable situation. However, if such an error occurs in an isolation layer, Squeak will attempt to exit from the isolation layer and then present a debugger. Here is the chain of events in such a recovery. * A recursive error is detected. * The current project is queried for an isolationHead * Changes in the isolationHead are revoked * The parent project of isolated project is returned to * The debugger is opened there and execution resumes. If the user closes that debugger, execution continues in the outer project and layer. If, after repairing some damage, the user proceeds from the debugger, then the isolationHead is re-invoked, the failed project is re-entered, and execution resumes in that world. ! !Debugger methodsFor: 'initialize' stamp: 'RAA 1/30/2001 13:05'! buildMVCDebuggerViewLabel: aString minSize: aPoint "Build an MVC debugger view around the receiver, and return the StandardSystemView thus created." | topView stackListView stackCodeView rcvrVarView rcvrValView ctxtVarView ctxtValView deltaY underPane annotationPane buttonsView oldContextStackIndex | oldContextStackIndex _ contextStackIndex. self expandStack. "Sets contextStackIndex to zero." contextStackIndex _ oldContextStackIndex. topView _ StandardSystemView new model: self. topView borderWidth: 1. stackListView _ PluggableListView on: self list: #contextStackList selected: #contextStackIndex changeSelected: #toggleContextStackIndex: menu: #contextStackMenu:shifted: keystroke: #contextStackKey:from:. stackListView menuTitleSelector: #messageListSelectorTitle. stackListView window: (0 @ 0 extent: 150 @ 50). topView addSubView: stackListView. deltaY _ 0. self wantsAnnotationPane ifTrue: [annotationPane _ PluggableTextView on: self text: #annotation accept: nil readSelection: nil menu: nil. annotationPane window: (0@0 extent: 150@self optionalAnnotationHeight). topView addSubView: annotationPane below: stackListView. deltaY _ deltaY + self optionalAnnotationHeight. underPane _ annotationPane] ifFalse: [underPane _ stackListView]. self wantsOptionalButtons ifTrue: [buttonsView _ self buildMVCOptionalButtonsButtonsView. buttonsView borderWidth: 1. topView addSubView: buttonsView below: underPane. underPane _ buttonsView. deltaY _ deltaY + self optionalButtonHeight]. stackCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. stackCodeView window: (0 @ 0 extent: 150 @ (75 - deltaY)). topView addSubView: stackCodeView below: underPane. rcvrVarView _ PluggableListView on: self receiverInspector list: #fieldList selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu: keystroke: #inspectorKey:from:. rcvrVarView window: (0 @ 0 extent: 25 @ (50 - deltaY)). topView addSubView: rcvrVarView below: stackCodeView. rcvrValView _ PluggableTextView on: self receiverInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. rcvrValView window: (0 @ 0 extent: 50 @ (50 - deltaY)). topView addSubView: rcvrValView toRightOf: rcvrVarView. ctxtVarView _ PluggableListView on: self contextVariablesInspector list: #fieldList selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu: keystroke: #inspectorKey:from:. ctxtVarView window: (0 @ 0 extent: 25 @ (50 - deltaY)). topView addSubView: ctxtVarView toRightOf: rcvrValView. ctxtValView _ PluggableTextView on: self contextVariablesInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. ctxtValView window: (0 @ 0 extent: 50 @ (50 - deltaY)). topView addSubView: ctxtValView toRightOf: ctxtVarView. topView label: aString. topView minimumSize: aPoint. ^ topView! ! !Debugger methodsFor: 'initialize' stamp: 'sw 12/28/1999 13:12'! buildMVCNotifierButtonView | aView bHeight priorButton buttonView | aView _ View new model: self. bHeight _ self notifierButtonHeight. aView window: (0@0 extent: 350@bHeight). priorButton _ nil. self preDebugButtonQuads do: [:aSpec | buttonView _ PluggableButtonView on: self getState: nil action: aSpec second.. buttonView label: aSpec first; insideColor: (Color perform: aSpec third) muchLighter lighter; borderWidthLeft: 1 right: 1 top: 0 bottom: 0; window: (0@0 extent: 117@bHeight). priorButton ifNil: [aView addSubView: buttonView] ifNotNil: [aView addSubView: buttonView toRightOf: priorButton]. priorButton _ buttonView]. ^ aView! ! !Debugger methodsFor: 'initialize' stamp: 'rhi 12/20/2000 16:50'! buildMVCNotifierViewLabel: aString message: messageString minSize: aPoint | topView notifyView buttonView x y bHeight | self expandStack. topView _ StandardSystemView new model: self. topView borderWidth: 1. buttonView _ self buildMVCNotifierButtonView. topView addSubView: buttonView. notifyView _ PluggableListView on: self list: #contextStackList selected: #contextStackIndex changeSelected: #debugAt: menu: nil keystroke: nil. x _ 350 max: (aPoint x). y _ ((4 * 15) + 16) max: (aPoint y - 16 - self optionalButtonHeight). bHeight _ self optionalButtonHeight. y _ y - bHeight. notifyView window: (0@0 extent: x@y). topView addSubView: notifyView below: buttonView; label: aString; minimumSize: aPoint. ^ topView! ! !Debugger methodsFor: 'initialize' stamp: 'sbw 12/28/1999 11:38'! buildMVCOptionalButtonsButtonsView | aView bHeight offset aButtonView wid pairs windowWidth previousView | aView _ View new model: self. bHeight _ self optionalButtonHeight. windowWidth _ 150. aView window: (0@0 extent: windowWidth@bHeight). offset _ 0. pairs _ self optionalButtonPairs. previousView _ nil. pairs do: [:pair | aButtonView _ PluggableButtonView on: self getState: nil action: pair last. pair last = pairs last last ifTrue: [wid _ windowWidth - offset] ifFalse: [aButtonView borderWidthLeft: 0 right: 1 top: 0 bottom: 0. wid _ windowWidth // (pairs size)]. aButtonView label: pair first asParagraph; insideColor: Color red muchLighter lighter; window: (offset@0 extent: wid@bHeight). offset _ offset + wid. pair last = pairs first last ifTrue: [aView addSubView: aButtonView] ifFalse: [aView addSubView: aButtonView toRightOf: previousView]. previousView _ aButtonView]. ^ aView! ! !Debugger methodsFor: 'initialize' stamp: 'sbw 12/22/1999 16:21'! buttonRowForPreDebugWindow: aDebugWindow | aRow aButton | aRow _ AlignmentMorph newRow hResizing: #spaceFill. aRow beSticky. aButton _ SimpleButtonMorph new target: aDebugWindow. aButton color: Color transparent; borderWidth: 1. aRow addMorphBack: AlignmentMorph newVariableTransparentSpacer. self preDebugButtonQuads do: [:quad | aButton _ aButton fullCopy. aButton actionSelector: quad second. aButton label: quad first. aButton submorphs first color: (Color colorFrom: quad third). aButton setBalloonText: quad fourth. aRow addMorphBack: aButton. aRow addMorphBack: AlignmentMorph newVariableTransparentSpacer]. ^ aRow! ! !Debugger methodsFor: 'initialize' stamp: 'kfr 10/4/2000 22:13'! debugAt: anInteger self toggleContextStackIndex: anInteger. ^ self debug.! ! !Debugger methodsFor: 'initialize' stamp: 'sw 12/28/1999 13:07'! notifierButtonHeight ^ 18! ! !Debugger methodsFor: 'initialize' stamp: 'RAA 1/30/2001 13:20'! openFullMorphicLabel: labelString | window aListMorph oldContextStackIndex | oldContextStackIndex _ contextStackIndex. self expandStack. "Sets contextStackIndex to zero." window _ (SystemWindow labelled: labelString) model: self. aListMorph _ PluggableListMorph on: self list: #contextStackList selected: #contextStackIndex changeSelected: #toggleContextStackIndex: menu: #contextStackMenu:shifted: keystroke: #contextStackKey:from:. aListMorph menuTitleSelector: #messageListSelectorTitle. window addMorph: aListMorph frame: (0@0 corner: 1@0.3). self addLowerPanesTo: window at: (0@0.3 corner: 1@0.7) with: nil. window addMorph: ( PluggableListMorph new doubleClickSelector: #inspectSelection; on: self receiverInspector list: #fieldList selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu: keystroke: #inspectorKey:from:) frame: (0@0.7 corner: 0.2@1). window addMorph: (PluggableTextMorph on: self receiverInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:) frame: (0.2@0.7 corner: 0.5@1). window addMorph: ( PluggableListMorph new doubleClickSelector: #inspectSelection; on: self contextVariablesInspector list: #fieldList selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu: keystroke: #inspectorKey:from:) frame: (0.5@0.7 corner: 0.7@1). window addMorph: (PluggableTextMorph on: self contextVariablesInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:) frame: (0.7@0.7 corner: 1@1). window openInWorld. self toggleContextStackIndex: oldContextStackIndex. ^ window ! ! !Debugger methodsFor: 'initialize' stamp: 'RAA 8/8/2000 10:44'! openFullNoSuspendLabel: aString "Create and schedule a full debugger with the given label. Do not terminate the current active process." | topView | Smalltalk isMorphic ifTrue: [ self openFullMorphicLabel: aString. errorWasInUIProcess _ CurrentProjectRefactoring newProcessIfUI: interruptedProcess. ^self ]. topView _ self buildMVCDebuggerViewLabel: aString minSize: 300@200. topView controller openNoTerminate. ^ topView ! ! !Debugger methodsFor: 'initialize' stamp: 'RAA 8/8/2000 10:45'! openNotifierContents: msgString label: label "Create and schedule a notifier view with the given label and message. A notifier view shows just the message or the first several lines of the stack, with a menu that allows the user to open a full debugger if so desired." "NOTE: When this method returns, a new process has been scheduled to run the windows, and thus this notifier, but the previous active porcess has not been suspended. The sender will do this." | msg topView p | Sensor flushKeyboard. savedCursor _ Sensor currentCursor. Sensor currentCursor: Cursor normal. msg _ msgString. (label beginsWith: 'Space is low') ifTrue: [msg _ self lowSpaceChoices, msgString]. isolationHead ifNotNil: ["We have already revoked the isolation layer -- now jump to the parent project." msg _ self isolationRecoveryAdvice, msgString. failedProject _ Project current. isolationHead parent enterForEmergencyRecovery]. Smalltalk isMorphic ifTrue: [ self buildMorphicNotifierLabelled: label message: msg. errorWasInUIProcess _ CurrentProjectRefactoring newProcessIfUI: interruptedProcess. ^self ]. Display fullScreen. topView _ self buildMVCNotifierViewLabel: label message: msg minSize: 350@((14 * 5) + 16 + self optionalButtonHeight). ScheduledControllers activeController ifNil: [p _ Display boundingBox center] ifNotNil: [p _ ScheduledControllers activeController view displayBox center]. topView controller openNoTerminateDisplayAt: (p max: (200@60)). ^ topView! ! !Debugger methodsFor: 'initialize' stamp: 'sbw 12/23/1999 09:50'! optionalAnnotationHeight ^ 10! ! !Debugger methodsFor: 'initialize' stamp: 'sbw 12/23/1999 08:31'! optionalButtonHeight ^ 10! ! !Debugger methodsFor: 'initialize' stamp: 'sbw 12/23/1999 09:28'! optionalButtonPairs ^ #(('Proceed' proceed) ('Restart' restart) ('Send' send) ('Step' doStep) ('Full Stack' fullStack) ('Where' where) ('Browse' browseMethodFull))! ! !Debugger methodsFor: 'initialize' stamp: 'RAA 1/17/2001 14:26'! optionalButtonRow "Answer a button pane affording the user one-touch access to certain functions; the pane is given the formal name 'buttonPane' by which it can be retrieved by code wishing to send messages to widgets residing on the pane" | aRow aButton | aRow _ AlignmentMorph newRow beSticky. aRow setNameTo: 'buttonPane'. aRow clipSubmorphs: true. aButton _ SimpleButtonMorph new target: self. aButton color: Color lightRed; borderWidth: 1; borderColor: Color red darker. aRow addTransparentSpacerOfSize: (5@0). self optionalButtonPairs do: [:pair | aButton _ PluggableButtonMorph on: self getState: nil action: pair second. aButton hResizing: #spaceFill; vResizing: #spaceFill; useRoundedCorners; label: pair first asString; askBeforeChanging: true; onColor: Color transparent offColor: Color transparent. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0)]. ^ aRow! ! !Debugger methodsFor: 'initialize' stamp: 'sw 8/14/2000 13:20'! preDebugButtonQuads ^ #(('Proceed' proceed blue 'continue execution' ) ('Abandon' abandon black 'abandon this execution by closing this window') ('Debug' debug red 'bring up a debugger'))! ! !Debugger methodsFor: 'initialize' stamp: 'sw 8/8/2000 13:13'! preDebugNotifierContentsFrom: messageString ^ Preferences eToyFriendly ifFalse: [messageString] ifTrue: ['An error has occurred; you should probably just hit ''abandon''. Sorry!!'] ! ! !Debugger methodsFor: 'initialize' stamp: 'jm 8/20/1998 18:31'! release self windowIsClosing. super release. ! ! !Debugger methodsFor: 'initialize' stamp: 'sw 1/24/2001 21:22'! wantsOptionalButtons "The debugger benefits so majorly from the optional buttons that we put them up regardless of the global setting. Some traditionalists will want to change this method manually!!" ^ true! ! !Debugger methodsFor: 'initialize' stamp: 'jm 8/20/1998 18:30'! windowIsClosing "My window is being closed; clean up. Restart the low space watcher." interruptedProcess == nil ifTrue: [^ self]. interruptedProcess terminate. interruptedProcess _ nil. interruptedController _ nil. contextStack _ nil. contextStackTop _ nil. receiverInspector _ nil. contextVariablesInspector _ nil. Smalltalk installLowSpaceWatcher. "restart low space handler" ! ! !Debugger methodsFor: 'accessing' stamp: 'di 10/9/1998 17:15'! contents "Depending on the current selection, different information is retrieved. Answer a string description of that information. This information is the method in the currently selected context." contents == nil ifTrue: [^ String new]. ^ contents copy! ! !Debugger methodsFor: 'accessing' stamp: 'di 1/17/2001 16:28'! contents: aText notifying: aController "The retrieved information has changed and its source must now be updated. In this case, the retrieved information is the method of the selected context." | selector classOfMethod category method priorMethod parseNode | contextStackIndex = 0 ifTrue: [^ false]. (self selectedContext isKindOf: MethodContext) ifFalse: [(self confirm: 'I will have to revert to the method from which this block originated. Is that OK?') ifTrue: [self resetContext: self selectedContext home] ifFalse: [^ false]]. classOfMethod _ self selectedClass. category _ self selectedMessageCategoryName. Cursor execute showWhile: [method _ classOfMethod compile: aText notifying: aController trailer: #(0 0 0 0) ifFail: [^ false] elseSetSelectorAndNode: [:sel :methodNode | selector _ sel. selector == self selectedMessageName ifFalse: [self notify: 'can''t change selector'. ^ false]. priorMethod _ (classOfMethod includesSelector: selector) ifTrue: [classOfMethod compiledMethodAt: selector] ifFalse: [nil]. sourceMap _ methodNode sourceMap. tempNames _ methodNode tempNames. parseNode _ methodNode]. method cacheTempNames: tempNames]. category isNil ifFalse: "Skip this for DoIts" [method putSource: aText fromParseNode: parseNode class: classOfMethod category: category inFile: 2 priorMethod: priorMethod. classOfMethod organization classify: selector under: category]. contents _ aText copy. self selectedContext restartWith: method. contextVariablesInspector object: nil. self resetContext: self selectedContext. ^true! ! !Debugger methodsFor: 'accessing'! contextVariablesInspector "Answer the instance of Inspector that is providing a view of the variables of the selected context." ^contextVariablesInspector! ! !Debugger methodsFor: 'accessing' stamp: 'tk 4/16/1998 12:16'! doNothing: newText "Notifier window can't accept text"! ! !Debugger methodsFor: 'accessing'! interruptedContext "Answer the suspended context of the interrupted process." ^contextStackTop! ! !Debugger methodsFor: 'accessing'! interruptedProcess "Answer the interrupted process." ^interruptedProcess! ! !Debugger methodsFor: 'accessing' stamp: 'tk 4/16/1998 15:47'! isNotifier "Return true if this debugger has not been expanded into a full sized window" ^ receiverInspector == nil! ! !Debugger methodsFor: 'accessing'! proceedValue "Answer the value to return to the selected context when the interrupted process proceeds." ^proceedValue! ! !Debugger methodsFor: 'accessing'! proceedValue: anObject "Set the value to be returned to the selected context when the interrupted process proceeds." proceedValue _ anObject! ! !Debugger methodsFor: 'accessing'! receiver "Answer the receiver of the selected context, if any. Answer nil otherwise." contextStackIndex = 0 ifTrue: [^nil] ifFalse: [^self selectedContext receiver]! ! !Debugger methodsFor: 'accessing'! receiverInspector "Answer the instance of Inspector that is providing a view of the variables of the selected context's receiver." ^receiverInspector! ! !Debugger methodsFor: 'notifier menu' stamp: 'RAA 1/30/2001 14:36'! debug "Open a full DebuggerView." | topView | topView _ self topView. topView model: nil. "so close won't release me." Smalltalk isMorphic ifTrue: [self breakDependents. self openFullMorphicLabel: topView label. ^ topView delete]. topView controller controlTerminate. topView deEmphasize; erase. "a few hacks to get the scroll selection artifacts out when we got here by clicking in the list" topView subViewWantingControl ifNotNil: [ topView subViewWantingControl controller controlTerminate ]. topView controller status: #closed. self openFullNoSuspendLabel: topView label. topView controller closeAndUnscheduleNoErase. Processor terminateActive. ! ! !Debugger methodsFor: 'context stack (message list)'! contextStackIndex "Answer the index of the selected context." ^contextStackIndex! ! !Debugger methodsFor: 'context stack (message list)'! contextStackList "Answer the array of contexts." ^contextStackList! ! !Debugger methodsFor: 'context stack (message list)' stamp: 'tk 4/17/1998 18:05'! expandStack "A Notifier is being turned into a full debugger. Show a substantial amount of stack in the context pane." self newStack: (contextStackTop stackOfSize: 20). contextStackIndex _ 0. receiverInspector _ Inspector inspect: nil. contextVariablesInspector _ ContextVariablesInspector inspect: nil. proceedValue _ nil! ! !Debugger methodsFor: 'context stack (message list)'! fullyExpandStack "Expand the stack to include all of it, rather than the first four or five contexts." self okToChange ifFalse: [^ self]. self newStack: contextStackTop stack. self changed: #contextStackList! ! !Debugger methodsFor: 'context stack (message list)'! messageListIndex "Answer the index of the currently selected context." ^contextStackIndex! ! !Debugger methodsFor: 'context stack (message list)' stamp: 'sw 11/6/1999 22:58'! selectedMessage "Answer the source code of the currently selected context." contents _ [self selectedContext sourceCode] ifError: [ :err :rcvr | 'ERROR "',(err reject: [ :each | each == $"]),'"' ]. Preferences browseWithPrettyPrint ifTrue: [contents _ self selectedClass compilerClass new format: contents in: self selectedClass notifying: nil decorated: Preferences colorWhenPrettyPrinting]. ^ contents _ contents asText makeSelectorBoldIn: self selectedClass! ! !Debugger methodsFor: 'context stack (message list)'! selectedMessageName "Answer the message selector of the currently selected context." ^self selectedContext selector! ! !Debugger methodsFor: 'context stack (message list)'! toggleContextStackIndex: anInteger "If anInteger is the same as the index of the selected context, deselect it. Otherwise, the context whose index is anInteger becomes the selected context." self contextStackIndex: (contextStackIndex = anInteger ifTrue: [0] ifFalse: [anInteger]) oldContextWas: (contextStackIndex = 0 ifTrue: [nil] ifFalse: [contextStack at: contextStackIndex])! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 12/28/1999 13:04'! abandon "abandon the debugger from its pre-debug notifier" self abandon: self topView! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 12/28/1999 13:05'! abandon: aTopView "abandon the notifier represented by aTopView" aTopView controller close! ! !Debugger methodsFor: 'context stack menu' stamp: 'wod 5/15/1998 00:24'! browseMessages "Present a menu of all messages sent by the currently selected message. Open a message set browser of all implementors of the message chosen. Do nothing if no message is chosen." contextStackIndex = 0 ifTrue: [^ self]. super browseMessages.! ! !Debugger methodsFor: 'context stack menu' stamp: 'wod 5/15/1998 00:23'! browseSendersOfMessages "Present a menu of the currently selected message, as well as all messages sent by it. Open a message set browser of all implementors of the message chosen." contextStackIndex = 0 ifTrue: [^ self]. super browseSendersOfMessages! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 10/12/1999 17:41'! browseVersions "Create and schedule a message set browser on all versions of the currently selected message selector." | class selector | class _ self selectedClassOrMetaClass. selector _ self selectedMessageName. VersionsBrowser browseVersionsOf: (class compiledMethodAt: selector) class: self selectedClass meta: self selectedClass isMeta category: self selectedMessageCategoryName selector: selector! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/6/98 23:00'! buildMessageBrowser "Create and schedule a message browser on the current method." contextStackIndex = 0 ifTrue: [^ self]. ^ Browser openMessageBrowserForClass: self selectedClassOrMetaClass selector: self selectedMessageName editString: nil! ! !Debugger methodsFor: 'context stack menu' stamp: 'rhi 12/20/2000 16:56'! buildMorphicNotifierLabelled: label message: messageString | notifyPane window contentTop extentToUse | self expandStack. window _ (PreDebugWindow labelled: label) model: self. contentTop _ 0.2. extentToUse _ 450 @ 156. "nice and wide to show plenty of the error msg" window addMorph: (self buttonRowForPreDebugWindow: window) frame: (0@0 corner: 1 @ contentTop). Preferences eToyFriendly ifFalse: [notifyPane _ PluggableListMorph on: self list: #contextStackList selected: #contextStackIndex changeSelected: #debugAt: menu: nil keystroke: nil] ifTrue: [notifyPane _ PluggableTextMorph on: self text: nil accept: nil readSelection: nil menu: #debugProceedMenu:. notifyPane editString: (self preDebugNotifierContentsFrom: messageString); askBeforeDiscardingEdits: false]. window addMorph: notifyPane frame: (0@contentTop corner: 1@1). "window deleteCloseBox. chickened out by commenting the above line out, sw 8/14/2000 12:54" window setBalloonTextForCloseBox. ^ window openInWorldExtent: extentToUse! ! !Debugger methodsFor: 'context stack menu'! close: aScheduledController "The argument is a controller on a view of the receiver. That view is closed." aScheduledController close ! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/18/1998 09:24'! contextStackKey: aChar from: view "Respond to a keystroke in the context list" | selector | selector _ ContextStackKeystrokes at: aChar ifAbsent: [nil]. selector ifNil: [self messageListKey: aChar from: view] ifNotNil: [self perform: selector]! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 1/4/2001 10:23'! contextStackMenu: aMenu shifted: shifted "Set up the menu appropriately for the context-stack-list, either shifted or unshifted as per the parameter provided" ^ shifted ifFalse: [aMenu labels: 'fullStack (f) restart (r) proceed (p) step (t) send (e) where (w) peel to first like this senders of... (n) implementors of... (m) inheritance (i) versions (v) inst var refs... inst var defs... class var refs... class variables class refs (N) browse full (b) file out mail out bug report more...' lines: #(7 11 13 16 19) selections: #(fullStack restart proceed doStep send where peelToFirst browseSendersOfMessages browseMessages methodHierarchy browseVersions browseInstVarRefs browseInstVarDefs browseClassVarRefs browseClassVariables browseClassRefs browseMethodFull fileOutMessage mailOutBugReport shiftedYellowButtonActivity)] ifTrue: [aMenu labels: 'browse class hierarchy browse class browse method (O) implementors of sent messages change sets with this method inspect instances inspect subinstances revert to previous version remove from current change set revert & remove from changes more...' lines: #(5 7 10) selections: #(classHierarchy browseClass openSingleMessageBrowser browseAllMessages findMethodInChangeSets inspectInstances inspectSubInstances revertToPreviousVersion removeFromCurrentChanges revertAndForget unshiftedYellowButtonActivity)] ! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 8/6/97 13:45'! currentCompiledMethod ^ self selectedClassOrMetaClass compiledMethodAt: self selectedMessageName! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/16/1998 12:19'! debugProceedMenu: aMenu ^ aMenu labels: 'proceed debug' lines: #() selections: #(proceed debug ) ! ! !Debugger methodsFor: 'context stack menu' stamp: 'di 1/14/1999 09:18'! doStep "Send the selected message in the accessed method, and regain control after the invoked method returns." | currentContext oldMethod | self okToChange ifFalse: [^ self]. self checkContextSelection. externalInterrupt ifFalse: [contextStackTop push: proceedValue]. externalInterrupt _ true. "simulation leaves same state as interrupting" currentContext _ self selectedContext. self contextStackIndex > 1 ifTrue: [currentContext completeCallee: contextStackTop. self resetContext: currentContext] ifFalse: [currentContext stepToSendOrReturn. currentContext willReturn ifTrue: [oldMethod _ currentContext method. currentContext _ currentContext step. currentContext stepToSendOrReturn. self resetContext: currentContext. oldMethod == currentContext method "didnt used to update pc here" ifTrue: [self changed: #contentsSelection]] ifFalse: [currentContext completeCallee: currentContext step. self changed: #contentsSelection. self updateInspectors]]! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/15/1998 16:55'! down "move down the context stack to the previous (enclosing) context" self toggleContextStackIndex: contextStackIndex+1! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/17/1998 18:06'! fullStack "Change from displaying the minimal stack to a full one." self contextStackList size > 20 "Already expanded" ifTrue: [self changed: #flash] ifFalse: [self contextStackIndex = 0 ifFalse: [ self toggleContextStackIndex: self contextStackIndex]. self fullyExpandStack]! ! !Debugger methodsFor: 'context stack menu' stamp: 'dvf 11/16/2000 03:12'! mailOutBugReport "Compose a useful bug report showing the state of the process as well as vital image statistics as suggested by Chris Norton - 'Squeak could pre-fill the bug form with lots of vital, but oft-repeated, information like what is the image version, last update number, VM version, platform, available RAM, author...' and address it to the list with the appropriate subject prefix." | subjectPrefix messageStrm | (Smalltalk includesKey: #Celeste) ifFalse: [^ self notify: 'no mail reader present']. subjectPrefix _ '[BUG]'. Cursor write showWhile: ["Prepare the message" messageStrm _ WriteStream on: (String new: 30). messageStrm nextPutAll: 'From: '; nextPutAll: Celeste userName; cr; nextPutAll: 'To: squeak@cs.uiuc.edu'; cr; nextPutAll: 'Subject: '; nextPutAll: subjectPrefix; nextPutAll: self interruptedContext printString; cr;cr; nextPutAll: 'here insert explanation of what you were doing, suspect changes youd made and so forth.';cr;cr; nextPutAll: 'Image version: '; nextPutAll: Smalltalk systemInformationString ; cr;cr; nextPutAll: 'VM version: '; nextPutAll: Smalltalk vmVersion, String cr, 'for: ', Smalltalk platformName ; cr;cr; nextPutAll: 'Receiver: '; nextPutAll: receiverInspector object printString; cr;cr; nextPutAll: 'Instance variables: ';cr; nextPutAll: receiverInspector object longPrintString; cr; nextPutAll: 'Method (temp) variables: ';cr; nextPutAll: contextVariablesInspector object tempsAndValues; cr; nextPutAll: 'Stack: '; cr. self contextStackList do: [:e | messageStrm nextPutAll: e; cr]. CelesteComposition openForCeleste: Celeste current initialText: messageStrm contents]. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 7/18/1999 23:01'! peelToFirst "Peel the stack back to the second occurance of the currently selected message. Very useful for an infinite recursion. Gets back to the second call so you can see one complete recursion cycle, and how it was called at the beginning. Also frees a lot of space!!" | upperGuy meth second | contextStackIndex = 0 ifTrue: [^ self beep]. "self okToChange ifFalse: [^ self]." upperGuy _ contextStack at: contextStackIndex. meth _ upperGuy method. contextStackIndex+1 to: contextStack size do: [:ind | (contextStack at: ind) method == meth ifTrue: [ second _ upperGuy. upperGuy _ contextStack at: ind]]. second ifNil: [second _ upperGuy]. self resetContext: second. interruptedProcess popTo: self selectedContext.! ! !Debugger methodsFor: 'context stack menu' stamp: 'di 5/5/1998 00:07'! proceed "Proceed execution of the receiver's model, starting after the expression at which an interruption occurred." Smalltalk okayToProceedEvenIfSpaceIsLow ifTrue: [ self proceed: self topView]. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'di 5/5/1998 00:07'! proceed: aTopView "Proceed from the interrupted state of the currently selected context. The argument is the topView of the receiver. That view is closed." self okToChange ifFalse: [^ self]. self checkContextSelection. contextStackIndex > 1 | externalInterrupt not ifTrue: [self selectedContext push: proceedValue]. self resumeProcess: aTopView! ! !Debugger methodsFor: 'context stack menu' stamp: 'di 5/5/1998 00:08'! restart "Proceed execution of the receiver's model, starting at the beginning of the currently selected method." self restart: self topView. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'di 5/5/1998 00:08'! restart: aTopView "Proceed from the initial state of the currently selected context. The argument is a controller on a view of the receiver. That view is closed." self okToChange ifFalse: [^ self]. self checkContextSelection. (self selectedContext isKindOf: MethodContext) ifFalse: [(self confirm: 'I will have to revert to the method from which this block originated. Is that OK?') ifTrue: [self resetContext: self selectedContext home] ifFalse: [^self]]. self selectedContext restart. self resumeProcess: aTopView! ! !Debugger methodsFor: 'context stack menu'! selectPC "Toggle the flag telling whether to automatically select the expression currently being executed by the selected context." selectingPC _ selectingPC not! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/16/1998 11:36'! send "Send the selected message in the accessed method, and take control in the method invoked to allow further step or send." | currentContext | "Sensor leftShiftDown ifTrue: [self halt]." self okToChange ifFalse: [^ self]. self checkContextSelection. externalInterrupt ifFalse: [contextStackTop push: proceedValue]. externalInterrupt _ true. "simulation leaves same state as interrupting" currentContext _ self selectedContext. currentContext stepToSendOrReturn. self contextStackIndex > 1 | currentContext willReturn ifTrue: [self changed: #notChanged] ifFalse: [currentContext _ currentContext step. currentContext stepToSendOrReturn. self resetContext: currentContext]! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/15/1998 16:55'! up "move up the context stack to the next (enclosed) context" contextStackIndex > 1 ifTrue: [self toggleContextStackIndex: contextStackIndex-1]! ! !Debugger methodsFor: 'context stack menu' stamp: 'tk 4/15/1998 16:54'! where "Select the expression whose evaluation was interrupted." self selectPC! ! !Debugger methodsFor: 'code pane' stamp: 'tk 4/15/1998 18:31'! contentsSelection ^ self pcRange! ! !Debugger methodsFor: 'code pane' stamp: 'di 11/16/2000 16:03'! createSyntaxMorph | methodNode rootMorph | methodNode _ self selectedClass compilerClass new parse: contents in: self selectedClass notifying: nil. (rootMorph _ methodNode asMorphicSyntaxUsing: SyntaxMorph) parsedInClass: self selectedClass; debugger: self. self addDependent: rootMorph. ^rootMorph ! ! !Debugger methodsFor: 'code pane'! doItContext "Answer the context in which a text selection can be evaluated." contextStackIndex = 0 ifTrue: [^super doItContext] ifFalse: [^self selectedContext]! ! !Debugger methodsFor: 'code pane'! doItReceiver "Answer the object that should be informed of the result of evaluating a text selection." ^self receiver! ! !Debugger methodsFor: 'code pane' stamp: 'tk 5/2/1998 10:04'! pc ^ self pcRange! ! !Debugger methodsFor: 'code pane' stamp: 'di 4/24/2000 07:49'! pcRange "Answer the indices in the source code for the method corresponding to the selected context's program counter value." | i methodNode pc end | (selectingPC and: [contextStackIndex ~= 0]) ifFalse: [^1 to: 0]. sourceMap == nil ifTrue: [methodNode _ self selectedClass compilerClass new parse: contents in: self selectedClass notifying: nil dialect: true. sourceMap _ methodNode sourceMap. tempNames _ methodNode tempNames. self selectedContext method cacheTempNames: tempNames]. sourceMap size = 0 ifTrue: [^1 to: 0]. pc_ self selectedContext pc - ((externalInterrupt and: [contextStackIndex=1]) ifTrue: [1] ifFalse: [2]). i _ sourceMap indexForInserting: (Association key: pc value: nil). i < 1 ifTrue: [^1 to: 0]. i > sourceMap size ifTrue: [end _ sourceMap inject: 0 into: [:prev :this | prev max: this value last]. ^ end+1 to: end]. ^(sourceMap at: i) value! ! !Debugger methodsFor: 'code pane' stamp: 'di 1/31/2001 11:14'! toggleSyntaxMorph " syntaxMorph ifNil: [syntaxMorph _ self createSyntaxMorph inAScrollPane. syntaxMorph color: Color paleOrange]. standardTextMorph visible ifTrue: [ standardTextMorph owner replacePane: standardTextMorph with: syntaxMorph. syntaxMorph scroller firstSubmorph update: #contentsSelection. ] ifFalse: [ syntaxMorph owner replacePane: syntaxMorph with: standardTextMorph. ]. " ! ! !Debugger methodsFor: 'code pane menu' stamp: 'tk 4/17/1998 17:25'! perform: selector orSendTo: otherTarget "Selector was just chosen from a menu by a user. If can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked." | result | (#(debug proceed) includes: selector) "When I am a notifier window" ifTrue: [^ self perform: selector] ifFalse: [result _ super perform: selector orSendTo: otherTarget. selector == #doIt ifTrue: [ result ~~ #failedDoit ifTrue: [self proceedValue: result]]. ^ result]! ! !Debugger methodsFor: 'message category list'! selectedMessageCategoryName "Answer the name of the message category of the message of the currently selected context." ^self selectedClass organization categoryOfElement: self selectedMessageName! ! !Debugger methodsFor: 'class list'! selectedClass "Answer the class in which the currently selected context's method was found." ^self selectedContext mclass! ! !Debugger methodsFor: 'class list'! selectedClassOrMetaClass "Answer the class in which the currently selected context's method was found." ^self selectedContext mclass! ! !Debugger methodsFor: 'dependents access' stamp: 'di 1/14/1999 09:28'! step "Update the inspectors." receiverInspector ifNotNil: [receiverInspector step]. contextVariablesInspector ifNotNil: [contextVariablesInspector step]. ! ! !Debugger methodsFor: 'dependents access'! updateInspectors "Update the inspectors on the receiver's variables." receiverInspector update. contextVariablesInspector update! ! !Debugger methodsFor: 'dependents access' stamp: 'di 1/14/1999 09:25'! wantsSteps ^ true! ! !Debugger methodsFor: 'private'! checkContextSelection contextStackIndex = 0 ifTrue: [contextStackIndex _ 1]! ! !Debugger methodsFor: 'private' stamp: 'sw 9/23/1999 15:58'! contextStackIndex: anInteger oldContextWas: oldContext | newMethod | contextStackIndex _ anInteger. anInteger = 0 ifTrue: [tempNames _ sourceMap _ contents _ nil. self changed: #contextStackIndex. self contentsChanged. contextVariablesInspector object: nil. receiverInspector object: self receiver. ^self]. (newMethod _ oldContext == nil or: [oldContext method ~~ self selectedContext method]) ifTrue: [tempNames _ sourceMap _ nil. contents _ self selectedMessage. self contentsChanged. self pcRange "will compute tempNamesunless noFrills"]. self changed: #contextStackIndex. tempNames == nil ifTrue: [tempNames _ self selectedClassOrMetaClass parserClass new parseArgsAndTemps: contents notifying: nil]. contextVariablesInspector object: self selectedContext. receiverInspector object: self receiver. newMethod ifFalse: [self changed: #contentsSelection]! ! !Debugger methodsFor: 'private'! externalInterrupt: aBoolean externalInterrupt _ aBoolean ! ! !Debugger methodsFor: 'private' stamp: 'tk 8/17/2000 15:36'! isolationRecoveryAdvice "Return a notifier message string to be presented in case of recovery from recursive error by revoking the changes in an isolation layer. This surely ranks as one of Squeak's longer help messages." ^ 'Warning!! You have encountered a recursive error situation. Don''t panic, but do read the following advice. If you were just fooling around, the simplest thing to do is to quit and NOT save, and restart Squeak. If you care about recovery, then read on... In the process of diagnosing one error, further errors occurred, making it impossible to give you a debugger to work with. Squeak has jumped to an outer project where many of the objects and code changes that might have caused this problem are not involved in normal operation. If you are looking at this window, chances are that this first level of recovery was successful. If there are changes you care a lot about, try to save them now. Then, hopefully, from the state in this debugger, you can determine what the problem was and fix it. Do not save this image until you are confident of its recovery. You are no longer in the world that is damaged. The two most likely causes of recursive errors are malformed objects (for instance a corrupt value encountered in any display of the desktop) and recurring code errors (such as a change that causes errors in any attempt to display the desktop). In the case of malformed objects, you can attempt to repair them by altering various bindings in the corrupted environment. Open this debugger and examine the state of the objects closest to the error. In the case of code errors, note that you are no longer in a world where the erroneous code is in effect. The only simple option available is for you to browse to the changeSet for the project in distress, and remove one or more of the changes (later it will be possible to edit the code remotely from here). If you feel you have repaired the problem, then you may proceed from this debugger. This will put you back in the project that failed with the changes that failed for another try. Note that the debugger from which you are proceeding is the second one that occurred; you will likely find the first one waiting for you when you reenter the failed project!! Also note that if your error occurred while displaying a morph, it may now be flagged as undisplayable (red with yellow cross); if so, use the morph debug menu to choose ''start drawing again''. If you have not repaired the problem, you should close this debugger and delete the failed project after retrieving whatever may be of value in it. Good luck. - The Squeak Fairy Godmother PS: If you feel you need the help of a quantum mechanic, do NOT close this window. Instead, the best thing to do (after saving anything that seems safe to save) would be to use the ''save as...'' command in the world menu, and give it a new image name, such as OOPS. There is a good chance that someone who knows their way around Squeak can help you out. '! ! !Debugger methodsFor: 'private' stamp: 'jm 5/1/1998 16:20'! lowSpaceChoices "Return a notifier message string to be presented when space is running low." ^ 'Warning!! Squeak is almost out of memory!! Low space detection is now disabled. It will be restored when you close or proceed from this error notifier. Don''t panic, but do proceed with caution. Here are some suggestions: Ä If you suspect an infinite recursion (the same methods calling each other again and again), then close this debugger, and fix the problem. Ä If you want this computation to finish, then make more space available (read on) and choose "proceed" in this debugger. Here are some ways to make more space available... > Close any windows that are not needed. > Get rid of some large objects (e.g., images). > Leave this window on the screen, choose "save as..." from the screen menu, quit, restart the Squeak VM with a larger memory allocation, then restart the image you just saved, and choose "proceed" in this window. Ä If you want to investigate further, choose "debug" in this window. Do not use the debugger "fullStack" command unless you are certain that the stack is not very deep. (Trying to show the full stack will definitely use up all remaining memory if the low-space problem is caused by an infinite recursion!!). ' ! ! !Debugger methodsFor: 'private'! newStack: stack | oldStack diff | oldStack _ contextStack. contextStack _ stack. (oldStack == nil or: [oldStack last ~~ stack last]) ifTrue: [contextStackList _ contextStack collect: [:ctx | ctx printString]. ^ self]. "May be able to re-use some of previous list" diff _ stack size - oldStack size. contextStackList _ diff <= 0 ifTrue: [contextStackList copyFrom: 1-diff to: oldStack size] ifFalse: [diff > 1 ifTrue: [contextStack collect: [:ctx | ctx printString]] ifFalse: [(Array with: stack first printString) , contextStackList]]! ! !Debugger methodsFor: 'private' stamp: 'di 4/14/2000 16:24'! process: aProcess controller: aController context: aContext ^ self process: aProcess controller: aController context: aContext isolationHead: nil! ! !Debugger methodsFor: 'private' stamp: 'di 4/14/2000 16:31'! process: aProcess controller: aController context: aContext isolationHead: projectOrNil super initialize. Smalltalk at: #MessageTally ifPresent: [:c | c new close]. contents _ nil. interruptedProcess _ aProcess. interruptedController _ aController. contextStackTop _ aContext. self newStack: (contextStackTop stackOfSize: 1). contextStackIndex _ 1. externalInterrupt _ false. selectingPC _ true. isolationHead _ projectOrNil! ! !Debugger methodsFor: 'private' stamp: 'tk 4/15/1998 19:04'! resetContext: aContext "Used when a new context becomes top-of-stack, for instance when the method of the selected context is re-compiled, or the simulator steps or returns to a new method. There is room for much optimization here, first to save recomputing the whole stack list (and text), and secondly to avoid recomposing all that text (by editing the paragraph instead of recreating it)." | oldContext | oldContext _ self selectedContext. contextStackTop _ aContext. self newStack: contextStackTop stack. self changed: #contextStackList. self contextStackIndex: 1 oldContextWas: oldContext. self changed: #content.! ! !Debugger methodsFor: 'private' stamp: 'RAA 8/8/2000 10:48'! resumeProcess: aTopView Smalltalk isMorphic ifFalse: [aTopView erase]. savedCursor ifNotNil: [Sensor currentCursor: savedCursor]. isolationHead ifNotNil: [failedProject enterForEmergencyRecovery. isolationHead invoke. isolationHead _ nil]. interruptedProcess suspendedContext method == (Process compiledMethodAt: #terminate) ifFalse: [contextStackIndex > 1 ifTrue: [interruptedProcess popTo: self selectedContext] ifFalse: [interruptedProcess install: self selectedContext]. Smalltalk isMorphic ifTrue: [errorWasInUIProcess ifTrue: [Project resumeProcess: interruptedProcess] ifFalse: [interruptedProcess resume]] ifFalse: [ScheduledControllers activeControllerNoTerminate: interruptedController andProcess: interruptedProcess]]. "if old process was terminated, just terminate current one" interruptedProcess _ nil. "Before delete, so release doesn't terminate it" Smalltalk isMorphic ifTrue: [aTopView delete. Display repaintMorphicDisplayNow] ifFalse: [aTopView controller closeAndUnscheduleNoErase]. Smalltalk installLowSpaceWatcher. "restart low space handler" errorWasInUIProcess == false ifFalse: [Processor terminateActive]! ! !Debugger methodsFor: 'private'! selectedContext contextStackIndex = 0 ifTrue: [^contextStackTop] ifFalse: [^contextStack at: contextStackIndex]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Debugger class instanceVariableNames: ''! !Debugger class methodsFor: 'class initialization' stamp: 'di 1/14/1999 09:17'! initialize ErrorRecursion _ false. ContextStackKeystrokes _ Dictionary new at: $e put: #send; at: $t put: #doStep; at: $p put: #proceed; at: $r put: #restart; at: $f put: #fullStack; at: $w put: #where; yourself. "Debugger initialize"! ! !Debugger class methodsFor: 'instance creation' stamp: 'di 4/14/2000 16:29'! context: aContext "Answer an instance of me for debugging the active process starting with the given context." ^ self context: aContext isolationHead: nil! ! !Debugger class methodsFor: 'instance creation' stamp: 'di 4/14/2000 16:29'! context: aContext isolationHead: isolationHead "Answer an instance of me for debugging the active process starting with the given context." ^ self new process: Processor activeProcess controller: ((Smalltalk isMorphic not and: [ScheduledControllers inActiveControllerProcess]) ifTrue: [ScheduledControllers activeController] ifFalse: [nil]) context: aContext isolationHead: isolationHead ! ! !Debugger class methodsFor: 'opening' stamp: 'RAA 6/3/2000 09:18'! openContext: aContext label: aString contents: contentsString | isolationHead | "Open a notifier in response to an error, halt, or notify. A notifier view just shows a short view of the sender stack and provides a menu that lets the user open a full debugger." "Simulation guard" ErrorRecursion not & Preferences logDebuggerStackToFile ifTrue: [Smalltalk logError: aString inContext: aContext to: 'SqueakDebug.log']. ErrorRecursion ifTrue: [ErrorRecursion _ false. (isolationHead _ CurrentProjectRefactoring currentIsolationHead) ifNil: [self primitiveError: aString] ifNotNil: [isolationHead revoke]]. ErrorRecursion _ true. (Debugger context: aContext isolationHead: isolationHead) openNotifierContents: contentsString label: aString. ErrorRecursion _ false. Processor activeProcess suspend. ! ! !Debugger class methodsFor: 'opening' stamp: 'ar 5/1/1999 09:25'! openInterrupt: aString onProcess: interruptedProcess "Open a notifier in response to an interrupt. An interrupt occurs when the user types the interrupt key (cmd-. on Macs, ctrl-c or alt-. on other systems) or when the low-space watcher detects that memory is low." | debugger | "Simulation guard" debugger _ self new. debugger process: interruptedProcess controller: ((Smalltalk isMorphic not and: [ScheduledControllers activeControllerProcess == interruptedProcess]) ifTrue: [ScheduledControllers activeController]) context: interruptedProcess suspendedContext. debugger externalInterrupt: true. Preferences logDebuggerStackToFile ifTrue: [(aString includesSubString: 'Space') & (aString includesSubString: 'low') ifTrue: [ Smalltalk logError: aString inContext: debugger interruptedContext to:'LowSpaceDebug.log']]. ^ debugger openNotifierContents: debugger interruptedContext shortStack label: aString ! ! InstructionStream subclass: #Decompiler instanceVariableNames: 'constructor method instVars tempVars constTable stack statements lastPc exit lastJumpPc lastReturnPc limit hasValue blockStackBase ' classVariableNames: 'ArgumentFlag CascadeFlag CaseFlag IfNilFlag ' poolDictionaries: '' category: 'System-Compiler'! !Decompiler commentStamp: '' prior: 0! I decompile a method in three phases: Reverser: postfix byte codes -> prefix symbolic codes (nodes and atoms) Parser: prefix symbolic codes -> node tree (same as the compiler) Printer: node tree -> text (done by the nodes)! !Decompiler methodsFor: 'initialize-release'! initSymbols: aClass | nTemps namedTemps | constructor method: method class: aClass literals: method literals. constTable _ constructor codeConstants. instVars _ Array new: aClass instSize. nTemps _ method numTemps. namedTemps _ tempVars == nil ifTrue: [Array new] ifFalse: [tempVars]. tempVars _ (1 to: nTemps) collect: [:i | i <= namedTemps size ifTrue: [constructor codeTemp: i - 1 named: (namedTemps at: i)] ifFalse: [constructor codeTemp: i - 1]]! ! !Decompiler methodsFor: 'initialize-release'! withTempNames: tempNameArray tempVars _ tempNameArray! ! !Decompiler methodsFor: 'control' stamp: 'tao 8/20/97 22:51'! blockForCaseTo: end "Decompile a range of code as in statementsForCaseTo:, but return a block node." | exprs block oldBase | oldBase _ blockStackBase. blockStackBase _ stack size. exprs _ self statementsForCaseTo: end. block _ constructor codeBlock: exprs returns: lastReturnPc = lastPc. blockStackBase _ oldBase. lastReturnPc _ -1. "So as not to mislead outer calls" ^block! ! !Decompiler methodsFor: 'control'! blockTo: end "Decompile a range of code as in statementsTo:, but return a block node." | exprs block oldBase | oldBase _ blockStackBase. blockStackBase _ stack size. exprs _ self statementsTo: end. block _ constructor codeBlock: exprs returns: lastReturnPc = lastPc. blockStackBase _ oldBase. lastReturnPc _ -1. "So as not to mislead outer calls" ^block! ! !Decompiler methodsFor: 'control'! checkForBlock: receiver "We just saw a blockCopy: message. Check for a following block." | savePc jump args argPos block | receiver == constructor codeThisContext ifFalse: [^false]. savePc _ pc. (jump _ self interpretJump) notNil ifFalse: [pc _ savePc. ^nil]. "Definitely a block" jump _ jump + pc. argPos _ statements size. [self willStorePop] whileTrue: [stack addLast: ArgumentFlag. "Flag for doStore:" self interpretNextInstructionFor: self]. args _ Array new: statements size - argPos. 1 to: args size do: "Retrieve args" [:i | args at: i put: statements removeLast. (args at: i) scope: -1 "flag args as block temps"]. block _ self blockTo: jump. stack addLast: (constructor codeArguments: args block: block). ^true! ! !Decompiler methodsFor: 'control' stamp: 'tao 8/20/97 22:51'! statementsForCaseTo: end "Decompile the method from pc up to end and return an array of expressions. If at run time this block will leave a value on the stack, set hasValue to true. If the block ends with a jump or return, set exit to the destination of the jump, or the end of the method; otherwise, set exit = end. Leave pc = end. Note that stack initially contains a CaseFlag which will be removed by a subsequent Pop instruction, so adjust the StackPos accordingly." | blockPos stackPos t | blockPos _ statements size. stackPos _ stack size - 1. "Adjust for CaseFlag" [pc < end] whileTrue: [lastPc _ pc. limit _ end. "for performs" self interpretNextInstructionFor: self]. "If there is an additional item on the stack, it will be the value of this block." (hasValue _ stack size > stackPos) ifTrue: [statements addLast: stack removeLast]. lastJumpPc = lastPc ifFalse: [exit _ pc]. ^self popTo: blockPos! ! !Decompiler methodsFor: 'control'! statementsTo: end "Decompile the method from pc up to end and return an array of expressions. If at run time this block will leave a value on the stack, set hasValue to true. If the block ends with a jump or return, set exit to the destination of the jump, or the end of the method; otherwise, set exit = end. Leave pc = end." | blockPos stackPos t | blockPos _ statements size. stackPos _ stack size. [pc < end] whileTrue: [lastPc _ pc. limit _ end. "for performs" self interpretNextInstructionFor: self]. "If there is an additional item on the stack, it will be the value of this block." (hasValue _ stack size > stackPos) ifTrue: [statements addLast: stack removeLast]. lastJumpPc = lastPc ifFalse: [exit _ pc]. ^self popTo: blockPos! ! !Decompiler methodsFor: 'instruction decoding'! blockReturnTop "No action needed"! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'tao 8/20/97 22:49'! case: dist "statements = keyStmts CascadeFlag keyValueBlock ... keyStmts" | nextCase end thenJump stmtStream elements b node cases otherBlock | nextCase _ pc + dist. end _ limit. "Now add CascadeFlag & keyValueBlock to statements" statements addLast: stack removeLast. stack addLast: CaseFlag. "set for next pop" statements addLast: (self blockForCaseTo: nextCase). stack last == CaseFlag ifTrue: "Last case" ["ensure jump is within block (in case thenExpr returns wierdly I guess)" stack removeLast. "get rid of CaseFlag". thenJump _ exit <= end ifTrue: [exit] ifFalse: [nextCase]. stmtStream _ ReadStream on: (self popTo: stack removeLast). elements _ OrderedCollection new. b _ OrderedCollection new. [stmtStream atEnd] whileFalse: [(node _ stmtStream next) == CascadeFlag ifTrue: [elements addLast: (constructor codeMessage: (constructor codeBlock: b returns: false) selector: (constructor codeSelector: #-> code: #macro) arguments: (Array with: stmtStream next)). b _ OrderedCollection new] ifFalse: [b addLast: node]]. b size > 0 ifTrue: [self error: 'Bad cases']. cases _ constructor codeBrace: elements. otherBlock _ self blockTo: thenJump. stack addLast: (constructor codeMessage: stack removeLast selector: (constructor codeSelector: #caseOf:otherwise: code: #macro) arguments: (Array with: cases with: otherBlock))]! ! !Decompiler methodsFor: 'instruction decoding'! doDup stack last == CascadeFlag ifFalse: ["Save position and mark cascade" stack addLast: statements size. stack addLast: CascadeFlag]. stack addLast: CascadeFlag! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'di 2/5/2000 09:34'! doPop stack isEmpty ifTrue: ["Ignore pop in first leg of ifNil for value" ^ self]. stack last == CaseFlag ifTrue: [stack removeLast] ifFalse: [statements addLast: stack removeLast].! ! !Decompiler methodsFor: 'instruction decoding'! doStore: stackOrBlock "Only called internally, not from InstructionStream. StackOrBlock is stack for store, statements for storePop." | var expr | var _ stack removeLast. expr _ stack removeLast. stackOrBlock addLast: (expr == ArgumentFlag ifTrue: [var] ifFalse: [constructor codeAssignTo: var value: expr])! ! !Decompiler methodsFor: 'instruction decoding'! jump: dist exit _ pc + dist. lastJumpPc _ lastPc! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'di 2/6/2000 08:46'! jump: dist if: condition | savePc elseDist sign elsePc elseStart end cond ifExpr thenBlock elseBlock thenJump elseJump condHasValue b isIfNil saveStack | stack last == CascadeFlag ifTrue: [^ self case: dist]. elsePc _ lastPc. elseStart _ pc + dist. end _ limit. "Check for bfp-jmp to invert condition. Don't be fooled by a loop with a null body." sign _ condition. savePc _ pc. ((elseDist _ self interpretJump) notNil and: [elseDist >= 0 and: [elseStart = pc]]) ifTrue: [sign _ sign not. elseStart _ pc + elseDist]. pc _ savePc. ifExpr _ stack removeLast. (stack size > 0 and: [stack last == IfNilFlag]) ifTrue: [stack removeLast. isIfNil _ true] ifFalse: [isIfNil _ false]. saveStack _ stack. stack _ OrderedCollection new. thenBlock _ self blockTo: elseStart. condHasValue _ hasValue or: [isIfNil]. "ensure jump is within block (in case thenExpr returns)" thenJump _ exit <= end ifTrue: [exit] ifFalse: [elseStart]. "if jump goes back, then it's a loop" thenJump < elseStart ifTrue: ["Must be a while loop... thenJump will jump to the beginning of the while expr. In the case of while's with a block in the condition, the while expr should include more than just the last expression: find all the statements needed by re-decompiling." stack _ saveStack. pc _ thenJump. b _ self statementsTo: elsePc. "discard unwanted statements from block" b size - 1 timesRepeat: [statements removeLast]. statements addLast: (constructor codeMessage: (constructor codeBlock: b returns: false) selector: (constructor codeSelector: (sign ifTrue: [#whileFalse:] ifFalse: [#whileTrue:]) code: #macro) arguments: (Array with: thenBlock)). pc _ elseStart. self convertToDoLoop] ifFalse: ["Must be a conditional..." elseBlock _ self blockTo: thenJump. elseJump _ exit. "if elseJump is backwards, it is not part of the elseExpr" elseJump < elsePc ifTrue: [pc _ lastPc]. isIfNil ifTrue: [cond _ constructor codeMessage: ifExpr ifNilReceiver selector: (sign ifTrue: [constructor codeSelector: #ifNotNil: code: #macro] ifFalse: [constructor codeSelector: #ifNil: code: #macro]) arguments: (Array with: thenBlock)] ifFalse: [cond _ constructor codeMessage: ifExpr selector: (constructor codeSelector: #ifTrue:ifFalse: code: #macro) arguments: (sign ifTrue: [Array with: elseBlock with: thenBlock] ifFalse: [Array with: thenBlock with: elseBlock])]. stack _ saveStack. condHasValue ifTrue: [stack addLast: cond] ifFalse: [statements addLast: cond]]! ! !Decompiler methodsFor: 'instruction decoding'! methodReturnConstant: value self pushConstant: value; methodReturnTop! ! !Decompiler methodsFor: 'instruction decoding'! methodReturnReceiver self pushReceiver; methodReturnTop! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'th 3/17/2000 20:48'! methodReturnTop | last | last _ stack removeLast "test test" asReturnNode. stack size > blockStackBase "get effect of elided pop before return" ifTrue: [statements addLast: stack removeLast]. exit _ method size + 1. lastJumpPc _ lastReturnPc _ lastPc. statements addLast: last! ! !Decompiler methodsFor: 'instruction decoding'! popIntoLiteralVariable: value self pushLiteralVariable: value; doStore: statements! ! !Decompiler methodsFor: 'instruction decoding'! popIntoReceiverVariable: offset self pushReceiverVariable: offset; doStore: statements! ! !Decompiler methodsFor: 'instruction decoding'! popIntoTemporaryVariable: offset self pushTemporaryVariable: offset; doStore: statements! ! !Decompiler methodsFor: 'instruction decoding'! pushActiveContext stack addLast: constructor codeThisContext! ! !Decompiler methodsFor: 'instruction decoding'! pushConstant: value | node | node _ value == true ifTrue: [constTable at: 2] ifFalse: [value == false ifTrue: [constTable at: 3] ifFalse: [value == nil ifTrue: [constTable at: 4] ifFalse: [constructor codeAnyLiteral: value]]]. stack addLast: node! ! !Decompiler methodsFor: 'instruction decoding'! pushLiteralVariable: assoc stack addLast: (constructor codeAnyLitInd: assoc)! ! !Decompiler methodsFor: 'instruction decoding'! pushReceiver stack addLast: (constTable at: 1)! ! !Decompiler methodsFor: 'instruction decoding'! pushReceiverVariable: offset | var | (var _ instVars at: offset + 1) == nil ifTrue: ["Not set up yet" instVars at: offset + 1 put: (var _ constructor codeInst: offset)]. stack addLast: var! ! !Decompiler methodsFor: 'instruction decoding'! pushTemporaryVariable: offset stack addLast: (tempVars at: offset + 1)! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'di 1/29/2000 08:38'! send: selector super: superFlag numArgs: numArgs | args rcvr selNode msgNode messages | args _ Array new: numArgs. (numArgs to: 1 by: -1) do: [:i | args at: i put: stack removeLast]. rcvr _ stack removeLast. superFlag ifTrue: [rcvr _ constructor codeSuper]. (selector == #blockCopy: and: [self checkForBlock: rcvr]) ifFalse: [selNode _ constructor codeAnySelector: selector. rcvr == CascadeFlag ifTrue: ["May actually be a cascade or an ifNil: for value." self willJumpIfFalse ifTrue: "= generated by a case macro" [selector == #= ifTrue: [" = signals a case statement..." statements addLast: args first. stack addLast: rcvr. "restore CascadeFlag" ^ self]. selector == #== ifTrue: [" == signals an ifNil: for value..." stack removeLast; removeLast. rcvr _ stack removeLast. stack addLast: IfNilFlag; addLast: (constructor codeMessage: rcvr selector: selNode arguments: args). ^ self]. self error: 'bad case: ', selector] ifFalse: [(self willJumpIfTrue and: [selector == #==]) ifTrue: [" == signals an ifNotNil: for value..." stack removeLast; removeLast. rcvr _ stack removeLast. stack addLast: IfNilFlag; addLast: (constructor codeMessage: rcvr selector: selNode arguments: args). ^ self]. msgNode _ constructor codeCascadedMessage: selNode arguments: args]. stack last == CascadeFlag ifFalse: ["Last message of a cascade" statements addLast: msgNode. messages _ self popTo: stack removeLast. "Depth saved by first dup" msgNode _ constructor codeCascade: stack removeLast messages: messages]] ifFalse: [msgNode _ constructor codeMessage: rcvr selector: selNode arguments: args]. stack addLast: msgNode]! ! !Decompiler methodsFor: 'instruction decoding'! storeIntoLiteralVariable: assoc self pushLiteralVariable: assoc; doStore: stack! ! !Decompiler methodsFor: 'instruction decoding'! storeIntoReceiverVariable: offset self pushReceiverVariable: offset; doStore: stack! ! !Decompiler methodsFor: 'instruction decoding'! storeIntoTemporaryVariable: offset self pushTemporaryVariable: offset; doStore: stack! ! !Decompiler methodsFor: 'public access'! decompile: aSelector in: aClass "See Decompiler|decompile:in:method:. The method is found by looking up the message, aSelector, in the method dictionary of the class, aClass." ^self decompile: aSelector in: aClass method: (aClass compiledMethodAt: aSelector)! ! !Decompiler methodsFor: 'public access'! decompile: aSelector in: aClass method: aMethod "Answer a MethodNode that is the root of the parse tree for the argument, aMethod, which is the CompiledMethod associated with the message, aSelector. Variables are determined with respect to the argument, aClass." ^self decompile: aSelector in: aClass method: aMethod using: DecompilerConstructor new! ! !Decompiler methodsFor: 'public access'! tempAt: offset "Needed by BraceConstructor=0]) primitive: method primitive class: aClass! ! !Decompiler methodsFor: 'private' stamp: 'di 2/6/2000 08:49'! interpretNextInstructionFor: client | code varNames | "Change false here will trace all state in Transcript." true ifTrue: [^ super interpretNextInstructionFor: client]. varNames _ Decompiler allInstVarNames. code _ (self method at: pc) radix: 16. Transcript cr; cr; print: pc; space; nextPutAll: '<' , (code copyFrom: 4 to: code size) , '>'. 8 to: varNames size do: [:i | i <= 10 ifTrue: [Transcript cr] ifFalse: [Transcript space; space]. Transcript nextPutAll: (varNames at: i); nextPutAll: ': '; print: (self instVarAt: i)]. Transcript endEntry. ^ super interpretNextInstructionFor: client! ! !Decompiler methodsFor: 'private' stamp: 'di 2/6/2000 10:55'! methodRefersOnlyOnceToTemp: offset | nRefs byteCode extension scanner | nRefs _ 0. offset <= 15 ifTrue: [byteCode _ 16 + offset. (InstructionStream on: method) scanFor: [:instr | instr = byteCode ifTrue: [nRefs _ nRefs + 1]. nRefs > 1]] ifFalse: [extension _ 64 + offset. scanner _ InstructionStream on: method. scanner scanFor: [:instr | (instr = 128 and: [scanner followingByte = extension]) ifTrue: [nRefs _ nRefs + 1]. nRefs > 1]]. ^ nRefs = 1 ! ! !Decompiler methodsFor: 'private'! popTo: oldPos | t | t _ Array new: statements size - oldPos. (t size to: 1 by: -1) do: [:i | t at: i put: statements removeLast]. ^t! ! !Decompiler methodsFor: 'private' stamp: 'di 12/26/1998 21:29'! quickMethod | | method isReturnSpecial ifTrue: [^ constructor codeBlock: (Array with: (constTable at: method primitive - 255)) returns: true]. method isReturnField ifTrue: [^ constructor codeBlock: (Array with: (constructor codeInst: method returnField)) returns: true]. self error: 'improper short method'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Decompiler class instanceVariableNames: ''! !Decompiler class methodsFor: 'class initialization' stamp: 'di 1/28/2000 22:21'! initialize CascadeFlag _ 'cascade'. "A unique object" CaseFlag _ 'case'. "Ditto" ArgumentFlag _ 'argument'. "Ditto" IfNilFlag _ 'ifNil'. "Ditto" "Decompiler initialize"! ! ParseNode subclass: #DecompilerConstructor instanceVariableNames: 'method instVars nArgs literalValues tempVars ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !DecompilerConstructor commentStamp: '' prior: 0! I construct the node tree for a Decompiler.! !DecompilerConstructor methodsFor: 'initialize-release'! method: aMethod class: aClass literals: literals method _ aMethod. instVars _ aClass allInstVarNames. nArgs _ method numArgs. literalValues _ literals! ! !DecompilerConstructor methodsFor: 'constructor'! codeAnyLitInd: association ^VariableNode new name: association key key: association index: 0 type: LdLitIndType! ! !DecompilerConstructor methodsFor: 'constructor'! codeAnyLiteral: value ^LiteralNode new key: value index: 0 type: LdLitType! ! !DecompilerConstructor methodsFor: 'constructor'! codeAnySelector: selector ^SelectorNode new key: selector index: 0 type: SendType! ! !DecompilerConstructor methodsFor: 'constructor'! codeArguments: args block: block ^block arguments: args! ! !DecompilerConstructor methodsFor: 'constructor'! codeAssignTo: variable value: expression ^AssignmentNode new variable: variable value: expression! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'sma 3/3/2000 13:34'! codeBlock: statements returns: returns ^ BlockNode statements: statements returns: returns! ! !DecompilerConstructor methodsFor: 'constructor'! codeBrace: elements ^BraceNode new elements: elements! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'di 11/19/1999 11:06'! codeCascade: receiver messages: messages ^ (BraceNode new matchBraceStreamReceiver: receiver messages: messages) ifNil: [CascadeNode new receiver: receiver messages: messages]! ! !DecompilerConstructor methodsFor: 'constructor'! codeCascadedMessage: selector arguments: arguments ^self codeMessage: nil selector: selector arguments: arguments! ! !DecompilerConstructor methodsFor: 'constructor'! codeConstants "Answer with an array of the objects representing self, true, false, nil, -1, 0, 1, 2." ^(Array with: NodeSelf with: NodeTrue with: NodeFalse with: NodeNil) , ((-1 to: 2) collect: [:i | LiteralNode new key: i code: LdMinus1 + i + 1])! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'sma 3/3/2000 13:35'! codeEmptyBlock ^ BlockNode withJust: NodeNil! ! !DecompilerConstructor methodsFor: 'constructor'! codeInst: index ^VariableNode new name: (instVars at: index + 1) index: index type: LdInstType! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'sma 2/5/2000 12:37'! codeMessage: receiver selector: selector arguments: arguments | symbol node | symbol _ selector key. (node _ BraceNode new matchBraceWithReceiver: receiver selector: symbol arguments: arguments) ifNotNil: [^ node]. (node _ self decodeIfNilWithReceiver: receiver selector: symbol arguments: arguments) ifNotNil: [^ node]. ^ MessageNode new receiver: receiver selector: selector arguments: arguments precedence: symbol precedence! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'sma 2/5/2000 12:37'! codeMethod: selector block: block tempVars: vars primitive: primitive class: class | node | node _ self codeSelector: selector code: nil. tempVars _ vars. ^MethodNode new selector: node arguments: (tempVars copyFrom: 1 to: nArgs) precedence: selector precedence temporaries: (tempVars copyFrom: nArgs + 1 to: tempVars size) block: block encoder: (Encoder new initScopeAndLiteralTables nTemps: tempVars size literals: literalValues class: class) primitive: primitive! ! !DecompilerConstructor methodsFor: 'constructor'! codeSelector: sel code: code ^SelectorNode new key: sel code: code! ! !DecompilerConstructor methodsFor: 'constructor'! codeSuper ^NodeSuper! ! !DecompilerConstructor methodsFor: 'constructor'! codeTemp: index ^ TempVariableNode new name: 't' , (index + 1) printString index: index type: LdTempType scope: 0! ! !DecompilerConstructor methodsFor: 'constructor'! codeTemp: index named: tempName ^ TempVariableNode new name: tempName index: index type: LdTempType scope: 0! ! !DecompilerConstructor methodsFor: 'constructor'! codeThisContext ^NodeThisContext! ! !DecompilerConstructor methodsFor: 'constructor' stamp: 'di 1/28/2000 21:23'! decodeIfNilWithReceiver: receiver selector: selector arguments: arguments selector == #ifTrue:ifFalse: ifFalse: [^ nil]. (receiver isMessage: #== receiver: nil arguments: [:argNode | argNode == NodeNil]) ifFalse: [^ nil]. ^ (MessageNode new receiver: receiver selector: (SelectorNode new key: #ifTrue:ifFalse: code: #macro) arguments: arguments precedence: 3) noteSpecialSelector: #ifNil:ifNotNil:! ! Object subclass: #DeepCopier instanceVariableNames: 'references uniClasses ' classVariableNames: 'NextVariableCheckTime ' poolDictionaries: '' category: 'System-Object Storage'! !DeepCopier commentStamp: '' prior: 0! I do veryDeepCopy. I hold the dictionary of objects seen, and the correspondance (uniClass -> new uniClass). See Object|veryDeepCopy which calls (self veryDeepCopyWith: aDeepCopier). When a tree of Morphs points at a morph outside of it, that morph should not be copied. Use our own kind of weak pointers for the potentially outside morphs. Default is that any new class will have all of its newly defined fields deeply copied. If a field needs to be weakly copied, define veryDeepInner: and veryDeepFixupWith:. veryDeepInner: has the loop that actually copies the fields. If a class defines its own copy of veryDeepInner: (to leave some fields out), then veryDeepFixupWith: will be called on that object at the end. veryDeepInner: can find an alternate object to put in a field. (Object veryDeepCopyWith: notes which superclasses did not do veryDeepInner:, and very deep copies those vars). Key to seeing if you have to add the methods veryDeepFixupWith: and veryDeepInner: Does field X contain a morph? If not, no action needed. Is the morph in field X a submorph of the object? Is it down lower in the submorph tree? If so, no action needed. Could the morph in field X every appear on the screen (be a submorph of some other morph)? If not, no action needed. If so, you must write the methods veryDeepFixupWith: and veryDeepInner:, and in them, refrain from sending veryDeepCopyWith: to the contents of field X. Rule: If a morph stores a uniClass class (Player 57) as an object in a field, the new uniClass will not be stored there. Each uniClass instance does have a new class created for it. (fix this by putting the old class in references and allow lookup? Wrong if encounter it before seeing an instance?) Rule: If object A has object C in a field, and A says (^ C) for the copy, but object B has A in a normal field and it gets deepCopied, and A in encountered first, then there will be two copies of C. (just be aware of it) Dependents are now fixed up. Suppose a model has a dependent view. In the DependentFields dictionary, model -> (view ...). If only the model is copied, no dependents are created (no one knows about the new model). If only the view is copied, it is inserted into DependentFields on the right side. model -> (view copiedView ...). If both are copied, the new model has the new view as its dependent. If additional things depend on a model that is copied, the caller must add them to its dependents. ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 1/8/1999 09:37'! checkDeep "Write exceptions in the Transcript. Every class that implements veryDeepInner: must copy all its inst vars. Danger is that a user will add a new instance variable and forget to copy it. This check is only run by hand once in a while to make sure nothing was forgotten. DeepCopier new checkDeep " | mm | Transcript cr; show: 'Instance variables shared with the original object when it is copied'. (Smalltalk allClassesImplementing: #veryDeepInner:) do: [:aClass | (mm _ aClass instVarNames size) > 0 ifTrue: [ (aClass instSize - mm + 1) to: aClass instSize do: [:index | ((aClass compiledMethodAt: #veryDeepInner:) writesField: index) ifFalse: [ Transcript cr; show: aClass name; space; show: (aClass allInstVarNames at: index)]]]]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'di 11/28/1999 20:59'! checkVariables "Check that no indexes of instance vars have changed in certain classes. If you get an error in this method, an implementation of veryDeepCopyWith: needs to be updated. The idea is to catch a change while it is still in the system of the programmer who made it. DeepCopier new checkVariables " | str str2 meth | str _ '|veryDeepCopyWith: or veryDeepInner: is out of date.'. Object instSize = 0 ifFalse: [self error: 'Many implementers of veryDeepCopyWith: are out of date']. Morph superclass == Object ifFalse: [self error: 'Morph', str]. (Morph instVarNames copyFrom: 1 to: 6) = #('bounds' 'owner' 'submorphs' 'fullBounds' 'color' 'extension') ifFalse: [self error: 'Morph', str]. "added ones are OK" "Every class that implements veryDeepInner: must copy all its inst vars. Danger is that a user will add a new instance variable and forget to copy it. So check that the last one is mentioned in the copy method." (Smalltalk allClassesImplementing: #veryDeepInner:) do: [:aClass | ((aClass compiledMethodAt: #veryDeepInner:) writesField: aClass instSize) ifFalse: [ aClass instSize > 0 ifTrue: [ self warnIverNotCopiedIn: aClass sel: #veryDeepInner:]]]. (Smalltalk allClassesImplementing: #veryDeepCopyWith:) do: [:aClass | meth _ aClass compiledMethodAt: #veryDeepCopyWith:. (meth size > 20) & (meth literals includes: #veryDeepCopyWith:) not ifTrue: [ (meth writesField: aClass instSize) ifFalse: [ self warnIverNotCopiedIn: aClass sel: #veryDeepCopyWith:]]]. str2 _ 'Player|copyUniClass and DeepCopier|mapUniClasses are out of date'. Behavior instVarNames = #('superclass' 'methodDict' 'format' ) ifFalse: [self error: str2]. ClassDescription instVarNames = #('instanceVariables' 'organization' ) ifFalse: [self error: str2]. Class instVarNames = #('subclasses' 'name' 'classPool' 'sharedPools' 'environment' 'category' ) ifFalse: [self error: str2]. Model superclass == Object ifFalse: [self error: str2]. Player superclass == Model ifFalse: [self error: str2]. Model class instVarNames = #() ifFalse: [self error: str2]. Player class instVarNames = #('scripts' 'slotInfo') ifFalse: [self error: str2]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'hg 11/23/1999 13:36'! initialize self initialize: 4096. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'hg 11/23/1999 13:35'! initialize: size references _ IdentityDictionary new: size. uniClasses _ IdentityDictionary new. "UniClass -> new UniClass" self isItTimeToCheckVariables ifTrue: [self checkVariables]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 11/24/1999 17:53'! intervalForChecks "set delay interval for checking for new instance variables to 10 minutes. hg 11/23/1999" ^600 ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 11/25/1999 14:37'! isItTimeToCheckVariables | now isIt | NextVariableCheckTime ifNil: [ NextVariableCheckTime _ Time totalSeconds. ^ true]. now _ Time totalSeconds. isIt _ NextVariableCheckTime < now. isIt ifTrue: ["update time for next check" NextVariableCheckTime _ now + self intervalForChecks]. ^isIt ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'di 2/3/2001 16:52'! mapUniClasses "For new Uniclasses, map their class vars to the new objects. And their additional class instance vars. (scripts slotInfo) and cross references like (player321)." "Players also refer to each other using associations in the References dictionary. Search the methods of our Players for those. Make new entries in References and point to them." | pp oldPlayer newKey newAssoc oldSelList newSelList | "Uniclasses use class vars to hold onto siblings who are referred to in code" pp _ Object class instSize + 1. uniClasses do: [:playersClass | "values = new ones" playersClass classPool associationsDo: [:assoc | assoc value: (assoc value veryDeepCopyWith: self)]. playersClass scripts: (playersClass privateScripts veryDeepCopyWith: self). "pp+0" "(pp+1) slotInfo was deepCopied in copyUniClass and that's all it needs" pp+2 to: playersClass class instSize do: [:i | playersClass instVarAt: i put: ((playersClass instVarAt: i) veryDeepCopyWith: self)]. ]. "Make new entries in References and point to them." References keys "copy" do: [:playerName | oldPlayer _ References at: playerName. (references includesKey: oldPlayer) ifTrue: [ newKey _ (references at: oldPlayer) "new player" uniqueNameForReference. "now installed in References" (references at: oldPlayer) renameTo: newKey]]. uniClasses "values" do: [:newClass | oldSelList _ OrderedCollection new. newSelList _ OrderedCollection new. newClass selectorsDo: [:sel | (newClass compiledMethodAt: sel) literals do: [:assoc | assoc class == Association ifTrue: [ (References associationAt: assoc key ifAbsent: [nil]) == assoc ifTrue: [ newKey _ (references at: assoc value ifAbsent: [assoc value]) externalName asSymbol. (assoc key ~= newKey) & (References includesKey: newKey) ifTrue: [ newAssoc _ References associationAt: newKey. newClass methodDictionary at: sel put: (newClass compiledMethodAt: sel) clone. "were sharing it" (newClass compiledMethodAt: sel) literalAt: ((newClass compiledMethodAt: sel) literals indexOf: assoc) put: newAssoc. (oldSelList includes: assoc key) ifFalse: [ oldSelList add: assoc key. newSelList add: newKey]]]]]]. oldSelList with: newSelList do: [:old :new | newClass replaceSilently: old to: new]]. "This is text replacement and can be wrong"! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 8/20/1998 22:13'! references ^ references! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 8/19/1998 15:48'! uniClasses ^uniClasses! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 1/8/1999 07:17'! warnIverNotCopiedIn: aClass sel: sel "Warn the user to update veryDeepCopyWith: or veryDeepInner:" self inform: ('Your file operation succeeded.\\An instance variable was added to to class ', aClass name, ',\and it is not copied in the method ', sel, '.\Please rewrite it to handle all instance variables.\See DeepCopier class comment.') withCRs. Browser openMessageBrowserForClass: aClass selector: sel editString: nil. ! ! StandardSystemController subclass: #DeferredActionStandardSystemController instanceVariableNames: 'queue ' classVariableNames: '' poolDictionaries: '' category: 'NK-Process Browser'! !DeferredActionStandardSystemController commentStamp: '' prior: 0! This is a StandardSystemController that can queue up objects to be evaluated before its control loop.! !DeferredActionStandardSystemController methodsFor: 'as yet unclassified' stamp: 'nk 10/28/2000 22:28'! addDeferredUIMessage: valuableObject queue nextPut: valuableObject! ! !DeferredActionStandardSystemController methodsFor: 'as yet unclassified' stamp: 'nk 10/28/2000 22:27'! controlActivity [queue isEmpty] whileFalse: [queue next value]. ^super controlActivity! ! !DeferredActionStandardSystemController methodsFor: 'as yet unclassified' stamp: 'nk 10/28/2000 22:28'! initialize super initialize. queue _ SharedQueue new.! ! InflatePlugin subclass: #DeflatePlugin instanceVariableNames: 'zipHashHead zipHashTail zipHashValue zipBlockPos zipBlockStart zipLiterals zipDistances zipLiteralFreq zipDistanceFreq zipLiteralCount zipLiteralSize zipMatchCount zipMatchLengthCodes zipDistanceCodes zipCrcTable zipExtraLengthBits zipExtraDistanceBits zipBaseLength zipBaseDistance ' classVariableNames: 'DeflateHashBits DeflateHashMask DeflateHashShift DeflateHashTableSize DeflateMaxDistance DeflateMaxDistanceCodes DeflateMaxLiteralCodes DeflateMaxMatch DeflateMinMatch DeflateWindowMask DeflateWindowSize ' poolDictionaries: '' category: 'VMConstruction-Plugins'! !DeflatePlugin methodsFor: 'primitives' stamp: 'ar 12/29/1999 22:21'! primitiveDeflateBlock "Primitive. Deflate the current contents of the receiver." | goodMatch chainLength lastIndex rcvr result | self export: true. self inline: false. interpreterProxy methodArgumentCount = 3 ifFalse:[^interpreterProxy primitiveFail]. goodMatch _ interpreterProxy stackIntegerValue: 0. chainLength _ interpreterProxy stackIntegerValue: 1. lastIndex _ interpreterProxy stackIntegerValue: 2. rcvr _ interpreterProxy stackObjectValue: 3. interpreterProxy failed ifTrue:[^nil]. self cCode:'' inSmalltalk:[ zipMatchLengthCodes _ CArrayAccessor on: ZipWriteStream matchLengthCodes. zipDistanceCodes _ CArrayAccessor on: ZipWriteStream distanceCodes]. (self loadDeflateStreamFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. result _ self deflateBlock: lastIndex chainLength: chainLength goodMatch: goodMatch. interpreterProxy failed ifFalse:[ "Store back modified values" interpreterProxy storeInteger: 6 ofObject: rcvr withValue: zipHashValue. interpreterProxy storeInteger: 7 ofObject: rcvr withValue: zipBlockPos. interpreterProxy storeInteger: 13 ofObject: rcvr withValue: zipLiteralCount. interpreterProxy storeInteger: 14 ofObject: rcvr withValue: zipMatchCount]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 4. interpreterProxy pushBool: result. ].! ! !DeflatePlugin methodsFor: 'primitives' stamp: 'ar 12/27/1999 20:42'! primitiveDeflateUpdateHashTable "Primitive. Update the hash tables after data has been moved by delta." | delta table tableSize tablePtr entry | self export: true. self var: #tablePtr declareC:'int *tablePtr'. interpreterProxy methodArgumentCount = 2 ifFalse:[^interpreterProxy primitiveFail]. delta _ interpreterProxy stackIntegerValue: 0. table _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy isWords: table) ifFalse:[^interpreterProxy primitiveFail]. tableSize _ interpreterProxy slotSizeOf: table. tablePtr _ interpreterProxy firstIndexableField: table. 0 to: tableSize-1 do:[:i| entry _ tablePtr at: i. entry >= delta ifTrue:[tablePtr at: i put: entry - delta] ifFalse:[tablePtr at: i put: 0]]. interpreterProxy pop: 2. "Leave rcvr on stack"! ! !DeflatePlugin methodsFor: 'primitives' stamp: 'ar 12/30/1999 14:38'! primitiveUpdateGZipCrc32 "Primitive. Update a 32bit CRC value." | collection stopIndex startIndex crc length bytePtr | self export: true. self var: #crc declareC:'unsigned int crc'. self var: #bytePtr declareC:'unsigned char *bytePtr'. self var: #crcTable declareC:'unsigned int *crcTable'. interpreterProxy methodArgumentCount = 4 ifFalse:[^interpreterProxy primitiveFail]. collection _ interpreterProxy stackObjectValue: 0. stopIndex _ interpreterProxy stackIntegerValue: 1. startIndex _ interpreterProxy stackIntegerValue: 2. crc _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 3). interpreterProxy failed ifTrue:[^0]. ((interpreterProxy isBytes: collection) and:[stopIndex >= startIndex and:[startIndex > 0]]) ifFalse:[^interpreterProxy primitiveFail]. length _ interpreterProxy byteSizeOf: collection. (stopIndex <= length) ifFalse:[^interpreterProxy primitiveFail]. bytePtr _ interpreterProxy firstIndexableField: collection. self cCode:'' inSmalltalk:[zipCrcTable _ CArrayAccessor on: GZipWriteStream crcTable]. startIndex _ startIndex - 1. stopIndex _ stopIndex - 1. startIndex to: stopIndex do:[:i| crc _ (zipCrcTable at: ((crc bitXor: (bytePtr at: i)) bitAnd: 255)) bitXor: (crc >> 8). ]. interpreterProxy pop: 5. "args + rcvr" interpreterProxy push: (interpreterProxy positive32BitIntegerFor: crc).! ! !DeflatePlugin methodsFor: 'primitives' stamp: 'ar 12/30/1999 15:54'! primitiveZipSendBlock | distTree litTree distStream litStream rcvr result | self export: true. interpreterProxy methodArgumentCount = 4 ifFalse:[^interpreterProxy primitiveFail]. distTree _ interpreterProxy stackObjectValue: 0. litTree _ interpreterProxy stackObjectValue: 1. distStream _ interpreterProxy stackObjectValue: 2. litStream _ interpreterProxy stackObjectValue: 3. rcvr _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^nil]. (self loadZipEncoderFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: distTree) and:[ (interpreterProxy slotSizeOf: distTree) >= 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: litTree) and:[ (interpreterProxy slotSizeOf: litTree) >= 2]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: litStream) and:[ (interpreterProxy slotSizeOf: litStream) >= 3]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isPointers: distStream) and:[ (interpreterProxy slotSizeOf: distStream) >= 3]) ifFalse:[^interpreterProxy primitiveFail]. self cCode:'' inSmalltalk:[ zipMatchLengthCodes _ CArrayAccessor on: ZipWriteStream matchLengthCodes. zipDistanceCodes _ CArrayAccessor on: ZipWriteStream distanceCodes. zipExtraLengthBits _ CArrayAccessor on: ZipWriteStream extraLengthBits. zipExtraDistanceBits _ CArrayAccessor on: ZipWriteStream extraDistanceBits. zipBaseLength _ CArrayAccessor on: ZipWriteStream baseLength. zipBaseDistance _ CArrayAccessor on: ZipWriteStream baseDistance]. result _ self sendBlock: litStream with: distStream with: litTree with: distTree. interpreterProxy failed ifFalse:[ interpreterProxy storeInteger: 1 ofObject: rcvr withValue: zipPosition. interpreterProxy storeInteger: 4 ofObject: rcvr withValue: zipBitBuf. interpreterProxy storeInteger: 5 ofObject: rcvr withValue: zipBitPos. ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 5. "rcvr + args" interpreterProxy pushInteger: result. ].! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 21:59'! compare: here with: matchPos min: minLength "Compare the two strings and return the length of matching characters. minLength is a lower bound for match lengths that will be accepted. Note: here and matchPos are zero based." | length | self inline: true. "First test if we can actually get longer than minLength" (zipCollection at: here+minLength) = (zipCollection at: matchPos+minLength) ifFalse:[^0]. (zipCollection at: here+minLength-1) = (zipCollection at: matchPos+minLength-1) ifFalse:[^0]. "Then test if we have an initial match at all" (zipCollection at: here) = (zipCollection at: matchPos) ifFalse:[^0]. (zipCollection at: here+1) = (zipCollection at: matchPos+1) ifFalse:[^1]. "Finally do the real comparison" length _ 2. [length < DeflateMaxMatch and:[ (zipCollection at: here+length) = (zipCollection at: matchPos+length)]] whileTrue:[length _ length + 1]. ^length! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 22:00'! deflateBlock: lastIndex chainLength: chainLength goodMatch: goodMatch "Continue deflating the receiver's collection from blockPosition to lastIndex. Note that lastIndex must be at least MaxMatch away from the end of collection" | here matchResult flushNeeded hereMatch hereLength newMatch newLength hasMatch | self inline: false. zipBlockPos > lastIndex ifTrue:[^false]. "Nothing to deflate" zipLiteralCount >= zipLiteralSize ifTrue:[^true]. hasMatch _ false. here _ zipBlockPos. [here <= lastIndex] whileTrue:[ hasMatch ifFalse:[ "Find the first match" matchResult _ self findMatch: here lastLength: DeflateMinMatch-1 lastMatch: here chainLength: chainLength goodMatch: goodMatch. self insertStringAt: here. "update hash table" hereMatch _ matchResult bitAnd: 16rFFFF. hereLength _ matchResult bitShift: -16]. "Look ahead if there is a better match at the next position" matchResult _ self findMatch: here+1 lastLength: hereLength lastMatch: hereMatch chainLength: chainLength goodMatch: goodMatch. newMatch _ matchResult bitAnd: 16rFFFF. newLength _ matchResult bitShift: -16. "Now check if the next match is better than the current one. If not, output the current match (provided that the current match is at least MinMatch long)" (hereLength >= newLength and:[hereLength >= DeflateMinMatch]) ifTrue:[ "Encode the current match" flushNeeded _ self encodeMatch: hereLength distance: here - hereMatch. "Insert all strings up to the end of the current match. Note: The first string has already been inserted." 1 to: hereLength-1 do:[:i| self insertStringAt: (here _ here + 1)]. hasMatch _ false. here _ here + 1. ] ifFalse:[ "Either the next match is better than the current one or we didn't have a good match after all (e.g., current match length < MinMatch). Output a single literal." flushNeeded _ self encodeLiteral: (zipCollection at: here). here _ here + 1. (here <= lastIndex and:[flushNeeded not]) ifTrue:[ "Cache the results for the next round" self insertStringAt: here. hasMatch _ true. hereMatch _ newMatch. hereLength _ newLength]. ]. flushNeeded ifTrue:[zipBlockPos _ here. ^true]. ]. zipBlockPos _ here. ^false! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 22:00'! findMatch: here lastLength: lastLength lastMatch: lastMatch chainLength: maxChainLength goodMatch: goodMatch "Find the longest match for the string starting at here. If there is no match longer than lastLength return lastMatch/lastLength. Traverse at most maxChainLength entries in the hash table. Stop if a match of at least goodMatch size has been found." | matchResult matchPos distance chainLength limit bestLength length | self inline: false. "Compute the default match result" matchResult _ (lastLength bitShift: 16) bitOr: lastMatch. "There is no way to find a better match than MaxMatch" lastLength >= DeflateMaxMatch ifTrue:[^matchResult]. "Start position for searches" matchPos _ zipHashHead at: (self updateHashAt: here + DeflateMinMatch - 1). "Compute the distance to the (possible) match" distance _ here - matchPos. "Note: It is required that 0 < distance < MaxDistance" (distance > 0 and:[distance < DeflateMaxDistance]) ifFalse:[^matchResult]. chainLength _ maxChainLength. "Max. nr of match chain to search" here > DeflateMaxDistance "Limit for matches that are too old" ifTrue:[limit _ here - DeflateMaxDistance] ifFalse:[limit _ 0]. "Best match length so far (current match must be larger to take effect)" bestLength _ lastLength. [true] whileTrue:[ "Compare the current string with the string at match position" length _ self compare: here with: matchPos min: bestLength. "Truncate accidental matches beyound stream position" (here + length > zipPosition) ifTrue:[length _ zipPosition - here]. "Ignore very small matches if they are too far away" (length = DeflateMinMatch and:[(here - matchPos) > (DeflateMaxDistance // 4)]) ifTrue:[length _ DeflateMinMatch - 1]. length > bestLength ifTrue:["We have a new (better) match than before" "Compute the new match result" matchResult _ (length bitShift: 16) bitOr: matchPos. bestLength _ length. "There is no way to find a better match than MaxMatch" bestLength >= DeflateMaxMatch ifTrue:[^matchResult]. "But we may have a good, fast match" bestLength > goodMatch ifTrue:[^matchResult]. ]. (chainLength _ chainLength - 1) > 0 ifFalse:[^matchResult]. "Compare with previous entry in hash chain" matchPos _ zipHashTail at: (matchPos bitAnd: DeflateWindowMask). matchPos <= limit ifTrue:[^matchResult]. "Match position is too old" ].! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 21:59'! insertStringAt: here "Insert the string at the given start position into the hash table. Note: The hash value is updated starting at MinMatch-1 since all strings before have already been inserted into the hash table (and the hash value is updated as well)." | prevEntry | self inline: true. zipHashValue _ self updateHashAt: (here + DeflateMinMatch - 1). prevEntry _ zipHashHead at: zipHashValue. zipHashHead at: zipHashValue put: here. zipHashTail at: (here bitAnd: DeflateWindowMask) put: prevEntry.! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 20:28'! updateHash: nextValue "Update the running hash value based on the next input byte. Return the new updated hash value." ^((zipHashValue bitShift: DeflateHashShift) bitXor: nextValue) bitAnd: DeflateHashMask.! ! !DeflatePlugin methodsFor: 'deflating' stamp: 'ar 12/29/1999 20:29'! updateHashAt: here "Update the hash value at position here (one based)" ^self updateHash: (zipCollection at: here)! ! !DeflatePlugin methodsFor: 'encoding' stamp: 'ar 12/29/1999 20:37'! encodeLiteral: lit "Encode the given literal" self inline: true. zipLiterals at: zipLiteralCount put: lit. zipDistances at: zipLiteralCount put: 0. zipLiteralFreq at: lit put: (zipLiteralFreq at: lit) + 1. zipLiteralCount _ zipLiteralCount + 1. ^zipLiteralCount = zipLiteralSize "We *must* flush" or:[(zipLiteralCount bitAnd: 16rFFF) = 0 "Only check every N kbytes" and:[self shouldFlush]]! ! !DeflatePlugin methodsFor: 'encoding' stamp: 'ar 12/29/1999 20:37'! encodeMatch: length distance: dist "Encode the given match of length length starting at dist bytes ahead" | literal distance | self inline: true. zipLiterals at: zipLiteralCount put: length - DeflateMinMatch. zipDistances at: zipLiteralCount put: dist. literal _ (zipMatchLengthCodes at: length - DeflateMinMatch). zipLiteralFreq at: literal put: (zipLiteralFreq at: literal) + 1. dist < 257 ifTrue:[distance _ zipDistanceCodes at: dist - 1] ifFalse:[distance _ zipDistanceCodes at: 256 + (dist - 1 bitShift: -7)]. zipDistanceFreq at: distance put: (zipDistanceFreq at: distance) + 1. zipLiteralCount _ zipLiteralCount + 1. zipMatchCount _ zipMatchCount + 1. ^zipLiteralCount = zipLiteralSize "We *must* flush" or:[(zipLiteralCount bitAnd: 16rFFF) = 0 "Only check every N kbytes" and:[self shouldFlush]]! ! !DeflatePlugin methodsFor: 'encoding' stamp: 'ar 12/30/1999 15:26'! nextZipBits: nBits put: value "Require: zipCollection, zipCollectionSize, zipPosition, zipBitBuf, zipBitPos. " self inline: true. (value >= 0 and:[(1 << nBits) > value]) ifFalse:[^interpreterProxy primitiveFail]. zipBitBuf _ zipBitBuf bitOr: (value bitShift: zipBitPos). zipBitPos _ zipBitPos + nBits. [zipBitPos >= 8 and:[zipPosition < zipCollectionSize]] whileTrue:[ zipCollection at: zipPosition put: (zipBitBuf bitAnd: 255). zipPosition _ zipPosition + 1. zipBitBuf _ zipBitBuf >> 8. zipBitPos _ zipBitPos - 8]. ! ! !DeflatePlugin methodsFor: 'encoding' stamp: 'ar 12/30/1999 18:15'! sendBlock: literalStream with: distanceStream with: litTree with: distTree "Require: zipCollection, zipCollectionSize, zipPosition, zipBitBuf, zipBitPos. " | oop litPos litLimit litArray distArray lit dist sum llBitLengths llCodes distBitLengths distCodes code extra litBlCount distBlCount | self var: #litArray declareC:'unsigned char *litArray'. self var: #distArray declareC:'unsigned int *distArray'. self var: #llBitLengths declareC:'unsigned int *llBitLengths'. self var: #llCodes declareC:'unsigned int *llCodes'. self var: #distBitLengths declareC:'unsigned int *distBitLengths'. self var: #distCodes declareC:'unsigned int *distCodes'. oop _ interpreterProxy fetchPointer: 0 ofObject: literalStream. litPos _ interpreterProxy fetchInteger: 1 ofObject: literalStream. litLimit _ interpreterProxy fetchInteger: 2 ofObject: literalStream. ((interpreterProxy isIntegerObject: oop) not and:[litPos <= litLimit and:[ litLimit <= (interpreterProxy byteSizeOf: oop) and:[interpreterProxy isBytes: oop]]]) ifFalse:[^interpreterProxy primitiveFail]. litArray _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 0 ofObject: distanceStream. ((interpreterProxy isIntegerObject: oop) not and:[ (interpreterProxy fetchInteger: 1 ofObject: distanceStream) = litPos and:[ (interpreterProxy fetchInteger: 2 ofObject: distanceStream) = litLimit]]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isWords: oop) and:[ litLimit <= (interpreterProxy slotSizeOf: oop)]) ifFalse:[^interpreterProxy primitiveFail]. distArray _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 0 ofObject: litTree. ((interpreterProxy isIntegerObject: oop) not and:[interpreterProxy isWords: oop]) ifFalse:[^interpreterProxy primitiveFail]. litBlCount _ interpreterProxy slotSizeOf: oop. llBitLengths _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 1 ofObject: litTree. ((interpreterProxy isIntegerObject: oop) not and:[interpreterProxy isWords: oop]) ifFalse:[^interpreterProxy primitiveFail]. (litBlCount = (interpreterProxy slotSizeOf: oop)) ifFalse:[^interpreterProxy primitiveFail]. llCodes _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 0 ofObject: distTree. ((interpreterProxy isIntegerObject: oop) not and:[interpreterProxy isWords: oop]) ifFalse:[^interpreterProxy primitiveFail]. distBlCount _ interpreterProxy slotSizeOf: oop. distBitLengths _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 1 ofObject: distTree. ((interpreterProxy isIntegerObject: oop) not and:[interpreterProxy isWords: oop]) ifFalse:[^interpreterProxy primitiveFail]. (distBlCount = (interpreterProxy slotSizeOf: oop)) ifFalse:[^interpreterProxy primitiveFail]. distCodes _ interpreterProxy firstIndexableField: oop. interpreterProxy failed ifTrue:[^nil]. self nextZipBits: 0 put: 0. "Flush pending bits if necessary" sum _ 0. [litPos < litLimit and:[zipPosition + 4 < zipCollectionSize]] whileTrue:[ lit _ litArray at: litPos. dist _ distArray at: litPos. litPos _ litPos + 1. dist = 0 ifTrue:["literal" sum _ sum + 1. lit < litBlCount ifFalse:[^interpreterProxy primitiveFail]. self nextZipBits: (llBitLengths at: lit) put: (llCodes at: lit). ] ifFalse:["match" sum _ sum + lit + DeflateMinMatch. lit < 256 ifFalse:[^interpreterProxy primitiveFail]. code _ zipMatchLengthCodes at: lit. code < litBlCount ifFalse:[^interpreterProxy primitiveFail]. self nextZipBits: (llBitLengths at: code) put: (llCodes at: code). extra _ zipExtraLengthBits at: code - 257. extra = 0 ifFalse:[ lit _ lit - (zipBaseLength at: code - 257). self nextZipBits: extra put: lit]. dist _ dist - 1. dist < 16r8000 ifFalse:[^interpreterProxy primitiveFail]. dist < 256 ifTrue:[code _ zipDistanceCodes at: dist] ifFalse:[code _ zipDistanceCodes at: 256 + (dist >> 7)]. code < distBlCount ifFalse:[^interpreterProxy primitiveFail]. self nextZipBits: (distBitLengths at: code) put: (distCodes at: code). extra _ zipExtraDistanceBits at: code. extra = 0 ifFalse:[ dist _ dist - (zipBaseDistance at: code). self nextZipBits: extra put: dist]. ]. ]. interpreterProxy failed ifTrue:[^nil]. interpreterProxy storeInteger: 1 ofObject: literalStream withValue: litPos. interpreterProxy storeInteger: 1 ofObject: distanceStream withValue: litPos. ^sum! ! !DeflatePlugin methodsFor: 'encoding' stamp: 'ar 12/29/1999 22:00'! shouldFlush "Check if we should flush the current block. Flushing can be useful if the input characteristics change." | nLits | self inline: false. zipLiteralCount = zipLiteralSize ifTrue:[^true]. "We *must* flush" (zipLiteralCount bitAnd: 16rFFF) = 0 ifFalse:[^false]. "Only check every N kbytes" zipMatchCount * 10 <= zipLiteralCount ifTrue:[ "This is basically random data. There is no need to flush early since the overhead for encoding the trees will add to the overall size" ^false]. "Try to adapt to the input data. We flush if the ratio between matches and literals changes beyound a certain threshold" nLits _ zipLiteralCount - zipMatchCount. nLits <= zipMatchCount ifTrue:[^false]. "whow!! so many matches" ^nLits * 4 <= zipMatchCount! ! !DeflatePlugin methodsFor: 'primitive support' stamp: 'ar 12/30/1999 15:28'! loadDeflateStreamFrom: rcvr | oop | self inline: false. ((interpreterProxy isPointers: rcvr) and:[ (interpreterProxy slotSizeOf: rcvr) >= 15]) ifFalse:[^false]. oop _ interpreterProxy fetchPointer: 0 ofObject: rcvr. (interpreterProxy isIntegerObject: oop) ifTrue:[^interpreterProxy primitiveFail]. (interpreterProxy isBytes: oop) ifFalse:[^interpreterProxy primitiveFail]. zipCollection _ interpreterProxy firstIndexableField: oop. zipCollectionSize _ interpreterProxy byteSizeOf: oop. zipPosition _ interpreterProxy fetchInteger: 1 ofObject: rcvr. zipReadLimit _ interpreterProxy fetchInteger: 2 ofObject: rcvr. "zipWriteLimit _ interpreterProxy fetchInteger: 3 ofObject: rcvr." oop _ interpreterProxy fetchPointer: 4 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isWords: oop) not]) ifTrue:[^false]. (interpreterProxy slotSizeOf: oop) = DeflateHashTableSize ifFalse:[^false]. zipHashHead _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 5 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isWords: oop) not]) ifTrue:[^false]. (interpreterProxy slotSizeOf: oop) = DeflateWindowSize ifFalse:[^false]. zipHashTail _ interpreterProxy firstIndexableField: oop. zipHashValue _ interpreterProxy fetchInteger: 6 ofObject: rcvr. zipBlockPos _ interpreterProxy fetchInteger: 7 ofObject: rcvr. "zipBlockStart _ interpreterProxy fetchInteger: 8 ofObject: rcvr." oop _ interpreterProxy fetchPointer: 9 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isBytes: oop) not]) ifTrue:[^false]. zipLiteralSize _ interpreterProxy slotSizeOf: oop. zipLiterals _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 10 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isWords: oop) not]) ifTrue:[^false]. (interpreterProxy slotSizeOf: oop) < zipLiteralSize ifTrue:[^false]. zipDistances _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 11 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isWords: oop) not]) ifTrue:[^false]. (interpreterProxy slotSizeOf: oop) = DeflateMaxLiteralCodes ifFalse:[^false]. zipLiteralFreq _ interpreterProxy firstIndexableField: oop. oop _ interpreterProxy fetchPointer: 12 ofObject: rcvr. ((interpreterProxy isIntegerObject: oop) or:[ (interpreterProxy isWords: oop) not]) ifTrue:[^false]. (interpreterProxy slotSizeOf: oop) = DeflateMaxDistanceCodes ifFalse:[^false]. zipDistanceFreq _ interpreterProxy firstIndexableField: oop. zipLiteralCount _ interpreterProxy fetchInteger: 13 ofObject: rcvr. zipMatchCount _ interpreterProxy fetchInteger: 14 ofObject: rcvr. ^interpreterProxy failed not! ! !DeflatePlugin methodsFor: 'primitive support' stamp: 'ar 12/30/1999 15:28'! loadZipEncoderFrom: rcvr | oop | self inline: false. ((interpreterProxy isPointers: rcvr) and:[ (interpreterProxy slotSizeOf: rcvr) >= 6]) ifFalse:[^false]. oop _ interpreterProxy fetchPointer: 0 ofObject: rcvr. (interpreterProxy isIntegerObject: oop) ifTrue:[^interpreterProxy primitiveFail]. (interpreterProxy isBytes: oop) ifFalse:[^interpreterProxy primitiveFail]. zipCollection _ interpreterProxy firstIndexableField: oop. zipCollectionSize _ interpreterProxy byteSizeOf: oop. zipPosition _ interpreterProxy fetchInteger: 1 ofObject: rcvr. zipReadLimit _ interpreterProxy fetchInteger: 2 ofObject: rcvr. "zipWriteLimit _ interpreterProxy fetchInteger: 3 ofObject: rcvr." zipBitBuf _ interpreterProxy fetchInteger: 4 ofObject: rcvr. zipBitPos _ interpreterProxy fetchInteger: 5 ofObject: rcvr. ^interpreterProxy failed not! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DeflatePlugin class instanceVariableNames: ''! !DeflatePlugin class methodsFor: 'class initialization' stamp: 'ar 12/29/1999 20:54'! initialize "DeflatePlugin initialize" DeflateWindowSize _ 16r8000. DeflateWindowMask _ DeflateWindowSize - 1. DeflateMinMatch _ 3. DeflateMaxMatch _ 258. DeflateMaxDistance _ DeflateWindowSize. DeflateHashBits _ 15. DeflateHashTableSize _ 1 << DeflateHashBits. DeflateHashMask _ DeflateHashTableSize - 1. DeflateHashShift _ (DeflateHashBits + DeflateMinMatch - 1) // DeflateMinMatch. DeflateMaxLiteralCodes _ ZipWriteStream maxLiteralCodes. DeflateMaxDistanceCodes _ ZipWriteStream maxDistanceCodes.! ! !DeflatePlugin class methodsFor: 'translation' stamp: 'sma 3/3/2000 12:33'! declareCVarsIn: cg super declareCVarsIn: cg. "Required since we share some instVars" cg var: #zipHashHead type: #'unsigned int*'. cg var: #zipHashTail type: #'unsigned int*'. cg var: #zipLiterals type: #'unsigned char*'. cg var: #zipDistances type: #'unsigned int*'. cg var: #zipLiteralFreq type: #'unsigned int*'. cg var: #zipDistanceFreq type: #'unsigned int*'. cg var: #zipMatchLengthCodes type: #'unsigned int' array: ZipWriteStream matchLengthCodes. cg var: #zipDistanceCodes type: #'unsigned int' array: ZipWriteStream distanceCodes. cg var: #zipCrcTable type: #'unsigned int' array: GZipWriteStream crcTable. cg var: #zipExtraLengthBits type: #'unsigned int' array: ZipWriteStream extraLengthBits. cg var: #zipExtraDistanceBits type: #'unsigned int' array: ZipWriteStream extraDistanceBits. cg var: #zipBaseLength type: #'unsigned int' array: ZipWriteStream baseLength. cg var: #zipBaseDistance type: #'unsigned int' array: ZipWriteStream baseDistance! ! WriteStream subclass: #DeflateStream instanceVariableNames: 'hashHead hashTail hashValue blockPosition blockStart ' classVariableNames: '' poolDictionaries: 'ZipConstants ' category: 'System-Compression'! !DeflateStream methodsFor: 'initialize-release' stamp: 'ar 12/29/1999 17:30'! flush "Force compression" self deflateBlock.! ! !DeflateStream methodsFor: 'initialize-release' stamp: 'ar 12/31/1999 18:00'! initialize blockStart _ nil. blockPosition _ 0. hashValue _ 0. self initializeHashTables.! ! !DeflateStream methodsFor: 'initialize-release' stamp: 'ar 12/29/1999 17:32'! initializeHashTables hashHead _ WordArray new: 1 << HashBits. hashTail _ WordArray new: WindowSize. ! ! !DeflateStream methodsFor: 'initialize-release' stamp: 'ar 12/29/1999 17:33'! on: aCollection self initialize. super on: (aCollection species new: WindowSize * 2).! ! !DeflateStream methodsFor: 'initialize-release' stamp: 'ar 12/28/1999 17:34'! on: aCollection from: firstIndex to: lastIndex "Not for DeflateStreams please" ^self shouldNotImplement! ! !DeflateStream methodsFor: 'accessing' stamp: 'ar 12/29/1999 20:00'! goodMatchLength "Return the length that is considered to be a 'good' match. Higher values will result in better compression but take more time." ^MaxMatch "Best compression"! ! !DeflateStream methodsFor: 'accessing' stamp: 'ar 12/29/1999 20:00'! hashChainLength "Return the max. number of hash chains to traverse. Higher values will result in better compression but take more time." ^4096 "Best compression"! ! !DeflateStream methodsFor: 'accessing' stamp: 'ar 12/29/1999 17:33'! nextPutAll: aCollection | start count max | aCollection species = collection species ifFalse:[ aCollection do:[:ch| self nextPut: ch]. ^aCollection]. start _ 1. count _ aCollection size. [count = 0] whileFalse:[ position = writeLimit ifTrue:[self deflateBlock]. max _ writeLimit - position. max > count ifTrue:[max _ count]. collection replaceFrom: position+1 to: position+max with: aCollection startingAt: start. start _ start + max. count _ count - max. position _ position + max]. ^aCollection! ! !DeflateStream methodsFor: 'accessing' stamp: 'ar 12/28/1999 17:35'! pastEndPut: anObject self deflateBlock. ^self nextPut: anObject! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 20:24'! compare: here with: matchPos min: minLength "Compare the two strings and return the length of matching characters. minLength is a lower bound for match lengths that will be accepted. Note: here and matchPos are zero based." | length | "First test if we can actually get longer than minLength" (collection at: here+minLength+1) = (collection at: matchPos+minLength+1) ifFalse:[^0]. (collection at: here+minLength) = (collection at: matchPos+minLength) ifFalse:[^0]. "Then test if we have an initial match at all" (collection at: here+1) = (collection at: matchPos+1) ifFalse:[^0]. (collection at: here+2) = (collection at: matchPos+2) ifFalse:[^1]. "Finally do the real comparison" length _ 3. [length <= MaxMatch and:[ (collection at: here+length) = (collection at: matchPos+length)]] whileTrue:[length _ length + 1]. ^length - 1! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/31/1999 18:00'! deflateBlock "Deflate the current contents of the stream" | flushNeeded lastIndex | (blockStart == nil) ifTrue:[ "One time initialization for the first block" 1 to: MinMatch-1 do:[:i| self updateHashAt: i]. blockStart _ 0]. [blockPosition < position] whileTrue:[ (position + MaxMatch > writeLimit) ifTrue:[lastIndex _ writeLimit - MaxMatch] ifFalse:[lastIndex _ position]. flushNeeded _ self deflateBlock: lastIndex-1 chainLength: self hashChainLength goodMatch: self goodMatchLength. flushNeeded ifTrue:[ self flushBlock. blockStart _ blockPosition]. "Make room for more data" self moveContentsToFront]. ! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 18:05'! deflateBlock: lastIndex chainLength: chainLength goodMatch: goodMatch "Continue deflating the receiver's collection from blockPosition to lastIndex. Note that lastIndex must be at least MaxMatch away from the end of collection" | here matchResult flushNeeded hereMatch hereLength newMatch newLength hasMatch | blockPosition > lastIndex ifTrue:[^false]. "Nothing to deflate" hasMatch _ false. here _ blockPosition. [here <= lastIndex] whileTrue:[ hasMatch ifFalse:[ "Find the first match" matchResult _ self findMatch: here lastLength: MinMatch-1 lastMatch: here chainLength: chainLength goodMatch: goodMatch. self insertStringAt: here. "update hash table" hereMatch _ matchResult bitAnd: 16rFFFF. hereLength _ matchResult bitShift: -16]. "Look ahead if there is a better match at the next position" matchResult _ self findMatch: here+1 lastLength: hereLength lastMatch: hereMatch chainLength: chainLength goodMatch: goodMatch. newMatch _ matchResult bitAnd: 16rFFFF. newLength _ matchResult bitShift: -16. "Now check if the next match is better than the current one. If not, output the current match (provided that the current match is at least MinMatch long)" (hereLength >= newLength and:[hereLength >= MinMatch]) ifTrue:[ self assert:[self validateMatchAt: here from: hereMatch to: hereMatch + hereLength - 1]. "Encode the current match" flushNeeded _ self encodeMatch: hereLength distance: here - hereMatch. "Insert all strings up to the end of the current match. Note: The first string has already been inserted." 1 to: hereLength-1 do:[:i| self insertStringAt: (here _ here + 1)]. hasMatch _ false. here _ here + 1. ] ifFalse:[ "Either the next match is better than the current one or we didn't have a good match after all (e.g., current match length < MinMatch). Output a single literal." flushNeeded _ self encodeLiteral: (collection byteAt: (here + 1)). here _ here + 1. (here <= lastIndex and:[flushNeeded not]) ifTrue:[ "Cache the results for the next round" self insertStringAt: here. hasMatch _ true. hereMatch _ newMatch. hereLength _ newLength]. ]. flushNeeded ifTrue:[blockPosition _ here. ^true]. ]. blockPosition _ here. ^false! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 17:45'! findMatch: here lastLength: lastLength lastMatch: lastMatch chainLength: maxChainLength goodMatch: goodMatch "Find the longest match for the string starting at here. If there is no match longer than lastLength return lastMatch/lastLength. Traverse at most maxChainLength entries in the hash table. Stop if a match of at least goodMatch size has been found." | matchResult matchPos distance chainLength limit bestLength length | "Compute the default match result" matchResult _ (lastLength bitShift: 16) bitOr: lastMatch. "There is no way to find a better match than MaxMatch" lastLength >= MaxMatch ifTrue:[^matchResult]. "Start position for searches" matchPos _ hashHead at: (self updateHashAt: here + MinMatch) + 1. "Compute the distance to the (possible) match" distance _ here - matchPos. "Note: It is required that 0 < distance < MaxDistance" (distance > 0 and:[distance < MaxDistance]) ifFalse:[^matchResult]. chainLength _ maxChainLength. "Max. nr of match chain to search" here > MaxDistance "Limit for matches that are too old" ifTrue:[limit _ here - MaxDistance] ifFalse:[limit _ 0]. "Best match length so far (current match must be larger to take effect)" bestLength _ lastLength. ["Compare the current string with the string at match position" length _ self compare: here with: matchPos min: bestLength. "Truncate accidental matches beyound stream position" (here + length > position) ifTrue:[length _ position - here]. "Ignore very small matches if they are too far away" (length = MinMatch and:[(here - matchPos) > (MaxDistance // 4)]) ifTrue:[length _ MinMatch - 1]. length > bestLength ifTrue:["We have a new (better) match than before" "Compute the new match result" matchResult _ (length bitShift: 16) bitOr: matchPos. bestLength _ length. "There is no way to find a better match than MaxMatch" bestLength >= MaxMatch ifTrue:[^matchResult]. "But we may have a good, fast match" bestLength > goodMatch ifTrue:[^matchResult]. ]. (chainLength _ chainLength - 1) > 0] whileTrue:[ "Compare with previous entry in hash chain" matchPos _ hashTail at: (matchPos bitAnd: WindowMask) + 1. matchPos <= limit ifTrue:[^matchResult]. "Match position is too old" ]. ^matchResult! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/28/1999 17:37'! flushBlock "Flush a deflated block"! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 17:46'! insertStringAt: here "Insert the string at the given start position into the hash table. Note: The hash value is updated starting at MinMatch-1 since all strings before have already been inserted into the hash table (and the hash value is updated as well)." | prevEntry | hashValue _ self updateHashAt: (here + MinMatch). prevEntry _ hashHead at: hashValue+1. hashHead at: hashValue+1 put: here. hashTail at: (here bitAnd: WindowMask)+1 put: prevEntry.! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 17:48'! updateHash: nextValue "Update the running hash value based on the next input byte. Return the new updated hash value." ^((hashValue bitShift: HashShift) bitXor: nextValue) bitAnd: HashMask.! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/29/1999 17:47'! updateHashAt: here "Update the hash value at position here (one based)" ^self updateHash: (collection byteAt: here)! ! !DeflateStream methodsFor: 'deflating' stamp: 'ar 12/28/1999 17:43'! validateMatchAt: pos from: startPos to: endPos | here | here _ pos. startPos+1 to: endPos+1 do:[:i| (collection at: i) = (collection at: (here _ here + 1)) ifFalse:[^self error:'Not a match']]. ^true! ! !DeflateStream methodsFor: 'encoding' stamp: 'ar 12/29/1999 18:04'! encodeLiteral: literal "Encode the given literal. Return true if the current block needs to be flushed." ^false! ! !DeflateStream methodsFor: 'encoding' stamp: 'ar 12/29/1999 18:04'! encodeMatch: matchLength distance: matchDistance "Encode a match of the given length and distance. Return true if the current block should be flushed." ^false! ! !DeflateStream methodsFor: 'private' stamp: 'ar 12/29/1999 17:50'! moveContentsToFront "Move the contents of the receiver to the front" | delta | delta _ (blockPosition - WindowSize). delta <= 0 ifTrue:[^self]. "Move collection" collection replaceFrom: 1 to: collection size - delta with: collection startingAt: delta+1. position _ position - delta. "Move hash table entries" blockPosition _ blockPosition - delta. blockStart _ blockStart - delta. self updateHashTable: hashHead delta: delta. self updateHashTable: hashTail delta: delta.! ! !DeflateStream methodsFor: 'private' stamp: 'ar 2/2/2001 15:47'! updateHashTable: table delta: delta | pos | 1 to: table size do:[:i| "Discard entries that are out of range" (pos _ table at: i) >= delta ifTrue:[table at: i put: pos - delta] ifFalse:[table at: i put: 0]].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DeflateStream class instanceVariableNames: ''! !DeflateStream class methodsFor: 'class initialization' stamp: 'ar 12/30/1999 00:24'! initialize "DeflateStream initialize" #( WindowSize WindowMask MaxDistance MinMatch MaxMatch HashBits HashMask HashShift ) do:[:sym| ZipConstants declare: sym from: Undeclared. ]. WindowSize _ 16r8000. WindowMask _ WindowSize - 1. MaxDistance _ WindowSize. MinMatch _ 3. MaxMatch _ 258. HashBits _ 15. HashMask _ (1 << HashBits) - 1. HashShift _ (HashBits + MinMatch - 1) // MinMatch. ! ! Object subclass: #Delay instanceVariableNames: 'delayDuration resumptionTime delaySemaphore beingWaitedOn ' classVariableNames: 'AccessProtect ActiveDelay ActiveDelayStartTime SuspendedDelays TimingSemaphore ' poolDictionaries: '' category: 'Kernel-Processes'! !Delay methodsFor: 'delaying' stamp: 'jm 9/12/97 11:11'! unschedule "Unschedule this Delay. Do nothing if it wasn't scheduled." | done | AccessProtect critical: [ done _ false. [done] whileFalse: [SuspendedDelays remove: self ifAbsent: [done _ true]]. ActiveDelay == self ifTrue: [ SuspendedDelays isEmpty ifTrue: [ ActiveDelay _ nil. ActiveDelayStartTime _ nil] ifFalse: [ SuspendedDelays removeFirst activate]]]. ! ! !Delay methodsFor: 'delaying' stamp: 'jm 9/12/97 09:10'! wait "Schedule this Delay, then wait on its semaphore. The current process will be suspended for the amount of time specified when this Delay was created." self schedule. delaySemaphore wait. ! ! !Delay methodsFor: 'private' stamp: 'jm 9/11/97 13:31'! activate "Private!! Make the receiver the Delay to be awoken when the next timer interrupt occurs. This method should only be called from a block protected by the AccessProtect semaphore." ActiveDelay _ self. ActiveDelayStartTime _ Time millisecondClockValue. TimingSemaphore initSignals. Delay primSignal: TimingSemaphore atMilliseconds: resumptionTime. ! ! !Delay methodsFor: 'private' stamp: 'jm 9/11/97 14:49'! adjustResumptionTimeOldBase: oldBaseTime newBase: newBaseTime "Private!! Adjust the value of the system's millisecond clock at which this Delay will be awoken. Used to adjust resumption times after a snapshot or clock roll-over." resumptionTime _ newBaseTime + (resumptionTime - oldBaseTime). ! ! !Delay methodsFor: 'private' stamp: 'jm 9/11/97 11:54'! resumptionTime "Answer the value of the system's millisecondClock at which the receiver's suspended Process will resume." ^ resumptionTime ! ! !Delay methodsFor: 'private' stamp: 'jm 9/12/97 11:10'! schedule "Private!! Schedule this Delay, but return immediately rather than waiting. The receiver's semaphore will be signalled when its delay duration has elapsed." beingWaitedOn ifTrue: [self error: 'This Delay has already been scheduled.']. AccessProtect critical: [ beingWaitedOn _ true. resumptionTime _ Time millisecondClockValue + delayDuration. ActiveDelay == nil ifTrue: [self activate] ifFalse: [ resumptionTime < ActiveDelay resumptionTime ifTrue: [ SuspendedDelays add: ActiveDelay. self activate] ifFalse: [SuspendedDelays add: self]]]. ! ! !Delay methodsFor: 'private' stamp: 'jm 9/12/97 08:56'! setDelay: millisecondCount forSemaphore: aSemaphore "Private!! Initialize this delay to signal the given semaphore after the given number of milliseconds." delayDuration _ millisecondCount. delaySemaphore _ aSemaphore. beingWaitedOn _ false. ! ! !Delay methodsFor: 'private' stamp: 'jm 9/11/97 11:54'! signalWaitingProcess "The delay time has elapsed; signal the waiting process." beingWaitedOn _ false. delaySemaphore signal. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Delay class instanceVariableNames: ''! !Delay class methodsFor: 'instance creation' stamp: 'jm 9/15/97 17:09'! forMilliseconds: anInteger "Return a new Delay for the given number of milliseconds. Sending 'wait' to this Delay will cause the sender's process to be suspended for approximately that length of time." anInteger < 0 ifTrue: [self error: 'delay times cannot be negative']. ^ self new setDelay: anInteger forSemaphore: Semaphore new ! ! !Delay class methodsFor: 'instance creation' stamp: 'di 6/16/1999 23:04'! forSeconds: aNumber "Return a new Delay for the given number of seconds. Sending 'wait' to this Delay will cause the sender's process to be suspended for approximately that length of time." aNumber < 0 ifTrue: [self error: 'delay times cannot be negative']. ^ self new setDelay: (aNumber * 1000) asInteger forSemaphore: Semaphore new ! ! !Delay class methodsFor: 'instance creation' stamp: 'jm 9/12/97 11:06'! howToUse "An instance of Delay responds to the message 'wait' by suspending the caller's process for a certain amount of time. The duration of the pause is specified when the Delay is created with the message forMilliseconds: or forSeconds:. A Delay can be used again when the current wait has finished. For example, a clock process might repeatedly wait on a one-second Delay. The maximum delay is (SmallInteger maxVal // 2) milliseconds, or about six days. A delay in progress when an image snapshot is saved is resumed when the snapshot is re-started. Delays work across millisecond clock roll-overs." ! ! !Delay class methodsFor: 'instance creation' stamp: 'jm 9/15/97 17:10'! timeoutSemaphore: aSemaphore afterMSecs: anInteger "Create and schedule a Delay to signal the given semaphore when the given number of milliseconds has elapsed. Return the scheduled Delay. The timeout can be cancelled by sending 'unschedule' to this Delay." "Details: This mechanism is used to provide a timeout when waiting for an external event, such as arrival of data over a network connection, to signal a semaphore. The timeout ensures that the semaphore will be signalled within a reasonable period of time even if the event fails to occur. Typically, the waiting process cancels the timeout request when awoken, then determines if the awaited event has actually occurred." anInteger < 0 ifTrue: [self error: 'delay times cannot be negative']. ^ (self new setDelay: anInteger forSemaphore: aSemaphore) schedule ! ! !Delay class methodsFor: 'snapshotting' stamp: 'jm 9/11/97 14:59'! restoreResumptionTimes "Private!! Restore the resumption times of all scheduled Delays after a snapshot or clock roll-over. This method should be called only while the AccessProtect semaphore is held." | newBaseTime | newBaseTime _ Time millisecondClockValue. SuspendedDelays do: [:d | d adjustResumptionTimeOldBase: 0 newBase: newBaseTime]. ActiveDelay == nil ifFalse: [ ActiveDelay adjustResumptionTimeOldBase: 0 newBase: newBaseTime. ActiveDelay activate]. ! ! !Delay class methodsFor: 'snapshotting' stamp: 'jm 9/11/97 15:15'! saveResumptionTimes "Private!! Record the resumption times of all Delays relative to a base time of zero. This is done prior to snapshotting or adjusting the resumption times after a clock roll-over. This method should be called only while the AccessProtect semaphore is held." | oldBaseTime | oldBaseTime _ Time millisecondClockValue. ActiveDelay == nil ifFalse: [ oldBaseTime < ActiveDelayStartTime ifTrue: [oldBaseTime _ ActiveDelayStartTime]. "clock rolled over" ActiveDelay adjustResumptionTimeOldBase: oldBaseTime newBase: 0]. SuspendedDelays do: [:d | d adjustResumptionTimeOldBase: oldBaseTime newBase: 0]. ! ! !Delay class methodsFor: 'snapshotting' stamp: 'jm 9/11/97 15:00'! shutDown "Suspend the active delay, if any, before snapshotting. It will be reactived when the snapshot is resumed." "Details: This prevents a timer interrupt from waking up the active delay in the midst snapshoting, since the active delay will be restarted when resuming the snapshot and we don't want to process the delay twice." AccessProtect wait. self primSignal: nil atMilliseconds: 0. self saveResumptionTimes. ! ! !Delay class methodsFor: 'snapshotting' stamp: 'jm 9/11/97 15:01'! startUp "Restart active delay, if any, when resuming a snapshot." self restoreResumptionTimes. ActiveDelay == nil ifFalse: [ActiveDelay activate]. AccessProtect signal. ! ! !Delay class methodsFor: 'timer process' stamp: 'jm 9/11/97 15:15'! startTimerInterruptWatcher "Reset the class variables that keep track of active Delays and re-start the timer interrupt watcher process. Any currently scheduled delays are forgotten." "Delay startTimerInterruptWatcher" | p | self primSignal: nil atMilliseconds: 0. TimingSemaphore == nil ifFalse: [TimingSemaphore terminateProcess]. TimingSemaphore _ Semaphore new. AccessProtect _ Semaphore forMutualExclusion. SuspendedDelays _ SortedCollection sortBlock: [:d1 :d2 | d1 resumptionTime <= d2 resumptionTime]. ActiveDelay _ nil. p _ [self timerInterruptWatcher] newProcess. p priority: Processor timingPriority. p resume. ! ! !Delay class methodsFor: 'timer process' stamp: 'jm 9/11/97 15:13'! timerInterruptWatcher "This loop runs in its own process. It waits for a timer interrupt and wakes up the active delay. Note that timer interrupts are only enabled when there are active delays." [true] whileTrue: [ TimingSemaphore wait. AccessProtect critical: [ ActiveDelay == nil ifFalse: [ ActiveDelay signalWaitingProcess. Time millisecondClockValue < ActiveDelayStartTime ifTrue: [ "clock wrapped" self saveResumptionTimes. self restoreResumptionTimes]]. SuspendedDelays isEmpty ifTrue: [ ActiveDelay _ nil. ActiveDelayStartTime _ nil] ifFalse: [ SuspendedDelays removeFirst activate]]]. ! ! !Delay class methodsFor: 'example' stamp: 'jm 9/11/97 11:23'! testDelayOf: delay for: testCount rect: r "Delay testDelayOf: 100 for: 20 rect: (10@10 extent: 30@30). Delay testDelayOf: 400 for: 20 rect: (50@10 extent: 30@30)." | onDelay offDelay | onDelay _ Delay forMilliseconds: 50. offDelay _ Delay forMilliseconds: delay - 50. Display fillBlack: r. [1 to: testCount do: [:i | Display fillWhite: r. onDelay wait. Display reverse: r. offDelay wait]. ] forkAt: Processor userInterruptPriority. ! ! !Delay class methodsFor: 'primitives' stamp: 'jm 9/11/97 10:54'! primSignal: aSemaphore atMilliseconds: aSmallInteger "Signal the semaphore when the millisecond clock reaches the value of the second argument. Fail if the first argument is neither a Semaphore nor nil. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed ! ! !Delay class methodsFor: 'testing' stamp: 'ar 9/6/1999 17:05'! anyActive "Return true if there is any delay currently active" ^ActiveDelay notNil! ! MacExternalData variableWordSubclass: #DescType instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Applescript'! !DescType commentStamp: '' prior: 0! I am an ExternalData representation of a MacOS DescType or OSID! !DescType methodsFor: 'printing' stamp: 'acg 9/20/1999 14:36'! printOn: aStream aStream nextPutAll: self species asString; nextPutAll: '('''; nextPutAll: (self fourBytesAt: 1); nextPutAll: ''')'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DescType class instanceVariableNames: ''! !DescType class methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 14:29'! fromString: aString ^(super new: 1) fourBytesAt: 1 put: aString; yourself! ! !DescType class methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 14:28'! fromUnsigned: anInteger ^(super new: 1) at: 1 put: anInteger; yourself! ! !DescType class methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 14:37'! of: aString ^self fromString: aString! ! MethodNode subclass: #DialectMethodNode instanceVariableNames: 'dialect ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !DialectMethodNode commentStamp: '' prior: 0! The purpose of this class is to carry along with theinformation in a regular method node the further information that it was parsed from an laternate dialect of Squeak. Which dialect that was is carried as a symbol in the dialect variable.! !DialectMethodNode methodsFor: 'as yet unclassified' stamp: 'di 4/13/2000 20:46'! setDialect: dialectSymbol dialect _ dialectSymbol! ! !DialectMethodNode methodsFor: 'as yet unclassified' stamp: 'di 4/13/2000 21:31'! test: arg1 with: arg2 ^ 3 between: arg1 and: arg2! ! Parser subclass: #DialectParser instanceVariableNames: 'dialect ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !DialectParser commentStamp: '' prior: 0! This Parser is part of a package designed to allow for experiments with alternatives to ST-80 syntax. The particular alternative offered by this parser eliminates colons, left-arrows and up-arrows, and adds prefix keywords for common control constructs. ST-80 SQ-00 ------- ------- a + b between: c and: c + 4 a + b between (c) and (c + 4) a _ 3.0 Set a to 3.0 ^ self size + 3 Return self size + 3 a > b Test (a > b) ifTrue: ['greater'] ifTrue ['greater'] ifFalse: ['less'] ifFalse ['less'] 1 to: 9 do: Repeat (1) to (9) do [:i | Transcript cr; show: i] [Set i. | Transcript cr; show (i)] The use of prefix keywords is currently ad-hoc; in other words they are built into the parser, and there is not a way to define a method pattern to include a prefix keyword. Most of the work has been done to support this, though, as selectors can now have the form #:prefix:kwd1:kwd2: and they will respond appropriately to #keywords and #numArgs. A test method in the class ensures that every method in the system can be pretty-printed in the alternative syntax, and that compiling the resulting text produces exactly the same bytecodes as the original method.! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/23/2000 22:18'! assignment: varNode " 'set' (var) 'to' (expression) => AssignmentNode." | loc | (loc _ varNode assignmentCheck: encoder at: prevMark + requestorOffset) >= 0 ifTrue: [^self notify: 'Cannot store into' at: loc]. varNode nowHasDef. self advance. " to " self expression ifFalse: [^self expected: 'Expression']. parseNode _ AssignmentNode new variable: varNode value: parseNode from: encoder. ^ true! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 6/13/2000 00:34'! blockExpression "[ ({:var} |) (| {temps} |) (statements) ] => BlockNode." | variableNodes temporaryBlockVariables | variableNodes _ OrderedCollection new. "Gather parameters." (self matchToken: 'With') ifTrue: [[self match: #period] whileFalse: [variableNodes addLast: (encoder autoBind: self argumentName)]]. temporaryBlockVariables _ self temporaryBlockVariables. self statements: variableNodes innerBlock: true. parseNode temporaries: temporaryBlockVariables. (self match: #rightBracket) ifFalse: [^ self expected: 'Period or right bracket']. "The scope of the parameters and temporary block variables is no longer active." temporaryBlockVariables do: [:variable | variable scope: -1]. variableNodes do: [:variable | variable scope: -1]! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/22/2000 17:11'! expression ^ self expressionWithInitialKeyword: '' ! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 6/7/2000 09:51'! expressionWithInitialKeyword: kwdIfAny | checkpoint | (hereType == #word and: [here = 'Set' and: [tokenType == #word]]) ifTrue: ["Parse assignment statement 'Set' var 'to' expression" checkpoint _ self checkpoint. self advance. token = 'to' ifTrue: [^ self assignment: self variable] ifFalse: [self revertToCheckpoint: checkpoint]]. self matchKeyword ifTrue: ["It's an initial keyword." kwdIfAny isEmpty ifFalse: [self error: 'compiler logic error']. ^ self expressionWithInitialKeyword: ':' , self advance , ':']. hereType == #leftBrace ifTrue: [self braceExpression] ifFalse: [self primaryExpression ifFalse: [^ false]]. (self messagePart: 3 repeat: true initialKeyword: kwdIfAny) ifTrue: [hereType == #semicolon ifTrue: [self cascade]]. ^ true! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/25/2000 22:36'! matchKeyword "Return true if we are looking at a keyword (and its argument)." hereType == #word ifFalse: [^ false]. tokenType == #leftParenthesis ifTrue: [^ true]. tokenType == #leftBracket ifTrue: [^ true]. tokenType == #leftBrace ifTrue: [^ true]. ^ false! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 6/11/2000 15:27'! matchReturn ^ self matchToken: 'Answer'! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/22/2000 16:56'! messagePart: level repeat: repeat ^ self messagePart: level repeat: repeat initialKeyword: ''! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/25/2000 22:46'! messagePart: level repeat: repeat initialKeyword: kwdIfAny | start receiver selector args precedence words keywordStart | [receiver _ parseNode. (self matchKeyword and: [level >= 3]) ifTrue: [start _ self startOfNextToken. selector _ WriteStream on: (String new: 32). selector nextPutAll: kwdIfAny. args _ OrderedCollection new. words _ OrderedCollection new. [self matchKeyword] whileTrue: [keywordStart _ self startOfNextToken + requestorOffset. selector nextPutAll: self advance , ':'. words addLast: (keywordStart to: hereMark + self previousTokenSize + requestorOffset). self primaryExpression ifFalse: [^ self expected: 'Argument']. args addLast: parseNode]. (Symbol hasInterned: selector contents ifTrue: [ :sym | selector _ sym]) ifFalse: [ selector _ self correctSelector: selector contents wordIntervals: words exprInterval: (start to: self endOfLastToken) ifAbort: [ ^ self fail ] ]. precedence _ 3] ifFalse: [((hereType == #binary or: [hereType == #verticalBar]) and: [level >= 2]) ifTrue: [start _ self startOfNextToken. selector _ self advance asSymbol. self primaryExpression ifFalse: [^self expected: 'Argument']. self messagePart: 1 repeat: true. args _ Array with: parseNode. precedence _ 2] ifFalse: [(hereType == #word and: [(#(leftParenthesis leftBracket leftBrace) includes: tokenType) not]) ifTrue: [start _ self startOfNextToken. selector _ self advance. args _ #(). words _ OrderedCollection with: (start + requestorOffset to: self endOfLastToken + requestorOffset). (Symbol hasInterned: selector ifTrue: [ :sym | selector _ sym]) ifFalse: [ selector _ self correctSelector: selector wordIntervals: words exprInterval: (start to: self endOfLastToken) ifAbort: [ ^ self fail ] ]. precedence _ 1] ifFalse: [^args notNil]]]. parseNode _ MessageNode new receiver: receiver selector: selector arguments: args precedence: precedence from: encoder sourceRange: (start to: self endOfLastToken). repeat] whileTrue: []. ^true! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 5/30/2000 22:01'! newMethodNode ^ DialectMethodNode new setDialect: #SQ00! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 4/5/2000 17:02'! parseArgsAndTemps: aString notifying: req "Parse the argument, aString, notifying req if an error occurs. Otherwise, answer a two-element Array containing Arrays of strings (the argument names and temporary variable names)." aString == nil ifTrue: [^#()]. doitFlag _ false. "Don't really know if a doit or not!!" ^self initPattern: aString notifying: req return: [:pattern | (pattern at: 2) , self temporaries]! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 6/11/2000 15:27'! pattern: fromDoit inContext: ctxt " unarySelector | binarySelector arg | keyword arg {keyword arg} => {selector, arguments, precedence}." | args selector checkpoint | doitFlag _ fromDoit. fromDoit ifTrue: [ctxt == nil ifTrue: [^ {#DoIt. {}. 1}] ifFalse: [^ {#DoItIn:. {encoder encodeVariable: 'homeContext'}. 3}]]. "NOTE: there is now an ambiguity between keywordSelector (argName) -and- unarySelector (first expression). Also, there is an amibuity (if there are no temp declarations) between keywordSelector (argName) -and- PrefixKeyword (some expression). We use duct tape for now." (hereType == #word and: [tokenType == #leftParenthesis]) ifTrue: [checkpoint _ self checkpoint. "in case we have to back out" selector _ WriteStream on: (String new: 32). args _ OrderedCollection new. [hereType == #word and: [tokenType == #leftParenthesis and: [here first isLowercase or: [(#('Test' 'Repeat' 'Answer') includes: here) not]]]] whileTrue: [selector nextPutAll: self advance , ':'. "selector part" self advance. "open paren" (args size = 0 and: [tokenType ~~ #rightParenthesis]) ifTrue: ["This is really a unary selector on a method that begins with a parenthesized expression. Back out now" self revertToCheckpoint: checkpoint. ^ {self advance asSymbol. {}. 1}]. args addLast: (encoder bindArg: self argumentName). (self match: #rightParenthesis) ifFalse: [^ self expected: 'right parenthesis']]. ^ {selector contents asSymbol. args. 3}]. hereType == #word ifTrue: [^ {self advance asSymbol. {}. 1}]. (hereType == #binary or: [hereType == #verticalBar]) ifTrue: [selector _ self advance asSymbol. args _ Array with: (encoder bindArg: self argumentName). ^ {selector. args. 2}]. ^ self expected: 'Message pattern'! ! !DialectParser methodsFor: 'as yet unclassified' stamp: 'di 6/13/2000 00:31'! temporaries " [ 'Use' (variable)* '.' ]" | vars theActualText | (self matchToken: #'Use') ifFalse: ["no temps" doitFlag ifTrue: [requestor ifNil: [tempsMark _ 1] ifNotNil: [tempsMark _ requestor selectionInterval first]. ^ #()]. tempsMark _ prevMark + prevToken. tempsMark > 0 ifTrue: [theActualText _ source contents. [tempsMark < theActualText size and: [(theActualText at: tempsMark) isSeparator]] whileTrue: [tempsMark _ tempsMark + 1]]. ^ #()]. vars _ OrderedCollection new. [hereType == #word] whileTrue: [vars addLast: (encoder bindTemp: self advance)]. (self match: #period) ifTrue: [tempsMark _ prevMark. ^ vars]. ^ self expected: 'Period'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DialectParser class instanceVariableNames: ''! !DialectParser class methodsFor: 'as yet unclassified' stamp: 'di 6/7/2000 09:32'! test "DialectParser test" "PrettyPrints the source for every method in the system in the alternative syntax, and then compiles that source and verifies that it generates identical code. No changes are actually made to the system. At the time of this writing, only two methods caused complaints (reported in Transcript and displayed in browse window after running): BalloonEngineSimulation circleCosTable and BalloonEngineSimulation circleSinTable. These are not errors, but merely a case of Floats embedded in literal arrays, and thus not specially checked for roundoff errors. Note that if an error or interruption occurs during execution of this method, the alternativeSyntax preference will be left on. NOTE: Some methods may not compare properly until the system has been recompiled once. Do this by executing... Smalltalk recompileAllFrom: 'AARDVAARK'. " | newCodeString methodNode oldMethod newMethod badOnes n heading | Preferences enable: #printAlternateSyntax. badOnes _ OrderedCollection new. Transcript clear. Smalltalk forgetDoIts. 'Formatting and recompiling all classes...' displayProgressAt: Sensor cursorPoint from: 0 to: CompiledMethod instanceCount during: [:bar | n _ 0. Smalltalk allClassesDo: "{MethodNode} do:" "<- to check one class" [:nonMeta | "Transcript cr; show: nonMeta name." {nonMeta. nonMeta class} do: [:cls | cls selectors do: [:selector | (n _ n+1) \\ 100 = 0 ifTrue: [bar value: n]. newCodeString _ (cls compilerClass new) format: (cls sourceCodeAt: selector) in: cls notifying: nil decorated: Preferences colorWhenPrettyPrinting. heading _ cls organization categoryOfElement: selector. methodNode _ cls compilerClass new compile: newCodeString in: cls notifying: (SyntaxError new category: heading) ifFail: []. newMethod _ methodNode generate: #(0 0 0 0). oldMethod _ cls compiledMethodAt: selector. "Transcript cr; show: cls name , ' ' , selector." oldMethod = newMethod ifFalse: [Transcript cr; show: '***' , cls name , ' ' , selector. oldMethod size = newMethod size ifFalse: [Transcript show: ' difft size']. oldMethod header = newMethod header ifFalse: [Transcript show: ' difft header']. oldMethod literals = newMethod literals ifFalse: [Transcript show: ' difft literals']. Transcript endEntry. badOnes add: cls name , ' ' , selector]]]]. ]. Smalltalk browseMessageList: badOnes asSortedCollection name: 'Formatter Discrepancies'. Preferences disable: #printAlternateSyntax. ! ! TextStream subclass: #DialectStream instanceVariableNames: 'dialect ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !DialectStream methodsFor: 'as yet unclassified' stamp: 'di 4/5/2000 08:48'! dialect ^ dialect! ! !DialectStream methodsFor: 'as yet unclassified' stamp: 'di 4/5/2000 08:48'! setDialect: dialectSymbol dialect _ dialectSymbol! ! !DialectStream methodsFor: 'as yet unclassified' stamp: 'di 4/5/2000 15:36'! withColor: colorSymbol emphasis: emphasisSymbol do: aBlock ^ self withAttributes: {TextColor color: (Color perform: colorSymbol). TextEmphasis perform: emphasisSymbol} do: aBlock! ! !DialectStream methodsFor: 'as yet unclassified' stamp: 'di 6/13/2000 00:48'! withStyleFor: elementType do: aBlock "For each element type, associate a color and emphasis" elementType == #temporaryVariable ifTrue: [^ self withColor: #black emphasis: #normal do: aBlock]. elementType == #methodArgument ifTrue: [^ self withColor: #black emphasis: #normal do: aBlock]. elementType == #methodSelector ifTrue: [^ self withColor: #black emphasis: #bold do: aBlock]. elementType == #blockArgument ifTrue: [^ self withColor: #black emphasis: #normal do: aBlock]. elementType == #comment ifTrue: [^ self withColor: #brown emphasis: #normal do: aBlock]. elementType == #variable ifTrue: [^ self withColor: #black emphasis: #normal do: aBlock]. elementType == #literal ifTrue: [^ self withColor: #blue emphasis: #normal do: aBlock]. elementType == #keyword ifTrue: [^ self withColor: #darkGray emphasis: #bold do: aBlock]. elementType == #prefixKeyword ifTrue: [^ self withColor: #veryDarkGray emphasis: #bold do: aBlock]. elementType == #setOrReturn ifTrue: [^ self withColor: #black emphasis: #bold do: aBlock]. ^ self withColor: #black emphasis: #normal do: aBlock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DialectStream class instanceVariableNames: ''! !DialectStream class methodsFor: 'as yet unclassified' stamp: 'di 4/5/2000 09:49'! dialect: dialectSymbol contents: blockWithArg | stream | stream _ self on: (Text new: 400). stream setDialect: dialectSymbol. blockWithArg value: stream. ^ stream contents! ! Set subclass: #Dictionary instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Unordered'! !Dictionary commentStamp: '' prior: 0! I represent a set of elements that can be viewed from one of two perspectives: a set of associations, or a set of values that are externally named where the name can be any object that responds to =. The external name is referred to as the key.! !Dictionary methodsFor: 'accessing'! associationAt: key ^ self associationAt: key ifAbsent: [self errorKeyNotFound]! ! !Dictionary methodsFor: 'accessing'! associationAt: key ifAbsent: aBlock "Answer the association with the given key. If key is not found, return the result of evaluating aBlock." | index assoc | index _ self findElementOrNil: key. assoc _ array at: index. nil == assoc ifTrue: [ ^ aBlock value ]. ^ assoc! ! !Dictionary methodsFor: 'accessing' stamp: 'tk 4/9/1999 10:22'! associationDeclareAt: aKey "Return an existing association, or create and return a new one. Needed as a single message by ImageSegment.prepareToBeSaved." | existing | ^ self associationAt: aKey ifAbsent: [ (Undeclared includesKey: aKey) ifTrue: [existing _ Undeclared associationAt: aKey. Undeclared removeKey: aKey. self add: existing] ifFalse: [self add: aKey -> false]]! ! !Dictionary methodsFor: 'accessing'! at: key "Answer the value associated with the key." ^ self at: key ifAbsent: [self errorKeyNotFound]! ! !Dictionary methodsFor: 'accessing' stamp: 'sma 5/12/2000 14:59'! at: key ifAbsent: aBlock "Answer the value associated with the key or, if key isn't found, answer the result of evaluating aBlock." | assoc | assoc _ array at: (self findElementOrNil: key). assoc ifNil: [^ aBlock value]. ^ assoc value! ! !Dictionary methodsFor: 'accessing' stamp: 'sma 5/12/2000 15:01'! at: key ifAbsentPut: aBlock "Return the value at the given key. If key is not included in the receiver store the result of evaluating aBlock as new value." ^ self at: key ifAbsent: [self at: key put: aBlock value]! ! !Dictionary methodsFor: 'accessing' stamp: 'jm 5/15/1998 07:20'! at: key ifPresent: aBlock "Lookup the given key in the receiver. If it is present, answer the value of evaluating the given block with the value associated with the key. Otherwise, answer nil." | v | v _ self at: key ifAbsent: [^ nil]. ^ aBlock value: v ! ! !Dictionary methodsFor: 'accessing' stamp: 'sma 5/12/2000 15:00'! at: key put: anObject "Set the value at key to be anObject. If key is not found, create a new entry for key and set is value to anObject. Answer anObject." | index assoc | index _ self findElementOrNil: key. assoc _ array at: index. assoc ifNil: [self atNewIndex: index put: (Association key: key value: anObject)] ifNotNil: [assoc value: anObject]. ^ anObject! ! !Dictionary methodsFor: 'accessing' stamp: 'ar 2/13/1999 21:16'! keyAtIdentityValue: value "Answer the key that is the external name for the argument, value. If there is none, answer nil. Note: There can be multiple keys with the same value. Only one is returned." ^self keyAtIdentityValue: value ifAbsent: [self errorValueNotFound]! ! !Dictionary methodsFor: 'accessing' stamp: 'ar 2/13/1999 21:16'! keyAtIdentityValue: value ifAbsent: exceptionBlock "Answer the key that is the external name for the argument, value. If there is none, answer the result of evaluating exceptionBlock. Note: There can be multiple keys with the same value. Only one is returned." self associationsDo: [:association | value == association value ifTrue: [^association key]]. ^exceptionBlock value! ! !Dictionary methodsFor: 'accessing'! keyAtValue: value "Answer the key that is the external name for the argument, value. If there is none, answer nil." ^self keyAtValue: value ifAbsent: [self errorValueNotFound]! ! !Dictionary methodsFor: 'accessing' stamp: 'tk 2/18/97'! keyAtValue: value ifAbsent: exceptionBlock "Answer the key that is the external name for the argument, value. If there is none, answer the result of evaluating exceptionBlock. : Use =, not ==, so stings like 'this' can be found. Note that MethodDictionary continues to use == so it will be fast." self associationsDo: [:association | value = association value ifTrue: [^association key]]. ^exceptionBlock value! ! !Dictionary methodsFor: 'accessing'! keys "Answer a Set containing the receiver's keys." | aSet | aSet _ Set new: self size. self keysDo: [:key | aSet add: key]. ^ aSet! ! !Dictionary methodsFor: 'accessing' stamp: 'sma 6/18/2000 12:56'! keysSortedSafely "Answer a SortedCollection containing the receiver's keys." | sortedKeys | sortedKeys _ SortedCollection new: self size. sortedKeys sortBlock: [:x :y | "Should really be use compareSafely..." ((x isString and: [y isString]) or: [x isNumber and: [y isNumber]]) ifTrue: [x < y] ifFalse: [x class == y class ifTrue: [x printString < y printString] ifFalse: [x class name < y class name]]]. self keysDo: [:each | sortedKeys addLast: each]. ^ sortedKeys reSort! ! !Dictionary methodsFor: 'accessing' stamp: 'ar 7/11/1999 07:28'! values "Answer a Collection containing the receiver's values." | out | out _ WriteStream on: (Array new: self size). self valuesDo: [:value | out nextPut: value]. ^ out contents! ! !Dictionary methodsFor: 'testing' stamp: 'bf 8/20/1999 15:07'! hasContentsInExplorer ^self isEmpty not! ! !Dictionary methodsFor: 'testing'! includes: anObject self do: [:each | anObject = each ifTrue: [^true]]. ^false! ! !Dictionary methodsFor: 'testing' stamp: 'sw 2/14/2000 14:34'! includesIdentity: anObject "Answer whether anObject is one of the values of the receiver. Contrast #includes: in which there is only an equality check, here there is an identity check" self do: [:each | anObject == each ifTrue: [^ true]]. ^ false! ! !Dictionary methodsFor: 'testing'! includesKey: key "Answer whether the receiver has a key equal to the argument, key." | index | index _ self findElementOrNil: key. (array at: index) == nil ifTrue: [^ false] ifFalse: [^ true]! ! !Dictionary methodsFor: 'testing' stamp: 'sw 3/23/2000 01:12'! keyForIdentity: anObject "If anObject is one of the values of the receive, return its key, else return nil. Contrast #keyAtValue: in which there is only an equality check, here there is an identity check" self associationsDo: [:assoc | assoc value == anObject ifTrue: [^ assoc key]]. ^ nil! ! !Dictionary methodsFor: 'testing'! occurrencesOf: anObject "Answer how many of the receiver's elements are equal to anObject." | count | count _ 0. self do: [:each | anObject = each ifTrue: [count _ count + 1]]. ^count! ! !Dictionary methodsFor: 'adding'! add: anAssociation | index element | index _ self findElementOrNil: anAssociation key. element _ array at: index. element == nil ifTrue: [self atNewIndex: index put: anAssociation] ifFalse: [element value: anAssociation value]. ^ anAssociation! ! !Dictionary methodsFor: 'adding'! declare: key from: aDictionary "Add key to the receiver. If key already exists, do nothing. If aDictionary includes key, then remove it from aDictionary and use its association as the element of the receiver." (self includesKey: key) ifTrue: [^ self]. (aDictionary includesKey: key) ifTrue: [self add: (aDictionary associationAt: key). aDictionary removeKey: key] ifFalse: [self add: key -> nil]! ! !Dictionary methodsFor: 'removing' stamp: 'di 4/4/2000 11:47'! keysAndValuesRemove: keyValueBlock "Removes all entries for which keyValueBlock returns true." "When removing many items, you must not do it while iterating over the dictionary, since it may be changing. This method takes care of tallying the removals in a first pass, and then performing all the deletions afterward. Many places in the sytem could be simplified by using this method." | removals | removals _ OrderedCollection new. self associationsDo: [:assoc | (keyValueBlock value: assoc key value: assoc value) ifTrue: [removals add: assoc key]]. removals do: [:aKey | self removeKey: aKey]! ! !Dictionary methodsFor: 'removing'! remove: anObject self shouldNotImplement! ! !Dictionary methodsFor: 'removing'! remove: anObject ifAbsent: exceptionBlock self shouldNotImplement! ! !Dictionary methodsFor: 'removing'! removeKey: key "Remove key from the receiver. If key is not in the receiver, notify an error." ^ self removeKey: key ifAbsent: [self errorKeyNotFound]! ! !Dictionary methodsFor: 'removing'! removeKey: key ifAbsent: aBlock "Remove key (and its associated value) from the receiver. If key is not in the receiver, answer the result of evaluating aBlock. Otherwise, answer the value externally named by key." | index assoc | index _ self findElementOrNil: key. assoc _ array at: index. assoc == nil ifTrue: [ ^ aBlock value ]. array at: index put: nil. tally _ tally - 1. self fixCollisionsFrom: index. ^ assoc value! ! !Dictionary methodsFor: 'removing'! removeUnreferencedKeys "Undeclared removeUnreferencedKeys" ^ self unreferencedKeys do: [:key | self removeKey: key].! ! !Dictionary methodsFor: 'removing'! unreferencedKeys "TextConstants unreferencedKeys" | n | ^ 'Scanning for references . . .' displayProgressAt: Sensor cursorPoint from: 0 to: self size during: [:bar | n _ 0. self keys select: [:key | bar value: (n _ n+1). (Smalltalk allCallsOn: (self associationAt: key)) isEmpty]]! ! !Dictionary methodsFor: 'enumerating'! associationsDo: aBlock "Evaluate aBlock for each of the receiver's elements (key/value associations)." super do: aBlock! ! !Dictionary methodsFor: 'enumerating'! collect: aBlock "Evaluate aBlock with each of my values as the argument. Collect the resulting values into a collection that is like me. Answer with the new collection." | newCollection | newCollection _ OrderedCollection new: self size. self do: [:each | newCollection add: (aBlock value: each)]. ^ newCollection! ! !Dictionary methodsFor: 'enumerating'! do: aBlock super do: [:assoc | aBlock value: assoc value]! ! !Dictionary methodsFor: 'enumerating' stamp: 'ar 7/11/1999 08:04'! keysAndValuesDo: aBlock ^self associationsDo:[:assoc| aBlock value: assoc key value: assoc value].! ! !Dictionary methodsFor: 'enumerating'! keysDo: aBlock "Evaluate aBlock for each of the receiver's keys." self associationsDo: [:association | aBlock value: association key]! ! !Dictionary methodsFor: 'enumerating'! select: aBlock "Evaluate aBlock with each of my values as the argument. Collect into a new dictionary, only those associations for which aBlock evaluates to true." | newCollection | newCollection _ self species new. self associationsDo: [:each | (aBlock value: each value) ifTrue: [newCollection add: each]]. ^newCollection! ! !Dictionary methodsFor: 'enumerating' stamp: 'ar 7/11/1999 07:29'! valuesDo: aBlock "Evaluate aBlock for each of the receiver's keys." self associationsDo: [:association | aBlock value: association value]! ! !Dictionary methodsFor: 'printing' stamp: 'MPW 1/4/1901 08:33'! flattenOnStream:aStream ^aStream writeDictionary:self. ! ! !Dictionary methodsFor: 'printing' stamp: 'sma 6/1/2000 09:52'! printElementsOn: aStream aStream nextPut: $(. self keysSortedSafely do: [:key | aStream print: key; nextPutAll: '->'; print: (self at: key); space]. aStream nextPut: $)! ! !Dictionary methodsFor: 'printing'! storeOn: aStream | noneYet | aStream nextPutAll: '(('. aStream nextPutAll: self class name. aStream nextPutAll: ' new)'. noneYet _ true. self associationsDo: [:each | noneYet ifTrue: [noneYet _ false] ifFalse: [aStream nextPut: $;]. aStream nextPutAll: ' add: '. aStream store: each]. noneYet ifFalse: [aStream nextPutAll: '; yourself']. aStream nextPut: $)! ! !Dictionary methodsFor: 'private' stamp: 'tk 8/21/97 16:12'! copy "Must copy the associations, or later store will effect both the original and the copy" ^ self shallowCopy withArray: (array collect: [:assoc | assoc ifNil: [nil] ifNotNil: [Association key: assoc key value: assoc value]])! ! !Dictionary methodsFor: 'private'! errorKeyNotFound self error: 'key not found'! ! !Dictionary methodsFor: 'private'! errorValueNotFound self error: 'value not found'! ! !Dictionary methodsFor: 'private'! keyAt: index "May be overridden by subclasses so that fixCollisions will work" | assn | assn _ array at: index. assn == nil ifTrue: [^ nil] ifFalse: [^ assn key]! ! !Dictionary methodsFor: 'private'! noCheckAdd: anObject "Must be defined separately for Dictionary because (self findElementOrNil:) expects a key, not an association. 9/7/96 tk" array at: (self findElementOrNil: anObject key) put: anObject. tally _ tally + 1! ! !Dictionary methodsFor: 'private'! rehash "Smalltalk rehash." | newSelf | newSelf _ self species new: self size. self associationsDo: [:each | newSelf noCheckAdd: each]. array _ newSelf array! ! !Dictionary methodsFor: 'private'! scanFor: anObject "Scan the key array for the first slot containing either a nil (indicating an empty slot) or an element that matches anObject. Answer the index of that slot or zero if no slot is found. This method will be overridden in various subclasses that have different interpretations for matching elements." | element start finish | start _ (anObject hash \\ array size) + 1. finish _ array size. "Search from (hash mod size) to the end." start to: finish do: [:index | ((element _ array at: index) == nil or: [element key = anObject]) ifTrue: [^ index ]]. "Search from 1 to where we started." 1 to: start-1 do: [:index | ((element _ array at: index) == nil or: [element key = anObject]) ifTrue: [^ index ]]. ^ 0 "No match AND no empty slot"! ! !Dictionary methodsFor: 'private'! valueAtNewKey: aKey put: anObject atIndex: index declareFrom: aDictionary "Support for coordinating class variable and global declarations with variables that have been put in Undeclared so as to redirect all references to the undeclared variable." (aDictionary includesKey: aKey) ifTrue: [self atNewIndex: index put: ((aDictionary associationAt: aKey) value: anObject). aDictionary removeKey: aKey] ifFalse: [self atNewIndex: index put: (Association key: aKey value: anObject)]! ! !Dictionary methodsFor: 'user interface' stamp: 'tk 4/12/1998 08:54'! inspect "Open a DictionaryInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." DictionaryInspector openOn: self withEvalPane: true! ! !Dictionary methodsFor: 'user interface' stamp: 'RAA 6/14/2000 18:24'! inspectFormsWithLabel: aLabel "Open a Form Dictionary inspector on the receiver, with the given label." | viewClass aList aGraphicalMenu | self couldOpenInMorphic ifTrue: [aList _ self collect: [:f | f]. aList isEmpty ifTrue: [^ self inform: 'Empty!!']. aGraphicalMenu _ GraphicalDictionaryMenu new initializeFor: nil fromDictionary: self. ^ HandMorph attach: (aGraphicalMenu wrappedInWindowWithTitle: aLabel)]. viewClass _ PluggableTextView. Smalltalk at: #FormInspectView ifPresent: [:formInspectView | viewClass _ formInspectView]. ^ DictionaryInspector openOn: self withEvalPane: true withLabel: aLabel valueViewClass: viewClass! ! !Dictionary methodsFor: 'user interface' stamp: 'tk 4/12/1998 08:54'! inspectWithLabel: aLabel "Open a DictionaryInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." DictionaryInspector openOn: self withEvalPane: true withLabel: aLabel! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Dictionary class instanceVariableNames: ''! !Dictionary class methodsFor: 'instance creation'! newFrom: aDict "Answer an instance of me containing the same associations as aDict. Error if any key appears twice." | newDictionary | newDictionary _ self new: aDict size. aDict associationsDo: [:x | (newDictionary includesKey: x key) ifTrue: [self error: 'Duplicate key: ', x key printString] ifFalse: [newDictionary add: x]]. ^ newDictionary " NewDictionary newFrom: {1->#a. 2->#b. 3->#c} {1->#a. 2->#b. 3->#c} as: NewDictionary NewDictionary newFrom: {1->#a. 2->#b. 1->#c} {1->#a. 2->#b. 1->#c} as: NewDictionary "! ! Inspector subclass: #DictionaryInspector instanceVariableNames: 'keyArray ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Inspector'! !DictionaryInspector methodsFor: 'accessing'! fieldList ^ keyArray collect: [ :key | key printString ]! ! !DictionaryInspector methodsFor: 'accessing'! inspect: aDictionary "Initialize the receiver so that it is inspecting aDictionary. There is no current selection." self initialize. (aDictionary isKindOf: Dictionary) ifFalse: [^ self error: 'DictionaryInspectors can only inspect dictionaries' ]. object _ aDictionary. contents _ ''. self calculateKeyArray! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'tk 4/11/1998 13:09'! addEntry: aKey object at: aKey put: nil. self calculateKeyArray. selectionIndex _ keyArray indexOf: aKey. self changed: #inspectObject. self changed: #fieldList. self update! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'di 3/8/2000 09:14'! calculateKeyArray "Recalculate the KeyArray from the object being inspected" keyArray _ object keysSortedSafely asArray. selectionIndex _ 0. ! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'di 9/22/1998 21:25'! contentsIsString "Hacked so contents empty when deselected" ^ (selectionIndex = 0)! ! !DictionaryInspector methodsFor: 'selecting'! replaceSelectionValue: anObject ^ object at: (keyArray at: selectionIndex) put: anObject! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'ar 12/7/1999 18:12'! selection selectionIndex = 0 ifTrue: [^ '']. ^ object at: (keyArray at: selectionIndex) ifAbsent:[nil]! ! !DictionaryInspector methodsFor: 'selecting'! selectionUnmodifiable "For dicionary inspectors, any selection is modifiable" ^ selectionIndex <= 0! ! !DictionaryInspector methodsFor: 'menu' stamp: 'tk 4/12/1998 08:15'! addEntry | newKey aKey | newKey _ FillInTheBlank request: 'Enter new key, then type RETURN. (Expression will be evaluated for value.) Examples: #Fred ''a string'' 3+4'. aKey _ Compiler evaluate: newKey. object at: aKey put: nil. self calculateKeyArray. selectionIndex _ keyArray indexOf: aKey. self changed: #inspectObject. self changed: #fieldList. self update! ! !DictionaryInspector methodsFor: 'menu' stamp: 'ar 1/15/2001 18:37'! copyName "Copy the name of the current variable, so the user can paste it into the window below and work with is. If collection, do (xxx at: 1). " | sel | sel _ '(self at: ', (String streamContents: [:strm | (keyArray at: selectionIndex) storeOn: strm]) , ')'. Clipboard clipboardText: sel asText. "no undo allowed"! ! !DictionaryInspector methodsFor: 'menu' stamp: 'tk 4/12/1998 09:01'! dictionaryMenu: aMenu ^ aMenu labels: 'inspect copy name references objects pointing to this value add key remove basic inspect' lines: #( 4 6) selections: #(inspectSelection copyName selectionReferences objectReferencesToSelection addEntry removeSelection inspectBasic) ! ! !DictionaryInspector methodsFor: 'menu' stamp: 'tk 4/12/1998 08:19'! removeSelection selectionIndex = 0 ifTrue: [^ self changed: #flash]. object removeKey: (keyArray at: selectionIndex). selectionIndex _ 0. contents _ ''. self calculateKeyArray. self changed: #inspectObject. self changed: #fieldList. self changed: #selection. self changed: #selectionIndex.! ! !DictionaryInspector methodsFor: 'menu' stamp: 'tk 4/12/1998 08:42'! selectionReferences "Create a browser on all references to the association of the current selection." self selectionIndex = 0 ifTrue: [^ self changed: #flash]. object class == MethodDictionary ifTrue: [^ self changed: #flash]. Smalltalk browseAllCallsOn: (object associationAt: (keyArray at: selectionIndex)). ! ! Object subclass: #DigitalSignatureAlgorithm instanceVariableNames: 'randKey randSeed ' classVariableNames: 'HighBitOfByte SmallPrimes ' poolDictionaries: '' category: 'System-Digital Signatures'! !DigitalSignatureAlgorithm commentStamp: '' prior: 0! This class implements the Digital Signature Algorithm (DSA) of the U.S. government's "Digital Signature Standard" (DSS). The DSA algorithm was proposed in 1991 and became a standard in May 1994. The official description is available as a Federal Information Processing Standards Publication (FIPS PUB 186, May 19, 1994). A companion standard, the Secure Hash Standard, or SHS (FIPS PUB 180-1, April 17, 1995), describes a 160-bit message digest algorithm known as the Secure Hash Algorithm (SHA). This message digest is used to compute the document signature. Here's how to use it: 1. The "signer" creates a pair of keys. One of these must be kept private. The other may be freely distributed. For example, it could be built into the signature checking code of an application. 2. When the signer wishes to sign a packet of data (a "message") , he uses the secure hash algorithm to create a 160-bit message digest (hash) which is used as the input to DSA. The result of this is a pair of large numbers called a "signature" that is attached to the original message. 3. When someone receives a signed message purported to have come from the signer, they compute the 160-bit hash of the message and pass that, along with the message signature and the signer's public key, to the signature verification algorithm. If the signature checks, then it is virtually guaranteed that the message originated from someone who had the signer's private key. That is, the message is not a forgery and has not been modified since it was signed. For example, if the message contains a program, and the recipient trusts the signer, then the recipient can run the program with the assurance that it won't do anything harmful. (At least, not intentionally. A digital signature is no guarantee against bugs!! :->) The signer must keep the private key secure, since anyone who has the private key can forge the signer's signature on any message they like. As long as the secret key is not stolen, cryptographers believe it to be virtually impossible either to forge a signature, to find a message that matches an existing sigature, or to discover the signer's private key by analyzing message signatures. Knowing the public key (which, for example, could be recovered from an application that had it built in), does not weaken the security at all. An excellent reference work on digital signatures and cryptography in general is: Schneier, Bruce "Applied Cryptography: Protocols, Algorithms, and Source Code in C" John Wiley and Sons, 1996. I used this book as a guide to implementing many of the numerical algorithms required by DSA. Patents and Export Restrictions: Many digital signature technologies are patented. DSA is also patented, but the patent is owned by the U.S. government which has made DSA available royalty-free. There is a claim that the government patent infringes on an earlier patent by Schnorr, but the government is requiring the use of DSA, so they apparently believe this claim is not strong enough to be a serious threat to their own patent. Most cryptography technology, including digital signature technology, requires an export license for it to be distributed outside the U.S. Recent legislation may have relaxed the export license requirements, but it would be prudent to check the current regulations before exporting this code.! !DigitalSignatureAlgorithm methodsFor: 'initialization' stamp: 'jm 1/11/2000 00:25'! initRandom: randomInteger "Initialize the the secure random number generator with the given value. The argument should be a positive integer of up to 512 bits chosen randomly to avoid someone being able to predict the sequence of random values generated." "Note: The random generator must be initialized before generating a key set or signature. Signature verification does not require initialization of the random generator." randSeed _ 16rEFCDAB8998BADCFE10325476C3D2E1F067452301. "initial seed" randKey _ randomInteger. Transcript show: 'Random seed: ', randomInteger printString; cr. ! ! !DigitalSignatureAlgorithm methodsFor: 'initialization' stamp: 'jm 12/12/1999 23:08'! initRandomFromUser "Ask the user to type a long random string and use the result to seed the secure random number generator." | s k srcIndex | s _ FillInTheBlank request: 'Enter a long random string to seed the random generator.'. k _ LargePositiveInteger new: (s size min: 64). srcIndex _ 0. k digitLength to: 1 by: -1 do: [:i | k digitAt: i put: (s at: (srcIndex _ srcIndex + 1)) asciiValue]. k _ k + (Random new next * 16r7FFFFFFF) asInteger. "a few additional bits randomness" k highBit > 512 ifTrue: [k _ k bitShift: k highBit - 512]. self initRandom: k. ! ! !DigitalSignatureAlgorithm methodsFor: 'initialization' stamp: 'jm 1/11/2000 00:13'! randomBitsFromSoundInput: bitCount "Answer a positive integer with the given number of random bits of 'noise' from a sound input source. Typically, one would use a microphone or line input as the sound source, although many sound cards have enough thermal noise that you get random low-order sample bits even with no microphone connected. Only the least signficant bit of the samples is used. Since not all sound cards support 16-bits of sample resolution, we use the lowest bit that changes." "(1 to: 10) collect: [:i | DigitalSignatureAlgorithm new randomBitsFromSoundInput: 512]" | recorder buf mid samples bitMask randomBits bit | "collect some sound data" recorder _ SoundRecorder new clearRecordedSound. recorder resumeRecording. (Delay forSeconds: 1) wait. recorder stopRecording. buf _ recorder condensedSamples. "grab bitCount samples from the middle" mid _ buf monoSampleCount // 2. samples _ buf copyFrom: mid to: mid + bitCount - 1. "find the least significant bit that varies" bitMask _ 1. [bitMask < 16r10000 and: [(samples collect: [:s | s bitAnd: bitMask]) asSet size < 2]] whileTrue: [bitMask _ bitMask bitShift: 1]. bitMask = 16r10000 ifTrue: [^ self error: 'sound samples do not vary']. "pack the random bits into a positive integer" randomBits _ 0. 1 to: samples size do: [:i | bit _ ((samples at: i) bitAnd: bitMask) = 0 ifTrue: [0] ifFalse: [1]. randomBits _ (randomBits bitShift: 1) + bit]. ^ randomBits ! ! !DigitalSignatureAlgorithm methodsFor: 'public' stamp: 'hh 8/3/2000 18:17'! computeSignatureForMessageHash: hash privateKey: privateKey "Answer the digital signature of the given message hash using the given private key. A signature is a pair of large integers. The private key is an array of four large integers: (p, q, g, x)." | p q g x r s k tmp | p _ privateKey first. q _ privateKey second. g _ privateKey third. x _ privateKey fourth. r _ s _ 0. [r = 0 or: [s = 0]] whileTrue: [ k _ self nextRandom160 \\ q. r _ (g raisedTo: k modulo: p) \\ q. tmp _ (hash + (x * r)) \\ q. s _ ((self inverseOf: k mod: q) * tmp) \\ q]. ^ Array with: r with: s ! ! !DigitalSignatureAlgorithm methodsFor: 'public' stamp: 'hh 8/3/2000 18:19'! generateKeySet "Generate and answer a key set for DSA. The result is a pair (). Each key is an array of four large integers. The private key is (p, q, g, x); the public one is (p, q, g, y). The signer must be sure to record (p, q, g, x), and must keep x secret to prevent someone from forging their signature." "Note: Key generation can take some time. Open a transcript so you can see what's happening and take a coffee break!!" | qAndPandS q p exp g h x y | qAndPandS _ self generateQandP. Transcript show: 'Computing g...'. q _ qAndPandS first. p _ qAndPandS second. exp _ (p - 1) / q. h _ 2. [g _ h raisedTo: exp modulo: p. g = 1] whileTrue: [h _ h + 1]. Transcript show: 'done.'; cr. Transcript show: 'Computing x and y...'. x _ self nextRandom160. y _ g raisedTo: x modulo: p. Transcript show: 'done.'; cr. Transcript show: 'Key generation complete!!'; cr. ^ Array with: (Array with: p with: q with: g with: x) with: (Array with: p with: q with: g with: y) ! ! !DigitalSignatureAlgorithm methodsFor: 'public' stamp: 'jm 12/14/1999 13:34'! signatureToString: aSignature "Answer a string representation of the given signature. This string can be parsed using the stringToSignature: method." | s hex | s _ WriteStream on: (String new: 2000). s nextPutAll: '[DSA digital signature '. hex _ aSignature first printStringBase: 16. s nextPutAll: (hex copyFrom: 4 to: hex size). s space. hex _ aSignature second printStringBase: 16. s nextPutAll: (hex copyFrom: 4 to: hex size). s nextPutAll: ']'. ^ s contents ! ! !DigitalSignatureAlgorithm methodsFor: 'public' stamp: 'jm 12/14/1999 13:33'! stringToSignature: aString "Answer the signature stored in the given string. A signature string has the format: '[DSA digital signature ]' where and are large positive integers represented by strings of hexidecimal digits." | prefix stream r s | prefix _ '[DSA digital signature '. (aString beginsWith: prefix) ifFalse: [self error: 'bad signature prefix']. stream _ ReadStream on: aString. stream position: prefix size. r _ Integer readFrom: stream base: 16. stream next. s _ Integer readFrom: stream base: 16. ^ Array with: r with: s ! ! !DigitalSignatureAlgorithm methodsFor: 'public' stamp: 'hh 8/3/2000 18:18'! verifySignature: aSignature ofMessageHash: hash publicKey: publicKey "Answer true if the given signature is the authentic signature of the given message hash. That is, if the signature must have been computed using the private key set corresponding to the given public key. The public key is an array of four large integers: (p, q, g, y)." | p q g y r s w u1 u2 v0 v | p _ publicKey first. q _ publicKey second. g _ publicKey third. y _ publicKey fourth. r _ aSignature first. s _ aSignature last. ((r > 0) and: [r < q]) ifFalse: [^ false]. "reject" ((s > 0) and: [s < q]) ifFalse: [^ false]. "reject" w _ self inverseOf: s mod: q. u1 _ (hash * w) \\ q. u2 _ (r * w) \\ q. v0 _ (g raisedTo: u1 modulo: p) * (y raisedTo: u2 modulo: p). v _ ( v0 \\ p) \\ q. ^ v = r ! ! !DigitalSignatureAlgorithm methodsFor: 'large integer arithmetic' stamp: 'jm 12/9/1999 21:49'! inverseOf: x mod: n "Answer the inverse of x modulus n. That is, the integer y such that (x * y) \\ n is 1. Both x and n must be positive, and it is assumed that x < n and that x and n are integers." "Details: Use the extended Euclidean algorithm, Schneier, p. 247." | v u k u1 u2 u3 t1 t2 t3 tmp | ((x <= 0) or: [n <= 0]) ifTrue: [self error: 'x and n must be greater than zero']. x >= n ifTrue: [self error: 'x must be < n']. v _ x. u _ n. k _ 0. [x even and: [n even and: [u > 0]]] whileTrue: [ "eliminate common factors of two" k _ k + 1. u _ u bitShift: -1. v _ v bitShift: -1]. u1 _ 1. u2 _ 0. u3 _ u. t1 _ v. t2 _ u - 1. t3 _ v. [ [u3 even ifTrue: [ ((u1 odd) or: [u2 odd]) ifTrue: [ u1 _ u1 + v. u2 _ u2 + u]. u1 _ u1 bitShift: -1. u2 _ u2 bitShift: -1. u3 _ u3 bitShift: -1]. ((t3 even) or: [u3 < t3]) ifTrue: [ tmp _ u1. u1 _ t1. t1 _ tmp. tmp _ u2. u2 _ t2. t2 _ tmp. tmp _ u3. u3 _ t3. t3 _ tmp]. u3 even and: [u3 > 0]] whileTrue: ["loop while u3 is even"]. [((u1 < t1) or: [u2 < t2]) and: [u1 > 0]] whileTrue: [ u1 _ u1 + v. u2 _ u2 + u]. u1 _ u1 - t1. u2 _ u2 - t2. u3 _ u3 - t3. t3 > 0] whileTrue: ["loop while t3 > 0"]. [u1 >= v and: [u2 >= u]] whileTrue: [ u1 _ u1 - v. u2 _ u2 - u]. u1 _ u1 bitShift: k. u2 _ u2 bitShift: k. u3 _ u3 bitShift: k. u3 = 1 ifFalse: [self error: 'no inverse']. ^ u - u2 ! ! !DigitalSignatureAlgorithm methodsFor: 'large integer arithmetic' stamp: 'hh 8/3/2000 18:18'! isProbablyPrime: p "Answer true if p is prime with very high probability. Such a number is sometimes called an 'industrial grade prime'--a large number that is so extremely likely to be prime that it can assumed that it actually is prime for all practical purposes. This implementation uses the Rabin-Miller algorithm (Schneier, p. 159)." | iterations factor pMinusOne b m r a j z couldBePrime | iterations _ 50. "Note: The DSA spec requires >50 iterations; Schneier says 5 are enough (p. 260)" "quick elimination: check for p divisible by a small prime" SmallPrimes ifNil: [ "generate list of small primes > 2" SmallPrimes _ Integer primesUpTo: 2000. SmallPrimes _ SmallPrimes copyFrom: 2 to: SmallPrimes size]. factor _ SmallPrimes detect: [:f | (p \\ f) = 0] ifNone: [nil]. factor ifNotNil: [^ p = factor]. pMinusOne _ p - 1. b _ self logOfLargestPowerOfTwoDividing: pMinusOne. m _ pMinusOne // (2 raisedTo: b). "Assert: pMinusOne = m * (2 raisedTo: b) and m is odd" Transcript show: ' Prime test pass '. r _ Random new. 1 to: iterations do: [:i | Transcript show: i printString; space. a _ (r next * 16rFFFFFF) truncated. j _ 0. z _ (a raisedTo: m modulo: p) normalize. couldBePrime _ z = 1. [couldBePrime] whileFalse: [ z = 1 ifTrue: [Transcript show: 'failed!!'; cr. ^ false]. "not prime" z = pMinusOne ifTrue: [couldBePrime _ true] ifFalse: [ (j _ j + 1) < b ifTrue: [z _ (z * z) \\ p] ifFalse: [Transcript show: 'failed!!'; cr. ^ false]]]]. "not prime" Transcript show: 'passed!!'; cr. ^ true "passed all tests; probably prime" ! ! !DigitalSignatureAlgorithm methodsFor: 'private' stamp: 'raa 5/30/2000 15:47'! generateQandP "Generate the two industrial-grade primes, q (160-bits) and p (512-bit) needed to build a key set. Answer the array (q, p, s), where s is the seed that from which q and p were created. This seed is normally discarded, but can be used to verify the key generation process if desired." | pBits halfTwoToTheP chunkCount sAndq q twoQ n c w x p s | pBits _ 512. "desired size of p in bits" halfTwoToTheP _ 2 raisedTo: (pBits - 1). chunkCount _ pBits // 160. Transcript show: 'Searching for primes q and p...'; cr. [true] whileTrue: [ sAndq _ self generateSandQ. Transcript show: ' Found a candidate q.'; cr. s _ sAndq first. q _ sAndq last. twoQ _ q bitShift: 1. n _ 2. c _ 0. [c < 4096] whileTrue: [ w _ self generateRandomLength: pBits s: s n: n. x _ w + halfTwoToTheP. p _ (x - ( x \\ twoQ)) + 1. p highBit = pBits ifTrue: [ Transcript show: ' Testing potential p ', (c + 1) printString, '...'; cr. (self isProbablyPrime: p) ifTrue: [ Transcript show: ' Found p!!'; cr. ^ Array with: q with: p with: s]]. n _ n + chunkCount + 1. c _ c + 1]]. ! ! !DigitalSignatureAlgorithm methodsFor: 'private' stamp: 'jm 12/13/1999 16:36'! generateRandomLength: bitLength s: s n: n "Answer a random number of bitLength bits generated using the secure hash algorithm." | sha out count extraBits v | sha _ SecureHashAlgorithm new. out _ 0. count _ (bitLength // 160). extraBits _ bitLength - (count * 160). 0 to: count do: [:k | v _ sha hashInteger: (s + n + k). k = count ifTrue: [ v _ v - ((v >> extraBits) << extraBits)]. out _ out bitOr: (v bitShift: (160 * k))]. ^ out ! ! !DigitalSignatureAlgorithm methodsFor: 'private' stamp: 'raa 5/30/2000 15:46'! generateSandQ "Generate a 160-bit random seed s and an industrial grade prime q." | hasher s sPlusOne u q | hasher _ SecureHashAlgorithm new. [true] whileTrue: [ s _ self nextRandom160. sPlusOne _ s + 1. sPlusOne highBit > 160 ifTrue: [sPlusOne _ sPlusOne \\ (2 raisedTo: 160)]. u _ (hasher hashInteger: s) bitXor: (hasher hashInteger: sPlusOne). q _ u bitOr: ((1 bitShift: 159) bitOr: 1). (self isProbablyPrime: q) ifTrue: [^ Array with: s with: q]]. ! ! !DigitalSignatureAlgorithm methodsFor: 'private' stamp: 'jm 12/13/1999 11:12'! logOfLargestPowerOfTwoDividing: aPositiveInteger "Answer the base-2 log of the largest power of two that divides the given integer. For example, the largest power of two that divides 24 is 8, whose log base-2 is 3. Do this efficiently even when the given number is a large integer. Assume that the given integer is > 0." "DigitalSignatureAlgorithm new largestPowerOfTwoDividing: (32 * 3)" | digitIndex power d | digitIndex _ (1 to: aPositiveInteger digitLength) detect: [:i | (aPositiveInteger digitAt: i) ~= 0]. power _ (digitIndex - 1) * 8. d _ aPositiveInteger digitAt: digitIndex. [d odd] whileFalse: [ power _ power + 1. d _ d bitShift: -1]. ^ power ! ! !DigitalSignatureAlgorithm methodsFor: 'private' stamp: 'jm 12/13/1999 14:39'! nextRandom160 "Answer a newly generated 160-bit random number in the range [1..(2^160 - 1)]." "Details: Try again in the extremely unlikely chance that zero is encountered." | result | result _ 0. [result = 0] whileTrue: [ result _ SecureHashAlgorithm new hashInteger: randKey seed: randSeed. randKey _ randKey + result + 1]. ^ result ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DigitalSignatureAlgorithm class instanceVariableNames: ''! !DigitalSignatureAlgorithm class methodsFor: 'class initialization' stamp: 'jm 12/21/1999 19:15'! initialize "DigitalSignatureAlgorithm initialize" "SmallPrimes is a list of small primes greater than two." SmallPrimes _ Integer primesUpTo: 2000. SmallPrimes _ SmallPrimes copyFrom: 2 to: SmallPrimes size. "HighBitOfByte maps a byte to the index of its top non-zero bit." HighBitOfByte _ (0 to: 255) collect: [:byte | byte highBit]. ! ! !DigitalSignatureAlgorithm class methodsFor: 'public' stamp: 'jm 1/11/2000 00:08'! generateKeySet "Generate and answer a key set for code signing. The result is a pair (). Each key is an array of four large integers. The signer must be sure to record this keys set and must keep the private key secret to prevent someone from forging their signature." "Note: Key generation can take some time. Open a transcript so you can see what's happening and take a coffee break!!" "Note: Unguessable random numbers are needed for key generation. The user will be prompted to type a really long random string (two or three lines) to initialize the random number generator before generating a key set. A different random string should be typed for every session; it is not a password and we wish to produce different random number streams." "DigitalSignatureAlgorithm generateKeySet" | dsa | dsa _ DigitalSignatureAlgorithm new. (self confirm: 'Shall I seed the random generator from the current sound input?') ifTrue: [dsa initRandom: (dsa randomBitsFromSoundInput: 512)] ifFalse: [dsa initRandomFromUser]. ^ dsa generateKeySet ! ! !DigitalSignatureAlgorithm class methodsFor: 'public' stamp: 'jm 12/22/1999 11:18'! sign: aStringOrStream privateKey: privateKey "Sign the given message (a stream or string) and answer a signature string." "Note: Unguessable random numbers are needed for message signing. The user will be prompted to type a really long random string (two or three lines) to initialize the random number generator before signing a message. A different random string should be typed for every session; it is not a password and we wish to produce different random number streams." | dsa hasher h sig | dsa _ DigitalSignatureAlgorithm new. dsa initRandomFromUser. hasher _ SecureHashAlgorithm new. (aStringOrStream class isBytes) ifTrue: [h _ hasher hashMessage: aStringOrStream] ifFalse: [h _ hasher hashStream: aStringOrStream]. sig _ dsa computeSignatureForMessageHash: h privateKey: privateKey. ^ dsa signatureToString: sig ! ! !DigitalSignatureAlgorithm class methodsFor: 'public' stamp: 'RAA 5/31/2000 08:46'! sign: aStringOrStream privateKey: privateKey dsa: dsa "Sign the given message (a stream or string) and answer a signature string." "Note: Unguessable random numbers are needed for message signing. The user will be prompted to type a really long random string (two or three lines) to initialize the random number generator before signing a message. A different random string should be typed for every session; it is not a password and we wish to produce different random number streams." | hasher h sig | hasher _ SecureHashAlgorithm new. (aStringOrStream class isBytes) ifTrue: [h _ hasher hashMessage: aStringOrStream] ifFalse: [h _ hasher hashStream: aStringOrStream]. sig _ dsa computeSignatureForMessageHash: h privateKey: privateKey. ^ dsa signatureToString: sig ! ! !DigitalSignatureAlgorithm class methodsFor: 'public' stamp: 'jm 12/22/1999 11:20'! verify: signatureString isSignatureOf: aStringOrStream publicKey: publicKey "Answer true if the given signature string signs the given message (a stream or string)." "Note: Random numbers are not needed for signature verification; thus, there is no need to call initRandomFromUser before verifying a signature." | dsa hasher h sig | dsa _ DigitalSignatureAlgorithm new. hasher _ SecureHashAlgorithm new. (aStringOrStream class isBytes) ifTrue: [h _ hasher hashMessage: aStringOrStream] ifFalse: [h _ hasher hashStream: aStringOrStream]. sig _ dsa stringToSignature: signatureString. ^ dsa verifySignature: sig ofMessageHash: h publicKey: publicKey ! ! !DigitalSignatureAlgorithm class methodsFor: 'examples' stamp: 'jm 12/22/1999 11:23'! example "Example of signing a message and verifying its signature." "Note: Secure random numbers are needed for key generation and message signing, but not for signature verification. There is no need to call initRandomFromUser if you are merely checking a signature." "DigitalSignatureAlgorithm example" | msg keys sig | msg _ 'This is a test...'. keys _ self testKeySet. sig _ self sign: msg privateKey: keys first. self inform: 'Signature created'. (self verify: sig isSignatureOf: msg publicKey: keys last) ifTrue: [self inform: 'Signature verified.'] ifFalse: [self error: 'ERROR!! Signature verification failed']. ! ! !DigitalSignatureAlgorithm class methodsFor: 'examples' stamp: 'mdr 8/31/2000 18:43'! testExamplesFromDisk "verify messages from file on disk" "Note: Secure random numbers are needed for key generation and message signing, but not for signature verification. There is no need to call initRandomFromUser if you are merely checking a signature." "DigitalSignatureAlgorithm testExamplesFromDisk" | msg sig file publicKey | file _ FileStream readOnlyFileNamed: 'dsa.test.out'. [ [file atEnd] whileFalse: [ sig _ file nextChunk. msg _ file nextChunk. publicKey _ Compiler evaluate: file nextChunk. (self verify: sig isSignatureOf: msg publicKey: publicKey) ifTrue: [ Transcript show: 'SUCCESS: ',msg; cr. ] ifFalse: [ self error: 'ERROR!! Signature verification failed' ]. ]. ] ensure: [file close] ! ! !DigitalSignatureAlgorithm class methodsFor: 'examples' stamp: 'jm 12/22/1999 11:28'! testKeySet "Answer a pair of keys for testing. The first key is the private key, the second one is the public key." "WARNING: This test key set is public should be used only for testing!! In a real application, the user would create a set of keys using generateKeySet and would keep the private key secret." ^ #( (8343811888543852523216773185009428259187948644369498021763210776677854991854533186365944349987509452133156416880596803846631577352387751880552969116768071 1197175832754339660404549606408619548226315875117 1433467472198821951822151391684734233265646022897503720591270330985699984763922266163182803556189497900262038518780931942996381297743579119123094520048965 957348690772296812) (8343811888543852523216773185009428259187948644369498021763210776677854991854533186365944349987509452133156416880596803846631577352387751880552969116768071 1197175832754339660404549606408619548226315875117 1433467472198821951822151391684734233265646022897503720591270330985699984763922266163182803556189497900262038518780931942996381297743579119123094520048965 4645213122572190617807944614677917601101008235397095646475699959851618402406173485853587185431290863173614335452934961425661774118334228449202337038283799)) ! ! !DigitalSignatureAlgorithm class methodsFor: 'examples' stamp: 'RAA 5/31/2000 08:46'! timeDecode: count "Example of signing a message and verifying its signature." "Note: Secure random numbers are needed for key generation and message signing, but not for signature verification. There is no need to call initRandomFromUser if you are merely checking a signature." "DigitalSignatureAlgorithm timeDecode: 20" | msg keys sig s dsa | dsa _ DigitalSignatureAlgorithm new. dsa initRandomFromUser. #(1 10 100 1000 10000 100000) do: [ :extraLen | s _ String new: extraLen. 1 to: s size do: [ :i | s at: i put: (Character value: 200 atRandom)]. msg _ 'This is a test...',s. keys _ self testKeySet. sig _ self sign: msg privateKey: keys first dsa: dsa. "self inform: 'Signature created'." self timeDirect: [ count timesRepeat: [ (self verify: sig isSignatureOf: msg publicKey: keys last) ifFalse: [self error: 'ERROR!! Signature verification failed']. ]. ] as: 'verify msgLen = ',msg size printString count: count ]. ! ! !DigitalSignatureAlgorithm class methodsFor: 'examples' stamp: 'RAA 5/31/2000 13:13'! writeExamplesToDisk "Example of signing a message and verifying its signature. Used to create samples from one implementation that could later be tested with a different implementation" "Note: Secure random numbers are needed for key generation and message signing, but not for signature verification. There is no need to call initRandomFromUser if you are merely checking a signature." "DigitalSignatureAlgorithm writeExamplesToDisk" | sig file keyList dsa msgList | dsa _ DigitalSignatureAlgorithm new. dsa initRandomFromUser. self inform: 'About to generate 5 key sets. Will take a while'. keyList _ {self testKeySet},((1 to: 5) collect: [ :ignore | self generateKeySet]). msgList _ {'This is a test...'. 'This is the second test period.'. 'And finally, a third message'}. file _ FileStream newFileNamed: 'dsa.test.out'. [ msgList do: [ :msg | keyList do: [ :keys | sig _ self sign: msg privateKey: keys first dsa: dsa. (self verify: sig isSignatureOf: msg publicKey: keys last) ifTrue: [ file nextChunkPut: sig; nextChunkPut: msg; nextChunkPut: keys last storeString. ] ifFalse: [ self error: 'ERROR!! Signature verification failed' ]. ]. ]. ] ensure: [file close] ! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:35'! runTiming " DigitalSignatureAlgorithm runTiming " | results ops modeNames | modeNames _ #('standard dsa' 'standard integer' 'digitDiv:neg:'). results _ OrderedCollection new. 1 to: 3 do: [ :mode | results add: (DigitalSignatureAlgorithm timeMultiply: 100000 mode: mode),{mode}. results add: (DigitalSignatureAlgorithm timeRemainder: 100000 mode: mode),{mode}. results add: (DigitalSignatureAlgorithm timeToDivide: 100000 mode: mode),{mode}. ]. ops _ (results collect: [ :each | each second]) asSet asSortedCollection. ops do: [ :eachOp | results do: [ :eachResult | eachResult second = eachOp ifTrue: [ Transcript show: eachResult first asStringWithCommas,' ', eachResult second ,' took ', eachResult third asStringWithCommas,' ms using ', (modeNames at: eachResult fourth); cr ]. ]. Transcript cr. ]. ! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:21'! time: aBlock as: aString count: anInteger ^{anInteger. aString. (Time millisecondsToRun: aBlock)}! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:40'! timeDirect: aBlock as: aString count: anInteger Transcript show: anInteger asStringWithCommas,' ', aString ,' took ', (Time millisecondsToRun: aBlock) asStringWithCommas,' ms'; cr ! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:18'! timeMultiply: iterationCount mode: mode "Exercise the multiply primitive on iterationCount pairs of random 60 bit integers." "DigitalSignatureAlgorithm timeMultiply: 100000 mode: 1" | dsa r x y | dsa _ DigitalSignatureAlgorithm new. r _ Random new. x _ ((r next * 16r3FFFFFFF highBit) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. y _ ((r next * 16r3FFFFFFF) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. ^self time: [ iterationCount timesRepeat: [ mode = 1 ifTrue: [dsa multiply: x by: y]. mode = 2 ifTrue: [x * y]. ]. ] as: 'multiply' count: iterationCount ! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:19'! timeRemainder: iterationCount mode: mode "Exercise the remainder method on iterationCount pairs of random 60 bit integers." "DigitalSignatureAlgorithm timeRemainder: 100000 mode: 1" | dsa r c d tmp | dsa _ DigitalSignatureAlgorithm new. r _ Random new. c _ ((r next * 16r3FFFFFFF highBit) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. d _ ((r next * 16r3FFFFFFF) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. c < d ifTrue: [tmp _ c. c _ d. d _ tmp]. ^self time: [ iterationCount timesRepeat: [ mode = 1 ifTrue: [dsa remainder: c mod: d]. mode = 2 ifTrue: [c \\ d]. mode = 3 ifTrue: [(c digitDiv: d neg: false) second]. ]. ] as: 'remainder' count: iterationCount ! ! !DigitalSignatureAlgorithm class methodsFor: 'testing' stamp: 'RAA 5/31/2000 08:19'! timeToDivide: iterationCount mode: mode "Exercise the divide primitive on iterationCount pairs of random 60 bit integers." "DigitalSignatureAlgorithm timeToDivide: 100000 mode: 1" | dsa r c d tmp | dsa _ DigitalSignatureAlgorithm new. r _ Random new. c _ ((r next * 16r3FFFFFFF highBit) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. d _ ((r next * 16r3FFFFFFF) asInteger bitShift: 30) + (r next * 16r3FFFFFFF) asInteger. c < d ifTrue: [tmp _ c. c _ d. d _ tmp]. ^self time: [ iterationCount timesRepeat: [ mode = 1 ifTrue: [dsa divide: c by: d]. mode = 2 ifTrue: [c // d. c \\ d]. mode = 3 ifTrue: [(c digitDiv: d neg: false) second]. ]. ] as: 'divide' count: iterationCount ! ! ArrayedCollection subclass: #DirectoryEntry instanceVariableNames: 'name creationTime modificationTime dirFlag fileSize ' classVariableNames: '' poolDictionaries: '' category: 'System-Files'! !DirectoryEntry commentStamp: '' prior: 0! an entry in a directory; a reference to either a file or a directory.! !DirectoryEntry methodsFor: 'access' stamp: 'ls 7/15/1998 21:37'! creationTime "time the entry was created. (what's its type?)" ^creationTime! ! !DirectoryEntry methodsFor: 'access' stamp: 'ls 7/15/1998 21:38'! fileSize "size of the entry, if it's a file" ^fileSize! ! !DirectoryEntry methodsFor: 'access' stamp: 'ls 7/15/1998 21:38'! isDirectory "whether this entry represents a directory" ^dirFlag! ! !DirectoryEntry methodsFor: 'access' stamp: 'ls 7/15/1998 21:37'! modificationTime "time the entry was last modified" ^modificationTime! ! !DirectoryEntry methodsFor: 'access' stamp: 'ls 7/15/1998 21:37'! name "name of the entry" ^name! ! !DirectoryEntry methodsFor: 'access-compatibility' stamp: 'ls 7/15/1998 22:29'! at: index "compatibility interface" "self halt: 'old-style access to DirectoryEntry'" index = 1 ifTrue: [ ^self name ]. index = 2 ifTrue: [ ^self creationTime ]. index = 3 ifTrue: [ ^self modificationTime ]. index = 4 ifTrue:[ ^self isDirectory ]. index = 5 ifTrue:[ ^self fileSize ]. self error: 'invalid index specified'.! ! !DirectoryEntry methodsFor: 'access-compatibility' stamp: 'ls 7/15/1998 22:16'! size ^5! ! !DirectoryEntry methodsFor: 'private-initialization' stamp: 'ls 7/15/1998 21:42'! privateName: name0 creationTime: creationTime0 modificationTime: modificationTime0 isDirectory: isDirectory0 fileSize: fileSize0 name _ name0. creationTime _ creationTime0. modificationTime _ modificationTime0. dirFlag _ isDirectory0. fileSize _ fileSize0.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DirectoryEntry class instanceVariableNames: ''! !DirectoryEntry class methodsFor: 'instance creation' stamp: 'ls 7/15/1998 21:42'! fromArray: array ^self name: (array at: 1) creationTime: (array at: 2) modificationTime: (array at: 3) isDirectory: (array at: 4) fileSize: (array at: 5) ! ! !DirectoryEntry class methodsFor: 'instance creation' stamp: 'ls 7/15/1998 21:41'! name: name0 creationTime: creationTime modificationTime: modificationTime isDirectory: isDirectory fileSize: fileSize ^self new privateName: name0 creationTime: creationTime modificationTime: modificationTime isDirectory: isDirectory fileSize: fileSize! ! Object subclass: #Discussion instanceVariableNames: 'notes title description relatedURL ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !Discussion commentStamp: '' prior: 0! A Discussion has some header information and a collection of related notes.! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:25'! addNote: aNote notes isNil ifTrue: [notes _ OrderedCollection new.]. notes add: aNote. ^notes size ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:29'! at: aKey ^notes at: aKey! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:24'! at: aKey addNote: aNote notes isNil ifTrue: [notes _ Dictionary new.]. notes at: aKey put: aNote. ! ! !Discussion methodsFor: 'access' stamp: 'TPR 7/21/1998 10:57'! author ^self description! ! !Discussion methodsFor: 'access' stamp: 'TPR 7/21/1998 10:57'! children ^#() ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! description ^description! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! description: aString description _ aString! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:20'! notes ^notes ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! relatedURL ^relatedURL! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! relatedURL: aString relatedURL _ aString! ! !Discussion methodsFor: 'access' stamp: 'mjg 12/8/97 11:11'! status | reply | reply _ WriteStream on: String new. reply nextPutAll: 'Number of notes: ', (notes size printString). notes size > 0 ifTrue: [reply nextPutAll: '. Last note: ',(notes last timestamp).]. ^reply contents! ! !Discussion methodsFor: 'access' stamp: 'TPR 7/21/1998 10:45'! timestamp ^String new:0! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:20'! title ^title! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! title: aString title _ aString! ! !Discussion methodsFor: 'access' stamp: 'TPR 7/21/1998 10:53'! url "fake the url as the title" ^'Comment.', self title! ! Object subclass: #DiskProxy instanceVariableNames: 'globalObjectName preSelector constructorSelector constructorArgs ' classVariableNames: '' poolDictionaries: '' category: 'System-Object Storage'! !DiskProxy commentStamp: '' prior: 0! A DiskProxy is an externalized form of an object to write on a DataStream. It contains a "constructor" message to regenerate the object, in context, when sent a comeFullyUpOnReload message (i.e. "internalize"). We are now using DiskProxy for shared system objects like StrikeFonts. The idea is to define, for each kind of object that needs special externalization, a class method that will internalize the object by reconstructing it from its defining state. We call this a "constructor" method. Then externalize such an object as a frozen message that invokes this method--a DiskProxy. (Here is the old comment: Constructing a new object is good for any object that (1) can not be externalized simply by snapshotting and reloading its instance variables (like a CompiledMethod or a Picture), or (2) wants to be free to evolve its internal representation without making stored instances obsolete (and dangerous). Snapshotting and reloading an object"s instance variables is a dangerous breach of encapsulation. The internal structure of the class is then free to evolve. All externalized instances will be useful as long as the constructor methods are maintained with the same semantics. There may be several constructor methods for a particular class. This is useful for (1) instances with characteristically different defining state, and (2) newer, evolved forms of an object and its constructors, with the old constructor methods kept around so old data can still be properly loaded.) Create one like this example from class Picture DiskProxy global: #Picture selector: #fromByteArray: args: (Array with: self storage asByteArray) * See also subclass DiskProxyQ that will construct an object in the above manner and then send it a sequence of messages. This may save creating a wide variety of constructor methods. It is also useful because the newly read-in DiskProxyQ can catch messages like #objectContainedIn: (via #doesNotUnderstand:) and add them to the queue of messages to send to the new object. * We may also want a subclass of DiskProxy that evaluates a string expression to compute the receiver of the constructor message. My instance variables: * globalObjectName -- the Symbol name of a global object in the System dictionary (usually a class). * constructorSelector -- the constructor message selector Symbol to send to the global object (perform:withArguments:), typically a variation on newFrom:. * constructorArgs -- the Array of arguments to pass in the constructor message. -- 11/9/92 Jerry Morrison ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 11/7/2000 11:28'! comeFullyUpOnReload: smartRefStream "Internalize myself into a fully alive object after raw loading from a DataStream. (See my class comment.) DataStream will substitute the object from this eval for the DiskProxy." | globalObj symbol pr nn arrayIndex | symbol _ globalObjectName. "See if class is mapped to another name" (smartRefStream respondsTo: #renamed) ifTrue: [ "If in outPointers in an ImageSegment, remember original class name. See mapClass:installIn:. Would be lost otherwise." ((thisContext sender sender sender sender sender sender sender sender receiver class == ImageSegment) and: [ thisContext sender sender sender sender method == (DataStream compiledMethodAt: #readArray)]) ifTrue: [ arrayIndex _ (thisContext sender sender sender sender) tempAt: 4. "index var in readArray. Later safer to find i on stack of context." smartRefStream renamedConv at: arrayIndex put: symbol]. "save original name" symbol _ smartRefStream renamed at: symbol ifAbsent: [symbol]]. "map" globalObj _ Smalltalk at: symbol ifAbsent: [^ self error: 'Global not found']. ((symbol == #World) and: [Smalltalk isMorphic not]) ifTrue: [ self inform: 'These objects will work better if opened in a Morphic World. Dismiss and reopen all menus.']. preSelector ifNotNil: [ Symbol hasInterned: preSelector ifTrue: [:selector | globalObj _ globalObj perform: selector]]. symbol == #Project ifTrue: [ (constructorSelector = #fromUrl:) ifTrue: [ nn _ (constructorArgs first findTokens: '/') last. nn _ (nn findTokens: '.|') first. pr _ Project named: nn. ^ pr ifNil: [self] ifNotNil: [pr]]. pr _ globalObj perform: constructorSelector withArguments: constructorArgs. ^ pr ifNil: [self] ifNotNil: [pr]]. "keep the Proxy if Project does not exist" constructorSelector ifNil: [^ globalObj]. constructorSelector ifNotNil: [ Symbol hasInterned: constructorSelector ifTrue: [:selector | ^ globalObj perform: selector withArguments: constructorArgs]]. "args not checked against Renamed" ^ nil "was not in proper form"! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 3/10/2000 23:50'! constructorArgs ^ constructorArgs! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 11/6/2000 22:38'! constructorSelector ^ constructorSelector! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 4/8/1999 12:58'! global: globalNameSymbol preSelector: aSelector selector: selectorSymbol args: argArray "Initialize self as a DiskProxy constructor with the given globalNameSymbol, selectorSymbol, and argument Array. I will internalize by looking up the global object name in the SystemDictionary (Smalltalk) and sending it this message with these arguments." globalObjectName _ globalNameSymbol asSymbol. preSelector _ aSelector asSymbol. constructorSelector _ selectorSymbol asSymbol. constructorArgs _ argArray.! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 11/4/1999 19:28'! global: globalNameSymbol selector: selectorSymbol args: argArray "Initialize self as a DiskProxy constructor with the given globalNameSymbol, selectorSymbol, and argument Array. I will internalize by looking up the global object name in the SystemDictionary (Smalltalk) and sending it this message with these arguments." (globalNameSymbol beginsWith: 'AnObsolete') ifTrue: [ self error: 'Trying to write out, ', globalNameSymbol]. globalObjectName _ globalNameSymbol asSymbol. constructorSelector _ selectorSymbol asSymbol. constructorArgs _ argArray.! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 11/6/2000 22:38'! globalObjectName ^ globalObjectName! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 11/6/2000 22:35'! preSelector ^ preSelector! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 4/8/1999 12:54'! preSelector: aSelector preSelector _ aSelector! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 3/22/2000 14:23'! printOn: aStream "Try to report the name of the project" globalObjectName == #Project ifFalse: [^ super printOn: aStream]. constructorArgs size > 0 ifFalse: [^ super printOn: aStream]. constructorArgs first class == String ifFalse: [^ super printOn: aStream]. aStream nextPutAll: constructorArgs first, ' (on server)'! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 10/6/2000 15:18'! simpleGlobalOrNil "Return the object I refer to if it is a simple global in Smalltalk." preSelector ifNotNil: [^ nil]. constructorSelector == #yourself ifFalse: [^ nil]. ^ Smalltalk at: globalObjectName ifAbsent: [nil]. ! ! !DiskProxy methodsFor: 'as yet unclassified' stamp: 'tk 3/26/98 11:17'! storeDataOn: aDataStream "Besides just storing, get me inserted into references, so structures will know about class DiskProxy." super storeDataOn: aDataStream. aDataStream references at: self put: #none. "just so instVarInfo: will find it and put it into structures"! ! !DiskProxy methodsFor: 'exceptions' stamp: 'tk 3/14/2000 16:27'! enter "Enter the new project" self enter: false revert: false saveForRevert: false.! ! !DiskProxy methodsFor: 'exceptions' stamp: 'RAA 6/3/2000 11:02'! enter: returningFlag revert: revertFlag saveForRevert: saveForRevert "Look for our project on the server, then try to enter it!! DiskProxy is acting as a stub for the real thing. Called from a ProjectViewMorph in the current project. If have url, use it. Else look in current Project's server and folder." constructorSelector == #namedUrl: ifTrue: ["Project namedUrl: xxx" ^ ((Smalltalk at: globalObjectName) perform: #fromUrl: withArguments: constructorArgs) ]. constructorSelector == #named: ifTrue: [ CurrentProjectRefactoring currentFromMyServerLoad: constructorArgs first]. "name" ! ! !DiskProxy methodsFor: 'exceptions' stamp: 'RAA 5/17/2000 11:51'! loadFromServer "In support of check for newer version in ProjectViewMorph menu" self enter ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DiskProxy class instanceVariableNames: ''! !DiskProxy class methodsFor: 'as yet unclassified'! global: globalNameSymbol selector: selectorSymbol args: argArray "Create a new DiskProxy constructor with the given globalNameSymbol, selectorSymbol, and argument Array. It will internalize itself by looking up the global object name in the SystemDictionary (Smalltalk) and sending it this message with these arguments." ^ self new global: globalNameSymbol selector: selectorSymbol args: argArray! ! DisplayObject subclass: #DisplayMedium instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !DisplayMedium commentStamp: '' prior: 0! I am a display object which can both paint myself on a medium (displayOn: messages), and can act as a medium myself. My chief subclass is Form.! !DisplayMedium methodsFor: 'coloring'! fill: aRectangle fillColor: aForm "Replace a rectangular area of the receiver with the pattern described by aForm according to the rule over." self fill: aRectangle rule: Form over fillColor: aForm! ! !DisplayMedium methodsFor: 'coloring'! fill: aRectangle rule: anInteger fillColor: aForm "Replace a rectangular area of the receiver with the pattern described by aForm according to the rule anInteger." self subclassResponsibility! ! !DisplayMedium methodsFor: 'coloring'! fillBlack "Set all bits in the receiver to black (ones)." self fill: self boundingBox fillColor: Color black! ! !DisplayMedium methodsFor: 'coloring'! fillBlack: aRectangle "Set all bits in the receiver's area defined by aRectangle to black (ones)." self fill: aRectangle rule: Form over fillColor: Color black! ! !DisplayMedium methodsFor: 'coloring'! fillColor: aColor "Set all pixels in the receiver to the color. Must be a correct color for this depth of medium. TK 1 Jun 96" self fill: self boundingBox fillColor: aColor! ! !DisplayMedium methodsFor: 'coloring'! fillGray "Set all bits in the receiver to gray." self fill: self boundingBox fillColor: Color gray! ! !DisplayMedium methodsFor: 'coloring'! fillGray: aRectangle "Set all bits in the receiver's area defined by aRectangle to the gray mask." self fill: aRectangle rule: Form over fillColor: Color gray! ! !DisplayMedium methodsFor: 'coloring'! fillShape: aShapeForm fillColor: aColor "Fill a region corresponding to 1 bits in aShapeForm with aColor" ^ self fillShape: aShapeForm fillColor: aColor at: 0@0! ! !DisplayMedium methodsFor: 'coloring' stamp: 'ar 5/28/2000 12:06'! fillShape: aShapeForm fillColor: aColor at: location "Fill a region corresponding to 1 bits in aShapeForm with aColor" ((BitBlt current destForm: self sourceForm: aShapeForm fillColor: aColor combinationRule: Form paint destOrigin: location + aShapeForm offset sourceOrigin: 0@0 extent: self extent clipRect: self boundingBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF)) copyBits! ! !DisplayMedium methodsFor: 'coloring'! fillWhite "Set all bits in the form to white." self fill: self boundingBox fillColor: Color white. ! ! !DisplayMedium methodsFor: 'coloring'! fillWhite: aRectangle "Set all bits in the receiver's area defined by aRectangle to white." self fill: aRectangle rule: Form over fillColor: Color white. ! ! !DisplayMedium methodsFor: 'coloring'! fillWithColor: aColor "Fill the receiver's bounding box with the given color." self fill: self boundingBox fillColor: aColor. ! ! !DisplayMedium methodsFor: 'coloring' stamp: 'jm 6/18/1999 19:01'! reverse "Change all the bits in the receiver that are white to black, and the ones that are black to white." self fill: self boundingBox rule: Form reverse fillColor: (Color quickHighLight: self depth)! ! !DisplayMedium methodsFor: 'coloring' stamp: 'jm 6/18/1999 19:00'! reverse: aRectangle "Change all the bits in the receiver's area that intersects with aRectangle that are white to black, and the ones that are black to white." self fill: aRectangle rule: Form reverse fillColor: (Color quickHighLight: self depth)! ! !DisplayMedium methodsFor: 'coloring'! reverse: aRectangle fillColor: aMask "Change all the bits in the receiver's area that intersects with aRectangle according to the mask. Black does not necessarily turn to white, rather it changes with respect to the rule and the bit in a corresponding mask location. Bound to give a surprise." self fill: aRectangle rule: Form reverse fillColor: aMask! ! !DisplayMedium methodsFor: 'bordering'! border: aRectangle width: borderWidth "Paint a border whose rectangular area is defined by aRectangle. The width of the border of each side is borderWidth. Uses black for drawing the border." self border: aRectangle width: borderWidth fillColor: Color black. ! ! !DisplayMedium methodsFor: 'bordering'! border: aRectangle width: borderWidth fillColor: aHalfTone "Paint a border whose rectangular area is defined by aRectangle. The width of the border of each side is borderWidth. Uses aHalfTone for drawing the border." self border: aRectangle widthRectangle: (Rectangle left: borderWidth right: borderWidth top: borderWidth bottom: borderWidth) rule: Form over fillColor: aHalfTone! ! !DisplayMedium methodsFor: 'bordering'! border: aRectangle width: borderWidth rule: combinationRule fillColor: aHalfTone "Paint a border whose rectangular area is defined by aRectangle. The width of the border of each side is borderWidth. Uses aHalfTone for drawing the border." self border: aRectangle widthRectangle: (Rectangle left: borderWidth right: borderWidth top: borderWidth bottom: borderWidth) rule: combinationRule fillColor: aHalfTone! ! !DisplayMedium methodsFor: 'bordering'! border: aRectangle widthRectangle: insets rule: combinationRule fillColor: aHalfTone "Paint a border whose rectangular area is defined by aRectangle. The width of each edge of the border is determined by the four coordinates of insets. Uses aHalfTone and combinationRule for drawing the border." (aRectangle areasOutside: (aRectangle insetBy: insets)) do: [:edgeStrip | self fill: edgeStrip rule: combinationRule fillColor: aHalfTone]! ! !DisplayMedium methodsFor: 'displaying'! copyBits: sourceRect from: sourceForm at: destOrigin clippingBox: clipRect rule: rule fillColor: aForm "Make up a BitBlt table and copy the bits." self subclassResponsibility! ! !DisplayMedium methodsFor: 'displaying'! drawLine: sourceForm from: beginPoint to: endPoint clippingBox: clipRect rule: anInteger fillColor: aForm "Draw line by copying the argument, sourceForm, starting at location beginPoint and ending at endPoint, clipped by the rectangle, clipRect. The rule and mask for copying are the arguments anInteger and aForm." self subclassResponsibility! ! Object subclass: #DisplayObject instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !DisplayObject commentStamp: '' prior: 0! The abstract protocol for most display primitives that are used by Views for presenting information on the screen.! !DisplayObject methodsFor: 'accessing'! extent "Answer the point that represents the width and height of the receiver's bounding box." ^self boundingBox extent! ! !DisplayObject methodsFor: 'accessing'! height "Answer the number that represents the height of the receiver's bounding box." ^self boundingBox height! ! !DisplayObject methodsFor: 'accessing'! offset "Answer the amount by which the receiver should be offset when it is displayed or its position is tested." self subclassResponsibility! ! !DisplayObject methodsFor: 'accessing'! offset: aPoint "Set the amount by which the receiver's position is offset." ^self! ! !DisplayObject methodsFor: 'accessing'! relativeRectangle "Answer a Rectangle whose top left corner is the receiver's offset position and whose width and height are the same as the receiver." ^Rectangle origin: self offset extent: self extent! ! !DisplayObject methodsFor: 'accessing'! width "Answer the number that represents the width of the receiver's bounding box." ^self boundingBox width! ! !DisplayObject methodsFor: 'truncation and round off'! rounded "Convert the offset of the receiver to integer coordinates." self offset: self offset rounded! ! !DisplayObject methodsFor: 'transforming'! align: alignmentPoint with: relativePoint "Translate the receiver's offset such that alignmentPoint aligns with relativePoint." self offset: (self offset translateBy: relativePoint - alignmentPoint)! ! !DisplayObject methodsFor: 'transforming'! scaleBy: aPoint "Scale the receiver's offset by aPoint." self offset: (self offset scaleBy: aPoint)! ! !DisplayObject methodsFor: 'transforming'! translateBy: aPoint "Translate the receiver's offset." self offset: (self offset translateBy: aPoint)! ! !DisplayObject methodsFor: 'display box access'! boundingBox "Answer the rectangular area that represents the boundaries of the receiver's space of information." ^self computeBoundingBox! ! !DisplayObject methodsFor: 'display box access'! center ^ self boundingBox center! ! !DisplayObject methodsFor: 'display box access'! computeBoundingBox "Answer the rectangular area that represents the boundaries of the receiver's area for displaying information. This is the primitive for computing the area if it is not already known." self subclassResponsibility! ! !DisplayObject methodsFor: 'display box access'! initialExtent "Included here for when a FormView is being opened as a window. (4@4) covers border widths." ^ self extent + (4@4) ! ! !DisplayObject methodsFor: 'displaying-generic'! displayAt: aDisplayPoint "Display the receiver located at aDisplayPoint with default settings for the displayMedium, rule and halftone." self displayOn: Display at: aDisplayPoint clippingBox: Display boundingBox rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium "Simple default display in order to see the receiver in the upper left corner of screen." self displayOn: aDisplayMedium at: 0 @ 0! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium at: aDisplayPoint "Display the receiver located at aDisplayPoint with default settings for rule and halftone." self displayOn: aDisplayMedium at: aDisplayPoint clippingBox: aDisplayMedium boundingBox rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle "Display the receiver located at aDisplayPoint with default settings for rule and halftone. Information to be displayed must be confined to the area that intersects with clipRectangle." self displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: ruleInteger fillColor: aForm "This is the basic display primitive for graphic display objects. Display the receiver located at aDisplayPoint with rule, ruleInteger, and mask, aForm. Information to be displayed must be confined to the area that intersects with clipRectangle." self subclassResponsibility! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium at: aDisplayPoint rule: ruleInteger "Display the receiver located at aPoint with default setting for the halftone and clippingBox." self displayOn: aDisplayMedium at: aDisplayPoint clippingBox: aDisplayMedium boundingBox rule: ruleInteger fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle "Display primitive for the receiver where a DisplayTransformation is provided as an argument. Alignment is defaulted to the receiver's rectangle. Information to be displayed must be confined to the area that intersects with clipRectangle." self displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: self relativeRectangle center with: self relativeRectangle center rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: alignmentPoint with: relativePoint "Display primitive where a DisplayTransformation is provided as an argument, rule is over and mask is Form black. Information to be displayed must be confined to the area that intersects with clipRectangle." self displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: alignmentPoint with: relativePoint rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: alignmentPoint with: relativePoint rule: ruleInteger fillColor: aForm "Display the receiver where a DisplayTransformation is provided as an argument, rule is ruleInteger and mask is aForm. Translate by relativePoint-alignmentPoint. Information to be displayed must be confined to the area that intersects with clipRectangle." | absolutePoint | absolutePoint _ displayTransformation applyTo: relativePoint. self displayOn: aDisplayMedium at: (absolutePoint - alignmentPoint) clippingBox: clipRectangle rule: ruleInteger fillColor: aForm ! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle fixedPoint: aPoint "Display the receiver where a DisplayTransformation is provided as an argument, rule is over and mask is Form black. No translation. Information to be displayed must be confined to the area that intersects with clipRectangle." self displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: aPoint with: aPoint rule: Form over fillColor: nil! ! !DisplayObject methodsFor: 'displaying-generic'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle rule: ruleInteger fillColor: aForm "Display the receiver where a DisplayTransformation is provided as an argument, rule is ruleInteger and mask is aForm. No translation. Information to be displayed must be confined to the area that intersects with clipRectangle." self displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: self relativeRectangle origin with: self relativeRectangle origin rule: ruleInteger fillColor: aForm! ! !DisplayObject methodsFor: 'displaying-generic'! displayOnPort: aPort self displayOnPort: aPort at: 0@0! ! !DisplayObject methodsFor: 'displaying-generic' stamp: 'jm 10/21/97 16:56'! displayOnPort: port at: location rule: rule port copyForm: self to: location rule: rule. ! ! !DisplayObject methodsFor: 'displaying-generic'! followCursor "Just show the Form following the mouse. 6/21/96 tk" Cursor blank showWhile: [self follow: [Sensor cursorPoint] while: [Sensor noButtonPressed]] ! ! !DisplayObject methodsFor: 'displaying-Display'! display "Display the receiver on the Display at location 0,0." self displayOn: Display! ! !DisplayObject methodsFor: 'displaying-Display'! follow: locationBlock while: durationBlock "Move an image around on the Display. Restore the background continuously without causing flashing. The argument, locationBlock, supplies each new location, and the argument, durationBlock, supplies true to continue, and then false to stop. 8/20/96 sw: call follow:while:bitsBehind: to do the real work. Note that th method now returns the final bits behind as method value." | bitsBehind loc | bitsBehind _ Form fromDisplay: ((loc _ locationBlock value) extent: self extent). ^ self follow: locationBlock while: durationBlock bitsBehind: bitsBehind startingLoc: loc! ! !DisplayObject methodsFor: 'displaying-Display' stamp: 'ar 5/28/2000 12:06'! follow: locationBlock while: durationBlock bitsBehind: initialBitsBehind startingLoc: loc "Move an image around on the Display. Restore the background continuously without causing flashing. The argument, locationBlock, supplies each new location, and the argument, durationBlock, supplies true to continue or false to stop. This variant takes the bitsBehind as an input argument, and returns the final saved saved bits as method value." | location rect1 save1 save1Blt buffer bufferBlt newLoc rect2 bothRects | location _ loc. rect1 _ location extent: self extent. save1 _ initialBitsBehind. save1Blt _ BitBlt current toForm: save1. buffer _ Form extent: self extent*2 depth: Display depth. "Holds overlapping region" bufferBlt _ BitBlt current toForm: buffer. Display deferUpdates: true. self displayOn: Display at: location rule: Form paint. Display deferUpdates: false; forceToScreen: (location extent: self extent). [durationBlock value] whileTrue: [ newLoc _ locationBlock value. newLoc ~= location ifTrue: [ rect2 _ newLoc extent: self extent. bothRects _ rect1 merge: rect2. (rect1 intersects: rect2) ifTrue: [ "when overlap, buffer background for both rectangles" bufferBlt copyFrom: bothRects in: Display to: 0@0. bufferBlt copyFrom: save1 boundingBox in: save1 to: rect1 origin - bothRects origin. "now buffer is clean background; get new bits for save1" save1Blt copy: (0@0 extent: self extent) from: rect2 origin - bothRects origin in: buffer. self displayOnPort: bufferBlt at: rect2 origin - bothRects origin rule: Form paint. Display deferUpdates: true. Display copy: bothRects from: 0@0 in: buffer rule: Form over. Display deferUpdates: false; forceToScreen: bothRects] ifFalse: [ "when no overlap, do the simple thing (both rects might be too big)" Display deferUpdates: true. Display copy: (location extent: save1 extent) from: 0@0 in: save1 rule: Form over. save1Blt copyFrom: rect2 in: Display to: 0@0. self displayOn: Display at: newLoc rule: Form paint. Display deferUpdates: false; forceToScreen: (location extent: save1 extent); forceToScreen: (newLoc extent: self extent)]. location _ newLoc. rect1 _ rect2]]. ^ save1 displayOn: Display at: location ! ! !DisplayObject methodsFor: 'displaying-Display' stamp: 'di 9/12/97 11:09'! isTransparent ^ false! ! !DisplayObject methodsFor: 'displaying-Display'! slideFrom: startPoint to: stopPoint nSteps: nSteps "does not display at the first point, but does at the last" | i p delta | i_0. p_ startPoint. delta _ (stopPoint-startPoint) // nSteps. ^ self follow: [p_ p+delta] while: [(i_i+1) < nSteps]! ! !DisplayObject methodsFor: 'displaying-Display' stamp: 'jm 10/22/97 07:43'! slideFrom: startPoint to: stopPoint nSteps: nSteps delay: milliSecs "Slide this object across the display over the given number of steps, pausing for the given number of milliseconds after each step." "Note: Does not display at the first point, but does at the last." | i p delta | i _ 0. p _ startPoint. delta _ (stopPoint - startPoint) / nSteps asFloat. ^ self follow: [(p _ p + delta) truncated] while: [ (Delay forMilliseconds: milliSecs) wait. (i _ i + 1) < nSteps] ! ! !DisplayObject methodsFor: 'displaying-Display' stamp: 'di 10/19/97 12:05'! slideFrom: startPoint to: stopPoint nSteps: nSteps delay: milliSecs andStay: stayAtEnd "Does not display at the first point, but does at the last. Moreover, if stayAtEnd is true, it leaves the dragged image at the stopPoint" | i done | i _ 0. ^ self follow: [startPoint + ((stopPoint-startPoint) * i // nSteps)] while: [milliSecs ifNotNil: [(Delay forMilliseconds: milliSecs) wait]. ((done _ (i _ i+1) > nSteps) and: [stayAtEnd]) ifTrue: [^ self "Return without clearing the image"]. done not]! ! !DisplayObject methodsFor: 'displaying-Display' stamp: 'sr 6/6/2000 05:37'! slideWithFirstFrom: startPoint to: stopPoint nSteps: nSteps delay: milliSecs "Slide this object across the display over the given number of steps, pausing for the given number of milliseconds after each step." "Note: Does display at the first point and at the last." | i p delta | i _ 0. delta _ stopPoint - startPoint / nSteps asFloat. p _ startPoint - delta. ^ self follow: [(p _ p + delta) truncated] while: [(Delay forMilliseconds: milliSecs) wait. (i _ i + 1) <= nSteps]! ! !DisplayObject methodsFor: 'fileIn/Out'! writeOnFileNamed: fileName "Saves the receiver on the file fileName in the format: fileCode, depth, extent, offset, bits." | file | file _ FileStream newFileNamed: fileName. file binary. file nextPut: 2. "file code = 2" self writeOn: file. file close " | f | [(f _ Form fromUser) boundingBox area>25] whileTrue: [f writeOnFileNamed: 'test.form'. (Form newFromFileNamed: 'test.form') display]. "! ! !DisplayObject methodsFor: 'fileIn/Out' stamp: 'tk 2/19/1999 07:20'! writeUncompressedOnFileNamed: fileName "Saves the receiver on the file fileName in the format: fileCode, depth, extent, offset, bits." | file | file _ FileStream newFileNamed: fileName. file binary. file nextPut: 2. "file code = 2" self writeUncompressedOn: file. file close " | f | [(f _ Form fromUser) boundingBox area>25] whileTrue: [f writeUncompressedOnFileNamed: 'test.form'. (Form fromBinaryStream: (FileStream oldFileNamed: 'test.form')) display]. "! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayObject class instanceVariableNames: ''! !DisplayObject class methodsFor: 'fileIn/Out' stamp: 'mdr 8/31/2000 19:11'! collectionFromFileNamed: fileName "Answer a collection of Forms read from the external file named fileName. The file format is: fileCode, {depth, extent, offset, bits}." | formList f fileCode | formList _ OrderedCollection new. f _ (FileStream readOnlyFileNamed: fileName) binary. fileCode _ f next. fileCode = 1 ifTrue: [ [f atEnd] whileFalse: [formList add: (self new readFromOldFormat: f)]] ifFalse: [ fileCode = 2 ifFalse: [self error: 'unknown Form file format'. ^ formList]. [f atEnd] whileFalse: [formList add: (self new readFrom: f)]]. f close. ^ formList ! ! !DisplayObject class methodsFor: 'fileIn/Out'! writeCollection: coll onFileNamed: fileName "Saves a collection of Forms on the file fileName in the format: fileCode, {depth, extent, offset, bits}." | file | file _ FileStream newFileNamed: fileName. file binary. file nextPut: 2. "file code = 2" coll do: [:f | f writeOn: file]. file close " | f c | c _ OrderedCollection new. [(f _ Form fromUser) boundingBox area>25] whileTrue: [c add: f]. Form writeCollection: c onFileNamed: 'test.forms'. c _ Form collectionFromFileNamed: 'test.forms'. 1 to: c size do: [:i | (c at: i) displayAt: 0@(i*100)]. "! ! CharacterScanner subclass: #DisplayScanner instanceVariableNames: 'bitBlt lineY runX foregroundColor backgroundColor fillBlt lineHeight paragraph paragraphColor morphicOffset ignoreColorChanges ' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Text'! !DisplayScanner commentStamp: '' prior: 0! My instances are used to scan text and display it on the screen or in a hidden form.! !DisplayScanner methodsFor: 'scanning' stamp: 'di 9/3/2000 16:21'! 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 | line _ textLine. morphicOffset _ offset. leftMargin _ (line leftMarginForAlignment: textStyle alignment) + offset x. destX _ runX _ leftMargin. rightMargin _ line rightMargin + offset x. lineY _ line top + offset y. lineHeight _ line lineHeight. fillBlt == nil ifFalse: ["Not right" fillBlt destX: line left destY: lineY width: line width left height: lineHeight; copyBits]. lastIndex _ line first. leftInRun <= 0 ifTrue: [self setStopConditions. "also sets the font" nowLeftInRun _ text runLengthFor: lastIndex] ifFalse: [nowLeftInRun _ leftInRun]. destY _ lineY + line baseline - font ascent. 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"! ! !DisplayScanner methodsFor: 'scanning' stamp: 'di 9/3/2000 16:18'! displayLines: linesInterval in: aParagraph clippedBy: visibleRectangle "The central display routine [MVC only]. 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)." | runLength done stopCondition leftInRun startIndex string lastPos | "leftInRun is the # of characters left to scan in the current run; when 0, it is time to call 'self setStopConditions'" leftInRun _ 0. self initializeFromParagraph: aParagraph clippedBy: visibleRectangle. ignoreColorChanges _ false. paragraph _ aParagraph. foregroundColor _ paragraphColor _ aParagraph foregroundColor. backgroundColor _ aParagraph backgroundColor. aParagraph backgroundColor isTransparent ifTrue: [fillBlt _ nil] ifFalse: [fillBlt _ bitBlt copy. "Blt to fill spaces, tabs, margins" fillBlt sourceForm: nil; sourceOrigin: 0@0. fillBlt fillColor: aParagraph backgroundColor]. rightMargin _ aParagraph rightMarginForDisplay. lineY _ aParagraph topAtLineIndex: linesInterval first. linesInterval do: [:lineIndex | leftMargin _ aParagraph leftMarginForDisplayForLine: lineIndex. destX _ (runX _ leftMargin). line _ aParagraph lines at: lineIndex. lineHeight _ line lineHeight. fillBlt == nil ifFalse: [fillBlt destX: visibleRectangle left destY: lineY width: visibleRectangle width height: lineHeight; copyBits]. lastIndex _ line first. leftInRun <= 0 ifTrue: [self setStopConditions. "also sets the font" leftInRun _ text runLengthFor: line first]. destY _ lineY + line baseline - font ascent. "Should have happened in setFont" runLength _ leftInRun. runStopIndex _ lastIndex + (runLength - 1) min: line last. leftInRun _ leftInRun - (runStopIndex - lastIndex + 1). 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]. lineY _ lineY + lineHeight]! ! !DisplayScanner methodsFor: 'scanning' stamp: 'RAA 7/15/2000 10:23'! placeEmbeddedObject: anchoredMorph (super placeEmbeddedObject: anchoredMorph) ifFalse: [^ false]. anchoredMorph isMorph ifTrue: [ anchoredMorph position: ((destX - anchoredMorph width)@lineY) - morphicOffset ] ifFalse: [ destY _ lineY. runX _ destX. anchoredMorph displayOn: bitBlt destForm at: destX - anchoredMorph width @ destY clippingBox: bitBlt clipRect ]. ^ true! ! !DisplayScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:49'! doesDisplaying ^true! ! !DisplayScanner methodsFor: 'private' stamp: 'di 9/3/2000 16:03'! initializeFromParagraph: aParagraph clippedBy: clippingRectangle super initializeFromParagraph: aParagraph clippedBy: clippingRectangle. bitBlt _ BitBlt current toForm: aParagraph destinationForm. bitBlt combinationRule: Form paint. bitBlt colorMap: (Bitmap with: 0 "Assumes 1-bit deep fonts" with: (aParagraph foregroundColor pixelValueForDepth: bitBlt destForm depth)). bitBlt clipRect: clippingRectangle. ! ! !DisplayScanner methodsFor: 'private' stamp: 'ar 5/17/2000 19:26'! setDestForm: df bitBlt setDestForm: df.! ! !DisplayScanner methodsFor: 'private' stamp: 'di 9/3/2000 16:13'! setFont foregroundColor _ paragraphColor. super setFont. "Sets font and emphasis bits, and maybe foregroundColor" font installOn: bitBlt foregroundColor: foregroundColor backgroundColor: Color transparent. text ifNotNil:[destY _ lineY + line baseline - font ascent]! ! !DisplayScanner methodsFor: 'private' stamp: 'ar 5/17/2000 20:25'! setPort: aBitBlt "Install the BitBlt to use" bitBlt _ aBitBlt. bitBlt sourceForm: nil. "Make sure font installation won't be confused" ! ! !DisplayScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:51'! text: t textStyle: ts foreground: foreColor background: backColor fillBlt: blt ignoreColorChanges: shadowMode text _ t. textStyle _ ts. foregroundColor _ paragraphColor _ foreColor. (backgroundColor _ backColor) isTransparent ifFalse: [fillBlt _ blt. fillBlt fillColor: backgroundColor]. ignoreColorChanges _ shadowMode! ! !DisplayScanner methodsFor: 'private' stamp: 'ar 1/8/2000 14:51'! textColor: textColor ignoreColorChanges ifTrue: [^ self]. foregroundColor _ textColor! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'ar 5/19/2000 14:46'! characterNotInFont "See the note in CharacterScanner>>characterNotInFont. All fonts have an illegal character to be used when a character is not within the font's legal range. When characters out of ranged are encountered in scanning text, then this special character indicates the appropriate behavior. The character is usually treated as a unary message understood by a subclass of CharacterScanner." | illegalAsciiString saveIndex stopCondition lastPos | saveIndex _ lastIndex. lastPos _ destX @ destY. illegalAsciiString _ String with: (font maxAscii + 1) asCharacter. stopCondition _ self scanCharactersFrom: 1 to: 1 in: illegalAsciiString rightX: rightMargin stopConditions: stopConditions kern: kern. font displayString: illegalAsciiString on: bitBlt from: 1 to: 1 at: lastPos kern: kern. lastIndex _ saveIndex + 1. stopCondition ~= (stopConditions at: EndOfRun) ifTrue: [^self perform: stopCondition] ifFalse: [lastIndex = runStopIndex ifTrue: [^self perform: (stopConditions at: EndOfRun)]. ^false] ! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'ar 1/8/2000 14:42'! cr "When a carriage return is encountered, simply increment the pointer into the paragraph." lastIndex_ lastIndex + 1. ^false! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'di 9/3/2000 16:24'! crossedX "This condition will sometimes be reached 'legally' during display, when, for instance the space that caused the line to wrap actually extends over the right boundary. This character is allowed to display, even though it is technically outside or straddling the clipping ectangle since it is in the normal case not visible and is in any case appropriately clipped by the scanner." ^ true ! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'di 9/3/2000 16:24'! endOfRun "The end of a run in the display case either means that there is actually a change in the style (run code) to be associated with the string or the end of this line has been reached." | runLength | lastIndex = line last ifTrue: [^true]. runX _ destX. runLength _ text runLengthFor: (lastIndex _ lastIndex + 1). runStopIndex _ lastIndex + (runLength - 1) min: line last. self setStopConditions. ^ false! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'di 9/3/2000 16:20'! paddedSpace "Each space is a stop condition when the alignment is right justified. Padding must be added to the base width of the space according to which space in the line this space is and according to the amount of space that remained at the end of the line when it was composed." spaceCount _ spaceCount + 1. destX _ destX + spaceWidth + (line justifiedPadFor: spaceCount). lastIndex _ lastIndex + 1. ^ false! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'ar 5/18/2000 16:47'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. textStyle alignment = Justified ifTrue:[ "Make a local copy of stop conditions so we don't modify the default" stopConditions == DefaultStopConditions ifTrue:[stopConditions _ stopConditions copy]. stopConditions at: Space asciiValue + 1 put: #paddedSpace]! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'RAA 9/6/2000 15:56'! setYFor: anchoredMorph anchoredMorph top: (lineY - morphicOffset y). ^ true! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'di 9/3/2000 16:19'! tab destX _ (textStyle alignment == Justified and: [self leadingTab not]) ifTrue: "imbedded tabs in justified text are weird" [destX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount)) max: destX] ifFalse: [textStyle nextTabXFrom: destX leftMargin: leftMargin rightMargin: rightMargin]. lastIndex _ lastIndex + 1. ^ false! ! !DisplayScanner methodsFor: 'quick print' stamp: 'RAA 6/16/2000 16:16'! drawString: aString at: aPoint "Draw the given string." destX _ aPoint x asInteger. destY _ aPoint y asInteger. lastIndex _ 1. "else the prim will fail" self primScanCharactersFrom: 1 to: aString size in: aString rightX: bitBlt clipX + bitBlt clipWidth + font maxWidth stopConditions: stopConditions kern: kern. font displayString: aString on: bitBlt from: 1 to: lastIndex at: aPoint kern: kern.! ! !DisplayScanner methodsFor: 'quick print' stamp: 'ar 5/17/2000 17:41'! lineHeight "Answer the height of the font used by QuickPrint." ^ font height! ! !DisplayScanner methodsFor: 'quick print' stamp: 'ar 5/28/2000 12:07'! quickPrintOn: aForm box: aRectangle font: aStrikeFont color: textColor "Initialize myself." bitBlt _ BitBlt current toForm: aForm. backgroundColor _ Color transparent. paragraphColor _ textColor. font _ aStrikeFont ifNil: [TextStyle defaultFont]. emphasisCode _ 0. kern _ 0. indentationLevel _ 0. self setFont. "Override cbrule and map" bitBlt combinationRule: Form paint. bitBlt colorMap: (Bitmap with: 0 "Assumes 1-bit deep fonts" with: (textColor pixelValueForDepth: bitBlt destForm depth)). bitBlt clipRect: aRectangle.! ! !DisplayScanner methodsFor: 'quick print' stamp: 'RAA 6/16/2000 16:16'! stringWidth: aString "Answer the width of the given string." destX _ destY _ 0. aString ifNil: [^ 0]. lastIndex _ 1. "else the prim will fail" self primScanCharactersFrom: 1 to: aString size in: aString rightX: 99999 "virtual infinity" stopConditions: stopConditions kern: kern. ^ destX " (1 to: 10) collect: [:i | QuickPrint new stringWidth: (String new: i withAll: $A)] "! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayScanner class instanceVariableNames: ''! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:39'! defaultFont ^ TextStyle defaultFont! ! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:40'! quickPrintOn: aForm "Create an instance to print on the given form in the given rectangle." ^(super new) quickPrintOn: aForm box: aForm boundingBox font: self defaultFont color: Color black! ! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:39'! quickPrintOn: aForm box: aRectangle "Create an instance to print on the given form in the given rectangle." ^(super new) quickPrintOn: aForm box: aRectangle font: self defaultFont color: Color black! ! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:40'! quickPrintOn: aForm box: aRectangle font: aStrikeFont "Create an instance to print on the given form in the given rectangle." ^(super new) quickPrintOn: aForm box: aRectangle font: aStrikeFont color: Color black! ! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:40'! quickPrintOn: aForm box: aRectangle font: aStrikeFont color: textColor "Create an instance to print on the given form in the given rectangle." ^ (super new) quickPrintOn: aForm box: aRectangle font: aStrikeFont color: textColor! ! !DisplayScanner class methodsFor: 'examples' stamp: 'ar 5/17/2000 17:41'! example "This will quickly print all the numbers from 1 to 100 on the display, and then answer the default width and height of the string 'hello world'." "NewDisplayScanner example" | scanner | scanner _ self quickPrintOn: Display. 0 to: 99 do: [: i | scanner drawString: i printString at: (i//10*20) @ (i\\10*12) ]. ^ (scanner stringWidth: 'hello world') @ (scanner lineHeight)! ! Form subclass: #DisplayScreen instanceVariableNames: 'clippingBox ' classVariableNames: 'DisplayChangeSignature OuterMorphicWorld ScreenSave ' poolDictionaries: '' category: 'Graphics-Display Objects'! !DisplayScreen commentStamp: '' prior: 0! There is only one instance of me, Display. It is a global and is used to handle general user requests to deal with the whole display screen. Although I offer no protocol, my name provides a way to distinguish this special instance from all other Forms. This is useful, for example, in dealing with saving and restoring the system. To change the depth of your Display... Display newDepth: 16. Display newDepth: 8. Display newDepth: 1. Valid display depths are 1, 2, 4, 8, 16 and 32. It is suggested that you run with your monitors setting the same, for better speed and color fidelity. Note that this can add up to 4Mb for the Display form. Finally, note that newDepth: ends by executing a 'ControlManager restore' which currently terminates the active process, so nothing that follows in the doit will get executed. Depths 1, 2, 4 and 8 bits go through a color map to put color on the screen, but 16 and 32-bit color use the pixel values directly for RGB color (5 and 8 bits per, respectivlely). The color choice an be observed by executing Color fromUser in whatever depth you are using. ! !DisplayScreen methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:07'! copyBits: rect from: sf at: destOrigin clippingBox: clipRect rule: cr fillColor: hf (BitBlt current destForm: self sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: rect origin extent: rect extent clipRect: (clipRect intersect: clippingBox)) copyBits! ! !DisplayScreen methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:07'! copyBits: rect from: sf at: destOrigin clippingBox: clipRect rule: cr fillColor: hf map: map ((BitBlt current destForm: self sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: rect origin extent: rect extent clipRect: (clipRect intersect: clippingBox)) colorMap: map) copyBits! ! !DisplayScreen methodsFor: 'displaying' stamp: 'jm 5/22/1998 01:23'! flash: aRectangle "Flash the area of the screen defined by the given rectangle." self reverse: aRectangle. self forceDisplayUpdate. (Delay forMilliseconds: 100) wait. self reverse: aRectangle. self forceDisplayUpdate. ! ! !DisplayScreen methodsFor: 'displaying' stamp: 'RAA 6/2/2000 12:09'! flash: aRectangle andWait: msecs "Flash the area of the screen defined by the given rectangle." self reverse: aRectangle. self forceDisplayUpdate. (Delay forMilliseconds: msecs) wait. self reverse: aRectangle. self forceDisplayUpdate. (Delay forMilliseconds: msecs) wait. ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 10:15'! bestGuessOfCurrentWorld ^World ! ! !DisplayScreen methodsFor: 'other'! boundingBox clippingBox == nil ifTrue: [clippingBox _ super boundingBox]. ^ clippingBox! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 6/14/2000 17:05'! changeMorphicWorldTo: aWorldOrNil self flag: #bob. OuterMorphicWorld _ World _ aWorldOrNil. ! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 10/5/2000 18:59'! checkCurrentHandForObjectToPaste | response | self flag: #bob. World currentHand pasteBuffer ifNil: [^self]. response _ (PopUpMenu labels: 'Delete\Keep' withCRs) startUpWithCaption: 'Hand is holding a Morph in its paste buffer:\' withCRs, World currentHand pasteBuffer printString. response = 1 ifTrue: [World currentHand pasteBuffer: nil]. ! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 10/5/2000 18:59'! checkCurrentHandForObjectToPaste2 self flag: #bob. World currentHand pasteBuffer ifNil: [^self]. self inform: 'Hand is holding a Morph in its paste buffer:\' withCRs, World currentHand pasteBuffer printString. ! ! !DisplayScreen methodsFor: 'other'! clippingTo: aRect do: aBlock "Display clippingTo: Rectangle fromUser do: [ScheduledControllers restore: Display fullBoundingBox]" | saveClip | saveClip _ clippingBox. clippingBox _ aRect. aBlock value. clippingBox _ saveClip! ! !DisplayScreen methodsFor: 'other' stamp: 'jm 5/17/1998 08:29'! deferUpdates: aBoolean "Set the deferUpdates flag in the virtual machine. When this flag is true, BitBlt operations on the Display are not automatically propagated to the screen. If this underlying platform does not support deferred updates, this primitive will fail. Answer the receiver if the primitive succeeds, nil if it fails." ^ nil "answer nil if primitive fails" ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 11/27/1999 15:48'! displayChangeSignature ^DisplayChangeSignature! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 6/2/2000 10:52'! doOneCycleMorphic self getOuterMorphicWorld doOneCycle. ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 6/2/2000 10:50'! doOneCycleNowMorphic self getOuterMorphicWorld doOneCycleNow. ! ! !DisplayScreen methodsFor: 'other' stamp: 'jm 5/21/1998 23:48'! forceDisplayUpdate "On platforms that buffer screen updates, force the screen to be updated immediately. On other platforms, or if the primitive is not implemented, do nothing." "do nothing if primitive fails"! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 2/11/1999 18:14'! forceToScreen "Force the entire display area to the screen" ^self forceToScreen: self boundingBox! ! !DisplayScreen methodsFor: 'other' stamp: 'jm 5/19/1998 17:50'! forceToScreen: aRectangle "Force the given rectangular section of the Display to be copied to the screen. The primitive call does nothing if the primitive is not implemented. Typically used when the deferUpdates flag in the virtual machine is on; see deferUpdates:." self primShowRectLeft: aRectangle left right: aRectangle right top: aRectangle top bottom: aRectangle bottom. ! ! !DisplayScreen methodsFor: 'other'! fullBoundingBox ^ super boundingBox! ! !DisplayScreen methodsFor: 'other'! fullScreen "Display fullScreen" ScreenSave notNil ifTrue: [Display _ ScreenSave]. clippingBox _ super boundingBox! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 6/14/2000 17:04'! getCurrentMorphicWorld ^RequestCurrentWorldNotification signal ifNil: [ (self morphicWorldAt: Sensor cursorPoint) ifNil: [ self getOuterMorphicWorld ]. ] ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/26/2000 09:53'! getOuterMorphicWorld ^OuterMorphicWorld ifNil: [OuterMorphicWorld _ World] ! ! !DisplayScreen methodsFor: 'other'! height ^ self boundingBox height! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 10:01'! isCurrentMorphicWorld: aWorld self flag: #bob. ^aWorld == World ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 11/6/2000 12:11'! lastKnownCursorPoint self flag: #bob. "maybe Sensor cursorPoint is adequate" ^Sensor cursorPoint "==== World cursorPoint sometimes seems to be 0@0 (fake events or World that has yet had no events??) ^Smalltalk isMorphic ifTrue: [World cursorPoint] ifFalse: [Sensor cursorPoint] ===="! ! !DisplayScreen methodsFor: 'other' stamp: 'di 9/24/2000 12:49'! morphicWorldAt: aPoint | roots outer worldCount | outer _ self getOuterMorphicWorld ifNil: [^ nil]. worldCount _ 0. outer worldMorphsDo: [ :each | worldCount _ worldCount + 1]. worldCount < 2 ifTrue: [^outer]. roots _ outer rootMorphsAt: aPoint. roots isEmpty ifTrue: [^ outer]. ^ ((roots first morphsAt: aPoint) detect: [:each | true] ifNone: [roots first]) world! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 6/2/2000 17:23'! morphicWorldString ^(self morphicWorldAt: Sensor cursorPoint) hash printString "=== aString _ UpdatingStringMorph new target: Display. aString useStringFormat; color: Color blue; stepTime: 1000; getSelector: #morphicWorldString. aString openInWorld. ===" ! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 5/5/1999 23:44'! newDepth: pixelSize " Display newDepth: 8. Display newDepth: 1. " (self supportsDisplayDepth: pixelSize) ifFalse:[^self inform:'Display depth ', pixelSize printString, ' is not supported on this system']. self newDepthNoRestore: pixelSize. self restore.! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 10:03'! pauseMorphicEventRecorder self flag: #bob. Smalltalk isMorphic ifTrue: [^World pauseEventRecorder]. ^nil ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 09:40'! reinstallMorphicWorldAfterError self flag: #bob. Smalltalk isMorphic ifTrue: [World install "To init hand events and redisplay world"]. ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/26/2000 09:54'! repaintMorphicDisplay "this merely says everything needs to be redrawn, but the actual redraw will happen later" (self getOuterMorphicWorld ifNil: [^self]) fullRepaintNeeded. ! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/26/2000 09:53'! repaintMorphicDisplayNow (self getOuterMorphicWorld ifNil: [^self]) displayWorld.! ! !DisplayScreen methodsFor: 'other'! replacedBy: aForm do: aBlock "Permits normal display to draw on aForm instead of the display." ScreenSave _ self. Display _ aForm. aBlock value. Display _ self. ScreenSave _ nil.! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 08:05'! restore Smalltalk isMorphic ifTrue: [self repaintMorphicDisplay] ifFalse: [ScheduledControllers unCacheWindows; restore].! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 08:06'! restoreAfter: aBlock "Evaluate the block, wait for a mouse click, and then restore the screen." aBlock value. Sensor waitButton. Smalltalk isMorphic ifTrue: [self repaintMorphicDisplay] ifFalse: [(ScheduledControllers restore; activeController) view emphasize]! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 12/12/2000 15:50'! restoreMorphicDisplay DisplayScreen startUp. (self getOuterMorphicWorld ifNil: [^ self]) extent: self extent; viewBox: self boundingBox; handsDo: [:h | h visible: true; showTemporaryCursor: nil]; restoreFlapsDisplay; fullRepaintNeeded. WorldState addDeferredUIMessage: [ Cursor normal show. ]. ! ! !DisplayScreen methodsFor: 'other' stamp: 'bf 9/18/1999 19:46'! supportedDisplayDepths "Return all pixel depths supported on the current host platform." ^#(1 2 4 8 16 32) select: [:d | self supportsDisplayDepth: d]! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 5/5/1999 23:45'! supportsDisplayDepth: pixelDepth "Return true if this pixel depth is supported on the current host platform. Primitive. Optional." ^#(1 2 4 8 16 32) includes: pixelDepth! ! !DisplayScreen methodsFor: 'other'! usableArea "Answer the usable area of the receiver. 5/22/96 sw." ^ self boundingBox deepCopy! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 5/25/2000 10:29'! useTemporaryHandDuring: aBlock | lastHand tmpHand w | w _ Display bestGuessOfCurrentWorld. w removeHand: (lastHand _ w activeHand). w addHand: (tmpHand _ lastHand copy). aBlock value: tmpHand. w removeHand: tmpHand. w addHand: lastHand. ! ! !DisplayScreen methodsFor: 'other'! width ^ self boundingBox width! ! !DisplayScreen methodsFor: 'disk I/O' stamp: 'tk 9/28/2000 15:41'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. Write a reference to the Display in the other system instead. " "A path to me" dp _ DiskProxy global: #Display selector: #yourself args: #(). refStrm replace: self with: dp. ^ dp ! ! !DisplayScreen methodsFor: 'private'! beDisplay "Primitive. Tell the interpreter to use the receiver as the current display image. Fail if the form is too wide to fit on the physical display. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !DisplayScreen methodsFor: 'private' stamp: 'di 3/3/1999 10:00'! copyFrom: aForm "Take on all state of aForm, with complete sharing" super copyFrom: aForm. clippingBox _ super boundingBox! ! !DisplayScreen methodsFor: 'private' stamp: 'ar 5/25/2000 23:43'! findAnyDisplayDepth "Return any display depth that is supported on this system." ^self findAnyDisplayDepthIfNone:[ "Ugh .... now this is a biggie - a system that does not support any of the Squeak display depths at all." Smalltalk logError:'Fatal error: This system has no support for any display depth at all.' inContext: thisContext to: 'SqueakDebug.log'. Smalltalk quitPrimitive. "There is no way to continue from here" ].! ! !DisplayScreen methodsFor: 'private' stamp: 'ar 5/25/2000 23:44'! findAnyDisplayDepthIfNone: aBlock "Return any display depth that is supported on this system. If there is none, evaluate aBlock." #(1 2 4 8 16 32) do:[:bpp| (self supportsDisplayDepth: bpp) ifTrue:[^bpp]. ]. ^aBlock value! ! !DisplayScreen methodsFor: 'private' stamp: 'di 4/15/1999 10:58'! newDepthNoRestore: pixelSize "Change depths. Check if there is enough space!! , di" | area need | pixelSize = depth ifTrue: [^ self "no change"]. pixelSize < depth ifFalse: ["Make sure there is enough space" area _ Display boundingBox area. "pixels" Smalltalk isMorphic ifFalse: [ScheduledControllers scheduledWindowControllers do: [:aController | "This should be refined..." aController view cacheBitsAsTwoTone ifFalse: [area _ area + aController view windowBox area]]]. need _ (area * (pixelSize-depth) // 8) "new bytes needed" + Smalltalk lowSpaceThreshold. (Smalltalk garbageCollectMost <= need and: [Smalltalk garbageCollect <= need]) ifTrue: [self error: 'Insufficient free space']]. self setExtent: self extent depth: pixelSize. Smalltalk isMorphic ifFalse: [ScheduledControllers updateGray]. DisplayScreen startUp! ! !DisplayScreen methodsFor: 'private' stamp: 'jm 6/3/1998 13:00'! primRetryShowRectLeft: l right: r top: t bottom: b "Copy the given rectangular section of the Display to to the screen. This primitive is not implemented on all platforms. Do nothing if it fails. " "do nothing if primitive fails" ! ! !DisplayScreen methodsFor: 'private' stamp: 'jm 6/3/1998 13:02'! primShowRectLeft: l right: r top: t bottom: b "Copy the given rectangular section of the Display to to the screen. This primitive is not implemented on all platforms. If this fails, retry integer coordinates." "if this fails, coerce coordinates to integers and try again" self primRetryShowRectLeft: l truncated right: r rounded top: t truncated bottom: b rounded. ! ! !DisplayScreen methodsFor: 'private' stamp: 'RAA 11/27/1999 15:48'! setExtent: aPoint depth: bitsPerPixel "DisplayScreen startUp" "This method is critical. If the setExtent fails, there will be no proper display on which to show the error condition..." "ar 5/1/1999: ... and that is exactly why we check for the available display depths first." "RAA 27 Nov 99 - if depth and extent are the same and acceptable, why go through this. also - record when we change so worlds can tell if it is time to repaint" (depth == bitsPerPixel and: [aPoint = self extent and: [self supportsDisplayDepth: bitsPerPixel]]) ifFalse: [ bits _ nil. "Free up old bitmap in case space is low" DisplayChangeSignature _ (DisplayChangeSignature ifNil: [0]) + 1. (self supportsDisplayDepth: bitsPerPixel) ifTrue:[super setExtent: aPoint depth: bitsPerPixel] ifFalse:["Search for a suitable depth" super setExtent: aPoint depth: self findAnyDisplayDepth]. ]. clippingBox _ super boundingBox! ! !DisplayScreen methodsFor: 'initialize-release' stamp: 'ar 5/26/2000 00:07'! release "I am no longer Display. Release any resources if necessary"! ! !DisplayScreen methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 11:25'! shutDown "Minimize Display memory saved in image" self setExtent: 240@120 depth: depth! ! !DisplayScreen methodsFor: 'testing' stamp: 'ar 5/25/2000 23:34'! isB3DDisplayScreen ^false! ! !DisplayScreen methodsFor: 'testing' stamp: 'ar 5/25/2000 23:34'! isDisplayScreen ^true! ! !DisplayScreen methodsFor: 'blitter defaults' stamp: 'ar 5/28/2000 12:01'! defaultBitBltClass "Return the BitBlt version to use when I am active" ^BitBlt! ! !DisplayScreen methodsFor: 'blitter defaults' stamp: 'ar 5/28/2000 12:02'! defaultCanvasClass "Return the WarpBlt version to use when I am active" ^FormCanvas! ! !DisplayScreen methodsFor: 'blitter defaults' stamp: 'ar 5/28/2000 12:01'! defaultWarpBltClass "Return the WarpBlt version to use when I am active" ^WarpBlt! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayScreen class instanceVariableNames: ''! !DisplayScreen class methodsFor: 'display box access'! boundingBox "Answer the bounding box for the form representing the current display screen." ^Display boundingBox! ! !DisplayScreen class methodsFor: 'display box access' stamp: 'RAA 5/25/2000 08:04'! checkForNewScreenSize Display extent = DisplayScreen actualScreenSize ifTrue: [^ self]. DisplayScreen startUp. Smalltalk isMorphic ifTrue: [Display restoreMorphicDisplay] ifFalse: [ScheduledControllers restore; searchForActiveController]! ! !DisplayScreen class methodsFor: 'display box access' stamp: 'sma 4/28/2000 19:07'! depth: depthInteger width: widthInteger height: heightInteger fullscreen: aBoolean "Force Squeak's window (if there's one) into a new size and depth." "DisplayScreen depth: 8 width: 1024 height: 768 fullscreen: false" self primitiveFail! ! !DisplayScreen class methodsFor: 'snapshots'! actualScreenSize ^ 640@480! ! !DisplayScreen class methodsFor: 'snapshots' stamp: 'ar 5/28/2000 11:26'! shutDown "Minimize Display memory saved in image" Display shutDown.! ! !DisplayScreen class methodsFor: 'snapshots' stamp: 'di 3/1/1999 17:04'! startUp "DisplayScreen startUp" Display setExtent: self actualScreenSize depth: Display depth. Display beDisplay! ! DisplayObject subclass: #DisplayText instanceVariableNames: 'text textStyle offset form foreColor backColor ' classVariableNames: '' poolDictionaries: 'TextConstants ' category: 'Graphics-Display Objects'! !DisplayText commentStamp: '' prior: 0! I represent Text whose emphasis changes are mapped to a set of fonts. My instances have an offset used in determining screen placement for displaying. They get used two different ways in the system. In the user interface, they mainly hold onto some text which is viewed by some form of ParagraphEditor. However, as a DisplayObject, they may need to display efficiently, so my instances have a cache for the bits.! !DisplayText methodsFor: 'accessing'! alignedTo: alignPointSelector "Return a copy with offset according to alignPointSelector which is one of... #(topLeft, topCenter, topRight, leftCenter, center, etc)" | boundingBox | boundingBox _ 0@0 corner: self form extent. ^ self shallowCopy offset: (0@0) - (boundingBox perform: alignPointSelector)! ! !DisplayText methodsFor: 'accessing'! fontsUsed "Return a list of all fonts used currently in this text. 8/19/96 tk" ^ text runs values asSet collect: [:each | textStyle fontAt: each]! ! !DisplayText methodsFor: 'accessing'! form "Answer the form into which the receiver's display bits are cached." form == nil ifTrue: [self composeForm]. ^form! ! !DisplayText methodsFor: 'accessing'! lineGrid "Answer the relative space between lines of the receiver's text." ^textStyle lineGrid! ! !DisplayText methodsFor: 'accessing'! numberOfLines "Answer the number of lines of text in the receiver." ^self height // text lineGrid! ! !DisplayText methodsFor: 'accessing'! offset "Refer to the comment in DisplayObject|offset." ^offset! ! !DisplayText methodsFor: 'accessing'! offset: aPoint "Refer to the comment in DisplayObject|offset:." offset _ aPoint! ! !DisplayText methodsFor: 'accessing'! string "Answer the string of the characters displayed by the receiver." ^text string! ! !DisplayText methodsFor: 'accessing'! text "Answer the text displayed by the receiver." ^text! ! !DisplayText methodsFor: 'accessing'! text: aText "Set the receiver to display the argument, aText." text _ aText. form _ nil. self changed. ! ! !DisplayText methodsFor: 'accessing'! textStyle "Answer the style by which the receiver displays its text." ^textStyle! ! !DisplayText methodsFor: 'accessing'! textStyle: aTextStyle "Set the style by which the receiver should display its text." textStyle _ aTextStyle. form _ nil. self changed. ! ! !DisplayText methodsFor: 'displaying' stamp: 'di 2/2/1999 17:12'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: ruleInteger fillColor: aForm "Refer to the comment in DisplayObject|displayOn:at:clippingBox:rule:mask:." self form displayOn: aDisplayMedium at: aDisplayPoint + offset clippingBox: clipRectangle rule: ((ruleInteger = Form over and: [backColor isTransparent]) ifTrue: [Form paint] ifFalse: [ruleInteger]) fillColor: aForm! ! !DisplayText methodsFor: 'displaying'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: alignmentPoint with: relativePoint rule: ruleInteger fillColor: aForm "Refer to the comment in DisplayObject|displayOn:transformation:clippingBox:align:with:rule:mask:." | absolutePoint | absolutePoint _ displayTransformation applyTo: relativePoint. absolutePoint _ absolutePoint x asInteger @ absolutePoint y asInteger. self displayOn: aDisplayMedium at: absolutePoint - alignmentPoint clippingBox: clipRectangle rule: ruleInteger fillColor: aForm! ! !DisplayText methodsFor: 'displaying'! displayOnPort: aPort at: location self form displayOnPort: aPort at: location + offset! ! !DisplayText methodsFor: 'display box access'! boundingBox "Refer to the comment in DisplayObject|boundingBox." ^self form boundingBox! ! !DisplayText methodsFor: 'display box access'! computeBoundingBox "Compute minimum enclosing rectangle around characters." | character font width carriageReturn lineWidth lineHeight | carriageReturn _ Character cr. width _ lineWidth _ 0. font _ textStyle defaultFont. lineHeight _ textStyle lineGrid. 1 to: text size do: [:i | character _ text at: i. character = carriageReturn ifTrue: [lineWidth _ lineWidth max: width. lineHeight _ lineHeight + textStyle lineGrid. width _ 0] ifFalse: [width _ width + (font widthOf: character)]]. lineWidth _ lineWidth max: width. ^offset extent: lineWidth @ lineHeight! ! !DisplayText methodsFor: 'converting' stamp: 'tk 10/21/97 12:28'! asParagraph "Answer a Paragraph whose text and style are identical to that of the receiver." | para | para _ Paragraph withText: text style: textStyle. para foregroundColor: foreColor backgroundColor: backColor. backColor isTransparent ifTrue: [para rule: Form paint]. ^ para! ! !DisplayText methodsFor: 'private' stamp: 'sma 4/30/2000 09:28'! composeForm | form1 | Smalltalk isMorphic ifTrue: [form1 _ (TextMorph new contentsAsIs: text) imageFormDepth: 1. form _ (ColorForm extent: form1 extent) offset: offset; colors: (Array with: (backColor == nil ifTrue: [Color transparent] ifFalse: [backColor]) with: (foreColor == nil ifTrue: [Color black] ifFalse: [foreColor])). form1 displayOn: form] ifFalse: [form _ self asParagraph asForm]! ! !DisplayText methodsFor: 'private'! setText: aText textStyle: aTextStyle offset: aPoint text _ aText. textStyle _ aTextStyle. offset _ aPoint. form _ nil! ! !DisplayText methodsFor: 'color'! backgroundColor backColor == nil ifTrue: [^ Color transparent]. ^ backColor! ! !DisplayText methodsFor: 'color'! foregroundColor foreColor == nil ifTrue: [^ Color black]. ^ foreColor! ! !DisplayText methodsFor: 'color'! foregroundColor: cf backgroundColor: cb foreColor _ cf. backColor _ cb! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayText class instanceVariableNames: ''! !DisplayText class methodsFor: 'instance creation'! text: aText "Answer an instance of me such that the text displayed is aText according to the system's default text style." ^self new setText: aText textStyle: DefaultTextStyle copy offset: 0 @ 0! ! !DisplayText class methodsFor: 'instance creation'! text: aText textStyle: aTextStyle "Answer an instance of me such that the text displayed is aText according to the style specified by aTextStyle." ^self new setText: aText textStyle: aTextStyle offset: 0 @ 0! ! !DisplayText class methodsFor: 'instance creation'! text: aText textStyle: aTextStyle offset: aPoint "Answer an instance of me such that the text displayed is aText according to the style specified by aTextStyle. The display of the information should be offset by the amount given as the argument, aPoint." ^self new setText: aText textStyle: aTextStyle offset: aPoint! ! !DisplayText class methodsFor: 'examples' stamp: 'mjg 4/28/2000 14:31'! example "Continually prints two lines of text wherever you point with the cursor and press any mouse button. Terminate by pressing any button on the mouse." | tx | tx _ 'this is a line of characters and this is the second line.' asDisplayText. tx foregroundColor: Color black backgroundColor: Color transparent. tx _ tx alignedTo: #center. [Sensor anyButtonPressed] whileFalse: [tx displayOn: Display at: Sensor cursorPoint] "DisplayText example."! ! View subclass: #DisplayTextView instanceVariableNames: 'rule mask editParagraph centered ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Views'! !DisplayTextView commentStamp: '' prior: 0! I represent a view of an instance of DisplayText.! !DisplayTextView methodsFor: 'initialize-release'! initialize "Refer to the comment in View|initialize." super initialize. centered _ false! ! !DisplayTextView methodsFor: 'accessing'! centered centered _ true. self centerText! ! !DisplayTextView methodsFor: 'accessing'! fillColor "Answer an instance of class Form that is to be used as the mask when displaying the receiver's model (a DisplayText)." ^ mask! ! !DisplayTextView methodsFor: 'accessing'! fillColor: aForm "Set aForm to be the mask used when displaying the receiver's model." mask _ aForm! ! !DisplayTextView methodsFor: 'accessing'! isCentered ^centered! ! !DisplayTextView methodsFor: 'accessing'! mask "Answer an instance of class Form that is to be used as the mask when displaying the receiver's model (a DisplayText)." ^ mask! ! !DisplayTextView methodsFor: 'accessing'! rule "Answer a number from 0 to 15 that indicates which of the sixteen display rules is to be used when copying the receiver's model (a DisplayText) onto the display screen." rule == nil ifTrue: [^self defaultRule] ifFalse: [^rule]! ! !DisplayTextView methodsFor: 'accessing'! rule: anInteger "Set anInteger to be the rule used when displaying the receiver's model." rule _ anInteger! ! !DisplayTextView methodsFor: 'controller access'! defaultController "Refer to the comment in View|defaultController." ^self defaultControllerClass newParagraph: editParagraph! ! !DisplayTextView methodsFor: 'controller access'! defaultControllerClass "Refer to the comment in View|defaultControllerClass." ^ParagraphEditor! ! !DisplayTextView methodsFor: 'window access'! defaultWindow "Refer to the comment in View|defaultWindow." ^self inverseDisplayTransform: (editParagraph boundingBox expandBy: 6 @ 6)! ! !DisplayTextView methodsFor: 'window access'! window: aWindow "Refer to the comment in View|window:." super window: aWindow. self centerText! ! !DisplayTextView methodsFor: 'model access'! model: aDisplayText "Refer to the comment in View|model:." super model: aDisplayText. editParagraph _ model asParagraph. self centerText! ! !DisplayTextView methodsFor: 'displaying'! display "Refer to the comment in View|display." self isUnlocked ifTrue: [self positionText]. super display! ! !DisplayTextView methodsFor: 'displaying'! displayView "Refer to the comment in View|displayView." self clearInside. (self controller isKindOf: ParagraphEditor ) ifTrue: [controller changeParagraph: editParagraph]. editParagraph foregroundColor: self foregroundColor backgroundColor: self backgroundColor. self isCentered ifTrue: [editParagraph displayOn: Display transformation: self displayTransformation clippingBox: self insetDisplayBox fixedPoint: editParagraph boundingBox center] ifFalse: [editParagraph displayOn: Display]! ! !DisplayTextView methodsFor: 'displaying'! uncacheBits "Normally only sent to a StandardSystemView, but for casees where a DisplayTextView is used alone, without a superview, in which we make this a no-op, put in so that the Character Recognizer doesn't fail. 8/9/96 sw"! ! !DisplayTextView methodsFor: 'deEmphasizing'! deEmphasizeView "Refer to the comment in View|deEmphasizeView." (self controller isKindOf: ParagraphEditor) ifTrue: [controller deselect]! ! !DisplayTextView methodsFor: 'private'! centerText self isCentered ifTrue: [editParagraph align: editParagraph boundingBox center with: self getWindow center]! ! !DisplayTextView methodsFor: 'private'! defaultRule ^Form over! ! !DisplayTextView methodsFor: 'private'! positionText | box | box _ (self displayBox insetBy: 6@6) origin extent: editParagraph boundingBox extent. editParagraph wrappingBox: box clippingBox: box. self centerText! ! !DisplayTextView methodsFor: 'lock access'! lock "Refer to the comment in View|lock. Must do what would be done by displaying..." self isUnlocked ifTrue: [self positionText]. super lock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayTextView class instanceVariableNames: ''! !DisplayTextView class methodsFor: 'examples'! example2 "Create a standarad system view with two parts, one editable, the other not." | topView aDisplayTextView | topView _ StandardSystemView new. topView label: 'Text Editor'. aDisplayTextView _ self new model: 'test string label' asDisplayText. aDisplayTextView controller: NoController new. aDisplayTextView window: (0 @ 0 extent: 100 @ 100). aDisplayTextView borderWidthLeft: 2 right: 0 top: 2 bottom: 2. topView addSubView: aDisplayTextView. aDisplayTextView _ self new model: 'test string' asDisplayText. aDisplayTextView window: (0 @ 0 extent: 100 @ 100). aDisplayTextView borderWidth: 2. topView addSubView: aDisplayTextView align: aDisplayTextView viewport topLeft with: topView lastSubView viewport topRight. topView controller open "DisplayTextView example2"! ! !DisplayTextView class methodsFor: 'examples'! example3 "Create a passive view of some text on the screen." | view | view_ self new model: 'this is a test of one line and the second line' asDisplayText. view translateBy: 100@100. view borderWidth: 2. view display. view release "DisplayTextView example3"! ! !DisplayTextView class methodsFor: 'examples'! example4 "Create four passive views of some text on the screen with fat borders." | view | view_ self new model: 'this is a test of one line and the second line' asDisplayText. view translateBy: 100@100. view borderWidth: 5. view display. 3 timesRepeat: [view translateBy: 100@100. view display]. view release "DisplayTextView example4"! ! Object subclass: #DisplayTransform instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Transformations'! !DisplayTransform commentStamp: '' prior: 0! This class represents a base for generic transformations of 2D points between different coordinate systems (including scaling and rotation). The transformations map objects between one coordinate system and another where it is assumed that a nested hierarchy of transformations can be defined. It is assumed that transformations deal with Integer points. All transformations should return Integer coordinates (even though float points may be passed in as argument). Compositions of transformations MUST work in the following order. A 'global' transformation (the argument in #composedWithGlobal:) is defined as a transformation that takes place between the receiver (the 'local') transformation and any 'global' point computations, whereas a 'local' transformation (e.g., the argument in #composedWithLocal:) takes place between the receiver ('global') and any 'local' points. For the transformation methods this means that combining a global and a local transformation will result in the following order: globalPointToLocal: globalPoint "globalPoint -> globalTransform -> localTransform -> locaPoint" ^localTransform globalPointToLocal: (globalTransform globalPointToLocal: globalPoint) localPointToGlobal: localPoint "localPoint -> localTransform -> globalTransform -> globalPoint" ^globalTransform localPointToGlobal: (localTransform localPointToGlobal: localPoint) ! !DisplayTransform methodsFor: 'initialize' stamp: 'ar 11/2/1998 23:18'! setIdentity "Initialize the receiver to the identity transformation (e.g., not affecting points)" ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'accessing' stamp: 'ar 11/2/1998 19:43'! inverseTransformation "Return the inverse transformation of the receiver" ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 22:47'! isCompositeTransform "Return true if the receiver is a composite transformation. Composite transformations may have impact on the accuracy." ^false! ! !DisplayTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 16:17'! isIdentity "Return true if the receiver is the identity transform; that is, if applying to a point returns the point itself." ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 22:48'! isMatrixTransform2x3 "Return true if the receiver is 2x3 matrix transformation" ^false! ! !DisplayTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 22:48'! isMorphicTransform "Return true if the receiver is a MorphicTransform, that is specifies the transformation values explicitly." ^false! ! !DisplayTransform methodsFor: 'testing' stamp: 'ar 11/2/1998 16:16'! isPureTranslation "Return true if the receiver specifies no rotation or scaling." ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'composing' stamp: 'ar 11/2/1998 16:15'! composedWithGlobal: aTransformation "Return the composition of the receiver and the global transformation passed in. A 'global' transformation is defined as a transformation that takes place between the receiver (the 'local') transformation and any 'global' point computations, e.g., for the methods globalPointToLocal: globalPoint globalPoint -> globalTransform -> localTransform -> locaPoint localPointToGlobal: localPoint localPoint -> localTransform -> globalTransform -> globalPoint " ^aTransformation composedWithLocal: self! ! !DisplayTransform methodsFor: 'composing' stamp: 'ar 11/2/1998 16:41'! composedWithLocal: aTransformation "Return the composition of the receiver and the local transformation passed in. A 'local' transformation is defined as a transformation that takes place between the receiver (the 'global') transformation and any 'local' point computations, e.g., for the methods globalPointToLocal: globalPoint globalPoint -> globalTransform -> localTransform -> locaPoint localPointToGlobal: localPoint localPoint -> localTransform -> globalTransform -> globalPoint " self isIdentity ifTrue:[^ aTransformation]. aTransformation isIdentity ifTrue:[^ self]. ^ CompositeTransform new globalTransform: self localTransform: aTransformation! ! !DisplayTransform methodsFor: 'transforming points' stamp: 'ar 11/2/1998 16:17'! globalPointToLocal: aPoint "Transform aPoint from global coordinates into local coordinates" ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'transforming points' stamp: 'ar 11/9/1998 14:35'! globalPointsToLocal: inArray "Transform all the points of inArray from global into local coordinates" ^inArray collect:[:pt| self globalPointToLocal: pt]! ! !DisplayTransform methodsFor: 'transforming points' stamp: 'ar 11/2/1998 16:18'! localPointToGlobal: aPoint "Transform aPoint from local coordinates into global coordinates" ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'transforming points' stamp: 'ar 11/9/1998 14:35'! localPointsToGlobal: inArray "Transform all the points of inArray from local into global coordinates" ^inArray collect:[:pt| self localPointToGlobal: pt]! ! !DisplayTransform methodsFor: 'transforming rects' stamp: 'ar 11/2/1998 16:19'! globalBoundsToLocal: aRectangle "Transform aRectangle from global coordinates into local coordinates" ^Rectangle encompassing: (self globalPointsToLocal: aRectangle corners)! ! !DisplayTransform methodsFor: 'transforming rects' stamp: 'ar 11/2/1998 16:19'! localBoundsToGlobal: aRectangle "Transform aRectangle from local coordinates into global coordinates" ^Rectangle encompassing: (self localPointsToGlobal: aRectangle corners)! ! !DisplayTransform methodsFor: 'transforming rects' stamp: 'di 10/25/1999 12:49'! sourceQuadFor: aRectangle ^ aRectangle innerCorners collect: [:p | self globalPointToLocal: p]! ! !DisplayTransform methodsFor: 'converting' stamp: 'ar 11/2/1998 19:59'! asCompositeTransform "Represent the receiver as a composite transformation" ^CompositeTransform new globalTransform: self localTransform: self species identity! ! !DisplayTransform methodsFor: 'converting' stamp: 'ar 11/2/1998 20:01'! asMatrixTransform2x3 "Represent the receiver as a 2x3 matrix transformation" ^self subclassResponsibility! ! !DisplayTransform methodsFor: 'encoding' stamp: 'ls 10/9/1999 18:56'! encodeForRemoteCanvas "encode this transform into a string for use by a RemoteCanvas" ^self subclassResponsibility! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayTransform class instanceVariableNames: ''! !DisplayTransform class methodsFor: 'instance creation' stamp: 'ls 3/19/2000 16:44'! fromRemoteCanvasEncoding: encoded | type | "decode a transform from the given encoded string" type _ (ReadStream on: encoded) upTo: $,. type = 'Morphic' ifTrue: [ ^MorphicTransform fromRemoteCanvasEncoding: encoded ]. type = 'Matrix' ifTrue: [ ^MatrixTransform2x3 fromRemoteCanvasEncoding: encoded ]. type = 'Composite' ifTrue: [ ^CompositeTransform fromRemoteCanvasEncoding: encoded ]. ^self error: 'invalid transform encoding'! ! !DisplayTransform class methodsFor: 'instance creation' stamp: 'ar 11/2/1998 20:55'! identity ^self new setIdentity! ! BorderedMorph subclass: #DoCommandOnceMorph instanceVariableNames: 'target command actionBlock innerArea ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !DoCommandOnceMorph commentStamp: '' prior: 0! I am used to execute a once-only command. My first use was in loading/saving the current project. In such cases it is necessary to be in another project to do the actual work. So an instance of me is added to a new world/project and that project is entered. I do my stuff (save/load followed by a re-enter of the previous project) and everyone is happy.! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 11:49'! actionBlock: aBlock actionBlock _ aBlock! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 17:30'! addText: aString | t | t _ TextMorph new beAllFont: (TextStyle default fontOfSize: 26); contents: aString. self extent: t extent * 3. innerArea _ Morph new color: Color white; extent: self extent - (16@16); position: self position + (8@8); lock. self addMorph: innerArea. self addMorph: (t position: self position + t extent; lock).! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 17:35'! initialize super initialize. borderColor _ Color blue. borderWidth _ 8. self useRoundedCorners.! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 12:19'! openInWorld: aWorld self position: aWorld topLeft + (aWorld extent - self extent // 2). super openInWorld: aWorld! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 17:30'! step | goForIt | actionBlock ifNil: [^self stopStepping]. goForIt _ actionBlock. actionBlock _ nil. [ goForIt value. ] on: ProgressTargetRequestNotification do: [ :ex | ex resume: innerArea]. "in case a save/load progress display needs a home" ! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 11:46'! stepTime ^1 ! ! !DoCommandOnceMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 11:50'! wantsSteps ^actionBlock notNil ! ! Object subclass: #DocLibrary instanceVariableNames: 'group lastUpdate lastUpdateName methodVersions ' classVariableNames: 'DocsCachePath DropBox External ' poolDictionaries: '' category: 'Tools-Changes'! !DocLibrary commentStamp: '' prior: 0! Method and Class shared documentation. Pane in browser. url for each official version of each method. Each update server group have a prefix (i=internal, e=external). Point.x;.738.sp Pane holds a pasteupmorph with comments and examples. Must be very careful to give the right options for when to look for docs. Could be annoying. Look on disk. If there, bring it in in background. If not there, and network has been active this session, or within 15 mins, get from server (in background) and cache on disk. When get updates, check for latest version of all comments in the cache. Need quick registration of version of inst vars (less bulky and quick to check.) If all inst var lists are the same as a certain release, mark it as that. Each release (or update?) have an automatic known registration. Get doc, Get all docs for this class. // Net: When you ask, If net has been used, Always (always gets in background) // From disk: When you ask, always (laptop users do the former). Security: Squeakers can write anything, including players. Users can only add Morphic objects, not players. (No new code) Mech: Users write file to a server with open drop box. Our server in Alan's office (the librarian) grabs the files once every two minutes, and scans them. Code must be same as before. Saves a copy. Writes on official directory on two outside servers. Any combo of objects of existing classes that can crash the system, or deny service? Should the librarian try all buttons first? If it crashes, won't post it. Need another machine to check if the librarian is up, and beep Ted. Could check a time stamp on the main server. Users could also tell if librarian is up. Number of docs in the queue. If we had mime really down, could have squeak email the page to the librarian. What if the user does not know his pop server? Use a standard one? How keep spam out? ----- [ ] set up folders, get reader going (no good interface yet) group Name of group of servers (internal/external) lastUpdate Number of last update we have. lastUpdateName File name without number for checking against ChangeSets. methodVersions Dictionary (class.method -> #(45 secs 120 secs 198 secs)) updates that this method appeared in. From my version, check backwards till find a doc file on server. secs is (Time totalSeconds) of file on the server (by its directory) of last version I have. so can tell if have most recent one. (use one day slop for older ones) point.x;.205.sp rectangle.205.sp Names of this form that are too long are run through a dictionary and given a serial number. It is (first two letters of class name), (crc16 of first half), (crc16 of second half).205.sp. Can't store over a file in the drop box, so append random number to end of name. Look at times to figure out which one is most recent when empty drop box. localCachePath name of cache directory on local disk. (Is shared between Squeaks on this machine, so might have things too new.) In the form of a url 'file://disk/folder/' Algorithm for finding the doc file: Find my version Find version of current def of method relative to me. make file name. look locally check server, might have changed. When put new update, no extra work needed. When put a new version of the system, include External with methodVersions filled in. If methods changed and not in a numbered update, must run a method to put them in the database. When get updates, add new entries as we read updates. Default method update number is 0. AA _ DocLibrary new initialize. AA scanFolder: 'file://Ted''s/Updates 328-/' from: 595. DocLibrary classPool at: #External put: AA. DocLibrary new setUp. [How use internal updates, but do documentation for external? Disable feature of adding to table when get updates. Point to UIUC external directory and scan the latest ext updates.] When a docPane comes in, store property: #classAndMethod. To put out, menu item "Broadcast Documentation" in PasteUpMorph that has the property. DocLibrary puts out this morph. Writes to drop box and local cache. In codePane, on more menu, "Fetch Documentation" (if none, ask if want blank one). Creates a new pasteUpMorph after verifying that it doesn't have one. Later need preference and do fetch always and in the background. Crude review process -- a method here that brings up each pane that is in drop box (10 at a time). First just shows code and text, not bring in. Then bring in. And a way for me to store it in the official directory. (Do as menu items in file list?) And archives and deletes for drop box. (I do manually twice a day?) When write a file, take lastUpdateName and look for it in ChangeSet names. When find, see if this method occurs in any newer changeSet. If so, writing to an older version. "The documentation will be attached to the version of this method in xxx.cs. You have a newer version of that method in yyy.cs. If what you are storing applies only to the newer version, please do not broadcast it!! Wait until the new version is in an external update." Broadcast to all Squeak users \ Cancel. (Otherwise "Make this documentation available to all Squeak users?") When fetch any updates, look for "latest.ix" Has format: External 407 'aChangeSet.cs' 376.ix 'class method:' updateNumber 'class method' updateNumber 'class' updateNumber Keep local copy of updates.list and read it for files not mentioned yet in latest.ix. ¥Warn the user if the method he is documenting is too new to be on the External updates server. ¥Correcting the database of method versions when new External Updates are released. ¥Create the file to put on the server with the database info for a new update. ¥Methods to help the reviewer (me) scan files. It will show me all the code, all the doits in text, and all the text. ¥Allow documentation for classes, as opposed to methods. (written in file, in dict, just need interface) self scanUpdatesIn: (ServerDirectory serverNamed: 'UpdatesExtUIUC') realUrl, '/'. self updateMethodVersions. [ ] When write, write to cache also. [ ] If can't write to server, tell user to store again later. [ ] Sparse database to tell if method has a docPane -- user fetches it explicitly. [ ] Write to both servers. Retrieve from either. Drop box on just UIUC. ! !DocLibrary methodsFor: 'initialize' stamp: 'tk 2/4/1999 12:29'! initialize lastUpdate _ 0. methodVersions _ Dictionary new.! ! !DocLibrary methodsFor: 'initialize' stamp: 'tk 3/9/1999 12:55'! setUp "set up the External version" | email | self initialize. External _ self. group _ 'Squeak Public Updates'. "right for http, but not for ftp" lastUpdate _ 599. lastUpdateName _ 'MTMcontainsPoint-ar.cs'. DropBox _ ServerDirectory new. DropBox server: 'squeak.cs.uiuc.edu'; directory: 'incoming'. DropBox type: #ftp. email _ nil. "Celeste popUserName." "If nil, we ask at drop time" DropBox user: 'anonymous'; password: email. DropBox moniker: 'Doc Pane DropBox'. "later allow a second server" ! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/13/1999 13:45'! assureCacheFolder "Make sure there is a folder docPaneCache and a file: url for it in DocsCachePath. In local folder or one level up. User may wish to install a different path and folder name (as a url). Could be a url to a local server." | dir local | DocsCachePath ifNil: [ dir _ FileDirectory default. (dir includesKey: 'docPaneCache') ifTrue: [ DocsCachePath _ dir url, 'docPaneCache/']]. DocsCachePath ifNil: [ dir _ FileDirectory default containingDirectory. DocsCachePath _ dir url, 'docPaneCache/'. (dir includesKey: 'docPaneCache') ifFalse: [ ^ dir createDirectory: 'docPaneCache']]. "create the folder" local _ ServerDirectory new fullPath: DocsCachePath. local exists ifFalse: [ DocsCachePath _ nil. "we must be on a new disk" self assureCacheFolder].! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/13/1999 14:03'! cache: strm as: fileName "Save the file locally in case the network is not available." | local | local _ ServerDirectory new fullPath: DocsCachePath. (local fileNamed: fileName) nextPutAll: strm contents; close.! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/5/1999 07:33'! docNamesAt: classAndMethod "Return a list of fileNames to try for this method. 'Point x:' is form of classAndMethod." | key verList fileNames | key _ DocLibrary properStemFor: classAndMethod. verList _ methodVersions at: key ifAbsent: [#()]. fileNames _ OrderedCollection new. 1 to: verList size by: 2 do: [:ind | fileNames addFirst: key,'.',(verList at: ind) printString, '.sp']. fileNames addLast: key,'.0.sp'. ^ fileNames! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/5/1999 07:33'! docNamesAt: classAndMethod asOf: currentUpdate "Return a list of fileNames to try for this method. 'Point x:' is form of classAndMethod." | key verList fileNames | key _ DocLibrary properStemFor: classAndMethod. verList _ methodVersions at: key ifAbsent: [#()]. fileNames _ OrderedCollection new. 1 to: verList size by: 2 do: [:ind | (verList at: ind) <= currentUpdate ifTrue: [ fileNames addFirst: key,'.',(verList at: ind) printString, '.sp']]. fileNames addLast: key,'.0.sp'. ^ fileNames! ! !DocLibrary methodsFor: 'doc pane' stamp: 'mdr 8/31/2000 18:48'! docObjectAt: classAndMethod "Return a morphic object that is the documentation pane for this method. nil if none can be found. Look on both the network and the disk." | fileNames server aUrl strm local obj | methodVersions size = 0 ifTrue: [self updateMethodVersions]. "first time" fileNames _ self docNamesAt: classAndMethod. self assureCacheFolder. self haveNetwork ifTrue: [ "server _ (ServerDirectory groupNamed: group) clone." "Note: directory ends with '/updates' which needs to be '/docpane', but altUrl end one level up" server _ ServerDirectory groupNamed: group. "later try multiple servers" aUrl _ server altUrl, 'docpane/'. fileNames do: [:aVersion | strm _ HTTPSocket httpGetNoError: aUrl,aVersion args: nil accept: 'application/octet-stream'. strm class == RWBinaryOrTextStream ifTrue: [ self cache: strm as: aVersion. strm reset. obj _ strm fileInObjectAndCode asMorph. (obj valueOfProperty: #classAndMethod) = classAndMethod ifFalse: [ self inform: 'suspicious object'. obj setProperty: #classAndMethod toValue: classAndMethod]. ^ obj]. "The pasteUpMorph itself" "If file not there, error 404, just keep going"]]. local _ ServerDirectory new fullPath: DocsCachePath. "check that it is really there -- let user respecify" fileNames do: [:aVersion | (local includesKey: aVersion) ifTrue: [ strm _ local readOnlyFileNamed: aVersion. obj _ strm fileInObjectAndCode asMorph. (obj valueOfProperty: #classAndMethod) = classAndMethod ifFalse: [ self inform: 'suspicious object'. obj setProperty: #classAndMethod toValue: classAndMethod]. Transcript cr; show: 'local cache: ', aVersion. ^ obj]. "The pasteUpMorph itself" "If file not there, just keep looking"]. "Never been documented" ^ nil! ! !DocLibrary methodsFor: 'doc pane' stamp: 'RAA 5/25/2000 08:16'! fetchDocSel: aSelector class: className "Look on servers to see if there is documentation pane for the selected message. Take into account the current update number. If not, ask the user if she wants to create a blank one." | key response docPane ext | key _ aSelector size = 0 ifFalse: [className, ' ', aSelector] ifTrue: [className]. (self openDocAt: key) ifNil: [ response _ (PopUpMenu labels: 'Create new page\Cancel' withCRs) startUpWithCaption: 'No documentation exists for this method.\ Would you like to write some?' withCRs. response = 1 ifTrue: [ docPane _ PasteUpMorph new. docPane color: Color white; borderWidth: 2; borderColor: Color green. docPane setProperty: #classAndMethod toValue: key. docPane setProperty: #initialExtent toValue: (ext _ 200@200). docPane topLeft: (RealEstateAgent initialFrameFor: docPane world: Smalltalk currentWorld) origin. docPane extent: ext. docPane addMorph: (TextMorph new topLeft: docPane topLeft + (10@10); extent: docPane width - 15 @ 30). Smalltalk currentWorld addMorph: docPane]]. "If found, openDocAt: put it on the screen"! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/15/1999 16:30'! haveNetwork | hn ask | (hn _ NetNameResolver haveNetwork) class == Symbol ifFalse: [^ hn]. hn == #expired ifTrue: [ ask _ self confirm: 'OK to connect to the internet?'. ask ifFalse: [NetNameResolver haveNetwork: false]. ^ ask]. ^ false! ! !DocLibrary methodsFor: 'doc pane' stamp: 'RAA 5/25/2000 08:17'! openDocAt: classAndMethod | docPane | (docPane _ self docObjectAt: classAndMethod) ifNotNil: [ docPane setProperty: #initialExtent toValue: docPane bounds extent. docPane topLeft: (RealEstateAgent initialFrameFor: docPane world: Smalltalk currentWorld) origin. Smalltalk currentWorld addMorph: docPane]. ^ docPane! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/15/1999 17:10'! saveDoc: aMorph "Broadcast this documentation to the Squeak community. Associate it with the method it documents. Send to a drop box, where it can be inspected before being posted on External servers." | classAndMethod fName remoteFile | classAndMethod _ aMorph valueOfProperty: #classAndMethod. classAndMethod ifNil: [ ^ self error: 'need to know the class and method']. "later let user set it" fName _ (self docNamesAt: classAndMethod) first. DropBox user asLowercase = 'anonymous' ifTrue: [ fName _ fName, 1000 atRandom printString]. "trusted users store directly" DropBox password. "In case user has to type it. Avoid timeout from server" Cursor wait showWhile: [ remoteFile _ DropBox fileNamed: fName. remoteFile fileOutClass: nil andObject: aMorph. "remoteFile close"]. ! ! !DocLibrary methodsFor: 'doc pane' stamp: 'tk 2/14/1999 20:40'! saveDocCheck: aMorph "Make sure the document gets attached to the version of the code that the user was looking at. Is there a version of this method in a changeSet beyond the updates we know about? Works even when the user has internal update numbers and the documentation is for external updates (It always is)." | classAndMethod parts selector class lastUp beyond ours docFor unNum ok key verList ext response | classAndMethod _ aMorph valueOfProperty: #classAndMethod. classAndMethod ifNil: [ ^ self error: 'need to know the class and method']. "later let user set it" parts _ classAndMethod findTokens: ' .'. selector _ parts last asSymbol. class _ Smalltalk at: (parts first asSymbol) ifAbsent: [^ self saveDoc: aMorph]. parts size = 3 ifTrue: [class _ class class]. "Four indexes we are looking for: docFor = highest numbered below lastUpdate that has method. unNum = a higher unnumbered set that has method. lastUp = lastUpdate we know about in methodVersions beyond = any set about lastUp that has the method." ChangeSorter gatherChangeSets doWithIndex: [:cs :ind | "youngest first" (cs name includesSubString: lastUpdateName) ifTrue: [lastUp _ ind]. (cs atSelector: selector class: class) ~~ #none ifTrue: [ lastUp ifNotNil: [beyond _ ind. ours _ cs name] ifNil: [cs name first isDigit ifTrue: [docFor _ ind] ifFalse: [unNum _ ind. ours _ cs name]]]]. "See if version the user sees is the version he is documenting" ok _ beyond == nil. unNum ifNotNil: [docFor ifNotNil: [ok _ docFor > unNum] ifNil: [ok _ false]]. "old changeSets gone" ok ifTrue: [^ self saveDoc: aMorph]. key _ DocLibrary properStemFor: classAndMethod. verList _ (methodVersions at: key ifAbsent: [#()]), #(0 0). ext _ verList first. "external update number we will write to" response _ (PopUpMenu labels: 'Cancel\Broadcast Page' withCRs) startUpWithCaption: 'You are documenting a method in External Update ', ext asString, '.\There is a more recent version of that method in ' withCRs, ours, '.\If you are explaining the newer version, please Cancel.\Wait until that version appears in an External Update.' withCRs. response = 2 ifTrue: [self saveDoc: aMorph]. ! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/13/1999 11:53'! absorbAfter: oldVersion from: fileName "Read the .ix file and add to the methodVersions database. See class comment." | server aUrl strm newUpdate newName prevFile classAndMethod updateID key verList new | server _ ServerDirectory groupNamed: group. "later try multiple servers" aUrl _ server altUrl, 'docpane/', fileName. strm _ HTTPSocket httpGetNoError: aUrl args: nil accept: 'application/octet-stream'. strm class == RWBinaryOrTextStream ifFalse: [^ false]. (strm upTo: $ ) = 'External' ifFalse: [strm close. ^ false]. newUpdate _ Integer readFrom: strm. newUpdate = oldVersion ifTrue: [strm close. ^ false]. "already have it" strm upTo: $'. newName _ strm nextDelimited: $'. strm upTo: Character cr. prevFile _ strm upTo: Character cr. "does this report on updates just after what I know?" oldVersion = (prevFile splitInteger first) ifFalse: [ strm close. ^ prevFile]. "see earlier sucessor file" [strm atEnd] whileFalse: [ strm upTo: $'. classAndMethod _ strm nextDelimited: $'. strm next. updateID _ Integer readFrom: strm. key _ DocLibrary properStemFor: classAndMethod. verList _ methodVersions at: key ifAbsent: [#()]. (verList includes: updateID) ifFalse: [ new _ verList, (Array with: updateID with: -1 "file date seen"). methodVersions at: key put: new]]. strm close. lastUpdate _ newUpdate. lastUpdateName _ newName. ^ true! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/5/1999 08:07'! scan: updateStream updateID: updateID "Scan this update file and remember the update numbers of the methods." | changeList ee semi key verList new | updateStream reset; readOnly. Cursor read showWhile: [changeList _ ChangeList new scanFile: updateStream from: 0 to: updateStream size]. changeList list do: [:entry | ee _ nil. (entry beginsWith: 'method: ') ifTrue: [ (semi _ entry indexOf: $;) = 0 ifTrue: [semi _ entry size] ifFalse: [semi _ semi-1]. ee _ entry copyFrom: 9 to: semi]. (entry beginsWith: 'class comment for ') ifTrue: [ (semi _ entry indexOf: $;) = 0 ifTrue: [semi _ entry size] ifFalse: [semi _ semi-1]. ee _ entry copyFrom: 19 to: semi]. "comment for whole class" ee ifNotNil: [ key _ DocLibrary properStemFor: ee. Transcript show: key; cr. verList _ methodVersions at: key ifAbsent: [#()]. (verList includes: updateID) ifFalse: [ new _ verList, (Array with: updateID with: -1 "file date seen"). methodVersions at: key put: new]]. ].! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/13/1999 11:22'! scan: updateStream updateID: updateID writeOn: strm "Scan this update file and remember the update numbers of the methods." | changeList ee semi | updateStream reset; readOnly. Cursor read showWhile: [changeList _ ChangeList new scanFile: updateStream from: 0 to: updateStream size]. changeList list do: [:entry | ee _ nil. (entry beginsWith: 'method: ') ifTrue: [ (semi _ entry indexOf: $;) = 0 ifTrue: [semi _ entry size] ifFalse: [semi _ semi-1]. ee _ entry copyFrom: 9 to: semi]. (entry beginsWith: 'class comment for ') ifTrue: [ (semi _ entry indexOf: $;) = 0 ifTrue: [semi _ entry size] ifFalse: [semi _ semi-1]. ee _ entry copyFrom: 19 to: semi]. "comment for whole class" ee ifNotNil: [ Transcript show: ee; cr. strm cr; nextPutAll: ee surroundedBySingleQuotes; space; nextPutAll: updateID asString]. ].! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/11/1999 12:06'! scanFolder: directoryUrl from: updateID "Scan all update files in the directory starting at updateID+1. updates.list must be present to tell us the file names." | updateList line num | updateList _ (ServerFile new fullPath: directoryUrl,'updates.list') asStream. [line _ updateList upTo: Character cr. updateList atEnd] whileFalse: [ line first isDigit ifTrue: [ num _ line splitInteger first. num > updateID ifTrue: [ self scan: (ServerFile new fullPath: directoryUrl,line) asStream updateID: num] ]]. lastUpdate <= num ifTrue: [ lastUpdate _ num. lastUpdateName _ line splitInteger last]. ! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/13/1999 11:25'! scanUpdatesIn: directoryUrl "Scan all update files in the directory starting at lastUpdate+1. Create a .ix file on my local hard disk. updates.list must be present to tell us the file names." | updateList line num temp out | updateList _ (ServerFile new fullPath: directoryUrl,'updates.list') asStream. temp _ WriteStream on: (String new: 2000). [line _ updateList upTo: Character cr. updateList atEnd] whileFalse: [ line first isDigit ifTrue: [ num _ line splitInteger first. num > lastUpdate ifTrue: [ self scan: (ServerFile new fullPath: directoryUrl,line) asStream updateID: num writeOn: temp] ]]. num >= lastUpdate ifTrue: [ out _ FileStream newFileNamed: 'to', num asString, '.ix'. out nextPutAll: 'External ', num asString; space. line splitInteger last storeOn: out. "quoted" out cr; nextPutAll: lastUpdate asString, '.ix' "; cr". "temp begins with cr" out nextPutAll: temp contents; close. self inform: 'Rename latest.ix to ', lastUpdate asString, '.ix on both external servers. Put to', num asString, '.ix on both and call it latest.ix']. ! ! !DocLibrary methodsFor: 'database of updates' stamp: 'tk 2/15/1999 14:49'! updateMethodVersions "See if any new updates have occurred, and put their methods into the database." | indexFile list result | self haveNetwork ifFalse: [^ self]. indexFile _ 'latest.ix'. list _ OrderedCollection new. [result _ self absorbAfter: lastUpdate from: indexFile. "boolean if succeeded, or we are up to date, or server not available" result class == String] whileTrue: [ "result is the prev file name" list addFirst: indexFile. indexFile _ result]. list do: [:aFile | self absorbAfter: lastUpdate from: aFile]. "should always work this time" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DocLibrary class instanceVariableNames: ''! !DocLibrary class methodsFor: 'as yet unclassified' stamp: 'tk 2/4/1999 15:58'! external "The dictionary for the External Updates" ^ External! ! !DocLibrary class methodsFor: 'as yet unclassified' stamp: 'tk 2/5/1999 08:11'! properStemFor: classAndMethod "Put 'class method' into proper form as a file name. Leave upper and lower case. The fileName must be short enough and have proper characters for all platforms and servers." | sz | classAndMethod size > 23 ifTrue: ["too long" sz _ classAndMethod size. "input contains space and :, not . and ;" ^ (classAndMethod copyFrom: 1 to: 2), ((classAndMethod copyFrom: 3 to: sz//2) crc16 printString), ((classAndMethod copyFrom: sz//2+1 to: sz) crc16 printString) ]. ^ (classAndMethod copyReplaceAll: ' ' with: '.') copyReplaceAll: ':' with: ';' ! ! FileDirectory subclass: #DosFileDirectory instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Files'! !DosFileDirectory commentStamp: '' prior: 0! I represent a DOS or Windows FileDirectory. ! !DosFileDirectory methodsFor: 'as yet unclassified' stamp: 'di 6/18/1998 08:57'! checkName: aFileName fixErrors: fixing "Check if the file name contains any invalid characters" | fName badChars hasBadChars | fName _ super checkName: aFileName fixErrors: fixing. badChars _ #( $: $< $> $| $/ $\ $? $* $") asSet. hasBadChars _ fName includesAnyOf: badChars. (hasBadChars and:[fixing not]) ifTrue:[^self error:'Invalid file name']. hasBadChars ifFalse:[^ fName]. ^ fName collect: [:char | (badChars includes: char) ifTrue:[$#] ifFalse:[char]]! ! !DosFileDirectory methodsFor: 'as yet unclassified' stamp: 'bf 3/21/2000 17:06'! setPathName: pathString "Ensure pathString is absolute - relative directories aren't supported on all platforms." (pathString isEmpty or: [pathString first = $\ or: [pathString size >= 2 and: [pathString second = $: and: [pathString first isLetter]]]]) ifTrue: [^ super setPathName: pathString]. self error: 'Fully qualified path expected'! ! !DosFileDirectory methodsFor: 'path access' stamp: 'je 11/8/2000 20:02'! driveName "return a possible drive letter and colon at the start of a Path name, empty string otherwise" | firstTwoChars | ( pathName size >= 2 ) ifTrue: [ firstTwoChars _ (pathName copyFrom: 1 to: 2). (self class isDrive: firstTwoChars) ifTrue: [^firstTwoChars] ]. ^''! ! !DosFileDirectory methodsFor: 'path access' stamp: 'je 11/8/2000 19:57'! fullPathFor: path "Return the fully-qualified path name for the given file." path isEmpty ifTrue:[^pathName]. (path at: 1) = $\ ifTrue:[ (path size >= 2 and:[(path at: 2) = $\]) ifTrue:[^path]. "e.g., \\pipe\" ^self driveName , path "e.g., \windows\"]. (path size >= 2 and:[(path at: 2) = $: and:[path first isLetter]]) ifTrue:[^path]. "e.g., c:" ^pathName, self slash, path! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DosFileDirectory class instanceVariableNames: ''! !DosFileDirectory class methodsFor: 'platform specific' stamp: 'ar 5/1/1999 01:48'! isCaseSensitive "Return true if file names are treated case sensitive" ^false! ! !DosFileDirectory class methodsFor: 'platform specific' stamp: 'sma 3/24/2000 11:15'! isDrive: fullName ^ (fullName size = 2 and: [fullName first isLetter and: [fullName last = $:]]) or: [(fullName beginsWith: '\\') and: [(fullName occurrencesOf: $\) <= 3]]! ! !DosFileDirectory class methodsFor: 'platform specific' stamp: 'jm 5/8/1998 20:45'! maxFileNameLength ^ 255 ! ! !DosFileDirectory class methodsFor: 'platform specific' stamp: 'jm 12/4/97 22:57'! pathNameDelimiter ^ $\ ! ! !DosFileDirectory class methodsFor: 'platform specific' stamp: 'sma 3/24/2000 11:15'! splitName: fullName to: pathAndNameBlock (self isDrive: fullName) ifTrue: [^ pathAndNameBlock value: fullName value: '']. ^ super splitName: fullName to: pathAndNameBlock! ! RectangleMorph subclass: #DoubleClickExample instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !DoubleClickExample commentStamp: '' prior: 0! Illustrates the double-click capabilities of Morphic. If you have a kind of morph you wish to have respond specially to a double-click, it should: (1) Respond "true" to #handlesMouseDown: (2) In its mouseDown: method, send #waitForClicksOrDrag:event: to the hand. (3) Reimplement #click: to react to single-clicked mouse-down. (4) Reimplement #doubleClick: to make the appropriate response to a double-click. (5) Reimplement #drag: to react to non-clicks. This message is sent continuously until the button is released. You can check the event argument to react differently on the first, intermediate, and last calls.! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'sw 9/28/1999 16:51'! balloonText ^ 'Double-click on me to change my color; single-click on me to change border color; hold mouse down within me to grow (if I''m red) or shrink (if I''m blue).'! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'ar 10/3/2000 17:05'! click: evt self showBalloon: 'click' hand: evt hand. self borderColor: (self borderColor = Color black ifTrue: [Color yellow] ifFalse: [Color black]) ! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'ar 10/3/2000 17:05'! doubleClick: evt self showBalloon: 'doubleClick' hand: evt hand. self color: ((color = Color blue) ifTrue: [Color red] ifFalse: [Color blue]) ! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'sw 9/14/1999 16:05'! handlesMouseDown: evt ^ true! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'sw 9/23/1999 17:55'! initialize super initialize. self color: Color red! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'bf 9/28/1999 17:20'! mouseDown: evt "Do nothing upon mouse-down except inform the hand to watch for a double-click; wait until an ensuing click:, doubleClick:, or drag: message gets dispatched" evt hand waitForClicksOrDrag: self event: evt! ! !DoubleClickExample methodsFor: 'as yet unclassified' stamp: 'ar 10/3/2000 17:05'! startDrag: evt "We'll get a mouseDown first, some mouseMoves, and a mouseUp event last" | oldCenter | evt isMouseDown ifTrue: [self showBalloon: 'drag (mouse down)' hand: evt hand. self world displayWorld. (Delay forMilliseconds: 750) wait]. evt isMouseUp ifTrue: [self showBalloon: 'drag (mouse up)' hand: evt hand]. (evt isMouseUp or: [evt isMouseDown]) ifFalse: [self showBalloon: 'drag (mouse still down)' hand: evt hand]. (self containsPoint: evt cursorPoint) ifFalse: [^ self]. oldCenter _ self center. color = Color red ifTrue: [self extent: self extent + (1@1)] ifFalse: [self extent: ((self extent - (1@1)) max: (16@16))]. self center: oldCenter! ! Morph subclass: #DownloadingImageMorph instanceVariableNames: 'url altText defaultExtent image downloadQueue imageMapName formatter ' classVariableNames: '' poolDictionaries: '' category: 'Network-HTML Formatter'! !DownloadingImageMorph commentStamp: '' prior: 0! a placeholder for an image that is downloading! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'bolot 11/2/1999 14:30'! altText: aString "set the text to be displayed while downloading" altText _ aString. aString ifNotNil: [self setBalloonText: aString]. self setContents! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'ls 9/9/1998 06:59'! defaultExtent: aPoint "set the size to use when the image hasn't yet downloaded" defaultExtent _ aPoint! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 23:08'! downloadStateIn: aScamper "download the image" | doc | doc _ url retrieveContents. downloadQueue nextPut: doc. ! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'ls 9/15/1998 19:21'! initialize super initialize. altText _ '[image]'. self color: Color transparent. downloadQueue _ SharedQueue new.! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'bolot 2/27/2000 23:54'! setContents "set up our morphic contents" | imageMorph imageMap | self removeAllMorphs. image ifNil: [^self setNoImageContents]. imageMorph _ ImageMorph new. (imageMapName notNil and: [formatter notNil and: [(imageMap _ formatter imageMapNamed: imageMapName) notNil]]) ifTrue: [imageMap buildImageMapForImage: imageMorph andBrowser: formatter browser]. imageMorph image: image. imageMorph position: self position. self addMorph: imageMorph. imageMorph extent ~= self extent ifTrue: [self extent: imageMorph extent].! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'bolot 2/28/2000 00:14'! setNoImageContents "set up our morphic contents in case image download/decoding failed" | stringMorph outlineMorph extent | altText isEmptyOrNil ifTrue: [ self extent: 0@0. "don't display anything..." ^self ]. stringMorph _ StringMorph new. stringMorph contents: altText. stringMorph position: self position+(2@2). self addMorph: stringMorph. outlineMorph _ RectangleMorph new. outlineMorph borderWidth: 1. outlineMorph color: Color transparent. outlineMorph position: self position. "figure out how big to make the box" extent _ defaultExtent ifNil: [ 0 @ 0 ]. stringMorph width + 4 > extent x ifTrue: [ extent _ (stringMorph width + 4) @ extent y ]. stringMorph height + 4 > extent y ifTrue: [ extent _ extent x @ (stringMorph height + 4) ]. outlineMorph extent: extent. self addMorph: outlineMorph. self extent: outlineMorph extent ! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'bolot 11/30/1999 23:22'! step | doc | downloadQueue size > 0 ifTrue: [ doc _ downloadQueue next. doc mainType = 'image' ifTrue: [ [image _ ImageReadWriter formFromStream: doc contentStream binary] ifError: [:err :rcvr | "ignore" image _ nil]. self setContents ] ].! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'ls 9/15/1998 19:19'! stepTime "this check doesn't need to be frequent" ^500! ! !DownloadingImageMorph methodsFor: 'as yet unclassified' stamp: 'ls 9/5/1998 17:48'! url: aUrl "set the url to download" url _ aUrl asUrl.! ! !DownloadingImageMorph methodsFor: 'accessing' stamp: 'bolot 2/27/2000 23:38'! formatter ^formatter! ! !DownloadingImageMorph methodsFor: 'accessing' stamp: 'bolot 2/27/2000 23:38'! formatter: aFormatter formatter _ aFormatter! ! !DownloadingImageMorph methodsFor: 'accessing' stamp: 'bolot 2/27/2000 23:34'! imageMapName ^imageMapName! ! !DownloadingImageMorph methodsFor: 'accessing' stamp: 'bolot 2/27/2000 23:35'! imageMapName: aString imageMapName _ aString! ! Morph subclass: #DrawErrorMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !DrawErrorMorph commentStamp: '' prior: 0! This morph simply invokes errors during drawing and stepping.! !DrawErrorMorph methodsFor: 'drawing' stamp: 'ar 4/2/1999 12:13'! drawOn: aCanvas aCanvas error:'DrawErrorMorph drawOn: invoked'! ! !DrawErrorMorph methodsFor: 'printing' stamp: 'ar 4/2/1999 12:15'! printOn: aStream "Indirectly invokes an error during stepping in an Inspector" aStream error:'DrawErrorMorph>>printOn: invoked'! ! PopUpChoiceMorph subclass: #DropDownChoiceMorph instanceVariableNames: 'items border ' classVariableNames: 'SubMenuMarker ' poolDictionaries: '' category: 'Morphic-Widgets'! !DropDownChoiceMorph methodsFor: 'copying' stamp: 'bolot 11/2/1999 12:17'! veryDeepInner: deepCopier super veryDeepInner: deepCopier. items _ items veryDeepCopyWith: deepCopier. border _ border veryDeepCopyWith: deepCopier! ! !DropDownChoiceMorph methodsFor: 'drawing' stamp: 'ar 2/12/2000 18:05'! drawOn: aCanvas aCanvas text: contents bounds: (bounds insetBy: 2) font: self fontToUse color: color. border ifNotNil: [aCanvas frameAndFillRectangle: bounds fillColor: Color transparent borderWidth: 1 borderColor: Color black]. aCanvas paintImage: SubMenuMarker at: (self right - 8 @ ((self top + self bottom - SubMenuMarker height) // 2))! ! !DropDownChoiceMorph methodsFor: 'drawing' stamp: 'ar 5/18/2000 18:34'! maxExtent: listOfStrings | scanner h w maxW | maxW _ 0. listOfStrings do: [:str | scanner _ DisplayScanner quickPrintOn: Display box: Display boundingBox font: self fontToUse. w _ (scanner stringWidth: str). h _ scanner lineHeight. maxW _ maxW max: w]. self extent: (maxW + 4 + h) @ (h + 4). self changed! ! !DropDownChoiceMorph methodsFor: 'accessing' stamp: 'bolot 11/2/1999 12:20'! border ^border! ! !DropDownChoiceMorph methodsFor: 'accessing' stamp: 'bolot 11/2/1999 12:20'! border: newBorder border _ newBorder! ! !DropDownChoiceMorph methodsFor: 'accessing' stamp: 'bolot 11/2/1999 12:20'! items (target notNil and: [getItemsSelector notNil]) ifTrue: [items _ target perform: getItemsSelector withArguments: getItemsArgs]. items ifNil: [items _ #()]. ^items! ! !DropDownChoiceMorph methodsFor: 'accessing' stamp: 'bolot 11/2/1999 12:20'! items: someItems items _ someItems! ! !DropDownChoiceMorph methodsFor: 'list access' stamp: 'bolot 11/2/1999 12:21'! getCurrentSelectionIndex ^self items indexOf: contents! ! !DropDownChoiceMorph methodsFor: 'list access' stamp: 'bolot 11/2/1999 12:21'! selection: val self contentsClipped: val! ! !DropDownChoiceMorph methodsFor: 'event handling' stamp: 'bolot 11/2/1999 12:22'! mouseDown: evt | menu selectedItem | self items isEmpty ifTrue: [^ self]. menu _ CustomMenu new. self items do: [:item | menu add: item action: item]. selectedItem _ menu startUp. selectedItem ifNil: [^ self]. self contentsClipped: selectedItem. "Client can override this if necess" actionSelector ifNotNil: [ target perform: actionSelector withArguments: (arguments copyWith: selectedItem)]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DropDownChoiceMorph class instanceVariableNames: ''! !DropDownChoiceMorph class methodsFor: 'class initialization' stamp: 'bolot 11/2/1999 12:19'! initialize "DropDownChoiceMorph initialize" | f | f _ Form extent: 5@9 fromArray: #(2147483648 3221225472 3758096384 4026531840 4160749568 4026531840 3758096384 3221225472 2147483648) offset: 0@0. SubMenuMarker _ ColorForm mappingWhiteToTransparentFrom: f. ! ! MorphicEvent subclass: #DropEvent instanceVariableNames: 'position contents wasHandled ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Events'! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 18:33'! contents ^contents! ! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 19:21'! cursorPoint "For compatibility with mouse events" ^position! ! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 18:33'! position ^position! ! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 18:33'! type ^#dropEvent! ! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 18:44'! wasHandled ^wasHandled! ! !DropEvent methodsFor: 'accessing' stamp: 'ar 9/13/2000 18:44'! wasHandled: aBool wasHandled _ aBool.! ! !DropEvent methodsFor: 'testing' stamp: 'ar 9/13/2000 18:33'! isDropEvent ^true! ! !DropEvent methodsFor: 'dispatching' stamp: 'ar 1/10/2001 21:24'! sentTo: anObject "Dispatch the receiver into anObject" self type == #dropEvent ifTrue:[^anObject handleDropMorph: self].! ! !DropEvent methodsFor: 'printing' stamp: 'ar 9/14/2000 18:15'! printOn: aStream aStream nextPut: $[. aStream nextPutAll: self position printString; space. aStream nextPutAll: self type. aStream nextPut: $].! ! !DropEvent methodsFor: 'private' stamp: 'ar 9/13/2000 19:23'! setPosition: pos contents: aMorph hand: aHand position _ pos. contents _ aMorph. source _ aHand. wasHandled _ false.! ! !DropEvent methodsFor: 'transforming' stamp: 'ar 10/7/2000 18:28'! transformBy: aMorphicTransform "Transform the receiver into a local coordinate system." position _ aMorphicTransform globalPointToLocal: position.! ! !DropEvent methodsFor: 'transforming' stamp: 'ar 10/7/2000 18:28'! transformedBy: aMorphicTransform "Return the receiver transformed by the given transform into a local coordinate system." ^self shallowCopy transformBy: aMorphicTransform! ! !DropEvent methodsFor: 'initialize' stamp: 'ar 10/10/2000 01:19'! copyHandlerState: anEvent "Copy the handler state from anEvent. Used for quickly transferring handler information between transformed events." wasHandled _ anEvent wasHandled.! ! !DropEvent methodsFor: 'initialize' stamp: 'ar 10/10/2000 01:18'! resetHandlerFields "Reset anything that is used to cross-communicate between two eventual handlers during event dispatch" wasHandled _ false.! ! DropEvent subclass: #DropFilesEvent instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Events'! !DropFilesEvent methodsFor: 'accessing' stamp: 'ar 1/10/2001 21:35'! type ^#dropFilesEvent! ! !DropFilesEvent methodsFor: 'dispatching' stamp: 'ar 1/10/2001 21:35'! sentTo: anObject "Dispatch the receiver into anObject" self type == #dropFilesEvent ifTrue:[^anObject handleDropFiles: self].! ! InterpreterPlugin subclass: #DropPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !DropPlugin methodsFor: 'initialize' stamp: 'ar 1/10/2001 19:57'! initialiseModule self export: true. ^self cCode: 'dropInit()' inSmalltalk:[true]! ! !DropPlugin methodsFor: 'initialize' stamp: 'ar 1/10/2001 19:57'! shutdownModule self export: true. ^self cCode: 'dropShutdown()' inSmalltalk:[true]! ! !DropPlugin methodsFor: 'primitives' stamp: 'ar 1/10/2001 20:46'! primitiveDropRequestFileHandle "Note: File handle creation needs to be handled by specific support code explicitly bypassing the plugin file sand box." | dropIndex handleOop | self export: true. self inline: false. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. dropIndex _ interpreterProxy stackIntegerValue: 0. handleOop _ self dropRequestFileHandle: dropIndex. "dropRequestFileHandle needs to return the actual oop returned" interpreterProxy failed ifFalse:[ interpreterProxy pop: 2. interpreterProxy push: handleOop. ].! ! !DropPlugin methodsFor: 'primitives' stamp: 'ar 1/10/2001 20:46'! primitiveDropRequestFileName "Note: File handle creation needs to be handled by specific support code explicitly bypassing the plugin file sand box." | dropIndex dropName nameLength nameOop namePtr | self export: true. self inline: false. self var: #dropName type: 'char *'. self var: #namePtr type: 'char *'. interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. dropIndex _ interpreterProxy stackIntegerValue: 0. dropName _ self dropRequestFileName: dropIndex. "dropRequestFileName returns name or NULL on error" dropName == nil ifTrue:[^interpreterProxy primitiveFail]. nameLength _ self strlen: dropName. nameOop _ interpreterProxy instantiateClass: interpreterProxy classString indexableSize: nameLength. namePtr _ interpreterProxy firstIndexableField: nameOop. 0 to: nameLength-1 do:[:i| namePtr at: i put: (dropName at: i)]. interpreterProxy pop: 2. interpreterProxy push: nameOop. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DropPlugin class instanceVariableNames: ''! !DropPlugin class methodsFor: 'translation' stamp: 'ar 1/10/2001 20:02'! headerFile ^'/* drop support primitives */ /* module initialization/shutdown */ int dropInit(void); int dropShutdown(void); char* dropRequestFileName(int dropIndex); /* return name of file or NULL if error */ /* note: dropRequestFileHandle needs to bypass plugin security checks when implemented */ int dropRequestFileHandle(int dropIndex); /* return READ-ONLY file handle OOP or nilObject if error */ '! ! Model subclass: #DualChangeSorter instanceVariableNames: 'leftCngSorter rightCngSorter ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !DualChangeSorter commentStamp: '' prior: 0! This class presents a view of a two change sets at once, and supports copying changes between change sets. ! !DualChangeSorter methodsFor: 'as yet unclassified'! isLeftSide: theOne "Which side am I?" ^ theOne == leftCngSorter! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'tk 4/30/1998 13:44'! labelString "The window label" ^ leftCngSorter labelString! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'tk 5/8/1998 16:30'! modelWakeUp "A window with me as model is being entered. Make sure I am up-to-date with the changeSets." "Dumb way" leftCngSorter canDiscardEdits ifTrue: [leftCngSorter update] "does both" ifFalse: [rightCngSorter update]. ! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'sw 3/6/1999 09:34'! morphicWindow | window | leftCngSorter _ ChangeSorter new myChangeSet: Smalltalk changes. leftCngSorter parent: self. rightCngSorter _ ChangeSorter new myChangeSet: ChangeSorter secondaryChangeSet. rightCngSorter parent: self. window _ (SystemWindow labelled: leftCngSorter label) model: self. "topView minimumSize: 300 @ 200." leftCngSorter openAsMorphIn: window rect: (0@0 extent: 0.5@1). rightCngSorter openAsMorphIn: window rect: (0.5@0 extent: 0.5@1). ^ window ! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'di 5/20/1998 21:44'! okToChange ^ leftCngSorter okToChange & rightCngSorter okToChange! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'sma 4/30/2000 09:29'! open | topView | Smalltalk isMorphic | Sensor leftShiftDown ifTrue: [^ self openAsMorph]. leftCngSorter _ ChangeSorter new myChangeSet: Smalltalk changes. leftCngSorter parent: self. rightCngSorter _ ChangeSorter new myChangeSet: ChangeSorter secondaryChangeSet. rightCngSorter parent: self. topView _ (StandardSystemView new) model: self; borderWidth: 1. topView label: leftCngSorter label. topView minimumSize: 300 @ 200. leftCngSorter openView: topView offsetBy: 0@0. rightCngSorter openView: topView offsetBy: 360@0. topView controller open. ! ! !DualChangeSorter methodsFor: 'as yet unclassified' stamp: 'sw 3/6/1999 09:34'! openAsMorph ^ self morphicWindow openInWorld ! ! !DualChangeSorter methodsFor: 'as yet unclassified'! other: theOne "Return the other side's ChangeSorter" ^ theOne == leftCngSorter ifTrue: [rightCngSorter] ifFalse: [leftCngSorter]! ! !DualChangeSorter methodsFor: 'as yet unclassified'! release leftCngSorter release. rightCngSorter release.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DualChangeSorter class instanceVariableNames: ''! !DualChangeSorter class methodsFor: 'opening' stamp: 'sw 3/24/1999 17:50'! open "Open a new instance of the receiver's class" self new open! ! MenuMorph subclass: #DumberMenuMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Explorer'! !DumberMenuMorph commentStamp: '' prior: 0! Contributed by Bob Arning as part of the ObjectExplorer package. ! !DumberMenuMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/21/1999 15:40'! setInvokingView: invokingView "I'd rather not, if that's OK"! ! Stream subclass: #DummyStream instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Object Storage'! !DummyStream commentStamp: '' prior: 0! The purpose of this class is to absorb all steam messages and do nothing. This is so ReferenceStream can pretend to write on it while traversing all objects it would normally write. We need to know what those object are. 8/17/96 tk ! !DummyStream methodsFor: 'as yet unclassified'! binary "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified' stamp: 'tk 10/31/97 11:43'! close "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified'! nextInt32Put: arg "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified'! nextNumber: cnt put: num "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified' stamp: 'tk 6/8/1998 21:07'! nextPut: aByte "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified' stamp: 'tk 6/8/1998 21:06'! nextPutAll: aByteArray "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified'! nextStringPut: aString "do nothing"! ! !DummyStream methodsFor: 'as yet unclassified' stamp: 'tk 3/6/2000 11:10'! originalContents ^ ''! ! !DummyStream methodsFor: 'as yet unclassified'! position "Return any random number. Here is where the real lying begins. We are a DummyStream afterall. 8/17/96 tk" ^ 47 ! ! !DummyStream methodsFor: 'as yet unclassified' stamp: 'tk 7/12/1998 12:51'! position: anOffset "Pretend to position wherever the caller says!!" ! ! !DummyStream methodsFor: 'as yet unclassified' stamp: '6/10/97 17:14 tk'! skip: aNumber "Do nothing."! ! !DummyStream methodsFor: 'as yet unclassified'! subclassResponsibility "Do nothing. Most messages to class Stream are defined as subclassResponsibility. Just accept them. 8/17/96 tk" "No error. Just go on."! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DummyStream class instanceVariableNames: ''! !DummyStream class methodsFor: 'as yet unclassified' stamp: 'jm 12/3/97 20:25'! on: aFile "Return a new DummyStream instance, ignoring the argument." ^ self basicNew ! ! UtteranceVisitor subclass: #DurationsVisitor instanceVariableNames: 'inherents lowers speed ' classVariableNames: '' poolDictionaries: '' category: 'Speech-TTS'! !DurationsVisitor commentStamp: '' prior: 0! This is an implementation of the Klatt rule system as described in chapter 9 of "From text to speech: The MITalk system", Allen, Hunnicutt and Klatt.! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:38'! defaultDurationFor: aPhoneme "Some hardcoded durations for phonemes." aPhoneme isVoiced ifTrue: [^ 0.0565]. aPhoneme isUnvoiced ifTrue: [^ 0.0751]. aPhoneme isConsonant ifTrue: [^ 0.06508]. aPhoneme isDiphthong ifTrue: [^ 0.1362]. ^ 0.0741! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! inherentDurationAt: aPhoneme ^ self inherents at: aPhoneme ifAbsent: [Transcript show: ' default duration for ', aPhoneme name. self defaultDurationFor: aPhoneme]! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! inherents ^ inherents! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! inherents: aDictionary inherents _ aDictionary! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! lowerDurationAt: aPhoneme ^ self lowers at: aPhoneme ifAbsent: [self inherentDurationAt: aPhoneme]! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! lowers ^ lowers! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 16:39'! lowers: aDictionary lowers _ aDictionary! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 18:28'! speed ^ speed! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 18:28'! speed: aNumber speed _ aNumber! ! !DurationsVisitor methodsFor: 'visiting' stamp: 'len 12/14/1999 04:22'! clause: aClause | min | super clause: aClause. self rule2. clause wordsDo: [ :eachWord | eachWord events do: [ :each | min _ self lowerDurationAt: each phoneme. eachWord isAccented ifFalse: [min _ min / 2.0]. each duration: each duration + min / 1.4 / self speed]]. clause syllablesDo: [ :each | each events recomputeTimes]! ! !DurationsVisitor methodsFor: 'visiting' stamp: 'len 12/8/1999 17:04'! phrase: aPhrase super phrase: aPhrase. self rule3; rule3b! ! !DurationsVisitor methodsFor: 'visiting' stamp: 'len 12/8/1999 18:27'! speaker: aSpeaker self speed: aSpeaker speed! ! !DurationsVisitor methodsFor: 'visiting' stamp: 'len 12/8/1999 17:05'! syllable: aSyllable super syllable: aSyllable. syllable events do: [ :each | each duration: (self inherentDurationAt: each phoneme) - (self lowerDurationAt: each phoneme)]. self rule4; rule5; rule9a; rule9b; rule10! ! !DurationsVisitor methodsFor: 'visiting' stamp: 'len 12/8/1999 17:06'! word: aWord super word: aWord. self rule6; rule7; rule8! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:55'! rule10 "Rule 10: Shortening in clusters." | current next previous stream | phrase lastSyllable == syllable ifTrue: [^ self]. stream _ ReadStream on: syllable events. current _ nil. next _ stream next. [stream atEnd] whileFalse: [previous _ current. current _ next. next _ stream next. current phoneme isVowel ifTrue: [next phoneme isVowel ifTrue: [current stretch: 1.2] ifFalse: [(previous notNil and: [previous phoneme isVowel]) ifTrue: [current stretch: 0.7]]] ifFalse: [next phoneme isConsonant ifTrue: [(previous notNil and: [previous phoneme isConsonant]) ifTrue: [current stretch: 0.5] ifFalse: [current stretch: 0.7]] ifFalse: [(previous notNil and: [previous phoneme isConsonant]) ifTrue: [current stretch: 0.5]]]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:53'! rule2 "Rule 2: Clause Final Lengthening." clause lastSyllable events stretch: 1.4! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/13/1999 02:36'! rule3 "Rule 3: Non-phrase-final shortening. Syllabic segments are shortened by 60 if not in a phrase-final syllable." phrase syllablesDo: [ :each | phrase lastSyllable == each ifFalse: [each events do: [ :event | event phoneme isSyllabic ifTrue: [event stretch: 0.6]]]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:53'! rule3b "A phrase-final postvocalic liquid or nasal is lengthened by 140" phrase lastSyllable events do: [ :each | (each phoneme isNasal or: [each phoneme isLiquid]) ifTrue: [each stretch: 1.4]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:56'! rule4 "Rule 4: Non-word-final shortening. Syllabic segments are shortened by 85 if not in a word-final syllable." word lastSyllable == syllable ifTrue: [^ self]. syllable events do: [ :each | each phoneme isSyllabic ifTrue: [each stretch: 0.85]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:56'! rule5 "Rule 5: Polysyllabic Shortening. Syllabic segments in a polysyllabic word are shortened by 80." word isPolysyllabic ifFalse: [^ self]. syllable events do: [ :each | each phoneme isSyllabic ifTrue: [each stretch: 0.8]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:59'! rule6 "Rule 6: Non-initial-consonant shortening." | nonInitial | nonInitial _ false. word events do: [ :each | (nonInitial and: [each phoneme isConsonant]) ifTrue: [each stretch: 0.85]. nonInitial _ true]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:59'! rule7 "Rule 7: Unstressed shortening." word syllables do: [ :each | each stress > 0 ifFalse: [each events do: [ :event | event phoneme isSyllabic ifTrue: [event stretch: 0.5]]. each events first phoneme isSyllabic ifTrue: [each events first stretch: 0.7 / 0.5]. (each events last phoneme isSyllabic and: [each events size > 1]) ifTrue: [each events last stretch: 0.7 / 0.5]]] ! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/13/1999 02:33'! rule8 "Rule 8: Lengthening for emphasis." word isAccented ifTrue: [word events do: [ :each | each phoneme isVowel ifTrue: [each stretch: 1.4]]]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:57'! rule9a "Rule 9a: Postvocalic context of vowels." | events current next nextnext | phrase lastSyllable == syllable ifTrue: [^ self]. events _ syllable events. 1 to: events size do: [ :i | current _ events at: i. next _ i + 1 <= events size ifTrue: [(events at: i + 1) phoneme]. nextnext _ i + 2 <= events size ifTrue: [(events at: i + 2) phoneme]. current stretch: (self rule9a: current phoneme next: next nextnext: nextnext)]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 17:00'! rule9a: current next: next nextnext: nextnext "Rule 9a: Postvocalic context of vowels." current isVowel ifTrue: [next isNil ifTrue: [^ 1.2]. nextnext isNil ifTrue: [^ self subRule9a: next]. (next isSonorant and: [nextnext isObstruent]) ifTrue: [^ self subRule9a: nextnext]] ifFalse: [current isSonorant ifTrue: [next isNil ifTrue: [^ 1.2]. next isObstruent ifTrue: [^ self subRule9a: next]]]. ^ 1.0! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 16:58'! rule9b "Rule 9b: Postvocalic context of vowels." | events current next nextnext | phrase lastSyllable == syllable ifFalse: [^ self]. events _ syllable events. 1 to: events size do: [ :i | current _ events at: i. next _ i + 1 <= events size ifTrue: [(events at: i + 1) phoneme]. nextnext _ i + 2 <= events size ifTrue: [(events at: i + 2) phoneme]. current stretch: 0.3 * (self rule9a: current phoneme next: next nextnext: nextnext) + 0.7]! ! !DurationsVisitor methodsFor: 'rules' stamp: 'len 12/8/1999 17:01'! subRule9a: aPhoneme "Sub-rule 9a, independent of segment position." aPhoneme isVoiced ifFalse: [^ aPhoneme isStop ifTrue: [0.7] ifFalse: [1.0]]. aPhoneme isFricative ifTrue: [^ 1.6]. aPhoneme isStop ifTrue: [^ 1.2]. aPhoneme isNasal ifTrue: [^ 0.85]. ^ 1.0! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DurationsVisitor class instanceVariableNames: ''! !DurationsVisitor class methodsFor: 'instance creation' stamp: 'len 12/8/1999 16:40'! inherents: aDictionary lowers: anotherDictionary ^ self new inherents: aDictionary; lowers: anotherDictionary! ! !DurationsVisitor class methodsFor: 'examples' stamp: 'len 12/8/1999 16:40'! default | phonemes inherents lowers | phonemes _ PhonemeSet arpabet. inherents _ Dictionary new. lowers _ Dictionary new. #( ('ae' 230.0 80.0) ('aa' 240.0 100.0) ('ax' 120.0 60.0) ('er' 180.0 80.0) ('ay' 250.0 150.0) ('aw' 240.0 100.0) ('b' 85.0 60.0) ('ch' 70.0 50.0) ('d' 75.0 50.0) ('dh' 50.0 30.0) ('eh' 150.0 70.0) ('ea' 270.0 130.0) ('ey' 180.0 100.0) ('f' 100.0 80.0) ('g' 80.0 60.0) ('hh' 80.0 20.0) ('ih' 135.0 40.0) ('ia' 230.0 100.0) ('iy' 155.0 55.0) ('jh' 70.0 50.0) ('k' 80.0 60.0) ('l' 80.0 40.0) ('m' 70.0 60.0) ('n' 60.0 50.0) ('ng' 95.0 60.0) " ('oh' 240.0 130.0)" ('oy' 280.0 150.0) ('ao' 240.0 130.0) ('ow' 220.0 80.0) ('p' 90.0 50.0) ('r' 80.0 30.0) ('s' 105.0 60.0) ('sh' 105.0 80.0) ('t' 75.0 50.0) ('th' 90.0 60.0) ('uh' 210.0 70.0) ('ua' 230.0 110.0) ('ah' 160.0 60.0) ('uw' 230.0 150.0) ('v' 60.0 40.0) ('w' 80.0 60.0) ('y' 80.0 40.0) ('z' 75.0 40.0) ('zh' 70.0 40.0) ('sil' 100.0 100.0)) do: [ :each | inherents at: (phonemes at: each first) put: each second / 1000.0. lowers at: (phonemes at: each first) put: each last / 1000.0]. ^ self inherents: inherents lowers: lowers! ! PostscriptCanvas subclass: #EPSCanvas instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Postscript Canvases'! !EPSCanvas commentStamp: '' prior: 0! I am a canvas for generating Encapsulates PostScript (EPS) files from single morphs, for example for screen-dumps. I make sure that the bounding box of the EPS surrounds exactly the morph, and am not capable of generating multiple pages. I do not generate an on-screen Preview for the EPS file, though that should be possible. ! !EPSCanvas methodsFor: 'as yet unclassified' stamp: 'mpw 9/12/1999 07:29'! fullDraw:aMorph super fullDraw:aMorph. morphLevel = 0 ifTrue: [ target showpage. ].! ! !EPSCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 16:27'! setupGStateForMorph: aMorph target comment: 'setupGState in EPSCanvas'. morphLevel == 1 ifTrue: [ EPSCanvas bobsPostScriptHacks ifTrue: [ "needed to print RectangleMorph" self writeSetupForRect: aMorph bounds. target translate: aMorph bounds origin negated. ] ifFalse: [ target translate: aMorph bounds origin negated. self writeSetupForRect: aMorph bounds. ]. ]. ! ! !EPSCanvas methodsFor: 'as yet unclassified' stamp: 'di 8/9/2000 21:43'! writePSIdentifierRotated: rotateFlag target print:'%!!PS-Adobe-2.0 EPSF-1.2'; cr. rotateFlag ifTrue: [target print: '%%BoundingBox: '; write: psBounds rounded; cr; print: '90 rotate'; cr; print: '0 -'; write: psBounds height rounded; print: ' translate'; cr] ifFalse: [target print: '%%BoundingBox: '; write: psBounds rounded; cr]. target print:'%%Title: '; write:self topLevelMorph externalName; cr. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EPSCanvas class instanceVariableNames: ''! !EPSCanvas class methodsFor: 'as yet unclassified' stamp: 'mpw 9/18/1999 11:04'! baseOffset ^10@10. ! ! !EPSCanvas class methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 16:56'! bobsPostScriptHacks ^true! ! EToyChatOrBadgeMorph subclass: #EToyChatMorph instanceVariableNames: 'listener receivingPane myForm recipientForm acceptOnCR sendingPane ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyChatMorph commentStamp: '' prior: 0! EToyChatMorph new open setIPAddress: '1.2.3.4' " EToyChatMorph represents a chat session with another person. Type your message in the top text pane and press cmd-S. "! ]style[(46 122)f2cblue;,f1! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/5/2000 14:14'! acceptTo: someText forMorph: aMorph | betterText | betterText _ self improveText: someText forMorph: aMorph. self transmitStreamedObject: (betterText eToyStreamedRepresentationNotifying: self) to: self ipAddress. aMorph setText: '' asText. self appendMessage: self startOfMessageFromMe, ' - ', betterText, String cr. ^true! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 22:34'! appendMessage: aText receivingPane appendTextEtoy: aText.! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/16/2000 12:26'! chatFrom: ipAddress name: senderName text: text | initialText attrib | recipientForm ifNil: [ initialText _ senderName asText allBold. ] ifNotNil: [ attrib _ TextAnchor new anchoredMorph: recipientForm "asMorph". initialText _ '*' asText. initialText addAttribute: attrib from: 1 to: 1. ]. self appendMessage: initialText,' - ',text,String cr. EToyCommunicatorMorph playArrivalSound. ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 10:37'! getChoice: aSymbol aSymbol == #acceptOnCR ifTrue: [^acceptOnCR ifNil: [true]]. ^false. ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/5/2000 14:14'! improveText: someText forMorph: aMorph | betterText conversions newAttr fontForAll | fontForAll _ aMorph eToyGetMainFont. betterText _ someText veryDeepCopy. conversions _ OrderedCollection new. betterText runs withStartStopAndValueDo: [:start :stop :attributes | attributes do: [:att | (att isMemberOf: TextFontChange) ifTrue: [ conversions add: {att. start. stop} ] ] ]. conversions do: [ :old | betterText removeAttribute: old first from: old second to: old third. newAttr _ TextFontReference toFont: (fontForAll fontAt: old first fontNumber). newAttr fontNumber: old first fontNumber. betterText addAttribute: newAttr from: old second to: old third. ]. ^betterText! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:30'! initialize super initialize. acceptOnCR _ true. self listDirection: #topToBottom. color _ Color paleYellow. self layoutInset: 0. borderColor _ self standardBorderColor. borderWidth _ 8. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self rubberBandCells: false. self minWidth: 200. self minHeight: 200. bounds _ 400@100 extent: 200@150. self rebuild. ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 16:14'! insetTheScrollbars self allMorphsDo: [ :each | (each isKindOf: PluggableTextMorph) ifTrue: [each retractable: false] ].! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 06:09'! ipAddress ^(fields at: #ipAddress) contents! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 11:30'! open ^self openIn: self currentWorld! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/3/2000 07:40'! openIn: aWorld "open an a chat window" aWorld ifNil: [^self]. self position: 400@100; extent: 200@150; openInWorld: aWorld.! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:17'! rebuild | r1 r2 | r1 _ self addARow: { self simpleToggleButtonFor: self attribute: #acceptOnCR help: 'Send with Return?'. self inAColumn: {StringMorph new contents: 'Your message to:'; lock}. self textEntryFieldNamed: #ipAddress with: '' help: 'IP address for chat partner'. }. recipientForm ifNotNil: [ r1 addMorphBack: recipientForm asMorph lock ]. sendingPane _ PluggableTextMorph on: self text: nil accept: #acceptTo:forMorph:. sendingPane hResizing: #spaceFill; vResizing: #spaceFill. self addMorphBack: sendingPane. r2 _ self addARow: {self inAColumn: {StringMorph new contents: 'Replies'; lock}}. receivingPane _ PluggableTextMorph on: self text: nil accept: nil. receivingPane hResizing: #spaceFill; vResizing: #spaceFill. self addMorphBack: receivingPane. receivingPane spaceFillWeight: 3. {r1. r2} do: [ :each | each vResizing: #shrinkWrap; minHeight: 18; color: Color veryLightGray. ]. sendingPane acceptOnCR: (acceptOnCR ifNil: [acceptOnCR _ true])! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 08:50'! recipientForm: aForm recipientForm _ aForm. recipientForm ifNotNil: [recipientForm _ recipientForm scaledToSize: 20@20].! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 16:43'! reportError: aString receivingPane appendTextEtoy: (aString asText addAttribute: TextColor red), String cr.! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 05:39'! setIPAddress: aString (fields at: #ipAddress) contents: aString! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 08:04'! standardBorderColor ^Color darkGray! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 14:50'! startOfMessageFromMe myForm ifNil: [ myForm _ EToySenderMorph pictureForIPAddress: NetNameResolver localAddressString. myForm ifNotNil: [ myForm _ myForm scaledToSize: 20@20 ]. ]. myForm ifNil: [ ^(Preferences defaultAuthorName asText allBold addAttribute: TextColor blue) ]. ^'*' asText addAttribute: (TextAnchor new anchoredMorph: myForm); yourself ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 10:41'! toggleChoice: aSymbol aSymbol == #acceptOnCR ifTrue: [ acceptOnCR _ (acceptOnCR ifNil: [true]) not. sendingPane ifNotNil: [sendingPane acceptOnCR: acceptOnCR]. ^self ]. ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 11:58'! transmittedObjectCategory ^EToyIncomingMessage typeKeyboardChat! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyChatMorph class instanceVariableNames: ''! !EToyChatMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 14:51'! chatFrom: ipAddress name: senderName text: text | chatWindow | chatWindow _ self chatWindowForIP: ipAddress name: senderName picture: (EToySenderMorph pictureForIPAddress: ipAddress) inWorld: self currentWorld. chatWindow chatFrom: ipAddress name: senderName text: text ! ! !EToyChatMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/16/2000 12:26'! chatWindowForIP: ipAddress name: senderName picture: aForm inWorld: aWorld | makeANewOne aSenderBadge existing | existing _ self instanceForIP: ipAddress inWorld: aWorld. existing ifNotNil: [^existing]. makeANewOne _ [ self new recipientForm: aForm; open; setIPAddress: ipAddress ]. EToyCommunicatorMorph playArrivalSound. self doChatsInternalToBadge ifTrue: [ aSenderBadge _ EToySenderMorph instanceForIP: ipAddress inWorld: aWorld. aSenderBadge ifNotNil: [ aSenderBadge startChat: false. ^aSenderBadge findDeepSubmorphThat: [ :x | x isKindOf: EToyChatMorph] ifAbsent: makeANewOne ]. aSenderBadge _ EToySenderMorph instanceForIP: ipAddress. aSenderBadge ifNotNil: [ aSenderBadge _ aSenderBadge veryDeepCopy. aSenderBadge killExistingChat; openInWorld: aWorld; startChat: false. ^aSenderBadge findDeepSubmorphThat: [ :x | x isKindOf: EToyChatMorph] ifAbsent: makeANewOne ]. (aSenderBadge _ EToySenderMorph new) userName: senderName userPicture: aForm userEmail: 'unknown' userIPAddress: ipAddress; position: 200@200; openInWorld: aWorld; startChat: false. ^aSenderBadge findDeepSubmorphThat: [ :x | x isKindOf: EToyChatMorph] ifAbsent: makeANewOne ]. ^makeANewOne value. ! ! !EToyChatMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 17:12'! doChatsInternalToBadge ^true! ! !EToyChatMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 12:48'! instanceForIP: ipAddress inWorld: aWorld ^self allInstances detect: [ :x | x world == aWorld and: [x ipAddress = ipAddress] ] ifNone: [nil] ! ! EToyCommunicatorMorph subclass: #EToyChatOrBadgeMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyChatOrBadgeMorph class instanceVariableNames: ''! !EToyChatOrBadgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/3/2000 07:51'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ self ~~ EToyChatOrBadgeMorph! ! AlignmentMorphBob1 subclass: #EToyCommunicatorMorph instanceVariableNames: 'fields resultQueue ' classVariableNames: 'LastFlashTime ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyCommunicatorMorph commentStamp: '' prior: 0! ====== find and report all instances ===== EToySenderMorph instanceReport ====== zap a bunch of ipAddresses ===== EToySenderMorph allInstances do: [ :each | each ipAddress = '11.11.11.11' ifTrue: [each ipAddress: 'whizzbang'] ]. ==================== now change one of the whizzbang's back to the right address===== ====== delete the whizzbangs ====== EToySenderMorph allInstances do: [ :each | each ipAddress = 'whizzbang' ifTrue: [each stopStepping; delete] ]. ! ]style[(44 32 41 64 13 42 1 85 37 115 1)f1,f1cblue;,f1,f1cblue;,f1cred;,f1cblue;,f1,f1cred;,f1,f1cblue;,f1! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:39'! addGateKeeperMorphs | list currentTime choices age row | self setProperty: #gateKeeperCounterValue toValue: EToyGateKeeperMorph updateCounter. choices _ #( (60 'm' 'in the last minute') (3600 'h' 'in the last hour') (86400 'd' 'in the last day') ). currentTime _ Time totalSeconds. list _ EToyGateKeeperMorph knownIPAddresses. list do: [ :each | age _ each timeBetweenLastAccessAnd: currentTime. age _ choices detect: [ :x | age <= x first] ifNone: [{0. '-'. (age // 86400) printString,'days ago'}]. row _ self addARow: (EToyIncomingMessage allTypes collect: [ :type | self toggleButtonFor: each attribute: type] ), { (self inAColumn: { (StringMorph contents: age second) lock. }) layoutInset: 2; hResizing: #shrinkWrap; setBalloonText: 'Last attempt was ',age third. (self inAColumn: { (StringMorph contents: each ipAddress) lock. }) layoutInset: 2; hResizing: #shrinkWrap. (self inAColumn: { (StringMorph contents: each latestUserName) lock. }) layoutInset: 2. }. row color: (Color r: 0.6 g: 0.8 b: 1.0); borderWidth: 1; borderColor: #raised; vResizing: #spaceFill; "on: #mouseUp send: #mouseUp:in: to: self;" setBalloonText: each fullInfoString ].! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 11:33'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString; color: aColor; actionSelector: aSymbol; setBalloonText: helpString. self field: aSymbol is: f. col _ (self inAColumn: {f}) hResizing: #shrinkWrap. ^col! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 05:57'! commResult: anArrayOfAssociations | aDictionary | aDictionary _ Dictionary new. anArrayOfAssociations do: [ :each | aDictionary add: each]. resultQueue nextPut: aDictionary! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/8/2000 17:45'! delete super delete. self breakDependents! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/8/2000 18:33'! editEvent: anEvent for: aMorph | answer | (aMorph bounds containsPoint: anEvent cursorPoint) ifFalse: [^self]. answer _ FillInTheBlankMorph request: 'Enter a new ',aMorph balloonText initialAnswer: aMorph contents. answer isEmptyOrNil ifTrue: [^self]. aMorph contents: answer ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/8/2000 16:59'! field: fieldName is: anObject fields at: fieldName put: anObject. ^anObject! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 10:05'! flashIndicator: aSymbol | now | now _ Time millisecondClockValue. (LastFlashTime notNil and: [(Time millisecondClockValue - now) abs < 500]) ifTrue: [^self]. LastFlashTime _ now. self trulyFlashIndicator: aSymbol ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 16:42'! handleResult: aDictionary | m | aDictionary at: #commFlash ifPresent: [ :ignore | ^self flashIndicator: #communicating]. self resetIndicator: #communicating. m _ aDictionary at: #message ifAbsent: ['unknown message']. m = 'OK' ifTrue: [^self]. self reportError: m! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/9/2000 17:32'! indicatorFieldNamed: aSymbol color: aColor help: helpString | f col | f _ EllipseMorph new extent: 10@10; color: aColor; setBalloonText: helpString. self field: aSymbol is: f. col _ (self inAColumn: {f}) hResizing: #shrinkWrap. ^col! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/30/2000 15:30'! initialize super initialize. self vResizing: #shrinkWrap. self hResizing: #shrinkWrap. resultQueue _ SharedQueue new. fields _ Dictionary new. self useRoundedCorners. ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/8/2000 16:48'! open self openInWorld! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 16:41'! reportError: aString self inform: aString! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/9/2000 08:18'! resetIndicator: aSymbol | indicator firstColor | indicator _ fields at: aSymbol ifAbsent: [^self]. firstColor _ indicator valueOfProperty: #firstColor ifAbsent: [^self]. indicator color: firstColor. self world displayWorldSafely. ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 10:14'! step | state | [resultQueue isEmpty] whileFalse: [ self handleResult: resultQueue next ]. (state _ self valueOfProperty: #flashingState ifAbsent: [0]) > 0 ifTrue: [ self borderColor: ( (self valueOfProperty: #flashingColors ifAbsent: [{Color green. Color red}]) atWrap: state ). self setProperty: #flashingState toValue: state + 1 ].! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 10:27'! stepTime (self valueOfProperty: #flashingState ifAbsent: [0]) > 0 ifTrue: [ ^200 ] ifFalse: [ ^1000 ].! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 16:19'! stopFlashing self setProperty: #flashingState toValue: 0. self borderColor: (self valueOfProperty: #normalBorderColor ifAbsent: [Color blue]). ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/8/2000 18:35'! textEntryFieldNamed: aSymbol with: aString help: helpString | f col | f _ (StringMorph new contents: aString) setBalloonText: helpString; on: #mouseUp send: #editEvent:for: to: self. self field: aSymbol is: f. col _ (self inAColumn: {f}) color: Color white; hResizing: #shrinkWrap. ^col! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/2/2000 12:42'! toggleButtonFor: entry attribute: attribute ^(self inAColumn: { self simpleToggleButtonFor: entry attribute: attribute help: 'Whether you want "',attribute,'" messages' }) hResizing: #shrinkWrap ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'mir 10/12/2000 14:55'! transmitStreamedObject: outData as: objectCategory to: anIPAddress EToyPeerToPeer transmitStreamedObject: outData as: objectCategory to: anIPAddress for: self! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'mir 10/10/2000 12:47'! transmitStreamedObject: outData to: anIPAddress self transmitStreamedObject: outData as: self transmittedObjectCategory to: anIPAddress ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 10:05'! trulyFlashIndicator: aSymbol | indicator firstColor | indicator _ fields at: aSymbol ifAbsent: [^self]. firstColor _ indicator valueOfProperty: #firstColor ifAbsent: [ indicator setProperty: #firstColor toValue: indicator color. indicator color ]. indicator color: (indicator color = firstColor ifTrue: [Color white] ifFalse: [firstColor]). self world displayWorldSafely. ! ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/9/2000 06:25'! wantsSteps ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyCommunicatorMorph class instanceVariableNames: ''! !EToyCommunicatorMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 16:19'! allForIPAddress: ipString "for cleaning up Alan's demo" " EToySenderMorph allForIPAddress: '1.2.3.4' " Smalltalk garbageCollect. (self allInstances select: [ :each | each ipAddress = ipString]) explore! ! !EToyCommunicatorMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/3/2000 07:48'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ self ~~ EToyCommunicatorMorph! ! !EToyCommunicatorMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 16:13'! instanceReport "for cleaning up Alan's demo" " EToySenderMorph instanceReport " | answer resp | Smalltalk garbageCollect. answer _ self allInstances collect: [ :each | { each. [each ipAddress] on: Error do: [ 'no ipAddress']. each owner ifNil: ['* no owner *'] ifNotNil: [each owner innocuousName,' ',each owner printString]. each world ifNil: ['-----no project-----'] ifNotNil: [each world project name]. } ]. resp _ (PopUpMenu labels: 'IP Address\Project\Owner' withCRs) startUpWithCaption: 'Sorted by'. resp = 1 ifTrue: [ ^(answer asSortedCollection: [ :a :b | a second <= b second]) asArray explore ]. resp = 2 ifTrue: [ ^(answer asSortedCollection: [ :a :b | a fourth <= b fourth]) asArray explore ]. resp = 3 ifTrue: [ ^(answer asSortedCollection: [ :a :b | a third <= b third]) asArray explore ]. answer explore! ! !EToyCommunicatorMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 17:09'! otherCleanup ">>> EToySenderMorph allInstances do: [ :each | each ipAddress = '11.11.11.11' ifTrue: [each ipAddress: 'whizzbang'] ]. <<<" "==================== now change one of the whizzbang's back to the right address=====" ">>> EToySenderMorph allInstances do: [ :each | each ipAddress = 'whizzbang' ifTrue: [each delete] ]. <<<" ! ! !EToyCommunicatorMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/16/2000 12:27'! playArrivalSound Preferences soundsEnabled ifTrue: [ SampledSound playSoundNamed: 'chirp'. ] ifFalse: [ 1 beep ].! ! EToyCommunicatorMorph subclass: #EToyFridgeMorph instanceVariableNames: 'recipients incomingRow recipientRow updateCounter groupMode ' classVariableNames: 'FridgeRecipients NewItems TheFridgeForm UpdateCounter ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyFridgeMorph commentStamp: '' prior: 0! EToyFridgeMorph new openInWorld! ]style[(31)f4cblue;! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 19:24'! acceptDroppingMorph: morphToDrop event: evt | outData | (morphToDrop isKindOf: NewHandleMorph) ifTrue: [ "don't send these" ^morphToDrop rejectDropMorphEvent: evt ]. self eToyRejectDropMorph: morphToDrop event: evt. "we will keep a copy" (morphToDrop isKindOf: EToySenderMorph) ifTrue: [ self class addRecipient: morphToDrop. ^self rebuild ]. self stopFlashing. outData _ morphToDrop veryDeepCopy eToyStreamedRepresentationNotifying: self. self resetIndicator: #working. self class fridgeRecipients do: [ :each | self transmitStreamedObject: outData to: each ipAddress ]. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 12:07'! drawOn: aCanvas | f cache | f _ self class fridgeForm ifNil: [^super drawOn: aCanvas]. cache _ Form extent: bounds extent depth: aCanvas depth. f displayInterpolatedIn: cache boundingBox truncated on: cache. cache replaceColor: Color black withColor: Color transparent. aCanvas translucentImage: cache at: bounds origin. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 12:47'! getChoice: aString aString = 'group' ifTrue: [^groupMode ifNil: [true]].! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 13:13'! groupToggleButton ^(self inAColumn: { (EtoyUpdatingThreePhaseButtonMorph checkBox) target: self; actionSelector: #toggleChoice:; arguments: {'group'}; getSelector: #getChoice:; setBalloonText: 'Changes between group mode and individuals'; step }) hResizing: #shrinkWrap ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 09:21'! handlesMouseDown: globalEvt | localCursorPoint | localCursorPoint _ self globalPointToLocal: globalEvt cursorPoint. groupMode ifFalse: [ self allMorphsDo: [ :each | (each isKindOf: EToySenderMorph) ifTrue: [ (each bounds containsPoint: localCursorPoint) ifTrue: [^false]. ]. ]. ]. ^true! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:47'! handlesMouseOver: globalEvt ^true! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:51'! handlesMouseOverDragging: globalEvt ^true! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:53'! initialize super initialize. groupMode _ true. color _ Color paleRed. self listDirection: #topToBottom. self layoutInset: 10. borderColor _ #raised. borderWidth _ 4. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self setProperty: #normalBorderColor toValue: borderColor. self setProperty: #flashingColors toValue: {Color red. Color yellow}. self rebuild. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:55'! mouseDown: localEvt self addMouseActionIndicatorsWidth: 15 color: (Color blue alpha: 0.7). ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:51'! mouseEnter: evt ^self mouseEnterEither: evt ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:51'! mouseEnterDragging: evt ^self mouseEnterEither: evt ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:51'! mouseEnterEither: evt evt hand hasSubmorphs ifFalse: [ ^self addMouseActionIndicatorsWidth: 10 color: (Color blue alpha: 0.3). ]. (evt hand firstSubmorph isKindOf: EToySenderMorph) ifTrue: [ ^self addMouseActionIndicatorsWidth: 10 color: (Color magenta alpha: 0.3). ]. self addMouseActionIndicatorsWidth: 10 color: (Color green alpha: 0.3). ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:53'! mouseLeave: evt ^self mouseLeaveEither: evt ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:54'! mouseLeaveDragging: evt ^self mouseLeaveEither: evt ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:53'! mouseLeaveEither: evt self deleteAnyMouseActionIndicators. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 09:55'! mouseUp: localEvt (self containsPoint: localEvt cursorPoint) ifFalse: [^self]. Project enterIfThereOrFind: 'Fridge'! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 11:05'! noteRemovalOf: aSenderMorph self class removeRecipientWithIPAddress: aSenderMorph ipAddress! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:35'! rebuild | row filler fudge people maxPerRow | updateCounter _ self class updateCounter. self removeAllMorphs. (self addARow: { filler _ Morph new color: Color transparent; extent: 4@4. }) vResizing: #shrinkWrap. self addARow: { (StringMorph contents: 'the Fridge') lock. self groupToggleButton. }. row _ self addARow: {}. people _ self class fridgeRecipients. maxPerRow _ people size < 7 ifTrue: [2] ifFalse: [3]. "how big can this get before we need a different approach?" people do: [ :each | row submorphCount >= maxPerRow ifTrue: [row _ self addARow: {}]. row addMorphBack: ( groupMode ifTrue: [ (each userPicture scaledToSize: 35@35) asMorph lock ] ifFalse: [ each veryDeepCopy killExistingChat ] ) ]. fullBounds _ nil. self fullBounds. "htsBefore _ submorphs collect: [ :each | each height]." fudge _ 20. filler extent: 4 @ (self height - filler height * 0.37 - self layoutInset - borderWidth - fudge) truncated. "self fixLayout. htsAfter _ submorphs collect: [ :each | each height]. {htsBefore. htsAfter} explore." ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 12:31'! step super step. updateCounter = self class updateCounter ifFalse: [self rebuild]. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 12:50'! toggleChoice: aString updateCounter _ nil. "force rebuild" aString = 'group' ifTrue: [^groupMode _ (groupMode ifNil: [true]) not]. ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 11:50'! transmittedObjectCategory ^EToyIncomingMessage typeFridge! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 10:16'! trulyFlashIndicator: aSymbol | state | state _ (self valueOfProperty: #fridgeFlashingState ifAbsent: [false]) not. self setProperty: #fridgeFlashingState toValue: state. self addMouseActionIndicatorsWidth: 15 color: (Color green alpha: (state ifTrue: [0.3] ifFalse: [0.7])). 1 beep. "self world displayWorldSafely."! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 15:53'! wantsDroppedMorph: aMorph event: evt ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyFridgeMorph class instanceVariableNames: ''! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 12:19'! addRecipient: aSenderMorph self fridgeRecipients do: [ :each | aSenderMorph ipAddress = each ipAddress ifTrue: [^self] ]. self fridgeRecipients add: aSenderMorph. UpdateCounter _ self updateCounter + 1 ! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 12:09'! fridgeForm | fridgeFileName | fridgeFileName _ 'fridge.form'. TheFridgeForm ifNotNil: [^TheFridgeForm]. (FileDirectory default fileExists: fridgeFileName) ifFalse: [^nil]. ^TheFridgeForm _ Form fromFileNamed: fridgeFileName.! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 19:23'! fridgeRecipients ^FridgeRecipients ifNil: [FridgeRecipients _ OrderedCollection new]! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 13:55'! newItem: newMorph | theFridge fridgeWorld trialRect | theFridge _ Project named: 'Fridge'. theFridge ifNil: [^self newItems add: newMorph]. fridgeWorld _ theFridge world. trialRect _ fridgeWorld randomBoundsFor: newMorph. fridgeWorld addMorphFront: (newMorph position: trialRect topLeft); startSteppingSubmorphsOf: newMorph ! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 19:19'! newItems ^NewItems ifNil: [NewItems _ OrderedCollection new]! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 11:04'! removeRecipientWithIPAddress: ipString FridgeRecipients _ self fridgeRecipients reject: [ :each | ipString = each ipAddress ]. UpdateCounter _ self updateCounter + 1 ! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 19:22'! updateCounter ^UpdateCounter ifNil: [0]! ! MorphicModel subclass: #EToyGateKeeperEntry instanceVariableNames: 'ipAddress accessAttempts lastTimes acceptableTypes latestUserName attempsDenied lastRequests ' classVariableNames: 'KnownIPAddresses ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:42'! acceptableTypes ^acceptableTypes! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 14:11'! dateAndTimeStringFrom: totalSeconds | dateAndTime | dateAndTime _ Time dateAndTimeFromSeconds: totalSeconds. ^dateAndTime first printString,' ',dateAndTime second printString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 13:51'! fullInfoString ^self latestUserName, ' at ', ipAddress , ' attempts: ', accessAttempts printString, '/', attempsDenied printString, ' last: ', (self lastIncomingMessageTimeString) "acceptableTypes" ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 12:19'! getChoice: aString ^acceptableTypes includes: aString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 11:49'! initialize self flag: #bob. "need to decide better initial types" super initialize. ipAddress _ '???'. accessAttempts _ attempsDenied _ 0. lastRequests _ OrderedCollection new. acceptableTypes _ Set withAll: EToyIncomingMessage allTypes. ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 09:33'! ipAddress ^ipAddress! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 09:18'! ipAddress: aString ipAddress _ aString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 13:37'! lastIncomingMessageTimeString lastRequests isEmpty ifTrue: [^'never']. ^self dateAndTimeStringFrom: lastRequests first first ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 13:56'! lastTimeChecked ^self valueOfProperty: #lastTimeChecked ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 13:57'! lastTimeChecked: aDateAndTimeInSeconds self setProperty: #lastTimeChecked toValue: aDateAndTimeInSeconds ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 14:22'! lastTimeCheckedString | statusTime | statusTime _ self valueOfProperty: #lastTimeChecked ifAbsent: [^'none']. ^(self dateAndTimeStringFrom: statusTime)! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 10:49'! latestUserName ^latestUserName ifNil: ['???']! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 10:46'! latestUserName: aString latestUserName _ aString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 14:09'! requestAccessOfType: aString | ok | accessAttempts _ accessAttempts + 1. lastRequests addFirst: {Time totalSeconds. aString}. lastRequests size > 10 ifTrue: [ lastRequests _ lastRequests copyFrom: 1 to: 10. ]. ok _ (acceptableTypes includes: aString) or: [acceptableTypes includes: 'all']. ok ifFalse: [attempsDenied _ attempsDenied + 1]. ^ok! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 14:10'! statusReplyReceived: anArray self setProperty: #lastStatusReplyTime toValue: Time totalSeconds. self setProperty: #lastStatusReply toValue: anArray.! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 8/1/2000 14:16'! statusReplyReceivedString | statusTime | statusTime _ self valueOfProperty: #lastStatusReplyTime ifAbsent: [^'none']. ^(self dateAndTimeStringFrom: statusTime),' accepts: ', (self valueOfProperty: #lastStatusReply) asArray printString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 09:38'! timeBetweenLastAccessAnd: currentTime lastRequests isEmpty ifTrue: [^0]. ^currentTime - lastRequests first first ! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 10:39'! toggleChoice: aString (acceptableTypes includes: aString) ifTrue: [ acceptableTypes remove: aString ifAbsent: [] ] ifFalse: [ acceptableTypes add: aString ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyGateKeeperEntry class instanceVariableNames: ''! !EToyGateKeeperEntry class methodsFor: 'as yet unclassified' stamp: 'RAA 8/3/2000 07:48'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! EToyCommunicatorMorph subclass: #EToyGateKeeperMorph instanceVariableNames: 'counter ' classVariableNames: 'KnownIPAddresses UpdateCounter ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyGateKeeperMorph commentStamp: '' prior: 0! EToyGateKeeperMorph new open " I am used to control the types of connections a user is willing to allow. "! ]style[(28 79)f4cblue;,f1! !EToyGateKeeperMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:53'! initialize super initialize. self listDirection: #topToBottom. color _ Color lightGray. self layoutInset: 4. borderColor _ #raised "Color brown". borderWidth _ 4. self hResizing: #spaceFill. self vResizing: #spaceFill. self useRoundedCorners. self rebuild. ! ! !EToyGateKeeperMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 09:29'! open self rebuild. self openInWorld.! ! !EToyGateKeeperMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 13:27'! rebuild self removeAllMorphs. self addGateKeeperMorphs. ! ! !EToyGateKeeperMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 13:28'! step (self valueOfProperty: #gateKeeperCounterValue) = EToyGateKeeperMorph updateCounter ifTrue: [^self]. self rebuild. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyGateKeeperMorph class instanceVariableNames: ''! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 11:06'! acceptRequest: requestType from: senderName at: ipAddressString | entry | UpdateCounter _ self updateCounter + 1. entry _ self entryForIPAddress: ipAddressString. senderName isEmpty ifFalse: [entry latestUserName: senderName]. ^entry requestAccessOfType: requestType! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:43'! acceptableTypesFor: ipAddressString ^(self knownIPAddresses at: ipAddressString ifAbsent: [^#()]) acceptableTypes! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 14:13'! entryForIPAddress: ipAddressString | known entry | UpdateCounter _ self updateCounter + 1. known _ self knownIPAddresses. entry _ known at: ipAddressString ifAbsentPut: [ entry _ EToyGateKeeperEntry new. entry ipAddress: ipAddressString. entry ]. ^entry! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/3/2000 07:48'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 08:42'! knownIPAddresses ^KnownIPAddresses ifNil: [KnownIPAddresses _ Dictionary new]! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 09:28'! updateCounter ^UpdateCounter ifNil: [UpdateCounter _ 0]! ! AlignmentMorphBob1 subclass: #EToyGenericDialogMorph instanceVariableNames: 'namedFields ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 10:17'! genericTextFieldNamed: aString | newField | newField _ TextMorph new beAllFont: self myFont; extent: 300@20; contentsWrapped: ''. namedFields at: aString put: newField. ^newField ! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 10:20'! inAColumnForText: someMorphs ^(self inAColumn: someMorphs) hResizing: #shrinkWrap; color: Color white; borderColor: Color black; borderWidth: 1 ! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 10:15'! initialize namedFields _ Dictionary new. super initialize. self rebuild. ! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 10:20'! lockedString: aString ^self inAColumn: {(StringMorph contents: aString font: self myFont) lock}. ! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 10:16'! myFont ^TextStyle default fontOfSize: 12! ! AbstractHierarchicalList subclass: #EToyHierarchicalTextGizmo instanceVariableNames: 'topNode ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Outliner'! !EToyHierarchicalTextGizmo commentStamp: '' prior: 0! EToyHierarchicalTextGizmo example! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:53'! addChild self addNewChildAfter: nil. ! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:52'! addNewChildAfter: aNodeOrNil currentSelection addNewChildAfter: aNodeOrNil. self changed: #getList.! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 11:41'! addSibling currentSelection addSibling. self changed: #getList.! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:29'! deleteSelectedItem currentSelection delete. self changed: #getList.! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 09:57'! expandAllBelow currentSelection withoutListWrapper withAllChildrenDo: [ :each | each setProperty: #showInOpenedState toValue: true ]. self changed: #getList.! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 09:57'! genericMenu: aMenu | menu | currentSelection ifNil: [ aMenu add: '*nothing selected*' target: self selector: #yourself. ^aMenu ]. menu _ DumberMenuMorph new defaultTarget: self. menu add: 'expand all below me' target: self selector: #expandAllBelow; add: 'addChild' target: self selector: #addChild; add: 'delete' target: self selector: #deleteSelectedItem. ^ menu! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 11:50'! getList ^Array with: (EToyTextNodeWrapper with: topNode model: self parent: nil) ! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 10:36'! inAWindow | window | window _ (SystemWindow labelled: 'HText') model: self. window addMorph: self notInAWindow frame: (0@0 corner: 1@1). ^ window! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 09:43'! notInAWindow | listMorph | (listMorph _ EToyHierarchicalTextMorph on: self list: #getList selected: #getCurrentSelection changeSelected: #noteNewSelection: menu: #genericMenu: keystroke: nil). listMorph autoDeselect: false. ^ listMorph! ! !EToyHierarchicalTextGizmo methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 00:40'! topNode: aTextNode topNode _ aTextNode! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyHierarchicalTextGizmo class instanceVariableNames: ''! !EToyHierarchicalTextGizmo class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 14:28'! example " EToyHierarchicalTextGizmo example " (EToyHierarchicalTextGizmo new topNode: EToyTextNode newNode; notInAWindow) openInWorld! ! SimpleHierarchicalListMorph subclass: #EToyHierarchicalTextMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Outliner'! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 02:08'! adjustSubmorphPositions | p h w | p _ 0@0. w _ self width. scroller submorphsDo: [ :each | h _ each position: p andWidth: w. p _ p + (0@h) ]. self changed; layoutChanged; setScrollDeltas. ! ! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 01:50'! extent: aPoint | wasDifferent | wasDifferent _ self extent ~= aPoint. super extent: aPoint. wasDifferent ifTrue: [self adjustSubmorphPositions].! ! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 22:20'! indentingItemClass ^IndentingListParagraphMorph! ! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 14:27'! initialize super initialize. color _ Color white. self useRoundedCorners. ! ! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 15:05'! keyStroke: evt selectedMorph ifNil: [^self]. selectedMorph keyStroke: evt ! ! !EToyHierarchicalTextMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 10:05'! selectedMorph: aMorph selectedMorph == aMorph ifTrue: [^self]. self unhighlightSelection. selectedMorph _ aMorph. self highlightSelection! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyHierarchicalTextMorph class instanceVariableNames: ''! !EToyHierarchicalTextMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 14:34'! new | listMorph model | model _ EToyHierarchicalTextGizmo new topNode: EToyTextNode newNode. (listMorph _ EToyHierarchicalTextMorph on: model list: #getList selected: #getCurrentSelection changeSelected: #noteNewSelection: menu: #genericMenu: keystroke: nil). listMorph autoDeselect: false. ^ listMorph! ! !EToyHierarchicalTextMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 14:32'! on: anObject list: getListSel selected: getSelectionSel changeSelected: setSelectionSel menu: getMenuSel keystroke: keyActionSel "Create a 'pluggable' list view on the given model parameterized by the given message selectors. See ListView>>aboutPluggability comment." ^ self basicNew initialize on: anObject list: getListSel selected: getSelectionSel changeSelected: setSelectionSel menu: getMenuSel keystroke: keyActionSel ! ! Object subclass: #EToyIncomingMessage instanceVariableNames: '' classVariableNames: 'MessageHandlers MessageTypes ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyIncomingMessage methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 09:22'! incomingMessgage: dataStream fromIPAddress: ipAddress | nullChar messageType senderName selectorAndReceiver | nullChar _ 0 asCharacter. messageType _ dataStream upTo: nullChar. senderName _ dataStream upTo: nullChar. (EToyGateKeeperMorph acceptRequest: messageType from: senderName at: ipAddress) ifFalse: [ ^self ]. selectorAndReceiver _ self class messageHandlers at: messageType ifAbsent: [^self]. ^selectorAndReceiver second perform: selectorAndReceiver first withArguments: {dataStream. senderName. ipAddress} ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyIncomingMessage class instanceVariableNames: ''! !EToyIncomingMessage class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 13:21'! forType: aMessageType send: aSymbol to: anObject self messageHandlers at: aMessageType put: {aSymbol. anObject}! ! !EToyIncomingMessage class methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 07:52'! initializeMessageHandlers self forType: self typeMorph send: #handleNewMorphFrom:sentBy:ipAddress: to: self; forType: self typeFridge send: #handleNewFridgeMorphFrom:sentBy:ipAddress: to: self; forType: self typeKeyboardChat send: #handleNewChatFrom:sentBy:ipAddress: to: self; forType: self typeMultiChat send: #handleNewMultiChatFrom:sentBy:ipAddress: to: self; forType: self typeStatusRequest send: #handleNewStatusRequestFrom:sentBy:ipAddress: to: self; forType: self typeStatusReply send: #handleNewStatusReplyFrom:sentBy:ipAddress: to: self; forType: self typeSeeDesktop send: #handleNewSeeDesktopFrom:sentBy:ipAddress: to: self. ! ! !EToyIncomingMessage class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 13:35'! messageHandlers ^MessageHandlers ifNil: [MessageHandlers _ Dictionary new].! ! !EToyIncomingMessage class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 13:36'! newObjectFromStream: dataStream | newObject | [newObject _ SmartRefStream objectFromStreamedRepresentation: dataStream upToEnd.] on: ProgressInitiationException do: [ :ex | ex sendNotificationsTo: [ :min :max :curr | "self flashIndicator: #working." ]. ]. "self resetIndicator: #working." ^newObject ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/4/2000 13:32'! handleNewChatFrom: dataStream sentBy: senderName ipAddress: ipAddressString ^ EToyChatMorph chatFrom: ipAddressString name: senderName text: (self newObjectFromStream: dataStream). ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/4/2000 13:33'! handleNewFridgeMorphFrom: dataStream sentBy: senderName ipAddress: ipAddressString | newObject | newObject _ self newObjectFromStream: dataStream. newObject setProperty: #fridgeSender toValue: senderName; setProperty: #fridgeIPAddress toValue: ipAddressString; setProperty: #fridgeDate toValue: Time dateAndTimeNow. WorldState addDeferredUIMessage: [EToyFridgeMorph newItem: newObject] fixTemps. ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/16/2000 12:26'! handleNewMorphFrom: dataStream sentBy: senderName ipAddress: ipAddressString | newObject thumbForm targetWorld | newObject _ self newObjectFromStream: dataStream. EToyCommunicatorMorph playArrivalSound. targetWorld _ self currentWorld. (EToyMorphsWelcomeMorph morphsWelcomeInWorld: targetWorld) ifTrue: [ newObject position: ( newObject valueOfProperty: #positionInOriginatingWorld ifAbsent: [(targetWorld randomBoundsFor: newObject) topLeft] ). WorldState addDeferredUIMessage: [ newObject openInWorld: targetWorld. ] fixTemps. ^self ]. thumbForm _ newObject imageForm scaledToSize: 50@50. EToyListenerMorph addToGlobalIncomingQueue: { thumbForm. newObject. senderName. ipAddressString }. WorldState addDeferredUIMessage: [ EToyListenerMorph ensureListenerInCurrentWorld ] fixTemps. ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/17/2000 09:22'! handleNewMultiChatFrom: dataStream sentBy: senderName ipAddress: ipAddressString ^ EToyMultiChatMorph chatFrom: ipAddressString name: senderName text: (self newObjectFromStream: dataStream). ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/4/2000 13:34'! handleNewSeeDesktopFrom: dataStream sentBy: senderName ipAddress: ipAddressString "more later" ^ EToyChatMorph chatFrom: ipAddressString name: senderName text: ipAddressString,' would like to see your desktop'. ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/4/2000 13:34'! handleNewStatusReplyFrom: dataStream sentBy: senderName ipAddress: ipAddressString (EToyGateKeeperMorph entryForIPAddress: ipAddressString) statusReplyReceived: ( self newObjectFromStream: dataStream ) ! ! !EToyIncomingMessage class methodsFor: 'handlers' stamp: 'RAA 8/4/2000 13:34'! handleNewStatusRequestFrom: dataStream sentBy: senderName ipAddress: ipAddressString "more later" ^ EToyChatMorph chatFrom: ipAddressString name: senderName text: ipAddressString,' would like to know if you are available'. ! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 11/13/2000 14:39'! allTypes ^MessageTypes ifNil: [ MessageTypes _ { self typeKeyboardChat. self typeMorph. self typeFridge. self typeStatusRequest. self typeStatusReply. self typeSeeDesktop. self typeAudioChat. self typeAudioChatContinuous. self typeMultiChat. } ] ! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 11/13/2000 14:39'! registerType: aMessageType MessageTypes _ self allTypes copyWith: aMessageType! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 13:20'! typeAudioChat ^'audiochat'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/5/2000 19:21'! typeAudioChatContinuous ^'audiochat2'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:49'! typeFridge ^'fridge'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:46'! typeKeyboardChat ^'chat'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:59'! typeMorph ^'morph'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/17/2000 07:41'! typeMultiChat ^'multichat'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:56'! typeSeeDesktop ^'seedesktop'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:53'! typeStatusReply ^'statusreply'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 8/4/2000 11:51'! typeStatusRequest ^'statusrequest'! ! !EToyIncomingMessage class methodsFor: 'message types' stamp: 'RAA 11/13/2000 14:39'! unregisterType: aMessageType MessageTypes _ self allTypes copyWithout: aMessageType! ! EToyCommunicatorMorph subclass: #EToyListenerMorph instanceVariableNames: 'listener updateCounter ' classVariableNames: 'GlobalIncomingQueue GlobalListener QueueSemaphore UpdateCounter WasListeningAtShutdown ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyListenerMorph commentStamp: '' prior: 0! EToyListenerMorph new open EToyListenerMorph startListening. EToyListenerMorph stopListening. " EToyListenerMorph listens for messgaes from other EToy communicators. You need one of these open to receive messages from elsewhere. - Received Morphs are shown in a list. Items can be grabbed (a copy) or deleted. - Chat messages are sent to an appropriate EToyChatMorph (created if necessary) " ! ]style[(45 16 18 15 1 299)cblue;f3,bf3,cblue;f3,bf3,cblue;f3,f1! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 14:17'! addNewObject: newObject thumbForm: aForm sentBy: senderName ipAddress: ipAddressString | thumb row | thumb _ aForm asMorph. thumb setProperty: #depictedObject toValue: newObject. row _ self addARow: { thumb. self inAColumn: { StringMorph new contents: senderName; lock. StringMorph new contents: ipAddressString; lock. } }. row on: #mouseDown send: #mouseDownEvent:for: to: self. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 10:44'! delete listener ifNotNil: [listener stopListening. listener _ nil]. "for old instances that were locally listening" super delete.! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:53'! initialize super initialize. self listDirection: #topToBottom. color _ Color lightBlue. self layoutInset: 4. borderColor _ Color blue. borderWidth _ 4. self rebuild. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:48'! mouseDownEvent: event for: aMorph | menu selection depictedObject | depictedObject _ aMorph firstSubmorph valueOfProperty: #depictedObject. menu _ CustomMenu new. menu add: 'Grab' action: [ event hand attachMorph: depictedObject veryDeepCopy ]; add: 'Delete' action: [ self class removeFromGlobalIncomingQueue: depictedObject. self rebuild. ]. selection _ menu build startUpCenteredWithCaption: 'Morph from ', (aMorph submorphs at: 2) firstSubmorph contents. selection ifNil: [^self]. selection value. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:49'! rebuild | earMorph | updateCounter _ UpdateCounter. self removeAllMorphs. self addGateKeeperMorphs. GlobalListener ifNil: [ earMorph _ (self class makeListeningToggle: false) asMorph. earMorph setBalloonText: 'Click to START listening for messages'. earMorph on: #mouseUp send: #startListening to: self. ] ifNotNil: [ earMorph _ (self class makeListeningToggle: true) asMorph. earMorph setBalloonText: 'Click to STOP listening for messages'. earMorph on: #mouseUp send: #stopListening to: self. ]. self addARow: {self inAColumn: {earMorph}}. self addARow: { self inAColumn: {(StringMorph contents: 'Incoming communications') lock}. self indicatorFieldNamed: #working color: Color blue help: 'working'. self indicatorFieldNamed: #communicating color: Color green help: 'receiving'. }. "{thumbForm. newObject. senderName. ipAddressString}" self class globalIncomingQueueCopy do: [ :each | self addNewObject: each second thumbForm: each first sentBy: each third ipAddress: each fourth. ].! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:23'! startListening self class startListening! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:49'! step | needRebuild | super step. needRebuild _ false. (self valueOfProperty: #gateKeeperCounterValue) = EToyGateKeeperMorph updateCounter ifFalse: [needRebuild _ true]. updateCounter = UpdateCounter ifFalse: [ needRebuild _ true. ]. needRebuild ifTrue: [self rebuild]. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:23'! stopListening self class stopListening! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyListenerMorph class instanceVariableNames: ''! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:24'! addToGlobalIncomingQueue: aMorphTuple self critical: [ self globalIncomingQueue add: aMorphTuple. self bumpUpdateCounter. ].! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:23'! bumpUpdateCounter UpdateCounter _ (UpdateCounter ifNil: [0]) + 1. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 11/6/2000 11:48'! commResult: anArrayOfAssociations WorldState addDeferredUIMessage: [self commResultDeferred: anArrayOfAssociations].! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 11/6/2000 11:49'! commResultDeferred: anArrayOfAssociations | m ipAddress aDictionary | "to be run as part of the UI process in case user interaction is required" aDictionary _ Dictionary new. anArrayOfAssociations do: [ :each | aDictionary add: each]. aDictionary at: #commFlash ifPresent: [ :ignore | ^self]. m _ aDictionary at: #message ifAbsent: [^self]. m = 'OK' ifFalse: [^self]. ipAddress _ NetNameResolver stringFromAddress: (aDictionary at: #ipAddress). EToyIncomingMessage new incomingMessgage: (ReadStream on: (aDictionary at: #data)) fromIPAddress: ipAddress ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 18:05'! confirmListening self isListening ifFalse: [ (self confirm: 'You currently are not listening and will not hear a reply. Shall I start listening for you?') ifTrue: [ self startListening ]. ]. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 14:11'! critical: aBlock QueueSemaphore ifNil: [QueueSemaphore _ Semaphore forMutualExclusion]. ^QueueSemaphore critical: aBlock ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 14:04'! ensureListenerInCurrentWorld | w | w _ self currentWorld. EToyListenerMorph allInstances detect: [ :each | each world == w] ifNone: [EToyListenerMorph new open]! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 12:46'! flashIndicator: ignoredForNow! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 14:12'! globalIncomingQueue ^GlobalIncomingQueue ifNil: [GlobalIncomingQueue _ OrderedCollection new].! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 14:13'! globalIncomingQueueCopy ^self critical: [self globalIncomingQueue copy]. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/25/2000 16:28'! initialize " EToyListenerMorph initialize " Smalltalk addToStartUpList: self. Smalltalk addToShutDownList: self. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/9/2000 17:56'! isListening ^GlobalListener notNil ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/19/2000 18:59'! makeListeningToggle: withEars | background c capExtent bgExtent earExtent earDeltaX earDeltaY botCent factor parts | factor _ 2. bgExtent _ (50@25) * factor. capExtent _ (30@30) * factor. earExtent _ (15@15) * factor. earDeltaX _ capExtent x // 2. earDeltaY _ capExtent y // 2. background _ Form extent: bgExtent depth: 8. botCent _ background boundingBox bottomCenter. c _ background getCanvas. "c fillColor: Color white." parts _ { (botCent - (capExtent // 2)) extent: capExtent. }. withEars ifTrue: [ parts _ parts , { (botCent - (earDeltaX @ earDeltaY) - (earExtent // 2)) extent: earExtent. (botCent - (earDeltaX negated @ earDeltaY) - (earExtent // 2)) extent: earExtent. } ]. parts do: [ :each | c fillOval: each color: Color black borderWidth: 0 borderColor: Color black. ]. ^background "===== f2 _ Form extent: 30@15 depth: 8. background displayInterpolatedOn: f2. f2 replaceColor: Color white withColor: Color transparent. ^f2 =====" ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:24'! removeFromGlobalIncomingQueue: theActualObject self critical: [ GlobalIncomingQueue _ self globalIncomingQueue reject: [ :each | each second == theActualObject ]. self bumpUpdateCounter. ].! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 12:47'! resetIndicator: ignoredForNow! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/25/2000 16:26'! shutDown: quitting WasListeningAtShutdown _ GlobalListener notNil. self stopListening. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:24'! startListening self stopListening. GlobalListener _ EToyPeerToPeer new awaitDataFor: self. self bumpUpdateCounter. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/25/2000 16:27'! startUp: resuming WasListeningAtShutdown == true ifTrue: [ self startListening. ]. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/15/2000 18:24'! stopListening GlobalListener ifNotNil: [ GlobalListener stopListening. GlobalListener _ nil. self bumpUpdateCounter. ]. ! ! EToyCommunicatorMorph subclass: #EToyMorphsWelcomeMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyMorphsWelcomeMorph commentStamp: '' prior: 0! EToyMorphsWelcomeMorph new openInWorld! !EToyMorphsWelcomeMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 13:40'! initialize | earMorph | super initialize. color _ Color yellow. earMorph _ (EToyListenerMorph makeListeningToggle: true) asMorph. earMorph setBalloonText: 'My presence in this world means received morphs may appear automatically'. self addARow: {earMorph}. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyMorphsWelcomeMorph class instanceVariableNames: ''! !EToyMorphsWelcomeMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 13:44'! morphsWelcomeInWorld: aWorld ^self allInstances anySatisfy: [ :each | each world == aWorld]! ! EToyChatMorph subclass: #EToyMultiChatMorph instanceVariableNames: 'targetIPAddresses ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 19:24'! acceptDroppingMorph: morphToDrop event: evt (morphToDrop isKindOf: EToySenderMorph) ifFalse: [ ^morphToDrop rejectDropMorphEvent: evt. ]. self eToyRejectDropMorph: morphToDrop event: evt. "we don't really want it" self updateIPAddressField: targetIPAddresses,{morphToDrop ipAddress}. ! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/5/2000 14:15'! acceptTo: someText forMorph: aMorph | streamedMessage betterText | betterText _ self improveText: someText forMorph: aMorph. streamedMessage _ {targetIPAddresses. betterText} eToyStreamedRepresentationNotifying: self. targetIPAddresses do: [ :each | self transmitStreamedObject: streamedMessage to: each. ]. aMorph setText: '' asText. self appendMessage: self startOfMessageFromMe, ' - ', betterText, String cr. ^true! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 09:01'! chatFrom: ipAddress name: senderName text: textPackage super chatFrom: ipAddress name: senderName text: textPackage second. self updateIPAddressField: ( targetIPAddresses,textPackage first,{ipAddress} copyWithout: NetNameResolver localAddressString ). ! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 09:01'! editEvent: anEvent for: aMorph | answer initialText aFillInTheBlankMorph | (aMorph bounds containsPoint: anEvent cursorPoint) ifFalse: [^self]. initialText _ String streamContents: [ :strm | targetIPAddresses do: [ :each | strm nextPutAll: each; cr]. ]. aFillInTheBlankMorph _ FillInTheBlankMorph new setQuery: 'Who are you chatting with?' initialAnswer: initialText answerHeight: 250 acceptOnCR: false. aFillInTheBlankMorph responseUponCancel: nil. self world addMorph: aFillInTheBlankMorph centeredNear: anEvent cursorPoint. answer _ aFillInTheBlankMorph getUserResponse. answer ifNil: [^self]. self updateIPAddressField: (answer findTokens: ' ',String cr). ! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 08:57'! initialize targetIPAddresses _ OrderedCollection new. super initialize. bounds _ 0@0 extent: 350@350.! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:20'! rebuild | r1 r2 | r1 _ self addARow: { self simpleToggleButtonFor: self attribute: #acceptOnCR help: 'Send with Return?'. self inAColumn: {StringMorph new contents: 'Multi chat with:'; lock}. self textEntryFieldNamed: #ipAddress with: '' help: 'Click to edit participant list'. }. sendingPane _ PluggableTextMorph on: self text: nil accept: #acceptTo:forMorph:. sendingPane hResizing: #spaceFill; vResizing: #spaceFill. self addMorphBack: sendingPane. r2 _ self addARow: {self inAColumn: {StringMorph new contents: 'Replies'; lock}}. receivingPane _ PluggableTextMorph on: self text: nil accept: nil. receivingPane hResizing: #spaceFill; vResizing: #spaceFill. self addMorphBack: receivingPane. receivingPane spaceFillWeight: 3. {r1. r2} do: [ :each | each vResizing: #shrinkWrap; minHeight: 18; color: Color veryLightGray. ]. self updateIPAddressField: targetIPAddresses. sendingPane acceptOnCR: (acceptOnCR ifNil: [acceptOnCR _ true]).! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 08:53'! standardBorderColor ^Color veryLightGray! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 07:44'! transmittedObjectCategory ^EToyIncomingMessage typeMultiChat! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 09:01'! updateIPAddressField: newAddresses targetIPAddresses _ ( newAddresses copyWithout: NetNameResolver localAddressString ) asSet asSortedCollection asArray. (fields at: #ipAddress) contents: targetIPAddresses size printString,' people'.! ! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 09:04'! wantsDroppedMorph: aMorph event: evt (aMorph isKindOf: EToySenderMorph) ifFalse: [^false]. (bounds containsPoint: evt cursorPoint) ifFalse: [^false]. ^true.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyMultiChatMorph class instanceVariableNames: ''! !EToyMultiChatMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 08:53'! chatWindowForIP: ipAddress name: senderName picture: aForm inWorld: aWorld ^self allInstances detect: [ :x | x world == aWorld] ifNone: [ EToyCommunicatorMorph playArrivalSound. self new open ]. ! ! Object subclass: #EToyPeerToPeer instanceVariableNames: 'socket communicatorMorph process ipAddress connectionQueue dataQueue remoteSocketAddress leftOverData ' classVariableNames: 'DEBUG PREVTICK ' poolDictionaries: '' category: 'Network-EToy Communications'! !EToyPeerToPeer methodsFor: 'sending' stamp: 'RAA 8/6/2000 09:29'! doConnectForSend | addr | addr _ NetNameResolver addressForName: ipAddress. addr ifNil: [ communicatorMorph commResult: {#message -> ('could not find ',ipAddress)}. ^false ]. socket connectTo: addr port: self class eToyCommunicationsPort. (socket waitForConnectionUntil: (Socket deadlineSecs: 15)) ifFalse: [ communicatorMorph commResult: {#message -> ('no connection to ',ipAddress,' (', (NetNameResolver stringFromAddress: addr),')')}. ^false ]. ^true ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'RAA 8/12/2000 14:28'! doSendData | totalLength myData allTheData | myData _ dataQueue next ifNil: [socket sendData: '0 '. ^false]. totalLength _ (myData collect: [ :x | x size]) sum. socket sendData: totalLength printString,' '. allTheData _ WriteStream on: (String new: totalLength). myData do: [ :chunk | allTheData nextPutAll: chunk asString]. NebraskaDebug at: #peerBytesSent add: {totalLength}. self sendDataCautiously: allTheData contents. ^true ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'RAA 8/9/2000 16:30'! sendDataCautiously: aStringOrByteArray "Send all of the data in the given array, even if it requires multiple calls to send it all. Return the number of bytes sent. Try not to send too much at once since this seemed to cause problems talking to a port on the same machine" | bytesSent bytesToSend count | bytesToSend _ aStringOrByteArray size. bytesSent _ 0. [bytesSent < bytesToSend] whileTrue: [ count _ socket sendSomeData: aStringOrByteArray startIndex: bytesSent + 1 count: (bytesToSend - bytesSent min: 4000). bytesSent _ bytesSent + count. communicatorMorph commResult: {#commFlash -> true}. (Delay forMilliseconds: 10) wait. ]. ^ bytesSent ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'RAA 8/6/2000 09:34'! sendSomeData: arrayOfByteObjects to: anIPAddress for: aCommunicatorMorph dataQueue _ self sendSomeData: arrayOfByteObjects to: anIPAddress for: aCommunicatorMorph multiple: false. dataQueue nextPut: nil. "only this message to send" ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'RAA 8/6/2000 09:34'! sendSomeData: arrayOfByteObjects to: anIPAddress for: aCommunicatorMorph multiple: aBoolean Socket initializeNetwork. socket _ Socket newTCP. dataQueue _ SharedQueue new. dataQueue nextPut: arrayOfByteObjects. communicatorMorph _ aCommunicatorMorph. ipAddress _ anIPAddress. process _ [ self doConnectForSend ifTrue: [ [self doSendData] whileTrue. communicatorMorph commResult: {#message -> 'OK'}. socket closeAndDestroy. ]. ] newProcess. process priority: Processor highIOPriority. process resume. ^dataQueue ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 7/14/2000 23:17'! awaitDataFor: aCommunicatorMorph Socket initializeNetwork. connectionQueue _ ConnectionQueue portNumber: self class eToyCommunicationsPort queueLength: 6. communicatorMorph _ aCommunicatorMorph. process _ [self doAwaitData] newProcess. process priority: Processor highIOPriority. process resume. ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 7/9/2000 14:05'! doAwaitData [true] whileTrue: [ socket _ connectionQueue getConnectionOrNilLenient. socket ifNil: [ (Delay forMilliseconds: 50) wait ] ifNotNil: [ self class new receiveDataOn: socket for: communicatorMorph ] ]. ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 8/6/2000 10:45'! doReceiveData | answer | [answer _ self doReceiveOneMessage] on: Error do: [ :ex | communicatorMorph commResult: {#message -> (ex description,' ',socket printString)}. ^false ]. communicatorMorph commResult: { #message -> 'OK'. #data -> answer . #ipAddress -> remoteSocketAddress. }. ^answer size > 0 ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 8/6/2000 14:29'! doReceiveOneMessage | awaitingLength i length answer | awaitingLength _ true. answer _ WriteStream on: String new. [awaitingLength] whileTrue: [ leftOverData _ leftOverData,socket getData. (i _ leftOverData indexOf: $ ) > 0 ifTrue: [ awaitingLength _ false. length _ (leftOverData first: i - 1) asNumber. answer nextPutAll: (leftOverData allButFirst: i). ]. ]. leftOverData _ ''. [answer size < length] whileTrue: [ answer nextPutAll: socket getData. communicatorMorph commResult: {#commFlash -> true}. ]. answer _ answer contents. answer size > length ifTrue: [ leftOverData _ answer allButFirst: length. answer _ answer first: length ]. ^answer ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 8/6/2000 14:26'! receiveDataOn: aSocket for: aCommunicatorMorph socket _ aSocket. remoteSocketAddress _ socket remoteAddress. communicatorMorph _ aCommunicatorMorph. process _ [ leftOverData _ ''. [self doReceiveData] whileTrue. socket closeAndDestroy. ] newProcess. process priority: Processor highIOPriority. process resume. ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'RAA 7/9/2000 08:22'! stopListening process ifNotNil: [process terminate. process _ nil]. connectionQueue ifNotNil: [connectionQueue destroy. connectionQueue _ nil]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyPeerToPeer class instanceVariableNames: ''! !EToyPeerToPeer class methodsFor: 'as yet unclassified' stamp: 'RAA 7/9/2000 06:21'! eToyCommunicationsPort ^34151 "picked at random"! ! !EToyPeerToPeer class methodsFor: 'as yet unclassified' stamp: 'mir 10/10/2000 12:51'! transmitStreamedObject: outData as: objectCategory to: anIPAddress for: aCommunicator | null | null _ String with: 0 asCharacter. self new sendSomeData: { objectCategory,null. Preferences defaultAuthorName,null. outData } to: anIPAddress for: aCommunicator ! ! EToyProjectRenamerMorph subclass: #EToyProjectDetailsMorph instanceVariableNames: 'projectDetails ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:55'! copyOutDetails | newDetails | newDetails _ Dictionary new. self fieldToDetailsMappings do: [ :each | namedFields at: each first ifPresent: [ :field | newDetails at: each second put: field contents string ]. ]. namedFields at: 'projectname' ifPresent: [ :field | newDetails at: 'projectname' put: field contents string ]. ^newDetails! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/20/2000 09:45'! doExpand self expandedFormat: true. self copyOutDetails. self rebuild. ! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 12:43'! doOK self validateTheProjectName ifFalse: [^false]. actionBlock value: self copyOutDetails. self delete.! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/20/2000 09:43'! expandButton ^self buttonNamed: 'More' action: #doExpand color: self buttonColor help: 'Show more info on this project.'! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/20/2000 09:40'! expandedFormat ^self valueOfProperty: #expandedFormat ifAbsent: [false]! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/20/2000 09:40'! expandedFormat: aBoolean self setProperty: #expandedFormat toValue: aBoolean! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:33'! fieldToDetailsMappings ^#( (#description 'projectdescription' 'Description:' 100) (#author 'projectauthor' 'Author:' 20) (#category 'projectcategory' 'Category:' 20) (#subCategory 'projectsubcategory' 'Sub-category:' 20) (#keywords 'projectkeywords' 'Key words:' 20) ) ! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:55'! fillInDetails theProject ifNotNil: [ namedFields at: 'projectname' ifPresent: [ :field | field contentsWrapped: theProject name ]. ]. projectDetails ifNotNil: [ self fieldToDetailsMappings do: [ :each | namedFields at: each first ifPresent: [ :field | projectDetails at: each second ifPresent: [ :data | field contentsWrapped: data ]. ]. ]. ].! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 12:45'! project: aProject actionBlock: aBlock theProject _ aProject. actionBlock _ aBlock. projectDetails _ theProject world valueOfProperty: #ProjectDetails ifAbsent: [Dictionary new]! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 11:08'! projectDetails: aDictionary projectDetails _ aDictionary.! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/20/2000 09:42'! rebuild | bottomButtons | self removeAllMorphs. self addARow: { self lockedString: 'Please describe this project'. }. self addARow: { self lockedString: 'Name:'. self inAColumnForText: {self fieldForProjectName} }. self expandedFormat ifTrue: [ self fieldToDetailsMappings do: [ :each | self addARow: { self lockedString: each third. self inAColumnForText: {(self genericTextFieldNamed: each first) height: each fourth} }. ]. ]. bottomButtons _ self expandedFormat ifTrue: [ { self okButton. self cancelButton. } ] ifFalse: [ { self okButton. self expandButton. self cancelButton. } ]. self addARow: bottomButtons. self fillInDetails.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectDetailsMorph class instanceVariableNames: ''! !EToyProjectDetailsMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 12/24/2000 09:34'! getFullInfoFor: aProject ifValid: aBlock ^self getFullInfoFor: aProject ifValid: aBlock expandedFormat: false! ! !EToyProjectDetailsMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 12/24/2000 09:33'! getFullInfoFor: aProject ifValid: aBlock expandedFormat: expandedFormat | me | (me _ self basicNew) expandedFormat: expandedFormat; project: aProject actionBlock: [ :x | aProject world setProperty: #ProjectDetails toValue: x. x at: 'projectname' ifPresent: [ :newName | newName = aProject name ifFalse: [aProject changeSet name: newName]. ]. me delete. aBlock value. ]; initialize; openCenteredInWorld! ! !EToyProjectDetailsMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:34'! test1: aProject "EToyProjectDetailsMorph test1: Project current" (self basicNew) project: aProject actionBlock: [ :x | aProject world setProperty: #ProjectDetails toValue: x. x at: 'projectname' ifPresent: [ :newName | newName = aProject name ifFalse: [aProject changeSet name: newName]. ] ]; initialize; openCenteredInWorld! ! EToyCommunicatorMorph subclass: #EToyProjectHistoryMorph instanceVariableNames: 'changeCounter ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyProjectHistoryMorph commentStamp: '' prior: 0! EToyProjectHistoryMorph new openInWorld EToyProjectHistoryMorph provides a quick reference of the most recent projects. Click on one to go there.! ]style[(40 106)f3cblue;,f1! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'ar 9/28/2000 13:53'! closeMyFlapIfAny | myFlap allTabs myTab myWorld | myWorld _ self world. myFlap _ self nearestOwnerThat: [ :each | each isFlap]. myFlap ifNil: [^self]. allTabs _ myWorld submorphs select: [ :each | each isFlapTab]. myTab _ allTabs detect: [ :each | each referent == myFlap] ifNone: [^self]. myTab hideFlap. myWorld displayWorldSafely. ! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/12/2000 13:24'! initialize super initialize. self listDirection: #topToBottom. color _ Color lightBrown. self layoutInset: 4. borderColor _ #raised "Color brown". borderWidth _ 4. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self useRoundedCorners. self rebuild. ! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 12:02'! jumpToProject | selection | selection _ (Project buildJumpToMenu: CustomMenu new) startUp. self closeMyFlapIfAny. Project jumpToSelection: selection ! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 07:25'! mouseDown: evt in: aMorph aMorph setProperty: #mouseDownPoint toValue: evt cursorPoint. ! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 07:28'! mouseLeave: evt in: aMorph aMorph removeProperty: #mouseDownPoint.! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/27/2000 22:47'! mouseMove: evt in: aMorph | start tuple project url pvm | start _ aMorph valueOfProperty: #mouseDownPoint ifAbsent: [^self]. (start dist: evt cursorPoint) abs < 5 ifTrue: [^self]. aMorph removeProperty: #mouseDownPoint. evt hand hasSubmorphs ifTrue: [^self]. tuple _ aMorph valueOfProperty: #projectParametersTuple ifAbsent: [^self]. project _ tuple fourth first. (project notNil and: [project world notNil]) ifTrue: [ ^evt hand attachMorph: (ProjectViewMorph on: project). ]. url _ tuple third. url isEmptyOrNil ifTrue: [^self]. pvm _ ProjectViewMorph new. pvm project: (DiskProxy global: #Project selector: #namedUrl: args: {url}); lastProjectThumbnail: tuple second. evt hand attachMorph: pvm. ! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/15/2000 18:04'! mouseUp: evt in: aMorph | tuple project url | (aMorph boundsInWorld containsPoint: evt cursorPoint) ifFalse: [^self]. tuple _ aMorph valueOfProperty: #projectParametersTuple ifAbsent: [^1 beep]. project _ tuple fourth first. (project notNil and: [project world notNil]) ifTrue: [self closeMyFlapIfAny. ^project enter]. url _ tuple third. url isEmptyOrNil ifTrue: [^1 beep]. self closeMyFlapIfAny. ProjectLoading thumbnailFromUrl: url. "--- newTuple _ { aProject name. aProject thumbnail. aProject url. WeakArray with: aProject. }. ---"! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/13/2000 07:46'! rebuild | history r1 | changeCounter _ ProjectHistory changeCounter. history _ ProjectHistory currentHistory mostRecentCopy. self removeAllMorphs. self rubberBandCells: false. "enable growing" r1 _ self addARow: { self inAColumn: { StringMorph new contents: 'Jump...'; lock. }. }. r1 on: #mouseUp send: #jumpToProject to: self. history do: [ :each | ( self addARow: { (self inAColumn: { StretchyImageMorph new form: each second; minWidth: 35; minHeight: 35; lock }) vResizing: #spaceFill. self inAColumn: { StringMorph new contents: each first; lock. "StringMorph new contents: each third; lock." }. } ) color: Color paleYellow; borderWidth: 1; borderColor: #raised; vResizing: #spaceFill; on: #mouseUp send: #mouseUp:in: to: self; on: #mouseDown send: #mouseDown:in: to: self; on: #mouseMove send: #mouseMove:in: to: self; on: #mouseLeave send: #mouseLeave:in: to: self; setProperty: #projectParametersTuple toValue: each; setBalloonText: (each third isEmptyOrNil ifTrue: ['not saved'] ifFalse: [each third]) ]. "--- newTuple _ { aProject name. aProject thumbnail. aProject url. WeakArray with: aProject. }. ---"! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/10/2000 23:07'! step changeCounter = ProjectHistory changeCounter ifTrue: [^self]. self rebuild. ! ! EToyProjectDetailsMorph subclass: #EToyProjectQueryMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyProjectQueryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:16'! doOK actionBlock value: self copyOutDetails. self delete.! ! !EToyProjectQueryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:17'! fillInDetails "leave them blank for now"! ! !EToyProjectQueryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:18'! project: ignored actionBlock: aBlock actionBlock _ aBlock. projectDetails _ Dictionary new.! ! !EToyProjectQueryMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:19'! rebuild self removeAllMorphs. self addARow: { self lockedString: 'Enter things to search for'. }. self addARow: { self lockedString: 'Name:'. self inAColumnForText: {self fieldForProjectName} }. self fieldToDetailsMappings do: [ :each | self addARow: { self lockedString: each third. self inAColumnForText: {(self genericTextFieldNamed: each first) height: each fourth} }. ]. self addARow: { self okButton. self cancelButton. }. self fillInDetails.! ! !EToyProjectQueryMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:39'! setColorsAndBorder color _ Color r: 0.545 g: 0.47 b: 0.621. borderWidth _ 8. borderColor _ color darker. self layoutInset: 4. self useRoundedCorners. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectQueryMorph class instanceVariableNames: ''! !EToyProjectQueryMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:34'! test1: aProject "EToyProjectQueryMorph test1: nil" | criteria clean | (self basicNew) project: aProject actionBlock: [ :x | criteria _ OrderedCollection new. x keysAndValuesDo: [ :k :v | (clean _ v withBlanksTrimmed) isEmpty ifFalse: [ criteria add: k,': *',clean,'*' ]. ]. SuperSwikiServer testOnlySuperSwiki queryProjectsAndShow: criteria ]; initialize; openCenteredInWorld! ! EToyGenericDialogMorph subclass: #EToyProjectRenamerMorph instanceVariableNames: 'actionBlock theProject ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Experimental'! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:01'! buttonColor ^color darker! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:06'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString font: self myFont; color: aColor; actionSelector: aSymbol; setBalloonText: helpString. col _ (self inAColumn: {f}) hResizing: #spaceFill. ^col! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:05'! cancelButton ^self buttonNamed: 'Cancel' action: #doCancel color: self buttonColor help: 'Cancel this Publish operation.'! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:06'! doCancel self delete.! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:55'! doOK self validateTheProjectName ifFalse: [^self]. self delete. actionBlock value: (namedFields at: 'projectname') contents string.! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/15/2000 12:02'! fieldForProjectName | tm | tm _ self genericTextFieldNamed: 'projectname'. tm setBalloonText: 'Pick a name 24 characters or less and avoid the following characters: : < > | / \ ? * " .'. ^tm ! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/30/2000 15:29'! initialize super initialize. self vResizing: #shrinkWrap. self hResizing: #shrinkWrap. self setColorsAndBorder. self rebuild. ! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:04'! myFont ^(TextStyle named: #ComicBold) fontOfSize: 16! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:21'! okButton ^self buttonNamed: 'OK' action: #doOK color: self buttonColor help: 'Change my name and continue publishing.'! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:55'! project: aProject actionBlock: aBlock theProject _ aProject. actionBlock _ aBlock. (namedFields at: 'projectname') contentsWrapped: theProject name.! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 12:36'! rebuild self removeAllMorphs. self addARow: { self lockedString: 'Please name this project'. }. self addARow: { self inAColumnForText: {self fieldForProjectName} }. self addARow: { self okButton. self cancelButton. }. ! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:39'! setColorsAndBorder color _ Color paleYellow. borderWidth _ 8. borderColor _ color darker. self layoutInset: 4. self useRoundedCorners. ! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 13:55'! validateTheProjectName | proposed | proposed _ (namedFields at: 'projectname') contents string. proposed size = 0 ifTrue: [ self inform: 'I do need a name for the project'. ^false ]. proposed size > 24 ifTrue: [ self inform: 'Please make the name 24 characters or less'. ^false ]. (Project isBadNameForStoring: proposed) ifTrue: [ self inform: 'Please remove any funny characters from the name'. ^false ]. proposed = theProject name ifTrue: [^true]. (ChangeSorter changeSetNamed: proposed) ifNotNil: [ Utilities inform: 'Sorry that name is already used'. ^false ]. ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectRenamerMorph class instanceVariableNames: ''! !EToyProjectRenamerMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 10/18/2000 12:35'! validate: aProject andDo: aBlock (self new) project: aProject actionBlock: aBlock; openCenteredInWorld! ! EToyChatOrBadgeMorph subclass: #EToySenderMorph instanceVariableNames: 'userPicture ' classVariableNames: 'DEBUG ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToySenderMorph commentStamp: '' prior: 0! EToySenderMorph new userName: 'Bob Arning' userPicture: nil userEmail: 'arning@charm.net' userIPAddress: '1.2.3.4'; position: 200@200; open " EToySenderMorph represents another person to whom you wish to send things. Drop a morph on an EToySenderMorph and a copy of that morph is sent to the person represented. Currently only peer-to-peer communications are supported, but other options are planned. "! ]style[(149 1 262)cblue;f2,f2,f1! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 19:58'! aboutToBeGrabbedBy: aHand | aFridge | super aboutToBeGrabbedBy: aHand. aFridge _ self ownerThatIsA: EToyFridgeMorph. aFridge ifNil: [^self]. aFridge noteRemovalOf: self.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'mir 10/12/2000 14:54'! acceptDroppingMorph: morphToDrop event: evt | myCopy outData | (morphToDrop isKindOf: NewHandleMorph) ifTrue: [ "don't send these" ^morphToDrop rejectDropMorphEvent: evt. ]. self eToyRejectDropMorph: morphToDrop event: evt. "we don't really want it" myCopy _ morphToDrop veryDeepCopy. "gradient fills require doing this second" myCopy setProperty: #positionInOriginatingWorld toValue: morphToDrop position. self stopFlashing. outData _ myCopy eToyStreamedRepresentationNotifying: self. self resetIndicator: #working. self transmitStreamedObject: outData to: self ipAddress. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/1/2000 14:15'! checkOnAFriend | gateKeeperEntry caption choices resp | gateKeeperEntry _ EToyGateKeeperMorph entryForIPAddress: self ipAddress. caption _ 'Last name: ',gateKeeperEntry latestUserName, '\Last message in: ',gateKeeperEntry lastIncomingMessageTimeString, '\Last status check at: ',gateKeeperEntry lastTimeCheckedString, '\Last status in: ',gateKeeperEntry statusReplyReceivedString. choices _ 'Get his status now\Send my status now' . resp _ (PopUpMenu labels: choices withCRs) startUpWithCaption: caption withCRs. resp = 1 ifTrue: [ gateKeeperEntry lastTimeChecked: Time totalSeconds. self sendStatusCheck. ]. resp = 2 ifTrue: [ self sendStatusReply. ]. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/10/2000 12:25'! currentBadgeVersion "enables on-the-fly updating of older morphs" ^10! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 12:30'! establishDropZone: aMorph self setProperty: #specialDropZone toValue: aMorph. aMorph on: #mouseEnterDragging send: #mouseEnteredDZ to: self; on: #mouseLeaveDragging send: #mouseLeftDZ to: self; on: #mouseLeave send: #mouseLeftDZ to: self. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 13:08'! fixOldVersion | uName uForm uEmail uIP | uName _ self userName. uForm _ userPicture ifNil: [ (self findDeepSubmorphThat: [ :x | (x isKindOf: ImageMorph) or: [x isKindOf: SketchMorph]] ifAbsent: [self halt]) form. ]. uEmail _ (fields at: #emailAddress) contents. uIP _ self ipAddress. self userName: uName userPicture: (uForm scaledToSize: 61@53) userEmail: uEmail userIPAddress: uIP ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:54'! initialize Socket initializeNetwork. "we may want our IP address" Preferences defaultAuthorName. "seems like a good place to insure we have a name" super initialize. self listDirection: #topToBottom. color _ Color lightMagenta. self layoutInset: 4. borderColor _ Color magenta. borderWidth _ 4. self setProperty: #normalBorderColor toValue: borderColor. self setProperty: #flashingColors toValue: {Color red. Color yellow}. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/31/2000 18:31'! installModelIn: myWorld "if we get this far and nothing exists, make it up" userPicture ifNotNil: [^self]. self userName: Preferences defaultAuthorName userPicture: nil userEmail: 'who@where.net' userIPAddress: NetNameResolver localAddressString ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 15:58'! ipAddress ^(fields at: #ipAddress) contents! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/17/2000 16:20'! ipAddress: aString ^(fields at: #ipAddress) contents: aString! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 14:23'! killExistingChat | oldOne | self rubberBandCells: true. "disable growing" (oldOne _ self valueOfProperty: #embeddedChatHolder) ifNotNil: [ oldOne delete. self removeProperty: #embeddedChatHolder ]. (oldOne _ self valueOfProperty: #embeddedAudioChatHolder) ifNotNil: [ oldOne delete. self removeProperty: #embeddedAudioChatHolder ]. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 18:05'! mouseEnteredDZ | dz | dz _ self valueOfProperty: #specialDropZone ifAbsent: [^self]. dz color: Color blue.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 18:05'! mouseLeftDZ | dz | dz _ self valueOfProperty: #specialDropZone ifAbsent: [^self]. dz color: Color transparent.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 11:52'! sendStatusCheck | null | null _ String with: 0 asCharacter. EToyPeerToPeer new sendSomeData: { EToyIncomingMessage typeStatusRequest,null. Preferences defaultAuthorName,null. } to: self ipAddress for: self. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 11:53'! sendStatusReply | null | null _ String with: 0 asCharacter. EToyPeerToPeer new sendSomeData: { EToyIncomingMessage typeStatusReply,null. Preferences defaultAuthorName,null. ((EToyGateKeeperMorph acceptableTypesFor: self ipAddress) eToyStreamedRepresentationNotifying: self). } to: self ipAddress for: self. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 14:22'! startAudioChat self startAudioChat: true ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/11/2000 12:32'! startAudioChat: toggleMode | chat r | (self valueOfProperty: #embeddedAudioChatHolder) ifNotNil: [ toggleMode ifFalse: [^self]. ^self killExistingChat ]. (self ownerThatIsA: EToyFridgeMorph) isNil ifTrue: [ chat _ AudioChatGUI new ipAddress: self ipAddress. chat removeConnectButton; "we already know the connectee" vResizing: #shrinkWrap; hResizing: #shrinkWrap; borderWidth: 2. r _ (self addARow: {chat}) vResizing: #shrinkWrap. self world startSteppingSubmorphsOf: chat. self setProperty: #embeddedAudioChatHolder toValue: r. self hResizing: #spaceFill; vResizing: #spaceFill. ] ifFalse: [ chat _ AudioChatGUI new ipAddress: self ipAddress. chat openInWorld: self world. ] ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 17:14'! startChat self startChat: true ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:10'! startChat: toggleMode | chat r | (self valueOfProperty: #embeddedChatHolder) ifNotNil: [ toggleMode ifFalse: [^self]. ^self killExistingChat ]. (EToyChatMorph doChatsInternalToBadge and: [(self ownerThatIsA: EToyFridgeMorph) isNil]) ifTrue: [ chat _ EToyChatMorph basicNew recipientForm: userPicture; initialize; setIPAddress: self ipAddress. chat vResizing: #spaceFill; hResizing: #spaceFill; borderWidth: 2; insetTheScrollbars. r _ (self addARow: {chat}) vResizing: #spaceFill. self rubberBandCells: false. "enable growing" self height: 350. "an estimated guess for allowing shrinking as well as growing" self world startSteppingSubmorphsOf: chat. self setProperty: #embeddedChatHolder toValue: r. ] ifFalse: [ chat _ EToyChatMorph chatWindowForIP: self ipAddress name: self userName picture: userPicture inWorld: self world. chat owner addMorphFront: chat. ] ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/15/2000 12:11'! startNebraskaClient | newMorph | [ [ newMorph _ NetworkTerminalMorph connectTo: self ipAddress. WorldState addDeferredUIMessage: [newMorph openInStyle: #scaled] fixTemps. ] on: Error do: [ :ex | WorldState addDeferredUIMessage: [ self inform: 'No connection to: '. self ipAddress,' (',ex printString,')' ] fixTemps ]. ] fork ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/24/2000 14:04'! startTelemorphic self world connectRemoteUserWithName: self userName picture: (userPicture ifNotNil: [userPicture scaledToSize: 16@20]) andIPAddress: self ipAddress ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/10/2000 12:24'! step (self valueOfProperty: #currentBadgeVersion) = self currentBadgeVersion ifFalse: [ self setProperty: #currentBadgeVersion toValue: self currentBadgeVersion. self fixOldVersion. Preferences defaultAuthorName. "seems like a good place to insure we have a name" ]. super step.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 11:12'! tellAFriend self world project tellAFriend: (fields at: #emailAddress) contents ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 16:15'! transmitStreamedObject: outData self transmitStreamedObject: outData to: self ipAddress ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 12:00'! transmittedObjectCategory ^EToyIncomingMessage typeMorph! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 13:07'! userName ^ (self findDeepSubmorphThat: [ :x | x isKindOf: StringMorph] ifAbsent: [self halt]) contents ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 15:04'! userName: aString userPicture: aFormOrNil userEmail: emailString userIPAddress: ipString | dropZoneRow | self setProperty: #currentBadgeVersion toValue: self currentBadgeVersion. userPicture _ aFormOrNil ifNil: [ (TextStyle default fontOfSize: 26) emphasized: 1; characterFormAt: $? ]. userPicture _ userPicture scaledToSize: 61@53. self killExistingChat. self removeAllMorphs. self useRoundedCorners. self addARow: { self inAColumn: {(StringMorph contents: aString) lock} }. dropZoneRow _ self addARow: { self inAColumn: {userPicture asMorph lock} }. self establishDropZone: dropZoneRow. self addARow: { self textEntryFieldNamed: #emailAddress with: emailString help: 'Email address for this person' }; addARow: { self textEntryFieldNamed: #ipAddress with: ipString help: 'IP address for this person' }; addARow: { self indicatorFieldNamed: #working color: Color blue help: 'working'. self indicatorFieldNamed: #communicating color: Color green help: 'sending'. self buttonNamed: 'C' action: #startChat color: Color paleBlue help: 'Open a written chat with this person'. self buttonNamed: 'T' action: #startTelemorphic color: Color paleYellow help: 'Start telemorphic with this person'. self buttonNamed: '!!' action: #tellAFriend color: Color paleGreen help: 'Tell this person about the current project'. self buttonNamed: '?' action: #checkOnAFriend color: Color lightBrown help: 'See if this person is available'. self buttonNamed: 'A' action: #startAudioChat color: Color yellow help: 'Open an audio chat with this person'. self buttonNamed: 'S' action: #startNebraskaClient color: Color white help: 'See this person''s world (if he allows that)'. }. ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/12/2000 16:06'! userPicture ^userPicture! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 18:02'! wantsDroppedMorph: aMorph event: evt | dz | dz _ self valueOfProperty: #specialDropZone ifAbsent: [^false]. (dz bounds containsPoint: (evt cursorPoint)) ifFalse: [^false]. ^true.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToySenderMorph class instanceVariableNames: ''! !EToySenderMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 12:52'! instanceForIP: ipAddress ^self allInstances detect: [ :x | x ipAddress = ipAddress ] ifNone: [nil] ! ! !EToySenderMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/16/2000 12:50'! instanceForIP: ipAddress inWorld: aWorld ^self allInstances detect: [ :x | x world == aWorld and: [x ipAddress = ipAddress] ] ifNone: [nil] ! ! !EToySenderMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/4/2000 15:42'! nameForIPAddress: ipString | senderMorphs | senderMorphs _ EToySenderMorph allInstances select: [ :x | x userName notNil and: [x ipAddress = ipString] ]. senderMorphs isEmpty ifTrue: [^nil]. ^senderMorphs first userName ! ! !EToySenderMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 14:47'! pictureForIPAddress: ipString | senderMorphs | senderMorphs _ EToySenderMorph allInstances select: [ :x | x userPicture notNil and: [x ipAddress = ipString] ]. senderMorphs isEmpty ifTrue: [^nil]. ^senderMorphs first userPicture ! ! StandardScriptingSystem subclass: #EToySystem instanceVariableNames: '' classVariableNames: 'EToyVersion EToyVersionDate ' poolDictionaries: '' category: 'Morphic-Experimental'! !EToySystem commentStamp: '' prior: 0! A global object holding onto properties and code of the overall E-toy system of the moment. Its code is entirely held on the class side; the class is never instantiated.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToySystem class instanceVariableNames: ''! !EToySystem class methodsFor: 'development support' stamp: 'tk 8/21/2000 12:59'! cleanupsForRelease "Miscellaneous space cleanups to do before a release." "EToySystem cleanupsForRelease" Socket deadServer: ''. "Don't reveal any specific server name" HandMorph initialize. "free cached ColorChart" PaintBoxMorph initialize. "forces Prototype to let go of extra things it might hold" Smalltalk removeKey: #AA ifAbsent: []. Smalltalk removeKey: #BB ifAbsent: []. Smalltalk removeKey: #CC ifAbsent: []. Smalltalk removeKey: #DD ifAbsent: []. Smalltalk removeKey: #Temp ifAbsent: []. ScriptingSystem reclaimSpace. Smalltalk cleanOutUndeclared. Smalltalk reclaimDependents. Smalltalk forgetDoIts. Smalltalk removeEmptyMessageCategories. Symbol rehash. ! ! !EToySystem class methodsFor: 'development support' stamp: 'di 9/14/1998 10:02'! loadJanForms "EToySystem loadJanForms" | aReferenceStream newFormDict | aReferenceStream _ ReferenceStream fileNamed: 'JanForms'. newFormDict _ aReferenceStream next. aReferenceStream close. newFormDict associationsDo: [:assoc | Smalltalk imageImports add: assoc]! ! !EToySystem class methodsFor: 'development support' stamp: 'sw 10/16/1998 09:31'! stripMethodsForExternalRelease "EToySystem stripMethodsForExternalRelease" Utilities stripMethods: self methodsToStripForExternalRelease messageCode: '2.3External'! ! !EToySystem class methodsFor: 'versioning' stamp: 'di 8/16/2000 12:29'! eToyVersion: aVersion date: dateStringOrDate "For calling from a fileIn" EToyVersion _ aVersion. EToyVersionDate _ dateStringOrDate asString. Smalltalk setVersion: EToyVersion , ' of ' , EToyVersionDate! ! !EToySystem class methodsFor: 'versioning' stamp: 'di 8/16/2000 12:28'! setVersion "EToySystem setVersion" | newName | newName _ FillInTheBlank request: ('Please name this EToy system version.\The old version is:\', EToyVersion, '\set on ', EToyVersionDate) withCRs initialAnswer: EToyVersion. newName size > 0 ifTrue: [self eToyVersion: newName date: Date today printString]! ! !EToySystem class methodsFor: 'versioning' stamp: 'sw 9/21/97 01:05'! version "EToySystem version" ^ EToyVersion! ! !EToySystem class methodsFor: 'versioning' stamp: 'sw 9/21/97 01:17'! versionDate "EToySystem versionDate" ^ EToyVersionDate! ! !EToySystem class methodsFor: 'loading' stamp: 'tk 10/31/97 12:57'! newEToysOn: serverList "Return a list of fully formed URLs of update files we do not yet have. Go to the listed servers and look at the file 'updates.list' for the names of the last N update files. We look backwards for the first one we have, and make the list from there." | doc list out | out _ OrderedCollection new. serverList do: [:server | doc _ HTTPSocket httpGet: server,'etoys.list2' "Just add to contents of file 'etoys.list2', don't change its name" accept: 'application/octet-stream'. "test here for server being up" doc class == RWBinaryOrTextStream ifTrue: [list _ doc reset; contents. "one file name per line" list _ Utilities extractThisVersion: list. ^ list collect: [:nn | server, nn]]. "Server was down, try next one"]. PopUpMenu notify: 'All EToy servers seem to be unavailable'. ^ out! ! !EToySystem class methodsFor: 'misc' stamp: 'sw 1/21/98 15:07'! fixComicCharacters "EToySystem fixComicCharacters" ((TextConstants at: #ComicBold) fontAt: 3) characterFormAt: $_ put: (Form extent: 9@16 depth: 1 fromArray: #( 0 0 0 134217728 402653184 805306368 2139095040 4278190080 2139095040 805306368 402653184 134217728 0 0 0 0) offset: 0@0). ((TextConstants at: #ComicBold) fontAt: 3) characterFormAt: $1 put: (Form extent: 5@16 depth: 1 fromArray: #( 0 0 0 0 1610612736 3758096384 3758096384 1610612736 1610612736 1610612736 1610612736 4026531840 4026531840 0 0 0) offset: 0@0). ((TextConstants at: #ComicBold) fontAt: 3) characterFormAt: $2 put: (Form extent: 6@16 depth: 1 fromArray: #( 0 0 0 0 1879048192 4160749568 2550136832 939524096 1879048192 3758096384 3221225472 4160749568 4160749568 0 0 0) offset: 0@0). ((TextConstants at: #ComicBold) fontAt: 3) characterFormAt: $4 put: (Form extent: 7@16 depth: 1 fromArray: #( 0 0 0 0 134217728 402653184 402653184 939524096 1476395008 4227858432 4227858432 402653184 402653184 0 0 0) offset: 0@0). ((TextConstants at: #ComicBold) fontAt: 3) characterFormAt: $j put: (Form extent: 4@16 depth: 1 fromArray: #( 0 0 0 0 1610612736 1610612736 0 1610612736 1610612736 1610612736 1610612736 1610612736 1610612736 1610612736 3758096384 3221225472) offset: 0@0). ! ! !EToySystem class methodsFor: 'external release' stamp: 'sw 10/16/1998 09:30'! methodsToStripForExternalRelease "Answer a list of triplets #(className, class/instance, methodName) of methods to be stripped in an external release." ^ #( (EToySystem class serverUrls) (EToySystem class prepareRelease) (EToySystem class previewEToysOn:) )! ! !EToySystem class methodsFor: 'stripped' stamp: 'di 1/15/1999 11:29'! prepareRelease self codeStrippedOut: '2.3External'! ! !EToySystem class methodsFor: 'stripped' stamp: 'di 1/15/1999 11:29'! previewEToysOn: arg1 self codeStrippedOut: '2.3External'! ! TextMorph subclass: #EToyTextNode instanceVariableNames: 'children firstDisplay ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Outliner'! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 01:12'! addChild: aTextNode children add: aTextNode. ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 10:05'! addNewChildAfter: anotherOrNilOrZero | where newNode | anotherOrNilOrZero == 0 ifTrue: [ newNode _ EToyTextNode newNode. children _ {newNode} asOrderedCollection,children. ^newNode ]. where _ children indexOf: anotherOrNilOrZero ifAbsent: [children size]. children add: (newNode _ EToyTextNode newNode) afterIndex: where. ^newNode ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 01:13'! children ^children ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 13:09'! clipToOwner: aBoolean aBoolean ifFalse: [^self setContainer: nil]. self setContainer: (SimplerTextContainer new for: self minWidth: textStyle lineGrid*2)! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 19:52'! firstDisplay ^firstDisplay ifNil: [false]! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 10:33'! firstDisplayedOnLevel: level firstDisplay _ false. text addAttribute: (TextFontChange fontNumber: ((5 - level) max: 1)). ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 17:09'! initialize | newStyle | super initialize. firstDisplay _ true. children _ OrderedCollection new. (newStyle _ TextStyle named: #Palatino) ifNotNil: [ textStyle _ newStyle copy defaultFontIndex: 2 ]. ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 15:08'! keyStroke: evt (owner notNil and: [owner keyStroke: evt]) ifTrue: [^self]. ^super keyStroke: evt.! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 10:07'! keyboardFocusChange: aBoolean super keyboardFocusChange: aBoolean. aBoolean ifTrue: [owner takeFocus]. ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:43'! removeChild: aTextNode children remove: aTextNode ifAbsent: []. ! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 09:53'! showInOpenedState | answer | answer _ self valueOfProperty: #showInOpenedState ifAbsent: [false]. self removeProperty: #showInOpenedState. ^answer! ! !EToyTextNode methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 09:55'! withAllChildrenDo: aBlock aBlock value: self. children do: [ :each | each withAllChildrenDo: aBlock].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyTextNode class instanceVariableNames: ''! !EToyTextNode class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 14:36'! includeInNewMorphMenu ^ false! ! !EToyTextNode class methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 11:11'! newNode ^self new contents: ( Text string: 'new item' attribute: (TextFontChange fontNumber: 2) )! ! ListItemWrapper subclass: #EToyTextNodeWrapper instanceVariableNames: 'parentWrapper ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Outliner'! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 17:25'! addNewChildAfter: anotherOrNil item addNewChildAfter: anotherOrNil. ! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:50'! addSibling parentWrapper ifNil: [^1 beep]. parentWrapper addNewChildAfter: item.! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 00:56'! asString ^item contents! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 17:08'! contents ^item children collect: [ :each | EToyTextNodeWrapper with: each model: model parent: self ]. ! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 16:42'! delete parentWrapper ifNil: [^1 beep]. parentWrapper withoutListWrapper removeChild: item withoutListWrapper. ! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 00:43'! hasContents ^true! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 11:51'! parentWrapper: anotherWrapper parentWrapper _ anotherWrapper ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyTextNodeWrapper class instanceVariableNames: ''! !EToyTextNodeWrapper class methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 11:52'! with: anObject model: aModel parent: anotherWrapper ^self new setItem: anObject model: aModel; parentWrapper: anotherWrapper! ! Vocabulary subclass: #EToyVocabulary instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Protocols'! !EToyVocabulary commentStamp: '' prior: 0! EToyVocabulary - a vocabulary mirroring the capabilities available to end users in Squeak's old 1997-2000 etoy prototype.! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 1/12/2001 01:17'! addGetterAndSetterInterfacesFromOldSlotSpec: aCommandSpec "Create, given an old etoy-style command spec, appropriate MethodInterfaces, and store those interfaces in my method-interface dictionary" | aMethodInterface aSelector | aMethodInterface _ MethodInterface new initializeFromEToySlotSpec: aCommandSpec. methodInterfaces at: aCommandSpec seventh put: aMethodInterface. (aCommandSpec size >= 9 and: [(#(unused missing) includes: aCommandSpec ninth) not]) ifTrue: [aMethodInterface _ MethodInterface new initializeFor: (aSelector _ aCommandSpec ninth). methodInterfaces at: aSelector put: aMethodInterface] " 1 #slot -- indicates that is a slot specification rather than a method specification 2 slot name 3 help message 4 type 5 #readOnly or #readWrite (if #readWrite, items 8 and 9 should be present & meaningful) 6 7 getter selector 8 9 setter selector" ! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 12/12/2000 06:06'! encompassesAPriori: aClass "Answer whether an object, by its very nature, is one that the receiver embraces" ^ aClass isKindOf: Player class! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 12/18/2000 14:33'! includesSelector: aSelector forInstance: anInstance ofClass: aTargetClass limitClass: mostGenericClass "Answer whether the vocabulary includes the given selector for the given class (and instance, if provided), only considering method implementations in mostGenericClass and lower" | classToUse aClass theKeys | (aTargetClass isUniClass and: [(aTargetClass namedTileScriptSelectors includes: aSelector) or: [(((theKeys _ aTargetClass slotInfo keys collect: [:anInstVarName | Utilities getterSelectorFor: anInstVarName])) includes: aSelector) or: [(theKeys collect: [:anInstVarName | Utilities setterSelectorFor: anInstVarName]) includes: aSelector]]]) ifTrue: [^ true]. (methodInterfaces includesKey: aSelector) ifFalse: [^ false]. classToUse _ self classToUseFromInstance: anInstance ofClass: aTargetClass. ^ (aClass _ classToUse whichClassIncludesSelector: aSelector) ifNil: [false] ifNotNil: [aClass includesBehavior: mostGenericClass] ! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 1/26/2001 23:03'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" | classes aMethodCategory selectors categorySymbols | super initialize. self vocabularyName: #eToy. self documentation: '"EToy" is a vocabulary that provides the equivalent of the 1997-2000 etoy prototype'. categorySymbols _ Set new. classes _ Smalltalk allImplementorsOf: #additionsToViewerCategories. classes do: [:anItem | MessageSet parse: anItem toClassAndSelector: [:aClass :aSelector | aClass]. categorySymbols addAll: aClass soleInstance basicNew categoriesForViewer]. categorySymbols asOrderedCollection do: [:aCategorySymbol | aMethodCategory _ ElementCategory new categoryName: aCategorySymbol. classes _ (Smalltalk allImplementorsOf: #additionsToViewerCategories) collect: [:anItem | MessageSet parse: anItem toClassAndSelector: [:aMetaClass :aSelector | aMetaClass soleInstance]]. selectors _ Set new. classes do: [:aClass | (aClass additionsToViewerCategory: aCategorySymbol) do: [:anElement | anElement first == #command ifTrue: [selectors add: (aSelector _ anElement second). (methodInterfaces includesKey: aSelector) ifFalse: [methodInterfaces at: aSelector put: (MethodInterface new initializeFromEToyCommandSpec: anElement category: aCategorySymbol)]] ifFalse: "#slot format" [selectors add: (aSelector _ anElement seventh). "the getter" selectors add: (anElement at: 9) "the setter". (methodInterfaces includesKey: aSelector) ifFalse: [self addGetterAndSetterInterfacesFromOldSlotSpec: anElement]]]]. (selectors copyWithout: #unused) asSortedArray do: [:aSelector | aMethodCategory elementAt: aSelector put: (methodInterfaces at: aSelector)]. self addCategory: aMethodCategory]. #(scripts 'instance variables') do: [:sym | self addCategoryNamed: sym]. self setCategoryDocumentationStrings! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 12/18/2000 09:57'! setCategoryDocumentationStrings "Initialize the documentation strings associated with the old etoy categories" #( (basic 'vitu muhimu') ('color & border' 'rangi na kadhalika') (geometry 'sijui neno hilo') (motion 'kusogea') ('pen use' 'utumizi wa kalamu ya wino') (tests 'majaribio') (miscellaneous 'mbali mbali') (slider 'oh yeah') (scripts 'methods added by this uniclass') ('instance variables' 'instance variables added by this uniclass')) do: [:aPair | (self categoryAt: aPair first asSymbol) documentation: aPair second].! ! !EToyVocabulary methodsFor: 'category list' stamp: 'sw 12/18/2000 15:34'! categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass "Answer the category list for the given object, considering only code implemented in aClass and lower" ^ (anObject isKindOf: Player) ifTrue: [(mostGenericClass == aClass) ifFalse: [anObject categories] ifTrue: [#(scripts #'instance variables')]] ifFalse: [super categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass]! ! !EToyVocabulary methodsFor: 'method list' stamp: 'sw 1/30/2001 16:31'! allMethodsInCategory: categoryName forInstance: anObject ofClass: aClass "Answer a list of all methods in the etoy interface which are in the given category, on behalf of anObject, or if it is nil, aClass" | aCategory unfiltered suitableSelectors isAll | categoryName ifNil: [^ OrderedCollection new]. aClass isUniClass ifTrue: [categoryName == #scripts ifTrue: [^ aClass namedTileScriptSelectors]. categoryName == #'instance variables' ifTrue: [^ aClass slotInfo keys asSortedArray collect: [:anInstVarName | Utilities getterSelectorFor: anInstVarName]]]. unfiltered _ (isAll _ categoryName = self allCategoryName) ifTrue: [methodInterfaces collect: [:anInterface | anInterface selector]] ifFalse: [aCategory _ categories detect: [:cat | cat categoryName == categoryName asSymbol] ifNone: [^ OrderedCollection new]. aCategory elementsInOrder collect: [:anElement | anElement selector]]. (anObject isKindOf: Player) ifTrue: [suitableSelectors _ anObject costume selectorsForViewer. unfiltered _ unfiltered select: [:aSelector | suitableSelectors includes: aSelector]]. (isAll and: [aClass isUniClass]) ifTrue: [unfiltered addAll: aClass namedTileScriptSelectors. unfiltered addAll: (aClass slotInfo keys asSortedArray collect: [:anInstVarName | Utilities getterSelectorFor: anInstVarName])]. ^ (unfiltered copyWithoutAll: #(dummy unused)) asSortedArray! ! ObjectWithDocumentation subclass: #ElementCategory instanceVariableNames: 'categoryName keysInOrder elementDictionary ' classVariableNames: '' poolDictionaries: '' category: 'System-Protocols'! !ElementCategory commentStamp: '' prior: 0! ElementCategory Contains a list of elements that affords keyed access but also has an inherent order. Add items to the category by sending it elementAt:put:. Obtain the elements in order by sending #elementsInOrder Obtain the value of an element at a given key by sending #elementAt:! !ElementCategory methodsFor: 'elements' stamp: 'sw 12/1/2000 22:46'! elementAt: aKey "Answer the element at the given key" ^ elementDictionary at: aKey ifAbsent: [nil]! ! !ElementCategory methodsFor: 'elements' stamp: 'sw 1/26/2001 22:54'! elementAt: sym put: element "Add symbol at the end of my sorted list (unless it is already present), and put the element in the dictionary" (keysInOrder includes: sym) ifFalse: [keysInOrder add: sym]. ^ elementDictionary at: sym put: element! ! !ElementCategory methodsFor: 'elements' stamp: 'sw 12/1/2000 22:47'! elementsInOrder "Answer the elements in order" ^ keysInOrder collect: [:aKey | elementDictionary at: aKey]! ! !ElementCategory methodsFor: 'elements' stamp: 'sw 1/26/2001 23:00'! placeKey: key1 afterKey: key2 "Place the first key after the second one in my keysInOrder ordering" keysInOrder remove: key1. keysInOrder add: key1 after: key2! ! !ElementCategory methodsFor: 'elements' stamp: 'sw 1/26/2001 23:00'! placeKey: key1 beforeKey: key2 "Place the first key before the second one in my keysInOrder ordering" keysInOrder remove: key1. keysInOrder add: key1 before: key2! ! !ElementCategory methodsFor: 'copying' stamp: 'sw 12/1/2000 22:45'! copy "Answer a copy of the receiver" ^ super copy copyFrom: self! ! !ElementCategory methodsFor: 'copying' stamp: 'sw 12/1/2000 22:46'! copyFrom: donor "Copy the receiver's contents from the donor" keysInOrder _ donor keysInOrder. elementDictionary _ donor copyOfElementDictionary! ! !ElementCategory methodsFor: 'copying' stamp: 'sw 12/1/2000 22:46'! copyOfElementDictionary "Answer a copy of the element dictionary" ^ elementDictionary copy! ! !ElementCategory methodsFor: 'keys' stamp: 'sw 12/11/2000 15:36'! includesKey: aKey "Answer whether the receiver's dictionary holds the given key" ^ elementDictionary includesKey: aKey! ! !ElementCategory methodsFor: 'keys' stamp: 'sw 12/1/2000 22:47'! keysInOrder "Answer the keys in their sorted order" ^ keysInOrder copy! ! !ElementCategory methodsFor: 'category name' stamp: 'sw 1/26/2001 22:45'! categoryName "Answer the formal name of the category" ^ categoryName! ! !ElementCategory methodsFor: 'category name' stamp: 'sw 1/26/2001 22:46'! categoryName: aName "Set the category name" categoryName _ aName! ! !ElementCategory methodsFor: 'initialization' stamp: 'sw 12/1/2000 22:01'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" super initialize. keysInOrder _ OrderedCollection new. elementDictionary _ IdentityDictionary new! ! !ElementCategory methodsFor: 'printing' stamp: 'sw 1/26/2001 22:47'! printOn: aStream "Append to the argument, aStream, a sequence of characters that identifies the receiver." super printOn: aStream. categoryName ifNotNil: [aStream nextPutAll: ' named ', categoryName asString]! ! Object subclass: #EllipseMidpointTracer instanceVariableNames: 'rect x y a b aSquared bSquared d1 d2 inFirstRegion ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !EllipseMidpointTracer methodsFor: 'initialize' stamp: 'ar 6/28/1999 15:33'! on: aRectangle rect _ aRectangle. a _ rect width // 2. b _ rect height // 2. x _ 0. y _ b. aSquared _ a * a. bSquared _ b * b. d1 _ bSquared - (aSquared * b) + (0.25 * aSquared). d2 _ nil. inFirstRegion _ true.! ! !EllipseMidpointTracer methodsFor: 'computing' stamp: 'ar 6/28/1999 15:35'! stepInY "Step to the next y value" inFirstRegion ifTrue:[ "In the upper region we must step until we reach the next y value" [(aSquared * (y-0.5)) > (bSquared * (x+1))] whileTrue:[ d1 < 0.0 ifTrue:[d1 _ d1 + (bSquared * (2*x+3)). x _ x + 1] ifFalse:[d1 _ d1 + (bSquared * (2*x+3)) + (aSquared * (-2*y+2)). y _ y - 1. ^x _ x + 1]]. "Stepping into second region" d2 _ (bSquared * (x + 0.5) squared) + (aSquared * (y-1) squared) - (aSquared * bSquared). inFirstRegion _ false. ]. "In the lower region each step is a y-step" d2 < 0.0 ifTrue:[d2 _ d2 + (bSquared * (2*x+2)) + (aSquared * (-2*y+3)). x _ x + 1] ifFalse:[d2 _ d2 + (aSquared * (-2*y+3))]. y _ y - 1. ^x! ! BorderedMorph subclass: #EllipseMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !EllipseMorph methodsFor: 'as yet unclassified' stamp: 'di 6/24/1998 14:27'! areasRemainingToFill: aRectangle "Could be improved by quick check of inner rectangle" ^ Array with: aRectangle! ! !EllipseMorph methodsFor: 'as yet unclassified' stamp: 'di 11/14/97 13:50'! containsPoint: aPoint | radius other delta xOverY | (bounds containsPoint: aPoint) ifFalse: [^ false]. "quick elimination" (bounds width = 1 or: [bounds height = 1]) ifTrue: [^ true]. "Degenerate case -- code below fails by a bit" radius _ bounds height asFloat / 2. other _ bounds width asFloat / 2. delta _ aPoint - bounds topLeft - (other@radius). xOverY _ bounds width asFloat / bounds height asFloat. ^ (delta x asFloat / xOverY) squared + delta y squared <= radius squared! ! !EllipseMorph methodsFor: 'as yet unclassified' stamp: 'di 6/20/97 11:29'! doesBevels ^ false! ! !EllipseMorph methodsFor: 'as yet unclassified' stamp: 'ar 6/18/1999 08:52'! drawOn: aCanvas aCanvas fillOval: bounds fillStyle: self fillStyle borderWidth: borderWidth borderColor: borderColor! ! !EllipseMorph methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:14'! initialize super initialize. borderColor _ Color black. borderWidth _ 1. ! ! !EllipseMorph methodsFor: 'visual properties' stamp: 'ar 6/25/1999 11:14'! canHaveFillStyles "Return true if the receiver can have general fill styles; not just colors. This method is for gradually converting old morphs." ^true! ! !EllipseMorph methodsFor: 'visual properties' stamp: 'sw 11/24/1999 14:59'! couldHaveRoundedCorners ^ false! ! !EllipseMorph methodsFor: 'visual properties' stamp: 'ar 6/25/1999 11:14'! defaultColor "Return the default fill style for the receiver" ^Color yellow! ! ServerAction subclass: #EmbeddedServerAction instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !EmbeddedServerAction commentStamp: '' prior: 0! EmbeddedServerAction works like a normal ServerAction, except that it interprets any embedded Smalltalk code (within tags, stolen from Microsoft's notation for a similar capability) and replaces the embedded code with the value returned from the execution of the code. ! !EmbeddedServerAction methodsFor: 'URL processing' stamp: 'mjg 10/5/1998 16:44'! replyTo: pieces from: request | theLast | (StandardFileStream isAFileNamed: pieces) ifTrue: [theLast _ request message last asLowercase. theLast = 'gif' ifTrue: [^ self process: request MIMEtype: 'image/gif']. theLast = 'jpeg' ifTrue: [^ self process: request MIMEtype: 'image/jpeg']. theLast = 'jpg' ifTrue: [^ self process: request MIMEtype: 'image/jpeg']. theLast = 'jpe' ifTrue: [^ self process: request MIMEtype: 'image/jpeg']. request reply: PWS success; reply: PWS contentHTML , PWS crlf. request reply: (HTMLformatter evalEmbedded: (FileStream fileNamed: pieces) contentsOfEntireFile with: request)] ifFalse: [request error: PWS notFound]! ! AlignmentMorph subclass: #EmbeddedWorldBorderMorph instanceVariableNames: 'heights minWidth minHeight ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:16'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. self worldIEnclose addScalingMenuItems: menu hand: aHandMorph ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:08'! drawOn: aCanvas super drawOn: aCanvas. self boxesAndColorsAndSelectors do: [ :each | aCanvas fillRectangle: each first fillStyle: each second ]. ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/26/2000 19:10'! extent: aPoint bounds extent = aPoint ifFalse: [ self changed. bounds _ bounds topLeft extent: aPoint. self myWorldChanged. ]. ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:16'! goAppView self worldIEnclose showApplicationView ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:17'! goFactoryView self worldIEnclose showFactoryView ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:18'! goFullView self worldIEnclose showFullView ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:18'! goNormalProjectEntry | w | w _ self worldIEnclose. self delete. w project enter. ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:10'! handlesMouseDown: evt self boxesAndColorsAndSelectors do: [ :each | (each first containsPoint: evt cursorPoint) ifTrue: [^true] ]. ^false ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:26'! initialize super initialize. self setBalloonText: 'This is the frame of an embedded project. Click on the colored boxes: blue - expand or reduce yellow - app view red - factory view cyan - full view white - enter the project completely'! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/26/2000 18:45'! minHeight: anInteger minHeight _ anInteger! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/26/2000 18:46'! minWidth: anInteger minWidth _ anInteger! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 6/27/2000 19:23'! morphicLayerNumber "helpful for insuring some morphs always appear in front of or behind others. smaller numbers are in front" ^20 "Embedded worlds come in front of other worlds' Project navigation morphs"! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:13'! mouseDown: evt self boxesAndColorsAndSelectors do: [ :each | (each first containsPoint: evt cursorPoint) ifTrue: [ ^self perform: each third ]. ]. ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/2/2000 13:31'! myTransformation ^submorphs detect: [ :x | x isKindOf: TransformationMorph] ifNone: [nil] ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 12/14/2000 10:48'! myWorldChanged | trans | trans _ self myTransformation. self changed. self layoutChanged. trans ifNotNil:[ trans extentFromParent: self innerBounds extent. bounds _ bounds topLeft extent: trans extent + (borderWidth * 2). ]. self changed. ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 10:12'! toggleZoom self bounds: ( bounds area > (Display boundingBox area * 0.9) ifTrue: [ Display extent // 4 extent: Display extent // 2. ] ifFalse: [ Display boundingBox ] ) ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'RAA 11/2/2000 13:31'! worldIEnclose ^self myTransformation firstSubmorph "quick hack since this is the only usage pattern at the moment" ! ! !EmbeddedWorldBorderMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/8/2000 22:42'! xxxfixLayout | trans | trans _ self myTransformation. trans ifNil:[^super fixLayout]. trans bounds: self innerBounds. ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:04'! appViewBoxArea ^self genericBoxArea: 1 ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:12'! boxesAndColorsAndSelectors ^{ {self zoomBoxArea. Color blue. #toggleZoom}. {self appViewBoxArea. Color yellow. #goAppView}. {self factoryViewBoxArea. Color red. #goFactoryView}. {self fullViewBoxArea. Color cyan. #goFullView}. {self normalEntryBoxArea. Color white. #goNormalProjectEntry}. }! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:04'! factoryViewBoxArea ^self genericBoxArea: 2 ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:04'! fullViewBoxArea ^self genericBoxArea: 3 ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:03'! genericBoxArea: countDownFromTop ^self innerBounds right @ (self top + (countDownFromTop * 2 * borderWidth)) extent: borderWidth @ borderWidth ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:04'! normalEntryBoxArea ^self genericBoxArea: 4 ! ! !EmbeddedWorldBorderMorph methodsFor: 'boxes' stamp: 'RAA 7/13/2000 10:03'! zoomBoxArea ^self genericBoxArea: 0 ! ! SelectionMenu subclass: #EmphasizedMenu instanceVariableNames: 'emphases ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Menus'! !EmphasizedMenu commentStamp: '' prior: 0! A selection menu in which individual selections are allowed to have different emphases. Emphases allowed are: bold, italic, struckThrough, and plain. Provide an emphasis array, with one element per selection, to use. Refer to the class method #example.! !EmphasizedMenu methodsFor: 'display'! startUpWithCaption: captionOrNil self setEmphasis. ^ super startUpWithCaption: captionOrNil! ! !EmphasizedMenu methodsFor: 'emphasis'! emphases: emphasisArray emphases _ emphasisArray! ! !EmphasizedMenu methodsFor: 'emphasis' stamp: 'di 4/13/1999 16:27'! onlyBoldItem: itemNumber "Set up emphasis such that all items are plain except for the given item number. " emphases _ (Array new: selections size) atAllPut: #plain. emphases at: itemNumber put: #bold! ! !EmphasizedMenu methodsFor: 'private' stamp: 'sw 4/5/1999 13:59'! setEmphasis "Set up the receiver to reflect the emphases in the emphases array. " | selStart selEnd currEmphasis | labelString _ labelString asText. emphases isEmptyOrNil ifTrue: [^ self]. selStart _ 1. 1 to: selections size do: [:line | selEnd _ selStart + (selections at: line) size - 1. ((currEmphasis _ emphases at: line) size > 0 and: [currEmphasis ~~ #plain]) ifTrue: [labelString addAttribute: (TextEmphasis perform: currEmphasis) from: selStart to: selEnd]. selStart _ selEnd + 2]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EmphasizedMenu class instanceVariableNames: ''! !EmphasizedMenu class methodsFor: 'instance creation' stamp: 'sw 12/23/96'! selectionAndEmphasisPairs: interleavedList "An alternative form of call. " | selList emphList | selList _ OrderedCollection new. emphList _ OrderedCollection new. interleavedList pairsDo: [:aSel :anEmph | selList add: aSel. emphList add: anEmph]. ^ self selections:selList emphases: emphList! ! !EmphasizedMenu class methodsFor: 'instance creation' stamp: 'sma 5/28/2000 16:14'! selections: selList emphases: emphList "Answer an instance of the receiver with the given selections and emphases." ^ (self selections: selList) emphases: emphList "Example: (EmphasizedMenu selections: #('how' 'well' 'does' 'this' 'work?') emphases: #(bold plain italic struckOut plain)) startUp"! ! !EmphasizedMenu class methodsFor: 'examples' stamp: 'sma 5/28/2000 16:16'! example1 "EmphasizedMenu example1" ^ (self selections: #('how' 'well' 'does' 'this' 'work?' ) emphases: #(#bold #plain #italic #struckOut #plain )) startUpWithCaption: 'A Menu with Emphases'! ! !EmphasizedMenu class methodsFor: 'examples' stamp: 'sw 9/11/97 16:14'! example2 "EmphasizedMenu example2" | aMenu | aMenu _ EmphasizedMenu selections: #('One' 'Two' 'Three' 'Four'). aMenu onlyBoldItem: 3. ^ aMenu startUpWithCaption: 'Only the Bold'! ! !EmphasizedMenu class methodsFor: 'examples' stamp: 'sma 5/28/2000 16:17'! example3 "EmphasizedMenu example3" ^ (self selectionAndEmphasisPairs: #('how' #bold 'well' #plain 'does' #italic 'this' #struckOut 'work' #plain)) startUpWithCaption: 'A Menu with Emphases'! ! ParseNode subclass: #Encoder instanceVariableNames: 'scopeTable nTemps supered requestor class literalStream selectorSet litIndSet litSet sourceRanges globalSourceRanges ' classVariableNames: '' poolDictionaries: '' category: 'System-Compiler'! !Encoder commentStamp: '' prior: 0! I encode names and literals into tree nodes with byte codes for the compiler. Byte codes for literals are not assigned until the tree-sizing pass of the compiler, because only then is it known which literals are actually needed. I also keep track of sourceCode ranges during parsing and code generation so I can provide an inverse map for the debugger.! !Encoder methodsFor: 'initialize-release'! fillDict: dict with: nodeClass mapping: keys to: codeArray | codeStream | codeStream _ ReadStream on: codeArray. keys do: [:key | dict at: key put: (nodeClass new name: key key: key code: codeStream next)]! ! !Encoder methodsFor: 'initialize-release' stamp: 'di 12/4/1999 22:22'! init: aClass context: aContext notifying: req | node n homeNode indexNode | requestor _ req. class _ aClass. nTemps _ 0. supered _ false. self initScopeAndLiteralTables. n _ -1. class allInstVarNames do: [:variable | node _ VariableNode new name: variable index: (n _ n + 1) type: LdInstType. scopeTable at: variable put: node]. aContext == nil ifFalse: [homeNode _ self bindTemp: 'homeContext'. "first temp = aContext passed as arg" n _ 0. aContext tempNames do: [:variable | indexNode _ self encodeLiteral: (n _ n + 1). node _ MessageAsTempNode new receiver: homeNode selector: #tempAt: arguments: (Array with: indexNode) precedence: 3 from: self. scopeTable at: variable put: node]]. sourceRanges _ Dictionary new: 32. globalSourceRanges _ OrderedCollection new: 32. ! ! !Encoder methodsFor: 'initialize-release'! initScopeAndLiteralTables scopeTable _ StdVariables copy. litSet _ StdLiterals copy. selectorSet _ StdSelectors copy. litIndSet _ Dictionary new: 16. literalStream _ WriteStream on: (Array new: 32)! ! !Encoder methodsFor: 'initialize-release'! nTemps: n literals: lits class: cl "Decompile." class _ cl. nTemps _ n. literalStream _ ReadStream on: lits. literalStream position: lits size! ! !Encoder methodsFor: 'initialize-release'! noteSuper supered _ true! ! !Encoder methodsFor: 'initialize-release'! release requestor _ nil! ! !Encoder methodsFor: 'encoding'! cantStoreInto: varName ^StdVariables includesKey: varName! ! !Encoder methodsFor: 'encoding'! encodeLiteral: object ^self name: object key: (class literalScannedAs: object notifying: self) class: LiteralNode type: LdLitType set: litSet! ! !Encoder methodsFor: 'encoding'! encodeSelector: selector ^self name: selector key: selector class: SelectorNode type: SendType set: selectorSet! ! !Encoder methodsFor: 'encoding' stamp: 'di 12/4/1999 20:09'! encodeVariable: name ^ self encodeVariable: name sourceRange: nil ifUnknown: [ self undeclared: name ]! ! !Encoder methodsFor: 'encoding' stamp: 'ls 1/19/2001 12:59'! encodeVariable: name ifUnknown: action ^self encodeVariable: name sourceRange: nil ifUnknown: action! ! !Encoder methodsFor: 'encoding' stamp: 'ls 1/19/2001 12:58'! encodeVariable: name sourceRange: range ifUnknown: action | varNode | varNode _ scopeTable at: name ifAbsent: [(self lookupInPools: name ifFound: [:assoc | varNode _ self global: assoc name: name]) ifTrue: [varNode] ifFalse: [action value]]. range ifNotNil: [ name first isUppercase ifTrue: [globalSourceRanges addLast: { name. range. false }]. ]. (varNode isTemp and: [varNode scope < 0]) ifTrue: [^self notify: 'out of scope']. ^ varNode! ! !Encoder methodsFor: 'encoding'! litIndex: literal | p | p _ literalStream position. p = 256 ifTrue: [self notify: 'More than 256 literals referenced. You must split or otherwise simplify this method. The 257th literal is: ', literal printString. ^nil]. "Would like to show where it is in the source code, but that info is hard to get." literalStream nextPut: literal. ^ p! ! !Encoder methodsFor: 'encoding' stamp: 'di 1/7/2000 15:24'! sharableLitIndex: literal "Special access prevents multiple entries for post-allocated super send special selectors" | p | p _ literalStream originalContents indexOf: literal. p = 0 ifFalse: [^ p-1]. ^ self litIndex: literal ! ! !Encoder methodsFor: 'encoding' stamp: 'tk 4/20/1999 15:41'! undeclared: name | sym | requestor interactive ifTrue: [ requestor requestor == #error: ifTrue: [requestor error: 'Undeclared']. ^ self notify: 'Undeclared']. Transcript show: ' (' , name , ' is Undeclared) '. sym _ name asSymbol. Undeclared at: sym put: nil. ^self global: (Undeclared associationAt: sym) name: sym! ! !Encoder methodsFor: 'temps'! autoBind: name "Declare a block argument as a temp if not already declared." | node | node _ scopeTable at: name ifAbsent: [(self lookupInPools: name ifFound: [:assoc | assoc]) ifTrue: [self notify: 'Name already used in a Pool or Global']. ^ (self reallyBind: name) nowHasDef nowHasRef scope: 1]. node isTemp ifTrue: [node scope >= 0 ifTrue: [^ self notify: 'Name already used in this method']. node nowHasDef nowHasRef scope: 1] ifFalse: [^ self notify: 'Name already used in this class']. ^node! ! !Encoder methodsFor: 'temps' stamp: 'di 10/12/1999 16:53'! bindAndJuggle: name | node nodes first thisCode | node _ self reallyBind: name. "Declared temps must precede block temps for decompiler and debugger to work right" nodes _ self tempNodes. (first _ nodes findFirst: [:n | n scope > 0]) > 0 ifTrue: [node == nodes last ifFalse: [self error: 'logic error']. thisCode _ (nodes at: first) code. first to: nodes size - 1 do: [:i | (nodes at: i) key: (nodes at: i) key code: (nodes at: i+1) code]. nodes last key: nodes last key code: thisCode]. ^ node! ! !Encoder methodsFor: 'temps' stamp: 'jm 9/18/97 21:06'! bindArg: name "Declare an argument." | node | nTemps >= 15 ifTrue: [^self notify: 'Too many arguments']. node _ self bindTemp: name. ^ node nowHasDef nowHasRef! ! !Encoder methodsFor: 'temps' stamp: 'crl 2/26/1999 12:18'! bindBlockTemp: name "Declare a temporary block variable; complain if it's not a field or class variable." | node | node _ scopeTable at: name ifAbsent: [^self reallyBind: name]. node isTemp ifTrue: [ node scope >= 0 ifTrue: [^ self notify: 'Name already used in this method']. node scope: 0] ifFalse: [^self notify: 'Name already used in this class']. ^node ! ! !Encoder methodsFor: 'temps' stamp: 'ar 7/12/1999 00:24'! bindTemp: name "Declare a temporary; error not if a field or class variable." scopeTable at: name ifPresent:[:node| "When non-interactive raise the error only if its a duplicate" (node isTemp or:[requestor interactive]) ifTrue:[^self notify:'Name is already defined'] ifFalse:[Transcript show: '(', name, ' is shadowed)']]. ^self reallyBind: name! ! !Encoder methodsFor: 'temps'! maxTemp ^nTemps! ! !Encoder methodsFor: 'temps'! newTemp: name nTemps _ nTemps + 1. ^ TempVariableNode new name: name index: nTemps - 1 type: LdTempType scope: 0! ! !Encoder methodsFor: 'results'! allLiterals supered ifTrue: [self litIndex: (self associationFor: class)]. ^ literalStream contents! ! !Encoder methodsFor: 'results' stamp: 'ar 2/13/1999 21:18'! associationFor: aClass | name | name _ Smalltalk keyAtIdentityValue: aClass ifAbsent: [^Association new value: aClass]. ^Smalltalk associationAt: name! ! !Encoder methodsFor: 'results'! literals "Should only be used for decompiling primitives" ^ literalStream contents! ! !Encoder methodsFor: 'results' stamp: 'di 10/12/1999 16:12'! tempNames ^ self tempNodes collect: [:node | (node isMemberOf: MessageAsTempNode) ifTrue: [scopeTable keyAtValue: node] ifFalse: [node key]]! ! !Encoder methodsFor: 'results' stamp: 'di 10/12/1999 15:31'! tempNodes | tempNodes | tempNodes _ SortedCollection sortBlock: [:n1 :n2 | n1 code <= n2 code]. scopeTable associationsDo: [:assn | assn value isTemp ifTrue: [tempNodes add: assn value]]. ^ tempNodes! ! !Encoder methodsFor: 'results'! tempsAndBlockArgs | tempNodes var | tempNodes _ OrderedCollection new. scopeTable associationsDo: [:assn | var _ assn value. ((var isTemp and: [var isArg not]) and: [var scope = 0 or: [var scope = -1]]) ifTrue: [tempNodes add: var]]. ^ tempNodes! ! !Encoder methodsFor: 'results' stamp: 'di 10/12/1999 17:15'! unusedTempNames | unused name | unused _ OrderedCollection new. scopeTable associationsDo: [:assn | (assn value isUnusedTemp) ifTrue: [name _ assn value key. name ~= 'homeContext' ifTrue: [unused add: name]]]. ^ unused! ! !Encoder methodsFor: 'error handling'! notify: string "Put a separate notifier on top of the requestor's window" | req | requestor == nil ifFalse: [req _ requestor. self release. req notify: string]. ^false! ! !Encoder methodsFor: 'error handling'! notify: string at: location | req | requestor == nil ifFalse: [req _ requestor. self release. req notify: string at: location]. ^false! ! !Encoder methodsFor: 'error handling'! requestor: req "Often the requestor is a BrowserCodeController" requestor _ req! ! !Encoder methodsFor: 'source mapping' stamp: 'di 12/4/1999 22:27'! globalSourceRanges ^ globalSourceRanges! ! !Encoder methodsFor: 'source mapping'! noteSourceRange: range forNode: node sourceRanges at: node put: range! ! !Encoder methodsFor: 'source mapping' stamp: 'RAA 8/21/1999 06:52'! rawSourceRanges ^ sourceRanges ! ! !Encoder methodsFor: 'source mapping'! sourceMap "Answer with a sorted set of associations (pc range)." ^ (sourceRanges keys collect: [:key | Association key: key pc value: (sourceRanges at: key)]) asSortedCollection! ! !Encoder methodsFor: 'private'! classEncoding "This is a hack so that the parser may findout what class it was parsing for when it wants to create a syntax error view." ^ class! ! !Encoder methodsFor: 'private'! global: ref name: name ^self name: name key: ref class: VariableNode type: LdLitIndType set: litIndSet! ! !Encoder methodsFor: 'private' stamp: 'di 12/4/1999 16:51'! lookupInPools: varName ifFound: assocBlock Symbol hasInterned: varName ifTrue: [:sym | (class scopeHas: sym ifTrue: assocBlock) ifTrue: [^ true]. (Preferences valueOfFlag: #lenientScopeForGlobals) "**Temporary**" ifTrue: [^ Smalltalk lenientScopeHas: sym ifTrue: assocBlock] ifFalse: [^ false]]. ^ class scopeHas: varName ifTrue: assocBlock. "Maybe a string in a pool **Eliminate this**"! ! !Encoder methodsFor: 'private'! name: name key: key class: leafNodeClass type: type set: dict | node | ^dict at: key ifAbsent: [node _ leafNodeClass new name: name key: key index: nil type: type. dict at: key put: node. ^node]! ! !Encoder methodsFor: 'private'! possibleVariablesFor: proposedVariable | results | results _ proposedVariable correctAgainstDictionary: scopeTable continuedFrom: nil. proposedVariable first isUppercase ifTrue: [ results _ class possibleVariablesFor: proposedVariable continuedFrom: results ]. ^ proposedVariable correctAgainst: nil continuedFrom: results. ! ! !Encoder methodsFor: 'private'! reallyBind: name | node | node _ self newTemp: name. scopeTable at: name put: node. ^node! ! Error subclass: #EndOfStream instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Extensions'! !EndOfStream commentStamp: '' prior: 0! Signalled when ReadStream>>next encounters a premature end.! !EndOfStream methodsFor: 'description' stamp: 'hh 5/17/2000 00:30'! isResumable "EndOfStream is resumable, so ReadStream>>next can answer" ^ true! ! !EndOfStream methodsFor: 'exceptionDescription' stamp: 'RAA 5/17/2000 03:10'! defaultAction "Answer ReadStream>>next default reply." ^ nil! ! Object subclass: #Envelope instanceVariableNames: 'points loopStartIndex loopEndIndex loopStartMSecs loopMSecs target updateSelector loopEndMSecs endMSecs scale decayScale lastValue currValue valueIncr nextRecomputeTime noChangesDuringLoop ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !Envelope commentStamp: '' prior: 0! An envelope models a three-stage progression for a musical note: attack, sustain, decay. Envelopes can either return the envelope value at a given time or can update some target object using a client-specified message selector. The points instance variable holds an array of (time, value) points, where the times are in milliseconds. The points array must contain at least two points. The time coordinate of the first point must be zero and the time coordinates of subsequent points must be in ascending order, although the spacing between them is arbitrary. Envelope values between points are computed by linear interpolation. The scale slot is initially set so that the peak of envelope matches some note attribute, such as its loudness. When entering the decay phase, the scale is adjusted so that the decay begins from the envelope's current value. This avoids a potential sharp transient when entering the decay phase. The loopStartIndex and loopEndIndex slots contain the indices of points in the points array; if they are equal, then the envelope holds a constant value for the sustain phase of the note. Otherwise, envelope values are computed by repeatedly looping between these two points. The loopEndMSecs slot can be set in advance (as when playing a score) or dynamically (as when responding to interactive inputs from a MIDI keyboard). In the latter case, the value of scale is adjusted to start the decay phase with the current envelope value. Thus, if a note ends before its attack is complete, the decay phase is started immediately (i.e., the attack phase is never completed). For best results, amplitude envelopes should start and end with zero values. Otherwise, the sharp transient at the beginning or end of the note may cause audible clicks or static. For envelopes on other parameters, this may not be necessary. ! !Envelope methodsFor: 'accessing' stamp: 'jm 8/17/1998 15:20'! attackTime "Return the time taken by the attack phase." ^ (points at: loopStartIndex) x ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 8/13/1998 18:10'! centerPitch: aNumber "Set the center pitch of a pitch-controlling envelope. This default implementation does nothing." ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 2/4/98 10:21'! decayEndIndex ^ points size ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/26/97 08:53'! decayTime "Return the time taken by the decay phase." ^ points last x - (points at: loopEndIndex) x ! ! !Envelope methodsFor: 'accessing' stamp: 'di 1/20/98 21:35'! duration "Return the time of the final point." loopEndMSecs == nil ifTrue: [^ points last x] ifFalse: [^ loopEndMSecs + self decayTime]. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 8/19/1998 09:07'! duration: seconds "Set the note duration to the given number of seconds." "Details: The duration is reduced by 19 mSec to ensure proper cutoffs even when the sound starts playing between doControl epochs." "Note: This is a hack. With a little additional work on the envelope logic, it should be possible to reduce or eliminate this fudge factor. In particular, an envelope should use the time remaining, rather than time-since-start to determine when to enter its decay phase. In addition, an envelope must be able to cut off in minimum time (~5-10 msec) if there isn't enough time to do their normal decay. All of this is to allow instruments with leisurely decays to play very short notes if necessary (say, when fast-forwarding through a score)." | attack decay endTime | endMSecs _ (seconds * 1000.0) asInteger - 19. attack _ self attackTime. decay _ self decayTime. endMSecs > (attack + decay) ifTrue: [endTime _ endMSecs - decay] ifFalse: [ endMSecs >= attack ifTrue: [endTime _ attack] ifFalse: [endTime _ endMSecs]]. self sustainEnd: (endTime max: 0). ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 2/4/98 17:24'! loopEndIndex ^ loopEndIndex ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 2/4/98 17:24'! loopStartIndex ^ loopStartIndex ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 8/13/1998 17:03'! name ^ self updateSelector allButLast ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 2/4/98 17:24'! points ^ points ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/24/97 14:36'! scale ^ scale ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/24/97 14:36'! scale: aNumber scale _ aNumber asFloat. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/26/97 09:25'! target ^ target ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/26/97 09:25'! target: anObject target _ anObject. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/24/97 14:34'! updateSelector ^ updateSelector ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/26/97 08:52'! updateSelector: aSymbol updateSelector _ aSymbol. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 8/13/1998 18:13'! volume: aNumber "Set the maximum volume of a volume-controlling envelope. This default implementation does nothing." ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 21:07'! computeValueAtMSecs: mSecs "Return the value of this envelope at the given number of milliseconds from its onset. Return zero for times outside the time range of this envelope." "Note: Unlike the private method incrementalComputeValueAtMSecs:, this method does is not increment. Thus it is slower, but it doesn't depend on being called sequentially at fixed time intervals." | t i | mSecs < 0 ifTrue: [^ 0.0]. ((loopEndMSecs ~~ nil) and: [mSecs >= loopEndMSecs]) ifTrue: [ "decay phase" t _ (points at: loopEndIndex) x + (mSecs - loopEndMSecs). i _ self indexOfPointAfterMSecs: t startingAt: loopEndIndex. i == nil ifTrue: [^ 0.0]. "past end" ^ (self interpolate: t between: (points at: i - 1) and: (points at: i)) * decayScale]. mSecs < loopStartMSecs ifTrue: [ "attack phase" i _ self indexOfPointAfterMSecs: mSecs startingAt: 1. i = 1 ifTrue: [^ (points at: 1) y * scale]. ^ self interpolate: mSecs between: (points at: i - 1) and: (points at: i)]. "sustain phase" loopMSecs = 0 ifTrue: [^ (points at: loopEndIndex) y * scale]. "looping on a single point" t _ loopStartMSecs + ((mSecs - loopStartMSecs) \\ loopMSecs). i _ self indexOfPointAfterMSecs: t startingAt: loopStartIndex. ^ self interpolate: t between: (points at: i - 1) and: (points at: i) ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 21:15'! reset "Reset the state for this envelope." lastValue _ -100000.0. "impossible value" nextRecomputeTime _ 0. self updateTargetAt: 0. ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 17:39'! showOnDisplay "Envelope example showOnDisplay" | xOrigin yOrigin minVal maxVal yScale step x v y | xOrigin _ 30. yOrigin _ 130. minVal _ 1e100. maxVal _ -1e100. points do: [:p | p y < minVal ifTrue: [minVal _ p y]. p y > maxVal ifTrue: [maxVal _ p y]]. yScale _ 100.0 / ((maxVal - minVal) * scale). step _ (self duration // 150) max: 1. Display fillBlack: ((xOrigin + ((points at: loopStartIndex) x // step))@(yOrigin - 100) extent: 1@100). Display fillBlack: ((xOrigin + ((points at: loopEndIndex) x // step))@(yOrigin - 100) extent: 1@100). Display fillBlack: (xOrigin@(yOrigin - 100) extent: 1@100). x _ xOrigin. step negated to: self duration + step by: step do: [:mSecs | v _ self computeValueAtMSecs: mSecs. y _ yOrigin - ((v - minVal) * yScale) asInteger. Display fillBlack: ((x - 1)@(y - 1) extent: 2@2). Display fillBlack: (x@yOrigin extent: 1@1). x _ x + 1]. ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 18:27'! sustainEnd: mSecs "Set the ending time of the sustain phase of this envelope; the decay phase will start this point. Typically derived from a note's duration." "Details: to avoid a sharp transient, the decay phase is scaled so that the beginning of the decay matches the envelope's instantaneous value when the decay phase starts." | vIfSustaining firstVOfDecay | loopEndMSecs _ nil. "pretend to be sustaining" decayScale _ 1.0. nextRecomputeTime _ 0. vIfSustaining _ self computeValueAtMSecs: mSecs. "get value at end of sustain phase" loopEndMSecs _ mSecs. firstVOfDecay _ (points at: loopEndIndex) y * scale. firstVOfDecay = 0.0 ifTrue: [decayScale _ 1.0] ifFalse: [decayScale _ vIfSustaining / firstVOfDecay]. ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 19:46'! updateTargetAt: mSecs "Send my updateSelector to the given target object with the value of this envelope at the given number of milliseconds from its onset. Answer true if the value changed." | newValue | newValue _ self valueAtMSecs: mSecs. newValue = lastValue ifTrue: [^ false]. target perform: updateSelector with: newValue. lastValue _ newValue. ^ true ! ! !Envelope methodsFor: 'applying' stamp: 'jm 2/4/98 20:24'! valueAtMSecs: mSecs "Return the value of this envelope at the given number of milliseconds from its onset. Return zero for times outside the time range of this envelope." mSecs < 0 ifTrue: [^ 0.0]. mSecs < nextRecomputeTime ifTrue: [currValue _ currValue + valueIncr] ifFalse: [currValue _ self incrementalComputeValueAtMSecs: mSecs]. ^ currValue ! ! !Envelope methodsFor: 'storing' stamp: 'di 2/1/98 15:45'! storeOn: strm strm nextPutAll: '((' , self class name; nextPutAll: ' points: '; store: (points collect: [:p | p x @ (p y roundTo: 0.00001)]); nextPutAll: ' loopStart: '; print: loopStartIndex; nextPutAll: ' loopEnd: '; print: loopEndIndex; nextPutAll: ')'; nextPutAll: ' updateSelector: '; store: self updateSelector; nextPutAll: ';'; nextPutAll: ' scale: '; print: scale; nextPutAll: ')'. ! ! !Envelope methodsFor: 'private' stamp: 'jm 11/26/97 09:03'! checkParameters "Verify that the point array, loopStartIndex, and loopStopIndex obey the rules." | lastT t | points size > 1 ifFalse: [^ self error: 'the point list must contain at least two points']. points first x = 0 ifFalse: [^ self error: 'the time of the first point must be zero']. lastT _ points first x. 2 to: points size do: [:i | t _ (points at: i) x. t >= lastT ifFalse: [^ self error: 'the points must be in ascending time order']]. (loopStartIndex isInteger and: [(loopStartIndex > 0) and: [loopStartIndex <= points size]]) ifFalse: [^ self error: 'loopStartIndex is not a valid point index']. (loopEndIndex isInteger and: [(loopEndIndex > 0) and: [loopEndIndex <= points size]]) ifFalse: [^ self error: 'loopEndIndex is not a valid point index']. loopStartIndex <= loopEndIndex ifFalse: [^ self error: 'loopEndIndex must not precede loopStartIndex']. ! ! !Envelope methodsFor: 'private' stamp: 'jm 2/4/98 20:20'! computeIncrementAt: mSecs between: p1 and: p2 scale: combinedScale "Compute the current and increment values for the given time between the given inflection points." "Assume: p1 x <= mSecs <= p2 x" | valueRange timeRange | valueRange _ (p2 y - p1 y) asFloat. timeRange _ (p2 x - p1 x) asFloat. currValue _ (p1 y + (((mSecs - p1 x) asFloat / timeRange) * valueRange)) * combinedScale. valueIncr _ (((p2 y * combinedScale) - currValue) / (p2 x - mSecs)) * 10.0. ^ currValue ! ! !Envelope methodsFor: 'private' stamp: 'jm 2/4/98 20:22'! incrementalComputeValueAtMSecs: mSecs "Compute the current value, per-step increment, and the time of the next inflection point." "Note: This method is part of faster, but less general, way of computing envelope values. It depends on a known, fixed control updating rate." | t i | ((loopEndMSecs ~~ nil) and: [mSecs >= loopEndMSecs]) ifTrue: [ "decay phase" t _ (points at: loopEndIndex) x + (mSecs - loopEndMSecs). i _ self indexOfPointAfterMSecs: t startingAt: loopEndIndex. i == nil ifTrue: [ "past end" currValue _ points last y * scale * decayScale. valueIncr _ 0.0. nextRecomputeTime _ mSecs + 1000000. ^ currValue]. nextRecomputeTime _ mSecs + ((points at: i) x - t). ^ self computeIncrementAt: t between: (points at: i - 1) and: (points at: i) scale: scale * decayScale]. mSecs < loopStartMSecs ifTrue: [ "attack phase" t _ mSecs. i _ self indexOfPointAfterMSecs: t startingAt: 1. nextRecomputeTime _ mSecs + ((points at: i) x - t)] ifFalse: [ "sustain (looping) phase" noChangesDuringLoop ifTrue: [ currValue _ (points at: loopEndIndex) y * scale. valueIncr _ 0.0. loopEndMSecs == nil ifTrue: [nextRecomputeTime _ mSecs + 10] "unknown end time" ifFalse: [nextRecomputeTime _ loopEndMSecs]. ^ currValue]. t _ loopStartMSecs + ((mSecs - loopStartMSecs) \\ loopMSecs). i _ self indexOfPointAfterMSecs: t startingAt: loopStartIndex. nextRecomputeTime _ (mSecs + ((points at: i) x - t)) min: loopEndMSecs]. ^ self computeIncrementAt: t between: (points at: i - 1) and: (points at: i) scale: scale. ! ! !Envelope methodsFor: 'private' stamp: 'jm 12/16/97 16:51'! indexOfPointAfterMSecs: mSecs startingAt: startIndex "Return the index of the first point whose time is greater that mSecs, starting with the given index. Return nil if mSecs is after the last point's time." startIndex to: points size do: [:i | (points at: i) x > mSecs ifTrue: [^ i]]. ^ nil ! ! !Envelope methodsFor: 'private' stamp: 'jm 2/4/98 21:05'! interpolate: mSecs between: p1 and: p2 "Return the scaled, interpolated value for the given time between the given time points." "Assume: p1 x <= mSecs <= p2 x" | valueRange timeRange | valueRange _ (p2 y - p1 y) asFloat. valueRange = 0.0 ifTrue: [^ p1 y * scale]. timeRange _ (p2 x - p1 x) asFloat. ^ (p1 y + (((mSecs - p1 x) asFloat / timeRange) * valueRange)) * scale. ! ! !Envelope methodsFor: 'private' stamp: 'jm 2/4/98 17:52'! setPoints: pointList loopStart: startIndex loopEnd: endIndex | lastVal | points _ pointList asArray collect: [:p | p x asInteger @ p y asFloat]. loopStartIndex _ startIndex. loopEndIndex _ endIndex. self checkParameters. loopStartMSecs _ (points at: loopStartIndex) x. loopMSecs _ (points at: loopEndIndex) x - (points at: loopStartIndex) x. loopEndMSecs _ nil. "unknown end time; sustain until end time is known" scale ifNil: [scale _ 1.0]. decayScale ifNil: [decayScale _ 1.0]. "note if there are no changes during the loop phase" noChangesDuringLoop _ true. lastVal _ (points at: loopStartIndex) y. loopStartIndex to: loopEndIndex do: [:i | (points at: i) y ~= lastVal ifTrue: [ noChangesDuringLoop _ false. ^ self]]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Envelope class instanceVariableNames: ''! !Envelope class methodsFor: 'instance creation' stamp: 'jm 11/26/97 09:14'! example "Envelope example showOnDisplay" | p | p _ Array with: 0@0 with: 100@1.0 with: 250@0.7 with: 400@1.0 with: 500@0. ^ (self points: p loopStart: 2 loopEnd: 4) sustainEnd: 1200. ! ! !Envelope class methodsFor: 'instance creation' stamp: 'jm 2/4/98 06:52'! exponentialDecay: multiplier "(Envelope exponentialDecay: 0.95) showOnDisplay" | mSecsPerStep pList t v last | mSecsPerStep _ 10. ((multiplier > 0.0) and: [multiplier < 1.0]) ifFalse: [self error: 'multiplier must be greater than 0.0 and less than 1.0']. pList _ OrderedCollection new. pList add: 0@0.0. last _ 0.0. v _ 1.0. t _ 10. [v > 0.01] whileTrue: [ (v - last) abs > 0.02 ifTrue: [ "only record substatial changes" pList add: t@v. last _ v]. t _ t + mSecsPerStep. v _ v * multiplier]. pList add: (t + mSecsPerStep)@0.0. ^ self points: pList asArray loopStart: pList size loopEnd: pList size ! ! !Envelope class methodsFor: 'instance creation' stamp: 'jm 11/26/97 08:49'! points: pList loopStart: loopStart loopEnd: loopEnd ^ self new setPoints: pList asArray loopStart: loopStart loopEnd: loopEnd ! ! RectangleMorph subclass: #EnvelopeEditorMorph instanceVariableNames: 'sound soundName envelope hScale vScale graphArea pixPerTick limits limitXs limitHandles line prevMouseDown sampleDuration showAllEnvelopes denominator keyboard ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Interface'! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'di 1/31/98 10:41'! editEnvelope: env envelope _ env. limits _ Array with: envelope loopStartIndex with: envelope loopEndIndex with: envelope points size. limitXs _ limits collect: [:i | (envelope points at: i) x]. self buildView! ! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'JMV 1/9/2001 13:43'! editSound: aSound | p | (aSound respondsTo: #envelopes) ifFalse: [ PopUpMenu inform: 'You selected a ', aSound class name, '.', String cr, 'I can''t handle these kinds of sounds.'. ^self ]. sound _ aSound. sound envelopes isEmpty ifTrue: [ "provide a default volume envelope" p _ OrderedCollection new. p add: 0@0.0; add: 10@1.0; add: 100@1.0; add: 120@0.0. sound addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3)]. self editEnvelope: sound envelopes first. keyboard soundPrototype: sound. ! ! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'di 9/5/1998 10:40'! initOnSound: aSound title: title sound _ aSound. soundName _ title. self initialize. ! ! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'di 7/14/2000 12:48'! initialize super initialize. prevMouseDown _ false. showAllEnvelopes _ true. soundName ifNil: [soundName _ 'test']. self editSound: (sound ifNil: [FMSound brass1 copy]). sound duration: 0.25. denominator _ 7. self extent: 10@10. "ie the minimum" ! ! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'di 11/7/2000 12:45'! soundBeingEdited ^ sound! ! !EnvelopeEditorMorph methodsFor: 'initialization' stamp: 'di 7/14/2000 11:13'! wantsRoundedCorners ^ Preferences roundedWindowCorners or: [super wantsRoundedCorners]! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 7/14/2000 13:08'! addControls | chooser | chooser _ PopUpChoiceMorph new extent: 110@14; contentsClipped: 'Editing: ' , envelope name; target: self; actionSelector: #chooseFrom:envelopeItem:; getItemsSelector: #curveChoices. chooser arguments: (Array with: chooser). self addMorph: chooser. chooser align: chooser bounds topLeft with: graphArea bounds bottomLeft + (0@5). chooser _ PopUpChoiceMorph new extent: 130@14; contentsClipped: 'Timbre: ' , soundName; target: self; actionSelector: #chooseFrom:soundItem:; getItemsSelector: #soundChoices. chooser arguments: (Array with: chooser). self addMorph: chooser. chooser align: chooser bounds topRight with: graphArea bounds bottomRight + (-50@5). ! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 2/1/98 11:54'! addCurves "Add the polyLine corresponding to the currently selected envelope, and possibly all the others, too." | verts aLine | sound envelopes do: [:env | (showAllEnvelopes or: [env == envelope]) ifTrue: [verts _ env points collect: [:p | (self xFromMs: p x) @ (self yFromValue: p y)]. aLine _ EnvelopeLineMorph basicNew vertices: verts borderWidth: 1 borderColor: (self colorForEnvelope: env). env == envelope ifTrue: [aLine borderWidth: 2. line _ aLine] ifFalse: [aLine on: #mouseUp send: #clickOnLine:evt:envelope: to: self withValue: env. self addMorph: aLine]]]. self addMorph: line "add the active one last (in front)"! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'ar 10/25/2000 17:47'! addHandlesIn: frame | handle | handle := PolygonMorph vertices: (Array with: 0@0 with: 8@0 with: 4@8) color: Color orange borderWidth: 1 borderColor: Color black. handle addMorph: ((RectangleMorph newBounds: ((self handleOffset: handle)-(2@0) extent: 1@(graphArea height-2)) color: Color orange) borderWidth: 0). limitHandles _ Array with: handle with: handle fullCopy with: handle fullCopy. 1 to: limitHandles size do: [:i | handle _ limitHandles at: i. handle on: #mouseDown send: #limitHandleMoveEvent:from:index: to: self withValue: i. handle on: #mouseMove send: #limitHandleMoveEvent:from:index: to: self withValue: i. self addMorph: handle. handle position: ((self xFromMs: (envelope points at: (limits at: i)) x) @ (graphArea top)) - (self handleOffset: handle)]! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 9/4/1998 15:49'! addKeyboard keyboard _ PianoKeyboardMorph new soundPrototype: sound. keyboard align: keyboard bounds bottomCenter with: bounds bottomCenter - (0@4). self addMorph: keyboard! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 9/4/1998 15:56'! buildGraphAreaIn: frame | r y | graphArea _ RectangleMorph newBounds: ((frame left + 40) @ (frame top + 40) corner: (frame right+1) @ (frame bottom - 60)) color: Color lightGreen lighter lighter. graphArea borderWidth: 1. self addMorph: graphArea. (envelope updateSelector = #pitch: and: [envelope scale <= 2.0]) ifTrue: ["Show half-steps" r _ graphArea innerBounds. 0.0 to: 1.0 by: 1.0/12.0/envelope scale do: [:val | y _ self yFromValue: val. graphArea addMorph: ((RectangleMorph newBounds: (r left@y extent: r width@1) color: Color veryLightGray) borderWidth: 0)]]. (envelope updateSelector = #ratio: and: [denominator ~= 9999]) ifTrue: ["Show denominator gridding" r _ graphArea innerBounds. (0.0 to: 1.0 by: 1.0/denominator/envelope scale) do: [:v | y _ self yFromValue: v. graphArea addMorph: ((RectangleMorph newBounds: (r left@y extent: r width@1) color: Color veryLightGray) borderWidth: 0)]]. ! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 9/4/1998 13:16'! buildView | frame | self color: Color lightGreen. self removeAllMorphs. frame _ self innerBounds. self buildGraphAreaIn: frame. self buildScalesIn: frame. self addHandlesIn: frame. self addCurves. line addHandles. self addControls. self addKeyboard! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 2/3/98 16:50'! colorForEnvelope: env | name index | name _ env name. index _ #('volume' 'modulation' 'pitch' 'ratio') indexOf: name ifAbsent: [5]. ^ Color perform: (#(red green blue magenta black) at: index)! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'JMV 1/29/2001 10:58'! curveChoices | extant others | extant _ sound envelopes collect: [:env | env name]. others _ #('volume' 'modulation' 'pitch' 'random pitch:' 'ratio') reject: [:x | (extant includes: x) | ((x = 'pitch') & (extant includes: 'random pitch:')) | ((x = 'random pitch:') & (extant includes: 'pitch')) ]. ^ (extant collect: [:name | 'edit ' , name]) , (others collect: [:name | 'add ' , name]) , (sound envelopes size > 1 ifTrue: [Array with: 'remove ' , envelope name] ifFalse: [Array new])! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 1/26/98 15:37'! handleOffset: handle "This is the offset from position to the bottom vertex" ^ (handle width//2+1) @ handle height ! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'di 7/14/2000 12:56'! soundChoices ^ #('new...') , AbstractSound soundNames! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 9/4/1998 16:03'! extent: newExtent super extent: (newExtent max: (self maxTime//10*3+50 max: 355) @ 284). self buildView! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 1/27/98 14:40'! maxTime ^ (envelope points at: limits last) x + 100! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 1/26/98 14:10'! msFromX: x ^ (x - graphArea left)//pixPerTick*10! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 1/31/98 01:10'! valueFromY: y "The convention is that envelope values are between 0.0 and 1.0" | value | value _ (graphArea bottom - y) asFloat / (graphArea height). envelope updateSelector = #ratio: ifTrue: ["Ratio gets gridded by denominator" ^ (value * envelope scale * denominator) rounded asFloat / denominator / envelope scale]. ^ value! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 1/26/98 14:09'! xFromMs: ms ^ graphArea left + (ms//10*pixPerTick)! ! !EnvelopeEditorMorph methodsFor: 'scaling' stamp: 'di 1/27/98 00:23'! yFromValue: val "The convention is that envelope values are between 0.0 and 1.0" ^ graphArea bottom - (val* (graphArea height))! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 9/8/2000 10:40'! acceptGraphPoint: p at: index | ms val points whichLim linePoint other boundedP | boundedP _ p adhereTo: graphArea bounds. ms _ self msFromX: boundedP x. points _ envelope points. ms _ self constrain: ms adjacentTo: index in: points. (index = 1 or: [(whichLim _ limits indexOf: index) > 0]) ifTrue: ["Limit points must not move laterally" ms _ (points at: index) x]. val _ self valueFromY: boundedP y. points at: index put: ms@val. linePoint _ (self xFromMs: ms) @ (self yFromValue: val). (whichLim notNil and: [whichLim between: 1 and: 2]) ifTrue: ["Loop start and loop end must be tied together" other _ limits at: (3 - whichLim). " 1 <--> 2 " points at: other put: (points at: other) x @ val. line verticesAt: other put: (line vertices at: other) x @ linePoint y]. "Make sure envelope feels the change in points array..." envelope setPoints: points loopStart: (limits at: 1) loopEnd: (limits at: 2). ^ linePoint! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'JMV 1/29/2001 10:57'! addEnvelopeNamed: envName | points env | points _ OrderedCollection new. points add: 0@0.0; add: (envelope points at: envelope loopStartIndex) x@1.0; add: (envelope points at: envelope loopEndIndex) x@1.0; add: (envelope points last) x@0.0. envName = 'volume' ifTrue: [env _ VolumeEnvelope points: points loopStart: 2 loopEnd: 3. env target: sound; scale: 0.7]. envName = 'modulation' ifTrue: [env _ Envelope points: (points collect: [:p | p x @ 0.5]) loopStart: 2 loopEnd: 3. env target: sound; updateSelector: #modulation:; scale: sound modulation*2.0]. envName = 'pitch' ifTrue: [env _ PitchEnvelope points: (points collect: [:p | p x @ 0.5]) loopStart: 2 loopEnd: 3. env target: sound; updateSelector: #pitch:; scale: 0.5]. envName = 'random pitch:' ifTrue: [env _ RandomEnvelope for: #pitch:. points _ OrderedCollection new. points add: 0@(env delta * 5 + 0.5); add: (envelope points at: envelope loopStartIndex) x@(env highLimit - 1 * 5 + 0.5); add: (envelope points at: envelope loopEndIndex) x@(env highLimit - 1 * 5 + 0.5); add: (envelope points last) x@(env lowLimit - 1 * 5 + 0.5). env setPoints: points loopStart: 2 loopEnd: 3. env target: sound. ]. envName = 'ratio' ifTrue: [denominator _ 9999. "No gridding" env _ Envelope points: (points collect: [:p | p x @ 0.5]) loopStart: 2 loopEnd: 3. env target: sound; updateSelector: #ratio:; scale: sound ratio*2.0]. env ifNotNil: [sound addEnvelope: env. self editEnvelope: env]! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'JMV 1/26/2001 11:28'! buildScalesIn: frame | env hmajortick hminortick | env _ envelope. pixPerTick _ graphArea width // (self maxTime//10) max: 1. hminortick _ ( 1 + ( self maxTime // 800 ) ) * 10. hmajortick _ ( 1 + ( self maxTime // 800 ) ) * 100. hScale _ (ScaleMorph newBounds: ((graphArea left)@(frame top) corner: (self xFromMs: self maxTime)@(graphArea top - 1))) start: 0 stop: self maxTime minorTick: hminortick minorTickLength: 3 majorTick: hmajortick majorTickLength: 10 caption: 'milliseconds' tickPrintBlock: [:v | v printString]. self addMorph: hScale. vScale _ ScaleMorph newBounds: (0@0 extent: (graphArea height)@(graphArea left - frame left)). env name = 'pitch' ifTrue: [env scale >= 2.0 ifTrue: [vScale start: 0 stop: env scale minorTick: env scale / 24 minorTickLength: 3 majorTick: env scale / 2.0 majorTickLength: 10 caption: 'pitch (octaves)' tickPrintBlock: [:v | (v-(env scale/2)) asInteger printString]] ifFalse: [vScale start: 0 stop: env scale minorTick: 1.0/48.0 minorTickLength: 3 majorTick: 1.0/12.0 majorTickLength: 10 caption: 'pitch (half-steps)' tickPrintBlock: [:v | (v-(env scale/2)*12) rounded printString]]] ifFalse: [ env name = 'random pitch:' ifTrue: [ vScale start: 0.9 stop: 1.1 minorTick: 0.2 / 50.0 minorTickLength: 3 majorTick: 0.2 / 5.0 majorTickLength: 10 caption: env name tickPrintBlock: [:v | v printString]] ifFalse: [ vScale start: 0 stop: env scale minorTick: env scale / 50.0 minorTickLength: 3 majorTick: env scale / 5.0 majorTickLength: 10 caption: env name tickPrintBlock: [:v | v printString]]. ]. vScale _ TransformationMorph new asFlexOf: vScale. vScale angle: Float pi / 2.0. self addMorph: vScale. vScale position: (frame left)@(graphArea top-1) - (3@1). ! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 1/31/98 11:53'! clickOnLine: aLine evt: anEvent envelope: env self editEnvelope: env! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 1/29/98 13:06'! constrain: xVal adjacentTo: ix in: points "Return xVal, restricted between points adjacent to vertX" | newVal | newVal _ xVal. ix > 1 ifTrue: [newVal _ newVal max: (points at: ix-1) x]. ix < points size ifTrue: [newVal _ newVal min: (points at: ix+1) x]. ^ newVal! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 1/28/98 20:19'! deletePoint: ix "If the point is a limit point, return false, otherwise, delete the point at ix, and return true." (limits includes: ix) ifTrue: [^ false]. 1 to: limits size do: [:i | "Decrease limit indices beyond the deletion" (limits at: i) > ix ifTrue: [limits at: i put: (limits at: i) - 1]]. envelope setPoints: (envelope points copyReplaceFrom: ix to: ix with: (Array new)) loopStart: (limits at: 1) loopEnd: (limits at: 2). ^ true! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 1/28/98 20:20'! insertPointAfter: ix "If there is not enough roon (in x) then return false. Otherwise insert a point between ix and ix+1 and return true." | points pt | points _ envelope points. (points at: ix+1) x - (points at: ix) x < 20 ifTrue: [^ false]. pt _ (points at: ix+1) + (points at: ix) // 2. 1 to: limits size do: [:i | "Increase limit indices beyond the insertion" (limits at: i) > ix ifTrue: [limits at: i put: (limits at: i) + 1]]. envelope setPoints: (points copyReplaceFrom: ix+1 to: ix with: (Array with: pt)) loopStart: (limits at: 1) loopEnd: (limits at: 2). ^ true! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'di 9/8/2000 10:41'! limitHandleMoveEvent: evt from: handle index: index "index is the handle index = 1, 2 or 3" | ix p ms x points limIx | ix _ limits at: index. "index of corresponding vertex" p _ evt cursorPoint adhereTo: graphArea bounds. ms _ self msFromX: p x + (self handleOffset: handle) x. "Constrain move to adjacent points on ALL envelopes" sound envelopes do: [:env | limIx _ env perform: (#(loopStartIndex loopEndIndex decayEndIndex) at: index). ms _ self constrain: ms adjacentTo: limIx in: env points]. "Update the handle, the vertex and the line being edited" x _ self xFromMs: ms. handle position: (x @ graphArea top) - (self handleOffset: handle). line verticesAt: ix put: x @ (line vertices at: ix) y. sound envelopes do: [:env | limIx _ env perform: (#(loopStartIndex loopEndIndex decayEndIndex) at: index). points _ env points. points at: limIx put: ms @ (points at: limIx) y. env setPoints: points loopStart: env loopStartIndex loopEnd: env loopEndIndex].! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 9/5/1998 10:45'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. menu addLine. envelope updateSelector = #ratio: ifTrue: [menu add: 'choose denominator...' action: #chooseDenominator:]. menu add: 'adjust scale...' action: #adjustScale:. SoundPlayer isReverbOn ifTrue: [menu add: 'turn reverb off' target: SoundPlayer selector: #stopReverb] ifFalse: [menu add: 'turn reverb on' target: SoundPlayer selector: #startReverb]. menu addLine. menu add: 'get sound from lib' action: #chooseSound:. menu add: 'put sound in lib' action: #saveSound:. menu add: 'read sound from disk...' action: #readFromDisk:. menu add: 'save sound on disk...' action: #saveToDisk:. menu add: 'save library on disk...' action: #saveLibToDisk:. ! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 2/17/98 12:02'! adjustScale: evt | scaleString oldScale baseValue | oldScale _ envelope scale. scaleString _ FillInTheBlank request: 'Enter the new full-scale value...' initialAnswer: oldScale printString. scaleString isEmpty ifTrue: [^ self]. envelope scale: (Number readFrom: scaleString) asFloat. baseValue _ envelope updateSelector = #pitch: ifTrue: [0.5] ifFalse: [0.0]. envelope setPoints: (envelope points collect: [:p | p x @ (p y - baseValue * oldScale / envelope scale + baseValue min: 1.0 max: 0.0)]) loopStart: (limits at: 1) loopEnd: (limits at: 2). self buildView! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! chooseDenominator: evt | menu | menu _ MenuMorph new. (Integer primesUpTo: 30) do: [:i | menu add: i printString target: self selector: #setDenominator: argument: i]. menu addLine. menu add: 'none' target: self selector: #setDenominator: argument: 9999. menu popUpEvent: evt in: self world! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 2/3/98 16:50'! chooseEnvelope: choice | name | (choice beginsWith: 'edit ') ifTrue: [name _ choice copyFrom: 'edit ' size+1 to: choice size. ^ self editEnvelope: (sound envelopes detect: [:env | env name = name])]. (choice beginsWith: 'add ') ifTrue: [name _ choice copyFrom: 'add ' size+1 to: choice size. ^ self addEnvelopeNamed: name]. (choice beginsWith: 'remove ') ifTrue: [^ self removeEnvelope "the current one"]. ! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 2/3/98 19:14'! chooseFrom: chooserMorph envelopeItem: item | name | (item beginsWith: 'edit ') ifTrue: [name _ item copyFrom: 'edit ' size+1 to: item size. self editEnvelope: (sound envelopes detect: [:env | env name = name])]. (item beginsWith: 'add ') ifTrue: [name _ item copyFrom: 'add ' size+1 to: item size. self addEnvelopeNamed: name]. (item beginsWith: 'remove ') ifTrue: [self removeEnvelope "the current one"]. chooserMorph contentsClipped: envelope name! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 7/14/2000 13:03'! chooseFrom: chooserMorph soundItem: item self editSoundNamed: item. ! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 7/14/2000 12:42'! chooseSound: evt | menu | menu _ MenuMorph new. menu add: 'new...' target: self selector: #editNewSound. menu addLine. AbstractSound soundNames do: [:name | menu add: name target: self selector: #editSoundNamed: argument: name]. menu popUpEvent: evt in: self world! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 1/30/98 22:58'! editNewSound | known i | known _ AbstractSound soundNames. i _ 0. [soundName _ 'unnamed' , i printString. known includes: soundName] whileTrue: [i _ 1+1]. soundName _ soundName. self editSound: FMSound default copy! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 7/14/2000 12:44'! editSoundNamed: name name = 'new...' ifTrue: [^ self editNewSound]. soundName _ name. self editSound: (AbstractSound soundNamed: soundName) copy! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 1/31/98 16:49'! readFileNamed: fileName | snd | snd _ Compiler evaluate: (FileStream readOnlyFileNamed: fileName) contentsOfEntireFile. soundName _ fileName copyFrom: 1 to: fileName size-4. "---.fmp" self editSound: snd! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! readFromDisk: evt | menu | menu _ MenuMorph new. (FileDirectory default fileNamesMatching: '*.fmp') do: [:fileName | menu add: fileName target: self selector: #readFileNamed: argument: fileName]. menu popUpEvent: evt in: self world! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 2/3/98 16:50'! removeEnvelope (PopUpMenu confirm: 'Really remove ' , envelope name , '?') ifFalse: [^ self]. sound removeEnvelope: envelope. self editEnvelope: sound envelopes first.! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 1/31/98 16:40'! saveLibToDisk: evt | newName f snd | newName _ FillInTheBlank request: 'Please confirm name for library...' initialAnswer: 'MySounds'. newName isEmpty ifTrue: [^ self]. f _ FileStream newFileNamed: newName , '.fml'. AbstractSound soundNames do: [:name | snd _ AbstractSound soundNamed: name. "snd isStorable" true ifTrue: [f nextChunkPut: 'AbstractSound soundNamed: ' , name , ' put: ' , snd storeString; cr; cr] ifFalse: [PopUpMenu notify: name , ' is not currently storable']]. f close! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 2/17/98 11:05'! saveSound: evt | newName | newName _ FillInTheBlank request: 'Please confirm name for save...' initialAnswer: soundName. newName isEmpty ifTrue: [^ self]. AbstractSound soundNamed: newName put: sound. soundName _ newName.! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 1/31/98 16:41'! saveToDisk: evt | newName f | newName _ FillInTheBlank request: 'Please confirm name for save...' initialAnswer: soundName. newName isEmpty ifTrue: [^ self]. f _ FileStream newFileNamed: newName , '.fmp'. sound storeOn: f. f close! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'di 1/31/98 01:36'! setDenominator: denom denominator _ denom. self buildView! ! !EnvelopeEditorMorph methodsFor: 'playing' stamp: 'di 2/3/98 17:07'! playNothing ! ! !EnvelopeEditorMorph methodsFor: 'stepping' stamp: 'di 6/7/1999 15:37'! step | mouseDown hand | hand _ self world firstHand. (bounds containsPoint: hand position) ifFalse: [^ self]. mouseDown _ hand lastEvent redButtonPressed. mouseDown not & prevMouseDown ifTrue: ["Mouse just went up" limitXs = (limits collect: [:i | (envelope points at: i) x]) ifFalse: ["Redisplay after changing limits" self editEnvelope: envelope]]. prevMouseDown _ mouseDown! ! !EnvelopeEditorMorph methodsFor: 'stepping' stamp: 'di 1/30/98 13:29'! stepTime ^ 100! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EnvelopeEditorMorph class instanceVariableNames: ''! !EnvelopeEditorMorph class methodsFor: 'as yet unclassified' stamp: 'di 5/15/1998 09:49'! openOn: aSound title: aString "EnvelopeEditorMorph openOn: (AbstractSound soundNamed: 'brass1') copy title: 'brass1'" (self basicNew initOnSound: aSound title: aString) openInWorld! ! PolygonMorph subclass: #EnvelopeLineMorph instanceVariableNames: 'editor ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Interface'! !EnvelopeLineMorph methodsFor: 'as yet unclassified' stamp: 'di 9/8/2000 10:41'! dragVertex: evt fromHandle: handle vertIndex: ix | p | super dragVertex: evt fromHandle: handle vertIndex: ix. p _ owner acceptGraphPoint: evt cursorPoint at: ix. self verticesAt: ix put: p. ! ! !EnvelopeLineMorph methodsFor: 'as yet unclassified' stamp: 'di 1/28/98 19:38'! dropVertex: evt fromHandle: handle vertIndex: ix | oldVerts | oldVerts _ vertices. super dropVertex: evt fromHandle: handle vertIndex: ix. vertices = oldVerts ifFalse: [owner deletePoint: ix "deleted a vertex"]! ! !EnvelopeLineMorph methodsFor: 'as yet unclassified' stamp: 'di 9/8/2000 10:42'! newVertex: evt fromHandle: handle afterVert: ix "Install a new vertex if there is room." (owner insertPointAfter: ix) ifFalse: [^ self "not enough room"]. super newVertex: evt fromHandle: handle afterVert: ix. self verticesAt: ix+1 put: (owner acceptGraphPoint: evt cursorPoint at: ix+1). ! ! !EnvelopeLineMorph methodsFor: 'as yet unclassified' stamp: 'di 9/7/2000 13:19'! vertices: verts borderWidth: bw borderColor: bc super initialize. vertices _ verts. color _ Color transparent. borderWidth _ bw. borderColor _ bc. closed _ false. arrows _ #none. self computeBounds. ! ! SystemDictionary subclass: #Environment instanceVariableNames: 'envtName outerEnvt ' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !Environment commentStamp: '' prior: 0! Environments are used to provide separate name spaces in Squeak. Each one operates pretty much the same way that the Smalltalk systemDictionary is used in a non-partitioned Squeak. Each class has a direct-access environment in which it is compiled. Its environment slot points to an instance of this class, and it is there where the bindings of global variables are sought. The compiler looks up these bindings using normal dictionary protocol (at:, etc). If a binding is not found, then the name is looked up in the environment from which that one inherits, if any. In this way a class may be compiled in a context that consists of several nested name spaces, and direct reference may be made to any of the objects resident in those spaces. Various methods may need to refer to objects that reside in environnments that are not a part of their direct-access environment. For these references, a simple global reference, Thing may not be used, and instead the construct, Envt Thing must be used. In this case Envt is a gloabl reference to another environment, and the global name, Thing, is sent as a message to that environment. Obviously, such a foreign reference cannot be resolved unless the environment in question implements a method of that name. This is how environmental variables are exported. Each environment has its own unique class. With this structure, each environment can have its own instance-specific messeages to provide access to its exported symbols. Note that this mechanism provides much faster runtime access than the Dictionary at: protocol. Also note that inheritance provides a trivial implementation of nested name scope by the same token. In the early stages of installing partitioned environments in Squeak, interpreted access will be provided in several ways. To begin with, environments will intercept the doesNotUnderstand: message and, if the message begins with a capital letter, it will look up the corresponding name using #at:, and return the value if found. A refinement to this feature will be to compile an export method on the spot, so that subsequent accesses to that variable run much faster. Note that there is no Environmental access pattern analogous to 'Envt Thing'. If an implementor wishes to store into environmental variables, he must do so by defining, eg, a SetThingTo: method and using a call to that method in his code. We may choose to only allow one certain pattern of access to be compiled in any subclass of Environment to enforce some understandable style of coding. ! !Environment methodsFor: 'instance creation' stamp: 'di 12/18/1999 15:12'! makeSubEnvironmentNamed: name "Make a new environment (with its own class) of the given name. Install it under that name in this environment, and point its outerEnvt link here as well." | envtClass envt | envtClass _ self class subclass: (name , 'Environment') asSymbol instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Environments'. envt _ envtClass new setName: name inOuterEnvt: self. envtClass addSharedPool: envt. "add it to its own compilation context for exports" ^ envt! ! !Environment methodsFor: 'instance creation' stamp: 'di 12/18/1999 15:26'! setName: name inOuterEnvt: outer outerEnvt _ outer. envtName _ name asSymbol. outerEnvt ifNotNil: [outerEnvt at: envtName put: self]. "install me in parent by name" ! ! !Environment methodsFor: 'instance creation' stamp: 'di 3/16/2000 12:54'! setName: name outerEnvt: outer outerEnvt _ outer. envtName _ name. ! ! !Environment methodsFor: 'dictionary access' stamp: 'di 2/16/2000 13:29'! = another "Environments should only be compared on the basis of identity" ^ self == another! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/2/1999 15:54'! allClassesAnywhereDo: classBlock | cl | self deepAssociationsDo: [:assn | cl _ assn value. (cl isKindOf: Class) ifTrue: [classBlock value: cl]]! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/2/1999 19:57'! associationAtOrAbove: key ifAbsent: absentBlock "Look up an association with this key here or in an outer environment." ^ super associationAt: key ifAbsent: [outerEnvt ifNil: [^ absentBlock value]. ^ outerEnvt associationAtOrAbove: key ifAbsent: absentBlock]! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/2/1999 20:55'! at: key "Compatibility hack for starting up Environments" ^ self atOrBelow: key ifAbsent: [self errorKeyNotFound]! ! !Environment methodsFor: 'dictionary access' stamp: 'acg 12/11/1999 02:15'! at: key ifAbsent: aBlock "Compatibility hack for starting up Environments" ^ self atOrBelow: key ifAbsent: aBlock! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/1/1999 20:37'! atOrAbove: key ifAbsent: absentBlock "Look up the value iof this key here or in an outer environment." ^ super at: key ifAbsent: [outerEnvt ifNil: [^ absentBlock value]. ^ outerEnvt atOrAbove: key ifAbsent: absentBlock]! ! !Environment methodsFor: 'dictionary access' stamp: 'di 2/16/2000 09:50'! atOrBelow: key ifAbsent: absentBlock | envt value maybe onDisk envName | "Compatibility hack -- find things in sub environments for now. Adjusted to not fault on every environment." ^ super at: key ifAbsent: [onDisk _ OrderedCollection new. self associationsDo: [:assn | ((assn key endsWith: 'Environment') and: [assn key size > 'Environment' size]) ifTrue: [ envName _ (assn key copyFrom: 1 to: assn key size - 11 "Environment") asSymbol. (envt _ super at: envName ifAbsent: [nil]) ifNotNil: [ envt isInMemory ifTrue: [((envt isKindOf: Environment) and: [envt ~~ self]) ifTrue: [maybe _ true. value _ envt atOrBelow: key ifAbsent: [maybe _ false]. maybe ifTrue: [^ value]]] ifFalse: [onDisk add: envName]]]]. onDisk do: [:outName | (envt _ super at: outName ifAbsent: [nil]) ifNotNil: [ ((envt isKindOf: Environment) and: [envt ~~ self]) ifTrue: [maybe _ true. value _ envt atOrBelow: key ifAbsent: [maybe _ false]. maybe ifTrue: [^ value]]]]. ^ absentBlock value]! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/2/1999 19:07'! deepAssociationsDo: assnBlock "Compatibility hack -- find things in sub environments for now" | envt | self associationsDo: [:assn | (((envt _ assn value) isKindOf: Environment) and: [envt ~~ self]) ifTrue: [envt deepAssociationsDo: assnBlock] ifFalse: [assnBlock value: assn]]! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/21/1999 13:03'! environmentForCategory: catString "Smalltalk environmentForCategory:'Morphic'" "Accepts a category name which may be a symbol or a string, and which may have trailing parts of the form '-zort'. Returns the environment object of that name." | catName envt | catName _ (catString copyUpTo: $-) asSymbol. (Smalltalk kernelCategories includes: catName) ifTrue: [^ Smalltalk]. envt _ Smalltalk at: catName ifAbsent: [(self confirm: 'Use of the category name ' , catName , ' implies the need to create a new system category. Is this what you wish to do?') ifFalse: [self error: 'dismiss me']. Smalltalk makeSubEnvironmentNamed: catName]. (envt isKindOf: Environment) ifFalse: [self error: catName , ' cannot be used as an environment name.']. ^ envt! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/5/1999 11:58'! exportMethodFor: varName ^ varName , ' "Automatically generated during environmental reorganization" ^ ' , varName ! ! !Environment methodsFor: 'dictionary access' stamp: 'di 12/4/1999 15:41'! lenientScopeHas: varName ifTrue: assocBlock "Compatibility hack -- find things in sub environments for now" | assoc envt | (assoc _ self associationAt: varName ifAbsent: []) == nil ifFalse: [assocBlock value: assoc. ^ true]. self associationsDo: [:assn | (((envt _ assn value) isKindOf: Environment) and: [envt ~~ self]) ifTrue: [(envt lenientScopeHas: varName ifTrue: assocBlock) ifTrue: [^ true]]]. ^ false! ! !Environment methodsFor: 'dictionary access' stamp: 'ls 10/23/2000 14:12'! scopeFor: varName from: prior envtAndPathIfFound: envtAndPathBlock "Look up varName here or in any sub-environments, and also in any sub-environments of the outer environment. If found, evaluate pathBlock with a string giving the path for the access, and return the environment in which the variable was found. Return nil if the variable is not found. Call from outside with prior == nil. prior ~= nil prevents revisiting prior parts of the tree." | envt | "Might be right here -- null path." (self includesKey: varName) ifTrue: [^ envtAndPathBlock value: self value: String new]. "Might be in a sub-environment -- append envt name to downward path." self associationsDo: [:assn | (((envt _ assn value) isKindOf: Environment) and: [envt ~~ self and: [envt ~~ prior]]) ifTrue: [envt scopeFor: varName from: self envtAndPathIfFound: [:subEnvt :subPath | ^ envtAndPathBlock value: subEnvt value: assn key , ' ' , subPath]]]. "If not found, traverse outer environment." outerEnvt ifNil: [^ nil]. outerEnvt == prior ifTrue: [^ nil]. outerEnvt scopeFor: varName from: self envtAndPathIfFound: [:subEnvt :subPath | ^ envtAndPathBlock value: subEnvt value: subPath]. ! ! !Environment methodsFor: 'system conversion' stamp: 'di 12/4/1999 14:26'! browseIndirectRefs "Smalltalk browseIndirectRefs" | cm lits browseList foundOne allClasses n | browseList _ OrderedCollection new. allClasses _ OrderedCollection new. Smalltalk allClassesAnywhereDo: [:cls | allClasses addLast: cls]. 'Locating methods with indirect global references...' displayProgressAt: Sensor cursorPoint from: 0 to: allClasses size during: [:bar | n _ 0. allClasses do: [:cls | bar value: (n_ n+1). { cls. cls class } do: [:cl | cl selectors do: [:sel | cm _ cl compiledMethodAt: sel. lits _ cm literals. foundOne _ false. lits do: [:lit | lit class == Association ifTrue: [(lit value == cl or: [cl scopeHas: lit key ifTrue: [:ignored]]) ifFalse: [foundOne _ true]]]. foundOne ifTrue: [browseList add: cl name , ' ' , sel]]]]]. Smalltalk browseMessageList: browseList asSortedCollection name: 'Indirect Global References' autoSelect: nil! ! !Environment methodsFor: 'system conversion' stamp: 'di 12/23/1999 12:29'! rewriteIndirectRefs "Smalltalk rewriteIndirectRefs" "For all classes, identify all methods with references to globals outside their direct access path. For each of these, call another method to rewrite the source with proper references." | cm lits envtForVar envt foundOne allClasses n | envtForVar _ Dictionary new. "Dict of varName -> envt name" Smalltalk associationsDo: [:assn | (((envt _ assn value) isKindOf: Environment) and: [envt size < 500]) ifTrue: [envt associationsDo: [:a | envtForVar at: a key put: assn key]]]. "Allow compiler to compile refs to globals out of the direct reference path" Preferences enable: #lenientScopeForGlobals. allClasses _ OrderedCollection new. Smalltalk allClassesAnywhereDo: [:cls | allClasses addLast: cls]. 'Updating indirect global references in source code...' displayProgressAt: Sensor cursorPoint from: 0 to: allClasses size during: [:bar | n _ 0. allClasses do: [:cls | bar value: (n_ n+1). { cls. cls class } do: [:cl | cl selectors do: [:sel | cm _ cl compiledMethodAt: sel. lits _ cm literals. foundOne _ false. lits do: [:lit | lit class == Association ifTrue: [(lit value == cl or: [cl scopeHas: lit key ifTrue: [:ignored]]) ifFalse: [foundOne _ true]]]. foundOne ifTrue: [self rewriteSourceForSelector: sel inClass: cl using: envtForVar]]]. ]]. Preferences disable: #lenientScopeForGlobals. ! ! !Environment methodsFor: 'system conversion' stamp: 'di 2/7/2000 14:41'! rewriteSourceForSelector: selector inClass: aClass using: envtForVar "Rewrite the source code for the method in question so that all global references out of the direct access path are converted to indirect global references. This is done by parsing the source with a lenient parser able to find variables in any environment. Then the parse tree is consulted for the source code ranges of each reference that needs to be rewritten and the pattern to which it should be rewritten. Note that assignments, which will take the form envt setValueOf: #GlobalName to: ... may generate spurious message due to agglutination of keywords with the value expression." | code methodNode edits varName eName envt | code _ aClass sourceCodeAt: selector. methodNode _ Compiler new parse: code in: aClass notifying: nil. edits _ OrderedCollection new. methodNode encoder globalSourceRanges do: [:tuple | "{ varName. srcRange. store }" (aClass scopeHas: (varName _ tuple first asSymbol) ifTrue: [:ignored]) ifFalse: ["This is a remote global. Add it as reference to be edited." edits addLast: { varName. tuple at: 2. tuple at: 3 }]]. "Sort the edits by source position." edits _ edits asSortedCollection: [:a :b | a second first < b second first]. edits reverseDo: [:edit | varName _ edit first. (eName _ envtForVar at: varName ifAbsent: [nil]) ifNotNil: ["If varName is not already exported, define an export method" envt _ self at: eName. (envt class includesSelector: varName) ifFalse: [envt class compile: (self exportMethodFor: varName) classified: 'exports']. "Replace each access out of scope with a proper remote reference" code _ code copyReplaceFrom: edit second first to: edit second last with: eName , ' ' , varName]]. aClass compile: code classified: (aClass organization categoryOfElement: selector)! ! !Environment methodsFor: 'system conversion' stamp: 'ls 1/19/2001 13:15'! tallyIndirectRefs "Smalltalk tallyIndirectRefs" "For all classes, tally the number of references to globals outside their inherited environment. Then determine the 'closest' environment that resolves most of them. If the closest environment is different from the one in whick the class currently resides, then enter the class name with the tallies of its references to all other environments. Return a triplet: A dictionary of all classes for which this is so, with those tallies, A dictionary giving the classes that would be happier in each of the other categories, A list of the variable names sorted by number of occurrences." | tallies refs cm lits envtForVar envt envtRefs allRefs newCategories cat allClasses n | envtForVar _ Dictionary new. "Dict of varName -> envt name" allRefs _ Bag new. Smalltalk associationsDo: [:assn | (((envt _ assn value) isKindOf: Environment) and: [envt size < 500]) ifTrue: [envt associationsDo: [:a | envtForVar at: a key put: assn key]]]. tallies _ Dictionary new. allClasses _ OrderedCollection new. Smalltalk allClassesAnywhereDo: [:cls | allClasses addLast: cls]. 'Scanning methods with indirect global references...' displayProgressAt: Sensor cursorPoint from: 0 to: allClasses size during: [:bar | n _ 0. allClasses do: [:cls | bar value: (n_ n+1). refs _ Set new. { cls. cls class } do: [:cl | cl selectors do: [:sel | cm _ cl compiledMethodAt: sel. lits _ cm literals. lits do: [:lit | lit class == Association ifTrue: [(lit value == cl or: [cls canFindWithoutEnvironment: lit key]) ifFalse: [refs add: lit key]]]]]. envtRefs _ Bag new. refs asSet do: [:varName | envtRefs add: (envtForVar at: varName) withOccurrences: (refs occurrencesOf: varName). (envtRefs sortedCounts isEmpty or: [envtRefs sortedCounts first value == (Smalltalk keyAtValue: cls environment)]) ifFalse: [allRefs add: varName withOccurrences: (refs occurrencesOf: varName). tallies at: cls name put: envtRefs sortedCounts. Transcript cr; print: envtRefs sortedCounts; endEntry]]]]. newCategories _ Dictionary new. tallies associationsDo: [:assn | cat _ assn value first value. (newCategories includesKey: cat) ifFalse: [newCategories at: cat put: Array new]. newCategories at: cat put: ((newCategories at: cat) copyWith: assn key)]. ^ { tallies. newCategories. allRefs sortedCounts }! ! !Environment methodsFor: 'system conversion' stamp: 'di 12/23/1999 11:46'! transferBindingsNamedIn: nameList from: otherEnvt | cls | nameList do: [:name | cls _ otherEnvt at: name. self add: (otherEnvt associationAt: name). cls environment: self. otherEnvt removeKey: name]. ! ! !Environment methodsFor: 'printing' stamp: 'di 12/18/1999 15:19'! name ^ envtName ifNil: ['Environment ' , self hash printString]! ! !Environment methodsFor: 'printing' stamp: 'sma 6/1/2000 09:54'! printOn: aStream envtName ifNil: [aStream nextPutAll: self name] ifNotNil: [aStream nextPutAll: 'An Environment named '; nextPutAll: envtName]! ! !Environment methodsFor: 'fileIn/out' stamp: 'di 4/1/2000 10:57'! isInMemory self associationsDo: [:a | ^ a value isInMemory]. ^ true! ! !Environment methodsFor: 'fileIn/out' stamp: 'di 2/16/2000 23:36'! stillOut "Smalltalk stillOut" "Write transcript the names of the Environments in the list who are still out on disk." Transcript clear. Smalltalk associationsDo: [:assn | (assn value isKindOf: Environment) ifTrue: [Transcript cr; nextPutAll: assn key , (assn value isInMemory ifTrue: [': in'] ifFalse: [': out'])]]. Transcript endEntry! ! !Environment methodsFor: 'fileIn/out' stamp: 'di 3/24/2000 21:50'! storeAll "Write all Environments except me and the top one out as image segments." | firstToGo others | firstToGo _ #(VMConstruction Morphic Sound Network Balloon) collect: [:x | Smalltalk at: x]. others _ Smalltalk values select: [:value | (value isKindOf: Environment) and: [(firstToGo includes: value) not & (value ~~ Smalltalk)]]. firstToGo , others do: [:anEnv | anEnv storeSegment].! ! !Environment methodsFor: 'fileIn/out' stamp: 'di 2/16/2000 22:28'! storeSegment "Store my project out on the disk as an ImageSegment. Keep the outPointers in memory. Name it .seg." | is roots | is _ ImageSegment new. is segmentName: self name. roots _ OrderedCollection new: self size * 2. "roots addFirst: self." self valuesDo: [:value | value == self ifFalse: [roots addLast: value]. value class class == Metaclass ifTrue: [roots addLast: value class]]. is copyFromRootsLocalFileFor: roots sizeHint: 0. "NOTE: self is now an ISRootStub..." is state = #tooBig ifTrue: [^ false]. is extract. is state = #active ifFalse: [^ false]. is writeToFile: is segmentName. ^ true ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Environment class instanceVariableNames: ''! !Environment class methodsFor: 'system conversion' stamp: 'di 2/16/2000 11:49'! computePrerequisites | bigCats bigCat preReqs supCat dict kernelCategories | "We say one environment is a prerequisite of another if classes defined in the other inherit from classes in the first. Compute a dictionary with an entry for every non-kernel environment. That entry is another dictionary giving the names of any prerequisite environments and the list of classes that require it." "Environment computePrerequisites." " <-- inspect this " bigCats _ IdentityDictionary new. kernelCategories _ Environment new kernelCategories. Smalltalk allClasses do: [:cl | bigCat _ (cl category asString copyUpTo: '-' first) asSymbol. (kernelCategories includes: bigCat) ifTrue: [bigCat _ #Kernel]. bigCats at: cl name put: bigCat]. preReqs _ IdentityDictionary new. Smalltalk allClasses do: [:cl | cl superclass ifNotNil: [bigCat _ bigCats at: cl name. supCat _ bigCats at: cl superclass name. bigCat ~~ supCat ifTrue: [dict _ preReqs at: bigCat ifAbsent: [preReqs at: bigCat put: IdentityDictionary new]. dict at: supCat put: ((dict at: supCat ifAbsent: [Array new]) copyWith: cl name)]]]. ^ preReqs ! ! !Environment class methodsFor: 'system conversion' stamp: 'di 2/16/2000 12:43'! reorganizeEverything "Environment reorganizeEverything." | bigCat envt pool s | "First check for clashes between environment names and existing globals..." SystemOrganization categories do: [:cat | bigCat _ (cat asString copyUpTo: '-' first) asSymbol. (Smalltalk kernelCategories includes: bigCat) ifFalse: [(Smalltalk includesKey: bigCat) ifTrue: [^ self error: bigCat , ' cannot be used to name both a package and a class or other global variable. No reorganization will be attempted.']]]. (PopUpMenu confirm: 'Your image is about to be partitioned into environments. Many things may not work after this, so you should be working in a throw-away copy of your working image. Are you really ready to procede? (choose ''no'' to stop here safely)') ifFalse: [^ PopUpMenu notify: 'No changes were made']. Smalltalk newChanges: (ChangeSet new initialize name: 'Reorganization'). "Recreate the Smalltalk dictionary as the top-level Environment." Smalltalk _ SmalltalkEnvironment newFrom: Smalltalk. Smalltalk setName: #Smalltalk inOuterEnvt: nil. "Don't hang onto old copy of Smalltalk ." Smalltalk recreateSpecialObjectsArray. Smalltalk allClassesDo: [:c | c environment: nil. "Flush any old values"]. "Run through all categories making up new sub-environments" SystemOrganization categories do: [:cat | bigCat _ (cat asString copyUpTo: '-' first) asSymbol. (Smalltalk kernelCategories includes: bigCat) ifFalse: ["Not a kernel category ..." envt _ Smalltalk at: bigCat ifAbsent: ["... make up a new environment if necessary ..." Smalltalk makeSubEnvironmentNamed: bigCat]. "... and install the member classes in that category" envt transferBindingsNamedIn: (SystemOrganization listAtCategoryNamed: cat) from: Smalltalk]. ]. "Move all shared pools that are only referred to in sub environments" Smalltalk associationsDo: [:assn | ((pool _ assn value) isMemberOf: Dictionary) ifTrue: [s _ IdentitySet new. Smalltalk allClassesAnywhereDo: [:c | c sharedPools do: [:p | p == pool ifTrue: [s add: c environment]]]. (s size = 1 and: [(envt _ s someElement) ~~ Smalltalk]) ifTrue: [envt declare: assn key from: Smalltalk]]]. Smalltalk rewriteIndirectRefs. Smalltalk newChanges: (ChangeSet new initialize name: 'PostReorganization'). ChangeSorter gatherChangeSets. Preferences enable: #browserShowsPackagePane. ! ! Exception subclass: #Error instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !Error commentStamp: '' prior: 0! >From the ANSI standard: This protocol describes the behavior of instances of class Error. These are used to represent error conditions that prevent the normal continuation of processing. Actual error exceptions used by an application may be subclasses of this class. As Error is explicitly specified to be subclassable, conforming implementations must implement its behavior in a non-fragile manner. Additional notes: Error>defaultAction uses an explicit test for the presence of the Debugger class to decide whether or not it is in development mode. In the future, TFEI hopes to enhance the semantics of #defaultAction to improve support for pluggable default handlers.! !Error methodsFor: 'private' stamp: 'di 9/22/1999 19:20'! devDefaultAction Debugger openContext: initialContext label: self description contents: initialContext shortStack! ! !Error methodsFor: 'private' stamp: 'tfei 6/5/1999 00:09'! isDevelopmentEnvironmentPresent ^Smalltalk includesKey: #Debugger! ! !Error methodsFor: 'private' stamp: 'tfei 6/5/1999 00:26'! runtimeDefaultAction "Dump the stack trace to a log file, then exit the program (image)." | file | file := FileStream newFileNamed: ('error', Utilities dateTimeSuffix, FileDirectory dot, 'log') asFileName. Smalltalk timeStamp: file. (thisContext sender stackOfSize: 20) do: [:ctx | file cr. ctx printOn: file]. file close. Smalltalk snapshot: false andQuit: true! ! !Error methodsFor: 'exceptionDescription' stamp: 'tfei 6/5/1999 00:10'! defaultAction "The current computation is terminated. The cause of the error should be logged or reported to the user. If the program is operating in an interactive debugging environment the computation should be suspended and the debugger activated." self isDevelopmentEnvironmentPresent ifTrue: [self devDefaultAction] ifFalse: [self runtimeDefaultAction]! ! UpdatingThreePhaseButtonMorph subclass: #EtoyUpdatingThreePhaseButtonMorph instanceVariableNames: '' classVariableNames: 'CheckedForm MouseDownForm UncheckedForm ' poolDictionaries: '' category: 'Morphic-Widgets'! !EtoyUpdatingThreePhaseButtonMorph commentStamp: '' prior: 0! A slight variation wherein the actionSelector and getSelector both take argument(s).! !EtoyUpdatingThreePhaseButtonMorph methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 12:18'! step | newBoolean | state == #pressed ifTrue: [^ self]. newBoolean _ target perform: getSelector withArguments: arguments. newBoolean == self isOn ifFalse: [self state: (newBoolean ifTrue: [#on] ifFalse: [#off])] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EtoyUpdatingThreePhaseButtonMorph class instanceVariableNames: ''! !EtoyUpdatingThreePhaseButtonMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 13:34'! checkBox "Answer a button pre-initialized with checkbox images." "(Form extent: 12@12 depth: 32) morphEdit" CheckedForm ifNil: [ self setForms ]. ^self new onImage: CheckedForm; pressedImage: MouseDownForm; offImage: UncheckedForm; extent: CheckedForm extent; yourself ! ! !EtoyUpdatingThreePhaseButtonMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 8/8/2000 13:34'! setForms CheckedForm _ (Form extent: 12@12 depth: 32 fromArray: #( 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 4278190081 4278190081 4278190081 0 0 0 0 0 0 0 0 4278190081 2003331177 4278190081 4278190081 0 0 0 0 0 0 0 4278190081 2003331177 0 4278190081 4278190081 0 0 0 0 0 0 4278190081 2003331177 0 0 4278190081 4278190081 0 4278190081 0 0 0 4278190081 2003331177 0 0 0 4278190081 4278190081 0 2003331177 4278190081 0 4278190081 2003331177 0 0 0 0 4278190081 4278190081 0 0 2003331177 4278190081 2003331177 0 0 0 0 0 4278190081 4278190081 0 0 0 2003331177 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081) offset: 0@0). MouseDownForm _ UncheckedForm _ (Form extent: 12@12 depth: 32 fromArray: #( 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 0 0 0 0 0 0 0 0 0 0 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081 4278190081) offset: 0@0)! ! Object subclass: #EventHandler instanceVariableNames: 'mouseDownRecipient mouseDownSelector mouseMoveRecipient mouseMoveSelector mouseStillDownRecipient mouseStillDownSelector mouseUpRecipient mouseUpSelector mouseEnterRecipient mouseEnterSelector mouseLeaveRecipient mouseLeaveSelector mouseEnterDraggingRecipient mouseEnterDraggingSelector mouseLeaveDraggingRecipient mouseLeaveDraggingSelector keyStrokeRecipient keyStrokeSelector valueParameter startDragRecipient startDragSelector doubleClickSelector doubleClickRecipient clickSelector clickRecipient ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Events'! !EventHandler commentStamp: '' prior: 0! Events in Morphic originate in a Hand, pass to a target morph, and are then dispatched by an EventHandler. EventHandlers support redirection of mouse and keyboard activity by specifying and independent recipient object and message selector for each of the possible events. In addition each eventHandler can supply an optional value parameter for distinguishing between, eg, events from a number of otherwise identical source morphs. The basic protocol of an event handler is to receive a message of the form mouseDown: event in: targetMorph and redirect this as one of mouseDownRecipient perform: mouseDownSelector0 mouseDownRecipient perform: mouseDownSelector1 with: event mouseDownRecipient perform: mouseDownSelector2 with: event with: targetMorph mouseDownRecipient perform: mouseDownSelector3 with: event with: targetMorph with: valueParameter depending on the arity of the mouseDownSelector. ! !EventHandler methodsFor: 'initialization' stamp: 'ar 10/7/2000 22:50'! adaptToWorld: aWorld "If any of my recipients refer to a world or a hand, make them now refer to the corresponding items in the new world. (instVarNamed: is slow, later use perform of two selectors.)" | value newValue | #(mouseDownRecipient mouseStillDownRecipient mouseUpRecipient mouseEnterRecipient mouseLeaveRecipient mouseEnterDraggingRecipient mouseLeaveDraggingRecipient clickRecipient doubleClickRecipient startDragRecipient keyStrokeRecipient valueParameter) do: [:aName | (value _ self instVarNamed: aName asString) ifNotNil: [newValue _ nil. value isMorph ifTrue: [value isWorldMorph ifTrue: [newValue _ aWorld]. value isHandMorph ifTrue: [newValue _ aWorld primaryHand]] ifFalse: [(value isKindOf: Presenter) ifTrue: [newValue _ aWorld presenter]]. (newValue notNil and: [newValue ~~ value]) ifTrue: [self instVarNamed: aName asString put: newValue]]]! ! !EventHandler methodsFor: 'initialization' stamp: 'ar 10/25/2000 17:32'! forgetDispatchesTo: aSelector "aSelector is no longer implemented by my corresponding Player, so don't call it any more" mouseDownSelector == aSelector ifTrue: [mouseDownRecipient _ mouseDownSelector _ nil]. mouseMoveSelector == aSelector ifTrue: [mouseMoveRecipient _ mouseMoveSelector _ nil]. mouseStillDownSelector == aSelector ifTrue: [mouseStillDownRecipient _ mouseStillDownSelector _ nil]. mouseUpSelector == aSelector ifTrue: [mouseUpRecipient _ mouseUpSelector _ nil]. mouseEnterSelector == aSelector ifTrue: [mouseEnterRecipient _ mouseEnterSelector _ nil]. mouseLeaveSelector == aSelector ifTrue: [mouseLeaveRecipient _ mouseLeaveSelector _ nil]. mouseEnterDraggingSelector == aSelector ifTrue: [mouseEnterDraggingRecipient _ mouseEnterDraggingSelector _ nil]. mouseLeaveDraggingSelector == aSelector ifTrue: [mouseLeaveDraggingRecipient _ mouseLeaveDraggingSelector _ nil]. clickSelector == aSelector ifTrue: [clickRecipient _ clickSelector _ nil]. doubleClickSelector == aSelector ifTrue: [doubleClickRecipient _ doubleClickSelector _ nil]. keyStrokeSelector == aSelector ifTrue: [keyStrokeRecipient _ keyStrokeSelector _ nil].! ! !EventHandler methodsFor: 'initialization' stamp: 'ar 10/25/2000 17:30'! on: eventName send: selector to: recipient eventName = #mouseDown ifTrue: [mouseDownRecipient _ recipient. mouseDownSelector _ selector. ^ self]. eventName = #mouseMove ifTrue: [mouseMoveRecipient _ recipient. mouseMoveSelector _ selector. ^ self]. eventName = #mouseStillDown ifTrue: [mouseStillDownRecipient _ recipient. mouseStillDownSelector _ selector. ^ self]. eventName = #mouseUp ifTrue: [mouseUpRecipient _ recipient. mouseUpSelector _ selector. ^ self]. eventName = #mouseEnter ifTrue: [mouseEnterRecipient _ recipient. mouseEnterSelector _ selector. ^ self]. eventName = #mouseLeave ifTrue: [mouseLeaveRecipient _ recipient. mouseLeaveSelector _ selector. ^ self]. eventName = #mouseEnterDragging ifTrue: [mouseEnterDraggingRecipient _ recipient. mouseEnterDraggingSelector _ selector. ^ self]. eventName = #mouseLeaveDragging ifTrue: [mouseLeaveDraggingRecipient _ recipient. mouseLeaveDraggingSelector _ selector. ^ self]. eventName = #click ifTrue: [clickRecipient _ recipient. clickSelector _ selector. ^ self]. eventName = #doubleClick ifTrue: [doubleClickRecipient _ recipient. doubleClickSelector _ selector. ^ self]. eventName = #startDrag ifTrue: [startDragRecipient _ recipient. startDragSelector _ selector. ^ self]. eventName = #keyStroke ifTrue: [keyStrokeRecipient _ recipient. keyStrokeSelector _ selector. ^ self]. self error: 'Event name, ' , eventName , ' is not recognizable.' ! ! !EventHandler methodsFor: 'initialization'! on: eventName send: selector to: recipient withValue: value selector numArgs = 3 ifFalse: [self halt: 'Warning: value parameters are passed as last of 3 arguments']. self on: eventName send: selector to: recipient. valueParameter _ value ! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/7/2000 22:56'! handlesClickOrDrag: evt clickRecipient ifNotNil:[^true]. doubleClickRecipient ifNotNil:[^true]. startDragRecipient ifNotNil:[^true]. ^false! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/28/2000 22:17'! handlesKeyboard: evt keyStrokeRecipient ifNotNil: [^ true]. ^ false! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/7/2000 22:56'! handlesMouseDown: evt mouseDownRecipient ifNotNil: [^ true]. mouseStillDownRecipient ifNotNil: [^ true]. mouseUpRecipient ifNotNil: [^ true]. (self handlesClickOrDrag: evt) ifTrue:[^true]. ^ false! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/25/2000 17:33'! handlesMouseMove: evt ^mouseMoveRecipient notNil and:[mouseMoveSelector notNil]! ! !EventHandler methodsFor: 'testing'! handlesMouseOver: evt mouseEnterRecipient ifNotNil: [^ true]. mouseLeaveRecipient ifNotNil: [^ true]. ^ false! ! !EventHandler methodsFor: 'testing' stamp: 'di 9/15/1998 16:35'! handlesMouseOverDragging: evt mouseEnterDraggingRecipient ifNotNil: [^ true]. mouseLeaveDraggingRecipient ifNotNil: [^ true]. ^ false! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/22/2000 17:05'! handlesMouseStillDown: evt ^mouseStillDownRecipient notNil and:[mouseStillDownSelector notNil]! ! !EventHandler methodsFor: 'events' stamp: 'ar 10/7/2000 22:55'! click: event fromMorph: sourceMorph "This message is sent only when double clicks are handled." ^ self send: clickSelector to: clickRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events' stamp: 'LC 2/14/2000 08:38'! doubleClick: event fromMorph: sourceMorph ^ self send: doubleClickSelector to: doubleClickRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events'! keyStroke: event fromMorph: sourceMorph ^ self send: keyStrokeSelector to: keyStrokeRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events' stamp: 'ar 10/7/2000 22:54'! mouseDown: event fromMorph: sourceMorph "Take double-clicks into account." ((self handlesClickOrDrag: event) and:[event redButtonPressed]) ifTrue:[ event hand waitForClicksOrDrag: sourceMorph event: event. ]. ^self send: mouseDownSelector to: mouseDownRecipient withEvent: event fromMorph: sourceMorph. ! ! !EventHandler methodsFor: 'events'! mouseEnter: event fromMorph: sourceMorph ^ self send: mouseEnterSelector to: mouseEnterRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events' stamp: 'di 9/15/1998 16:35'! mouseEnterDragging: event fromMorph: sourceMorph ^ self send: mouseEnterDraggingSelector to: mouseEnterDraggingRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events'! mouseLeave: event fromMorph: sourceMorph ^ self send: mouseLeaveSelector to: mouseLeaveRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events' stamp: 'di 9/15/1998 16:35'! mouseLeaveDragging: event fromMorph: sourceMorph ^ self send: mouseLeaveDraggingSelector to: mouseLeaveDraggingRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events' stamp: 'ar 10/25/2000 17:32'! mouseMove: event fromMorph: sourceMorph ^ self send: mouseMoveSelector to: mouseMoveRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events'! mouseStillDown: event fromMorph: sourceMorph ^ self send: mouseStillDownSelector to: mouseStillDownRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events'! mouseUp: event fromMorph: sourceMorph ^ self send: mouseUpSelector to: mouseUpRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'events'! send: selector to: recipient withEvent: event fromMorph: sourceMorph | arity | recipient ifNil: [^ self]. arity _ selector numArgs. arity = 0 ifTrue: [^ recipient perform: selector]. arity = 1 ifTrue: [^ recipient perform: selector with: event]. arity = 2 ifTrue: [^ recipient perform: selector with: event with: sourceMorph]. arity = 3 ifTrue: [^ recipient perform: selector with: event with: sourceMorph with: valueParameter]. self error: 'Event handling selectors must be Symbols and take 0-3 arguments'! ! !EventHandler methodsFor: 'events' stamp: 'mir 5/23/2000 17:43'! startDrag: event fromMorph: sourceMorph ^ self send: startDragSelector to: startDragRecipient withEvent: event fromMorph: sourceMorph! ! !EventHandler methodsFor: 'access' stamp: 'ar 10/25/2000 17:33'! allRecipients "Answer a list, without duplication, of all the objects serving as recipients to any of the events I handle. Intended for debugging/documentation use only" | aList | aList _ OrderedCollection with: mouseDownRecipient with: mouseStillDownRecipient with: mouseUpRecipient with: mouseEnterRecipient with: mouseLeaveRecipient. aList addAll: (OrderedCollection with: mouseEnterDraggingRecipient with: mouseLeaveDraggingRecipient with: doubleClickRecipient with: keyStrokeRecipient). aList add: mouseMoveRecipient. ^ (aList copyWithout: nil) asSet asArray! ! !EventHandler methodsFor: 'access' stamp: 'ar 10/25/2000 17:34'! firstMouseSelector "Answer the selector corresponding to the first mouse-handling selector fielded. Created in support of providing balloon-help for halo handles, triggered by the selector handled" mouseDownSelector ifNotNil: [^ mouseDownSelector]. mouseMoveSelector ifNotNil:[^mouseMoveSelector]. mouseStillDownSelector ifNotNil: [^ mouseStillDownSelector]. mouseUpSelector ifNotNil: [^ mouseUpSelector]. mouseEnterSelector ifNotNil: [^ mouseEnterSelector]. mouseLeaveSelector ifNotNil: [^ mouseLeaveSelector]. mouseEnterDraggingSelector ifNotNil: [^ mouseEnterDraggingSelector]. mouseLeaveDraggingSelector ifNotNil: [^ mouseLeaveDraggingSelector]. doubleClickSelector ifNotNil: [^ doubleClickSelector]. ^ nil! ! !EventHandler methodsFor: 'access' stamp: 'ar 10/25/2000 17:31'! messageList "Return a list of 'Class selector' for each message I can send. tk 9/13/97" | list | list _ SortedCollection new. mouseDownRecipient ifNotNil: [list add: (mouseDownRecipient class classThatUnderstands: mouseDownSelector) name , ' ', mouseDownSelector]. mouseMoveRecipient ifNotNil: [list add: (mouseMoveRecipient class classThatUnderstands: mouseMoveSelector) name , ' ', mouseMoveSelector]. mouseStillDownRecipient ifNotNil: [list add: (mouseStillDownRecipient class classThatUnderstands: mouseStillDownSelector) name , ' ', mouseStillDownSelector]. mouseUpRecipient ifNotNil: [list add: (mouseUpRecipient class classThatUnderstands: mouseUpSelector) name , ' ', mouseUpSelector]. mouseEnterRecipient ifNotNil: [list add: (mouseEnterRecipient class classThatUnderstands: mouseEnterSelector) name , ' ', mouseEnterSelector]. mouseLeaveRecipient ifNotNil: [list add: (mouseLeaveRecipient class classThatUnderstands: mouseLeaveSelector) name , ' ', mouseLeaveSelector]. mouseEnterDraggingRecipient ifNotNil: [list add: (mouseEnterDraggingRecipient class classThatUnderstands: mouseEnterDraggingSelector) name , ' ', mouseEnterDraggingSelector]. mouseLeaveDraggingRecipient ifNotNil: [list add: (mouseLeaveDraggingRecipient class classThatUnderstands: mouseLeaveDraggingSelector) name , ' ', mouseLeaveDraggingSelector]. doubleClickRecipient ifNotNil: [list add: (doubleClickRecipient class classThatUnderstands: doubleClickSelector) name , ' ', doubleClickSelector]. keyStrokeRecipient ifNotNil: [list add: (keyStrokeRecipient class classThatUnderstands: keyStrokeSelector) name , ' ', keyStrokeSelector]. ^ list! ! !EventHandler methodsFor: 'access' stamp: 'di 9/14/1998 08:32'! mouseDownSelector ^ mouseDownSelector! ! !EventHandler methodsFor: 'access' stamp: 'ar 10/25/2000 18:27'! mouseStillDownRecipient ^mouseStillDownRecipient! ! !EventHandler methodsFor: 'access' stamp: 'ar 10/25/2000 18:27'! mouseStillDownSelector ^mouseStillDownSelector! ! !EventHandler methodsFor: 'access' stamp: 'di 9/14/1998 08:32'! mouseUpSelector ^ mouseUpSelector! ! !EventHandler methodsFor: 'access' stamp: 'LC 2/14/2000 09:01'! printOn: aStream | aVal recipients | super printOn: aStream. #('mouseDownSelector' 'mouseStillDownSelector' 'mouseUpSelector' 'mouseEnterSelector' 'mouseLeaveSelector' 'mouseEnterDraggingSelector' 'mouseLeaveDraggingSelector' 'doubleClickSelector' 'keyStrokeSelector') do: [:aName | (aVal _ self instVarNamed: aName) ~~ nil ifTrue: [aStream nextPutAll: '; ', aName, '=', aVal]]. (recipients _ self allRecipients) size > 0 ifTrue: [aStream nextPutAll: ' recipients: '. recipients printOn: aStream]! ! !EventHandler methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:45'! convertToCurrentVersion: varDict refStream: smartRefStrm "20 dec 2000 - only a few (old) conversion exists" varDict at: 'mouseEnterLadenRecipient' ifPresent: [ :x | mouseEnterDraggingRecipient _ x]. varDict at: 'mouseEnterLadenSelector' ifPresent: [ :x | mouseEnterDraggingSelector _ x]. varDict at: 'mouseLeaveLadenRecipient' ifPresent: [ :x | mouseLeaveDraggingRecipient _ x]. varDict at: 'mouseLeaveLadenSelector' ifPresent: [ :x | mouseLeaveDraggingSelector _ x]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !EventHandler methodsFor: 'fixups' stamp: 'RAA 7/12/2000 14:54'! fixAlansOldEventHandlers (#(programmedMouseUp:for: programmedMouseUp:for:with:) includes: mouseUpSelector) ifFalse: [^self]. mouseDownSelector ifNotNil: [^self]. mouseUpRecipient addMouseUpActionWith: ( mouseUpRecipient valueOfProperty: #mouseUpCodeToRun ifAbsent: [valueParameter] ) ! ! !EventHandler methodsFor: 'copying' stamp: 'tk 1/22/2001 17:43'! veryDeepFixupWith: deepCopier | old | "ALL inst vars were weakly copied. If they were in the tree being copied, fix them up, otherwise point to the originals!!!!" super veryDeepFixupWith: deepCopier. 1 to: self class instSize do: [:ii | old _ self instVarAt: ii. self instVarAt: ii put: (deepCopier references at: old ifAbsent: [old])]. ! ! !EventHandler methodsFor: 'copying' stamp: 'tk 1/22/2001 17:39'! veryDeepInner: deepCopier "ALL fields are weakly copied!! Can't duplicate an object by duplicating a button that activates it. See DeepCopier." super veryDeepInner: deepCopier. "just keep old pointers to all fields" clickRecipient _ clickRecipient.! ]style[(25 108 10 111)f1b,f1,f1LDeepCopier Comment;,f1! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EventHandler class instanceVariableNames: ''! !EventHandler class methodsFor: 'fixups' stamp: 'RAA 7/12/2000 15:03'! fixAlansOldEventHandlers " EventHandler fixAlansOldEventHandlers " | allHandlers | allHandlers _ EventHandler allInstances select: [ :each | (#(programmedMouseUp:for: programmedMouseUp:for:with:) includes: each mouseUpSelector) and: [each mouseDownSelector isNil] ]. allHandlers do: [ :each | each fixAlansOldEventHandlers ].! ! Object subclass: #EventModel instanceVariableNames: 'myEvents ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Objects'! !EventModel commentStamp: '' prior: 0! This is an abstract superclass for models which want to use the #when:send:to: event system in an efficient way. Instances of this class keep their own list of event symbols and registered events instead of using the global class variable EventsFields in class Object. Structure: myEvents Dictionary -- set of event symbols and registered listeners! !EventModel methodsFor: 'events' stamp: 'sma 3/11/2000 10:37'! myEvents ^ myEvents! ! !EventModel methodsFor: 'events' stamp: 'sma 3/11/2000 10:37'! myEvents: aDictionaryOrNil myEvents _ aDictionaryOrNil! ! AlignmentMorph subclass: #EventRecorderMorph instanceVariableNames: 'tape state time deltaTime recHand playHand lastEvent lastDelta tapeStream saved statusLight voiceRecorder startSoundEvent recordMeter caption journalFile ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !EventRecorderMorph commentStamp: '' prior: 0! During recording, the EventRecorder subscribes to all events of the normal morphic hand, and saves them as they occur. For replay, a second playback hand is created that reads events from the recorder and plays them back in the world. The EventRecorder began with the work of Ted Kaehler and John Malone. This was then signifcantly expanded by Leandro Caniglia and Valeria Murgia as a tutorial aid for the Morphic Wrapper project. Since that time, I have... Changed to a simple inboard array for the tape (event storage). Provided the ability to condense linear mouse movement with interpolation at replay. Made simple provisions for wrap-around of the millisecond clock. Eliminated step methods in favor of using the processEvents cycle in the playback hand. Provided a pause/resume mechanism that is capable of surviving project changes. Added the ability to spawn a simple 'play me' button that can be saved as a morph. Caused the playback hand to display its cursor double size for visibility. Integrated a voice recorder with on-the-fly compression. This currently does NOT survive project changes, not is its data stored on the tape. Right now it can only be saved by saving the entire recorder as a morph. This will be fixed by adding a startSound event at each project change. We will also convert read/write file to use saveOnFile. Added a journal file facility for recording sequences that end in a crash. The above two features can be engaged via the ER's morph menu. - Dan Ingalls 3/6/99! !EventRecorderMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:14'! addButtons | r b w | caption ifNotNil: ["Special setup for play-only interface" r _ AlignmentMorph newRow vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter; minCellSize: 4; color: Color blue. r addMorphBack: (SimpleButtonMorph new target: self; label: caption; actionSelector: #play). r addMorphBack: (Morph new extent: 4@4; color: Color transparent). r addMorphBack: (statusLight _ EllipseMorph new extent: 11 @ 11; color: Color green; borderWidth: 0). r addMorphBack: (Morph new extent: 4@4; color: Color transparent). ^ self addMorphBack: r]. "record - stop - play" r _ AlignmentMorph newRow vResizing: #shrinkWrap; minCellSize: 4; color: Color blue. r addMorphBack: (b _ self buttonFor: #record). w _ b width. r addMorphBack: (AlignmentMorph newSpacer: Color transparent). r addMorphBack: ((self buttonFor: #stop) width: w). r addMorphBack: (AlignmentMorph newSpacer: Color transparent). r addMorphBack: ((self buttonFor: #play) width: w). self addMorphBack: r. "read file - write file" r _ AlignmentMorph newRow vResizing: #shrinkWrap; minCellSize: 4; color: Color blue. r addMorphBack: (b _ self buttonFor: #writeTape). w _ b width. r addMorphBack: (AlignmentMorph newSpacer: Color transparent). r addMorphBack: ((self buttonFor: #readTape) width: w). self addMorphBack: r. "rewind - light - reset" r _ AlignmentMorph newRow vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter; minCellSize: 4; color: Color blue. r addMorphBack: (b _ self buttonFor: #shrink). w _ b width. r addMorphBack: (AlignmentMorph newSpacer: Color transparent). r addMorphBack: (statusLight _ EllipseMorph new extent: 11 @ 11; color: Color green; borderWidth: 0). r addMorphBack: (AlignmentMorph newSpacer: Color transparent). r addMorphBack: ((self buttonFor: #button) width: w). self addMorph: r! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'di 3/6/1999 15:47'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'add voice controls' action: #addVoiceControls. aCustomMenu add: 'add journal file' action: #addJournalFile. ! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'ar 10/25/2000 21:42'! addJournalFile "In case there is a chance of not regaining control to stop recording and save a file, the EventRecorder can write directly to file as it is recording. This is useful for capturing a sequence that results in a nasty crash." journalFile ifNotNil: [journalFile close]. journalFile _ FileStream newFileNamed: 'EventRecorder.tape'. journalFile nextPutAll:'Event Tape v1 ASCII'; cr.! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'RAA 1/2/2001 10:35'! addVoiceControls | levelSlider r meterBox | voiceRecorder _ SoundRecorder new desiredSampleRate: 11025.0; "<==try real hard to get the low rate" codec: (GSMCodec new). "<--this should compress better than ADPCM.. is it too slow?" "codec: (ADPCMCodec new initializeForBitsPerSample: 4 samplesPerFrame: 0)." levelSlider _ SimpleSliderMorph new color: color; extent: 100@2; target: voiceRecorder; actionSelector: #recordLevel:; adjustToValue: voiceRecorder recordLevel. r _ AlignmentMorph newRow color: color; layoutInset: 0; wrapCentering: #center; cellPositioning: #leftCenter; hResizing: #shrinkWrap; vResizing: #rigid; height: 24. r addMorphBack: (StringMorph contents: '0 '). r addMorphBack: levelSlider. r addMorphBack: (StringMorph contents: ' 10'). self addMorphBack: r. meterBox _ Morph new extent: 102@18; color: Color gray. recordMeter _ Morph new extent: 1@16; color: Color yellow. recordMeter position: meterBox topLeft + (1@1). meterBox addMorph: recordMeter. r _ AlignmentMorph newRow vResizing: #shrinkWrap. r addMorphBack: meterBox. self addMorphBack: r. ! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'LC 12/20/1998 11:25'! buttonFor: aSymbol ^ SimpleButtonMorph new target: self; label: aSymbol asString; actionSelector: aSymbol! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'di 2/17/1999 17:44'! caption: butnCaption voiceRecorder: butnRecorder tape: butnTape caption _ butnCaption. voiceRecorder _ butnRecorder. tape _ butnTape! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:14'! initialize super initialize. saved _ true. borderWidth _ 2. borderColor _ #raised. color _ Color red. self listDirection: #topToBottom. self wrapCentering: #center; cellPositioning: #topCenter. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self layoutInset: 2. self minCellSize: 4. self addButtons. ! ! !EventRecorderMorph methodsFor: 'accessing' stamp: 'LC 12/23/1998 12:48'! button: label ^ self allMorphs detect: [:one | (one isKindOf: SimpleButtonMorph) and: [one label = label]] ifNone: []! ! !EventRecorderMorph methodsFor: 'accessing' stamp: 'RAA 12/13/2000 12:51'! recTime self flag: #bob. "not sent and no longer working" "| ms | ms _ 0. tape do: [:cell | ms _ ms + cell key]. ^ String streamContents: [:stream | (Time fromSeconds: ms // 1000) print24: true on: stream]"! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'di 2/17/1999 17:45'! button "Make a simple button interface for replay only" | butnCaption erm | butnCaption _ FillInTheBlank request: 'Caption for this butn?' initialAnswer: 'play'. butnCaption isEmpty ifTrue: [^ self]. erm _ (EventRecorderMorph basicNew caption: butnCaption voiceRecorder: voiceRecorder copy tape: tape) initialize. self world primaryHand attachMorph: erm! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'ar 10/25/2000 22:38'! condense "Shorten the tape by deleting mouseMove events that can just as well be interpolated later at playback time." | e1 e2 t1 t2 e3 t3 | "e1, e2, and e3 are three consecutive events on the tape. t1, t2, and t3 are the associated time steps for each of them." tape _ Array streamContents: [:tStream | e1 _ e2 _ e3 _ nil. t1 _ t2 _ t3 _ nil. 1 to: tape size do:[:i| e1 _ e2. t1 _ t2. e2 _ e3. t2 _ t3. e3 _ tape at: i. t3 _ e3 timeStamp. ((e1 ~~ nil and: [(e2 type == #mouseMove) & (e1 type == #mouseMove or: [e3 type == #mouseMove])]) and: ["Middle point within 3 pixels of mean of outer two" e2 position onLineFrom: e1 position to: e3 position within: 2.5]) ifTrue: ["Delete middle mouse move event. Absorb its time into e3" e2 _ e1. t2 _ t1] ifFalse: [e1 ifNotNil: [tStream nextPut: (e1 copy setTimeStamp: t1)]]]. e2 ifNotNil: [tStream nextPut: (e2 copy setTimeStamp: t2)]. e3 ifNotNil: [tStream nextPut: (e3 copy setTimeStamp: t3)]]. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'ar 10/25/2000 23:00'! play self isInWorld ifFalse: [^ self]. self stop. tape ifNil: [^ self]. tapeStream _ ReadStream on: tape. self resumePlayIn: self world. statusLight color: Color yellow. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'ar 10/25/2000 20:24'! record self isInWorld ifFalse: [^ self]. self stop. self writeCheck. self addJournalFile. tapeStream _ WriteStream on: (Array new: 10000). self resumeRecordIn: self world. statusLight color: Color red. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'ar 10/25/2000 23:00'! shrink "Shorten the tape by deleting mouseMove events that can just as well be interpolated later at playback time." | oldSize priorSize | self writeCheck. oldSize _ priorSize _ tape size. [self condense. tape size < priorSize] whileTrue: [priorSize _ tape size]. PopUpMenu notify: oldSize printString , ' events reduced to ' , tape size printString. voiceRecorder ifNotNil: [voiceRecorder suppressSilence]. saved _ false. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 1/2/2001 15:45'! step (state == #record and: [voiceRecorder notNil]) ifTrue: [ recordMeter width: (voiceRecorder meterLevel + 1). ]. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 1/2/2001 10:28'! stepTime ^500 ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 1/2/2001 15:45'! stop state = #record ifTrue: [tape _ tapeStream contents. saved _ false]. journalFile ifNotNil: [journalFile close]. self pauseIn: self world. tapeStream _ nil. state _ nil. statusLight color: Color green. recordMeter ifNotNil: [recordMeter width: 1]. self checkTape.! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 1/2/2001 10:25'! wantsSteps ^true ! ! !EventRecorderMorph methodsFor: 'pause/resume' stamp: 'ar 10/25/2000 20:04'! pauseIn: aWorld "Suspend playing or recording, either as part of a stop command, or as part of a project switch, after which it will be resumed." state = #play ifTrue: [state _ #suspendedPlay. playHand delete. aWorld removeHand: playHand. playHand _ nil]. state = #record ifTrue: [state _ #suspendedRecord. recHand removeEventListener: self. recHand _ nil]. voiceRecorder ifNotNil: [voiceRecorder pause. startSoundEvent ifNotNil: [startSoundEvent argument: voiceRecorder recordedSound. voiceRecorder clearRecordedSound. startSoundEvent _ nil]]. ! ! !EventRecorderMorph methodsFor: 'pause/resume' stamp: 'di 4/20/1999 16:29'! resumeIn: aWorld "Resume playing or recording after a project switch." state = #suspendedPlay ifTrue: [self resumePlayIn: aWorld]. state = #suspendedRecord ifTrue: [self resumeRecordIn: aWorld]. ! ! !EventRecorderMorph methodsFor: 'pause/resume' stamp: 'ar 10/25/2000 20:58'! resumePlayIn: aWorld playHand _ HandMorphForReplay new recorder: self. playHand position: tapeStream peek position. aWorld addHand: playHand. playHand newKeyboardFocus: aWorld. playHand userInitials: 'play' andPicture: nil. lastEvent _ nil. lastDelta _ 0@0. state _ #play. self synchronize. ! ! !EventRecorderMorph methodsFor: 'pause/resume' stamp: 'ar 10/26/2000 00:50'! resumeRecordIn: aWorld recHand _ aWorld activeHand ifNil: [aWorld primaryHand]. recHand newKeyboardFocus: aWorld. recHand addEventListener: self. lastEvent _ nil. state _ #record. voiceRecorder ifNotNil: [voiceRecorder clearRecordedSound. voiceRecorder resumeRecording. startSoundEvent _ MorphicUnknownEvent new setType: #startSound argument: nil hand: nil stamp: Time millisecondClockValue. tapeStream nextPut: startSoundEvent]. self synchronize. ! ! !EventRecorderMorph methodsFor: 'event handling' stamp: 'ar 10/25/2000 21:26'! handleListenEvent: anEvent "Record the given event" (state == #record and:[anEvent hand == recHand]) ifFalse:[^self]. anEvent = lastEvent ifTrue: [^ self]. (anEvent isKeyboard and:[anEvent keyValue = 27 "esc"]) ifTrue: [^ self stop]. time _ anEvent timeStamp. tapeStream nextPut: (anEvent copy setHand: nil). journalFile ifNotNil: [journalFile store: anEvent; cr; flush]. lastEvent _ anEvent.! ! !EventRecorderMorph methodsFor: 'event handling' stamp: 'ar 10/26/2000 00:50'! nextEventToPlay "Return the next event when it is time to be replayed. If it is not yet time, then return an interpolated mouseMove. Return nil if nothing has happened. Return an EOF event if there are no more events to be played." | nextEvent now nextTime lastP delta | (tapeStream == nil or:[tapeStream atEnd]) ifTrue:[^MorphicUnknownEvent new setType: #EOF argument: nil]. now _ Time millisecondClockValue. nextEvent _ tapeStream next. deltaTime ifNil:[deltaTime _ now - nextEvent timeStamp]. nextTime _ nextEvent timeStamp + deltaTime. now < time ifTrue:["clock rollover" time _ now. deltaTime _ nil. ^nil "continue it on next cycle"]. time _ now. (now >= nextTime) ifTrue:[ nextEvent _ nextEvent copy setTimeStamp: nextTime. nextEvent isMouse ifTrue:[lastEvent _ nextEvent] ifFalse:[lastEvent _ nil]. ^nextEvent]. tapeStream skip: -1. "Not time for the next event yet, but interpolate the mouse. This allows tapes to be compressed when velocity is fairly constant." lastEvent ifNil: [^ nil]. lastP _ lastEvent position. delta _ (nextEvent position - lastP) * (now - lastEvent timeStamp) // (nextTime - lastEvent timeStamp). delta = lastDelta ifTrue: [^ nil]. "No movement" lastDelta _ delta. ^MouseMoveEvent new setType: #mouseMove startPoint: lastEvent position endPoint: lastP + delta trail: #() buttons: lastEvent buttons hand: nil stamp: now.! ! !EventRecorderMorph methodsFor: 'event handling' stamp: 'ar 10/25/2000 20:44'! synchronize time _ Time millisecondClockValue. deltaTime _ nil.! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/25/2000 23:02'! checkTape "See if this tape was already converted to the new format" tape ifNil:[^self]. tape size = 0 ifTrue:[^self]. (tape first isKindOf: Association) ifTrue:[tape _ self convertV0Tape: tape].! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/26/2000 01:04'! convertV0Tape: anArray "Convert the tape into the new format" | lastKey evt | lastKey _ 0. ^anArray collect:[:assn| evt _ assn value. evt setTimeStamp: (lastKey _ lastKey + assn key). evt]! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/25/2000 22:00'! readFrom: aStream "Private" | cr header | cr _ Character cr. header _ aStream upTo: cr. (header = 'Event Tape v1 BINARY') ifTrue:[^aStream fileInObjectAndCode]. (header = 'Event Tape v1 ASCII') ifTrue:[^self readFromV1: aStream]. "V0 had no header so guess" aStream reset. header first isDigit ifFalse:[^self convertV0Tape: (aStream fileInObjectAndCode)]. ^self convertV0Tape: (self readFromV0: aStream). ! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/26/2000 01:55'! readFromV0: aStream | cr line lineStream t evt | cr _ Character cr. ^Array streamContents:[:tStream | [aStream atEnd] whileFalse: [line _ aStream upTo: cr. line isEmpty "Some MW tapes have an empty record at the end" ifFalse: [lineStream _ ReadStream on: line. t _ Integer readFrom: lineStream. [lineStream peek isLetter] whileFalse: [lineStream next]. evt _ MorphicEvent readFromObsolete: lineStream. tStream nextPut: t -> evt]]].! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/26/2000 01:55'! readFromV1: aStream | cr | cr _ Character cr. ^Array streamContents:[:tStream | [aStream atEnd] whileFalse:[ tStream nextPut: (MorphicEvent readFromString: (aStream upTo: cr))]]! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'sma 6/18/2000 10:33'! readTape (Smalltalk at: #RequestBoxMorph ifAbsent: [^ self readTape: (FillInTheBlank request: 'Tape to read' initialAnswer: 'tapeName.tape')]) request: 'Tape to read' respondTo: self selector: #readTape:! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/25/2000 22:00'! readTape: fileName | file | self writeCheck. (FileStream isAFileNamed: fileName) ifFalse: [^ nil]. file _ FileStream oldFileNamed: fileName. tape _ self readFrom: file. file close. saved _ true "Still exists on file"! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'di 2/15/1999 16:05'! writeCheck (saved not and: [self confirm: 'The current tape has not been saved. Would you like to do so now?']) ifTrue: [self writeTape]. ! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/25/2000 22:11'! writeFileNamed: fileName | file noVoice delta | file _ FileStream newFileNamed: fileName. noVoice _ true. tape do:[:evt | evt type = #startSound ifTrue: [noVoice _ false]]. noVoice ifTrue: ["Simple format (reads fast) for no voice" file nextPutAll:'Event Tape v1 ASCII'; cr. delta _ tape first timeStamp. tape do: [:evt | file store: (evt copy setTimeStamp: evt timeStamp-delta); cr]. file close] ifFalse: ["Inclusion of voice events requires general object storage" file nextPutAll:'Event Tape v1 BINARY'; cr. file fileOutClass: nil andObject: tape]. saved _ true. ^ file name! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'sma 6/18/2000 10:34'! writeTape | args b | args _ (b _ self button: 'writeTape') isNil ifTrue: [#()] ifFalse: [b arguments]. (args size > 0 and: [args first size > 0]) ifTrue: [args first. self writeTape: args first] ifFalse: [(Smalltalk at: #RequestBoxMorph ifAbsent: [^ self writeTape: (FillInTheBlank request: 'Tape to write' initialAnswer: 'tapeName.tape')]) request: 'Tape to write' respondTo: self selector: #writeTape:]! ! !EventRecorderMorph methodsFor: 'fileIn/Out' stamp: 'ar 10/25/2000 22:11'! writeTape: fileName | b name | name _ self writeFileNamed: fileName. (b _ self button: 'writeTape') ifNotNil: [ b actionSelector: #writeTape:. b arguments: (Array with: name)]. ! ! !EventRecorderMorph methodsFor: 'piano rolls' stamp: 'RAA 12/13/2000 13:07'! addMorphsTo: morphList pianoRoll: pianoRoll eventTime: t betweenTime: leftTime and: rightTime | startX myDurationInTicks endX | startX _ pianoRoll xForTime: t. myDurationInTicks _ pianoRoll scorePlayer ticksForMSecs: self myDurationInMS. t > rightTime ifTrue: [^ self]. (t + myDurationInTicks) < leftTime ifTrue: [^ self]. endX _ pianoRoll xForTime: t + myDurationInTicks. morphList add: (self hResizing: #spaceFill; left: startX; width: endX - startX). ! ! !EventRecorderMorph methodsFor: 'piano rolls' stamp: 'RAA 12/13/2000 12:40'! encounteredAtTime: ticks inScorePlayer: scorePlayer atIndex: index inEventTrack: track secsPerTick: secsPerTick self play.! ! !EventRecorderMorph methodsFor: 'piano rolls' stamp: 'RAA 12/13/2000 13:07'! justDroppedIntoPianoRoll: newOwner event: evt | startX lengthInTicks endX startTimeInScore endTimeInScore | super justDroppedIntoPianoRoll: newOwner event: evt. startTimeInScore _ newOwner timeForX: self left. lengthInTicks _ newOwner scorePlayer ticksForMSecs: self myDurationInMS. endTimeInScore _ startTimeInScore + lengthInTicks. endTimeInScore > newOwner scorePlayer durationInTicks ifTrue: [newOwner scorePlayer updateDuration]. startX _ newOwner xForTime: startTimeInScore. endX _ newOwner xForTime: endTimeInScore. self width: endX - startX. ! ! !EventRecorderMorph methodsFor: 'piano rolls' stamp: 'RAA 12/13/2000 13:07'! myDurationInMS ^tape isEmptyOrNil ifTrue: [ 10 ] ifFalse: [ tape last timeStamp - tape first timeStamp ] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EventRecorderMorph class instanceVariableNames: ''! !EventRecorderMorph class methodsFor: 'instance creation' stamp: 'mdr 8/31/2000 18:48'! fromFileNamed: aFileName | file answer | file _ FileStream readOnlyFileNamed: aFileName. answer _ self readFrom: file. file close. ^ answer! ! !EventRecorderMorph class methodsFor: 'instance creation' stamp: 'LC 12/23/1998 11:14'! readFrom: aStream ^ self new readFrom: aStream! ! InputSensor subclass: #EventSensor instanceVariableNames: 'mouseButtons mousePosition keyboardBuffer interruptKey interruptSemaphore eventQueue inputProcess inputSemaphore ' classVariableNames: 'EventPollFrequency ' poolDictionaries: 'EventSensorConstants ' category: 'Kernel-Processes'! !EventSensor commentStamp: '' prior: 0! EventSensor is a replacement for InputSensor based on a set of (optional) event primitives. An EventSensor updates its state when events are received so that all state based users of Sensor (e.g., Sensor keyboard, Sensor leftShiftDown, Sensor mouseButtons) will work exactly as before, by moving the current VM mechanisms into EventSensor itself. An optional input semaphore is part of the new design. For platforms that support true asynchronous event notification the semaphore can be signaled to indicate pending events. However, since most platforms do not support asynchronous notifications about events EventSensor still has to poll every now and then. Instance variables: mouseButtons - mouse button state as replacement for primMouseButtons mousePosition - mouse position as replacement for primMousePt keyboardBuffer - keyboard input buffer interruptKey - currently defined interrupt key interruptSemaphore - the semaphore signaled when the interruptKey is detected eventQueue - an optional event queue for event driven applications inputProcess - the process receiving low-level events inputSemaphore - the semaphore signaled by the VM if asynchronous event notification is supported Class variables: EventPollFrequency - the number of milliseconds to wait between polling for more events Event format: The current event format is very simple. Each event is recorded into an 8 element array. All events must provide some SmallInteger ID (the first field in the event buffer) and a time stamp (the second field in the event buffer), so that the difference between the time stamp of an event and the current time can be reported. Currently, the following events are defined: Null event ============= The Null event is returned when the ST side asks for more events but no more events are available. Structure: [1] - event type 0 [2-8] - unused Mouse event structure ========================== Mouse events are generated when mouse input is detected. Structure: [1] - event type 1 [2] - time stamp [3] - mouse x position [4] - mouse y position [5] - button state; bitfield with the following entries: 1 - yellow (e.g., right) button 2 - blue (e.g., middle) button 4 - red (e.g., left) button [all other bits are currently undefined] [6] - modifier keys; bitfield with the following entries: 1 - shift key 2 - ctrl key 4 - (Mac specific) option key 8 - Cmd/Alt key [all other bits are currently undefined] [7] - reserved. [8] - reserved. Keyboard events ==================== Keyboard events are generated when keyboard input is detected. [1] - event type 2 [2] - time stamp [3] - character code For now the character code is in Mac Roman encoding. [4] - press state; integer with the following meaning 0 - character 1 - key press (down) 2 - key release (up) [5] - modifier keys (same as in mouse events) [6] - reserved. [7] - reserved. [8] - reserved. ! !EventSensor methodsFor: 'initialize' stamp: 'ar 7/23/2000 14:39'! initialize "Initialize the receiver" mouseButtons _ 0. mousePosition _ 0@0. keyboardBuffer _ SharedQueue new. interruptKey _ interruptKey ifNil:[2094]. "cmd-." interruptSemaphore _ (Smalltalk specialObjectsArray at: 31) ifNil:[Semaphore new]. eventQueue _ nil. inputProcess _ nil. inputSemaphore _ Semaphore new. ! ! !EventSensor methodsFor: 'initialize' stamp: 'ar 7/23/2000 14:51'! shutDown inputProcess ifNotNil:[inputProcess terminate]. inputProcess _ nil. inputSemaphore ifNotNil:[Smalltalk unregisterExternalObject: inputSemaphore].! ! !EventSensor methodsFor: 'initialize' stamp: 'ar 10/10/2000 23:09'! startUp "Run the I/O process" self shutDown. self initialize. self primSetInputSemaphore: (Smalltalk registerExternalObject: inputSemaphore). inputProcess _ [self ioProcess] forkAt: Processor lowIOPriority. super startUp. Smalltalk isMorphic ifTrue:[self eventQueue: SharedQueue new].! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 7/23/2000 14:37'! eventQueue "Return the current event queue" ^eventQueue! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 7/23/2000 14:38'! eventQueue: aSharedQueue "Install a new queue for events. If an eventQueue is present all events will be queued up there. It is assumed that a client installing an event queue will actually read data from it, otherwise the system will overflow." eventQueue _ aSharedQueue.! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 9/26/2000 21:33'! flushEvents eventQueue ifNotNil:[eventQueue _ SharedQueue new].! ! !EventSensor methodsFor: 'accessing' stamp: 'RAA 12/1/2000 10:18'! inputProcess ^ inputProcess! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 7/30/2000 15:50'! nextEvent "Return the next event from the receiver." eventQueue == nil ifTrue:[^self nextEventSynthesized] ifFalse:[^self nextEventFromQueue] ! ! !EventSensor methodsFor: 'accessing' stamp: 'RAA 11/24/2000 11:03'! nextEventFromQueue "Return the next event from the receiver." EventPollFrequency _ 20. "since Squeak is taking the event, reset to normal delay" eventQueue isEmpty ifTrue:[^nil] ifFalse:[^eventQueue next]! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 8/16/2000 22:06'! nextEventSynthesized "Return a synthesized event. This method is called if an event driven client wants to receive events but the primary user interface is not event-driven (e.g., the receiver does not have an event queue but only updates its state). This can, for instance, happen if a Morphic World is run in an MVC window. To simplify the clients work this method will always return all available keyboard events first, and then (repeatedly) the mouse events. Since mouse events come last, the client can assume that after one mouse event has been received there are no more to come. Note that it is impossible for EventSensor to determine if a mouse event has been issued before so the client must be aware of the possible problem of getting repeatedly the same mouse events. See HandMorph>>processEvents for an example on how to deal with this." | kbd array buttons pos modifiers mapped | "First check for keyboard" array _ Array new: 8. kbd _ self primKbdNext. kbd = nil ifFalse:[ "simulate keyboard event" array at: 1 put: EventTypeKeyboard. "evt type" array at: 2 put: Time millisecondClockValue. "time stamp" array at: 3 put: (kbd bitAnd: 255). "char code" array at: 4 put: EventKeyChar. "key press/release" array at: 5 put: (kbd bitShift: -8). "modifier keys" ^array]. "Then check for mouse" buttons _ self primMouseButtons. pos _ self primMousePt. modifiers _ buttons bitShift: -3. buttons _ buttons bitAnd: 7. mapped _ self mapButtons: buttons modifiers: modifiers. array at: 1 put: EventTypeMouse; at: 2 put: Time millisecondClockValue; at: 3 put: pos x; at: 4 put: pos y; at: 5 put: mapped; at: 6 put: modifiers. ^array! ! !EventSensor methodsFor: 'accessing' stamp: 'di 9/13/2000 13:00'! peekEvent "Look ahead at the next event." eventQueue == nil ifTrue:[^ nil] ifFalse:[^ eventQueue peek] ! ! !EventSensor methodsFor: 'private-I/O' stamp: 'RAA 11/26/2000 11:57'! ioProcess "Run the i/o process" | eventBuffer type | eventBuffer _ Array new: 8. [true] whileTrue:[ [self primGetNextEvent: eventBuffer. type _ eventBuffer at: 1. type = EventTypeNone] whileFalse:[self processEvent: eventBuffer]. inputSemaphore waitTimeoutMSecs: EventPollFrequency. Preferences higherPerformance ifTrue: [ EventPollFrequency _ (EventPollFrequency * 1.5) rounded min: 500. ]. ]. ! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 7/30/2000 18:12'! mapButtons: buttons modifiers: modifiers "Map the buttons to yellow or blue based on the given modifiers. If only the red button is pressed, then map Ctrl-RedButton -> BlueButton. Cmd-RedButton -> YellowButton. " (buttons = RedButtonBit) ifFalse:[^buttons]. (modifiers allMask: CtrlKeyBit) ifTrue:[^BlueButtonBit]. (modifiers allMask: CommandKeyBit) ifTrue:[^YellowButtonBit]. ^buttons! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 8/16/2000 22:06'! primGetNextEvent: array "Store the next OS event available into the provided array. Essential. If the VM is not event driven the ST code will fall back to the old-style mechanism and use the state based primitives instead." | kbd buttons modifiers pos mapped | "Simulate the events" array at: 1 put: EventTypeNone. "assume no more events" "First check for keyboard" kbd _ super primKbdNext. kbd = nil ifFalse:[ "simulate keyboard event" array at: 1 put: EventTypeKeyboard. "evt type" array at: 2 put: Time millisecondClockValue. "time stamp" array at: 3 put: (kbd bitAnd: 255). "char code" array at: 4 put: EventKeyChar. "key press/release" array at: 5 put: (kbd bitShift: -8). "modifier keys" ^self]. "Then check for mouse" buttons _ super primMouseButtons. pos _ super primMousePt. modifiers _ buttons bitShift: -3. buttons _ buttons bitAnd: 7. mapped _ self mapButtons: buttons modifiers: modifiers. (pos = mousePosition and:[(mapped bitOr: (modifiers bitShift: 3)) = mouseButtons]) ifTrue:[^self]. array at: 1 put: EventTypeMouse; at: 2 put: Time millisecondClockValue; at: 3 put: pos x; at: 4 put: pos y; at: 5 put: mapped; at: 6 put: modifiers. ! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 7/30/2000 18:16'! primSetInputSemaphore: semaIndex "Set the input semaphore the VM should use for asynchronously signaling the availability of events. Primitive. Optional." ^nil! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 9/1/2000 14:53'! processEvent: evt "Process a single event. This method is run at high priority." | type | type _ evt at: 1. "Check if the event is a user interrupt" (type = EventTypeKeyboard and:[(evt at: 4) = 0 and:[ ((evt at: 3) bitOr: ((evt at: 5) bitShift: 8)) = interruptKey]]) ifTrue:["interrupt key is meta - not reported as event" ^interruptSemaphore signal]. "Store the event in the queue if there's any" self queueEvent: evt. "Update state for InputSensor." EventTypeMouse = type ifTrue:[self processMouseEvent: evt]. EventTypeKeyboard = type ifTrue:[self processKeyboardEvent: evt]. ! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 8/1/2000 11:55'! processKeyboardEvent: evt "process a keyboard event, updating InputSensor state" | charCode pressCode | "Never update keyboardBuffer if we have an eventQueue active" eventQueue ifNotNil:[^self]. charCode _ evt at: 3. charCode = nil ifTrue:[^self]. "extra characters not handled in MVC" pressCode _ evt at: 4. pressCode = EventKeyChar ifFalse:[^self]. "key down/up not handled in MVC" "mix in modifiers" charCode _ charCode bitOr: ((evt at: 5) bitShift: 8). mouseButtons _ (mouseButtons bitAnd: 7) bitOr: ((evt at: 5) bitShift: 3). keyboardBuffer nextPut: charCode.! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 8/16/2000 22:07'! processMouseEvent: evt "process a mouse event, updating InputSensor state" | modifiers buttons mapped | mousePosition _ (evt at: 3) @ (evt at: 4). buttons _ evt at: 5. modifiers _ evt at: 6. mapped _ self mapButtons: buttons modifiers: modifiers. mouseButtons _ mapped bitOr: (modifiers bitShift: 3).! ! !EventSensor methodsFor: 'private-I/O' stamp: 'ar 7/23/2000 14:55'! queueEvent: evt "Queue the given event in the event queue (if any). Note that the event buffer must be copied since it will be reused later on." eventQueue ifNil:[^self]. eventQueue nextPut: evt clone.! ! !EventSensor methodsFor: 'private' stamp: 'ar 7/23/2000 00:34'! primInterruptSemaphore: aSemaphore "Primitive. Install the argument as the semaphore to be signalled whenever the user presses the interrupt key. The semaphore will be signaled once each time the interrupt key is pressed." interruptSemaphore _ aSemaphore. "backward compatibility: use the old primitive which is obsolete now" super primInterruptSemaphore: aSemaphore! ! !EventSensor methodsFor: 'private' stamp: 'ar 7/23/2000 14:59'! primKbdNext keyboardBuffer isEmpty ifTrue:[^nil] ifFalse:[^keyboardBuffer next]! ! !EventSensor methodsFor: 'private' stamp: 'ar 7/23/2000 14:59'! primKbdPeek ^keyboardBuffer peek! ! !EventSensor methodsFor: 'private' stamp: 'ar 7/23/2000 14:59'! primMouseButtons ^mouseButtons! ! !EventSensor methodsFor: 'private' stamp: 'ar 7/23/2000 14:59'! primMousePt ^mousePosition! ! !EventSensor methodsFor: 'private' stamp: 'ls 10/23/2000 14:14'! primSetInterruptKey: anInteger "Primitive. Register the given keycode as the user interrupt key. The low byte of the keycode is the ISO character and its next four bits are the Smalltalk modifer bits ." interruptKey _ anInteger. "backward compatibility: use the old primitive which is obsolete now" super primSetInterruptKey: anInteger! ! !EventSensor methodsFor: 'NOTES' stamp: 'RAA 11/26/2000 12:09'! higherPerformanceNotes " This is mostly a Mac issue, but may have some effect on other platforms. These changes do not take effect until you set the preference #higherPerformance to true. The impact of setting this pref to true may be higher performance for this Squeak image, but lower performance for other applications/processes that may be running concurrently. Experiment with your particular configuration/desires and decide for yourself. 1. In order to reduce the amount of time lost (perhaps 20 to 30% in some cases) to background applications on the Mac, change the strategy used to poll for UI events. Every time we poll the OS for UI events, increase the delay until the next check. Every time Squeak actually requests an event from EventSensor, reset the delay to its normal value (20 ms). This means that a long-running evaluation started in the UI process will receive less competition from background apps (and less overhead even if it is the only app), but normal UI-intensive operations will happen as they do now. What is lost by this change is some sensitivity to mouse events that occur while Squeak is busy over long periods. My thought is that if Squeak is so occupied for a period of seconds, these events are much less useful and perhaps even harmful. 2. Reduce the minimum morphic cycle time (MinCycleLapse) so that the frame rate (and, hence, running of #step methods) can proceed at greater than 50 frames per second. This can be quite beneficial to things like simulations that are run via #step. "! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EventSensor class instanceVariableNames: ''! !EventSensor class methodsFor: 'class initialization' stamp: 'ar 7/23/2000 15:33'! initialize "EventSensor initialize" self initializeEventSensorConstants. EventPollFrequency _ 20. "Note: The above is important. Most systems will not notify the VM about the occurance of events asynchronously. Therefore, we have to go check for ourselves every now and then."! ! !EventSensor class methodsFor: 'class initialization' stamp: 'ar 1/30/2001 19:41'! initializeEventSensorConstants "EventSensor initialize" Smalltalk declare: #EventSensorConstants from: Undeclared. EventSensorConstants == nil ifTrue:[EventSensorConstants _ Dictionary new]. #( (RedButtonBit 4) (BlueButtonBit 2) (YellowButtonBit 1) (ShiftKeyBit 1) (CtrlKeyBit 2) (OptionKeyBit 4) (CommandKeyBit 8) "Types of events" (EventTypeNone 0) (EventTypeMouse 1) (EventTypeKeyboard 2) (EventTypeDragDropFiles 3) "Press codes for keyboard events" (EventKeyChar 0) (EventKeyDown 1) (EventKeyUp 2) ) do:[:spec| EventSensorConstants declare: spec first from: Undeclared. EventSensorConstants at: spec first put: spec last. ].! ! !EventSensor class methodsFor: 'class initialization' stamp: 'ar 7/23/2000 15:06'! install "EventSensor install" "Install an EventSensor in place of the current Sensor." | newSensor | Sensor shutDown. newSensor _ self new. newSensor startUp. "Note: We must use #become: here to replace all references to the old sensor with the new one, since Sensor is referenced from all the existing controllers." Sensor becomeForward: newSensor. "done"! ! !EventSensor class methodsFor: 'instance creation' stamp: 'ar 7/22/2000 23:22'! new ^super new initialize! ! Object subclass: #Exception instanceVariableNames: 'messageText initialContext resignalException handlerContext tag activeHandler ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !Exception commentStamp: '' prior: 0! This is the main class used to implement the exception handling system (EHS). It plays two distinct roles: that of the exception, and that of the exception handler. More specifically, it implements the bulk of the protocols laid out in the ANSI specification - those protocol names are reflected in the message categories. Exception is an abstract class. Instances should neither be created nor trapped. In most cases, subclasses should inherit from Error or Notification rather than directly from Exception. In implementing this EHS, The Fourth Estate Inc. incorporated some ideas and code from Craig Latta's EHS. His insights were crucial in allowing us to implement BlockContext>>valueUninterruptably (and by extension, #ensure: and #ifCurtailed:), and we imported the following methods with little or no modification: ContextPart>>terminateTo: ContextPart>>terminate MethodContext>>receiver: MethodContext>>answer: Thanks, Craig!!! !Exception methodsFor: 'private' stamp: 'ikp 9/18/2000 21:36'! findHandlerFrom: startCtx | ctx handler | ctx := startCtx. [ctx == nil] whileFalse: [ctx isHandlerContext ifTrue: [handler := ctx tempAt: 1. "the first argument" ((handler handles: self) and: [(ctx tempAt: 3)]) ifTrue: [^ctx]]. ctx := ctx sender]. ^nil! ! !Exception methodsFor: 'private' stamp: 'ikp 9/18/2000 21:47'! handlerAction | na handler returnValue | handler := handlerContext tempAt: 2. "the second argument" na := handler numArgs. handlerContext tempAt: 3 put: false. returnValue := na == 0 ifTrue: [handler value] ifFalse: [handler value: self]. resignalException == nil ifFalse: [^returnValue]. "Execution will only continue beyond this point if the handler did not specify a handler action." self return: returnValue! ! !Exception methodsFor: 'private' stamp: 'tfei 3/22/1999 10:43'! initialContext: aContext initialContext := aContext! ! !Exception methodsFor: 'private' stamp: 'bf 9/27/1999 16:09'! receiver ^initialContext ifNotNil: [initialContext receiver]! ! !Exception methodsFor: 'private' stamp: 'tfei 1/10/2000 18:22'! setHandlerFrom: startCtx handlerContext := self findHandlerFrom: startCtx. ^handlerContext! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 1/10/2000 18:19'! isNested "Determine whether the current exception handler is within the scope of another handler for the same exception." ^(self findHandlerFrom: handlerContext sender) ~~ nil! ! !Exception methodsFor: 'signaledException' stamp: 'ar 9/28/2000 20:29'! outer "Evaluate the enclosing exception action for the receiver and return." ^self isResumable ifTrue: [self setHandlerFrom: handlerContext sender. handlerContext == nil ifTrue: [self defaultAction] ifFalse: [self handlerAction]] ifFalse: [self pass]! ! !Exception methodsFor: 'signaledException' stamp: 'ar 9/28/2000 20:28'! pass "Yield control to the enclosing exception action for the receiver." | result | self setHandlerFrom: handlerContext sender. handlerContext == nil ifTrue: [result _ self defaultAction. self isResumable ifTrue: [self resume: result] ifFalse: [IllegalResumeAttempt signal]] ifFalse: [self handlerAction]! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 6/13/1999 02:05'! resignalAs: replacementException "Signal an alternative exception in place of the receiver." thisContext unwindTo: initialContext. replacementException initialContext: initialContext. resignalException := replacementException! ! !Exception methodsFor: 'signaledException' stamp: 'RAA 12/8/2000 16:26'! resume "Return from the message that signaled the receiver." ^self resume: nil! ! !Exception methodsFor: 'signaledException' stamp: 'RAA 12/8/2000 16:26'! resume: resumptionValue "Return the argument as the value of the message that signaled the receiver." | tc | handlerContext ifNotNil: [handlerContext tempAt: 3 put: true]. self isResumable ifFalse: [IllegalResumeAttempt signal]. tc := thisContext. tc unwindTo: initialContext. tc terminateTo: initialContext. ^resumptionValue! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 6/4/1999 17:55'! retry "Abort an exception handler and re-evaluate its protected block." thisContext unwindTo: handlerContext. thisContext terminateTo: handlerContext. handlerContext restart! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 6/5/1999 00:34'! retryUsing: alternativeBlock "Abort an exception handler and evaluate a new block in place of the handler's protected block." handlerContext receiver: alternativeBlock. self retry! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 6/4/1999 17:57'! return "Return nil as the value of the block protected by the active exception handler." self return: nil! ! !Exception methodsFor: 'signaledException' stamp: 'tfei 6/9/1999 16:35'! return: returnValue "Return the argument as the value of the block protected by the active exception handler." | handlerHomeContext | handlerHomeContext := ExceptionAboutToReturn signal. initialContext unwindTo: handlerContext. thisContext terminateTo: handlerContext. handlerHomeContext == nil ifFalse: [handlerContext sender swapSender: handlerHomeContext]. ^returnValue! ! !Exception methodsFor: 'exceptionBuilder' stamp: 'tfei 6/4/1999 17:47'! messageText: signalerText "Set an exception's message text." messageText := signalerText! ! !Exception methodsFor: 'exceptionBuilder' stamp: 'tfei 6/6/1999 23:06'! tag: t "TFEI - This message is not specified in the ANSI protocol, but that looks like an oversight because #tag is specified, and states that the signaler may store the tag value." tag := t! ! !Exception methodsFor: 'exceptionDescription' stamp: 'tfei 6/6/1999 23:06'! defaultAction "The default action taken if the exception is signaled." self subclassResponsibility! ! !Exception methodsFor: 'exceptionDescription' stamp: 'tfei 6/6/1999 23:09'! description "Return a textual description of the exception." | desc mt | desc := self class name asString. ^(mt := self messageText) == nil ifTrue: [desc] ifFalse: [desc, ': ', mt]! ! !Exception methodsFor: 'exceptionDescription' stamp: 'tfei 6/4/1999 17:36'! isResumable "Determine whether an exception is resumable." ^false! ! !Exception methodsFor: 'exceptionDescription' stamp: 'tfei 6/4/1999 17:40'! messageText "Return an exception's message text." ^messageText! ! !Exception methodsFor: 'exceptionDescription' stamp: 'tfei 11/19/1999 08:42'! tag "Return an exception's tag value." ^tag == nil ifTrue: [self messageText] ifFalse: [tag]! ! !Exception methodsFor: 'exceptionSignaler' stamp: 'tfei 1/10/2000 18:23'! signal "Signal the occurrence of an exceptional condition." | result | initialContext == nil ifTrue: [initialContext := thisContext sender]. resignalException := nil. (self setHandlerFrom: initialContext) == nil ifTrue: [^self defaultAction]. result := self handlerAction. ^resignalException == nil ifTrue: [result] ifFalse: [resignalException signal]! ! !Exception methodsFor: 'exceptionSignaler' stamp: 'tfei 6/4/1999 17:46'! signal: signalerText "Signal the occurrence of an exceptional condition with a specified textual description." self messageText: signalerText. initialContext == nil ifTrue: [initialContext := thisContext sender]. ^self signal! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Exception class instanceVariableNames: ''! !Exception class methodsFor: 'exceptionInstantiator' stamp: 'tfei 6/4/1999 18:12'! signal "Signal the occurrence of an exceptional condition." | ex | ex := self new. ex initialContext: thisContext sender. ^ex signal! ! !Exception class methodsFor: 'exceptionInstantiator' stamp: 'tfei 6/4/1999 18:15'! signal: signalerText "TFEI - Signal the occurrence of an exceptional condition with a specified textual description." | ex | ex := self new. ex initialContext: thisContext sender. ^ex signal: signalerText! ! !Exception class methodsFor: 'exceptionSelector' stamp: 'tfei 6/4/1999 18:08'! , anotherException "Create an exception set." ^ExceptionSet new add: self; add: anotherException; yourself! ! !Exception class methodsFor: 'exceptionSelector' stamp: 'sma 2/12/2000 14:15'! handles: exception "Determine whether an exception handler will accept a signaled exception." (exception isKindOf: Halt) ifTrue: [^ false]. ^ exception isKindOf: self! ! Notification subclass: #ExceptionAboutToReturn instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !ExceptionAboutToReturn commentStamp: '' prior: 0! This class is private to the EHS implementation. Its use allows for ensured execution to survive code such as: [self doThis. ^nil] ensure: [self doThat] Signaling or handling this exception is not recommended. Not even slightly.! Object subclass: #ExceptionSet instanceVariableNames: 'exceptions ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !ExceptionSet commentStamp: '' prior: 0! An ExceptionSet is a grouping of exception handlers which acts as a single handler. Within the group, the most recently added handler will be the last handler found during a handler search (in the case where more than one handler in the group is capable of handling a given exception). ! !ExceptionSet methodsFor: 'private' stamp: 'tfei 7/16/1999 1:07'! add: anException exceptions add: anException! ! !ExceptionSet methodsFor: 'private' stamp: 'tfei 3/23/1999 14:07'! initialize exceptions := OrderedCollection new! ! !ExceptionSet methodsFor: 'exceptionSelector' stamp: 'tfei 6/4/1999 18:37'! , anException "Return an exception set that contains the receiver and the argument exception. This is commonly used to specify a set of exception selectors for an exception handler." self add: anException. ^self! ! !ExceptionSet methodsFor: 'exceptionSelector' stamp: 'tfei 6/4/1999 18:37'! handles: anException "Determine whether an exception handler will accept a signaled exception." exceptions do: [:ex | (ex handles: anException) ifTrue: [^true]]. ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExceptionSet class instanceVariableNames: ''! !ExceptionSet class methodsFor: 'exceptionInstantiator' stamp: 'tfei 3/23/1999 14:06'! new ^super new initialize! ! Object subclass: #ExceptionTester instanceVariableNames: 'log suiteLog ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Tests'! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:13'! doSomething self log: self doSomethingString! ! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:14'! doSomethingElse self log: self doSomethingElseString! ! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:14'! doSomethingExceptional self log: self doSomethingExceptionalString! ! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:15'! doYetAnotherThing self log: self doYetAnotherThingString! ! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:16'! methodWithError MyTestError signal: self testString! ! !ExceptionTester methodsFor: 'pseudo actions' stamp: 'tfei 6/8/1999 09:16'! methodWithNotification MyTestNotification signal: self testString! ! !ExceptionTester methodsFor: 'logging' stamp: 'tfei 6/8/1999 09:17'! clearLog log := nil! ! !ExceptionTester methodsFor: 'logging' stamp: 'tfei 6/7/1999 15:16'! contents ^( self log inject: (WriteStream on: (String new: 80)) into: [:result :item | result cr; nextPutAll: item; yourself] ) contents! ! !ExceptionTester methodsFor: 'logging' stamp: 'tfei 6/7/1999 15:03'! log: aString self log add: aString! ! !ExceptionTester methodsFor: 'logging' stamp: 'tfei 6/12/1999 23:07'! logTest: aSelector self suiteLog add: aSelector! ! !ExceptionTester methodsFor: 'logging' stamp: 'tfei 6/8/1999 09:38'! logTestResult: aString | index | index := self suiteLog size. self suiteLog at: index put: ((self suiteLog at: index), ' ', aString)! ! !ExceptionTester methodsFor: 'suites' stamp: 'tfei 6/13/1999 01:25'! runAllTests "ExceptionTester new runAllTests" self runBasicTests; runBasicANSISignaledExceptionTests! ! !ExceptionTester methodsFor: 'suites' stamp: 'tfei 6/12/1999 23:54'! runBasicANSISignaledExceptionTests self basicANSISignaledExceptionTestSelectors do: [:eachTestSelector | self runTest: eachTestSelector]! ! !ExceptionTester methodsFor: 'suites' stamp: 'tfei 6/9/1999 16:06'! runBasicTests self basicTestSelectors do: [:eachTestSelector | self runTest: eachTestSelector]! ! !ExceptionTester methodsFor: 'testing' stamp: 'tfei 8/19/1999 03:10'! runTest: aSelector | expectedResult | [expectedResult := self perform: (aSelector, #Results) asSymbol. self logTest: aSelector. self clearLog. self perform: aSelector. ] on: MyTestError do: [ :ex | self log: 'Unhandled Exception'. ex return: nil]. self log = expectedResult ifTrue: [self logTestResult: 'succeeded'] ifFalse: [self logTestResult: 'failed']! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 11/14/1999 17:26'! doubleResumeTest [self doSomething. MyResumableTestError signal. self doSomethingElse. MyResumableTestError signal. self doYetAnotherThing] on: MyResumableTestError do: [:ex | ex resume].! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/7/1999 13:43'! nonResumableFallOffTheEndHandler [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [:ex | self doSomethingExceptional]. self doYetAnotherThing! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/9/1999 16:07'! resumableFallOffTheEndHandler [self doSomething. MyTestNotification signal. self doSomethingElse] on: MyTestNotification do: [:ex | self doSomethingExceptional]. self doYetAnotherThing! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 8/19/1999 01:39'! signalFromHandlerActionTest [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [self doYetAnotherThing. MyTestError signal]! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/8/1999 09:44'! simpleEnsureTest [self doSomething. self doSomethingElse] ensure: [self doYetAnotherThing]. ! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/8/1999 12:50'! simpleEnsureTestWithError [self doSomething. MyTestError signal. self doSomethingElse] ensure: [self doYetAnotherThing]. ! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/8/1999 10:15'! simpleEnsureTestWithNotification [self doSomething. self methodWithNotification. self doSomethingElse] ensure: [self doYetAnotherThing]. ! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/9/1999 16:04'! simpleEnsureTestWithUparrow [self doSomething. true ifTrue: [^nil]. self doSomethingElse] ensure: [self doYetAnotherThing]. ! ! !ExceptionTester methodsFor: 'tests' stamp: 'tfei 6/7/1999 14:28'! warningTest self log: 'About to signal warning.'. Warning signal: 'Ouch'. self log: 'Warning signal handled and resumed.'! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 11/14/1999 17:29'! doubleResumeTestResults ^OrderedCollection new add: self doSomethingString; add: self doSomethingElseString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 6/8/1999 09:21'! nonResumableFallOffTheEndHandlerResults ^OrderedCollection new add: self doSomethingString; add: self doSomethingExceptionalString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 8/19/1999 02:39'! resumableFallOffTheEndHandlerResults ^OrderedCollection new add: self doSomethingString; add: self doSomethingExceptionalString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 8/19/1999 01:51'! signalFromHandlerActionTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: 'Unhandled Exception'; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 6/8/1999 09:47'! simpleEnsureTestResults ^OrderedCollection new add: self doSomethingString; add: self doSomethingElseString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 6/9/1999 17:44'! simpleEnsureTestWithErrorResults ^OrderedCollection new add: self doSomethingString; add: 'Unhandled Exception'; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 6/8/1999 10:13'! simpleEnsureTestWithNotificationResults ^OrderedCollection new add: self doSomethingString; add: self doSomethingElseString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'tfei 6/8/1999 18:55'! simpleEnsureTestWithUparrowResults ^OrderedCollection new add: self doSomethingString; " add: self doSomethingElseString;" add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/12/1999 23:59'! basicANSISignaledExceptionTestSelectors ^#( simpleIsNestedTest simpleOuterTest simplePassTest simpleResignalAsTest simpleResumeTest simpleRetryTest simpleRetryUsingTest simpleReturnTest)! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 11/14/1999 17:33'! basicTestSelectors ^#( simpleEnsureTest simpleEnsureTestWithNotification simpleEnsureTestWithUparrow simpleEnsureTestWithError signalFromHandlerActionTest resumableFallOffTheEndHandler nonResumableFallOffTheEndHandler doubleResumeTest)! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:14'! doSomethingElseString ^'Do something else.'! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:14'! doSomethingExceptionalString ^'Do something exceptional.'! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:13'! doSomethingString ^'Do something.'! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:15'! doYetAnotherThingString ^'Do yet another thing.'! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/7/1999 15:03'! log log == nil ifTrue: [log := OrderedCollection new]. ^log! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:30'! suiteLog suiteLog == nil ifTrue: [suiteLog := OrderedCollection new]. ^suiteLog! ! !ExceptionTester methodsFor: 'accessing' stamp: 'tfei 6/8/1999 09:15'! testString ^'This is only a test.'! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 01:27'! simpleIsNestedTest "uses resignalAs:" [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [:ex | ex isNested "expecting to detect handler in #runTest:" ifTrue: [self doYetAnotherThing. ex resignalAs: MyTestNotification new]]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 00:36'! simpleOuterTest "uses #resume" [[self doSomething. MyTestNotification signal. self doSomethingElse] on: MyTestNotification do: [:ex | ex outer]] on: MyTestNotification do: [:ex | self doYetAnotherThing. ex resume]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 00:37'! simplePassTest [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [:ex | self doYetAnotherThing. ex pass "expecting handler in #runTest:"]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 02:12'! simpleResignalAsTest "ExceptionTester new simpleResignalAsTest" [self doSomething. MyTestNotification signal. self doSomethingElse] on: MyTestNotification do: [:ex | ex resignalAs: MyTestError new]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'RAA 12/8/2000 12:58'! simpleResumeTest "see if we can resume twice" | it | [self doSomething. it := MyResumableTestError signal. it = 3 ifTrue: [self doSomethingElse]. it := MyResumableTestError signal. it = 3 ifTrue: [self doSomethingElse]. ] on: MyResumableTestError do: [:ex | self doYetAnotherThing. ex resume: 3]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 01:02'! simpleRetryTest | theMeaningOfLife | theMeaningOfLife := nil. [self doSomething. theMeaningOfLife == nil ifTrue: [MyTestError signal] ifFalse: [self doSomethingElse]] on: MyTestError do: [:ex | theMeaningOfLife := 42. self doYetAnotherThing. ex retry]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 01:03'! simpleRetryUsingTest [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [:ex | ex retryUsing: [self doYetAnotherThing]]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'tfei 6/13/1999 00:59'! simpleReturnTest | it | it := [self doSomething. MyTestError signal. self doSomethingElse] on: MyTestError do: [:ex | ex return: 3]. it = 3 ifTrue: [self doYetAnotherThing]! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:09'! simpleIsNestedTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:10'! simpleOuterTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:10'! simplePassTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: 'Unhandled Exception'; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:11'! simpleResignalAsTestResults ^OrderedCollection new add: self doSomethingString; add: 'Unhandled Exception'; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'RAA 12/8/2000 12:59'! simpleResumeTestResults "see if we can resume twice" ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingElseString; add: self doYetAnotherThingString; add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:23'! simpleRetryTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingString; add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 01:23'! simpleRetryUsingTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'tfei 6/13/1999 02:22'! simpleReturnTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; yourself! ! ByteArray variableByteSubclass: #ExternalAddress instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FFI-Kernel'! !ExternalAddress commentStamp: '' prior: 0! An ExternalAddress is an opaque handle to objects outside Smalltalk memory (e.g., a pointer).! !ExternalAddress methodsFor: 'initialize-release' stamp: 'ar 11/22/1999 04:25'! beNull "Make the receiver a NULL pointer" self atAllPut: 0.! ! !ExternalAddress methodsFor: 'initialize-release' stamp: 'ar 11/28/1999 23:40'! free "Primitive. Free the object pointed to on the external heap. Dangerous - may break your system if the receiver hasn't been allocated by ExternalAddress class>>allocate:. No checks are done." ^self primitiveFailed! ! !ExternalAddress methodsFor: 'accessing' stamp: 'ar 11/21/1999 15:43'! byteAt: byteOffset "Go through a different primitive since the receiver describes data in the outside world" ^self unsignedByteAt: byteOffset! ! !ExternalAddress methodsFor: 'accessing' stamp: 'ar 11/21/1999 15:43'! byteAt: byteOffset put: value "Go through a different primitive since the receiver describes data in the outside world" ^self unsignedByteAt: byteOffset put: value! ! !ExternalAddress methodsFor: 'accessing' stamp: 'ar 11/28/1999 23:09'! isExternalAddress "Return true if the receiver describes an object in the outside world" ^true! ! !ExternalAddress methodsFor: 'private' stamp: 'ar 1/28/2000 17:45'! asByteArrayPointer "Return a ByteArray describing a pointer to the contents of the receiver." ^(ByteArray new: 4) byteAt: 1 put: (self basicAt: 1); byteAt: 2 put: (self basicAt: 2); byteAt: 3 put: (self basicAt: 3); byteAt: 4 put: (self basicAt: 4); yourself! ! !ExternalAddress methodsFor: 'converting' stamp: 'hg 3/1/2000 22:27'! asInteger "convert address to integer" ^self inject: 0 into: [:total :byte | total * 256 + byte]! ! !ExternalAddress methodsFor: 'converting' stamp: 'hg 3/1/2000 23:00'! fromInteger: address "set my handle to point at address" (4 to: 1 by: -1) inject: address into: [:remainder :index | self at: index put: (remainder bitAnd: 255). remainder // 256]! ! !ExternalAddress methodsFor: 'printing' stamp: 'sma 6/1/2000 09:45'! printOn: aStream "print this as a hex address ('@ 16rFFFFFFFF') to distinguish it from ByteArrays" aStream nextPutAll: '@ '; nextPutAll: self asInteger hex8! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalAddress class instanceVariableNames: ''! !ExternalAddress class methodsFor: 'class initialization' stamp: 'ar 11/28/1999 23:32'! startUp: resuming resuming ifTrue:[self allInstancesDo:[:addr| addr beNull]].! ! !ExternalAddress class methodsFor: 'instance creation' stamp: 'ar 11/28/1999 23:20'! allocate: byteSize "Primitive. Allocate an object on the external heap." ^self primitiveFailed! ! !ExternalAddress class methodsFor: 'instance creation' stamp: 'ar 11/21/1999 15:44'! new "External addresses are always 4 bytes long" ^super new: 4! ! !ExternalAddress class methodsFor: 'instance creation' stamp: 'ar 11/21/1999 15:44'! new: n "You better don't try this..." ^self shouldNotImplement! ! ExternalStructure subclass: #ExternalData instanceVariableNames: 'type ' classVariableNames: '' poolDictionaries: '' category: 'FFI-Kernel'! !ExternalData commentStamp: '' prior: 0! Instances of ExternalData explicitly describe objects with associated type. They can be used for describing atomic C types like arrays of atomic types (e.g., 'int[]') or pointer to atomic types (e.g., 'int *'). Instance variables: type The basic type of the receiver. The encoding of type is equivalent to that of the basic type in class ExternalType. The interpretation of whether the receiver describes an array of data or a pointer to data depends on the contents of the instance variable 'handle'. If handle contains an ExternalAddress the receiver is treated as pointer to type. If the handle contains a ByteArray the receiver is interpreted as describing an array of type. Note that both interpretations are treated equivalent in external calls, e.g., if one describes an argument to an external call as taking 'int*' then, depending on the type of handle either the actual contents (if ExternalAddress) or a pointer to the contents (if ByteArray) is passed. ! !ExternalData methodsFor: 'private' stamp: 'ar 11/21/1999 14:23'! setHandle: aHandle type: aType handle _ aHandle. type _ aType.! ! !ExternalData methodsFor: 'conversion' stamp: 'hg 2/25/2000 14:51'! fromCString "Assume that the receiver represents a C string and convert it to a Smalltalk string. hg 2/25/2000 14:18" | stream index char | type isPointerType ifFalse: [self error: 'External object is not a pointer type.']. stream _ WriteStream on: String new. index _ 1. [(char _ handle unsignedCharAt: index) = 0 asCharacter] whileFalse: [ stream nextPut: char. index _ index + 1]. ^stream contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalData class instanceVariableNames: ''! !ExternalData class methodsFor: 'field definition' stamp: 'ar 1/27/2000 01:23'! fields "ExternalData defineFields" "Note: The definition is for completeness only. ExternalData is treated specially by the VM." ^#(nil 'void*')! ! !ExternalData class methodsFor: 'instance creation' stamp: 'ar 12/2/1999 14:57'! fromHandle: aHandle type: aType "Create a pointer to the given type" "ExternalData fromHandle: ExternalAddress new type: ExternalType float" ^self basicNew setHandle: aHandle type: aType! ! !ExternalData class methodsFor: 'instance creation' stamp: 'ar 11/22/1999 04:28'! new "You better not..." ^self shouldNotImplement! ! Form subclass: #ExternalForm instanceVariableNames: 'display argbMap ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-External'! !ExternalForm commentStamp: '' prior: 0! An ExternalForm is just like any other form. It's only difference is that it is allocated on a specific display and can be used for accelerated blts on the particular display. Upon shutdown of the system ExternalForms will be deallocated from the display and be kept in their internalized form.! !ExternalForm methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 17:39'! destroy "Destroy the receiver" display ifNotNil:[display destroyForm: self]! ! !ExternalForm methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 17:48'! shutDown "System is going down. Internalize my bits and be finished." | copy | copy _ Form extent: self extent depth: self depth. self displayOn: copy. copy hibernate. "compact bits of copy" self destroy. "Release my external handle" bits _ copy bits. "Now compressed" display _ nil. "No longer allocated" argbMap _ nil. "No longer external"! ! !ExternalForm methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:13'! colormapFromARGB "Return a ColorMap mapping from canonical ARGB pixel values into the receiver" ^argbMap ifNil:[argbMap _ ColorMap mappingFromARGB: self rgbaBitMasks].! ! !ExternalForm methodsFor: 'accessing' stamp: 'ar 5/27/2000 16:56'! displayScreen "Return the display screen the receiver is allocated on." ^display! ! !ExternalForm methodsFor: 'accessing' stamp: 'ar 5/28/2000 17:42'! rgbaBitMasks "Return the masks for specifying the R,G,B, and A components in the receiver" display ifNil:[^super rgbaBitMasks] ifNotNil:[^display rgbaBitMasksOfForm: self]! ! !ExternalForm methodsFor: 'testing' stamp: 'ar 5/28/2000 17:48'! isExternalForm "I am an external form but only as long as I'm allocated on a display" ^display notNil! ! !ExternalForm methodsFor: 'private' stamp: 'ar 5/27/2000 16:36'! getExternalHandle "Private. Return the virtual handle used to represent the receiver" ^bits! ! !ExternalForm methodsFor: 'private' stamp: 'ar 5/27/2000 16:52'! setExternalHandle: aHandle on: aDisplay "Initialize the receiver from the given external handle" display _ aDisplay. bits _ aHandle.! ! WeakKeyDictionary subclass: #ExternalFormRegistry instanceVariableNames: 'lockFlag ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-External'! !ExternalFormRegistry commentStamp: '' prior: 0! The ExternalFormRegistry needs to be synchronized with rendering to prevent forms from being destroyed during rendering. Only at certain points (that is after a rendering cycle is completed) the texture registry may be cleaned up.! !ExternalFormRegistry methodsFor: 'accessing' stamp: 'ar 5/26/2000 00:40'! lock lockFlag _ true! ! !ExternalFormRegistry methodsFor: 'accessing' stamp: 'ar 5/26/2000 00:40'! unlock lockFlag _ false.! ! !ExternalFormRegistry methodsFor: 'finalization' stamp: 'ar 5/27/2000 00:56'! finalizeValues "This message is sent when an element has gone away." lockFlag == true ifTrue:[^self]. self forceFinalization.! ! !ExternalFormRegistry methodsFor: 'finalization' stamp: 'ar 5/27/2000 00:55'! forceFinalization self associationsDo:[:assoc| assoc key isNil ifTrue:[assoc value destroy]. ]. super finalizeValues.! ! ExternalObject subclass: #ExternalFunction instanceVariableNames: 'flags argTypes ' classVariableNames: 'FFIErrorMessages ' poolDictionaries: 'FFIConstants ' category: 'FFI-Kernel'! !ExternalFunction commentStamp: '' prior: 0! This class represents an external function called from Smalltalk. Instances of ExternalFunction can be created if the address/parameters of the function are known by some other means than loading from a shared library or compiling the appropriate primitive specification. Instance variables: flags a set of flags encoding the calling convention args the parameters of the function Implementation notes: The arguments consist of an array with the first element defining the return type, the remaining arguments defining the parameters of the call. ! !ExternalFunction methodsFor: 'initialize-release' stamp: 'ar 11/29/1999 00:35'! initialize "Initialize the receiver" handle _ ExternalAddress new.! ! !ExternalFunction methodsFor: 'accessing' stamp: 'ar 11/19/1999 19:13'! argTypes ^argTypes! ! !ExternalFunction methodsFor: 'accessing' stamp: 'ar 11/19/1999 19:13'! flags ^flags! ! !ExternalFunction methodsFor: 'accessing' stamp: 'ar 11/17/1999 19:55'! module ^nil! ! !ExternalFunction methodsFor: 'accessing' stamp: 'ar 11/17/1999 17:06'! name ^nil! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:54'! invoke ^self invokeWithArguments: #()! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:53'! invokeWith: arg1 ^self invokeWithArguments: (Array with: arg1)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:53'! invokeWith: arg1 with: arg2 ^self invokeWithArguments: (Array with: arg1 with: arg2)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:53'! invokeWith: arg1 with: arg2 with: arg3 ^self invokeWithArguments: (Array with: arg1 with: arg2 with: arg3)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 19:08'! invokeWith: arg1 with: arg2 with: arg3 with: arg4 ^self invokeWithArguments: (Array with: arg1 with: arg2 with: arg3 with: arg4)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:53'! invokeWith: arg1 with: arg2 with: arg3 with: arg4 with: arg5 ^self invokeWithArguments: (Array with: arg1 with: arg2 with: arg3 with: arg4 with: arg5)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/19/1999 21:54'! invokeWith: arg1 with: arg2 with: arg3 with: arg4 with: arg5 with: arg6 ^self invokeWithArguments: (Array with: arg1 with: arg2 with: arg3 with: arg4 with: arg5 with: arg6)! ! !ExternalFunction methodsFor: 'invoking' stamp: 'ar 11/28/1999 20:12'! invokeWithArguments: argArray "Manually invoke the receiver, representing an external function." ^self externalCallFailed! ! !ExternalFunction methodsFor: 'printing' stamp: 'ar 11/19/1999 16:35'! callingConventionString (flags allMask: FFICallTypeApi) ifTrue:[^'apicall'] ifFalse:[^'cdecl']! ! !ExternalFunction methodsFor: 'printing' stamp: 'ar 11/19/1999 19:12'! printOn: aStream aStream nextPut:$<; nextPutAll: self callingConventionString; nextPutAll:': '; print: argTypes first; space. self name == nil ifTrue:[aStream nextPutAll:'(*) '] ifFalse:[aStream print: self name asString; space]. aStream nextPut:$(. 2 to: argTypes size do:[:i| aStream print: (argTypes at: i). i < argTypes size ifTrue:[aStream space]]. aStream nextPut:$). self module == nil ifFalse:[ aStream space; nextPutAll:'module: '; print: self module asString. ]. aStream nextPut:$>! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalFunction class instanceVariableNames: ''! !ExternalFunction class methodsFor: 'class initialization' stamp: 'ar 11/19/1999 19:10'! initialize "ExternalFunction initialize" self initializeCallingConventions. self initializeErrorConstants.! ! !ExternalFunction class methodsFor: 'class initialization' stamp: 'ar 11/19/1999 16:35'! initializeCallingConventions "ExternalFunction initializeCallingConventions" #( (FFICallTypeCDecl 0) (FFICallTypeApi 1) ) do:[:spec| FFIConstants declare: spec first from: Undeclared. FFIConstants at: spec first put: spec last. ]. ! ! !ExternalFunction class methodsFor: 'class initialization' stamp: 'ar 11/28/1999 18:52'! initializeErrorConstants "ExternalFunction initializeErrorConstants" FFIErrorMessages _ Dictionary new. #( "No callout mechanism available" (FFINoCalloutAvailable -1 'Callout mechanism not available') "generic error" (FFIErrorGenericError 0 'A call to an external function failed') "primitive invoked without ExternalFunction" (FFIErrorNotFunction 1 'Only ExternalFunctions can be called') "bad arguments to primitive call" (FFIErrorBadArgs 2 'Bad arguments in primitive invokation') "generic bad argument" (FFIErrorBadArg 3 'Bad argument for external function') "int passed as pointer" (FFIErrorIntAsPointer 4 'Cannot use integer as pointer') "bad atomic type (e.g., unknown)" (FFIErrorBadAtomicType 5 'Unknown atomic type in external call') "argument coercion failed" (FFIErrorCoercionFailed 6 'Could not coerce arguments') "Type check for non-atomic types failed" (FFIErrorWrongType 7 'Wrong type in external call') "struct size wrong or too large" (FFIErrorStructSize 8 'Bad structure size in external call') "unsupported calling convention" (FFIErrorCallType 9 'Unsupported calling convention') "cannot return the given type" (FFIErrorBadReturn 10 'Cannot return the given type') "bad function address" (FFIErrorBadAddress 11 'Bad function address') "no module given but required for finding address" (FFIErrorNoModule 12 'No module to load address from') "function address not found" (FFIErrorAddressNotFound 13 'Unable to find function address') "attempt to pass 'void' parameter" (FFIErrorAttemptToPassVoid 14 'Cannot pass ''void'' parameter') "module not found" (FFIErrorModuleNotFound 15 'External module not found') "external library invalid" (FFIErrorBadExternalLibrary 16 'External library is invalid') "external function invalid" (FFIErrorBadExternalFunction 17 'External function is invalid') "ExternalAddress points to ST memory (don't you dare to do this!!)" (FFIErrorInvalidPointer 18 'Attempt to pass invalid pointer') ) do:[:spec| FFIConstants declare: spec first from: Undeclared. FFIConstants at: spec first put: spec second. FFIErrorMessages at: spec second put: spec third. ].! ! !ExternalFunction class methodsFor: 'constants' stamp: 'ar 11/19/1999 16:36'! callTypeAPI ^FFICallTypeApi! ! !ExternalFunction class methodsFor: 'constants' stamp: 'ar 11/19/1999 16:36'! callTypeCDecl ^FFICallTypeCDecl! ! !ExternalFunction class methodsFor: 'compiler support' stamp: 'ar 12/2/1999 16:20'! atomicTypeNamed: aString ^ExternalType atomicTypeNamed: aString! ! !ExternalFunction class methodsFor: 'compiler support' stamp: 'ar 11/17/1999 19:58'! callingConventionFor: aString "Return the constant describing the calling convention for the given string specification or nil if unknown." aString = 'cdecl:' ifTrue:[^self callTypeCDecl]. aString = 'apicall:' ifTrue:[^self callTypeAPI]. ^nil! ! !ExternalFunction class methodsFor: 'compiler support' stamp: 'ar 12/2/1999 16:49'! forceTypeNamed: aString ^ExternalType forceTypeNamed: aString! ! !ExternalFunction class methodsFor: 'compiler support' stamp: 'ar 12/2/1999 16:30'! isValidType: anObject ^anObject isBehavior and:[anObject includesBehavior: ExternalStructure]! ! !ExternalFunction class methodsFor: 'compiler support' stamp: 'ar 12/2/1999 16:21'! structTypeNamed: aString ^ExternalType structTypeNamed: aString! ! !ExternalFunction class methodsFor: 'error handling' stamp: 'ar 11/19/1999 14:17'! errorMessageFor: code "Return the error message for the given error code from the foreign function interface" ^FFIErrorMessages at: code ifAbsent:['Call to external function failed'].! ! !ExternalFunction class methodsFor: 'error handling' stamp: 'ar 11/19/1999 19:09'! externalCallFailed "Raise an error after a failed call to an external function" | errCode | errCode _ self getLastError. "this allows us to look at the actual error code" ^self error: (self errorMessageFor: errCode).! ! !ExternalFunction class methodsFor: 'error handling' stamp: 'ar 11/28/1999 18:37'! getLastError "Return the last error from an external call. Only valid immediately after the external call failed." ^-1! ! ExternalObject subclass: #ExternalLibrary instanceVariableNames: 'name ' classVariableNames: '' poolDictionaries: '' category: 'FFI-Kernel'! !ExternalLibrary commentStamp: '' prior: 0! An external library bundles calls to functions from the same library. It is provided mainly as convenience since every external function can be fully specified by the name and the module it resides in. Every external function that is defined in an external library by default will use the library it is defined in. This can always be modified by providing the appropriate module in the specification. ! !ExternalLibrary methodsFor: 'initialize-release' stamp: 'ar 12/8/1999 21:49'! forceLoading "Primitive. Force loading the given library. The primitive will fail if the library is not available or if anything is wrong with the receiver." ^self externalCallFailed "The primitive will set the error code"! ! !ExternalLibrary methodsFor: 'initialize-release' stamp: 'ar 11/29/1999 00:35'! initialize "Initialize the receiver" name _ self class moduleName. handle _ ExternalAddress new.! ! !ExternalLibrary methodsFor: 'accessing' stamp: 'ar 11/17/1999 19:35'! handle ^handle! ! !ExternalLibrary methodsFor: 'accessing' stamp: 'ar 11/17/1999 19:35'! name ^name! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalLibrary class instanceVariableNames: ''! !ExternalLibrary class methodsFor: 'accessing' stamp: 'ar 11/17/1999 19:33'! moduleName "Return the name of the module for this library" ^nil! ! ExternalFunction subclass: #ExternalLibraryFunction instanceVariableNames: 'name module ' classVariableNames: '' poolDictionaries: '' category: 'FFI-Kernel'! !ExternalLibraryFunction commentStamp: '' prior: 0! An ExternalLibraryFunction specifies a fully qualified function from an external library. Instance variables: name name or ordinal of function module name of module (nil if bound in the VM).! !ExternalLibraryFunction methodsFor: 'accessing' stamp: 'ar 11/17/1999 19:55'! module ^module! ! !ExternalLibraryFunction methodsFor: 'accessing' stamp: 'ar 11/17/1999 17:06'! name ^name! ! !ExternalLibraryFunction methodsFor: 'private' stamp: 'ar 11/19/1999 19:12'! name: aName module: aModule flags: anInteger argTypes: argTypeArray name _ aName. module _ aModule. flags _ anInteger. argTypes _ argTypeArray.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalLibraryFunction class instanceVariableNames: ''! !ExternalLibraryFunction class methodsFor: 'instance creation' stamp: 'ar 11/17/1999 14:52'! name: aName module: aModule callType: callType returnType: retType argumentTypes: argTypes ^self new name: aName module: aModule flags: callType argTypes: (Array with: retType), argTypes! ! Object subclass: #ExternalObject instanceVariableNames: 'handle ' classVariableNames: '' poolDictionaries: '' category: 'FFI-Kernel'! !ExternalObject commentStamp: '' prior: 0! External objects represent entities that are not part of the Smalltalk universe. They are accessed using a unique handle which is interpreted depending on the actual entity that is represented. Instance variables: handle ! !ExternalObject methodsFor: 'initialize-release' stamp: 'ar 11/29/1999 00:35'! initialize "Initialize the receiver"! ! !ExternalObject methodsFor: 'private' stamp: 'ar 11/16/1999 20:25'! getHandle "Private. Return the handle used to represent the external entitiy." ^handle! ! !ExternalObject methodsFor: 'private' stamp: 'ar 11/16/1999 20:26'! setHandle: anObject "Private. Set the handle used to represent the external entity." handle _ anObject! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalObject class instanceVariableNames: ''! !ExternalObject class methodsFor: 'class initialization' stamp: 'ar 11/19/1999 22:37'! initialize "ExternalObject initialize" Smalltalk addToStartUpList: self after: ShortRunArray.! ! !ExternalObject class methodsFor: 'instance creation' stamp: 'ar 11/17/1999 17:02'! new ^super new initialize! ! !ExternalObject class methodsFor: 'system startup' stamp: 'ar 11/28/1999 23:37'! install "Notify all instances of the receiver that we're coming up on a new platform. Note: The default implementation does nothing since the general external objects are cleaned up by ExternalAddress>>startUp: but subclasses may implement this method so that the appropriate action for existing instances can be taken."! ! !ExternalObject class methodsFor: 'system startup' stamp: 'ar 11/28/1999 23:36'! installSubclasses "Notify all the subclasses of ExternalObject that we are starting up on a new platform." self withAllSubclassesDo:[:cls| cls install].! ! !ExternalObject class methodsFor: 'system startup' stamp: 'ar 11/28/1999 23:36'! startUp: resuming "The system is coming up. If it is on a new platform, clear out the existing handles." ExternalAddress startUp: resuming. "Make sure handles are invalid" resuming ifTrue:[self installSubclasses]. ! ! DisplayScreen subclass: #ExternalScreen instanceVariableNames: 'argbMap allocatedForms ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-External'! !ExternalScreen commentStamp: '' prior: 0! I represent a DisplayScreen that is not part of the Squeak universe. Subclasses must implement the appropriate primitives for creating, destroying and allocating the appropriate external objects. Note: It is assumed that all external display surfaces are accessible by FXBlt, meaning that any support code must register the surfaces with the surface plugin. This requires that the support code will have a way of accessing the bits of the surface. Although this can be terribly expensive (such as on X where a roundtrip to the server might be required or for an OpenGL display where glReadPixels usually is slow as hell) the appropriate methods should be implemented. This allows for a gradual transition to less expensive model (such as implementing an X11Canvas supporting the drawing primitives of X) and is therefore the preferred solution. In the eventual case that it's known that BitBlt/FXBlt will *never* be used in conjunction with a particular drawing surface, the support code should return a handle that is a) not a SmallInteger (these are used by the surface plugin) and b) not of the 'bitsSize' of a Form. One possible representation for such a handle would be a ByteArray of a non-integral word size (e.g., a ByteArray of size 5,6, or 7). In this case, all attempts to use FXBlt with the drawing surface will simply fail. ! !ExternalScreen methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 18:44'! destroy "Destroy the receiver" allocatedForms ifNotNil:[ allocatedForms lock. "Make sure we don't get interrupted" allocatedForms forceFinalization. "Clean up all lost references" allocatedForms keys do:[:stillValid| stillValid shutDown]. "All remaining references are simply destroyed" allocatedForms associationsDo:[:assoc| assoc key: nil]. allocatedForms forceFinalization. "destroy all others" allocatedForms _ nil. ]. bits ifNotNil:[self primDestroyDisplaySurface: bits]. bits _ nil.! ! !ExternalScreen methodsFor: 'initialize-release' stamp: 'ar 5/27/2000 01:30'! finish "Flush the receiver" self primFinish: bits. "Now is the time to do some cleanup" allocatedForms unlock. allocatedForms finalizeValues.! ! !ExternalScreen methodsFor: 'initialize-release' stamp: 'ar 5/27/2000 01:30'! flush "Flush the receiver" self primFlush: bits.! ! !ExternalScreen methodsFor: 'initialize-release' stamp: 'ar 5/27/2000 20:16'! release "I am no longer Display. Release any resources if necessary" self destroy! ! !ExternalScreen methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 19:00'! shutDown "Minimize Display memory saved in image" self destroy. width _ 240. height _ 120. bits _ Bitmap new: self bitsSize.! ! !ExternalScreen methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:18'! colormapFromARGB "Return a ColorMap mapping from canonical ARGB pixel values into the receiver" ^argbMap ifNil:[argbMap _ ColorMap mappingFromARGB: self rgbaBitMasks].! ! !ExternalScreen methodsFor: 'accessing' stamp: 'ar 5/26/2000 19:44'! defaultBitBltClass ^FXBlt! ! !ExternalScreen methodsFor: 'accessing' stamp: 'ar 5/26/2000 19:45'! defaultWarpBltClass ^FXBlt! ! !ExternalScreen methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:18'! rgbaBitMasks "Return the masks for specifying the R,G,B, and A components in the receiver" | rgbaBitMasks | rgbaBitMasks _ Array new: 4. self primDisplay: bits colorMasksInto: rgbaBitMasks. ^rgbaBitMasks! ! !ExternalScreen methodsFor: 'testing' stamp: 'ar 5/27/2000 20:17'! isExternalForm "Sorta. Kinda." ^true! ! !ExternalScreen methodsFor: 'form support' stamp: 'ar 5/28/2000 16:55'! allocateForm: extentPoint "Allocate a new form which is similar to the receiver and can be used for accelerated blts" | formHandle displayForm | formHandle _ self primAllocateForm: self depth width: extentPoint x height: extentPoint y. formHandle = nil ifTrue:[^super allocateForm: extentPoint]. displayForm _ (ExternalForm extent: extentPoint depth: self depth bits: nil) setExternalHandle: formHandle on: self. allocatedForms at: displayForm put: displayForm executor. ^displayForm! ! !ExternalScreen methodsFor: 'form support' stamp: 'ar 5/28/2000 15:56'! destroyForm: anExternalForm "Destroy the given external form" self primDestroyForm: anExternalForm getExternalHandle. anExternalForm setExternalHandle: nil on: nil. allocatedForms removeKey: anExternalForm ifAbsent:[].! ! !ExternalScreen methodsFor: 'form support' stamp: 'ar 5/27/2000 18:03'! rgbaBitMasksOfForm: anExternalForm | rgbaBitMasks | rgbaBitMasks _ Array new: 4. self primForm: anExternalForm getExternalHandle colorMasksInto: rgbaBitMasks. ^rgbaBitMasks! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/28/2000 01:12'! allocateOrRecycleTexture: aB3DTexture "If a texture for the given one has already been allocated return it. If not, allocate a new texture." | texture | allocatedForms lock. "Rendering may begin any time" ^allocatedForms at: aB3DTexture ifAbsent:[ texture _ self allocateTexture: aB3DTexture. texture ifNotNil:[ allocatedForms at: aB3DTexture put: texture. aB3DTexture hasBeenModified: true]. texture]! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/28/2000 00:26'! allocateTexture: aB3DTexture "Allocate a new texture for the given (Squeak internal) form. NOTE: The size/depth of the texture allocated can differ. Right now there's an implicit strategy in the primitive code for choosing the right tradeoff between speed and space. In the optimal case this will result in a texture which is 'good enough' for what we have but if that can't be achieved anything might come back. Also, textures might be subject to certain restrictions. Some graphics cards have minimum/maximum sizes of textures (some older even require squared textures) and this needs to be taken into account by the primitive. One thing that's currently not handled is if insufficient memory is encountered. This can happen if there's just not enough VRAM or AGP memory. A good idea would be to free up some of the already allocated textures but it's not quite sure if that'll do the trick and it would require flushing the renderer. Tricky." | textureHandle | textureHandle _ self primAllocateTexture: aB3DTexture depth width: aB3DTexture width height: aB3DTexture height. textureHandle = nil ifTrue:[^nil]. "And return the allocated texture. Note: #setExternalHandle: will automatically check for w/h/d" ^(ExternalTexture initializeFrom: aB3DTexture) setExternalHandle: textureHandle on: self! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/27/2000 17:04'! depthOfTexture: anExternalTexture "Return the actual height of the given external texture" ^self primGetTextureDepth: anExternalTexture getExternalHandle! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/27/2000 17:44'! destroyTexture: anExternalTexture "Destroy the given external form" self primDestroyTexture: anExternalTexture getExternalHandle. anExternalTexture setExternalHandle: nil on: nil.! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/27/2000 17:04'! heightOfTexture: anExternalTexture "Return the actual height of the given external texture" ^self primGetTextureHeight: anExternalTexture getExternalHandle! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/27/2000 18:04'! rgbaBitMasksOfTexture: anExternalTexture | rgbaBitMasks | rgbaBitMasks _ Array new: 4. self primTexture: anExternalTexture getExternalHandle colorMasksInto: rgbaBitMasks. ^rgbaBitMasks! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 6/9/2000 19:27'! textureHandleOf: aTexture | textureForm | aTexture ifNil:[^-1]. textureForm _ self allocateOrRecycleTexture: aTexture. textureForm ifNil:[^-1]. "Update textureForm if aTexture is dirty" aTexture hasBeenModified ifTrue:[ "Use the best method we have" aTexture displayInterpolatedOn: textureForm. aTexture hasBeenModified: false]. ^textureForm getExternalHandle! ! !ExternalScreen methodsFor: 'texture support' stamp: 'ar 5/27/2000 17:03'! widthOfTexture: anExternalTexture "Return the actual width of the given external texture" ^self primGetTextureWidth: anExternalTexture getExternalHandle! ! !ExternalScreen methodsFor: 'blitting support' stamp: 'ar 5/28/2000 16:16'! copyBits: sourceRect from: sourceForm at: destOrigin clippingBox: clipRect rule: rule fillColor: hf map: map "Attempt to accelerate blts to the receiver" | r | ((self isBltAccelerated: rule for: sourceForm) and:[map == nil and:[hf == nil]]) ifTrue:[ "Try an accelerated blt" r _ (destOrigin extent: sourceRect extent) intersect: (clipRect intersect: clippingBox). r area <= 0 ifTrue:[^self]. (self primBltFast: bits from: sourceForm getExternalHandle at: r origin from: sourceRect origin extent: r extent) ifNotNil:[^self]. ]. ^super copyBits: sourceRect from: sourceForm at: destOrigin clippingBox: clipRect rule: rule fillColor: hf map: map! ! !ExternalScreen methodsFor: 'blitting support' stamp: 'ar 5/28/2000 16:16'! displayOn: destForm at: destOrigin clippingBox: clipRect rule: rule fillColor: hf "Attempt to accelerate blts to aDisplayMedium" | sourceRect | ((self isBltAccelerated: rule for: destForm) and:[hf = nil]) ifTrue:[ "Try an accelerated blt" sourceRect _ (clipRect translateBy: destOrigin negated) intersect: clippingBox. (self primBltFast: bits to: destForm getExternalHandle at: 0@0 from: sourceRect origin extent: sourceRect extent) ifNotNil:[^self]]. destForm copyBits: self boundingBox from: self at: destOrigin + self offset clippingBox: clipRect rule: rule fillColor: hf map: (self colormapIfNeededFor: destForm). ! ! !ExternalScreen methodsFor: 'blitting support' stamp: 'ar 5/27/2000 21:09'! fill: aRectangle rule: anInteger fillColor: aColor "Replace a rectangular area of the receiver with the pattern described by aForm according to the rule anInteger." | rect | (self isFillAccelerated: anInteger for: aColor) ifTrue:[ rect _ aRectangle intersect: clippingBox. (self primFill: bits color: (self pixelWordFor: aColor) x: rect left y: rect top w: rect width h: rect height) ifNotNil:[^self]]. ^super fill: aRectangle rule: anInteger fillColor: aColor! ! !ExternalScreen methodsFor: 'blitting support' stamp: 'ar 5/28/2000 15:52'! isBltAccelerated: ruleInteger for: aForm "Return true if the receiver can perform accelerated blt operations by itself. It is assumed that blts of forms allocated on the receiverusing Form>>over may be accelerated. Although some hardware may allow source-key blts (that is, Form>>paint or similar) this is usually questionable and the additional effort for allocating and maintaining the OS form doesn't quite seem worth the effort." ^aForm displayScreen == self and:[ruleInteger = Form over]! ! !ExternalScreen methodsFor: 'blitting support' stamp: 'ar 5/27/2000 16:09'! isFillAccelerated: ruleInteger for: aColor "Return true if the receiver can perform accelerated fill operations by itself. It is assumed that the hardware can accelerate plain color fill operations." ^ruleInteger = Form over and:[aColor isColor]! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:18'! primBltFast: displayHandle from: sourceHandle at: destOrigin from: sourceOrigin extent: extent "Primitive. Perform a fast blt operation. Return the receiver if successful." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/28/2000 01:46'! primBltFast: displayHandle to: dstHandle at: destOrigin from: sourceOrigin extent: extent "Primitive. Perform a fast blt operation. Return the receiver if successful." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:18'! primCreateDisplaySurface: d width: w height: h "Primitive. Create a new external display surface. Return the handle used to identify the receiver. Fail if the surface cannot be created." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:18'! primDestroyDisplaySurface: aHandle "Primitive. Destroy the display surface associated with the given handle." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:19'! primDisplay: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:19'! primFill: handle color: pixelWord x: x y: y w: w h: h "Primitive. Perform an accelerated fill operation on the receiver." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:20'! primFinish: aHandle "Primitive. Finish all rendering operations on the receiver. Do not return before all rendering operations have taken effect." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:21'! primFlush: aHandle "Primitive. If any rendering operations are pending, force them to be executed. Do not wait until they have taken effect." ^nil! ! !ExternalScreen methodsFor: 'primitives-display' stamp: 'ar 5/27/2000 17:21'! supportsDisplayDepth: pixelDepth "Return true if this pixel depth is supported on the current host platform." ^false! ! !ExternalScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:22'! primAllocateForm: d width: w height: h "Primitive. Allocate a form with the given parameters" ^nil! ! !ExternalScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:22'! primDestroyForm: aHandle "Primitive. Destroy the form associated with the given handle." ^nil! ! !ExternalScreen methodsFor: 'primitives-forms' stamp: 'ar 5/27/2000 17:22'! primForm: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:23'! primAllocateTexture: d width: w height: h "Primitive. Allocate a texture with the given dimensions. Note: The texture allocated may *not* match the specified values here." ^nil! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:23'! primDestroyTexture: aHandle "Primitive. Destroy the texture associated with the given handle." ^nil! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:23'! primGetTextureDepth: aHandle "Primitive. Return the actual depth of the texture with the given handle" ^self primitiveFailed! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primGetTextureHeight: aHandle "Primitive. Return the actual height of the texture with the given handle" ^self primitiveFailed! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primGetTextureWidth: aHandle "Primitive. Return the actual width of the texture with the given handle" ^self primitiveFailed! ! !ExternalScreen methodsFor: 'primitives-textures' stamp: 'ar 5/27/2000 17:24'! primTexture: aHandle colorMasksInto: anArray "Primitive. Store the bit masks for each color into the given array." ^nil! ! !ExternalScreen methodsFor: 'private' stamp: 'ar 5/28/2000 19:00'! setExtent: aPoint depth: bitsPerPixel "Create a 3D accelerated display screen" | screen | (bits isInteger and:[depth == bitsPerPixel and: [aPoint = self extent and: [self supportsDisplayDepth: bitsPerPixel]]]) ifFalse: [ bits ifNotNil:[self primDestroyDisplaySurface: bits]. bits _ nil. "Free up old bitmap in case space is low" DisplayChangeSignature _ (DisplayChangeSignature ifNil: [0]) + 1. (self supportsDisplayDepth: bitsPerPixel) ifTrue:[depth _ bitsPerPixel] ifFalse:["Search for a suitable depth" depth _ self findAnyDisplayDepthIfNone:[nil]]. depth == nil ifFalse:[ bits _ self primCreateDisplaySurface: depth width: aPoint x height: aPoint y]. "Bail out if surface could not be created" (bits == nil) ifTrue:[ screen _ DisplayScreen extent: aPoint depth: bitsPerPixel. self == Display ifTrue:[ Display _ screen. Display beDisplay]. ^screen]. width _ aPoint x. height _ aPoint y. ]. clippingBox _ super boundingBox. allocatedForms ifNil:[ allocatedForms _ ExternalFormRegistry new. WeakArray addWeakDependent: allocatedForms]. ! ! Object subclass: #ExternalSemaphoreTable instanceVariableNames: '' classVariableNames: 'ProtectTable ' poolDictionaries: '' category: 'System-Support'! !ExternalSemaphoreTable commentStamp: '' prior: 0! By John M McIntosh johnmci@smalltalkconsulting.com This class was written to mange the external semaphore table. When I was writing a Socket test server I discovered various race conditions on the access to the externalSemaphore table. This new class uses class side methods to restrict access using a mutex semaphore. It seemed cleaner to deligate the reponsibility here versus adding more code and another class variable to SystemDictionary Note that in Smalltalk recreateSpecialObjectsArray we still directly play with the table.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalSemaphoreTable class instanceVariableNames: ''! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 20:36'! clearExternalObjects "Clear the array of objects that have been registered for use in non-Smalltalk code." ProtectTable critical: [Smalltalk specialObjectsArray at: 39 put: Array new]. ! ! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 21:01'! externalObjects ^ProtectTable critical: [Smalltalk specialObjectsArray at: 39].! ! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 20:44'! registerExternalObject: anObject ^ ProtectTable critical: [self safelyRegisterExternalObject: anObject] ! ! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 20:57'! safelyRegisterExternalObject: anObject "Register the given object in the external objects array and return its index. If it is already there, just return its index." | objects firstEmptyIndex obj sz newObjects | objects _ Smalltalk specialObjectsArray at: 39. "find the first empty slot" firstEmptyIndex _ 0. 1 to: objects size do: [:i | obj _ objects at: i. obj == anObject ifTrue: [^ i]. "object already there, just return its index" (obj == nil and: [firstEmptyIndex = 0]) ifTrue: [firstEmptyIndex _ i]]. "if no empty slots, expand the array" firstEmptyIndex = 0 ifTrue: [ sz _ objects size. newObjects _ objects species new: sz + 20. "grow linearly" newObjects replaceFrom: 1 to: sz with: objects startingAt: 1. firstEmptyIndex _ sz + 1. Smalltalk specialObjectsArray at: 39 put: newObjects. objects _ newObjects]. objects at: firstEmptyIndex put: anObject. ^ firstEmptyIndex ! ! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 20:59'! safelyUnregisterExternalObject: anObject "Unregister the given object in the external objects array. Do nothing if it isn't registered. JMM change to return if we clear the element, since it should only appear once in the array" | objects | anObject ifNil: [^ self]. objects _ Smalltalk specialObjectsArray at: 39. 1 to: objects size do: [:i | (objects at: i) == anObject ifTrue: [objects at: i put: nil. ^self]]. ! ! !ExternalSemaphoreTable class methodsFor: 'accessing' stamp: 'JMM 6/6/2000 20:45'! unregisterExternalObject: anObject ProtectTable critical: [self safelyUnregisterExternalObject: anObject] ! ! !ExternalSemaphoreTable class methodsFor: 'initialize' stamp: 'JMM 6/6/2000 20:32'! initialize ProtectTable _ Semaphore forMutualExclusion! ! ExternalObject subclass: #ExternalStructure instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'FFIConstants ' category: 'FFI-Kernel'! !ExternalStructure commentStamp: '' prior: 0! This class provides an abstract base for all structures that can be used by external functions. ExternalStructures have two possible handle types: - ExternalAddress If the handle is an external address then the object described does not reside in the Smalltalk object memory. - ByteArray If the handle is a byte array then the object described resides in Smalltalk memory. Useful methods should be implemented by subclasses of ExternalStructure using the common ByteArray/ExternalAddress platform dependent access protocol which will transparently access the correct memory location.! !ExternalStructure methodsFor: 'initialize-release' stamp: 'ar 11/28/1999 23:21'! free "Free the handle pointed to by the receiver" (handle ~~ nil and:[handle isExternalAddress]) ifTrue:[handle free]. handle _ nil.! ! !ExternalStructure methodsFor: 'inspecting' stamp: 'hg 2/28/2000 15:00'! inspect "Open an ExternalStructureInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." self class fields size > 0 ifTrue: [ExternalStructureInspector openOn: self withEvalPane: true] ifFalse: [super inspect]! ! !ExternalStructure methodsFor: 'printing' stamp: 'bf 5/18/2000 16:26'! longPrintOn: aStream "Append to the argument, aStream, the names and values of all the record's variables." | fields | fields _ self class fields. (fields isEmpty or: [fields first isNil]) ifTrue: [fields _ #()] ifFalse: [(fields first isKindOf: Array) ifFalse: [fields _ Array with: fields]]. fields do: [ :field | field first ~~ #nil ifTrue: [ aStream nextPutAll: field first; nextPut: $:; space; tab. (self perform: field first) printOn: aStream. aStream cr]].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalStructure class instanceVariableNames: 'compiledSpec '! !ExternalStructure class methodsFor: 'instance creation' stamp: 'ar 12/1/1999 15:58'! externalNew "Create an instance of the receiver on the external heap" ^self fromHandle: (ExternalAddress allocate: self byteSize)! ! !ExternalStructure class methodsFor: 'instance creation' stamp: 'ar 11/29/1999 00:36'! fromHandle: aHandle ^self basicNew setHandle: aHandle! ! !ExternalStructure class methodsFor: 'instance creation' stamp: 'ar 12/1/1999 15:58'! new ^self fromHandle: (ByteArray new: self byteSize)! ! !ExternalStructure class methodsFor: 'converting' stamp: 'ar 12/2/1999 16:55'! externalType "Return an external type describing the receiver as a structure" ^ExternalType structTypeNamed: self name! ! !ExternalStructure class methodsFor: 'class management' stamp: 'ar 11/22/1999 10:10'! doneCompiling "I have been recompiled. Update any types that reference me." ExternalType noticeModificationOf: self.! ! !ExternalStructure class methodsFor: 'class management' stamp: 'ar 1/26/2000 14:20'! fileOutInitializerOn: aFileStream super fileOutInitializerOn: aFileStream. aFileStream cr. aFileStream cr. aFileStream nextChunkPut: self name , ' compileFields'. aFileStream cr.! ! !ExternalStructure class methodsFor: 'class management' stamp: 'ar 1/26/2000 14:19'! fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex initializing: aBool super fileOutOn: aFileStream moveSource: moveSource toFile: fileIndex initializing: aBool. (aBool and:[moveSource not]) ifTrue: [aFileStream cr. aFileStream cr. aFileStream nextChunkPut: self name , ' compileFields'. aFileStream cr]! ! !ExternalStructure class methodsFor: 'class management' stamp: 'sma 6/16/2000 22:12'! obsolete "The receiver is becoming obsolete. NOTE: You if you remove the whole class category at once, you cannot assume that the ExternalType class is still present." Smalltalk at: #ExternalType ifPresent: [:class | class noticeRemovalOf: self]. ^ super obsolete! ! !ExternalStructure class methodsFor: 'class management' stamp: 'ar 11/22/1999 04:12'! rename: aString | oldName | oldName _ name. super rename: aString. oldName = name ifFalse:[ExternalType noticeRenamingOf: self from: oldName to: name].! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:31'! byteSize "Return the size in bytes of this structure." ^self compiledSpec first bitAnd: FFIStructSizeMask! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 1/27/2000 00:32'! compileAlias: spec withAccessors: aBool "Define all the fields in the receiver. Return the newly compiled spec." | fieldName fieldType isPointerField externalType | fieldName _ spec first. fieldType _ spec second. isPointerField _ fieldType last = $*. fieldType _ fieldType copyWithout: $*. externalType _ ExternalType atomicTypeNamed: fieldType. externalType == nil ifTrue:["non-atomic" Symbol hasInterned: fieldType ifTrue:[:sym| externalType _ ExternalType structTypeNamed: sym]]. externalType == nil ifTrue:[ Transcript show:'(', fieldType,' is void)'. externalType _ ExternalType void]. isPointerField ifTrue:[externalType _ externalType asPointerType]. (fieldName ~~ #nil and:[aBool]) ifTrue:[ self defineAliasAccessorsFor: fieldName type: externalType]. isPointerField ifTrue:[compiledSpec _ WordArray with: (ExternalType structureSpec bitOr: ExternalType pointerSpec)] ifFalse:[compiledSpec _ externalType compiledSpec]. ExternalType noticeModificationOf: self. ^compiledSpec! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 15:35'! compileAllFields "ExternalStructure compileAllFields" self withAllSubclassesDo:[:cls| cls compileFields. ].! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:28'! compileFields "Compile the field definition of the receiver. Return the newly compiled spec." ^self compileFields: self fields! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:28'! compileFields: fieldSpec "Compile the field definition of the receiver. Return the newly compiled spec." ^self compileFields: fieldSpec withAccessors: false.! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'hg 2/29/2000 10:54'! compileFields: specArray withAccessors: aBool "Define all the fields in the receiver. Return the newly compiled spec." | fieldName fieldType isPointerField externalType byteOffset typeSize typeSpec | (specArray size > 0 and:[specArray first class ~~ Array]) ifTrue:[^self compileAlias: specArray withAccessors: aBool]. byteOffset _ 1. typeSpec _ WriteStream on: (WordArray new: 10). typeSpec nextPut: (FFIFlagStructure). "dummy for size" specArray do:[:spec| fieldName _ spec first. fieldType _ spec second. isPointerField _ fieldType last = $*. fieldType _ (fieldType findTokens: ' *') first. externalType _ ExternalType atomicTypeNamed: fieldType. externalType == nil ifTrue:["non-atomic" Symbol hasInterned: fieldType ifTrue:[:sym| externalType _ ExternalType structTypeNamed: sym]]. externalType == nil ifTrue:[ Transcript show:'(', fieldType,' is void)'. externalType _ ExternalType void]. isPointerField ifTrue:[externalType _ externalType asPointerType]. typeSize _ externalType byteSize. spec size > 2 ifTrue:["extra size" spec third < typeSize ifTrue:[^self error:'Explicit type size is less than expected']. typeSize _ spec third]. (fieldName ~~ #nil and:[aBool]) ifTrue:[ self defineFieldAccessorsFor: fieldName startingAt: byteOffset type: externalType]. typeSpec nextPutAll: (externalType embeddedSpecWithSize: typeSize). byteOffset _ byteOffset + typeSize. ]. compiledSpec _ typeSpec contents. compiledSpec at: 1 put: (byteOffset - 1 bitOr: FFIFlagStructure). ExternalType noticeModificationOf: self. ^compiledSpec! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:28'! compiledSpec "Return the compiled spec of the receiver" ^compiledSpec ifNil:[self compileFields].! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 1/27/2000 00:54'! defineAliasAccessorsFor: fieldName type: type "Define read/write accessors for the given field" | code refClass argName | (type isVoid and:[type isPointerType not]) ifTrue:[^self]. refClass _ type referentClass. code _ String streamContents:[:s| s nextPutAll: fieldName; crtab; nextPutAll:'"This method was automatically generated"'; crtab. refClass == nil ifTrue:[(type isAtomic and:[type isPointerType not]) ifTrue:[s nextPutAll:'^handle'] ifFalse:[s nextPutAll:'^ExternalData fromHandle: handle'. type isPointerType ifTrue:[s nextPutAll:' asExternalPointer']. s nextPutAll:' type: '; nextPutAll: type externalTypeName]] ifFalse:[s nextPutAll:'^', refClass name,' fromHandle: handle'. type isPointerType ifTrue:[s nextPutAll:' asExternalPointer']]]. self compile: code classified: 'accessing'. code _ String streamContents:[:s| argName _ refClass == nil ifTrue:[(type isAtomic and:[type isPointerType not]) ifTrue:['anObject'] ifFalse:['anExternalData']] ifFalse:['a',refClass name]. s nextPutAll: fieldName,': '; nextPutAll: argName; crtab; nextPutAll:'"This method was automatically generated"'; crtab. (refClass == nil and:[type isAtomic and:[type isPointerType not]]) ifTrue:[s nextPutAll:'handle _ ', argName] ifFalse:[s nextPutAll:'handle _ ', argName,' getHandle'. type isPointerType ifTrue:[s nextPutAll:' asByteArrayPointer']]]. self compile: code classified: 'accessing'.! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 11/29/1999 00:42'! defineFieldAccessorsFor: fieldName startingAt: byteOffset type: type "Define read/write accessors for the given field" | code | (type isVoid and:[type isPointerType not]) ifTrue:[^self]. code _ fieldName,' "This method was automatically generated" ', (type readFieldAt: byteOffset). self compile: code classified: 'accessing'. code _ fieldName,': anObject "This method was automatically generated" ', (type writeFieldAt: byteOffset with:'anObject'). self compile: code classified: 'accessing'.! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:37'! defineFields "Define all the fields in the receiver" self defineFields: self fields.! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 12/2/1999 14:38'! defineFields: fields "Define all the fields in the receiver" self compileFields: fields withAccessors: true.! ! !ExternalStructure class methodsFor: 'field definition' stamp: 'ar 11/29/1999 00:28'! fields "Return the fields defining the receiver" ^#()! ! Inspector subclass: #ExternalStructureInspector instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Inspector'! !ExternalStructureInspector methodsFor: 'selecting' stamp: 'hg 2/28/2000 14:12'! replaceSelectionValue: anObject "Add colon to fieldname to get setter selector, and send it to object with the argument. Refer to the comment in Inspector|replaceSelectionValue:." selectionIndex = 1 ifTrue: [^object] ifFalse: [^object perform: ((self fieldList at: selectionIndex), ':') asSymbol with: anObject]! ! !ExternalStructureInspector methodsFor: 'selecting' stamp: 'hg 2/28/2000 14:22'! selection "Refer to the comment in Inspector|selection." selectionIndex = 0 ifTrue:[^object printString]. selectionIndex = 1 ifTrue: [^object]. selectionIndex = 2 ifTrue:[^object longPrintString]. selectionIndex > 2 ifTrue: [^object perform: (self fieldList at: selectionIndex)]! ! !ExternalStructureInspector methodsFor: 'accessing' stamp: 'hg 2/28/2000 14:20'! fieldList ^ (Array with: 'self: ', object defaultLabelForInspector with: 'all inst vars'), self recordFieldList! ! !ExternalStructureInspector methodsFor: 'accessing' stamp: 'bf 5/18/2000 16:33'! recordFieldList | fields | fields _ object class fields. (fields first isKindOf: Array) ifFalse: [fields _ Array with: fields]. ^fields collect: [ :field | field first ] thenSelect: [:name | name ~~ #nil]! ! B3DTexture subclass: #ExternalTexture instanceVariableNames: 'display argbMap ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-External'! !ExternalTexture commentStamp: '' prior: 0! An ExternalTexture is a B3DTexture which is allocated on a specific (hopefully hardware accelerated) display. External textures are resources that are exclusively managed on the Display - no external textures should be manually allocated. This is because the texture format provided by the display may severely differ from our internal format. Therefore we always keep an 'original' B3DTexture and the external textures are allocated on demand. Because of the above, external textures will never be kept inbetween sessions.! !ExternalTexture methodsFor: 'initialize-release' stamp: 'ar 5/27/2000 16:58'! destroy "Destroy the receiver" ^display destroyTexture: self! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 17:10'! actualDepth "Return the actual depth of the receiver" ^self displayScreen depthOfTexture: self! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 17:09'! actualHeight "Return the actual height of the receiver" ^self displayScreen heightOfTexture: self! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 17:09'! actualWidth "Return the actual width of the receiver" ^self displayScreen widthOfTexture: self! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:19'! colormapFromARGB "Return a ColorMap mapping from canonical ARGB pixel values into the receiver" ^argbMap ifNil:[argbMap _ ColorMap mappingFromARGB: self rgbaBitMasks].! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 16:58'! displayScreen "Return the display screen the receiver is allocated on." ^display! ! !ExternalTexture methodsFor: 'accessing' stamp: 'ar 5/27/2000 20:20'! rgbaBitMasks "Return the masks for specifying the R,G,B, and A components in the receiver" ^self displayScreen rgbaBitMasksOfTexture: self! ! !ExternalTexture methodsFor: 'testing' stamp: 'ar 5/27/2000 16:58'! isExternalForm "Sorta. Kinda. But not really..." ^true! ! !ExternalTexture methodsFor: 'testing' stamp: 'ar 5/27/2000 17:11'! isExternalTexture ^true! ! !ExternalTexture methodsFor: 'private' stamp: 'ar 5/27/2000 16:59'! getExternalHandle "Private. Return the virtual handle used to represent the receiver" ^bits! ! !ExternalTexture methodsFor: 'private' stamp: 'ar 5/27/2000 17:01'! initializeFrom: aTexture "Private. Initialize the receiver from aTexture. Note: width/height/depth are not set here since textures are subject to restricted allocation and need to be handled specially." wrap _ aTexture wrap. envMode _ aTexture envMode. interpolate _ aTexture interpolate.! ! !ExternalTexture methodsFor: 'private' stamp: 'ar 5/27/2000 17:43'! setExternalHandle: aHandle on: aDisplay "Initialize the receiver from the given external handle" display _ aDisplay. bits _ aHandle. (display notNil and:[bits notNil]) ifTrue:[ "Now we can find out what the format of the receiver is" width _ self actualWidth. height _ self actualHeight. depth _ self actualDepth. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalTexture class instanceVariableNames: ''! !ExternalTexture class methodsFor: 'instance creation' stamp: 'ar 5/27/2000 20:49'! initializeFrom: anotherForm ^self basicNew initializeFrom: anotherForm! ! Object subclass: #ExternalType instanceVariableNames: 'compiledSpec referentClass referencedType ' classVariableNames: 'AtomicSelectors AtomicTypeNames AtomicTypes StructTypes ' poolDictionaries: 'FFIConstants ' category: 'FFI-Kernel'! !ExternalType commentStamp: '' prior: 0! An external type represents the type of external objects. Instance variables: compiledSpec Compiled specification of the external type referentClass Class type of argument required referencedType Associated (non)pointer type with the receiver Compiled Spec: The compiled spec defines the type in terms which are understood by the VM. Each word is defined as: bits 0...15 - byte size of the entity bit 16 - structure flag (FFIFlagStructure) This flag is set if the following words define a structure bit 17 - pointer flag (FFIFlagPointer) This flag is set if the entity represents a pointer to another object bit 18 - atomic flag (FFIFlagAtomic) This flag is set if the entity represents an atomic type. If the flag is set the atomic type bits are valid. bits 19...23 - unused bits 24...27 - atomic type (FFITypeVoid ... FFITypeDoubleFloat) bits 28...31 - unused Note that all combinations of the flags FFIFlagPointer, FFIFlagAtomic, and FFIFlagStructure are invalid, EXCEPT from the following: FFIFlagPointer + FFIFlagAtomic: This defines a pointer to an atomic type (e.g., 'char*', 'int*'). The actual atomic type is represented in the atomic type bits. FFIFlagPointer + FFIFlagStructure: This defines a structure which is a typedef of a pointer type as in typedef void* VoidPointer; typedef Pixmap* PixmapPtr; It requires a byte size of four (e.g. a 32bit pointer) to work correctly. [Note: Other combinations may be allowed in the future] ! !ExternalType methodsFor: 'accessing' stamp: 'ar 12/2/1999 14:15'! atomicType ^(self headerWord bitAnd: FFIAtomicTypeMask) >> FFIAtomicTypeShift! ! !ExternalType methodsFor: 'accessing' stamp: 'ar 12/2/1999 14:11'! byteSize "Return the size in bytes of this type" ^self headerWord bitAnd: FFIStructSizeMask! ! !ExternalType methodsFor: 'accessing' stamp: 'ar 12/2/1999 14:29'! compiledSpec "Return the compiled spec of the receiver" ^compiledSpec! ! !ExternalType methodsFor: 'accessing' stamp: 'ar 12/2/1999 14:11'! referentClass "Return the class specifying the receiver" ^referentClass! ! !ExternalType methodsFor: 'testing' stamp: 'ar 12/2/1999 20:27'! isAtomic "Return true if the receiver describes a built-in type" ^self headerWord anyMask: FFIFlagAtomic! ! !ExternalType methodsFor: 'testing' stamp: 'ar 12/2/1999 14:14'! isIntegerType "Return true if the receiver is a built-in integer type" | type | type _ self atomicType. ^type > FFITypeBool and:[type <= FFITypeUnsignedLongLong]! ! !ExternalType methodsFor: 'testing' stamp: 'ar 1/27/2000 00:29'! isPointerType "Return true if the receiver represents a pointer type" ^self isStructureType not and:[self headerWord anyMask: FFIFlagPointer]! ! !ExternalType methodsFor: 'testing' stamp: 'ar 12/2/1999 14:15'! isSigned "Return true if the receiver is a signed type. Note: Only useful for integer types." ^self atomicType anyMask: 1! ! !ExternalType methodsFor: 'testing' stamp: 'ar 12/2/1999 14:15'! isStructureType "Return true if the receiver represents a structure type" ^self headerWord anyMask: FFIFlagStructure! ! !ExternalType methodsFor: 'testing' stamp: 'ar 11/18/1999 18:28'! isUnsigned "Return true if the receiver is an unsigned type. Note: Only useful for integer types." ^self isSigned not! ! !ExternalType methodsFor: 'testing' stamp: 'ar 12/2/1999 14:16'! isVoid "Return true if the receiver describes a plain 'void' type" ^self isAtomic and:[self atomicType = 0]! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 15:19'! compiledSpec: aWordArray compiledSpec _ aWordArray.! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 20:34'! embeddedSpecWithSize: typeSize "Return a compiled spec for embedding in a new compiled spec." | spec header | spec _ self compiledSpec copy. header _ spec at: 1. header _ (header bitAnd: FFIStructSizeMask bitInvert32) bitOr: typeSize. spec at: 1 put: header. (self isStructureType and:[self isPointerType not]) ifTrue:[spec _ spec copyWith: self class structureSpec]. ^spec! ! !ExternalType methodsFor: 'private' stamp: 'ar 1/27/2000 00:22'! externalTypeName ^'ExternalType ', (AtomicTypeNames at: self atomicType), ' asPointerType'! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 14:11'! headerWord "Return the compiled header word" ^compiledSpec at: 1! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 20:30'! newReferentClass: aClass "The class I'm referencing has changed. Update my spec." referentClass _ aClass. self isPointerType ifTrue:[^self]. "for pointers only the referentClass changed" referentClass == nil ifTrue:[ "my class has been removed - make me 'struct { void }'" compiledSpec _ WordArray with: (FFIFlagStructure). ] ifFalse:[ "my class has been changed - update my compiledSpec" compiledSpec _ referentClass compiledSpec. ].! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 18:13'! readFieldAt: byteOffset "Return a string defining the accessor to an entity of the receiver type starting at the given byte offset. Private. Used for field definition only." self isPointerType ifTrue:[ referentClass == nil ifTrue:[ ^String streamContents:[:s| s nextPutAll:'^ExternalData fromHandle: (handle pointerAt: '; print: byteOffset; nextPutAll:') type: ExternalType '; nextPutAll: (AtomicTypeNames at: self atomicType); nextPutAll: ' asPointerType']]. ^String streamContents:[:s| s nextPutAll:'^'; print: referentClass; nextPutAll:' fromHandle: (handle pointerAt: '; print: byteOffset; nextPutAll:')']]. (self isAtomic) ifFalse:["structure type" ^String streamContents:[:s| s nextPutAll:'^'; print: referentClass; nextPutAll:' fromHandle: (handle structAt: '; print: byteOffset; nextPutAll:' length: '; print: self byteSize; nextPutAll:')']]. "Atomic non-pointer types" ^String streamContents:[:s| s nextPutAll:'^handle '; nextPutAll: (AtomicSelectors at: self atomicType); space; print: byteOffset].! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 16:41'! setReferencedType: aType referencedType _ aType! ! !ExternalType methodsFor: 'private' stamp: 'ar 12/2/1999 14:53'! writeFieldAt: byteOffset with: valueName "Return a string defining the accessor to an entity of the receiver type starting at the given byte offset. Private. Used for field definition only." self isPointerType ifTrue:[ ^String streamContents:[:s| s nextPutAll:'handle pointerAt: '; print: byteOffset; nextPutAll:' put: '; nextPutAll: valueName; nextPutAll:' getHandle.']]. self isAtomic ifFalse:[ ^String streamContents:[:s| s nextPutAll:'handle structAt: '; print: byteOffset; nextPutAll:' put: '; nextPutAll: valueName; nextPutAll:' getHandle'; nextPutAll:' length: '; print: self byteSize; nextPutAll:'.']]. ^String streamContents:[:s| s nextPutAll:'handle '; nextPutAll: (AtomicSelectors at: self atomicType); space; print: byteOffset; nextPutAll:' put: '; nextPutAll: valueName].! ! !ExternalType methodsFor: 'printing' stamp: 'ar 12/2/1999 17:02'! printOn: aStream referentClass == nil ifTrue:[aStream nextPutAll: (AtomicTypeNames at: self atomicType)] ifFalse:[aStream nextPutAll: referentClass name]. self isPointerType ifTrue:[aStream nextPut: $*].! ! !ExternalType methodsFor: 'converting' stamp: 'ar 12/2/1999 16:41'! asNonPointerType "convert the receiver into a non pointer type" self isPointerType ifTrue:[^referencedType] ifFalse:[^self]! ! !ExternalType methodsFor: 'converting' stamp: 'ar 12/2/1999 16:40'! asPointerType "convert the receiver into a pointer type" self isPointerType ifTrue:[^self] ifFalse:[^referencedType]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalType class instanceVariableNames: ''! !ExternalType class methodsFor: 'class initialization' stamp: 'ar 12/2/1999 16:15'! initialize "ExternalType initialize" self initializeFFIConstants. self initializeDefaultTypes.! ! !ExternalType class methodsFor: 'class initialization' stamp: 'ar 1/26/2000 14:57'! initializeAtomicTypes "ExternalType initialize" | atomicType byteSize type typeName | #( "name atomic id byte size" ('void' 0 0) ('bool' 1 1) ('byte' 2 1) ('sbyte' 3 1) ('ushort' 4 2) ('short' 5 2) ('ulong' 6 4) ('long' 7 4) ('ulonglong' 8 8) ('longlong' 9 8) ('char' 10 1) ('schar' 11 1) ('float' 12 4) ('double' 13 8) ) do:[:spec| typeName _ spec first. atomicType _ spec second. byteSize _ spec third. spec _ WordArray with: ((byteSize bitOr: FFIFlagAtomic) bitOr: (atomicType bitShift: FFIAtomicTypeShift)). type _ (AtomicTypes at: typeName). type compiledSpec: spec. spec _ WordArray with: ((self pointerSpec bitOr: FFIFlagAtomic) bitOr: (atomicType bitShift: FFIAtomicTypeShift)). type asPointerType compiledSpec: spec. ].! ! !ExternalType class methodsFor: 'class initialization' stamp: 'ar 12/2/1999 17:01'! initializeDefaultTypes "ExternalType initialize" | type pointerType | AtomicTypes = nil ifTrue:[ "Create new atomic types and setup the dictionaries" AtomicTypes _ Dictionary new. StructTypes _ WeakValueDictionary new. AtomicTypeNames valuesDo:[:k| type _ self basicNew. pointerType _ self basicNew. AtomicTypes at: k put: type. type setReferencedType: pointerType. pointerType setReferencedType: type. ]. ]. self initializeAtomicTypes. self initializeStructureTypes. "AtomicTypes _ nil"! ! !ExternalType class methodsFor: 'class initialization' stamp: 'ar 12/2/1999 20:25'! initializeFFIConstants "ExternalType initialize" | dict | AtomicTypeNames _ IdentityDictionary new. AtomicSelectors _ IdentityDictionary new. dict _ Smalltalk at: #FFIConstants ifAbsentPut:[Dictionary new]. #( "type void" (FFITypeVoid 0 'void' voidAt:) "type bool" (FFITypeBool 1 'bool' booleanAt:) "basic integer types. note: (integerType anyMask: 1) = integerType isSigned" (FFITypeUnsignedByte 2 'byte' unsignedByteAt:) (FFITypeSignedByte 3 'sbyte' signedByteAt:) (FFITypeUnsignedShort 4 'ushort' unsignedShortAt:) (FFITypeSignedShort 5 'short' signedShortAt:) (FFITypeUnsignedInt 6 'ulong' unsignedLongAt:) (FFITypeSignedInt 7 'long' signedLongAt:) "64bit types" (FFITypeUnsignedLongLong 8 'ulonglong' unsignedLongLongAt:) (FFITypeSignedLongLong 9 'longlong' signedLongLongAt:) "special integer types" (FFITypeUnsignedChar 10 'char' unsignedCharAt:) (FFITypeSignedChar 11 'schar' signedCharAt:) "float types" (FFITypeSingleFloat 12 'float' floatAt:) (FFITypeDoubleFloat 13 'double' doubleAt:) "type flags" (FFIFlagAtomic 16r40000) "type is atomic" (FFIFlagPointer 16r20000) "type is pointer to base type" (FFIFlagStructure 16r10000) "baseType is structure of 64k length" (FFIStructSizeMask 16rFFFF) "mask for max size of structure" (FFIAtomicTypeMask 16r0F000000) "mask for atomic type spec" (FFIAtomicTypeShift 24) "shift for atomic type" ) do:[:spec| dict declare: spec first from: Undeclared. dict at: spec first put: spec second. spec size > 2 ifTrue:[ AtomicTypeNames at: spec second put: spec third. AtomicSelectors at: spec second put: spec fourth]. ].! ! !ExternalType class methodsFor: 'class initialization' stamp: 'ar 12/2/1999 20:34'! initializeStructureTypes "ExternalType initialize" | referentClass pointerType | self cleanupUnusedTypes. StructTypes keysAndValuesDo:[:referentName :type| referentClass _ (Smalltalk at: referentName ifAbsent:[nil]). (referentClass isBehavior and:[ referentClass includesBehavior: ExternalStructure]) ifFalse:[referentClass _ nil]. type compiledSpec: (WordArray with: self structureSpec). type newReferentClass: referentClass. pointerType _ type asPointerType. pointerType compiledSpec: (WordArray with: (self pointerSpec bitOr: self structureSpec)). pointerType newReferentClass: referentClass. ].! ! !ExternalType class methodsFor: 'class initialization' stamp: 'sma 6/18/2000 11:26'! obsolete Smalltalk removeKey: #FFIConstants ifAbsent: []. super obsolete! ! !ExternalType class methodsFor: 'instance creation' stamp: 'ar 1/26/2000 14:58'! new "Use either the type constants or #externalType for creating external types" ^self shouldNotImplement! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:56'! bool ^AtomicTypes at: 'bool'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/20/1999 17:29'! byte "byte defaults to unsigned byte" ^self unsignedByte! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/20/1999 17:29'! char "char defaults to unsigned char" ^self unsignedChar! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:56'! double ^AtomicTypes at: 'double'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! float ^AtomicTypes at: 'float'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/22/1999 13:10'! long ^self signedLong! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/28/1999 23:43'! sbyte ^self signedByte! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/28/1999 23:43'! schar ^self signedChar! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/20/1999 17:26'! short ^self signedShort! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! signedByte ^AtomicTypes at: 'sbyte'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! signedChar ^AtomicTypes at: 'schar'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! signedLong ^AtomicTypes at: 'long'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! signedLongLong ^AtomicTypes at: 'longlong'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! signedShort ^AtomicTypes at: 'short'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! string ^(AtomicTypes at: 'char') asPointerType! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/28/1999 23:44'! ulong ^self unsignedLong! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:57'! unsignedByte ^AtomicTypes at: 'byte'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:58'! unsignedChar ^AtomicTypes at: 'char'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:58'! unsignedLong ^AtomicTypes at: 'ulong'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:58'! unsignedLongLong ^AtomicTypes at: 'ulonglong'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:58'! unsignedShort ^AtomicTypes at: 'ushort'! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 11/28/1999 23:44'! ushort ^self unsignedShort! ! !ExternalType class methodsFor: 'type constants' stamp: 'ar 12/2/1999 16:58'! void ^AtomicTypes at: 'void'! ! !ExternalType class methodsFor: 'housekeeping' stamp: 'ar 12/2/1999 18:00'! cleanupUnusedTypes "ExternalType cleanupUnusedTypes" | value | StructTypes keys do:[:key| value _ StructTypes at: key. value == nil ifTrue:[StructTypes removeKey: key ifAbsent:[]]].! ! !ExternalType class methodsFor: 'housekeeping' stamp: 'ar 12/2/1999 17:58'! noticeModificationOf: aClass "A subclass of ExternalStructure has been redefined. Clean out any obsolete references to its type." | type | aClass isBehavior ifFalse:[^nil]. "how could this happen?" aClass withAllSubclassesDo:[:cls| type _ StructTypes at: cls name ifAbsent:[nil]. type == nil ifFalse:[ type newReferentClass: cls. type asPointerType newReferentClass: cls]. ].! ! !ExternalType class methodsFor: 'housekeeping' stamp: 'ar 12/2/1999 17:59'! noticeRemovalOf: aClass "A subclass of ExternalStructure is being removed. Clean out any obsolete references to its type." | type | type _ StructTypes at: aClass name ifAbsent:[nil]. type == nil ifFalse:[ type newReferentClass: nil. type asPointerType newReferentClass: nil]. ! ! !ExternalType class methodsFor: 'housekeeping' stamp: 'ar 12/2/1999 16:14'! noticeRenamingOf: aClass from: oldName to: newName "An ExternalStructure has been renamed from oldName to newName. Keep our type names in sync." | type | type _ StructTypes at: oldName ifAbsent:[nil]. type == nil ifFalse:[StructTypes at: newName put: type]. StructTypes removeKey: oldName ifAbsent:[].! ! !ExternalType class methodsFor: 'private' stamp: 'ar 12/2/1999 16:59'! atomicTypeNamed: aString ^AtomicTypes at: aString ifAbsent:[nil]! ! !ExternalType class methodsFor: 'private' stamp: 'ar 12/2/1999 16:50'! forceTypeNamed: aString ^self newTypeNamed: aString force: true! ! !ExternalType class methodsFor: 'private' stamp: 'ar 1/26/2000 21:41'! newTypeNamed: aString force: aBool | sym type referentClass pointerType | sym _ aString asSymbol. type _ StructTypes at: aString ifAbsent:[nil]. type == nil ifFalse:[^type]. referentClass _ Smalltalk at: sym ifAbsent:[nil]. (referentClass isBehavior and:[referentClass includesBehavior: ExternalStructure]) ifFalse:[referentClass _ nil]. "If we don't have a referent class and are not forced to create a type get out" (referentClass == nil and:[aBool not]) ifTrue:[^nil]. type _ self basicNew compiledSpec: (WordArray with: self structureSpec). pointerType _ self basicNew compiledSpec: (WordArray with: self pointerSpec). type setReferencedType: pointerType. pointerType setReferencedType: type. type newReferentClass: referentClass. pointerType newReferentClass: referentClass. StructTypes at: sym put: type. ^type! ! !ExternalType class methodsFor: 'private' stamp: 'ar 12/2/1999 20:33'! pointerSpec ^(4 bitOr: FFIFlagPointer)! ! !ExternalType class methodsFor: 'private' stamp: 'ar 12/2/1999 16:48'! structTypeNamed: aSymbol aSymbol == nil ifTrue:[^nil]. ^self newTypeNamed: aSymbol force: false! ! !ExternalType class methodsFor: 'private' stamp: 'ar 12/2/1999 20:34'! structureSpec ^FFIFlagStructure! ! EllipseMorph subclass: #EyeMorph instanceVariableNames: 'iris ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Gestures'! !EyeMorph methodsFor: 'initialization' stamp: 'len 9/6/1999 23:36'! initialize super initialize. self color: (Color r: 1.0 g: 0.968 b: 0.935). self borderColor: Color black; borderWidth: 1. self extent: 30 @ 37. self addMorphFront: (iris _ EllipseMorph new extent: 6 @ 6; borderWidth: 0; color: Color black). self lookAtFront! ! !EyeMorph methodsFor: 'accessing' stamp: 'len 8/22/1999 15:40'! iris ^ iris! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/22/1999 19:25'! closeEyelid self iris delete. self position: self position + (0 @ (self extent y // 2)). self extent: self extent x @ 2! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/22/1999 15:57'! dilate: amount | irisCenter | irisCenter _ self iris center. self iris extent: self iris extent * amount. self iris position: irisCenter - self iris center + self iris position! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/24/1999 00:48'! lookAt: aPoint | theta scale | (self containsPoint: aPoint) ifTrue: [self iris align: iris center with: aPoint. ^ self]. theta _ (aPoint - self center) theta. scale _ (aPoint - self center) r / 100.0 min: 1.0. self iris align: self iris center with: self center + (theta cos @ theta sin * self extent / 3.0 * scale) rounded! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/22/1999 22:03'! lookAtFront self iris position: self center - self iris center + self iris position! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/22/1999 22:23'! lookAtMorph: aMorph self lookAt: aMorph center! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/23/1999 22:49'! openEyelid self extent: self extent x @ (self extent x * 37.0 / 30.0) rounded. self position: self position - (0 @ (self extent y // 2)). self addMorphFront: self iris! ! !EyeMorph methodsFor: 'actions' stamp: 'len 8/24/1999 01:18'! openness: aNumber | previousCenter | previousCenter _ self center. self extent: self extent x @ (self extent x * 37.0 / 30.0 * aNumber) rounded. self align: self center with: previousCenter. (self containsPoint: self iris center) ifFalse: [self lookAtFront]! ! UtteranceVisitor subclass: #F0RenderingVisitor instanceVariableNames: 'pitch range contour ' classVariableNames: '' poolDictionaries: '' category: 'Speech-TTS'! !F0RenderingVisitor methodsFor: 'accessing' stamp: 'len 12/13/1999 03:47'! highPitch ^ pitch + (pitch * range)! ! !F0RenderingVisitor methodsFor: 'accessing' stamp: 'len 12/13/1999 03:47'! lowPitch ^ pitch - (pitch * range)! ! !F0RenderingVisitor methodsFor: 'accessing' stamp: 'len 12/13/1999 01:21'! timeForEvent: aVoiceEvent | time | time _ 0. clause eventsDo: [ :each | aVoiceEvent == each ifTrue: [^ time] ifFalse: [time _ time + each duration]]! ! !F0RenderingVisitor methodsFor: 'visiting' stamp: 'len 12/24/1999 03:20'! clause: aClause contour _ CosineInterpolator new at: 0 put: pitch; yourself. super clause: aClause. self renderPhraseAccentOrBoundaryTone: clause accent. self assignF0ToEvents! ! !F0RenderingVisitor methodsFor: 'visiting' stamp: 'len 12/14/1999 02:31'! phrase: aPhrase super phrase: aPhrase. self renderPhraseAccentOrBoundaryTone: phrase accent! ! !F0RenderingVisitor methodsFor: 'visiting' stamp: 'len 12/14/1999 02:41'! renderPhraseAccentOrBoundaryTone: aStringOrNil aStringOrNil isNil ifTrue: [^ self]. (aStringOrNil findTokens: ' ') do: [ :each | each = 'H-' ifTrue: [self renderHighPhraseAccent]. each = 'L-' ifTrue: [self renderLowPhraseAccent]. each = 'H%' ifTrue: [self renderHighBoundary]. each = 'L%' ifTrue: [self renderLowBoundary]. each = '%H' ifTrue: [self renderHighInitial]. each = '%r' ifTrue: [self notYetImplemented]]! ! !F0RenderingVisitor methodsFor: 'visiting' stamp: 'len 12/13/1999 02:42'! speaker: aSpeaker pitch _ aSpeaker pitch. range _ aSpeaker range! ! !F0RenderingVisitor methodsFor: 'visiting' stamp: 'len 12/14/1999 02:41'! syllable: aSyllable super syllable: aSyllable. aSyllable isAccented ifFalse: [^ self]. aSyllable accent = 'H*' ifTrue: [^ self renderPeakAccent]. aSyllable accent = 'L*' ifTrue: [^ self renderLowAccent]. aSyllable accent = 'L*+H' ifTrue: [^ self renderScoopedAccent]. aSyllable accent = 'L+H*' ifTrue: [^ self renderRisingPeakAccent]! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 05:47'! renderLowAccent "Render a L* accent." | start stop peakPosition | start _ self syllableStartTime. stop _ self syllableStopTime. peakPosition _ (syllable events detect: [ :one | one phoneme isSyllabic] ifNone: [syllable events first]) duration / 2.0. self time: start startingF0: (contour at: start) amplitude: (contour at: start) - self lowPitch duration: stop - start peakPosition: peakPosition tilt: 0.0! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 05:47'! renderPeakAccent "Render a H* accent." | start stop peakPosition | start _ self syllableStartTime. stop _ self syllableStopTime. peakPosition _ (syllable events detect: [ :one | one phoneme isSyllabic] ifNone: [syllable events first]) duration / 2.0. self time: start startingF0: (contour at: start) amplitude: self highPitch - (contour at: start) duration: stop - start peakPosition: peakPosition tilt: 0.0! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 01:44'! renderRisingPeakAccent "Render a L+H* accent." self notYetImplemented! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 01:45'! renderScoopedAccent "Render a L*+H accent." self notYetImplemented! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 01:43'! syllableStartTime ^ self timeForEvent: syllable events first! ! !F0RenderingVisitor methodsFor: 'rendering-pitch accents' stamp: 'len 12/14/1999 01:43'! syllableStopTime ^ self syllableStartTime + syllable events duration! ! !F0RenderingVisitor methodsFor: 'rendering-phrase accents' stamp: 'len 12/14/1999 03:45'! phraseAccentStartTime | syl | syl _ nil. (phrase ifNil: [clause phrases last]) syllablesDo: [ :each | (syl isNil or: [syl isAccented]) ifTrue: [syl _ each]]. ^ self timeForEvent: syl events last! ! !F0RenderingVisitor methodsFor: 'rendering-phrase accents' stamp: 'len 12/14/1999 01:17'! phraseAccentStopTime | lastEvent | lastEvent _ (phrase ifNil: [clause phrases last]) lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-phrase accents' stamp: 'len 12/14/1999 01:23'! renderHighPhraseAccent "Render a H- accent." | start stop | start _ self phraseAccentStartTime. stop _ self phraseAccentStopTime. self time: start startingF0: (contour at: start) amplitude: self highPitch - (contour at: start) duration: stop - start peakPosition: stop - start tilt: 1.0! ! !F0RenderingVisitor methodsFor: 'rendering-phrase accents' stamp: 'len 12/14/1999 03:48'! renderLowPhraseAccent "Render a L- accent." | start stop | start _ self phraseAccentStartTime. stop _ self phraseAccentStopTime. self time: start startingF0: (contour at: start) amplitude: (contour at: start) - self lowPitch duration: stop - start peakPosition: stop - start tilt: -0.5! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 02:27'! boundaryStartTime ^ self timeForEvent: (phrase ifNil: [clause phrases last]) words last events first! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 02:27'! boundaryStopTime | lastEvent | lastEvent _ (phrase ifNil: [clause phrases last]) lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 01:38'! initialStopTime | lastEvent | lastEvent _ clause phrases first words first lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 01:34'! renderHighBoundary "Render a H% boundary tone." | start stop | start _ self boundaryStartTime. stop _ self boundaryStopTime. self time: start startingF0: (contour at: start) amplitude: self highPitch - (contour at: start) duration: stop - start peakPosition: stop - start tilt: 1.0! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 03:07'! renderHighInitial "Render a %H tone." | start stop | start _ 0. stop _ self initialStopTime. self time: start startingF0: (contour at: start) amplitude: self highPitch - (contour at: start) * 2 duration: stop - start peakPosition: start tilt: 0.0! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'len 12/14/1999 03:44'! renderLowBoundary "Render a L% boundary tone." | start stop | start _ self boundaryStartTime. stop _ self boundaryStopTime. self time: start startingF0: (contour at: start) amplitude: (contour at: start) - self lowPitch duration: stop - start peakPosition: stop - start tilt: -1.0! ! !F0RenderingVisitor methodsFor: 'private' stamp: 'len 12/14/1999 00:48'! assignF0ToEvents | time | time _ 0. clause events do: [ :each | each pitchPoints: (self pitchesBetween: time and: time + each duration). time _ time + each duration]! ! !F0RenderingVisitor methodsFor: 'private' stamp: 'len 12/13/1999 02:55'! pitchesBetween: t1 and: t2 | step | step _ (t2 - t1 / 0.035) asInteger + 1. "step small enough" ^ (t1 to: t2 by: t2 - t1 / step) collect: [ :each | each - t1 @ (contour at: each)]! ! !F0RenderingVisitor methodsFor: 'private' stamp: 'len 12/14/1999 03:47'! time: time startingF0: startingF0 amplitude: amplitude duration: duration peakPosition: peakPosition tilt: tilt | vowelStart riseAmplitude fallAmplitude | vowelStart _ self timeOfFirstVowelAfter: time. riseAmplitude _ tilt + 1.0 * amplitude / 2.0. fallAmplitude _ amplitude - riseAmplitude. contour x: time y: startingF0; x: vowelStart + peakPosition y: ((startingF0 + riseAmplitude max: self lowPitch) min: self highPitch); x: time + duration y: ((startingF0 + riseAmplitude - fallAmplitude max: self lowPitch) min: self highPitch); commit! ! !F0RenderingVisitor methodsFor: 'private' stamp: 'len 12/14/1999 01:47'! timeOfFirstVowelAfter: time | currentTime | currentTime _ 0. clause events do: [ :each | (currentTime >= time and: [each phoneme isSyllabic]) ifTrue: [^ currentTime]. currentTime _ currentTime + each duration]. ^ time "if not found, answer the time itself"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! F0RenderingVisitor class instanceVariableNames: ''! !F0RenderingVisitor class methodsFor: 'examples' stamp: 'len 12/13/1999 02:25'! default ^ self new! ! InterpreterPlugin subclass: #FFIPlugin instanceVariableNames: 'ffiLastError ffiArgClass ffiArgSpec ffiArgSpecSize ffiArgHeader ffiRetOop ffiRetClass ffiRetSpec ffiRetSpecSize ffiRetHeader ' classVariableNames: '' poolDictionaries: 'FFIConstants ' category: 'VMConstruction-Plugins'! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:25'! primitiveCallout "IMPORTANT: IF YOU CHANGE THE NAME OF THIS METHOD YOU MUST CHANGE Interpreter>>primitiveCalloutToFFI TO REFLECT THE CHANGE." "Perform a function call to a foreign function. Only invoked from method containing explicit external call spec." | lit address flags argTypes litClass nArgs meth | self export: true. self inline: false. self ffiSetLastError: FFIErrorGenericError. "educated guess if we fail silently" lit _ nil. "Look if the method is itself a callout function" meth _ interpreterProxy primitiveMethod. (interpreterProxy literalCountOf: meth) > 0 ifFalse:[^interpreterProxy primitiveFail]. lit _ interpreterProxy literal: 0 ofMethod: meth. litClass _ interpreterProxy fetchClassOf: lit. (interpreterProxy includesBehavior: litClass ThatOf: interpreterProxy classExternalFunction) ifFalse:[^self ffiFail: FFIErrorNotFunction]. address _ self ffiLoadCalloutAddress: lit. interpreterProxy failed ifTrue:[^0]. "Load and check the other values before we call out" flags _ interpreterProxy fetchInteger: 1 ofObject: lit. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorBadArgs]. argTypes _ interpreterProxy fetchPointer: 2 ofObject: lit. "must be array of arg types" (interpreterProxy fetchClassOf: argTypes) == interpreterProxy classArray ifFalse:[^self ffiFail: FFIErrorBadArgs]. nArgs _ interpreterProxy slotSizeOf: argTypes. "must be argumentCount+1 arg types" nArgs = (interpreterProxy methodArgumentCount+1) ifFalse:[^self ffiFail: FFIErrorBadArgs]. self ffiInitialize. "announce the execution of an external call" self ffiCall: address WithFlags: flags AndTypes: argTypes. self ffiCleanup. "cleanup temp allocations" ^0! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:26'! primitiveCalloutWithArgs "Perform a function call to a foreign function. Only invoked from ExternalFunction>>invokeWithArguments:" | lit address flags argTypes litClass nArgs argArray | self export: true. self inline: false. self ffiSetLastError: FFIErrorGenericError. "educated guess if we fail silently" lit _ nil. "Look if the method is itself a callout function" lit _ interpreterProxy stackValue: interpreterProxy methodArgumentCount. litClass _ interpreterProxy fetchClassOf: lit. (interpreterProxy includesBehavior: litClass ThatOf: interpreterProxy classExternalFunction) ifFalse:[^self ffiFail: FFIErrorNotFunction]. address _ self ffiLoadCalloutAddress: lit. interpreterProxy failed ifTrue:[^nil]. "Load and check the other values before we call out" flags _ interpreterProxy fetchInteger: 1 ofObject: lit. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorBadArgs]. argTypes _ interpreterProxy fetchPointer: 2 ofObject: lit. "must be array of arg types" (interpreterProxy fetchClassOf: argTypes) == interpreterProxy classArray ifFalse:[^self ffiFail: FFIErrorBadArgs]. nArgs _ interpreterProxy slotSizeOf: argTypes. (interpreterProxy methodArgumentCount = 1) ifFalse:[^self ffiFail: FFIErrorBadArgs]. argArray _ interpreterProxy stackValue: 0. (interpreterProxy fetchClassOf: argArray) = (interpreterProxy classArray) ifFalse:[^self ffiFail: FFIErrorBadArgs]. nArgs = ((interpreterProxy slotSizeOf: argArray) + 1) ifFalse:[^self ffiFail: FFIErrorBadArgs]. self ffiInitialize. "announce the execution of an external call" self ffiCall: address WithFlags: flags Args: argArray AndTypes: argTypes OfSize: nArgs-1. self ffiCleanup. "cleanup temp allocations" ^0! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 12/1/1999 16:08'! primitiveFFIAllocate "Primitive. Allocate an object on the external heap." | byteSize addr oop ptr | self export: true. self inline: false. self var: #ptr declareC:'int *ptr'. byteSize _ interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. addr _ self ffiAlloc: byteSize. addr = 0 ifTrue:[^interpreterProxy primitiveFail]. oop _ interpreterProxy instantiateClass: interpreterProxy classExternalAddress indexableSize: 4. ptr _ interpreterProxy firstIndexableField: oop. ptr at: 0 put: addr. interpreterProxy pop: 2. ^interpreterProxy push: oop. ! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/29/1999 00:07'! primitiveFFIDoubleAt "Return a (signed or unsigned) n byte integer from the given byte offset." | byteOffset rcvr addr floatValue | self export: true. self inline: false. self var: #floatValue declareC:'double floatValue'. byteOffset _ interpreterProxy stackIntegerValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^0]. addr _ self addressOf: rcvr startingAt: byteOffset size: 8. interpreterProxy failed ifTrue:[^0]. self cCode:'((int*)(&floatValue))[0] = ((int*)addr)[0]'. self cCode:'((int*)(&floatValue))[1] = ((int*)addr)[1]'. interpreterProxy pop: 2. ^interpreterProxy pushFloat: floatValue ! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/29/1999 10:10'! primitiveFFIDoubleAtPut "Return a (signed or unsigned) n byte integer from the given byte offset." | byteOffset rcvr addr floatValue floatOop | self export: true. self inline: false. self var: #floatValue declareC:'double floatValue'. floatOop _ interpreterProxy stackValue: 0. (interpreterProxy isIntegerObject: floatOop) ifTrue:[floatValue _ self cCoerce: (interpreterProxy integerValueOf: floatOop) to:'double'] ifFalse:[floatValue _ self cCoerce: (interpreterProxy floatValueOf: floatOop) to:'double']. byteOffset _ interpreterProxy stackIntegerValue: 1. rcvr _ interpreterProxy stackObjectValue: 2. interpreterProxy failed ifTrue:[^0]. addr _ self addressOf: rcvr startingAt: byteOffset size: 8. interpreterProxy failed ifTrue:[^0]. self cCode:'((int*)addr)[0] = ((int*)(&floatValue))[0]'. self cCode:'((int*)addr)[1] = ((int*)(&floatValue))[1]'. interpreterProxy pop: 3. ^interpreterProxy push: floatOop! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/29/1999 00:07'! primitiveFFIFloatAt "Return a (signed or unsigned) n byte integer from the given byte offset." | byteOffset rcvr addr floatValue | self export: true. self inline: false. self var: #floatValue declareC:'float floatValue'. byteOffset _ interpreterProxy stackIntegerValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^0]. addr _ self addressOf: rcvr startingAt: byteOffset size: 4. interpreterProxy failed ifTrue:[^0]. self cCode:'((int*)(&floatValue))[0] = ((int*)addr)[0]'. interpreterProxy pop: 2. ^interpreterProxy pushFloat: floatValue! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/29/1999 10:10'! primitiveFFIFloatAtPut "Return a (signed or unsigned) n byte integer from the given byte offset." | byteOffset rcvr addr floatValue floatOop | self export: true. self inline: false. self var: #floatValue declareC:'float floatValue'. floatOop _ interpreterProxy stackValue: 0. (interpreterProxy isIntegerObject: floatOop) ifTrue:[floatValue _ self cCoerce: (interpreterProxy integerValueOf: floatOop) to:'float'] ifFalse:[floatValue _ self cCoerce: (interpreterProxy floatValueOf: floatOop) to:'float']. byteOffset _ interpreterProxy stackIntegerValue: 1. rcvr _ interpreterProxy stackObjectValue: 2. interpreterProxy failed ifTrue:[^0]. addr _ self addressOf: rcvr startingAt: byteOffset size: 4. interpreterProxy failed ifTrue:[^0]. self cCode:'((int*)addr)[0] = ((int*)(&floatValue))[0]'. interpreterProxy pop: 3. ^interpreterProxy push: floatOop! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 12/1/1999 16:08'! primitiveFFIFree "Primitive. Free the object pointed to on the external heap." | addr oop ptr | self export: true. self inline: false. self var: #ptr declareC:'int *ptr'. oop _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. (interpreterProxy fetchClassOf: oop) = (interpreterProxy classExternalAddress) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy byteSizeOf: oop) = 4 ifFalse:[^interpreterProxy primitiveFail]. ptr _ interpreterProxy firstIndexableField: oop. addr _ ptr at: 0. "Don't you dare to free Squeak's memory!!" (addr = 0 or:[interpreterProxy isInMemory: addr]) ifTrue:[^interpreterProxy primitiveFail]. self ffiFree: addr. ^ptr at: 0 put: 0. "cleanup" ! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:26'! primitiveFFIGetLastError "Primitive. Return the error code from a failed call to the foreign function interface." self export: true. self inline: false. interpreterProxy pop: 1. ^interpreterProxy pushInteger: self ffiGetLastError.! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 12/1/1999 18:55'! primitiveFFIIntegerAt "Return a (signed or unsigned) n byte integer from the given byte offset." | isSigned byteSize byteOffset rcvr addr value mask | self export: true. self inline: false. isSigned _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). byteSize _ interpreterProxy stackIntegerValue: 1. byteOffset _ interpreterProxy stackIntegerValue: 2. rcvr _ interpreterProxy stackObjectValue: 3. interpreterProxy failed ifTrue:[^0]. (byteOffset > 0 and:[byteSize = 1 or:[byteSize = 2 or:[byteSize = 4]]]) ifFalse:[^interpreterProxy primitiveFail]. addr _ self addressOf: rcvr startingAt: byteOffset size: byteSize. interpreterProxy failed ifTrue:[^0]. byteSize < 4 ifTrue:[ "short/byte" byteSize = 1 ifTrue:[value _ interpreterProxy byteAt: addr] ifFalse:[ value _ self cCode: '*((short int *) addr)' inSmalltalk: [interpreterProxy halfWordAt: addr]]. isSigned ifTrue:["sign extend value" mask _ 1 << (byteSize * 8 - 1). value _ (value bitAnd: mask-1) - (value bitAnd: mask)]. "note: byte/short never exceed SmallInteger range" value _ interpreterProxy integerObjectOf: value. ] ifFalse:[ "general 32 bit integer" value _ interpreterProxy longAt: addr. isSigned ifTrue:[value _ interpreterProxy signed32BitIntegerFor: value] ifFalse:[value _ interpreterProxy positive32BitIntegerFor: value]. ]. interpreterProxy pop: 4. ^interpreterProxy push: value ! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 12/1/1999 18:55'! primitiveFFIIntegerAtPut "Store a (signed or unsigned) n byte integer at the given byte offset." | isSigned byteSize byteOffset rcvr addr value max valueOop | self export: true. self inline: false. isSigned _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). byteSize _ interpreterProxy stackIntegerValue: 1. valueOop _ interpreterProxy stackValue: 2. byteOffset _ interpreterProxy stackIntegerValue: 3. rcvr _ interpreterProxy stackObjectValue: 4. interpreterProxy failed ifTrue:[^0]. (byteOffset > 0 and:[byteSize = 1 or:[byteSize = 2 or:[byteSize = 4]]]) ifFalse:[^interpreterProxy primitiveFail]. addr _ self addressOf: rcvr startingAt: byteOffset size: byteSize. interpreterProxy failed ifTrue:[^0]. isSigned ifTrue:[value _ interpreterProxy signed32BitValueOf: valueOop] ifFalse:[value _ interpreterProxy positive32BitValueOf: valueOop]. interpreterProxy failed ifTrue:[^0]. byteSize < 4 ifTrue:[ isSigned ifTrue:[ max _ 1 << (8 * byteSize - 1). value >= max ifTrue:[^interpreterProxy primitiveFail]. value < (0 - max) ifTrue:[^interpreterProxy primitiveFail]. ] ifFalse:[ value >= (1 << (8*byteSize)) ifTrue:[^interpreterProxy primitiveFail]. ]. "short/byte" byteSize = 1 ifTrue:[interpreterProxy byteAt: addr put: value] ifFalse:[ self cCode: '*((short int *) addr) = value' inSmalltalk: [interpreterProxy halfWordAt: addr put: value]]. ] ifFalse:[interpreterProxy longAt: addr put: value]. interpreterProxy pop: 5. ^interpreterProxy push: valueOop.! ! !FFIPlugin methodsFor: 'primitives' stamp: 'ar 5/12/2000 17:21'! primitiveForceLoad "Primitive. Force loading the receiver (an instance of ExternalLibrary)." | rcvr theClass moduleHandlePtr moduleHandle ffiModuleName moduleLength ptr | self export: true. self inline: false. self var: #ptr declareC:'int *ptr'. self ffiSetLastError: FFIErrorGenericError. "educated guess if we fail silently" interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. rcvr _ interpreterProxy stackValue: 0. theClass _ interpreterProxy fetchClassOf: rcvr. (interpreterProxy includesBehavior: theClass ThatOf: interpreterProxy classExternalLibrary) ifFalse:[^self ffiFail: FFIErrorBadExternalLibrary]. moduleHandlePtr _ interpreterProxy fetchPointer: 0 ofObject: rcvr. moduleHandle _ self ffiContentsOfHandle: moduleHandlePtr errCode: FFIErrorBadExternalLibrary. interpreterProxy failed ifTrue:[^0]. ffiModuleName _ interpreterProxy fetchPointer: 1 ofObject: rcvr. (interpreterProxy isBytes: ffiModuleName) ifFalse:[^self ffiFail: FFIErrorBadExternalLibrary]. moduleLength _ interpreterProxy byteSizeOf: ffiModuleName. moduleHandle _ interpreterProxy ioLoadModule: (self cCoerce: (interpreterProxy firstIndexableField: ffiModuleName) to:'int') OfLength: moduleLength. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorModuleNotFound]. "failed" "and store back" ptr _ interpreterProxy firstIndexableField: moduleHandlePtr. ptr at: 0 put: moduleHandle. ^0 "done"! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 19:24'! ffiArgByValue: oop "Support for generic callout. Prepare an argument by value for a callout." | atomicType intValue floatValue | self inline: true. atomicType _ self atomicTypeOf: ffiArgHeader. "check if the range is valid" (atomicType < 0 or:[atomicType > FFITypeDoubleFloat]) ifTrue:[^self ffiFail: FFIErrorBadAtomicType]. atomicType < FFITypeSingleFloat ifTrue:["integer types" (atomicType >> 1) = (FFITypeSignedLongLong >> 1) ifTrue:[intValue _ oop] "ffi support code must coerce longlong" ifFalse:[intValue _ self ffiIntegerValueOf: oop]. "does all the coercions" interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. self dispatchOn: atomicType in: #( ffiPushVoid: ffiPushUnsignedInt: ffiPushUnsignedByte: ffiPushSignedByte: ffiPushUnsignedShort: ffiPushSignedShort: ffiPushUnsignedInt: ffiPushSignedInt: ffiPushUnsignedLongLongOop: ffiPushSignedLongLongOop: ffiPushUnsignedChar: ffiPushSignedChar:) with: intValue. ] ifFalse:[ "either float or double" floatValue _ self ffiFloatValueOf: oop. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. atomicType = FFITypeSingleFloat ifTrue:[self ffiPushSingleFloat: floatValue] ifFalse:[self ffiPushDoubleFloat: floatValue]. ]. ^0! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/27/2000 00:06'! ffiArgument: oop Spec: argSpec Class: argClass "Callout support. Prepare the given oop as argument. argSpec defines the compiled spec for the argument. argClass (if non-nil) defines the required (super)class for the argument." | valueOop oopClass isStruct nilOop | self inline: false. oopClass _ interpreterProxy fetchClassOf: oop. "Prefetch class (we'll need it)" nilOop _ interpreterProxy nilObject. "Do the necessary type checks" argClass == nilOop ifFalse:[ "Type check 1: Is the required class of the argument a subclass of ExternalStructure?" (interpreterProxy includesBehavior: argClass ThatOf: interpreterProxy classExternalStructure) ifFalse:[^self ffiFail: FFIErrorWrongType]. "Nope. Fail." "Type check 2: Is the class of the argument a subclass of required class?" ((nilOop == oop) or:[interpreterProxy includesBehavior: oopClass ThatOf: argClass]) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. "Nope. Fail." "Okay, we've passed the type check (so far)" ]. "Check if oopClass is a subclass of ExternalStructure. If this is the case we'll work on it's handle and not the actual oop." isStruct _ false. ((interpreterProxy isIntegerObject: oop) or:[oop == nilOop]) ifFalse:[ "#isPointers: will fail if oop is SmallInteger so don't even attempt to use it" (interpreterProxy isPointers: oop) ifTrue:[isStruct _ interpreterProxy includesBehavior: oopClass ThatOf: interpreterProxy classExternalStructure. (argClass == nilOop or:[isStruct]) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]]. "note: the test for #isPointers: above should speed up execution since no pointer type ST objects are allowed in external calls and thus if #isPointers: is true then the arg must be ExternalStructure to work. If it isn't then the code fails anyways so speed isn't an issue" ]. "Determine valueOop (e.g., the actual oop to pass as argument)" isStruct ifTrue:[valueOop _ interpreterProxy fetchPointer: 0 ofObject: oop] ifFalse:[valueOop _ oop]. ffiArgClass _ argClass. "Fetch and check the contents of the compiled spec" (interpreterProxy isIntegerObject: argSpec) ifTrue:[self ffiFail: FFIErrorWrongType. ^nil]. (interpreterProxy isWords: argSpec) ifFalse:[self ffiFail: FFIErrorWrongType. ^nil]. ffiArgSpecSize _ interpreterProxy slotSizeOf: argSpec. ffiArgSpecSize = 0 ifTrue:[self ffiFail: FFIErrorWrongType. ^nil]. ffiArgSpec _ self cCoerce: (interpreterProxy firstIndexableField: argSpec) to: 'int'. ffiArgHeader _ interpreterProxy longAt: ffiArgSpec. "Do the actual preparation of the argument" "Note: Order is important since FFIFlagStructure + FFIFlagPointer is used to represent 'typedef void* VoidPointer' and VoidPointer really is *struct* not pointer." (ffiArgHeader anyMask: FFIFlagStructure) ifTrue:[ "argument must be ExternalStructure" isStruct ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. (ffiArgHeader anyMask: FFIFlagAtomic) ifTrue:[^self ffiFail: FFIErrorWrongType]. "bad combination" ^self ffiPushStructureContentsOf: valueOop]. (ffiArgHeader anyMask: FFIFlagPointer) ifTrue:[ "no integers for pointers please" (interpreterProxy isIntegerObject: oop) ifTrue:[^self ffiFail: FFIErrorIntAsPointer]. "but allow passing nil pointer for any pointer type" oop == interpreterProxy nilObject ifTrue:[^self ffiPushPointer: nil]. "argument is reference to either atomic or structure type" (ffiArgHeader anyMask: FFIFlagAtomic) ifTrue:[ isStruct "e.g., ExternalData" ifTrue:[^self ffiAtomicStructByReference: oop Class: oopClass] ifFalse:[^self ffiAtomicArgByReference: oop Class: oopClass]. "********* NOTE: The above uses 'oop' not 'valueOop' (for ExternalData) ******" ]. "Needs to be external structure here" isStruct ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. ^self ffiPushPointerContentsOf: valueOop]. (ffiArgHeader anyMask: FFIFlagAtomic) ifTrue:[ "argument is atomic value" self ffiArgByValue: valueOop. ^0]. "None of the above - bad spec" ^self ffiFail: FFIErrorWrongType! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 22:38'! ffiAtomicArgByReference: oop Class: oopClass "Support for generic callout. Prepare a pointer reference to an atomic type for callout. Note: for type 'void*' we allow either one of ByteArray/String/Symbol or wordVariableSubclass." | atomicType isString | self inline: true. atomicType _ self atomicTypeOf: ffiArgHeader. (atomicType = FFITypeBool) "No bools on input" ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. ((atomicType >> 1) = (FFITypeSignedChar >> 1)) ifTrue:["string value (char*)" "note: the only types allowed for passing into char* types are ByteArray, String, Symbol and *no* other byte indexed objects (e.g., CompiledMethod, LargeInteger). We only check for strings here and fall through to the byte* check otherwise." isString _ interpreterProxy includesBehavior: oopClass ThatOf: interpreterProxy classString. isString ifTrue:["String/Symbol" "Strings must be allocated by the ffi support code" ^self ffiPushString: (self cCoerce: (interpreterProxy firstIndexableField: oop) to: 'int') OfLength: (interpreterProxy byteSizeOf: oop)]. "Fall through to byte* test" atomicType _ FFITypeUnsignedByte]. (atomicType = FFITypeVoid or:[(atomicType >> 1) = (FFITypeSignedByte >> 1)]) ifTrue:[ "byte* -- see comment on string above" oopClass = interpreterProxy classByteArray ifTrue:["ByteArray" ^self ffiPushPointer: (self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int')]. isString _ interpreterProxy includesBehavior: oopClass ThatOf: interpreterProxy classString. isString ifTrue:["String/Symbol" ^self ffiPushPointer: (self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int')]. atomicType = FFITypeVoid ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. "note: type void falls through" ]. (atomicType <= FFITypeSignedInt "void/short/int" or:[atomicType = FFITypeSingleFloat]) ifTrue:[ "require a word subclass to work" (interpreterProxy isWords: oop) ifTrue:[ ^self ffiPushPointer: (self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int')]]. ^self ffiFail: FFIErrorCoercionFailed.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 23:43'! ffiAtomicStructByReference: oop Class: oopClass "Support for generic callout. Prepare an external pointer reference to an atomic type for callout." | atomicType valueOop | self inline: true. "must be external data to pass pointers to atomic type" oopClass == interpreterProxy classExternalData ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. atomicType _ self atomicTypeOf: ffiArgHeader. "no type checks for void pointers" atomicType ~= FFITypeVoid ifTrue:[ self ffiValidateExternalData: oop AtomicType: atomicType. interpreterProxy failed ifTrue:[^nil]. ]. "and push pointer contents" valueOop _ interpreterProxy fetchPointer: 0 ofObject: oop. ^self ffiPushPointerContentsOf: valueOop! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 22:19'! ffiCall: address WithFlags: callType AndTypes: argTypeArray "Generic callout. Does the actual work." | stackIndex argType argTypes oop nArgs argClass argSpec | self inline: true. "check if the calling convention is supported" (self ffiSupportsCallingConvention: callType) ifFalse:[^self ffiFail: FFIErrorCallType]. argTypes _ argTypeArray. "Fetch return type and args" argType _ interpreterProxy fetchPointer: 0 ofObject: argTypes. argSpec _ interpreterProxy fetchPointer: 0 ofObject: argType. argClass _ interpreterProxy fetchPointer: 1 ofObject: argType. self ffiCheckReturn: argSpec With: argClass. interpreterProxy failed ifTrue:[^0]. "cannot return" ffiRetOop _ argType. nArgs _ interpreterProxy methodArgumentCount. stackIndex _ nArgs - 1. "stack index goes downwards" 1 to: nArgs do:[:i| argType _ interpreterProxy fetchPointer: i ofObject: argTypes. argSpec _ interpreterProxy fetchPointer: 0 ofObject: argType. argClass _ interpreterProxy fetchPointer: 1 ofObject: argType. oop _ interpreterProxy stackValue: stackIndex. self ffiArgument: oop Spec: argSpec Class: argClass. interpreterProxy failed ifTrue:[^0]. "coercion failed" stackIndex _ stackIndex - 1. ]. "Go out and call this guy" ^self ffiCalloutTo: address WithFlags: callType! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 22:19'! ffiCall: address WithFlags: callType Args: argArray AndTypes: argTypeArray OfSize: nArgs "Generic callout. Does the actual work." | argType argTypes oop argSpec argClass | self inline: true. "check if the calling convention is supported" (self ffiSupportsCallingConvention: callType) ifFalse:[^self ffiFail: FFIErrorCallType]. argTypes _ argTypeArray. "Fetch return type and args" argType _ interpreterProxy fetchPointer: 0 ofObject: argTypes. argSpec _ interpreterProxy fetchPointer: 0 ofObject: argType. argClass _ interpreterProxy fetchPointer: 1 ofObject: argType. self ffiCheckReturn: argSpec With: argClass. interpreterProxy failed ifTrue:[^0]. "cannot return" ffiRetOop _ argType. 1 to: nArgs do:[:i| argType _ interpreterProxy fetchPointer: i ofObject: argTypes. argSpec _ interpreterProxy fetchPointer: 0 ofObject: argType. argClass _ interpreterProxy fetchPointer: 1 ofObject: argType. oop _ interpreterProxy fetchPointer: i-1 ofObject: argArray. self ffiArgument: oop Spec: argSpec Class: argClass. interpreterProxy failed ifTrue:[^0]. "coercion failed" ]. "Go out and call this guy" ^self ffiCalloutTo: address WithFlags: callType! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 23:11'! ffiCalloutTo: address WithFlags: callType "Go out, call this guy and create the return value" | retVal | self inline: false. "Note: Order is important here since FFIFlagPointer + FFIFlagStructure is used to represent 'typedef void* VoidPointer' and VoidPointer must be returned as pointer *not* as struct" (ffiRetHeader anyMask: FFIFlagPointer) ifTrue:[ retVal _ self ffiCallAddressOf: address WithPointerReturn: callType. ^self ffiCreateReturnPointer: retVal. ]. (ffiRetHeader anyMask: FFIFlagStructure) ifTrue:[ self ffiCallAddressOf: address With: callType Struct: (self cCoerce: ffiRetSpec to:'int*') Return: ffiRetSpecSize. ^self ffiCreateReturnStruct. ]. retVal _ self ffiCallAddressOf: address With: callType ReturnType: ffiRetHeader. ^self ffiCreateReturn: retVal.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 21:29'! ffiCheckReturn: retSpec With: retClass "Make sure we can return an object of the given type" self inline: true. retClass == interpreterProxy nilObject ifFalse:[ (interpreterProxy includesBehavior: retClass ThatOf: interpreterProxy classExternalStructure) ifFalse:[^self ffiFail: FFIErrorBadReturn]]. ffiRetClass _ retClass. (interpreterProxy isIntegerObject: retSpec) ifTrue:[self ffiFail: FFIErrorWrongType. ^nil]. (interpreterProxy isWords: retSpec) ifFalse:[self ffiFail: FFIErrorWrongType. ^nil]. ffiRetSpecSize _ interpreterProxy slotSizeOf: retSpec. ffiRetSpecSize = 0 ifTrue:[self ffiFail: FFIErrorWrongType. ^nil]. ffiRetSpec _ self cCoerce: (interpreterProxy firstIndexableField: retSpec) to: 'int'. ffiRetHeader _ interpreterProxy longAt: ffiRetSpec. (self isAtomicType: ffiRetHeader) ifFalse:[ (ffiRetClass == interpreterProxy nilObject) ifTrue:[^self ffiFail: FFIErrorBadReturn]]. (self ffiCan: (self cCoerce: ffiRetSpec to:'int*') Return: ffiRetSpecSize) ifFalse:[self ffiFail: FFIErrorBadReturn]. "cannot return this type" ^0! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 21:35'! ffiContentsOfHandle: oop errCode: errCode "Make sure that the given oop is a valid external handle" self inline: true. (interpreterProxy isIntegerObject: oop) ifTrue:[^self ffiFail: errCode]. (interpreterProxy isBytes: oop) ifFalse:[^self ffiFail: errCode]. ((interpreterProxy byteSizeOf: oop) == 4) ifFalse:[^self ffiFail: errCode]. ^interpreterProxy fetchWord: 0 ofObject: oop! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 19:43'! ffiCreateLongLongReturn: isSigned "Create a longlong return value from a previous call out" | lowWord highWord largeClass nBytes largeInt ptr | self var: #ptr declareC:'unsigned char *ptr'. lowWord _ self ffiLongLongResultLow. highWord _ self ffiLongLongResultHigh. isSigned ifTrue:["check for 32 bit signed" (highWord = 0 and:[lowWord >= 0]) ifTrue:[^interpreterProxy signed32BitIntegerFor: lowWord]. (highWord = -1 and:[lowWord < 0]) ifTrue:[^interpreterProxy signed32BitIntegerFor: lowWord]. "negate value for negative longlong" highWord < 0 ifTrue:[ largeClass _ interpreterProxy classLargeNegativeInteger. lowWord _ lowWord bitInvert32. highWord _ highWord bitInvert32. lowWord = -1 "e.g., overflow when adding one" ifTrue:[highWord _ highWord + 1]. lowWord _ lowWord + 1] ifFalse:[largeClass _ interpreterProxy classLargePositiveInteger]. "fall through" ] ifFalse:["check for 32 bit unsigned" highWord = 0 ifTrue:[ ^interpreterProxy positive32BitIntegerFor: lowWord]. largeClass _ interpreterProxy classLargePositiveInteger. "fall through" ]. "Create LargeInteger result" nBytes _ 8. (highWord anyMask: 255 << 24) ifFalse:[ nBytes _ 7. highWord < (1 << 16) ifTrue:[nBytes _ 6]. highWord < (1 << 8) ifTrue:[nBytes _ 5]. highWord = 0 ifTrue:[nBytes _ 4]]. "now we know how many bytes to create" largeInt _ interpreterProxy instantiateClass: largeClass indexableSize: nBytes. (interpreterProxy isBytes: largeInt) ifFalse:[^self ffiFail: FFIErrorBadReturn]. "Hossa!!" ptr _ interpreterProxy firstIndexableField: largeInt. 4 to: nBytes-1 do:[:i| ptr at: i put: (highWord >> (i-4*8) bitAnd: 255)]. ptr at: 3 put: (lowWord >> 24 bitAnd: 255). ptr at: 2 put: (lowWord >> 16 bitAnd: 255). ptr at: 1 put: (lowWord >> 8 bitAnd: 255). ptr at: 0 put: (lowWord bitAnd: 255). ^largeInt! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 19:47'! ffiCreateReturn: retVal "Generic callout support. Create an atomic return value from an external function call" | atomicType retOop oop | self inline: true. interpreterProxy failed ifTrue:[^nil]. atomicType _ self atomicTypeOf: ffiRetHeader. "void returns self" atomicType <= FFITypeVoid ifTrue:[ ^interpreterProxy pop: interpreterProxy methodArgumentCount]. "everything else returns value" interpreterProxy pop: interpreterProxy methodArgumentCount+1. interpreterProxy pushRemappableOop: ffiRetClass. retOop _ self ffiCreateReturnOop: retVal. ffiRetClass _ interpreterProxy popRemappableOop. ffiRetClass == interpreterProxy nilObject ifTrue:[ "Just return oop" ^interpreterProxy push: retOop]. "Otherwise create an instance of external structure and store the return oop" interpreterProxy pushRemappableOop: retOop. retOop _ interpreterProxy instantiateClass: ffiRetClass indexableSize: 0. oop _ interpreterProxy popRemappableOop. interpreterProxy storePointer: 0 ofObject: retOop withValue: oop. ^interpreterProxy push: retOop.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 5/16/2000 13:39'! ffiCreateReturnOop: retVal "Callout support. Return the appropriate oop for the given atomic value" | atomicType shift value mask byteSize | atomicType _ self atomicTypeOf: ffiRetHeader. atomicType = FFITypeBool ifTrue:[ "Make sure bool honors the byte size requested" byteSize _ ffiRetHeader bitAnd: FFIStructSizeMask. byteSize = 4 ifTrue:[value _ retVal] ifFalse:[value _ retVal bitAnd: 1 << (byteSize * 8) - 1]. value = 0 ifTrue:[^interpreterProxy falseObject] ifFalse:[^interpreterProxy trueObject]]. atomicType <= FFITypeSignedInt ifTrue:[ "these are all generall integer returns" atomicType <= FFITypeSignedShort ifTrue:[ "byte/short. first extract partial word, then sign extend" shift _ (atomicType >> 1) * 8. "# of significant bits" value _ retVal bitAnd: (1 << shift - 1). (atomicType anyMask: 1) ifTrue:[ "make the guy signed" mask _ 1 << (shift-1). value _ (value bitAnd: mask-1) - (value bitAnd: mask)]. ^interpreterProxy integerObjectOf: value]. "32bit integer return" (atomicType anyMask: 1) ifTrue:[^(interpreterProxy signed32BitIntegerFor: retVal)] "signed return" ifFalse:[^(interpreterProxy positive32BitIntegerFor: retVal)]]. "unsigned return" atomicType < FFITypeSingleFloat ifTrue:[ "longlong, char" (atomicType >> 1) = (FFITypeSignedLongLong >> 1) ifTrue:[^self ffiCreateLongLongReturn: (atomicType anyMask: 1)] ifFalse:[^(interpreterProxy fetchPointer: (retVal bitAnd: 255) ofObject: interpreterProxy characterTable)]]. "float return" ^interpreterProxy floatObjectOf: (self ffiReturnFloatValue).! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 23:15'! ffiCreateReturnPointer: retVal "Generic callout support. Create a pointer return value from an external function call" | atomicType retOop oop ptr classOop | self var: #ptr declareC:'int *ptr'. interpreterProxy failed ifTrue:[^nil]. interpreterProxy pop: interpreterProxy methodArgumentCount+1. (ffiRetClass == interpreterProxy nilObject) ifTrue:[ "Create ExternalData upon return" atomicType _ self atomicTypeOf: ffiRetHeader. (atomicType >> 1) = (FFITypeSignedChar >> 1) ifTrue:["String return" ^self ffiReturnCStringFrom: retVal]. "generate external data" interpreterProxy pushRemappableOop: ffiRetOop. oop _ interpreterProxy instantiateClass: interpreterProxy classExternalAddress indexableSize: 4. ptr _ interpreterProxy firstIndexableField: oop. ptr at: 0 put: retVal. interpreterProxy pushRemappableOop: oop. "preserve for gc" retOop _ interpreterProxy instantiateClass: interpreterProxy classExternalData indexableSize: 0. oop _ interpreterProxy popRemappableOop. "external address" interpreterProxy storePointer: 0 ofObject: retOop withValue: oop. oop _ interpreterProxy popRemappableOop. "return type" interpreterProxy storePointer: 1 ofObject: retOop withValue: oop. ^interpreterProxy push: retOop. ]. "non-atomic pointer return" interpreterProxy pushRemappableOop: ffiRetClass. "preserve for gc" (ffiRetHeader anyMask: FFIFlagStructure) ifTrue:[classOop _ interpreterProxy classByteArray] ifFalse:[classOop _ interpreterProxy classExternalAddress]. oop _ interpreterProxy instantiateClass: classOop indexableSize: 4. ptr _ interpreterProxy firstIndexableField: oop. ptr at: 0 put: retVal. ffiRetClass _ interpreterProxy popRemappableOop. "return class" interpreterProxy pushRemappableOop: oop. "preserve for gc" retOop _ interpreterProxy instantiateClass: ffiRetClass indexableSize: 0. oop _ interpreterProxy popRemappableOop. "external address" interpreterProxy storePointer: 0 ofObject: retOop withValue: oop. ^interpreterProxy push: retOop.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 21:33'! ffiCreateReturnStruct "Generic callout support. Create a structure return value from an external function call" | retOop structSize oop | self inline: true. interpreterProxy failed ifTrue:[^nil]. interpreterProxy pop: interpreterProxy methodArgumentCount+1. structSize _ ffiRetHeader bitAnd: FFIStructSizeMask. interpreterProxy pushRemappableOop: ffiRetClass. oop _ interpreterProxy instantiateClass: interpreterProxy classByteArray indexableSize: structSize. self ffiStore: (self cCoerce: (interpreterProxy firstIndexableField: oop) to:'int') Structure: structSize. ffiRetClass _ interpreterProxy popRemappableOop. interpreterProxy pushRemappableOop: oop. "secure byte array" retOop _ interpreterProxy instantiateClass: ffiRetClass indexableSize: 0. oop _ interpreterProxy popRemappableOop. interpreterProxy storePointer: 0 ofObject: retOop withValue: oop. ^interpreterProxy push: retOop.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/29/1999 10:42'! ffiFail: reason self inline: true. self ffiSetLastError: reason. ^interpreterProxy primitiveFail! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 21:36'! ffiFloatValueOf: oop "Support for generic callout. Return a float value that is coerced as C would do." | oopClass | self returnTypeC:'double'. oopClass _ interpreterProxy fetchClassOf: oop. oopClass == interpreterProxy classFloat ifTrue:[^interpreterProxy floatValueOf: oop]. "otherwise try the integer coercions and return its float value" ^(self ffiIntegerValueOf: oop) asFloat! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 18:06'! ffiGetLastError ^ffiLastError! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/1/1999 18:55'! ffiIntegerValueOf: oop "Support for generic callout. Return an integer value that is coerced as C would do." | oopClass | self inline: true. (interpreterProxy isIntegerObject: oop) ifTrue:[^interpreterProxy integerValueOf: oop]. oop == interpreterProxy nilObject ifTrue:[^0]. "@@: should we really allow this????" oop == interpreterProxy falseObject ifTrue:[^0]. oop == interpreterProxy trueObject ifTrue:[^1]. oopClass _ interpreterProxy fetchClassOf: oop. oopClass == interpreterProxy classFloat ifTrue:[^(interpreterProxy floatValueOf: oop) asInteger]. oopClass == interpreterProxy classCharacter ifTrue:[^interpreterProxy fetchInteger: 0 ofObject: oop]. ^interpreterProxy signed32BitValueOf: oop "<- will fail if not integer"! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/26/2000 23:08'! ffiPushPointerContentsOf: oop "Push the contents of the given external structure" | ptrValue ptrClass ptrAddress | self inline: false. ptrValue _ oop. ptrClass _ interpreterProxy fetchClassOf: ptrValue. ptrClass == interpreterProxy classExternalAddress ifTrue:[ ptrAddress _ interpreterProxy fetchWord: 0 ofObject: ptrValue. "Don't you dare to pass pointers into object memory" (interpreterProxy isInMemory: ptrAddress) ifTrue:[^self ffiFail: FFIErrorInvalidPointer]. ^self ffiPushPointer: ptrAddress]. ptrClass == interpreterProxy classByteArray ifTrue:[ ptrAddress _ self cCoerce: (interpreterProxy firstIndexableField: ptrValue) to: 'int'. ^self ffiPushPointer: ptrAddress]. ^self ffiFail: FFIErrorBadArg! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 18:21'! ffiPushSignedLongLongOop: oop "Push a longlong type (e.g., a 64bit integer). Note: Coercions from float are *not* supported." | lowWord highWord length oopClass negative ptr | self var: #ptr declareC:'unsigned char *ptr'. oop == interpreterProxy nilObject ifTrue:[^self ffiPushSignedLong: 0 Long: 0.]. "@@: check this" oop == interpreterProxy falseObject ifTrue:[^self ffiPushSignedLong: 0 Long: 0]. oop == interpreterProxy trueObject ifTrue:[^self ffiPushSignedLong: 0 Long: 1]. (interpreterProxy isIntegerObject: oop) ifTrue:[ lowWord _ interpreterProxy integerValueOf: oop. lowWord < 0 ifTrue:[highWord _ -1] ifFalse:[highWord _ 0]. ] ifFalse:[ oopClass _ interpreterProxy fetchClassOf: oop. oopClass == interpreterProxy classLargePositiveInteger ifTrue:[negative _ false] ifFalse:[oopClass == interpreterProxy classLargeNegativeInteger ifTrue:[negative _ true] ifFalse:[^self ffiFail: FFIErrorCoercionFailed]]. (interpreterProxy isBytes: oop) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. length _ interpreterProxy byteSizeOf: oop. length > 8 ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. lowWord _ highWord _ 0. ptr _ interpreterProxy firstIndexableField: oop. 0 to: (length min: 4)-1 do:[:i| lowWord _ lowWord + ((ptr at: i) << (i*8))]. 0 to: (length-5) do:[:i| highWord _ highWord + ((ptr at: i+4) << (i*8))]. negative ifTrue:[ lowWord _ lowWord bitInvert32. highWord _ highWord bitInvert32. lowWord = -1 "e.g., will overflow when adding one" ifTrue:[highWord _ highWord + 1]. lowWord _ lowWord + 1]. ]. ^self ffiPushSignedLong: lowWord Long: highWord.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 1/27/2000 00:02'! ffiPushStructureContentsOf: oop "Push the contents of the given external structure" | ptrValue ptrClass ptrAddress | self inline: true. ptrValue _ oop. ptrClass _ interpreterProxy fetchClassOf: ptrValue. ptrClass == interpreterProxy classExternalAddress ifTrue:[ ptrAddress _ interpreterProxy fetchWord: 0 ofObject: ptrValue. "There is no way we can make sure the structure is valid. But we can at least check for attempts to pass pointers to ST memory." (interpreterProxy isInMemory: ptrAddress) ifTrue:[^self ffiFail: FFIErrorInvalidPointer]. ^self ffiPush: ptrAddress Structure: (self cCoerce: ffiArgSpec to:'int*') OfLength: ffiArgSpecSize]. ptrClass == interpreterProxy classByteArray ifTrue:[ "The following is a somewhat pessimistic test but I like being sure..." (interpreterProxy byteSizeOf: ptrValue) = (ffiArgHeader bitAnd: FFIStructSizeMask) ifFalse:[^self ffiFail: FFIErrorStructSize]. ptrAddress _ self cCoerce: (interpreterProxy firstIndexableField: ptrValue) to: 'int'. (ffiArgHeader anyMask: FFIFlagPointer) ifFalse:[ ^self ffiPush: ptrAddress Structure: (self cCoerce: ffiArgSpec to: 'int*') OfLength: ffiArgSpecSize]. "If FFIFlagPointer + FFIFlagStructure is set use ffiPushPointer on the contents" (ffiArgHeader bitAnd: FFIStructSizeMask) = 4 ifFalse:[^self ffiFail: FFIErrorStructSize]. ptrAddress _ interpreterProxy fetchWord: 0 ofObject: ptrValue. (interpreterProxy isInMemory: ptrAddress) ifTrue:[^self ffiFail: FFIErrorInvalidPointer]. ^self ffiPushPointer: ptrAddress]. ^self ffiFail: FFIErrorBadArg! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 19:05'! ffiPushUnsignedLongLongOop: oop "Push a longlong type (e.g., a 64bit integer). Note: Coercions from float are *not* supported." | lowWord highWord length ptr | self var: #ptr declareC:'unsigned char *ptr'. oop == interpreterProxy nilObject ifTrue:[^self ffiPushUnsignedLong: 0 Long: 0.]. "@@: check this" oop == interpreterProxy falseObject ifTrue:[^self ffiPushUnsignedLong: 0 Long: 0]. oop == interpreterProxy trueObject ifTrue:[^self ffiPushUnsignedLong: 0 Long: 1]. (interpreterProxy isIntegerObject: oop) ifTrue:[ lowWord _ interpreterProxy integerValueOf: oop. lowWord < 0 ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. highWord _ 0. ] ifFalse:[ (interpreterProxy fetchClassOf: oop) = interpreterProxy classLargePositiveInteger ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isBytes: oop) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. length _ interpreterProxy byteSizeOf: oop. length > 8 ifTrue:[^self ffiFail: FFIErrorCoercionFailed]. lowWord _ highWord _ 0. ptr _ interpreterProxy firstIndexableField: oop. 0 to: (length min: 4)-1 do:[:i| lowWord _ lowWord + ((ptr at: i) << (i*8))]. 0 to: (length-5) do:[:i| highWord _ highWord + ((ptr at: i+4) << (i*8))]. ]. ^self ffiPushUnsignedLong: lowWord Long: highWord.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 19:25'! ffiPushVoid: ignored "This is a fallback in case somebody tries to pass a 'void' value. We could simply ignore the argument but I think it's better to let the caller know what he did" ^self ffiFail: FFIErrorAttemptToPassVoid.! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 18:25'! ffiReturnCStringFrom: cPointer "Create a Smalltalk string from a zero terminated C string" | strLen strOop cString strPtr | self var: #cString declareC:'char *cString'. self var: #strPtr declareC:'char *strPtr'. cPointer = nil ifTrue:[ ^interpreterProxy push: interpreterProxy nilObject]. "nil always returs as nil" cString _ self cCoerce: cPointer to:'char *'. strLen _ 0. [(cString at: strLen) = 0] whileFalse:[strLen _ strLen+1]. strOop _ interpreterProxy instantiateClass: interpreterProxy classString indexableSize: strLen. strPtr _ interpreterProxy firstIndexableField: strOop. 0 to: strLen-1 do:[:i| strPtr at: i put: (cString at: i)]. ^interpreterProxy push: strOop! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 11/28/1999 18:25'! ffiSetLastError: errCode ^ffiLastError _ errCode! ! !FFIPlugin methodsFor: 'callout support' stamp: 'ar 12/2/1999 21:37'! ffiValidateExternalData: oop AtomicType: atomicType "Validate if the given oop (an instance of ExternalData) can be passed as a pointer to the given atomic type." | ptrType specOop spec specType | self inline: true. ptrType _ interpreterProxy fetchPointer: 1 ofObject: oop. (interpreterProxy isIntegerObject: ptrType) ifTrue:[^self ffiFail: FFIErrorWrongType]. (interpreterProxy isPointers: ptrType) ifFalse:[^self ffiFail: FFIErrorWrongType]. (interpreterProxy slotSizeOf: ptrType) < 2 ifTrue:[^self ffiFail: FFIErrorWrongType]. specOop _ interpreterProxy fetchPointer: 0 ofObject: ptrType. (interpreterProxy isIntegerObject: specOop) ifTrue:[^self ffiFail: FFIErrorWrongType]. (interpreterProxy isWords: specOop) ifFalse:[^self ffiFail: FFIErrorWrongType]. (interpreterProxy slotSizeOf: specOop) = 0 ifTrue:[^self ffiFail: FFIErrorWrongType]. spec _ interpreterProxy fetchWord: 0 ofObject: specOop. (self isAtomicType: spec) ifFalse:[^self ffiFail: FFIErrorWrongType]. specType _ self atomicTypeOf: spec. specType ~= atomicType ifTrue:[ "allow for signed/unsigned conversion but nothing else" (atomicType > FFITypeBool and:[atomicType < FFITypeSingleFloat]) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]. ((atomicType >> 1) = (specType >> 1)) ifFalse:[^self ffiFail: FFIErrorCoercionFailed]]. ^0! ! !FFIPlugin methodsFor: 'symbol loading' stamp: 'ar 11/28/1999 19:10'! ffiLoadCalloutAddress: lit "Load the address of the foreign function from the given object" | addressPtr address ptr | self var: #ptr declareC:'int *ptr'. "Lookup the address" addressPtr _ interpreterProxy fetchPointer: 0 ofObject: lit. "Make sure it's an external handle" address _ self ffiContentsOfHandle: addressPtr errCode: FFIErrorBadAddress. interpreterProxy failed ifTrue:[^0]. address = 0 ifTrue:["Go look it up in the module" (interpreterProxy slotSizeOf: lit) < 5 ifTrue:[^self ffiFail: FFIErrorNoModule]. address _ self ffiLoadCalloutAddressFrom: lit. interpreterProxy failed ifTrue:[^0]. "Store back the address" ptr _ interpreterProxy firstIndexableField: addressPtr. ptr at: 0 put: address]. ^address! ! !FFIPlugin methodsFor: 'symbol loading' stamp: 'ar 11/28/1999 20:09'! ffiLoadCalloutAddressFrom: oop "Load the function address for a call out to an external function" | module moduleHandle functionName functionLength address | self inline: false. "First find and load the module" module _ interpreterProxy fetchPointer: 4 ofObject: oop. moduleHandle _ self ffiLoadCalloutModule: module. interpreterProxy failed ifTrue:[^0]. "failed" "fetch the function name" functionName _ interpreterProxy fetchPointer: 3 ofObject: oop. (interpreterProxy isBytes: functionName) ifFalse:[^self ffiFail: FFIErrorBadExternalFunction]. functionLength _ interpreterProxy byteSizeOf: functionName. address _ interpreterProxy ioLoadSymbol: (self cCoerce: (interpreterProxy firstIndexableField: functionName) to:'int') OfLength: functionLength FromModule: moduleHandle. (interpreterProxy failed or:[address = 0]) ifTrue:[^self ffiFail: FFIErrorAddressNotFound]. ^address! ! !FFIPlugin methodsFor: 'symbol loading' stamp: 'ar 5/12/2000 17:22'! ffiLoadCalloutModule: module "Load the given module and return its handle" | moduleHandlePtr moduleHandle ffiModuleName moduleLength rcvr theClass ptr | self var: #ptr declareC:'int *ptr'. (interpreterProxy isBytes: module) ifTrue:[ "plain module name" ffiModuleName _ module. moduleLength _ interpreterProxy byteSizeOf: ffiModuleName. moduleHandle _ interpreterProxy ioLoadModule: (self cCoerce: (interpreterProxy firstIndexableField: ffiModuleName) to:'int') OfLength: moduleLength. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorModuleNotFound]. "failed" ^moduleHandle]. "Check if the external method is defined in an external library" rcvr _ interpreterProxy stackValue: interpreterProxy methodArgumentCount. theClass _ interpreterProxy fetchClassOf: rcvr. (interpreterProxy includesBehavior: theClass ThatOf: interpreterProxy classExternalLibrary) ifFalse:[^0]. "external library" moduleHandlePtr _ interpreterProxy fetchPointer: 0 ofObject: rcvr. moduleHandle _ self ffiContentsOfHandle: moduleHandlePtr errCode: FFIErrorBadExternalLibrary. interpreterProxy failed ifTrue:[^0]. moduleHandle = 0 ifTrue:["need to reload module" ffiModuleName _ interpreterProxy fetchPointer: 1 ofObject: rcvr. (interpreterProxy isBytes: ffiModuleName) ifFalse:[^self ffiFail: FFIErrorBadExternalLibrary]. moduleLength _ interpreterProxy byteSizeOf: ffiModuleName. moduleHandle _ interpreterProxy ioLoadModule: (self cCoerce: (interpreterProxy firstIndexableField: ffiModuleName) to:'int') OfLength: moduleLength. interpreterProxy failed ifTrue:[^self ffiFail: FFIErrorModuleNotFound]. "failed" "and store back" ptr _ interpreterProxy firstIndexableField: moduleHandlePtr. ptr at: 0 put: moduleHandle]. ^moduleHandle! ! !FFIPlugin methodsFor: 'primitive support' stamp: 'ar 11/28/1999 23:59'! addressOf: rcvr startingAt: byteOffset size: byteSize | rcvrClass rcvrSize addr | (interpreterProxy isBytes: rcvr) ifFalse:[^interpreterProxy primitiveFail]. (byteOffset > 0) ifFalse:[^interpreterProxy primitiveFail]. rcvrClass _ interpreterProxy fetchClassOf: rcvr. rcvrSize _ interpreterProxy byteSizeOf: rcvr. rcvrClass == interpreterProxy classExternalAddress ifTrue:[ (rcvrSize = 4) ifFalse:[^interpreterProxy primitiveFail]. addr _ interpreterProxy fetchWord: 0 ofObject: rcvr. "don't you dare to read from object memory!!" (addr == 0 or:[interpreterProxy isInMemory: addr]) ifTrue:[^interpreterProxy primitiveFail]. ] ifFalse:[ (byteOffset+byteSize-1 <= rcvrSize) ifFalse:[^interpreterProxy primitiveFail]. addr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'int'. ]. addr _ addr + byteOffset - 1. ^addr! ! !FFIPlugin methodsFor: 'primitive support' stamp: 'ar 12/2/1999 21:02'! atomicTypeOf: value ^(value bitAnd: FFIAtomicTypeMask) >> FFIAtomicTypeShift! ! !FFIPlugin methodsFor: 'primitive support' stamp: 'ar 12/2/1999 21:04'! isAtomicType: typeSpec ^typeSpec anyMask: FFIFlagAtomic! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFIPlugin class instanceVariableNames: ''! !FFIPlugin class methodsFor: 'accessing' stamp: 'ar 11/28/1999 18:55'! declareCVarsIn: aCCodeGen aCCodeGen addHeaderFile: '"sqFFI.h"'! ! !FFIPlugin class methodsFor: 'accessing' stamp: 'ar 12/2/1999 22:19'! moduleName "FFIPlugin translate" "IMPORTANT: IF YOU CHANGE THE NAME OF THIS PLUGIN YOU MUST CHANGE Interpreter>>primitiveCalloutToFFI TO REFLECT THE CHANGE." ^'SqueakFFIPrims'! ! !FFIPlugin class methodsFor: 'C support code' stamp: 'ar 1/26/2000 14:27'! sqFFIHeaderFile ^'/**************************************************************************** * PROJECT: Squeak foreign function interface * FILE: sqFFI.h * CONTENT: Declarations for the foreign function interface * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * *****************************************************************************/ #ifndef SQ_FFI_H #define SQ_FFI_H /* Calling conventions */ #define FFICallTypeCDecl 0 #define FFICallTypeApi 1 /* Atomic types */ #define FFITypeVoid 0 #define FFITypeBool 1 #define FFITypeUnsignedByte 2 #define FFITypeSignedByte 3 #define FFITypeUnsignedShort 4 #define FFITypeSignedShort 5 #define FFITypeUnsignedInt 6 #define FFITypeSignedInt 7 #define FFITypeUnsignedLongLong 8 #define FFITypeSignedLongLong 9 #define FFITypeUnsignedChar 10 #define FFITypeSignedChar 11 #define FFITypeSingleFloat 12 #define FFITypeDoubleFloat 13 /* Shift and mask for atomic types */ #define FFIAtomicTypeShift 24 #define FFIAtomicTypeMask 251658240 /* Type flags */ #define FFIFlagPointer 131072 #define FFIFlagStructure 65536 #define FFIFlagAtomic 262144 /* Size mask */ #define FFIStructSizeMask 65535 /* error constants */ #define FFINoCalloutAvailable -1 #define FFIErrorGenericError 0 #define FFIErrorNotFunction 1 #define FFIErrorBadArgs 2 #define FFIErrorBadArg 3 #define FFIErrorIntAsPointer 4 #define FFIErrorBadAtomicType 5 #define FFIErrorCoercionFailed 6 #define FFIErrorWrongType 7 #define FFIErrorStructSize 8 #define FFIErrorCallType 9 #define FFIErrorBadReturn 10 #define FFIErrorBadAddress 11 #define FFIErrorNoModule 12 #define FFIErrorAddressNotFound 13 #define FFIErrorAttemptToPassVoid 14 #define FFIErrorModuleNotFound 15 #define FFIErrorBadExternalLibrary 16 #define FFIErrorBadExternalFunction 17 #define FFIErrorInvalidPointer 18 /* Announce a coming FFI call */ int ffiInitialize(void); /* cleanup */ int ffiCleanup(void); /* Allocate/free external memory */ int ffiAlloc(int byteSize); int ffiFree(int ptr); /* general <=32bit integer loads */ int ffiPushSignedByte(int value); int ffiPushUnsignedByte(int value); int ffiPushSignedShort(int value); int ffiPushUnsignedShort(int value); int ffiPushSignedInt(int value); int ffiPushUnsignedInt(int value); /* 64bit integer loads */ int ffiPushSignedLongLong(int lowWord, int highWord); int ffiPushUnsignedLongLong(int lowWord, int highWord); /* 64bit integer returns */ int ffiLongLongResultLow(void); int ffiLongLongResultHigh(void); /* special <=32bit loads */ int ffiPushSignedChar(int value); int ffiPushUnsignedChar(int value); /* float loads */ int ffiPushSingleFloat(double value); int ffiPushDoubleFloat(double value); /* structure loads */ int ffiPushStructureOfLength(int pointer, int* structSpec, int specSize); /* pointer loads */ int ffiPushPointer(int pointer); /* string loads */ int ffiPushStringOfLength(int srcIndex, int length); /* return true if calling convention is supported */ int ffiSupportsCallingConvention(int callType); /* return true if these types can be returned */ int ffiCanReturn(int* structSpec, int specSize); /* call the appropriate function w/ the given return type */ int ffiCallAddressOfWithPointerReturn(int fn, int callType); int ffiCallAddressOfWithStructReturn(int fn, int callType, int* structSpec, int specSize); int ffiCallAddressOfWithReturnType(int fn, int callType, int typeSpec); /* store the structure result of a previous call */ int ffiStoreStructure(int address, int structSize); /* return the float value from a previous call */ double ffiReturnFloatValue(void); #endif /* SQ_FFI_H */ '! ! !FFIPlugin class methodsFor: 'C support code' stamp: 'JMM 8/16/2000 13:53'! sqMacFFIPPCFile ^'/**************************************************************************** * PROJECT: Squeak foreign function interface * FILE: sqMacFFIPPC.c * CONTENT: Mac/PPC specific support for the foreign function interface * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: Andreas.Raab@disney.com * RCSID: $Id$ * * NOTES: * *****************************************************************************/ #include "sq.h" #include "sqFFI.h" /* note: LONGLONG is usually declared by universal headers */ #ifndef LONGLONG #define LONGLONG long long #endif extern struct VirtualMachine *interpreterProxy; #define primitiveFail() interpreterProxy->primitiveFail(); #define GP_MAX_REGS 8 #define FP_MAX_REGS 13 /* Values passed in GPR3-GPR10 */ static int GPRegs[8]; /* Nr of GPRegs used so far */ static int gpRegCount = 0; /* Values passed in FPR1-FPR13 */ static double FPRegs[13]; /* Nr of FPRegs used so far */ static int fpRegCount = 0; /* Max stack size */ #define FFI_MAX_STACK 512 /* The stack used to assemble the arguments for a call */ static int ffiStack[FFI_MAX_STACK]; /* The stack pointer while filling the stack */ static int ffiStackIndex = 0; /* The area for temporarily allocated strings */ static char *ffiTempStrings[FFI_MAX_STACK]; /* The number of temporarily allocated strings */ static int ffiTempStringCount = 0; /* The return values for calls */ static int intReturnValue; static LONGLONG longReturnValue; static double floatReturnValue; static int *structReturnValue = NULL; /**************************************************************/ #define ARG_CHECK() if(gpRegCount >= GP_MAX_REGS && ffiStackIndex >= FFI_MAX_STACK) return primitiveFail(); #define ARG_PUSH(value) { \ ARG_CHECK(); \ if(gpRegCount < GP_MAX_REGS) GPRegs[gpRegCount++] = value; \ ffiStack[ffiStackIndex++] = value; \ } /*****************************************************************************/ /*****************************************************************************/ /* ffiInitialize: Announce that the VM is about to do an external function call. */ int ffiInitialize(void) { ffiStackIndex = 0; gpRegCount = 0; fpRegCount = 0; floatReturnValue = 0.0; return 1; } /* ffiSupportsCallingConvention: Return true if the support code supports the given calling convention. */ int ffiSupportsCallingConvention(int callType) { if(callType == FFICallTypeCDecl) return 1; if(callType == FFICallTypeApi) return 1; return 0; } int ffiAlloc(int byteSize) { return (int) malloc(byteSize); } int ffiFree(int ptr) { if(ptr) free((void*)ptr); return 1; } /*****************************************************************************/ /*****************************************************************************/ int ffiPushSignedChar(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedChar(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedByte(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedByte(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedShort(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedShort(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedInt(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedInt(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedLongLong(int low, int high) { ARG_PUSH(high); ARG_PUSH(low); return 1; } int ffiPushUnsignedLongLong(int low, int high) { ARG_PUSH(high); ARG_PUSH(low); return 1; } int ffiPushSingleFloat(double value) { float floatValue = (float) value; if(fpRegCount < FP_MAX_REGS) { /* Still space in FPRegs - so we use the more accurate double value */ FPRegs[fpRegCount++] = value; } /* Note: Even for args that are passed in FPRegs we pass the actual 32bit value in either GPRegs or stack frame for varargs calls. */ ARG_PUSH(*(int*)(&floatValue)); return 1; } int ffiPushDoubleFloat(double value) { if(fpRegCount < FP_MAX_REGS) { /* Still space in FPRegs */ FPRegs[fpRegCount++] = value; } /* Note: Even for args that are passed in FPRegs we pass the actual 64bit value in either GPRegs or stack frame for varargs calls. */ ARG_PUSH(((int*)(&value))[1]); ARG_PUSH(((int*)(&value))[0]); return 1; } int ffiPushStructureOfLength(int pointer, int *structSpec, int specSize) { int i, typeSpec; int *data = (int*) pointer; for(i = 0; i> FFIAtomicTypeShift; switch(atomicType) { case FFITypeUnsignedChar: case FFITypeUnsignedByte: ffiPushUnsignedByte(*(unsigned char*)data); break; case FFITypeSignedChar: case FFITypeSignedByte: ffiPushSignedByte(*(signed char*)data); break; case FFITypeUnsignedShort: ffiPushUnsignedShort(*(unsigned short*)data); break; case FFITypeSignedShort: ffiPushSignedShort(*(signed short*)data); break; case FFITypeUnsignedInt: ffiPushUnsignedInt(*(unsigned int*)data); break; case FFITypeSignedInt: ffiPushSignedInt(*(signed int*)data); break; case FFITypeUnsignedLongLong: ffiPushUnsignedLongLong( ((unsigned int*)data)[1], ((unsigned int*)data)[0]); break; case FFITypeSignedLongLong: ffiPushSignedLongLong( ((signed int*)data)[1], ((signed int*)data)[0]); break; case FFITypeSingleFloat: ffiPushSingleFloat( *(float*)data); break; case FFITypeDoubleFloat: { double fArg; ((int*)&fArg)[0] = ((int*)data)[0]; ((int*)&fArg)[1] = ((int*)data)[1]; ffiPushDoubleFloat(fArg); } break; default: return primitiveFail(); } data = (int*) ((int)data + (typeSpec & FFIStructSizeMask)); } } return 1; } int ffiPushPointer(int pointer) { ARG_PUSH(pointer); return 1; } int ffiPushStringOfLength(int srcIndex, int length) { char *ptr; ARG_CHECK(); /* fail before allocating */ ptr = (char*) malloc(length+1); if(!!ptr) return primitiveFail(); memcpy(ptr, (void*)srcIndex, length); ptr[length] = 0; ffiTempStrings[ffiTempStringCount++] = ptr; ARG_PUSH((int)ptr); return 1; } /*****************************************************************************/ /*****************************************************************************/ /* ffiCanReturn: Return true if the support code can return the given type. */ int ffiCanReturn(int *structSpec, int specSize) { int header = *structSpec; if(header & FFIFlagPointer) return 1; if(header & FFIFlagStructure) { /* structs are always returned as pointers to hidden structures */ int structSize = header & FFIStructSizeMask; structReturnValue = malloc(structSize); if(!!structReturnValue) return 0; ARG_PUSH((int)structReturnValue); } return 1; } /* ffiReturnFloatValue: Return the value from a previous ffi call with float return type. */ double ffiReturnFloatValue(void) { return floatReturnValue; } /* ffiLongLongResultLow: Return the low 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultLow(void) { return ((int*) &longReturnValue)[1]; } /* ffiLongLongResultHigh: Return the high 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultHigh(void) { return ((int*) &longReturnValue)[0]; } /* ffiStoreStructure: Store the structure result of a previous ffi call into the given address. */ int ffiStoreStructure(int address, int structSize) { if(structReturnValue) { memcpy((void*)address, (void*)structReturnValue, structSize); } else { memcpy((void*)address, (void*)&intReturnValue, structSize); } return 1; } /* ffiCleanup: Cleanup after a foreign function call has completed. The generic support code only frees the temporarily allocated strings. */ int ffiCleanup(void) { int i; for(i=0; ix, pt1->y, pt1->z, pt1->w); printf("pt2.x = %d\npt2.y = %d\npt2.z = %d\npt2.w = %d\n", pt2->x, pt2->y, pt2->z, pt2->w); result = (ffiTestPoint4*) malloc(sizeof(ffiTestPoint4)); result->x = pt1->x + pt2->x; result->y = pt1->y + pt2->y; result->z = pt1->z + pt2->z; result->w = pt1->w + pt2->w; return result; } /* test passing and returning longlongs */ EXPORT(LONGLONG) ffiTestLongLong(LONGLONG i1, LONGLONG i2) { return i1 + i2; } #endif /* NO_FFI_TEST */ '! ! !FFIPlugin class methodsFor: 'C support code' stamp: 'ar 1/26/2000 14:29'! sqUnixFFIFile ^'/**************************************************************************** * PROJECT: Squeak foreign function interface * FILE: sqUnixFFI.c * CONTENT: Unix support for the foreign function interface * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: The Unix version of the FFI support code relies on libffi from * http://sourceware.cygnus.com/libffi/ * *****************************************************************************/ #include "sq.h" #include "sqFFI.h" #ifndef NO_FFI_SUPPORT #include extern struct VirtualMachine *interpreterProxy; #define primitiveFail() interpreterProxy->primitiveFail() #if 1 #define HAVE_LONGLONG #endif /* Check if HAVE_LONGLONG is defined (should be figured out by configure */ #ifdef HAVE_LONGLONG #define HAS_LONGLONG 1 #define LONGLONG long long #endif /* Error if LONGLONG is not defined */ #if HAS_LONGLONG #ifndef LONGLONG #error "You must define LONGLONG if HAS_LONGLONG is defined" #endif #endif /* Max number of arguments in call */ #define FFI_MAX_ARGS 32 static ffi_type* ffiTypes[FFI_MAX_ARGS]; static void* ffiArgs[FFI_MAX_ARGS]; static char ffiBytes[FFI_MAX_ARGS]; static short ffiShorts[FFI_MAX_ARGS]; static int ffiInts[FFI_MAX_ARGS]; static float ffiFloats[FFI_MAX_ARGS]; static double ffiDoubles[FFI_MAX_ARGS]; static int ffiArgIndex = 0; static ffi_type* ffiStructTypes[FFI_MAX_ARGS]; static int ffiStructIndex = 0; /* helpers */ #define CHECK_ARGS() if(ffiArgIndex >= FFI_MAX_ARGS) return primitiveFail(); #define PUSH_TYPE(type) { CHECK_ARGS(); ffiTypes[ffiArgIndex] = &type; } #define PUSH(where, what, type) { \ PUSH_TYPE(type); where[ffiArgIndex] = what; \ ffiArgs[ffiArgIndex] = (void*) (where + ffiArgIndex); \ ffiArgIndex++;\ } #define BARG_PUSH(value, type) PUSH(ffiBytes, value, type) #define SARG_PUSH(value, type) PUSH(ffiShorts, value, type) #define IARG_PUSH(value, type) PUSH(ffiInts, value, type) #define FARG_PUSH(value) PUSH(ffiFloats, value, ffi_type_float) #define DARG_PUSH(value) PUSH(ffiDoubles, value, ffi_type_double) #if HAS_LONGLONG static LONGLONG ffiLongLongs[FFI_MAX_ARGS]; #define LARG_PUSH(value, type) PUSH(ffiLongLongs, value, type) #endif /* The 64bit return value storage area - aligned by the C compiler */ static double returnValue; /* Storage area for large structure returns */ static ffi_type* structReturnType = NULL; static void *structReturnValue = NULL; /* The area for temporarily allocated strings */ static char *ffiTempStrings[FFI_MAX_ARGS]; /* The number of temporarily allocated strings */ static int ffiTempStringCount = 0; /*****************************************************************************/ /*****************************************************************************/ /* ffiInitialize: Announce that the VM is about to do an external function call. */ int ffiInitialize(void) { ffiArgIndex = 0; ffiTempStringCount = 0; ffiStructIndex = 0; structReturnType = NULL; structReturnValue = NULL; return 1; } /* ffiSupportsCallingConvention: Return true if the support code supports the given calling convention */ int ffiSupportsCallingConvention(int callType) { if(callType == FFICallTypeCDecl) return 1; return 0; } /* ffiAlloc: Allocate space from the external heap */ int ffiAlloc(int byteSize) { return (int)malloc(byteSize); } /* ffiFree: Free space from the external heap */ int ffiFree(int pointer) { if(pointer) free((void*)pointer); return 1; } /*****************************************************************************/ /*****************************************************************************/ int ffiPushSignedByte(int value) { BARG_PUSH((char)value, ffi_type_sint8); return 1; } int ffiPushUnsignedByte(int value) { BARG_PUSH((char)value, ffi_type_uint8); return 1; } int ffiPushSignedShort(int value) { SARG_PUSH((short)value, ffi_type_sint16); return 1; } int ffiPushUnsignedShort(int value) { SARG_PUSH((short)value, ffi_type_uint16); return 1; } int ffiPushSignedInt(int value) { IARG_PUSH(value, ffi_type_sint32); return 1; } int ffiPushUnsignedInt(int value) { IARG_PUSH(value, ffi_type_uint32); return 1; } int ffiPushSignedLongLong(int low, int high) { #if HAS_LONGLONG LONGLONG value = (((LONGLONG) high) << 32) | ((LONGLONG) (unsigned) low); LARG_PUSH(value, ffi_type_sint64); return 1; #else return primitiveFail(); #endif } int ffiPushUnsignedLongLong(int low, int high) { #if HAS_LONGLONG LONGLONG value = (((LONGLONG) high) << 32) | ((LONGLONG) (unsigned) low); LARG_PUSH(value, ffi_type_uint64); return 1; #else return primitiveFail(); #endif } int ffiPushSignedChar(int value) { BARG_PUSH(value, ffi_type_sint8); return 1; } int ffiPushUnsignedChar(int value) { BARG_PUSH(value, ffi_type_uint8); return 1; } int ffiPushBool(int value) { IARG_PUSH(value, ffi_type_uint8); return 1; } int ffiPushSingleFloat(double value) { FARG_PUSH((float)value); return 1; } int ffiPushDoubleFloat(double value) { DARG_PUSH(value); return 1; } ffi_type* ffiCreateType(int *structSpec, int structSize) { ffi_type *structType, **newTypes; int nTypes, i, typeSpec; /* count the number of atomic types we need to create */ nTypes = 0; for(i=0; isize = (*structSpec) & FFIStructSizeMask; structType->alignment = 4; structType->type = FFI_TYPE_STRUCT; structType->elements = newTypes; /* now go over the structure and fill in the fields */ nTypes = 0; for(i=0; i> FFIAtomicTypeShift) { case FFITypeBool: newTypes[nTypes++] = &ffi_type_uint8; break; case FFITypeUnsignedByte: newTypes[nTypes++] = &ffi_type_uint8; break; case FFITypeSignedByte: newTypes[nTypes++] = &ffi_type_sint8; break; case FFITypeUnsignedShort: newTypes[nTypes++] = &ffi_type_uint16; break; case FFITypeSignedShort: newTypes[nTypes++] = &ffi_type_sint16; break; case FFITypeUnsignedInt: newTypes[nTypes++] = &ffi_type_uint32; break; case FFITypeSignedInt: newTypes[nTypes++] = &ffi_type_sint32; break; case FFITypeUnsignedLongLong: newTypes[nTypes++] = &ffi_type_uint64; break; case FFITypeSignedLongLong: newTypes[nTypes++] = &ffi_type_sint64; break; case FFITypeUnsignedChar: newTypes[nTypes++] = &ffi_type_uint8; break; case FFITypeSignedChar: newTypes[nTypes++] = &ffi_type_sint8; break; case FFITypeSingleFloat: newTypes[nTypes++] = &ffi_type_float; break; case FFITypeDoubleFloat: newTypes[nTypes++] = &ffi_type_double; break; default: printf("Warning: unknown atomic type (%x) in ffiCreateTypes\n", typeSpec); free(newTypes); free(structType); return NULL; }; } newTypes[nTypes++] = NULL; return structType; } int ffiPushStructureOfLength(int pointer, int* structSpec, int structSize) { ffi_type *structType; if(pointer == 0) return primitiveFail(); CHECK_ARGS(); /* fail early on */ structType = ffiCreateType(structSpec, structSize); if(structType == NULL) return primitiveFail(); ffiStructTypes[ffiStructIndex++] = structType; ffiTypes[ffiArgIndex] = structType; ffiArgs[ffiArgIndex] = (void*) pointer; ffiArgIndex++; return 1; } int ffiPushPointer(int pointer) { IARG_PUSH(pointer, ffi_type_pointer); return 1; } int ffiPushStringOfLength(int srcIndex, int length) { char *ptr; ptr = (char*) malloc(length+1); if(!!ptr) return primitiveFail(); memcpy(ptr, (void*)srcIndex, length); ptr[length] = 0; ffiTempStrings[ffiTempStringCount++] = ptr; IARG_PUSH((int)ptr, ffi_type_pointer); return 1; } /*****************************************************************************/ /*****************************************************************************/ /* ffiCanReturn: Return true if the support code can return the given type. */ int ffiCanReturn(int *structSpec, int specSize) { int header = *structSpec; if(header & FFIFlagPointer) return 1; if(header & FFIFlagStructure) { int structSize = header & FFIStructSizeMask; structReturnType = ffiCreateType(structSpec, specSize); if(!!structReturnType) return 0; if(structSize > 8) { structReturnValue = calloc(1,structSize); if(!!structReturnValue) return 0; return 1; } } return 1; } /* ffiReturnFloatValue: Return the value from a previous ffi call with float return type. */ double ffiReturnFloatValue(void) { return returnValue; } /* ffiLongLongResultLow: Return the low 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultLow(void) { #if HAS_LONGLONG return (int) ( (*(LONGLONG*)&returnValue) & (LONGLONG)0xFFFFFFFFU); #else return 0; #endif } /* ffiLongLongResultHigh: Return the high 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultHigh(void) { #if HAS_LONGLONG return (int) ( (*(LONGLONG*)&returnValue) >> 32); #else return 0; #endif } /* ffiStoreStructure: Store the structure result of a previous ffi call into the given address*/ int ffiStoreStructure(int address, int structSize) { if(structReturnValue) { memcpy((void*)address, (void*)structReturnValue, structSize); } else { memcpy((void*)address, (void*)&returnValue, structSize); } return 1; } /* ffiCleanup: Cleanup after a foreign function call has completed. The generic support code only frees the temporarily allocated strings. */ int ffiCleanup(void) { int i; for(i=0; ielements); free(ffiStructTypes[i]); ffiStructTypes[i]=NULL; } if(structReturnType) { free(structReturnType->elements); free(structReturnType); structReturnType = NULL; } if(structReturnValue) { free(structReturnValue); structReturnValue = NULL; } ffiTempStringCount = 0; ffiStructIndex = 0; return 1; } /*****************************************************************************/ /*****************************************************************************/ int ffiCallAddress(int fn, ffi_type *returnType, int atomicArgType) { ffi_cif cif; ffi_status result; int retVal; result = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, ffiArgIndex, returnType, ffiTypes); if(result !!= FFI_OK) return primitiveFail(); if(structReturnValue) { ffi_call(&cif, (void *)fn, (void *)structReturnValue, (void **)ffiArgs); return (int) structReturnValue; } ffi_call(&cif, (void *)fn, (void *)&returnValue, (void **)ffiArgs); retVal = *(int*)&returnValue; #ifdef FFI_MIPS_N32 /* Note: MIPS N32 ABI returns 64bit for integer/pointer whatever. This seems to be a bug in the fficall implementation. */ retVal = ((int*)(&returnValue))[1]; #endif /* Promote certain return types to integral size */ switch(atomicArgType) { case FFITypeUnsignedChar: case FFITypeUnsignedByte: retVal = *(unsigned char*) &retVal; break; case FFITypeSignedChar: case FFITypeSignedByte: retVal = *(signed char*) &retVal; break; case FFITypeUnsignedShort: retVal = *(unsigned short*) &retVal; break; case FFITypeSignedShort: retVal = *(signed short*) &retVal; break; case FFITypeSingleFloat: returnValue = *(float*)&returnValue; break; } return retVal; } int ffiCallAddressOfWithPointerReturn(int fn, int callType) { return ffiCallAddress(fn, &ffi_type_pointer,-1); } int ffiCallAddressOfWithStructReturn(int fn, int callType, int *structSpec, int specSize) { if(!!structReturnType) return primitiveFail(); return ffiCallAddress(fn, structReturnType,-1); } int ffiCallAddressOfWithReturnType(int fn, int callType, int typeSpec) { ffi_type *returnType; int atomicType; atomicType = (typeSpec & FFIAtomicTypeMask) >> FFIAtomicTypeShift; switch(atomicType) { case FFITypeVoid: returnType = &ffi_type_void; break; case FFITypeBool: returnType = &ffi_type_uint8; break; case FFITypeUnsignedByte: returnType = &ffi_type_uint8; break; case FFITypeSignedByte: returnType = &ffi_type_sint8; break; case FFITypeUnsignedShort: returnType = &ffi_type_uint16; break; case FFITypeSignedShort: returnType = &ffi_type_sint16; break; case FFITypeUnsignedInt: returnType = &ffi_type_uint32; break; case FFITypeSignedInt: returnType = &ffi_type_sint32; break; case FFITypeUnsignedLongLong: returnType = &ffi_type_uint64; break; case FFITypeSignedLongLong: returnType = &ffi_type_sint64; break; case FFITypeUnsignedChar: returnType = &ffi_type_uint8; break; case FFITypeSignedChar: returnType = &ffi_type_sint8; break; case FFITypeSingleFloat: returnType = &ffi_type_float; break; case FFITypeDoubleFloat: returnType = &ffi_type_double; break; default: return primitiveFail(); } return ffiCallAddress(fn, returnType, atomicType); } #endif /* NO_FFI_SUPPORT */ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /************ Test functions for the foreign function interface **************/ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ #ifndef NO_FFI_TEST typedef struct ffiTestPoint2 { int x; int y; } ffiTestPoint2; typedef struct ffiTestPoint4 { int x; int y; int z; int w; } ffiTestPoint4; typedef struct ffiTestPointMix { int x; double y; int z; double w; } ffiTestPointMix; #pragma export on EXPORT(char) ffiTestChars(char c1, char c2, char c3, char c4); EXPORT(short) ffiTestShorts(short c1, short c2, short c3, short c4); EXPORT(int) ffiTestInts(int c1, int c2, int c3, int c4); EXPORT(float) ffiTestFloats(float f1, float f2); EXPORT(double) ffiTestDoubles(double d1, double d2); EXPORT(char *) ffiPrintString(char *string); EXPORT(ffiTestPoint2) ffiTestStruct64(ffiTestPoint2 pt1, ffiTestPoint2 pt2); EXPORT(ffiTestPoint4) ffiTestStructBig(ffiTestPoint4 pt1, ffiTestPoint4 pt2); EXPORT(ffiTestPoint4*) ffiTestPointers(ffiTestPoint4 *pt1, ffiTestPoint4 *pt2); EXPORT(ffiTestPointMix) ffiTestStructMix(ffiTestPointMix pt1, ffiTestPointMix pt2); EXPORT(LONGLONG) ffiTestLongLong(LONGLONG i1, LONGLONG i2); #pragma export off /* test passing characters */ EXPORT(char) ffiTestChars(char c1, char c2, char c3, char c4) { printf("4 characters came in as\nc1 = %c (%x)\nc2 = %c (%x)\nc3 = %c (%x)\nc4 = %c (%x)\n", c1, c1, c2, c2, c3, c3, c4, c4); return ''C''; } /* test passing shorts */ EXPORT(short) ffiTestShorts(short c1, short c2, short c3, short c4) { printf("4 shorts came in as\ns1 = %d (%x)\ns2 = %d (%x)\ns3 = %d (%x)\ns4 = %d (%x)\n", c1, c1, c2, c2, c3, c3, c4, c4); return -42; } /* test passing ints */ EXPORT(int) ffiTestInts(int c1, int c2, int c3, int c4) { printf("4 ints came in as\ni1 = %d (%x)\ni2 = %d (%x)\ni3 = %d (%x)\ni4 = %d (%x)\n", c1, c1, c2, c2, c3, c3, c4, c4); return 42; } /* test passing and returning floats */ EXPORT(float) ffiTestFloats(float f1, float f2) { printf("The two floats are %f and %f\n", f1, f2); return (float) (f1 + f2); } /* test passing and returning doubles */ EXPORT(double) ffiTestDoubles(double d1, double d2) { printf("The two floats are %f and %f\n", (float)d1, (float)d2); return d1+d2; } /* test passing and returning strings */ EXPORT(char*) ffiPrintString(char *string) { printf("%s\n", string); return string; } /* test passing and returning 64bit structures */ EXPORT(ffiTestPoint2) ffiTestStruct64(ffiTestPoint2 pt1, ffiTestPoint2 pt2) { ffiTestPoint2 result; printf("pt1.x = %d\npt1.y = %d\npt2.x = %d\npt2.y = %d\n", pt1.x, pt1.y, pt2.x, pt2.y); result.x = pt1.x + pt2.x; result.y = pt1.y + pt2.y; return result; } /* test passing and returning large structures */ EXPORT(ffiTestPoint4) ffiTestStructBig(ffiTestPoint4 pt1, ffiTestPoint4 pt2) { ffiTestPoint4 result; printf("pt1.x = %d\npt1.y = %d\npt1.z = %d\npt1.w = %d\n", pt1.x, pt1.y, pt1.z, pt1.w); printf("pt2.x = %d\npt2.y = %d\npt2.z = %d\npt2.w = %d\n", pt2.x, pt2.y, pt2.z, pt2.w); result.x = pt1.x + pt2.x; result.y = pt1.y + pt2.y; result.z = pt1.z + pt2.z; result.w = pt1.w + pt2.w; return result; } /* test passing and returning pointers */ EXPORT(ffiTestPoint4*) ffiTestPointers(ffiTestPoint4 *pt1, ffiTestPoint4 *pt2) { ffiTestPoint4 *result; printf("pt1.x = %d\npt1.y = %d\npt1.z = %d\npt1.w = %d\n", pt1->x, pt1->y, pt1->z, pt1->w); printf("pt2.x = %d\npt2.y = %d\npt2.z = %d\npt2.w = %d\n", pt2->x, pt2->y, pt2->z, pt2->w); result = (ffiTestPoint4*) malloc(sizeof(ffiTestPoint4)); result->x = pt1->x + pt2->x; result->y = pt1->y + pt2->y; result->z = pt1->z + pt2->z; result->w = pt1->w + pt2->w; return result; } /* test passing and returning longlongs */ EXPORT(LONGLONG) ffiTestLongLong(LONGLONG i1, LONGLONG i2) { return i1 + i2; } #endif /* NO_FFI_TEST */ '! ! !FFIPlugin class methodsFor: 'C support code' stamp: 'ar 5/14/2000 20:25'! sqWin32FFIFile ^'/**************************************************************************** * PROJECT: Squeak foreign function interface * FILE: sqWin32FFI.c * CONTENT: Win32 support for the foreign function interface * * AUTHOR: Andreas Raab (ar) * ADDRESS: Walt Disney Imagineering, Glendale, CA * EMAIL: andreasr@wdi.disney.com * RCSID: $Id$ * * NOTES: * *****************************************************************************/ #include "sq.h" #include "sqFFI.h" extern struct VirtualMachine *interpreterProxy; #define primitiveFail() interpreterProxy->primitiveFail(); #ifdef _MSC_VER #define LONGLONG __int64 #endif #ifdef __GNUC__ #define LONGLONG long long int #endif /* Max stack size */ #define FFI_MAX_ARGS 128 /* The stack used to assemble the arguments for a call */ static int ffiArgs[FFI_MAX_ARGS]; /* The stack pointer while filling the stack */ static int ffiArgIndex = 0; /* The area for temporarily allocated strings */ static char *ffiTempStrings[FFI_MAX_ARGS]; /* The number of temporarily allocated strings */ static int ffiTempStringCount = 0; /* The return values for calls */ volatile static int intReturnValue; volatile static int intReturnValue2; volatile static double floatReturnValue; static void* structReturnValue; #define ARG_CHECK() if(ffiArgIndex >= FFI_MAX_ARGS) return primitiveFail(); #define ARG_PUSH(value) { ARG_CHECK(); ffiArgs[ffiArgIndex++] = value; } /*****************************************************************************/ /*****************************************************************************/ /* ffiInitialize: Announce that the VM is about to do an external function call. */ int ffiInitialize(void) { ffiArgIndex = 0; ffiTempStringCount = 0; return 1; } /* ffiSupportsCallingConvention: Return true if the support code supports the given calling convention. */ int ffiSupportsCallingConvention(int callType) { if(callType == FFICallTypeCDecl) return 1; if(callType == FFICallTypeApi) return 1; return 0; } int ffiAlloc(int byteSize) { return (int) malloc(byteSize); } int ffiFree(int ptr) { if(ptr) free((void*)ptr); return 1; } /*****************************************************************************/ /*****************************************************************************/ int ffiPushSignedChar(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedChar(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedByte(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedByte(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedShort(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedShort(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedInt(int value) { ARG_PUSH(value); return 1; } int ffiPushUnsignedInt(int value) { ARG_PUSH(value); return 1; } int ffiPushSignedLongLong(int lowWord, int highWord) { ARG_PUSH(lowWord); ARG_PUSH(highWord); return 1; } int ffiPushUnsignedLongLong(int lowWord, int highWord) { ARG_PUSH(lowWord); ARG_PUSH(highWord); return 1; } int ffiPushSingleFloat(double value) { float floatValue; floatValue = (float) value; ARG_PUSH(*(int*)(&floatValue)); return 1; } int ffiPushDoubleFloat(double value) { ARG_PUSH(((int*)(&value))[0]); ARG_PUSH(((int*)(&value))[1]); return 1; } int ffiPushStructureOfLength(int pointer, int* structSpec, int structSize) { int nItems, i; nItems = ((*structSpec & FFIStructSizeMask) + 3) / 4; if(pointer == 0) return primitiveFail(); for(i=0; i < nItems;i++) ARG_PUSH(((int*)pointer)[i]); return 1; } int ffiPushPointer(int pointer) { ARG_PUSH(pointer); return 1; } int ffiPushStringOfLength(int srcIndex, int length) { char *ptr; ARG_CHECK(); /* fail before allocating */ ptr = (char*) malloc(length+1); if(!!ptr) return primitiveFail(); memcpy(ptr, (void*)srcIndex, length); ptr[length] = 0; ffiTempStrings[ffiTempStringCount++] = ptr; ARG_PUSH((int)ptr); return 1; } /*****************************************************************************/ /*****************************************************************************/ /* ffiCanReturn: Return true if the support code can return the given type. */ int ffiCanReturn(int *structSpec, int specSize) { int header = *structSpec; if(header & FFIFlagPointer) return 1; if(header & FFIFlagStructure) { int structSize = header & FFIStructSizeMask; if(structSize > 8) { structReturnValue = malloc(structSize); if(!!structReturnValue) return 0; ARG_PUSH((int)structReturnValue); } } return 1; } /* ffiReturnFloatValue: Return the value from a previous ffi call with float return type. */ double ffiReturnFloatValue(void) { return floatReturnValue; } /* ffiLongLongResultLow: Return the low 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultLow(void) { return intReturnValue; } /* ffiLongLongResultHigh: Return the high 32bit from the 64bit result of a call to an external function */ int ffiLongLongResultHigh(void) { return intReturnValue2; } /* ffiStoreStructure: Store the structure result of a previous ffi call into the given address. Note: Since the ST allocator always allocates multiples of 32bit we can use the atomic types for storing <= 64bit result structures. */ int ffiStoreStructure(int address, int structSize) { if(structSize <= 4) { *(int*)address = intReturnValue; return 1; } if(structSize <= 8) { *(int*)address = intReturnValue; *(int*)(address+4) = intReturnValue2; return 1; } /* assume pointer to hidden structure */ memcpy((void*)address, (void*) structReturnValue, structSize); return 1; } /* ffiCleanup: Cleanup after a foreign function call has completed. */ int ffiCleanup(void) { int i; for(i=0; ix, pt1->y, pt1->z, pt1->w); printf("pt2.x = %d\npt2.y = %d\npt2.z = %d\npt2.w = %d\n", pt2->x, pt2->y, pt2->z, pt2->w); result = (ffiTestPoint4*) malloc(sizeof(ffiTestPoint4)); result->x = pt1->x + pt2->x; result->y = pt1->y + pt2->y; result->z = pt1->z + pt2->z; result->w = pt1->w + pt2->w; return result; } /* test passing and returning longlongs */ EXPORT(LONGLONG) ffiTestLongLong(LONGLONG i1, LONGLONG i2) { return i1 + i2; } #endif /* NO_FFI_TEST */ '! ! !FFIPlugin class methodsFor: 'C support code' stamp: 'ar 1/26/2000 15:12'! writeSupportFiles "FFIPlugin writeSupportFiles" InterpreterSupportCode storeString: self sqFFIHeaderFile onFileNamed:'sqFFI.h'; storeString: self sqMacFFIPPCFile onFileNamed:'sqMacFFIPPC.c'; storeString: self sqUnixFFIFile onFileNamed:'sqUnixFFI.c'; storeString: self sqWin32FFIFile onFileNamed:'sqWin32FFI.c'.! ! ExternalStructure subclass: #FFITestPoint2 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FFI-Plugin'! !FFITestPoint2 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:42'! x "This method was automatically generated" ^handle signedLongAt: 1! ! !FFITestPoint2 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:42'! x: anObject "This method was automatically generated" handle signedLongAt: 1 put: anObject! ! !FFITestPoint2 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:42'! y "This method was automatically generated" ^handle signedLongAt: 5! ! !FFITestPoint2 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:42'! y: anObject "This method was automatically generated" handle signedLongAt: 5 put: anObject! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFITestPoint2 class instanceVariableNames: ''! !FFITestPoint2 class methodsFor: 'field definition' stamp: 'ar 12/1/1999 16:42'! fields "FFITestPoint2 defineFields" ^#( (x 'long') (y 'long') )! ! ExternalStructure subclass: #FFITestPoint4 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FFI-Plugin'! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! w "This method was automatically generated" ^handle signedLongAt: 13! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! w: anObject "This method was automatically generated" handle signedLongAt: 13 put: anObject! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! x "This method was automatically generated" ^handle signedLongAt: 1! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! x: anObject "This method was automatically generated" handle signedLongAt: 1 put: anObject! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! y "This method was automatically generated" ^handle signedLongAt: 5! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! y: anObject "This method was automatically generated" handle signedLongAt: 5 put: anObject! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! z "This method was automatically generated" ^handle signedLongAt: 9! ! !FFITestPoint4 methodsFor: 'accessing' stamp: 'ar 12/1/1999 16:41'! z: anObject "This method was automatically generated" handle signedLongAt: 9 put: anObject! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFITestPoint4 class instanceVariableNames: ''! !FFITestPoint4 class methodsFor: 'field definition' stamp: 'ar 12/1/1999 16:41'! fields "FFITestPoint4 defineFields" ^#( (x 'long') (y 'long') (z 'long') (w 'long') )! ! ExternalLibrary subclass: #FFITester instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FFI-Plugin'! !FFITester methodsFor: 'primitives' stamp: 'ar 11/19/1999 20:37'! ffiPrintString: aString "FFITester ffiPrintString: 'Hello'" ^self externalCallFailed! ! !FFITester methodsFor: 'primitives' stamp: 'ar 11/20/1999 23:41'! ffiTestChar: c1 with: c2 with: c3 with: c4 "FFITester ffiTestChar: $A with: 65 with: 65.0 with: true" ^self externalCallFailed! ! !FFITester methodsFor: 'primitives' stamp: 'ar 11/19/1999 21:30'! ffiTestDoubles: f1 with: f2 "FFITester ffiTestDoubles: $A with: 65.0" ^self externalCallFailed! ! !FFITester methodsFor: 'primitives' stamp: 'ar 11/19/1999 20:31'! ffiTestFloats: f1 with: f2 "FFITester ffiTestFloats: $A with: 65.0" ^self externalCallFailed! ! !FFITester methodsFor: 'primitives' stamp: 'ar 11/19/1999 20:31'! ffiTestInt: c1 with: c2 with: c3 with: c4 "FFITester ffiTestInt: $A with: 65 with: 65.0 with: $A" ^self externalCallFailed! ! !FFITester methodsFor: 'primitives' stamp: 'ar 11/19/1999 20:31'! ffiTestShort: c1 with: c2 with: c3 with: c4 "FFITester ffiTestShort: $A with: 65 with: 65.0 with: $A" ^self externalCallFailed! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFITester class instanceVariableNames: ''! !FFITester class methodsFor: 'public' stamp: 'ar 11/28/1999 20:13'! testAll "FFITester testAll" "Run all the tests" "Pass 1: Run all the methods in the class and see if it works" "Pass 2: Run all the methods in an instance (ExternalLibrary) and see if it works" "Pass 3: Run all the methods directly invoked from an ExternalMethod" | rcvr value meth module | 1 to: 2 do:[:i| i = 1 ifTrue:[rcvr _ self] ifFalse:[rcvr _ self new]. "Test argument coercion and passing of arguments of different sizes" rcvr ffiTestChar: $A with: 65 with: 65.0 with: true. rcvr ffiTestShort: $A with: 65 with: 65.0 with: true. rcvr ffiTestInt: $A with: 65 with: 65.0 with: true. value _ rcvr ffiTestFloats: $A with: 65.0. value rounded = 130 ifFalse:[self error:'single floats don''t work']. value _ rcvr ffiTestDoubles: 41 with: true. value = 42.0 ifFalse:[self error:'problem with doubles']. value _ rcvr ffiPrintString:'Hello World!!'. value = 'Hello World!!' ifFalse:[self error:'Problem with strings']. ]. module _ self moduleName. meth _ ExternalLibraryFunction name:'ffiTestChars' module: module callType: 0 returnType: ExternalType char argumentTypes: ((1 to: 4) collect:[:i| ExternalType char]). meth invokeWith: $A with: 65 with: 65.0 with: true. meth _ ExternalLibraryFunction name:'ffiTestShorts' module: module callType: 0 returnType: ExternalType short argumentTypes: ((1 to: 4) collect:[:i| ExternalType short]). meth invokeWithArguments: (Array with: $A with: 65 with: 65.0 with: true). meth _ ExternalLibraryFunction name:'ffiTestInts' module: module callType: 0 returnType: ExternalType long argumentTypes: ((1 to: 4) collect:[:i| ExternalType long]). meth invokeWith: $A with: 65 with: 65.0 with: true. meth _ ExternalLibraryFunction name:'ffiTestFloats' module: module callType: 0 returnType: ExternalType float argumentTypes: ((1 to: 2) collect:[:i| ExternalType float]). value _ meth invokeWith: $A with: 65.0. value rounded = 130 ifFalse:[self error:'single floats don''t work']. meth _ ExternalLibraryFunction name:'ffiTestDoubles' module: module callType: 0 returnType: ExternalType double argumentTypes: ((1 to: 2) collect:[:i| ExternalType double]). value _ meth invokeWithArguments: (Array with: 41 with: true). value = 42.0 ifFalse:[self error:'problem with doubles']. meth _ ExternalLibraryFunction name:'ffiPrintString' module: module callType: 0 returnType: ExternalType string argumentTypes: ((1 to: 1) collect:[:i| ExternalType string]). value _ meth invokeWith:'Hello World!!'. value = 'Hello World!!' ifFalse:[self error:'Problem with strings']. ! ! !FFITester class methodsFor: 'public' stamp: 'ar 11/22/1999 05:02'! testLongLongs "FFITester testLongLongs" "Test passing and returning longlongs" | long1 long2 long3 | long1 _ 16r123456789012. long2 _ (-1 << 31). long3 _ self ffiTestLongLong: long1 with: long2. long3 = (long1 + long2) ifFalse:[self error:'Problem passing/returning longlongs']. ^long3! ! !FFITester class methodsFor: 'public' stamp: 'ar 11/22/1999 05:03'! testPoint2 "FFITester testPoint2" "Test passing and returning up of structures >32bit and <= 64 bit" | pt1 pt2 pt3 | pt1 _ FFITestPoint2 new. pt1 x: 1. pt1 y: 2. pt2 _ FFITestPoint2 new. pt2 x: 3. pt2 y: 4. pt3 _ self ffiTestPoint2: pt1 with: pt2. (pt3 x = 4 and:[ pt3 y = 6]) ifFalse:[self error:'Problem passing 64bit structures']. ^pt3! ! !FFITester class methodsFor: 'public' stamp: 'ar 11/22/1999 05:03'! testPoint4 "FFITester testPoint4" "Test passing and returning up of structures > 64 bit" | pt1 pt2 pt3 | pt1 _ FFITestPoint4 new. pt1 x: 1. pt1 y: 2. pt1 z: 3. pt1 w: 4. pt2 _ FFITestPoint4 new. pt2 x: 5. pt2 y: 6. pt2 z: 7. pt2 w: 8. pt3 _ self ffiTestPoint4: pt1 with: pt2. (pt3 x = 6 and:[ pt3 y = 8 and:[pt3 z = 10 and:[pt3 w = 12]]]) ifFalse:[self error:'Problem passing large structures']. ^pt3! ! !FFITester class methodsFor: 'public' stamp: 'ar 12/1/1999 16:39'! testPointers "FFITester testPointers" "Test passing and returning of pointers to structs" | pt1 pt2 pt3 | pt1 _ FFITestPoint4 new. pt1 x: 1. pt1 y: 2. pt1 z: 3. pt1 w: 4. pt2 _ FFITestPoint4 new. pt2 x: 5. pt2 y: 6. pt2 z: 7. pt2 w: 8. pt3 _ self ffiTestPointers: pt1 with: pt2. (pt3 x = 6 and:[ pt3 y = 8 and:[pt3 z = 10 and:[pt3 w = 12]]]) ifFalse:[self error:'Problem passing large structures']. ^pt3! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 1/27/2000 01:21'! ffiPrintString: aString "FFITester ffiPrintString: 'Hello'" ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestChar: c1 with: c2 with: c3 with: c4 "FFITester ffiTestChar: $A with: 65 with: 65.0 with: true" ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestDoubles: f1 with: f2 "FFITester ffiTestDoubles: $A with: 65.0" ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestFloats: f1 with: f2 "FFITester ffiTestFloats: $A with: 65.0" ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/29/1999 14:49'! ffiTestInt: c1 with: c2 with: c3 with: c4 "FFITester ffiTestInt: $A with: 65 with: 65.0 with: true" ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestLongLong: long1 with: long2 ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestPoint2: pt1 with: pt2 ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/28/1999 19:51'! ffiTestPoint4: pt1 with: pt2 ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 12/1/1999 16:39'! ffiTestPointers: pt1 with: pt2 ^self externalCallFailed! ! !FFITester class methodsFor: 'primitives' stamp: 'ar 11/29/1999 14:16'! ffiTestShort: c1 with: c2 with: c3 with: c4 "FFITester ffiTestShort: $A with: 65 with: 65.0 with:1" ^self externalCallFailed! ! !FFITester class methodsFor: 'accessing' stamp: 'ar 11/28/1999 19:51'! moduleName "Use the fully qualified VM name so we ensure testing loading a library" ^'SqueakFFIPrims'! ! Object subclass: #FFT instanceVariableNames: 'nu n sinTable permTable realData imagData window ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !FFT commentStamp: '' prior: 0! This class implements the Fast Fourier Transform roughly as described on page 367 of "Theory and Application of Digital Signal Processing" by Rabiner and Gold. Each instance caches tables used for transforming a given size (n = 2^nu samples) of data. It would have been cleaner using complex numbers, but often the data is all real.! !FFT methodsFor: 'initialization' stamp: 'jm 8/25/1999 21:59'! n ^ n ! ! !FFT methodsFor: 'initialization' stamp: 'di 6/17/97 07:47'! nu: order "Initialize variables and tables for transforming 2^nu points" | j perms k | nu _ order. n _ 2 bitShift: nu-1. "Initialize permutation table (bit-reversed indices)" j_0. perms _ WriteStream on: (Array new: n). 0 to: n-2 do: [:i | i < j ifTrue: [perms nextPut: i+1; nextPut: j+1]. k _ n // 2. [k <= j] whileTrue: [j _ j-k. k _ k//2]. j _ j + k]. permTable _ perms contents. "Initialize sin table 0..pi/2 in n/4 steps." sinTable _ (0 to: n/4) collect: [:i | (i asFloat / (n//4) * Float pi / 2.0) sin]! ! !FFT methodsFor: 'initialization' stamp: 'di 6/17/97 07:47'! realData: real realData _ real. imagData _ real collect: [:i | 0.0] "imaginary component all zero"! ! !FFT methodsFor: 'initialization' stamp: 'di 6/17/97 07:47'! realData: real imagData: imag realData _ real. imagData _ imag! ! !FFT methodsFor: 'transforming' stamp: 'di 6/17/97 07:47'! permuteData | i end a b | i _ 1. end _ permTable size. [i <= end] whileTrue: [a _ permTable at: i. b _ permTable at: i+1. realData swap: a with: b. imagData swap: a with: b. i _ i + 2]! ! !FFT methodsFor: 'transforming' stamp: 'di 6/17/97 07:47'! scaleData "Scale all elements by 1/n when doing inverse" | realN | realN _ n asFloat. 1 to: n do: [:i | realData at: i put: (realData at: i) / realN. imagData at: i put: (imagData at: i) / realN]! ! !FFT methodsFor: 'transforming' stamp: 'di 6/17/97 07:47'! transformForward: forward | lev lev1 ip theta realU imagU realT imagT i | self permuteData. 1 to: nu do: [:level | lev _ 1 bitShift: level. lev1 _ lev // 2. 1 to: lev1 do: [:j | theta _ j-1 * (n // lev). "pi * (j-1) / lev1 mapped onto 0..n/2" theta < (n//4) "Compute U, the complex multiplier for each level" ifTrue: [realU _ sinTable at: sinTable size - theta. imagU _ sinTable at: theta + 1] ifFalse: [realU _ (sinTable at: theta - (n//4) + 1) negated. imagU _ sinTable at: (n//2) - theta + 1]. forward ifFalse: [imagU _ imagU negated]. " Here is the inner loop... j to: n by: lev do: [:i | hand-transformed to whileTrue... " i _ j. [i <= n] whileTrue: [ip _ i + lev1. realT _ ((realData at: ip) * realU) - ((imagData at: ip) * imagU). imagT _ ((realData at: ip) * imagU) + ((imagData at: ip) * realU). realData at: ip put: (realData at: i) - realT. imagData at: ip put: (imagData at: i) - imagT. realData at: i put: (realData at: i) + realT. imagData at: i put: (imagData at: i) + imagT. i _ i + lev]]]. forward ifFalse: [self scaleData] "Reverse transform must scale to be an inverse"! ! !FFT methodsFor: 'testing' stamp: 'jm 8/1/1998 13:08'! imagData ^ imagData ! ! !FFT methodsFor: 'testing' stamp: 'di 6/17/97 07:47'! plot: samples in: rect "Throw-away code just to check out a couple of examples" | min max x dx pen y | Display fillWhite: rect; border: (rect expandBy: 2) width: 2. min _ 1.0e30. max _ -1.0e30. samples do: [:v | min _ min min: v. max _ max max: v]. pen _ Pen new. pen up. x _ rect left. dx _ rect width asFloat / samples size. samples do: [:v | y _ (max-v) / (max-min) * rect height asFloat. pen goto: x asInteger @ (rect top + y asInteger). pen down. x _ x + dx]. max printString displayOn: Display at: (x+2) @ (rect top-9). min printString displayOn: Display at: (x+2) @ (rect bottom - 9)! ! !FFT methodsFor: 'testing' stamp: 'jm 8/1/1998 13:08'! realData ^ realData ! ! !FFT methodsFor: 'testing' stamp: 'jm 8/16/1998 17:36'! samplesPerCycleForIndex: i "Answer the number of samples per cycle corresponding to a power peak at the given index. Answer zero if i = 1, since an index of 1 corresponds to the D.C. component." | windowSize | windowSize _ 2 raisedTo: nu. (i < 1 or: [i > (windowSize // 2)]) ifTrue: [^ self error: 'index is out of range']. i = 1 ifTrue: [^ 0]. "the D.C. component" ^ windowSize asFloat / (i - 1) ! ! !FFT methodsFor: 'testing' stamp: 'di 6/17/97 07:47'! test "Display restoreAfter: [(FFT new nu: 8) test]. -- Test on an array of 256 samples" "Initialize to pure (co)Sine Wave, plot, transform, plot, invert and plot again" self realData: ((1 to: n) collect: [:i | (Float pi * (i-1) / (n/8)) cos]). self plot: realData in: (100@20 extent: 256@60). self transformForward: true. self plot: realData in: (100@100 extent: 256@60). self plot: imagData in: (100@180 extent: 256@60). self transformForward: false. self plot: realData in: (100@260 extent: 256@60)! ! !FFT methodsFor: 'plugin-testing' stamp: 'ar 10/10/1998 21:53'! pluginPrepareData "The FFT plugin requires data to be represented in WordArrays or FloatArrays" sinTable _ sinTable asFloatArray. permTable _ permTable asWordArray. realData _ realData asFloatArray. imagData _ imagData asFloatArray.! ! !FFT methodsFor: 'plugin-testing' stamp: 'ar 10/10/1998 21:53'! pluginTest "Display restoreAfter: [(FFT new nu: 12) pluginTest]." "Test on an array of 256 samples" "Initialize to pure (co)Sine Wave, plot, transform, plot, invert and plot again" self realData: ((1 to: n) collect: [:i | (Float pi * (i-1) / (n/8)) cos]). self plot: realData in: (100@20 extent: 256@60). self pluginPrepareData. Transcript cr; print: (Time millisecondsToRun:[self pluginTransformData: true]); endEntry. self plot: realData in: (100@100 extent: 256@60). self plot: imagData in: (100@180 extent: 256@60). Transcript cr; print: (Time millisecondsToRun:[self pluginTransformData: false]); endEntry. self plot: realData in: (100@260 extent: 256@60)! ! !FFT methodsFor: 'plugin-testing' stamp: 'ar 2/2/2001 15:47'! pluginTransformData: forward "Plugin testing -- if the primitive is not implemented or cannot be found run the simulation. See also: FFTPlugin" ^FFTPlugin doPrimitive: 'primitiveFFTTransformData'.! ! !FFT methodsFor: 'bulk processing' stamp: 'jm 9/8/1999 17:10'! initializeHammingWindow: alpha "Initialize the windowing function to the generalized Hamming window. See F. Richard Moore, Elements of Computer Music, p. 100. An alpha of 0.54 gives the Hamming window, 0.5 gives the hanning window." | v midPoint | window _ FloatArray new: n. midPoint _ (n + 1) / 2.0. 1 to: n do: [:i | v _ alpha + ((1.0 - alpha) * (2.0 * Float pi * ((i - midPoint) / n)) cos). window at: i put: v]. ! ! !FFT methodsFor: 'bulk processing' stamp: 'jm 9/8/1999 16:42'! initializeTriangularWindow "Initialize the windowing function to the triangular, or Parzen, window. See F. Richard Moore, Elements of Computer Music, p. 100." | v | window _ FloatArray new: n. 0 to: (n // 2) - 1 do: [:i | v _ i / ((n // 2) - 1). window at: (i + 1) put: v. window at: (n - i) put: v]. ! ! !FFT methodsFor: 'bulk processing' stamp: 'jm 9/8/1999 17:40'! setSize: anIntegerPowerOfTwo "Initialize variables and tables for performing an FFT on the given number of samples. The number of samples must be an integral power of two (e.g. 1024). Prepare data for use with the fast primitive." self nu: (anIntegerPowerOfTwo log: 2) asInteger. n = anIntegerPowerOfTwo ifFalse: [self error: 'size must be a power of two']. sinTable _ sinTable asFloatArray. permTable _ permTable asWordArray. realData _ FloatArray new: n. imagData _ FloatArray new: n. self initializeHammingWindow: 0.54. "0.54 for Hamming, 0.5 for hanning" ! ! !FFT methodsFor: 'bulk processing' stamp: 'jm 9/8/1999 17:55'! transformDataFrom: anIndexableCollection startingAt: index "Forward transform a block of real data taken from from the given indexable collection starting at the given index. Answer a block of values representing the normalized magnitudes of the frequency components." | j real imag out | j _ 0. index to: index + n - 1 do: [:i | realData at: (j _ j + 1) put: (anIndexableCollection at: i)]. realData *= window. imagData _ FloatArray new: n. self pluginTransformData: true. "compute the magnitudes of the complex results" "note: the results are in bottom half; the upper half is just its mirror image" real _ realData copyFrom: 1 to: (n / 2). imag _ imagData copyFrom: 1 to: (n / 2). out _ (real * real) + (imag * imag). 1 to: out size do: [:i | out at: i put: (out at: i) sqrt]. ^ out ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFT class instanceVariableNames: ''! !FFT class methodsFor: 'instance creation' stamp: 'jm 8/25/1999 12:49'! new: anIntegerPowerOfTwo "Answer a new FFT instance for transforming data packets of the given size." ^ self new setSize: anIntegerPowerOfTwo ! ! InterpreterPlugin subclass: #FFTPlugin instanceVariableNames: 'nu fftSize sinTable sinTableSize permTable permTableSize realData realDataSize imagData imagDataSize ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FFTPlugin commentStamp: '' prior: 0! FFTPlugin is an example of how plugins are written. It shows the use of FloatArray for heavy numerical stuff as well as the simulation of plugins from Squeak. See also: FFT pluginTransformData: ! ]style[(173 24 1)f1,f1LFFT pluginTransformData:;,f1! !FFTPlugin methodsFor: 'primitives' stamp: 'ar 10/11/1998 01:59'! primitiveFFTPermuteData | rcvr | self export: true. rcvr _ interpreterProxy stackObjectValue: 0. (self loadFFTFrom: rcvr) ifFalse:[^nil]. self permuteData. interpreterProxy failed ifTrue:[ "permuteData went wrong. Do the permutation again -- this will restore the original order" self permuteData].! ! !FFTPlugin methodsFor: 'primitives' stamp: 'ar 10/11/1998 01:59'! primitiveFFTScaleData | rcvr | self export: true. rcvr _ interpreterProxy stackObjectValue: 0. (self loadFFTFrom: rcvr) ifFalse:[^nil]. self scaleData.! ! !FFTPlugin methodsFor: 'primitives' stamp: 'ar 10/11/1998 01:59'! primitiveFFTTransformData | rcvr forward | self export: true. forward _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). rcvr _ interpreterProxy stackObjectValue: 1. (self loadFFTFrom: rcvr) ifFalse:[^nil]. self transformData: forward. interpreterProxy failed ifFalse:[ interpreterProxy pop: 1. "Leave rcvr on stack" ].! ! !FFTPlugin methodsFor: 'transforming' stamp: 'ar 9/16/1998 00:25'! permuteData | i end a b tmp | self var: #tmp declareC: 'float tmp'. i _ 0. end _ permTableSize. [i < end] whileTrue: [a _ (permTable at: i) - 1. b _ (permTable at: i+1) - 1. (a < realDataSize and:[b < realDataSize]) ifFalse:[^interpreterProxy success: false]. tmp _ realData at: a. realData at: a put: (realData at: b). realData at: b put: tmp. tmp _ imagData at: a. imagData at: a put: (imagData at: b). imagData at: b put: tmp. i _ i + 2]! ! !FFTPlugin methodsFor: 'transforming' stamp: 'ar 10/3/1998 18:10'! scaleData "Scale all elements by 1/n when doing inverse" | realN | self var: #realN declareC: 'float realN'. fftSize <= 1 ifTrue:[^nil]. realN _ self cCoerce: (1.0 / (self cCoerce: fftSize to: 'double')) to: 'float'. 0 to: fftSize-1 do: [:i | realData at: i put: (realData at: i) * realN. imagData at: i put: (imagData at: i) * realN]! ! !FFTPlugin methodsFor: 'transforming' stamp: 'ar 9/16/1998 20:21'! transformData: forward self permuteData. interpreterProxy failed ifTrue:[ "permuteData went wrong. Do the permutation again -- this will restore the original order" self permuteData. ^nil]. self transformForward: forward. forward ifFalse: [self scaleData] "Reverse transform must scale to be an inverse"! ! !FFTPlugin methodsFor: 'transforming' stamp: 'ar 9/16/1998 21:28'! transformForward: forward | lev lev1 ip theta realU imagU realT imagT i fftSize2 fftSize4 fftScale ii | self var: #realU declareC:'float realU'. self var: #realT declareC:'float realT'. self var: #imagU declareC:'float imagU'. self var: #imagT declareC:'float imagT'. fftSize2 _ fftSize // 2. fftSize4 _ fftSize // 4. 1 to: nu do: [:level | lev _ 1 bitShift: level. lev1 _ lev // 2. fftScale _ fftSize // lev. 1 to: lev1 do: [:j | theta _ j-1 * fftScale. "pi * (j-1) / lev1 mapped onto 0..n/2" theta < fftSize4 "Compute U, the complex multiplier for each level" ifTrue: [realU _ sinTable at: sinTableSize - theta - 1. imagU _ sinTable at: theta] ifFalse: [realU _ 0.0 - (sinTable at: theta - fftSize4). imagU _ sinTable at: fftSize2 - theta]. forward ifFalse: [imagU _ 0.0 - imagU]. " Here is the inner loop... j to: n by: lev do: [:i | hand-transformed to whileTrue... " i _ j. [i <= fftSize] whileTrue: [ip _ i + lev1 - 1. ii _ i-1. realT _ ((realData at: ip) * realU) - ((imagData at: ip) * imagU). imagT _ ((realData at: ip) * imagU) + ((imagData at: ip) * realU). realData at: ip put: (realData at: ii) - realT. imagData at: ip put: (imagData at: ii) - imagT. realData at: ii put: (realData at: ii) + realT. imagData at: ii put: (imagData at: ii) + imagT. i _ i + lev]]].! ! !FFTPlugin methodsFor: 'private' stamp: 'ar 9/16/1998 21:40'! checkedFloatPtrOf: oop "Return the first indexable word of oop which is assumed to be variableWordSubclass" self returnTypeC:'float *'. interpreterProxy success: (interpreterProxy isWords: oop). interpreterProxy failed ifTrue:[^0]. ^self cCoerce: (interpreterProxy firstIndexableField: oop) to:'float *'! ! !FFTPlugin methodsFor: 'private' stamp: 'ar 9/16/1998 21:40'! checkedWordPtrOf: oop "Return the first indexable word of oop which is assumed to be variableWordSubclass" self returnTypeC:'unsigned int *'. interpreterProxy success: (interpreterProxy isWords: oop). ^self cCoerce: (interpreterProxy firstIndexableField: oop) to: 'unsigned int *'! ! !FFTPlugin methodsFor: 'private' stamp: 'ar 10/10/1998 21:43'! loadFFTFrom: fftOop | oop | interpreterProxy success: (interpreterProxy slotSizeOf: fftOop) >= 6. interpreterProxy failed ifTrue:[^false]. nu _ interpreterProxy fetchInteger: 0 ofObject: fftOop. fftSize _ interpreterProxy fetchInteger: 1 ofObject: fftOop. oop _ interpreterProxy fetchPointer: 2 ofObject: fftOop. sinTableSize _ interpreterProxy stSizeOf: oop. sinTable _ self checkedFloatPtrOf: oop. oop _ interpreterProxy fetchPointer: 3 ofObject: fftOop. permTableSize _ interpreterProxy stSizeOf: oop. permTable _ self checkedWordPtrOf: oop. oop _ interpreterProxy fetchPointer: 4 ofObject: fftOop. realDataSize _ interpreterProxy stSizeOf: oop. realData _ self checkedFloatPtrOf: oop. oop _ interpreterProxy fetchPointer: 5 ofObject: fftOop. imagDataSize _ interpreterProxy stSizeOf: oop. imagData _ self checkedFloatPtrOf: oop. "Check assumptions about sizes" interpreterProxy success: (1 << nu = fftSize) & (fftSize // 4 + 1 = sinTableSize) & (fftSize = realDataSize) & (fftSize = imagDataSize) & (realDataSize = imagDataSize). ^interpreterProxy failed == false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FFTPlugin class instanceVariableNames: ''! !FFTPlugin class methodsFor: 'translation to C' stamp: 'sma 3/3/2000 12:43'! declareCVarsIn: cg cg var: #sinTable type: #'float*'. cg var: #realData type: #'float*'. cg var: #imagData type: #'float*'. cg var: #permTable type: #'unsigned int*'! ! FMSound subclass: #FMBassoonSound instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !FMBassoonSound methodsFor: 'as yet unclassified' stamp: 'jm 5/30/1999 21:17'! setPitch: pitchNameOrNumber dur: d loudness: l "Select a modulation ratio and modulation envelope scale based on my pitch." | p modScale | p _ self nameOrNumberToPitch: pitchNameOrNumber. modScale _ 9.4. p > 100.0 ifTrue: [modScale _ 8.3]. p > 150.0 ifTrue: [modScale _ 6.4]. p > 200.0 ifTrue: [modScale _ 5.2]. p > 300.0 ifTrue: [modScale _ 3.9]. p > 400.0 ifTrue: [modScale _ 2.8]. p > 600.0 ifTrue: [modScale _ 1.7]. envelopes size > 0 ifTrue: [ envelopes do: [:e | (e updateSelector = #modulation:) ifTrue: [e scale: modScale]]]. super setPitch: p dur: d loudness: l. ! ! FMSound subclass: #FMClarinetSound instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !FMClarinetSound methodsFor: 'initialization' stamp: 'jm 5/30/1999 10:10'! setPitch: pitchNameOrNumber dur: d loudness: l "Select a modulation ratio and modulation envelope scale based on my pitch." | p modScale | p _ self nameOrNumberToPitch: pitchNameOrNumber. p < 262.0 ifTrue: [modScale _ 25.0. self ratio: 4] ifFalse: [modScale _ 20.0. self ratio: 2]. p > 524.0 ifTrue: [modScale _ 8.0]. envelopes size > 0 ifTrue: [ envelopes do: [:e | (e updateSelector = #modulation:) ifTrue: [e scale: modScale]]]. super setPitch: p dur: d loudness: l. ! ! AbstractSound subclass: #FMSound instanceVariableNames: 'initialCount count waveTable scaledWaveTableSize scaledIndex scaledIndexIncr modulation multiplier normalizedModulation scaledOffsetIndex scaledOffsetIndexIncr ' classVariableNames: 'SineTable ' poolDictionaries: '' category: 'Sound-Synthesis'! !FMSound methodsFor: 'initialization' stamp: 'jm 7/5/1998 11:44'! initialize super initialize. waveTable _ SineTable. scaledWaveTableSize _ waveTable size * ScaleFactor. self setPitch: 440.0 dur: 1.0 loudness: 0.2. ! ! !FMSound methodsFor: 'initialization' stamp: 'jm 7/6/1998 17:08'! setPitch: pitchNameOrNumber dur: d loudness: vol "(FMSound pitch: 'a4' dur: 2.5 loudness: 0.4) play" super setPitch: pitchNameOrNumber dur: d loudness: vol. modulation ifNil: [modulation _ 0.0]. multiplier ifNil: [multiplier _ 0.0]. self pitch: (self nameOrNumberToPitch: pitchNameOrNumber). self reset. ! ! !FMSound methodsFor: 'initialization' stamp: 'jm 9/20/1998 10:10'! setWavetable: anArray "(AbstractSound lowMajorScaleOn: (FMSound new setWavetable: AA)) play" | samples p dur vol | "copy the array into a SoundBuffer if necessary" anArray class isPointers ifTrue: [samples _ SoundBuffer fromArray: anArray] ifFalse: [samples _ anArray]. p _ self pitch. dur _ self duration. vol _ self loudness. waveTable _ samples. scaledWaveTableSize _ waveTable size * ScaleFactor. self setPitch: p dur: dur loudness: vol. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 3/26/98 10:45'! duration ^ initialCount asFloat / self samplingRate asFloat ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 9/9/1998 07:49'! duration: seconds super duration: seconds. count _ initialCount _ (seconds * self samplingRate) rounded. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:44'! internalizeModulationAndRatio "Recompute the internal state for the modulation index and frequency ratio relative to the current pitch." modulation < 0.0 ifTrue: [modulation _ modulation negated]. multiplier < 0.0 ifTrue: [multiplier _ multiplier negated]. normalizedModulation _ ((modulation * scaledIndexIncr) / ScaleFactor) asInteger. scaledOffsetIndexIncr _ (multiplier * scaledIndexIncr) asInteger. "clip to maximum values if necessary" normalizedModulation > MaxScaledValue ifTrue: [ normalizedModulation _ MaxScaledValue. modulation _ (normalizedModulation * ScaleFactor) asFloat / scaledIndexIncr]. scaledOffsetIndexIncr > (scaledWaveTableSize // 2) ifTrue: [ scaledOffsetIndexIncr _ scaledWaveTableSize // 2. multiplier _ scaledOffsetIndexIncr asFloat / scaledIndexIncr]. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:15'! modulation "Return the FM modulation index." ^ modulation ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:22'! modulation: mod "Set the FM modulation index. Typical values range from 0 (no modulation) to 5, although values up to about 10 are sometimes useful." "Warning: This method is intended primarily for use by envelopes. For efficiency during envelope processing, this change will not take effect until internalizeModulationAndRatio is called." modulation _ mod asFloat. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:39'! modulation: mod multiplier: freqRatio "For backward compatibility. Needed to read old .fmp files." self modulation: mod ratio: freqRatio. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:41'! modulation: mod ratio: freqRatio "Set the modulation index and carrier to modulation frequency ratio for this sound, and compute the internal state that depends on these parameters." modulation _ mod asFloat. multiplier _ freqRatio asFloat. self internalizeModulationAndRatio. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 12/17/97 18:05'! multiplier ^ multiplier ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 8/7/1998 15:45'! pitch ^ (self samplingRate asFloat * scaledIndexIncr / ScaleFactor) asFloat / waveTable size ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:38'! pitch: p "Warning: Since the modulation and ratio are relative to the current pitch, some internal state must be recomputed when the pitch is changed. However, for efficiency during envelope processing, this compuation will not be done until internalizeModulationAndRatio is called." scaledIndexIncr _ ((p asFloat * waveTable size asFloat * ScaleFactor asFloat) / self samplingRate asFloat) asInteger min: (waveTable size // 2) * ScaleFactor. ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:08'! ratio "Return the FM modulation to carrier frequency ratio." ^ multiplier ! ! !FMSound methodsFor: 'accessing' stamp: 'jm 2/4/98 07:22'! ratio: freqRatio "Set the FM modulation to carrier frequency ratio." "Warning: This method is intended primarily for use by envelopes. For efficiency during envelope processing, this change will not take effect until internalizeModulationAndRatio is called." multiplier _ freqRatio asFloat. ! ! !FMSound methodsFor: 'sound generation' stamp: 'ar 2/3/2001 15:22'! mixSampleCount: n into: aSoundBuffer startingAt: startIndex leftVol: leftVol rightVol: rightVol "Play samples from a wave table by stepping a fixed amount through the table on every sample. The table index and increment are scaled to allow fractional increments for greater pitch accuracy." "(FMSound pitch: 440.0 dur: 1.0 loudness: 0.5) play" | doingFM lastIndex sample offset i s | self var: #aSoundBuffer declareC: 'short int *aSoundBuffer'. self var: #waveTable declareC: 'short int *waveTable'. doingFM _ (normalizedModulation ~= 0) and: [scaledOffsetIndexIncr ~= 0]. lastIndex _ (startIndex + n) - 1. startIndex to: lastIndex do: [:sliceIndex | sample _ (scaledVol * (waveTable at: (scaledIndex // ScaleFactor) + 1)) // ScaleFactor. doingFM ifTrue: [ offset _ normalizedModulation * (waveTable at: (scaledOffsetIndex // ScaleFactor) + 1). scaledOffsetIndex _ (scaledOffsetIndex + scaledOffsetIndexIncr) \\ scaledWaveTableSize. scaledOffsetIndex < 0 ifTrue: [scaledOffsetIndex _ scaledOffsetIndex + scaledWaveTableSize]. scaledIndex _ (scaledIndex + scaledIndexIncr + offset) \\ scaledWaveTableSize. scaledIndex < 0 ifTrue: [scaledIndex _ scaledIndex + scaledWaveTableSize]] ifFalse: [ scaledIndex _ (scaledIndex + scaledIndexIncr) \\ scaledWaveTableSize]. leftVol > 0 ifTrue: [ i _ (2 * sliceIndex) - 1. s _ (aSoundBuffer at: i) + ((sample * leftVol) // ScaleFactor). s > 32767 ifTrue: [s _ 32767]. "clipping!!" s < -32767 ifTrue: [s _ -32767]. "clipping!!" aSoundBuffer at: i put: s]. rightVol > 0 ifTrue: [ i _ 2 * sliceIndex. s _ (aSoundBuffer at: i) + ((sample * rightVol) // ScaleFactor). s > 32767 ifTrue: [s _ 32767]. "clipping!!" s < -32767 ifTrue: [s _ -32767]. "clipping!!" aSoundBuffer at: i put: s]. scaledVolIncr ~= 0 ifTrue: [ scaledVol _ scaledVol + scaledVolIncr. ((scaledVolIncr > 0 and: [scaledVol >= scaledVolLimit]) or: [scaledVolIncr < 0 and: [scaledVol <= scaledVolLimit]]) ifTrue: [ "reached the limit; stop incrementing" scaledVol _ scaledVolLimit. scaledVolIncr _ 0]]]. count _ count - n. ! ! !FMSound methodsFor: 'sound generation' stamp: 'jm 2/4/98 20:12'! reset self internalizeModulationAndRatio. super reset. count _ initialCount. scaledIndex _ 0. scaledOffsetIndex _ 0. ! ! !FMSound methodsFor: 'sound generation' stamp: 'jm 12/8/97 19:34'! samplesRemaining ^ count ! ! !FMSound methodsFor: 'sound generation' stamp: 'jm 9/9/1998 21:55'! stopAfterMSecs: mSecs "Terminate this sound this note after the given number of milliseconds." count _ (mSecs * self samplingRate) // 1000. ! ! !FMSound methodsFor: 'storing' stamp: 'jm 2/4/98 07:02'! storeOn: strm | env | strm nextPutAll: '(((FMSound'; nextPutAll: ' pitch: '; print: self pitch; nextPutAll: ' dur: '; print: self duration; nextPutAll: ' loudness: '; print: self loudness; nextPutAll: ')'; nextPutAll: ' modulation: '; print: self modulation; nextPutAll: ' ratio: '; print: self ratio; nextPutAll: ')'. 1 to: envelopes size do: [:i | env _ envelopes at: i. strm cr; nextPutAll: ' addEnvelope: '. env storeOn: strm. i < envelopes size ifTrue: [strm nextPutAll: ';']]. strm nextPutAll: ')'. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FMSound class instanceVariableNames: ''! !FMSound class methodsFor: 'class initialization' stamp: 'jm 7/6/1998 10:26'! initialize "Build a sine wave table." "FMSound initialize" | tableSize radiansPerStep peak | tableSize _ 4000. SineTable _ SoundBuffer newMonoSampleCount: tableSize. radiansPerStep _ (2.0 * Float pi) / tableSize asFloat. peak _ ((1 bitShift: 15) - 1) asFloat. "range is +/- (2^15 - 1)" 1 to: tableSize do: [:i | SineTable at: i put: (peak * (radiansPerStep * (i - 1)) sin) rounded]. ! ! !FMSound class methodsFor: 'class initialization' stamp: 'jm 7/5/1998 14:22'! sineTable "Answer a SoundBuffer containing one complete cycle of a sine wave." ^ SineTable ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! bass1 "FMSound bass1 play" "(FMSound lowMajorScaleOn: FMSound bass1) play" | snd | snd _ FMSound new modulation: 0 ratio: 0. snd addEnvelope: (VolumeEnvelope exponentialDecay: 0.95). ^ snd setPitch: 220 dur: 1.0 loudness: 0.3 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 5/30/1999 20:37'! bassoon1 "FMSound bassoon1 play" "(FMSound lowMajorScaleOn: FMSound bassoon1) play" | snd p env | snd _ FMBassoonSound new ratio: 1. p _ OrderedCollection new. p add: 0@0.0; add: 40@0.45; add: 90@1.0; add: 180@0.9; add: 270@1.0; add: 320@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 3 loopEnd: 5). p _ OrderedCollection new. p add: 0@0.2; add: 40@0.9; add: 90@0.6; add: 270@0.6; add: 320@0.5. env _ Envelope points: p loopStart: 3 loopEnd: 4. env updateSelector: #modulation:; scale: 5.05. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! brass1 "FMSound brass1 play" "(FMSound lowMajorScaleOn: FMSound brass1) play" | snd p env | snd _ FMSound new modulation: 0 ratio: 1. p _ OrderedCollection new. p add: 0@0.0; add: 30@0.8; add: 90@1.0; add: 120@0.9; add: 220@0.7; add: 320@0.9; add: 360@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 4 loopEnd: 6). p _ OrderedCollection new. p add: 0@0.5; add: 60@1.0; add: 120@0.8; add: 220@0.65; add: 320@0.8; add: 360@0.0. env _ Envelope points: p loopStart: 3 loopEnd: 5. env target: snd; updateSelector: #modulation:; scale: 5.0. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! brass2 "FMSound brass2 play" "(FMSound lowMajorScaleOn: FMSound brass2) play" | snd p env | snd _ FMSound new modulation: 1 ratio: 1. p _ OrderedCollection new. p add: 0@0.0; add: 20@1.0; add: 40@0.9; add: 100@0.7; add: 160@0.9; add: 200@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 3 loopEnd: 5). p _ OrderedCollection new. p add: 0@0.5; add: 30@1.0; add: 40@0.8; add: 100@0.7; add: 160@0.8; add: 200@0.0. env _ Envelope points: p loopStart: 3 loopEnd: 5. env updateSelector: #modulation:; scale: 5.0. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:10'! clarinet "FMSound clarinet play" "(FMSound lowMajorScaleOn: FMSound clarinet) play" | snd p env | snd _ FMSound new modulation: 0 ratio: 2. p _ OrderedCollection new. p add: 0@0.0; add: 60@1.0; add: 310@1.0; add: 350@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). p _ OrderedCollection new. p add: 0@0.0167; add: 60@0.106; add: 310@0.106; add: 350@0.0. env _ Envelope points: p loopStart: 2 loopEnd: 3. env updateSelector: #modulation:; scale: 10.0. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 5/30/1999 10:20'! clarinet2 "FMSound clarinet2 play" "(FMSound lowMajorScaleOn: FMSound clarinet2) play" | snd p env | snd _ FMClarinetSound new modulation: 0 ratio: 2. p _ OrderedCollection new. p add: 0@0.0; add: 60@1.0; add: 310@1.0; add: 350@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). p _ OrderedCollection new. p add: 0@0.0167; add: 60@0.106; add: 310@0.106; add: 350@0.0. env _ Envelope points: p loopStart: 2 loopEnd: 3. env updateSelector: #modulation:; scale: 10.0. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/5/98 17:35'! default ^ self oboe1 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 8/14/1998 13:02'! flute1 "FMSound flute1 play" "(FMSound majorScaleOn: FMSound flute1) play" | snd p | snd _ FMSound new. p _ OrderedCollection new. p add: 0@0; add: 20@1.0; add: 100@1.0; add: 120@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). ^ snd setPitch: 440.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 8/14/1998 13:02'! flute2 "FMSound flute2 play" "(FMSound majorScaleOn: FMSound flute2) play" | snd p | snd _ FMSound new. p _ OrderedCollection new. p add: 0@0; add: 20@1.0; add: 100@1.0; add: 120@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). snd addEnvelope: (RandomEnvelope for: #pitch:). ^ snd setPitch: 440.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 9/2/1999 13:32'! marimba "FMSound marimba play" "(FMSound majorScaleOn: FMSound marimba) play" | snd p env | snd _ FMSound new modulation: 1 ratio: 0.98. p _ OrderedCollection new. p add: 0@1.0; add: 10@0.3; add: 40@0.1; add: 80@0.02; add: 120@0.1; add: 160@0.02; add: 220@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 4 loopEnd: 6). p _ OrderedCollection new. p add: 0@1.2; add: 80@0.85; add: 120@1.0; add: 160@0.85; add: 220@0.0. env _ Envelope points: p loopStart: 2 loopEnd: 4. env updateSelector: #modulation:. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! mellowBrass "FMSound mellowBrass play" "(FMSound lowMajorScaleOn: FMSound mellowBrass) play" | snd p env | snd _ FMSound new modulation: 0 ratio: 1. p _ OrderedCollection new. p add: 0@0.0; add: 70@0.325; add: 120@0.194; add: 200@0.194; add: 320@0.194; add: 380@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 3 loopEnd: 5). p _ OrderedCollection new. p add: 0@0.1; add: 70@0.68; add: 120@0.528; add: 200@0.519; add: 320@0.528; add: 380@0.0. env _ Envelope points: p loopStart: 3 loopEnd: 5. env updateSelector: #modulation:; scale: 5.0. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! oboe1 "FMSound oboe1 play" "(FMSound majorScaleOn: FMSound oboe1) play" | snd p | snd _ FMSound new modulation: 1 ratio: 1. p _ OrderedCollection new. p add: 0@0.0; add: 10@1.0; add: 100@1.0; add: 120@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). ^ snd setPitch: 440.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:00'! oboe2 "FMSound oboe2 play" "(FMSound majorScaleOn: FMSound oboe2) play" | snd p | snd _ FMSound new modulation: 1 ratio: 1. p _ OrderedCollection new. p add: 0@0; add: 20@1.0; add: 100@1.0; add: 120@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 3). snd addEnvelope: (RandomEnvelope for: #pitch:). ^ snd setPitch: 440.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 1/14/1999 12:56'! organ1 "FMSound organ1 play" "(FMSound majorScaleOn: FMSound organ1) play" | snd p | snd _ FMSound new. p _ OrderedCollection new. p add: 0@0; add: 60@1.0; add: 110@0.8; add: 200@1.0; add: 250@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 2 loopEnd: 4). ^ snd setPitch: 440.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 9/1/1999 17:33'! pluckedElecBass "FMSound pluckedElecBass play" "(FMSound lowMajorScaleOn: FMSound pluckedElecBass) play" | snd p env | snd _ FMSound new modulation: 1 ratio: 3.0. p _ OrderedCollection new. p add: 0@0.4; add: 20@1.0; add: 30@0.6; add: 100@0.6; add: 130@0.0. snd addEnvelope: (VolumeEnvelope points: p loopStart: 3 loopEnd: 4). p _ OrderedCollection new. p add: 0@1.0; add: 20@2.0; add: 30@4.5; add: 100@4.5; add: 130@0.0. env _ Envelope points: p loopStart: 3 loopEnd: 4. env updateSelector: #modulation:. snd addEnvelope: env. p _ OrderedCollection new. p add: 0@6.0; add: 20@4.0; add: 30@3.0; add: 100@3.0; add: 130@3.0. env _ Envelope points: p loopStart: 3 loopEnd: 4. env updateSelector: #ratio:. snd addEnvelope: env. ^ snd setPitch: 220.0 dur: 1.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 8/14/1998 13:00'! randomWeird1 "FMSound randomWeird1 play" | snd p | snd _ FMSound new. snd addEnvelope: (VolumeEnvelope exponentialDecay: 0.96). p _ Array with: 0@0 with: 100@1.0 with: 250@0.7 with: 400@1.0 with: 500@0. snd addEnvelope: (PitchEnvelope points: p loopStart: 2 loopEnd: 4). ^ snd setPitch: (150 + 2000 atRandom) dur: 2.0 loudness: 0.5 ! ! !FMSound class methodsFor: 'instruments' stamp: 'jm 8/14/1998 12:57'! randomWeird2 "FMSound randomWeird2 play" | snd | snd _ FMSound new. snd addEnvelope: (VolumeEnvelope exponentialDecay: 0.96). snd addEnvelope: (PitchEnvelope exponentialDecay: 0.98). ^ snd setPitch: (150 + 2000 atRandom) dur: 2.0 loudness: 0.5 ! ! HTTPSocket subclass: #FTPSocket instanceVariableNames: 'portNum dataSocket readAhead ' classVariableNames: '' poolDictionaries: '' category: 'Network-Protocols'! !FTPSocket commentStamp: '' prior: 0! A minimal FTP client program. Could store all state in inst vars, and use an instance to represent the full state of a connection in progress. But simpler to do all that in one method and have it be a complete transaction. Always operates in passive mode (PASV). All connections are initiated from client in order to get through firewalls. See ServerDirectory openFTP, ServerDirectory getFileNamed:, ServerDirectory putFile:named: for examples of use. See TCP/IP, second edition, by Dr. Sidnie Feit, McGraw-Hill, 1997, Chapter 14, p311.! ]style[(259 1 89 23 2 29 2 30 27 6 74)f1,f1-,f1,f1LServerDirectory openFTP;,f1,f1LServerDirectory getFileNamed:;,f1,f1LServerDirectory putFile:named:;,f1,f1b,f1! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 12/26/97 22:13'! dataSocket ^ dataSocket! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 12/26/97 22:13'! dataSocket: dd dataSocket _ dd! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 12/26/97 21:56'! getAllData "Reel in all data until the server closes the connection. Return a RWBinaryOrTextStream. Don't know how much is coming." | buf response bytesRead | buf _ String new: 4000. response _ RWBinaryOrTextStream on: (String new: 4000). [(self dataAvailable | self isConnected)] whileTrue: [ (self waitForDataUntil: (Socket deadlineSecs: 5)) ifFalse: [ Transcript show: 'data was late'; cr]. bytesRead _ self primSocket: socketHandle receiveDataInto: buf startingAt: 1 count: buf size. 1 to: bytesRead do: [:ii | response nextPut: (buf at: ii)]. "Any way to do this so we do not have to recopy?" ]. response reset. "position: 0." ^ response! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 9/22/1998 15:03'! getAllDataWhileWatching: otherSocket "Reel in all data until the server closes the connection. At the same time, watch for errors on otherSocket. Return a RWBinaryOrTextStream. Don't know how much is coming." | buf response bytesRead | buf _ String new: 4000. response _ RWBinaryOrTextStream on: (String new: 4000). [self isConnected | self dataAvailable] whileTrue: [ (self waitForDataUntil: (Socket deadlineSecs: 5)) ifFalse: [ otherSocket responseError ifTrue: [self destroy. ^ #error:]. Transcript show: 'data was late'; cr]. bytesRead _ self primSocket: socketHandle receiveDataInto: buf startingAt: 1 count: buf size. 1 to: bytesRead do: [:ii | response nextPut: (buf at: ii)]. "Any way to do this so we do not have to recopy?" ]. response reset. "position: 0." ^ response! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 9/22/1998 15:05'! getDataTo: dataStream whileWatching: otherSocket "Reel in all data until the server closes the connection. At the same time, watch for errors on otherSocket. Don't know how much is coming. Put the data on the stream." | buf bytesRead | buf _ String new: 4000. [self isConnected | self dataAvailable] whileTrue: [ (self waitForDataUntil: (Socket deadlineSecs: 5)) ifFalse: [ otherSocket responseError ifTrue: [self destroy. ^ #error:]. Transcript show: 'data was late'; cr]. bytesRead _ self primSocket: socketHandle receiveDataInto: buf startingAt: 1 count: buf size. 1 to: bytesRead do: [:ii | dataStream nextPut: (buf at: ii)]. "Any way to do this so we do not have to recopy?" ]. dataStream reset. "position: 0." ^ dataStream! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 3/23/2000 22:10'! getOnlyBuffer: ubuffer whileWatching: otherSocket "Reel in all data until the buffer is full. At the same time, watch for errors on otherSocket. Caller will break the connection after we have the data." | bytesRead ind | ind _ 1. [self isConnected | self dataAvailable] whileTrue: [ (self waitForDataUntil: (Socket deadlineSecs: 5)) ifFalse: [ otherSocket responseError ifTrue: [self destroy. ^ #error:]. Transcript show: 'data was late'; cr]. bytesRead _ self primSocket: socketHandle receiveDataInto: ubuffer startingAt: ind count: ubuffer size - ind + 1. (ind _ ind + bytesRead) > ubuffer size ifTrue: [^ ubuffer]. ]. ^ ubuffer "file was shorter"! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'di 3/29/1999 17:18'! lookFor: beginning "Get the response from the server. If 1xx, in progress. If 2xx, success. If 3xx, intermediate point successful. 4xx, transient error. 5xx, true error. If 4 or 5, put up dialog, kill the socket, and return the response string. Return true the string in beginning is at the front of what came back. Ignore any 2xx response that is not what we want, but print it." | resp what all | (readAhead ~~ nil and: [readAhead size > 0]) ifTrue: [resp _ readAhead removeFirst] "response already came in" ifFalse: [ all _ self getResponseUpTo: CrLf. resp _ all at: 1. "150 Opening binary mode data conn" readAhead _ (all at: 3) findBetweenSubStrs: (Array with: CrLf)]. resp size > 0 ifTrue: [ resp first isDigit ifFalse: [ ^self lookFor: beginning ]. "we're in the middle of a line, not the end." #XXX. "this should be fixed..." (resp at: 4) == $- ifTrue: [^ self lookFor: beginning]. "is a comment" (resp beginsWith: beginning) ifTrue: [^ true]. "exactly what we wanted" ] ifFalse: [resp _ '[timeout]']. what _ (PopUpMenu labels: 'OK\ debug ' withCRs) startUpWithCaption: 'Server reported this error:\' withCRs, resp. what = 2 ifTrue: [self halt]. self destroy. ^ resp ! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 7/8/1999 14:29'! lookSoftlyFor: beginning "Get the response from the server. Return true the string in beginning is at the front of what came back. Don't kill the socket if we fail. Users wants to try another password." | resp what all | (readAhead ~~ nil and: [readAhead size > 0]) ifTrue: [resp _ readAhead removeFirst] "response already came in" ifFalse: [ all _ self getResponseUpTo: CrLf. resp _ all at: 1. "150 Opening binary mode data conn" readAhead _ (all at: 3) findBetweenSubStrs: (Array with: CrLf)]. resp size > 0 ifTrue: [ resp first isDigit ifFalse: [ ^self lookFor: beginning ]. "we're in the middle of a line, not the end." #XXX. "this should be fixed..." (resp at: 4) == $- ifTrue: [^ self lookFor: beginning]. "is a comment" (resp beginsWith: beginning) ifTrue: [^ true]. "exactly what we wanted" ] ifFalse: [resp _ '[timeout]']. what _ (PopUpMenu labels: 'OK\ debug ' withCRs) startUpWithCaption: 'Server reported this error:\' withCRs, resp. what = 2 ifTrue: [self halt]. ^ resp ! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 12/26/97 22:12'! portNum ^ portNum! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 12/26/97 22:12'! portNum: anInteger portNum _ anInteger! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 1/5/98 11:59'! responseCheck "If data is waiting, do a responseOK to catch any error reports." self dataAvailable ifTrue: [^ self responseOK]. ^ true "all OK so far"! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'tk 1/5/98 12:28'! responseError "If data is waiting, Check it to catch any error reports. Keep all responses in a queue for caller to examine later." | all what | self dataAvailable ifTrue: [ all _ self getResponseUpTo: CrLf. readAhead ifNil: [readAhead _ OrderedCollection new]. readAhead addLast: (all at: 1). "150 Opening binary mode data conn" readAhead addAll: ((all at: 3) findBetweenSubStrs: (Array with: CrLf)). readAhead do: [:resp | ((resp at: 1) == $5) | ((resp at: 1) == $4) ifTrue: [ what _ (PopUpMenu labels: 'OK\ debug ' withCRs) startUpWithCaption: 'Server reported this error:\' withCRs, resp. what = 2 ifTrue: [self halt]. self sendCommand: 'QUIT'. readAhead _ nil. "clear queue" self responseOK. "221" self destroy. ^ true]]]. ^ false "all OK so far"! ! !FTPSocket methodsFor: 'as yet unclassified' stamp: 'mjg 5/7/1999 13:54'! responseOK "Get the response from the server. If 1xx, in progress. If 2xx, success. If 3xx, intermediate point successful. 4xx, transient error. 5xx, true error. If 4 or 5, put up dialog and kill the socket. Return true if OK, the error string if not." | resp what all | readAhead isNil ifTrue: [readAhead _ '']. readAhead size > 0 ifTrue: [resp _ readAhead removeFirst] "response already came in" ifFalse: [ all _ self getResponseUpTo: CrLf. resp _ all at: 1. "150 Opening binary mode data conn" readAhead _ (all at: 3) findBetweenSubStrs: (Array with: CrLf)]. "Transcript show: resp; cr." resp size > 0 ifTrue: [((resp at: 1) == $5) | ((resp at: 1) == $4) ifFalse: [^ true]] "All is well" ifFalse: [resp _ '[timeout]']. what _ (PopUpMenu labels: 'OK\ debug ' withCRs) startUpWithCaption: 'Server reported this error:\' withCRs, resp. what = 2 ifTrue: [self halt]. self destroy. ^ resp! ! !FTPSocket methodsFor: 'finalization' stamp: 'ar 3/21/98 18:19'! actAsExecutor super actAsExecutor. dataSocket := nil.! ! Object subclass: #FWT instanceVariableNames: 'alpha beta coeffs h g hTilde gTilde samples nSamples nLevels transform ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !FWT commentStamp: '' prior: 0! This class implements the Fast Wavelet Transform. It follows Mac Cody's article in Dr. Dobb's Journal, April 1992. See also... http://www.dfw.net/~mcody/fwt/fwt.html Notable features of his implementation include... 1. The ability to generate a large family of wavelets (including the Haar (alpha=beta) and Daubechies) from two parameters, alpha and beta, which range between -pi and pi. 2. All data arrays have 5 elements added on to allow for convolution overrun with filters up to 6 in length (the max for this implementation). 3. After a forward transform, the detail coefficients of the deomposition are found in transform at: 2*i, for i = 1, 2, ... nLevels; and the approximation coefficients are in transform at: (2*nLevels-1). these together comprise the complete wavelet transform. The following changes from cody's listings should also be noted... 1. The three DotProduct routines have been merged into one. 2. The four routines WaveletDecomposition, DecomposeBranches, WaveletReconstruction, ReconstructBranches have all been merged into transformForward:. 3. All indexing follows the Smalltalk 1-to-N convention, naturally.! !FWT methodsFor: 'initialization' stamp: 'di 10/31/1998 12:23'! nSamples: n nLevels: nLevs "Initialize a wavelet transform." "Note the sample array size must be N + 5, where N is a multiple of 2^nLevels" | dyadSize | (n // (1 bitShift: nLevs)) > 0 ifFalse: [self error: 'Data size error']. (n \\ (1 bitShift: nLevs)) = 0 ifFalse: [self error: 'Data size error']. nSamples _ n. samples _ Array new: n + 5. nLevels _ nLevs. transform _ Array new: nLevels*2. "Transformed data is stored as a tree of coeffs" dyadSize _ nSamples. 1 to: nLevels do: [:i | dyadSize _ dyadSize // 2. transform at: 2*i-1 put: (Array new: dyadSize + 5). transform at: 2*i put: (Array new: dyadSize + 5)]! ! !FWT methodsFor: 'initialization' stamp: 'di 10/30/1998 10:59'! setAlpha: alph beta: bet "Set alpha and beta, compute wavelet coeefs, and derive hFilter and lFilter" | tcosa tcosb tsina tsinb | alpha _ alph. beta _ bet. "WaveletCoeffs..." "precalculate cosine of alpha and sine of beta" tcosa _ alpha cos. tcosb _ beta cos. tsina _ alpha sin. tsinb _ beta sin. coeffs _ Array new: 6. "calculate first two wavelet coefficients a _ a(-2) and b _ a(-1)" coeffs at: 1 put: ((1.0 + tcosa + tsina) * (1.0 - tcosb - tsinb) + (2.0 * tsinb * tcosa)) / 4.0. coeffs at: 2 put: ((1.0 - tcosa + tsina) * (1.0 + tcosb - tsinb) - (2.0 * tsinb * tcosa)) / 4.0. "precalculate cosine and sine of alpha minus beta" tcosa _ (alpha - beta) cos. tsina _ (alpha - beta) sin. "calculate last four wavelet coefficients c _ a(0), d _ a(1), e _ a(2), and f _ a(3)" coeffs at: 3 put: (1.0 + tcosa + tsina) / 2.0. coeffs at: 4 put: (1.0 + tcosa - tsina) / 2.0. coeffs at: 5 put: 1.0 - (coeffs at: 1) - (coeffs at: 3). coeffs at: 6 put: 1.0 - (coeffs at: 2) - (coeffs at: 4). "MakeFiltersFromCoeffs..." "Select the non-zero wavelet coefficients" coeffs _ coeffs copyFrom: (coeffs findFirst: [:c | c abs > 1.0e-14]) to: (coeffs findLast: [:c | c abs > 1.0e-14]). "Form the low pass and high pass filters for decomposition" hTilde _ coeffs reversed collect: [:c | c / 2.0]. gTilde _ coeffs collect: [:c | c / 2.0]. 1 to: gTilde size by: 2 do: [:i | gTilde at: i put: (gTilde at: i) negated]. "Form the low pass and high pass filters for reconstruction" h _ coeffs copy. g _ coeffs reversed. 2 to: g size by: 2 do: [:i | g at: i put: (g at: i) negated] ! ! !FWT methodsFor: 'access' stamp: 'di 10/31/1998 12:19'! coeffs "Return all coefficients neede to reconstruct the original samples" | header csize strm | header _ Array with: nSamples with: nLevels with: alpha with: beta. csize _ header size. 1 to: nLevels do: [:i | csize _ csize + (transform at: i*2) size]. csize _ csize + (transform at: nLevels*2-1) size. coeffs _ Array new: csize. strm _ WriteStream on: coeffs. strm nextPutAll: header. 1 to: nLevels do: [:i | strm nextPutAll: (transform at: i*2)]. strm nextPutAll: (transform at: nLevels*2-1). ^ coeffs! ! !FWT methodsFor: 'access' stamp: 'di 10/31/1998 12:23'! coeffs: coeffArray "Initialize this instance from the given coeff array (including header)." | header strm | strm _ ReadStream on: coeffArray. header _ strm next: 4. self nSamples: header first nLevels: header second. self setAlpha: header third beta: header fourth. 1 to: nLevels do: [:i | transform at: i*2 put: (strm next: (transform at: i*2) size)]. transform at: nLevels*2-1 put: (strm next: (transform at: nLevels*2-1) size). strm atEnd ifFalse: [self error: 'Data size error']. ! ! !FWT methodsFor: 'access' stamp: 'di 10/31/1998 12:26'! samples ^ samples copyFrom: 1 to: nSamples! ! !FWT methodsFor: 'access' stamp: 'di 10/31/1998 12:25'! samples: anArray 1 to: anArray size do: [:i | samples at: i put: (anArray at: i)]. nSamples+1 to: nSamples+5 do: [:i | samples at: i put: 0.0]! ! !FWT methodsFor: 'computation' stamp: 'di 10/31/1998 09:20'! convolveAndDec: inData dataLen: inLen filter: filter out: outData "convolve the input sequence with the filter and decimate by two" | filtLen offset outi dotp | filtLen _ filter size. outi _ 1. 1 to: inLen+9 by: 2 do: [:i | i < filtLen ifTrue: [dotp _ self dotpData: inData endIndex: i filter: filter start: 1 stop: i inc: 1] ifFalse: [i > (inLen+5) ifTrue: [offset _ i - (inLen+5). dotp _ self dotpData: inData endIndex: inLen+5 filter: filter start: 1+offset stop: filtLen inc: 1] ifFalse: [dotp _ self dotpData: inData endIndex: i filter: filter start: 1 stop: filtLen inc: 1]]. outData at: outi put: dotp. outi _ outi + 1]! ! !FWT methodsFor: 'computation' stamp: 'ls 10/10/1999 13:13'! convolveAndInt: inData dataLen: inLen filter: filter sumOutput: sumOutput into: outData "insert zeros between each element of the input sequence and convolve with the filter to interpolate the data" | outi filtLen oddTerm evenTerm j | outi _ 1. filtLen _ filter size. "every other dot product interpolates the data" filtLen // 2 to: inLen + filtLen - 2 do: [:i | oddTerm _ self dotpData: inData endIndex: i filter: filter start: 2 stop: filter size inc: 2. evenTerm _ self dotpData: inData endIndex: i+1 filter: filter start: 1 stop: filter size inc: 2. sumOutput ifTrue: ["summation with previous convolution if true" outData at: outi put: (outData at: outi) + oddTerm. outData at: outi+1 put: (outData at: outi+1) + evenTerm] ifFalse: ["first convolution of pair if false" outData at: outi put: oddTerm. outData at: outi+1 put: evenTerm]. outi _ outi + 2]. "Ought to be able to fit this last term into the above loop." j _ inLen + filtLen - 1. oddTerm _ self dotpData: inData endIndex: j filter: filter start: 2 stop: filter size inc: 2. sumOutput ifTrue: [outData at: outi put: (outData at: outi) + oddTerm] ifFalse: [outData at: outi put: oddTerm]. ! ! !FWT methodsFor: 'computation' stamp: 'di 10/31/1998 12:55'! dotpData: data endIndex: endIndex filter: filter start: start stop: stop inc: inc | sum i j | sum _ 0.0. j _ endIndex. i _ start. [i <= stop] whileTrue: [sum _ sum + ((data at: j) * (filter at: i)). i _ i + inc. j _ j - 1]. ^ sum! ! !FWT methodsFor: 'computation' stamp: 'di 10/30/1998 15:53'! transformForward: forward | inData inLen outData | forward ifTrue: ["first InData is input signal, following are intermediate approx coefficients" inData _ samples. inLen _ nSamples. 1 to: nLevels do: [:i | self convolveAndDec: inData dataLen: inLen filter: hTilde out: (transform at: 2*i-1). self convolveAndDec: inData dataLen: inLen filter: gTilde out: (transform at: 2*i). inData _ transform at: 2*i-1. inLen _ inLen // 2]] ifFalse: [inLen _ nSamples >> nLevels. "all but last outData are next higher intermediate approximations, last is final reconstruction of samples" nLevels to: 1 by: -1 do: [:i | outData _ i = 1 ifTrue: [samples] ifFalse: [transform at: 2*(i-1)-1]. self convolveAndInt: (transform at: 2*i-1) dataLen: inLen filter: h sumOutput: false into: outData. self convolveAndInt: (transform at: 2*i) dataLen: inLen filter: g sumOutput: true into: outData. inLen _ inLen * 2]] ! ! !FWT methodsFor: 'testing' stamp: 'di 10/31/1998 12:25'! doWaveDemo "FWT new doWaveDemo" "Printing the above should yield a small number -- I get 1.1e-32" | originalData | self nSamples: 312 nLevels: 3. self setAlpha: 0.0 beta: 0.0. "Install a sine wave as sample data" self samples: ((1 to: nSamples) collect: [:i | ((i-1) * 0.02 * Float pi) sin]). originalData _ samples copy. FFT new plot: (samples copyFrom: 1 to: nSamples) in: (0@0 extent: nSamples@100). "Transform forward and plot the decomposition" self transformForward: true. transform withIndexDo: [:w :i | FFT new plot: (w copyFrom: 1 to: w size-5) in: (i-1\\2*320@(i+1//2*130) extent: (w size-5)@100)]. "Test copy out and read in the transform coefficients" self coeffs: self coeffs. "Ttransform back, plot the reconstruction, and return the error figure" self transformForward: false. FFT new plot: (samples copyFrom: 1 to: nSamples) in: (320@0 extent: nSamples@100). ^ self meanSquareError: originalData! ! !FWT methodsFor: 'testing' stamp: 'di 10/30/1998 15:58'! meanSquareError: otherData "Return the mean-square error between the current sample array and some other data, presumably to evaluate a compression scheme." | topSum bottomSum pointDiff | topSum _ bottomSum _ 0.0. 1 to: nSamples do: [:i | pointDiff _ (samples at: i) - (otherData at: i). topSum _ topSum + (pointDiff * pointDiff). bottomSum _ bottomSum + ((otherData at: i) * (otherData at: i))]. ^ topSum / bottomSum! ! !FWT methodsFor: 'testing' stamp: 'di 10/31/1998 22:17'! viewPhiAndPsi "(FWT new nSamples: 256 nLevels: 6) viewPhiAndPsi" "View the scaling function and mother wavelets for this transform" | p | Display fillWhite: (0@0 extent: 300@300). Display border: (0@0 extent: 300@300) width: 2. [Sensor anyButtonPressed] whileFalse: ["Move mouse around in the outer rectangle to explore" p _ Sensor cursorPoint min: 300@300. self setAlpha: (p x - 150) / 150.0 * Float pi beta: (p y - 150) / 150.0 * Float pi. 'alpha=', (alpha roundTo: 0.01) printString, ' ', 'beta=', (beta roundTo: 0.01) printString, ' ' displayAt: 50@5. transform do: [:w | w atAllPut: 0.0]. (transform at: transform size - 1) at: (nSamples>>nLevels) put: 1.0. self transformForward: false. FFT new plot: (samples copyFrom: 1 to: nSamples) in: (20@30 extent: nSamples@100). transform do: [:w | w atAllPut: 0.0]. (transform at: transform size) at: (nSamples>>nLevels) put: 1.0. self transformForward: false. FFT new plot: (samples copyFrom: 1 to: nSamples) in: (20@170 extent: nSamples@100)]. Sensor waitNoButton! ! Object subclass: #FXBlt instanceVariableNames: 'destForm sourceForm halftoneForm combinationRule destX destY width height sourceX sourceY clipX clipY clipWidth clipHeight colorMap sourceMap destMap warpQuad warpQuality sourceKey destKey sourceAlpha tallyMap raiseErrors ' classVariableNames: 'CachedFontColorMaps RecursionLock ' poolDictionaries: '' category: 'Graphics-FXBlt'! !FXBlt commentStamp: '' prior: 0! "Copy all methods from BitBlt" | from to | from _ BitBlt class. to _ FXBlt class. from selectorsDo:[:sel| to compile: (from sourceCodeAt: sel) classified: (from organization categoryOfElement: sel)]. Instance variables: destForm

Target form sourceForm Source form pattern Fill or mask pattern combinationRule Describes the operation to perform destRect Destination rectangle of operation clipRect Destination clip rectangle of operation sourceOrigin Offset in source form sourceMap Mapping from source pixels into 'operational depth' destMap Mapping from dest pixels into 'operational depth' colorMap Mapping from 'operational depth' into destination depth warpQuad Warp rectangle warpQuality Sampling rate for warps sourceKey Source color key destKey Destination color key sourceAlpha Constant source alpha tallyMap Map for tallying pixels. ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipHeight ^clipHeight! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipHeight: anInteger "Set the receiver's clipping area height to be the argument, anInteger." clipHeight _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipRect "Answer the receiver's clipping area rectangle." ^clipX @ clipY extent: clipWidth @ clipHeight! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 10/4/2000 16:37'! clipRect: aRectangle "Set the receiver's clipping area rectangle to be the argument, aRectangle." clipX _ aRectangle left truncated. clipY _ aRectangle top truncated. clipWidth _ aRectangle right truncated - clipX. clipHeight _ aRectangle bottom truncated - clipY.! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipWidth ^clipWidth! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipWidth: anInteger "Set the receiver's clipping area width to be the argument, anInteger." clipWidth _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipX ^clipX! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipX: anInteger "Set the receiver's clipping area top left x coordinate to be the argument, anInteger." clipX _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipY ^clipY! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! clipY: anInteger "Set the receiver's clipping area top left y coordinate to be the argument, anInteger." clipY _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! colorMap ^ colorMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/17/2000 21:54'! colorMap: map "See last part of BitBlt comment. 6/18/96 tk" colorMap _ map. "As long as we need to fix problems with colorMaps" map ifNotNil:[ (map isKindOf: ColorMap) ifFalse:[ map size < 256 ifTrue:[ colorMap _ ColorMap shifts: nil masks: nil colors: map. ] ifFalse:[ sourceForm ifNil:["Can't fix -- ignore". colorMap _ nil] ifNotNil:[colorMap _ sourceForm colormapIfNeededFor: destForm]]]]. ! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! combinationRule ^combinationRule! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! combinationRule: anInteger "Set the receiver's combination rule to be the argument, anInteger, a number in the range 0-15." combinationRule _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destForm ^ destForm! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destForm: aForm destForm _ aForm! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 23:37'! destMap ^destMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 23:37'! destMap: aColorMap destMap _ aColorMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destOrigin: aPoint "Set the receiver's destination top left coordinates to be those of the argument, aPoint." destX _ aPoint x. destY _ aPoint y! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destRect "The rectangle we are about to blit to or just blitted to. " ^ destX @ destY extent: width @ height! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destRect: aRectangle "Set the receiver's destination form top left coordinates to be the origin of the argument, aRectangle, and set the width and height of the receiver's destination form to be the width and height of aRectangle." destX _ aRectangle left. destY _ aRectangle top. width _ aRectangle width. height _ aRectangle height! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destX: anInteger "Set the top left x coordinate of the receiver's destination form to be the argument, anInteger." destX _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destX: x destY: y width: w height: h "Combined init message saves 3 sends from DisplayScanner" destX _ x. destY _ y. width _ w. height _ h.! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! destY: anInteger "Set the top left y coordinate of the receiver's destination form to be the argument, anInteger." destY _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! fillColor ^ halftoneForm! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 6/8/2000 20:38'! fillColor: aColorOrPattern "The destForm will be filled with this color or pattern of colors. May be an old Color, a new type Color, a Bitmap (see BitBlt comment), a Pattern, or a Form. 6/18/96 tk" aColorOrPattern == nil ifTrue: [halftoneForm _ nil. ^ self]. destForm == nil ifTrue: [self error: 'Must set destForm first']. halftoneForm _ destForm bitPatternFor: aColorOrPattern.! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! height: anInteger "Set the receiver's destination form height to be the argument, anInteger." height _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/21/2000 22:06'! isFXBlt ^true! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/13/2000 16:31'! raiseErrors: aBoolean raiseErrors _ aBoolean! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceForm ^ sourceForm! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceForm: aForm "Set the receiver's source form to be the argument, aForm." sourceForm _ aForm! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/17/2000 22:06'! sourceKey ^sourceKey! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/17/2000 22:06'! sourceKey: aKey sourceKey _ aKey! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 23:37'! sourceMap ^sourceMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 23:37'! sourceMap: aColorMap sourceMap _ aColorMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceOrigin: aPoint "Set the receiver's source form coordinates to be those of the argument, aPoint." sourceX _ aPoint x. sourceY _ aPoint y! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceRect: aRectangle "Set the receiver's source form top left x and y, width and height to be the top left coordinate and extent of the argument, aRectangle." sourceX _ aRectangle left. sourceY _ aRectangle top. width _ aRectangle width. height _ aRectangle height! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceX: anInteger "Set the receiver's source form top left x to be the argument, anInteger." sourceX _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceX: sx sourceY: sy destX: x destY: y width: w height: h "Combined init message saves sends from DisplayScanner" sourceX _ sx. sourceY _ sy. destX _ x. destY _ y. width _ w. height _ h.! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! sourceY: anInteger "Set the receiver's source form top left y to be the argument, anInteger." sourceY _ anInteger! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 5/25/2000 19:39'! tallyMap "Return the map used for tallying pixels" ^tallyMap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 5/25/2000 19:39'! tallyMap: aBitmap "Install the map used for tallying pixels" tallyMap _ aBitmap! ! !FXBlt methodsFor: 'accessing' stamp: 'ar 2/9/2000 19:21'! width: anInteger "Set the receiver's destination form width to be the argument, anInteger." width _ anInteger! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copy: destRectangle from: sourcePt in: srcForm | destOrigin | sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ 3. "store" destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 21:12'! copy: destRectangle from: sourcePt in: srcForm fillColor: hf rule: rule "Specify a Color to fill, not a Form. 6/18/96 tk" | destOrigin | sourceForm _ srcForm. self fillColor: hf. "sets halftoneForm" combinationRule _ rule. destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. srcForm == nil ifFalse: [colorMap _ srcForm colormapIfNeededFor: destForm]. ^ self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copy: destRectangle from: sourcePt in: srcForm halftoneForm: hf rule: rule | destOrigin | sourceForm _ srcForm. self fillColor: hf. "sets halftoneForm" combinationRule _ rule. destOrigin _ destRectangle origin. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourcePt x. sourceY _ sourcePt y. width _ destRectangle width. height _ destRectangle height. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/19/2000 20:18'! copyBits "Primitive. Perform the movement of bits from the source form to the destination form. Fail if any variables are not of the right type (Integer, Float, or Form) or if the combination rule is not implemented. In addition to the original 16 combination rules, this BitBlt supports 16 fail (to simulate paint) 17 fail (to simulate mask) 18 sourceWord + destinationWord 19 sourceWord - destinationWord 20 rgbAdd: sourceWord with: destinationWord 21 rgbSub: sourceWord with: destinationWord 22 rgbDiff: sourceWord with: destinationWord 23 tallyIntoMap: destinationWord 24 alphaBlend: sourceWord with: destinationWord 25 pixPaint: sourceWord with: destinationWord 26 pixMask: sourceWord with: destinationWord 27 rgbMax: sourceWord with: destinationWord 28 rgbMin: sourceWord with: destinationWord 29 rgbMin: sourceWord bitInvert32 with: destinationWord " | result | result _ self primCopyBits. result == nil ifFalse:[^result]. raiseErrors == true ifTrue:[^self primitiveFailed]. self recover ifTrue:[^self copyBits]. "Check for unimplmented rules" combinationRule = Form oldPaint ifTrue: [^ self paintBits]. combinationRule = Form oldErase1bitShape ifTrue: [^ self eraseBits]. self error: 'Bad BitBlt arg (Fraction?); proceed to convert.'. "Convert all numeric parameters to integers and try again." destX _ destX asInteger. destY _ destY asInteger. width _ width asInteger. height _ height asInteger. sourceX _ sourceX asInteger. sourceY _ sourceY asInteger. clipX _ clipX asInteger. clipY _ clipY asInteger. clipWidth _ clipWidth asInteger. clipHeight _ clipHeight asInteger. ^ self copyBitsAgain! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copyBitsSimulated ^FXBltSimulation copyBitsFrom: self.! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 21:11'! copyBitsTranslucent: factor "This entry point to BitBlt supplies an extra argument to specify translucency for operations 30 and 31. The argument must be an integer between 0 and 255." sourceAlpha _ factor. sourceForm ifNotNil:[sourceMap _ sourceForm colormapToARGB]. destMap _ destForm colormapToARGB. colorMap _ destForm colormapFromARGB. ^self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 20:41'! copyForm: srcForm to: destPt rule: rule ^ self copyForm: srcForm to: destPt rule: rule colorMap: (srcForm colormapIfNeededFor: destForm)! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copyForm: srcForm to: destPt rule: rule color: color sourceForm _ srcForm. halftoneForm _ color. combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copyForm: srcForm to: destPt rule: rule colorMap: map sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. colorMap _ map. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! copyForm: srcForm to: destPt rule: rule fillColor: color sourceForm _ srcForm. self fillColor: color. "sets halftoneForm" combinationRule _ rule. destX _ destPt x + sourceForm offset x. destY _ destPt y + sourceForm offset y. sourceX _ 0. sourceY _ 0. width _ sourceForm width. height _ sourceForm height. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 20:41'! copyFrom: sourceRectangle in: srcForm to: destPt | sourceOrigin | sourceForm _ srcForm. halftoneForm _ nil. combinationRule _ 3. "store" destX _ destPt x. destY _ destPt y. sourceOrigin _ sourceRectangle origin. sourceX _ sourceOrigin x. sourceY _ sourceOrigin y. width _ sourceRectangle width. height _ sourceRectangle height. colorMap _ srcForm colormapIfNeededFor: destForm. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 5/25/2000 17:40'! displayString: aString from: startIndex to: stopIndex at: aPoint strikeFont: font kern: kernDelta destY _ aPoint y. destX _ aPoint x. ^self primDisplayString: aString from: startIndex to: stopIndex map: font characterToGlyphMap xTable: font xTable kern: kernDelta.! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! fill: destRect fillColor: grayForm rule: rule "Fill with a Color, not a Form. 6/18/96 tk" sourceForm _ nil. self fillColor: grayForm. "sets halftoneForm" combinationRule _ rule. destX _ destRect left. destY _ destRect top. sourceX _ 0. sourceY _ 0. width _ destRect width. height _ destRect height. self copyBits! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! pixelAt: aPoint "Assumes this BitBlt has been set up specially (see the init message, BitBlt bitPeekerFromForm:. Returns the pixel at aPoint." sourceX _ aPoint x. sourceY _ aPoint y. destForm bits at: 1 put: 0. "Just to be sure" self copyBits. ^ destForm bits at: 1! ! !FXBlt methodsFor: 'copying' stamp: 'ar 2/9/2000 19:21'! pixelAt: aPoint put: pixelValue "Assumes this BitBlt has been set up specially (see the init message, BitBlt bitPokerToForm:. Overwrites the pixel at aPoint." destX _ aPoint x. destY _ aPoint y. sourceForm bits at: 1 put: pixelValue. self copyBits " | bb | bb _ (BitBlt bitPokerToForm: Display). [Sensor anyButtonPressed] whileFalse: [bb pixelAt: Sensor cursorPoint put: 55] "! ! !FXBlt methodsFor: 'private' stamp: 'hg 6/27/2000 13:13'! cachedFontColormapFrom: sourceDepth to: destDepth | srcIndex map | CachedFontColorMaps class == Array ifFalse: [CachedFontColorMaps _ (1 to: 9) collect: [:i | Array new: 32]]. srcIndex _ sourceDepth. sourceDepth > 8 ifTrue: [srcIndex _ 9]. (map _ (CachedFontColorMaps at: srcIndex) at: destDepth) ~~ nil ifTrue: [^ map]. map _ (Color cachedColormapFrom: sourceDepth to: destDepth) copy. (CachedFontColorMaps at: srcIndex) at: destDepth put: map. ^ map ! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! copyBitsAgain "Primitive. See BitBlt|copyBits, also a Primitive. Essential. See Object documentation whatIsAPrimitive." self primitiveFailed! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/9/2000 19:21'! copyBitsFrom: x0 to: x1 at: y destX _ x0. destY _ y. sourceX _ x0. width _ (x1 - x0). self copyBits.! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/9/2000 20:42'! eraseBits "Perform the erase operation, which puts 0's in the destination wherever the source (which is assumed to be just 1 bit deep) has a 1. This requires the colorMap to be set in order to AND all 1's into the destFrom pixels regardless of their size." | oldMask oldMap | oldMask _ halftoneForm. halftoneForm _ nil. oldMap _ colorMap. self colorMap: (ColorMap shifts: nil masks: nil colors: (Bitmap with: 0 with: 16rFFFFFFFF)). combinationRule _ Form erase. self copyBits. "Erase the dest wherever the source is 1" halftoneForm _ oldMask. "already converted to a Bitmap" colorMap _ oldMap! ! !FXBlt methodsFor: 'private' stamp: 'ar 5/26/2000 16:39'! getPluginName "Private. Return the name of the plugin representing FXBlt. Used for dynamically switching between different BB representations only." ^'FXBltPlugin'! ! !FXBlt methodsFor: 'private' stamp: 'hg 6/27/2000 13:14'! installStrikeFont: aStrikeFont foregroundColor: foregroundColor backgroundColor: backgroundColor | lastSourceDepth | sourceForm ifNotNil:[lastSourceDepth _ sourceForm depth]. sourceForm _ aStrikeFont glyphs. (colorMap notNil and:[lastSourceDepth = sourceForm depth]) ifFalse: ["Set up color map for a different source depth (color font)" "Uses caching for reasonable efficiency" colorMap _ self cachedFontColormapFrom: sourceForm depth to: destForm depth. colorMap at: 1 put: (backgroundColor pixelValueForDepth: destForm depth). self colorMap: colorMap]. sourceForm depth = 1 ifTrue: [colorMap colors at: 2 put: (foregroundColor pixelValueForDepth: destForm depth). "Ignore any halftone pattern since we use a color map approach here" halftoneForm _ nil]. sourceY _ 0. height _ aStrikeFont height. ! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/9/2000 20:41'! paintBits "Perform the paint operation, which requires two calls to BitBlt." | color oldMap saveRule | sourceForm depth = 1 ifFalse: [^ self halt: 'paint operation is only defined for 1-bit deep sourceForms']. saveRule _ combinationRule. color _ halftoneForm. halftoneForm _ nil. oldMap _ colorMap. "Map 1's to ALL ones, not just one" self colorMap: (ColorMap shifts: nil masks: nil colors: (Bitmap with: 0 with: 16rFFFFFFFF)). combinationRule _ Form erase. self copyBits. "Erase the dest wherever the source is 1" halftoneForm _ color. combinationRule _ Form under. self copyBits. "then OR, with whatever color, into the hole" colorMap _ oldMap. combinationRule _ saveRule " | dot | dot _ Form dotOfSize: 32. ((BitBlt destForm: Display sourceForm: dot fillColor: Color lightGray combinationRule: Form paint destOrigin: Sensor cursorPoint sourceOrigin: 0@0 extent: dot extent clipRect: Display boundingBox) colorMap: (ColorMap shifts: nil masks: nil colors: (Bitmap with: 0 with: 16rFFFFFFFF))) copyBits"! ! !FXBlt methodsFor: 'private' stamp: 'ar 5/25/2000 17:14'! primCopyBits ^nil "indicates error"! ! !FXBlt methodsFor: 'private' stamp: 'ar 5/25/2000 17:21'! primDisplayString: aString from: startIndex to: stopIndex map: glyphMap xTable: xTable kern: kernDelta | ascii glyph | startIndex to: stopIndex do:[:charIndex| ascii _ (aString at: charIndex) asciiValue. glyph _ glyphMap at: ascii + 1. sourceX _ xTable at: glyph + 1. width _ (xTable at: glyph + 2) - sourceX. self copyBits. destX _ destX + width + kernDelta. ].! ! !FXBlt methodsFor: 'private' stamp: 'ar 5/25/2000 17:13'! recover "Recover after a BitBlt operation has failed. Return true if the copyBits operation should be tried again." "As long as we need to fix problems with colorMaps" colorMap ifNotNil:[ (colorMap isKindOf: ColorMap) ifFalse:[ colorMap size < 256 ifTrue:[ colorMap _ ColorMap shifts: nil masks: nil colors: colorMap. ] ifFalse:[ sourceForm ifNil:["Can't fix -- ignore". colorMap _ nil] ifNotNil:[colorMap _ sourceForm colormapIfNeededFor: destForm]]. ^true "try again"]]. "Check for compressed source, destination or halftone forms" ((sourceForm isKindOf: Form) and: [sourceForm unhibernate]) ifTrue: [^true]. "try again" ((destForm isKindOf: Form) and: [destForm unhibernate]) ifTrue: [^true]. "try again" ((halftoneForm isKindOf: Form) and: [halftoneForm unhibernate]) ifTrue: [^true]. "try again" ^false "unable to recover"! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/9/2000 19:21'! setDestForm: df | bb | bb _ df boundingBox. destForm _ df. clipX _ bb left. clipY _ bb top. clipWidth _ bb width. clipHeight _ bb height! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/9/2000 20:40'! setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect | aPoint | destForm _ df. sourceForm _ sf. self fillColor: hf. "sets halftoneForm" combinationRule _ cr. destX _ destOrigin x. destY _ destOrigin y. sourceX _ sourceOrigin x. sourceY _ sourceOrigin y. width _ extent x. height _ extent y. aPoint _ clipRect origin. clipX _ aPoint x. clipY _ aPoint y. aPoint _ clipRect corner. clipWidth _ aPoint x - clipX. clipHeight _ aPoint y - clipY. sourceForm == nil ifFalse: [colorMap _ sourceForm colormapIfNeededFor: destForm]! ! !FXBlt methodsFor: 'private' stamp: 'ar 2/12/2000 19:51'! traceProblem: aString from: aContext RecursionLock == true ifTrue:[^self]. RecursionLock _ true. Transcript cr; show: aString; cr; show: aContext shortStack. RecursionLock _ false.! ! !FXBlt methodsFor: 'line drawing' stamp: 'ar 2/9/2000 19:21'! drawFrom: startPoint to: stopPoint ^ self drawFrom: startPoint to: stopPoint withFirstPoint: true! ! !FXBlt methodsFor: 'line drawing' stamp: 'ar 2/9/2000 19:21'! drawFrom: startPoint to: stopPoint withFirstPoint: drawFirstPoint "Draw a line whose end points are startPoint and stopPoint. The line is formed by repeatedly calling copyBits at every point along the line. If drawFirstPoint is false, then omit the first point so as not to overstrike at line junctions." | offset point1 point2 forwards | "Always draw down, or at least left-to-right" forwards _ (startPoint y = stopPoint y and: [startPoint x < stopPoint x]) or: [startPoint y < stopPoint y]. forwards ifTrue: [point1 _ startPoint. point2 _ stopPoint] ifFalse: [point1 _ stopPoint. point2 _ startPoint]. sourceForm == nil ifTrue: [destX _ point1 x. destY _ point1 y] ifFalse: [width _ sourceForm width. height _ sourceForm height. offset _ sourceForm offset. destX _ (point1 x + offset x) rounded. destY _ (point1 y + offset y) rounded]. "Note that if not forwards, then the first point is the last and vice versa. We agree to always paint stopPoint, and to optionally paint startPoint." (drawFirstPoint or: [forwards == false "ie this is stopPoint"]) ifTrue: [self copyBits]. self drawLoopX: (point2 x - point1 x) rounded Y: (point2 y - point1 y) rounded. (drawFirstPoint or: [forwards "ie this is stopPoint"]) ifTrue: [self copyBits]. ! ! !FXBlt methodsFor: 'line drawing' stamp: 'ar 2/22/2000 22:30'! drawLoopX: xDelta Y: yDelta "Primitive. Implements the Bresenham plotting algorithm (IBM Systems Journal, Vol. 4 No. 1, 1965). It chooses a principal direction, and maintains a potential, P. When P's sign changes, it is time to move in the minor direction as well. This particular version does not write the first and last points, so that these can be called for as needed in client code. Optional. See Object documentation whatIsAPrimitive." | dx dy px py P | dx _ xDelta sign. dy _ yDelta sign. px _ yDelta abs. py _ xDelta abs. "self copyBits." py > px ifTrue: ["more horizontal" P _ py // 2. 1 to: py do: [:i | destX _ destX + dx. (P _ P - px) < 0 ifTrue: [destY _ destY + dy. P _ P + py]. i < py ifTrue: [self copyBits]]] ifFalse: ["more vertical" P _ px // 2. 1 to: px do: [:i | destY _ destY + dy. (P _ P - py) < 0 ifTrue: [destX _ destX + dx. P _ P + px]. i < px ifTrue: [self copyBits]]]! ! !FXBlt methodsFor: 'warping' stamp: 'ar 2/22/2000 17:13'! cellSize ^warpQuality! ! !FXBlt methodsFor: 'warping' stamp: 'ar 2/10/2000 00:16'! cellSize: aNumber warpQuality _ aNumber.! ! !FXBlt methodsFor: 'warping' stamp: 'ar 2/22/2000 17:14'! copyQuad: pts toRect: destRect self sourceQuad: pts destRect: destRect. self warpBits! ! !FXBlt methodsFor: 'warping' stamp: 'ar 5/25/2000 22:05'! sourceForm: srcForm destRect: dstRectangle "Set up a WarpBlt from the entire source Form to the given destination rectangle." | w h | sourceForm _ srcForm. sourceX _ sourceY _ 0. destX _ dstRectangle left. destY _ dstRectangle top. width _ dstRectangle width. height _ dstRectangle height. w _ srcForm width. h _ srcForm height. (w = width and:[h = height]) ifTrue:[ "Don't need no stinking warp" warpQuad _ nil ] ifFalse:[ "Oh well ... " w _ 16384 * (w - 1). h _ 16384 * (h - 1). warpQuad _ Array with: 0@0 with: 0@h with: w@h with: w@0. ].! ! !FXBlt methodsFor: 'warping' stamp: 'ar 2/22/2000 17:21'! sourceQuad: pts destRect: aRectangle | fixedPt1 | sourceX _ sourceY _ 0. self destRect: aRectangle. fixedPt1 _ (pts at: 1) x isInteger ifTrue: [16384] ifFalse: [16384.0]. warpQuad _ pts collect:[:pt| pt * fixedPt1].! ! !FXBlt methodsFor: 'warping' stamp: 'ar 2/22/2000 19:35'! warpBits warpQuality ifNil:[warpQuality _ 1]. warpQuality > 1 ifTrue:[ "Must install source map" sourceMap _ sourceForm colormapToARGB. colorMap _ destForm colormapFromARGB. ]. self copyBits! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FXBlt class instanceVariableNames: ''! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/9/2000 19:48'! alphaBlendDemo "To run this demo, use... Display restoreAfter: [FXBlt alphaBlendDemo] Displays 10 alphas, then lets you paint. Option-Click to stop painting." "This code exhibits alpha blending in any display depth by performing the blend in an off-screen buffer with 32-bit pixels, and then copying the result back onto the screen with an appropriate color map. - tk 3/10/97" "This version uses a sliding buffer for painting that keeps pixels in 32 bits as long as they are in the buffer, so as not to lose info by converting down to display resolution and back up to 32 bits at each operation. - di 3/15/97" | brush buff dispToBuff buffToDisplay mapDto32 map32toD prevP p brushToBuff theta buffRect buffSize buffToBuff brushRect delta newBuffRect updateRect | "compute color maps if needed" Display depth <= 8 ifTrue: [ mapDto32 _ Color cachedColormapFrom: Display depth to: 32. map32toD _ Color cachedColormapFrom: 32 to: Display depth]. "display 10 different alphas, across top of screen" buff _ Form extent: 500@50 depth: 32. dispToBuff _ self toForm: buff. dispToBuff colorMap: mapDto32. dispToBuff copyFrom: (50@10 extent: 500@50) in: Display to: 0@0. 1 to: 10 do: [:i | dispToBuff fill: (50*(i-1)@0 extent: 50@50) fillColor: (Color red alpha: i/10) rule: Form blend]. buffToDisplay _ self toForm: Display. buffToDisplay colorMap: map32toD. buffToDisplay copyFrom: buff boundingBox in: buff to: 50@10. "Create a brush with radially varying alpha" brush _ Form extent: 30@30 depth: 32. 1 to: 5 do: [:i | brush fillShape: (Form dotOfSize: brush width*(6-i)//5) fillColor: (Color red alpha: 0.02 * i - 0.01) at: brush extent // 2]. "Now paint with the brush using alpha blending." buffSize _ 100. buff _ Form extent: brush extent + buffSize depth: 32. "Travelling 32-bit buffer" dispToBuff _ self toForm: buff. "This is from Display to buff" dispToBuff colorMap: mapDto32. brushToBuff _ self toForm: buff. "This is from brush to buff" brushToBuff sourceForm: brush; sourceOrigin: 0@0. brushToBuff combinationRule: Form blend. buffToBuff _ self toForm: buff. "This is for slewing the buffer" [Sensor yellowButtonPressed] whileFalse: [prevP _ nil. buffRect _ Sensor cursorPoint - (buffSize // 2) extent: buff extent. dispToBuff copyFrom: buffRect in: Display to: 0@0. [Sensor redButtonPressed] whileTrue: ["Here is the painting loop" p _ Sensor cursorPoint - (brush extent // 2). (prevP == nil or: [prevP ~= p]) ifTrue: [prevP == nil ifTrue: [prevP _ p]. (p dist: prevP) > buffSize ifTrue: ["Stroke too long to fit in buffer -- clip to buffer, and next time through will do more of it" theta _ (p-prevP) theta. p _ ((theta cos@theta sin) * buffSize asFloat + prevP) truncated]. brushRect _ p extent: brush extent. (buffRect containsRect: brushRect) ifFalse: ["Brush is out of buffer region. Scroll the buffer, and fill vacated regions from the display" delta _ brushRect amountToTranslateWithin: buffRect. buffToBuff copyFrom: buff boundingBox in: buff to: delta. newBuffRect _ buffRect translateBy: delta negated. (newBuffRect areasOutside: buffRect) do: [:r | dispToBuff copyFrom: r in: Display to: r origin - newBuffRect origin]. buffRect _ newBuffRect]. "Interpolate from prevP to p..." brushToBuff drawFrom: prevP - buffRect origin to: p - buffRect origin withFirstPoint: false. "Update (only) the altered pixels of the destination" updateRect _ (p min: prevP) corner: (p max: prevP) + brush extent. buffToDisplay copy: updateRect from: updateRect origin - buffRect origin in: buff. prevP _ p]]]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 18:16'! antiAliasDemo "To run this demo, use... Display restoreAfter: [FXBlt antiAliasDemo] Goes immediately into on-screen paint mode. Option-Click to stop painting." "This code exhibits alpha blending in any display depth by performing the blend in an off-screen buffer with 32-bit pixels, and then copying the result back onto the screen with an appropriate color map. - tk 3/10/97" "This version uses a sliding buffer for painting that keeps pixels in 32 bits as long as they are in the buffer, so as not to lose info by converting down to display resolution and back up to 32 bits at each operation. - di 3/15/97" "This version also uses WarpBlt to paint into twice as large a buffer, and then use smoothing when reducing back down to the display. In fact this same routine will now work for 3x3 soothing as well. Remove the statements 'buff displayAt: 0@0' to hide the buffer. - di 3/19/97" | brush buff dispToBuff buffToDisplay mapDto32 map32toD prevP p brushToBuff theta buffRect buffSize buffToBuff brushRect delta newBuffRect updateRect scale p0 | "compute color maps if needed" Display depth <= 8 ifTrue: [ mapDto32 _ Color cachedColormapFrom: Display depth to: 32. map32toD _ Color cachedColormapFrom: 32 to: Display depth]. "Create a brush with radially varying alpha" brush _ Form extent: 3@3 depth: 32. brush fill: brush boundingBox fillColor: (Color red alpha: 0.05). brush fill: (1@1 extent: 1@1) fillColor: (Color red alpha: 0.2). scale _ 2. "Actual drawing happens at this magnification" "Scale brush up for painting in magnified buffer" brush _ brush magnify: brush boundingBox by: scale. "Now paint with the brush using alpha blending." buffSize _ 100. buff _ Form extent: (brush extent + buffSize) * scale depth: 32. "Travelling 32-bit buffer" dispToBuff _ (self toForm: buff) "From Display to buff - magnify by 2" sourceForm: Display; colorMap: mapDto32; combinationRule: Form over. brushToBuff _ (self toForm: buff) "From brush to buff" sourceForm: brush; sourceOrigin: 0@0; combinationRule: Form blend. buffToDisplay _ (self toForm: Display) "From buff to Display - shrink by 2" sourceForm: buff; colorMap: map32toD; cellSize: scale; "...and use smoothing" combinationRule: Form over. buffToBuff _ self toForm: buff. "This is for slewing the buffer" [Sensor yellowButtonPressed] whileFalse: [prevP _ nil. buffRect _ Sensor cursorPoint - (buff extent // scale // 2) extent: buff extent // scale. p0 _ (buff extent // 2) - (buffRect extent // 2). dispToBuff copyQuad: buffRect innerCorners toRect: buff boundingBox. buff displayAt: 0@0. "** remove to hide sliding buffer **" [Sensor redButtonPressed] whileTrue: ["Here is the painting loop" p _ Sensor cursorPoint - buffRect origin + p0. "p, prevP are rel to buff origin" (prevP == nil or: [prevP ~= p]) ifTrue: [prevP == nil ifTrue: [prevP _ p]. (p dist: prevP) > (buffSize-1) ifTrue: ["Stroke too long to fit in buffer -- clip to buffer, and next time through will do more of it" theta _ (p-prevP) theta. p _ ((theta cos@theta sin) * (buffSize-2) asFloat + prevP) truncated]. brushRect _ p extent: brush extent. ((buff boundingBox insetBy: scale) containsRect: brushRect) ifFalse: ["Brush is out of buffer region. Scroll the buffer, and fill vacated regions from the display" delta _ (brushRect amountToTranslateWithin: (buff boundingBox insetBy: scale)) // scale. buffToBuff copyFrom: buff boundingBox in: buff to: delta*scale. newBuffRect _ buffRect translateBy: delta negated. p _ p translateBy: delta*scale. prevP _ prevP translateBy: delta*scale. (newBuffRect areasOutside: buffRect) do: [:r | dispToBuff copyQuad: r innerCorners toRect: (r origin - newBuffRect origin*scale extent: r extent*scale)]. buffRect _ newBuffRect]. "Interpolate from prevP to p..." brushToBuff drawFrom: prevP to: p withFirstPoint: false. buff displayAt: 0@0. "** remove to hide sliding buffer **" "Update (only) the altered pixels of the destination" updateRect _ (p min: prevP) corner: (p max: prevP) + brush extent. updateRect _ updateRect origin // scale * scale corner: updateRect corner + scale // scale * scale. buffToDisplay copyQuad: updateRect innerCorners toRect: (updateRect origin // scale + buffRect origin extent: updateRect extent // scale). prevP _ p]]]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/9/2000 19:49'! exampleOne "This tests BitBlt by displaying the result of all sixteen combination rules that BitBlt is capable of using. (Please see the comment in BitBlt for the meaning of the combination rules)." | path | path _ Path new. 0 to: 3 do: [:i | 0 to: 3 do: [:j | path add: j * 100 @ (i * 75)]]. Display fillWhite. path _ path translateBy: 60 @ 40. 1 to: 16 do: [:index | self exampleAt: (path at: index) rule: index - 1 fillColor: Color black] "FXBlt exampleOne"! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/9/2000 19:49'! exampleTwo "This is to test painting with a gray tone. It also tests that the seaming with gray patterns is correct in the microcode. Lets you paint for a while and then automatically stops." | f aBitBlt | "create a small black Form source as a brush. " f _ Form extent: 20 @ 20. f fillBlack. "create a BitBlt which will OR gray into the display. " aBitBlt _ self destForm: Display sourceForm: f fillColor: Color gray combinationRule: Form under destOrigin: Sensor cursorPoint sourceOrigin: 0 @ 0 extent: f extent clipRect: Display computeBoundingBox. "paint the gray Form on the screen for a while. " [Sensor anyButtonPressed] whileFalse: [aBitBlt destOrigin: Sensor cursorPoint. aBitBlt copyBits] "FXBlt exampleTwo"! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 17:22'! warpTest1 "Display restoreAfter: [FXBlt warpTest1]" "Demonstrates variable scale and rotate" | warp pts r1 p0 p ext | Utilities informUser: 'Choose a rectangle with interesting stuff' during: [r1 _ Rectangle originFromUser: 50@50. Sensor waitNoButton]. Utilities informUser: 'Now click down and up and move the mouse around the dot' during: [p0 _ Sensor waitClickButton. (Form dotOfSize: 8) displayAt: p0]. warp _ (self toForm: Display) clipRect: (0@0 extent: r1 extent*5); sourceForm: Display; combinationRule: Form over. [Sensor anyButtonPressed] whileFalse: [p _ Sensor cursorPoint. pts _ {r1 topLeft. r1 bottomLeft. r1 bottomRight. r1 topRight} collect: [:pt | pt rotateBy: (p-p0) theta about: r1 center]. ext _ (r1 extent*((p-p0) r / 20.0 max: 0.1)) asIntegerPoint. warp copyQuad: pts toRect: (r1 extent*5-ext//2 extent: ext)]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 17:23'! warpTest12 "Display restoreAfter: [FXBlt warpTest12]" "Just like test1, but comparing smooth to non-smooth warps" | warp pts r1 p0 p ext warp2 | Utilities informUser: 'Choose a rectangle with interesting stuff' during: [r1 _ Rectangle originFromUser: 50@50. Sensor waitNoButton]. Utilities informUser: 'Now click down and up and move the mouse around the dot' during: [p0 _ Sensor waitClickButton. (Form dotOfSize: 8) displayAt: p0]. warp _ (self toForm: Display) cellSize: 2; "installs a colormap" clipRect: (0@0 extent: r1 extent*5); sourceForm: Display; combinationRule: Form over. warp2 _ (self toForm: Display) clipRect: ((0@0 extent: r1 extent*5) translateBy: 200@0); sourceForm: Display; combinationRule: Form over. [Sensor anyButtonPressed] whileFalse: [p _ Sensor cursorPoint. pts _ {r1 topLeft. r1 bottomLeft. r1 bottomRight. r1 topRight} collect: [:pt | pt rotateBy: (p-p0) theta about: r1 center]. ext _ (r1 extent*((p-p0) r / 20.0 max: 0.1)) asIntegerPoint. warp copyQuad: pts toRect: (r1 extent*5-ext//2 extent: ext). warp2 copyQuad: pts toRect: ((r1 extent*5-ext//2 extent: ext) translateBy: 200@0). ]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 17:31'! warpTest3 "Display restoreAfter: [FXBlt warpTest3]" "The Squeak Release Mandala - 9/23/96 di" "Move the mouse near the center of the square. Up and down affects shrink/grow Left and right affect rotation angle" | warp pts p0 p box | box _ 100@100 extent: 300@300. Display border: (box expandBy: 2) width: 2. warp _ (self toForm: Display) clipRect: box; sourceForm: Display; combinationRule: Form over. p0 _ box center. [Sensor anyButtonPressed] whileFalse: [p _ Sensor cursorPoint. pts _ (box insetBy: p y - p0 y) innerCorners collect: [:pt | pt rotateBy: p x - p0 x / 50.0 about: p0]. warp copyQuad: pts toRect: box]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 17:32'! warpTest4 "Display restoreAfter: [FXBlt warpTest4]" "The Squeak Release Mandala - 9/23/96 di This version does smoothing" "Move the mouse near the center ofhe square. Up and dn affects shrink/grow Left and right affect rotation angle" | warp pts p0 p box | box _ 100@100 extent: 300@300. Display border: (box expandBy: 2) width: 2. warp _ (self toForm: Display) clipRect: box; sourceForm: Display; cellSize: 2; "installs a colormap" combinationRule: Form over. p0 _ box center. [Sensor anyButtonPressed] whileFalse: [p _ Sensor cursorPoint. pts _ (box insetBy: p y - p0 y) innerCorners collect: [:pt | pt rotateBy: p x - p0 x / 50.0 about: p0]. warp copyQuad: pts toRect: box]! ! !FXBlt class methodsFor: 'examples' stamp: 'ar 2/22/2000 17:33'! warpTest5 "Display restoreAfter: [FXBlt warpTest5]" "Demonstrates variable scale and rotate" | warp pts r1 p0 p | Utilities informUser: 'Choose a rectangle with interesting stuff' during: [r1 _ Rectangle fromUser. Sensor waitNoButton]. Utilities informUser: 'Now click down and up and move the mouse around the dot' during: [p0 _ Sensor waitClickButton. (Form dotOfSize: 8) displayAt: p0]. warp _ (self toForm: Display) cellSize: 1; sourceForm: Display; cellSize: 2; "installs a colormap" combinationRule: Form over. [Sensor anyButtonPressed] whileFalse: [p _ Sensor cursorPoint. pts _ {r1 topLeft. r1 bottomLeft. r1 bottomRight. r1 topRight} collect: [:pt | pt rotateBy: (p-p0) theta about: r1 center]. warp copyQuad: pts toRect: (r1 translateBy: r1 width@0)]! ! !FXBlt class methodsFor: 'form rotation' stamp: 'ar 2/22/2000 17:35'! rotate: srcForm degrees: angleInDegrees center: aPoint scaleBy: scalePoint smoothing: cellSize "Rotate the given Form the given number of degrees about the given center and scale its width and height by x and y of the given scale point. Smooth using the given cell size, an integer between 1 and 3, where 1 means no smoothing. Return a pair where the first element is the rotated Form and the second is the position offset required to align the center of the rotated Form with that of the original. Note that the dimensions of the resulting Form generally differ from those of the original." | srcRect center radians dstOrigin dstCorner p dstRect inverseScale quad dstForm newCenter warpSrc | srcRect _ srcForm boundingBox. center _ srcRect center. radians _ angleInDegrees degreesToRadians. dstOrigin _ dstCorner _ center. srcRect corners do: [:corner | "find the limits of a rectangle that just encloses the rotated original; in general, this rectangle will be larger than the original (e.g., consider a square rotated by 45 degrees)" p _ ((corner - center) scaleBy: scalePoint) + center. p _ (p rotateBy: radians about: center) rounded. dstOrigin _ dstOrigin min: p. dstCorner _ dstCorner max: p]. "rotate the enclosing rectangle back to get the source quadrilateral" dstRect _ dstOrigin corner: dstCorner. inverseScale _ (1.0 / scalePoint x)@(1.0 / scalePoint y). quad _ dstRect innerCorners collect: [:corner | p _ corner rotateBy: radians negated about: center. ((p - center) scaleBy: inverseScale) + center]. "make a Form to hold the result and do the rotation" warpSrc _ srcForm. (srcForm isKindOf: ColorForm) ifTrue: [ cellSize > 1 ifTrue: [ warpSrc _ Form extent: srcForm extent depth: 16. srcForm displayOn: warpSrc. dstForm _ Form extent: dstRect extent depth: 16] "use 16-bit depth to allow smoothing" ifFalse: [ dstForm _ srcForm class extent: dstRect extent depth: srcForm depth]] ifFalse: [ dstForm _ srcForm class extent: dstRect extent depth: srcForm depth]. (self toForm: dstForm) sourceForm: warpSrc; colorMap: (dstForm colormapIfNeededFor: warpSrc); cellSize: cellSize; "installs a new colormap if cellSize > 1" combinationRule: Form paint; copyQuad: quad toRect: dstForm boundingBox. (dstForm isKindOf: ColorForm) ifTrue: [dstForm colors: srcForm colors copy]. newCenter _ (center rotateBy: radians about: aPoint) truncated. ^ Array with: dstForm with: dstRect origin + (newCenter - center) ! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 5/28/2000 12:04'! asGrafPort "Return the GrafPort associated with the receiver" ^FXGrafPort! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 2/9/2000 19:48'! bitPeekerFromForm: sourceForm "Answer an instance to be used extract individual pixels from the given Form. The destination for a 1x1 copyBits will be the low order bits of (bits at: 1)." | pixPerWord | pixPerWord _ 32 // sourceForm depth. sourceForm unhibernate. ^ self destForm: (Form extent: pixPerWord@1 depth: sourceForm depth) sourceForm: sourceForm halftoneForm: nil combinationRule: Form over destOrigin: (pixPerWord - 1)@0 sourceOrigin: 0@0 extent: 1@1 clipRect: (0@0 extent: pixPerWord@1) ! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 2/9/2000 19:48'! bitPokerToForm: destForm "Answer an instance to be used for valueAt: aPoint put: pixValue. The source for a 1x1 copyBits will be the low order of (bits at: 1)" | pixPerWord | pixPerWord _ 32//destForm depth. destForm unhibernate. ^ self destForm: destForm sourceForm: (Form extent: pixPerWord@1 depth: destForm depth) halftoneForm: nil combinationRule: Form over destOrigin: 0@0 sourceOrigin: (pixPerWord-1)@0 extent: 1@1 clipRect: (0@0 extent: destForm extent) ! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 2/9/2000 19:48'! destForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect "Answer an instance of me with values set according to the arguments." ^ self new setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 2/9/2000 19:48'! destForm: df sourceForm: sf halftoneForm: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect "Answer an instance of me with values set according to the arguments." ^ self new setDestForm: df sourceForm: sf fillColor: hf combinationRule: cr destOrigin: destOrigin sourceOrigin: sourceOrigin extent: extent clipRect: clipRect! ! !FXBlt class methodsFor: 'instance creation' stamp: 'ar 2/9/2000 19:48'! toForm: aForm ^ self new setDestForm: aForm! ! !FXBlt class methodsFor: 'benchmarks' stamp: 'ar 2/9/2000 19:48'! benchDiffsFrom: before to: afterwards "Given two outputs of BitBlt>>benchmark show the relative improvements." | old new log oldLine newLine oldVal newVal improvement | log _ WriteStream on: String new. old _ ReadStream on: before. new _ ReadStream on: afterwards. [old atEnd or:[new atEnd]] whileFalse:[ oldLine _ old upTo: Character cr. newLine _ new upTo: Character cr. (oldLine includes: Character tab) ifTrue:[ oldLine _ ReadStream on: oldLine. newLine _ ReadStream on: newLine. Transcript cr; show: (oldLine upTo: Character tab); tab. log cr; nextPutAll: (newLine upTo: Character tab); tab. [oldLine skipSeparators. newLine skipSeparators. oldLine atEnd] whileFalse:[ oldVal _ Integer readFrom: oldLine. newVal _ Integer readFrom: newLine. improvement _ oldVal asFloat / newVal asFloat roundTo: 0.1. Transcript show: improvement printString; tab. log print: improvement; tab]. ] ifFalse:[ Transcript cr; show: oldLine. log cr; nextPutAll: oldLine. ]. ]. ^log contents! ! !FXBlt class methodsFor: 'benchmarks' stamp: 'ar 5/25/2000 17:58'! benchmark "FXBlt benchmark" "Run a benchmark on different combinations rules, source/destination depths and BitBlt modes. Note: This benchmark doesn't give you any 'absolute' value - it is intended only for benchmarking improvements in the bitblt code and nothing else. Attention: *this*may*take*a*while*" | bb source dest destRect log t | log _ WriteStream on: String new. destRect _ 0@0 extent: 600@600. "Form paint/Form over - the most common rules" #( 25 3 ) do:[:rule| Transcript cr; show:'---- Combination rule: ', rule printString,' ----'. log cr; nextPutAll:'---- Combination rule: ', rule printString,' ----'. #(1 2 4 8 16 32) do:[:destDepth| dest _ nil. dest _ Form extent: destRect extent depth: destDepth. Transcript cr. log cr. #(1 2 4 8 16 32) do:[:sourceDepth| Transcript cr; show: sourceDepth printString, ' => ', destDepth printString. log cr; nextPutAll: sourceDepth printString, ' => ', destDepth printString. source _ nil. bb _ nil. source _ Form extent: destRect extent depth: sourceDepth. (source getCanvas) fillOval: destRect color: Color yellow borderWidth: 30 borderColor: Color black. bb _ FXBlt toForm: dest. bb raiseErrors: true. bb sourceForm: source. bb sourceRect: source boundingBox. bb destRect: destRect. bb colorMap: (source colormapIfNeededFor: dest). bb combinationRule: rule. "Measure speed of copyBits" t _ Time millisecondsToRun:[bb copyBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. bb sourceForm: source destRect: source boundingBox. "Measure speed of 1x1 warpBits" bb cellSize: 1. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. "Measure speed of 2x2 warpBits" bb cellSize: 2. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. "Measure speed of 3x3 warpBits" bb cellSize: 3. t _ Time millisecondsToRun:[bb warpBits]. Transcript tab; show: t printString. log tab; nextPutAll: t printString. ]. ]. ]. ^log contents! ! !FXBlt class methodsFor: 'private' stamp: 'ar 2/9/2000 19:50'! exampleAt: originPoint rule: rule fillColor: mask "This builds a source and destination form and copies the source to the destination using the specifed rule and mask. It is called from the method named exampleOne." | s d border aBitBlt | border_Form extent: 32@32. border fillBlack. border fill: (1@1 extent: 30@30) fillColor: Color white. s _ Form extent: 32@32. s fillWhite. s fillBlack: (7@7 corner: 25@25). d _ Form extent: 32@32. d fillWhite. d fillBlack: (0@0 corner: 32@16). s displayOn: Display at: originPoint. border displayOn: Display at: originPoint rule: Form under. d displayOn: Display at: originPoint + (s width @0). border displayOn: Display at: originPoint + (s width @0) rule: Form under. d displayOn: Display at: originPoint + (s extent // (2 @ 1)). aBitBlt _ self destForm: Display sourceForm: s fillColor: mask combinationRule: rule destOrigin: originPoint + (s extent // (2 @ 1)) sourceOrigin: 0 @ 0 extent: s extent clipRect: Display computeBoundingBox. aBitBlt copyBits. border displayOn: Display at: originPoint + (s extent // (2 @ 1)) rule: Form under. "BitBlt exampleAt: 100@100 rule: Form over fillColor: Color gray"! ! !FXBlt class methodsFor: 'private' stamp: 'ar 2/10/2000 00:34'! swapBytesIn: aBitmap "Byte-swap the contents of aBitmap" "Notes: * We could setup BitBlt so that it uses aBitmap as source but it's faster without a source * We could move the conversion into the destMap but I'm planning to add a color cache for conversions and this cache will definitely be used for colorMap." | bb form | form _ Form extent: aBitmap size @ 1 depth: 32 bits: aBitmap. bb _ self toForm: form. bb fillColor: (Bitmap with: 16rFFFFFFFF). "No conversion when reading the pixels" bb destMap:(ColorMap shifts: #(0 0 0 0) masks: #(16rFF0000 16rFF00 16rFF 16rFF000000)). "<- so mask is identity!!" "But do swap when writing" bb colorMap:(ColorMap shifts: #(-24 -8 8 24) masks: #(16rFF000000 16rFF0000 16rFF00 16rFF)). "Set the combination rule to #destinationWord:with: so that the result is just the word we have in destForm. Only byte swapped." bb combinationRule: 5. "And swap those bytes" bb copyBits.! ! InterpreterPlugin subclass: #FXBltSimulation instanceVariableNames: 'bitBltOop destForm sourceForm halftoneForm combinationRule destX destY sourceX sourceY width height clipX clipY clipWidth clipHeight destBits destWidth destHeight destDepth destPitch destPPW destMSB destIndex destDelta sourceBits sourceWidth sourceHeight sourceDepth sourcePitch sourcePPW sourceMSB sourceIndex sourceDelta noColorMap colorMap cmMask cmShiftTable cmMaskTable noDestMap destMap dmMask dmShiftTable dmMaskTable noSourceMap sourceMap smMask smShiftTable smMaskTable tallyMap tallyMapSize sourceAlpha sourceAlphaKey destAlphaKey srcKeyMode dstKeyMode bitCount skew mask1 mask2 preload nWords destMask hDir vDir sx sy dx dy bbW bbH halftoneHeight noSource noHalftone halftoneBase srcBitShift dstBitShift pixelDepth cmCache affectedL affectedR affectedT affectedB opTable maskTable ditherMatrix4x4 ditherThresholds16 ditherValues16 hasSurfaceLock noWarp warpQuad warpQuality warpSrcShift warpSrcMask warpAlignShift warpAlignMask warpBitShiftTable unlockSurfaceFn querySurfaceFn lockSurfaceFn ' classVariableNames: 'AllOnes AlphaIndex BBClipHeightIndex BBClipWidthIndex BBClipXIndex BBClipYIndex BBColorMapIndex BBDestFormIndex BBDestXIndex BBDestYIndex BBHalftoneFormIndex BBHeightIndex BBRuleIndex BBSourceFormIndex BBSourceXIndex BBSourceYIndex BBWidthIndex BinaryPoint BlueIndex ColorCacheMask ColorCacheSize FXDestKeyIndex FXDestMapIndex FXSourceAlphaIndex FXSourceKeyIndex FXSourceMapIndex FXTallyMapIndex FXWarpQuadIndex FXWarpQualityIndex FixedPt1 FormBitsIndex FormDepthIndex FormHeightIndex FormWidthIndex GreenIndex InvalidColorCacheEntry OpTable OpTableSize RedIndex ' poolDictionaries: '' category: 'Graphics-FXBlt'! !FXBltSimulation commentStamp: '' prior: 0! This class implements BitBlt, much as specified in the Blue Book spec. Performance has been enhanced through the use of pointer variables such as sourceIndex and destIndex, and by separating several special cases of the inner loop. Operation has been extended to color, with support for 1, 2, 4, 8, 16, and 32-bit pixel sizes. Conversion between different pixel sizes is facilitated by accepting an optional color map. In addition to the original 16 combination rules, this BitBlt supports 16 fail (for old paint mode) 17 fail (for old mask mode) 18 sourceWord + destinationWord 19 sourceWord - destinationWord 20 rgbAdd: sourceWord with: destinationWord 21 rgbSub: sourceWord with: destinationWord 22 OLDrgbDiff: sourceWord with: destinationWord 23 OLDtallyIntoMap: destinationWord -- old vers doesn't clip to bit boundary 24 alphaBlend: sourceWord with: destinationWord 25 pixPaint: sourceWord with: destinationWord 26 pixMask: sourceWord with: destinationWord 27 rgbMax: sourceWord with: destinationWord 28 rgbMin: sourceWord with: destinationWord 29 rgbMin: sourceWord bitInvert32 with: destinationWord 30 alphaBlendConst: sourceWord with: destinationWord -- alpha passed as an arg 31 alphaPaintConst: sourceWord with: destinationWord -- alpha passed as an arg 32 rgbDiff: sourceWord with: destinationWord 33 tallyIntoMap: destinationWord 34 alphaBlendScaled: sourceWord with: destinationWord 35 srcPaint: sourceWord with: destinationWord 36 dstPaint: sourceWord with: destinationWord This implementation has also been fitted with an experimental "warp drive" that allows abritrary scaling and rotation (and even limited affine deformations) with all BitBlt storage modes supported. To add a new rule to BitBlt... 1. add the new rule method or methods in the category 'combination rules' of BBSim 2. describe it in the class comment of BBSim and in the class comment for BitBlt 3. add refs to initializeRuleTable in proper positions 4. add refs to initBBOpTable, following the pattern ! !FXBltSimulation methodsFor: 'initialize' stamp: 'ar 5/25/2000 15:20'! initBBOpTable self cCode: 'opTable[0+1] = (int)clearWordwith'. self cCode: 'opTable[1+1] = (int)bitAndwith'. self cCode: 'opTable[2+1] = (int)bitAndInvertwith'. self cCode: 'opTable[3+1] = (int)sourceWordwith'. self cCode: 'opTable[4+1] = (int)bitInvertAndwith'. self cCode: 'opTable[5+1] = (int)destinationWordwith'. self cCode: 'opTable[6+1] = (int)bitXorwith'. self cCode: 'opTable[7+1] = (int)bitOrwith'. self cCode: 'opTable[8+1] = (int)bitInvertAndInvertwith'. self cCode: 'opTable[9+1] = (int)bitInvertXorwith'. self cCode: 'opTable[10+1] = (int)bitInvertDestinationwith'. self cCode: 'opTable[11+1] = (int)bitOrInvertwith'. self cCode: 'opTable[12+1] = (int)bitInvertSourcewith'. self cCode: 'opTable[13+1] = (int)bitInvertOrwith'. self cCode: 'opTable[14+1] = (int)bitInvertOrInvertwith'. self cCode: 'opTable[15+1] = (int)destinationWordwith'. self cCode: 'opTable[16+1] = (int)destinationWordwith'. self cCode: 'opTable[17+1] = (int)destinationWordwith'. self cCode: 'opTable[18+1] = (int)addWordwith'. self cCode: 'opTable[19+1] = (int)subWordwith'. self cCode: 'opTable[20+1] = (int)rgbAddwith'. self cCode: 'opTable[21+1] = (int)rgbSubwith'. self cCode: 'opTable[22+1] = (int)OLDrgbDiffwith'. self cCode: 'opTable[23+1] = (int)OLDtallyIntoMapwith'. self cCode: 'opTable[24+1] = (int)alphaBlendwith'. self cCode: 'opTable[25+1] = (int)pixPaintwith'. self cCode: 'opTable[26+1] = (int)pixMaskwith'. self cCode: 'opTable[27+1] = (int)rgbMaxwith'. self cCode: 'opTable[28+1] = (int)rgbMinwith'. self cCode: 'opTable[29+1] = (int)rgbMinInvertwith'. self cCode: 'opTable[30+1] = (int)alphaBlendConstwith'. self cCode: 'opTable[31+1] = (int)alphaPaintConstwith'. self cCode: 'opTable[32+1] = (int)rgbDiffwith'. self cCode: 'opTable[33+1] = (int)tallyIntoMapwith'. self cCode: 'opTable[34+1] = (int)alphaBlendScaledwith'. self cCode: 'opTable[35+1] = (int)srcPaintwith'. self cCode: 'opTable[36+1] = (int)dstPaintwith'. ! ! !FXBltSimulation methodsFor: 'initialize' stamp: 'ar 5/25/2000 15:20'! initialiseModule self export: true. self initBBOpTable. ^true! ! !FXBltSimulation methodsFor: 'initialize' stamp: 'ar 5/25/2000 16:11'! moduleUnloaded: aModuleName "The module with the given name was just unloaded. Make sure we have no dangling references." self export: true. self var: #aModuleName type: 'char *'. (aModuleName strcmp: 'SurfacePlugin') = 0 ifTrue:[ "The surface plugin just shut down. How nasty." querySurfaceFn _ lockSurfaceFn _ unlockSurfaceFn _ 0. ].! ! !FXBltSimulation methodsFor: 'primitives' stamp: 'ar 2/21/2000 20:24'! primitiveCopyBits | rcvr | self export: true. self inline: false. rcvr _ interpreterProxy stackValue: interpreterProxy methodArgumentCount. (self loadBitBltFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. self copyBits. self showDisplayBits.! ! !FXBltSimulation methodsFor: 'primitives' stamp: 'ar 2/21/2000 20:24'! primitiveDrawLoop "Invoke the line drawing primitive." | rcvr xDelta yDelta | self export: true. self inline: false. rcvr _ interpreterProxy stackValue: 2. xDelta _ interpreterProxy stackIntegerValue: 1. yDelta _ interpreterProxy stackIntegerValue: 0. (self loadBitBltFrom: rcvr) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ self drawLoopX: xDelta Y: yDelta. self showDisplayBits]. interpreterProxy failed ifFalse:[interpreterProxy pop: 2].! ! !FXBltSimulation methodsFor: 'primitives' stamp: 'ar 2/23/2000 21:39'! primitiveVersion "Return the version of FXBlt" interpreterProxy pop: interpreterProxy methodArgumentCount+1. interpreterProxy pushInteger: self version. ! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 5/25/2000 15:10'! drawLoopX: xDelta Y: yDelta "This is the primitive implementation of the line-drawing loop. See the comments in BitBlt>>drawLoopX:Y:" | dx1 dy1 px py P affL affR affT affB | xDelta > 0 ifTrue: [dx1 _ 1] ifFalse: [xDelta = 0 ifTrue: [dx1 _ 0] ifFalse: [dx1 _ -1]]. yDelta > 0 ifTrue: [dy1 _ 1] ifFalse: [yDelta = 0 ifTrue: [dy1 _ 0] ifFalse: [dy1 _ -1]]. px _ yDelta abs. py _ xDelta abs. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999. py > px ifTrue: ["more horizontal" P _ py // 2. 1 to: py do: [:i | destX _ destX + dx1. (P _ P - px) < 0 ifTrue: [destY _ destY + dy1. P _ P + py]. i < py ifTrue: [self copyBits. interpreterProxy failed ifTrue: [^ nil "bail out now on failure -- avoid storing x,y"]. (affectedL < affectedR and: [affectedT < affectedB]) ifTrue: ["Affected rectangle grows along the line" affL _ affL min: affectedL. affR _ affR max: affectedR. affT _ affT min: affectedT. affB _ affB max: affectedB. (affR - affL) * (affB - affT) > 4000 ifTrue: ["If affected rectangle gets large, update it in chunks" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. self showDisplayBits. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999]]. ]]] ifFalse: ["more vertical" P _ px // 2. 1 to: px do: [:i | destY _ destY + dy1. (P _ P - py) < 0 ifTrue: [destX _ destX + dx1. P _ P + px]. i < px ifTrue: [self copyBits. interpreterProxy failed ifTrue: [^ nil "bail out now on failure -- avoid storing x,y"]. (affectedL < affectedR and: [affectedT < affectedB]) ifTrue: ["Affected rectangle grows along the line" affL _ affL min: affectedL. affR _ affR max: affectedR. affT _ affT min: affectedT. affB _ affB max: affectedB. (affR - affL) * (affB - affT) > 4000 ifTrue: ["If affected rectangle gets large, update it in chunks" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. self showDisplayBits. affL _ affT _ 9999. "init null rectangle" affR _ affB _ -9999]]. ]]]. "Remaining affected rect" affectedL _ affL. affectedR _ affR. affectedT _ affT. affectedB _ affB. "store destX, Y back" interpreterProxy storeInteger: BBDestXIndex ofObject: bitBltOop withValue: destX. interpreterProxy storeInteger: BBDestYIndex ofObject: bitBltOop withValue: destY.! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/19/2000 20:50'! fetchIntOrFloat: index ofObject: oop | fieldOop floatValue | self var: #floatValue declareC:'double floatValue'. fieldOop _ interpreterProxy fetchPointer: index ofObject: oop. (interpreterProxy isIntegerObject: fieldOop) ifTrue:[^interpreterProxy integerValueOf: fieldOop]. floatValue _ interpreterProxy floatValueOf: fieldOop. (-2147483648.0 <= floatValue and:[floatValue <= 2147483647.0]) ifFalse:[interpreterProxy primitiveFail. ^0]. ^floatValue asInteger! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 1/16/2000 21:26'! fetchIntOrFloat: index ofObject: oop ifNil: defaultValue | fieldOop floatValue | self var: #floatValue declareC:'double floatValue'. fieldOop _ interpreterProxy fetchPointer: index ofObject: oop. (fieldOop = interpreterProxy nilObject) ifTrue:[^defaultValue]. (interpreterProxy isIntegerObject: fieldOop) ifTrue:[^interpreterProxy integerValueOf: fieldOop]. floatValue _ interpreterProxy floatValueOf: fieldOop. (-2147483648.0 <= floatValue and:[floatValue <= 2147483647.0]) ifFalse:[interpreterProxy primitiveFail]. ^floatValue asInteger! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 1/16/2000 16:32'! loadBitBltClipRect "Load the clipping rectangle from a BitBlt oop." self inline: true. clipX _ self fetchIntOrFloat: BBClipXIndex ofObject: bitBltOop ifNil: 0. clipY _ self fetchIntOrFloat: BBClipYIndex ofObject: bitBltOop ifNil: 0. clipWidth _ self fetchIntOrFloat: BBClipWidthIndex ofObject: bitBltOop ifNil: destWidth. clipHeight _ self fetchIntOrFloat: BBClipHeightIndex ofObject: bitBltOop ifNil: destHeight. interpreterProxy failed ifTrue: [^ false "non-integer value"]. clipX < 0 ifTrue: [clipWidth _ clipWidth + clipX. clipX _ 0]. clipY < 0 ifTrue: [clipHeight _ clipHeight + clipY. clipY _ 0]. clipX+clipWidth > destWidth ifTrue: [clipWidth _ destWidth - clipX]. clipY+clipHeight > destHeight ifTrue: [clipHeight _ destHeight - clipY]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 1/16/2000 16:32'! loadBitBltCombinationRule "Load the combination rule from a BitBlt oop" self inline: true. combinationRule _ interpreterProxy fetchInteger: BBRuleIndex ofObject: bitBltOop. (interpreterProxy failed or: [combinationRule < 0 or: [combinationRule > (OpTableSize - 2)]]) ifTrue: [^ false "operation out of range"]. (combinationRule >= 16 and: [combinationRule <= 17]) ifTrue: [^ false "fail for old simulated paint, erase modes"]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/13/2000 15:38'! loadBitBltDestForm "Load the dest form for BitBlt. Return false if anything is wrong, true otherwise." | destBitsSize | self inline: true. destForm _ interpreterProxy fetchPointer: BBDestFormIndex ofObject: bitBltOop. ((interpreterProxy isPointers: destForm) and: [ (interpreterProxy slotSizeOf: destForm) >= 4]) ifFalse: [^ false]. destBits _ interpreterProxy fetchPointer: FormBitsIndex ofObject: destForm. destWidth _ interpreterProxy fetchInteger: FormWidthIndex ofObject: destForm. destHeight _ interpreterProxy fetchInteger: FormHeightIndex ofObject: destForm. (destWidth >= 0 and: [destHeight >= 0]) ifFalse: [^ false]. destDepth _ interpreterProxy fetchInteger: FormDepthIndex ofObject: destForm. (destDepth bitAnd: destDepth-1) = 0 ifFalse:[^false]. "Ignore an integer bits handle for Display in which case the appropriate values will be obtained by calling ioLockSurfaceBits()." (interpreterProxy isIntegerObject: destBits) ifTrue:[ "Query for actual surface dimensions" (self queryDestSurface: (interpreterProxy integerValueOf: destBits)) ifFalse:[^false]. destPPW _ 32 // destDepth. destBits _ destPitch _ 0. ] ifFalse:[ destPPW _ 32 // destDepth. destPitch _ destWidth + (destPPW-1) // destPPW * 4. destBitsSize _ interpreterProxy byteSizeOf: destBits. ((interpreterProxy isWordsOrBytes: destBits) and: [destBitsSize = (destPitch * destHeight)]) ifFalse: [^ false]. ((interpreterProxy isWords: destBits) and:[ (interpreterProxy fetchClassOf: destBits) = interpreterProxy classBitmap]) ifTrue:[destMSB _ true] ifFalse:[destMSB _ false]. "Skip header since external bits don't have one" destBits _ self cCoerce: (interpreterProxy firstIndexableField: destBits) to:'int'. ]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 1/16/2000 16:32'! loadBitBltDestRect "Load the destination rectangle from a BitBlt oop" self inline: true. destX _ self fetchIntOrFloat: BBDestXIndex ofObject: bitBltOop ifNil: 0. destY _ self fetchIntOrFloat: BBDestYIndex ofObject: bitBltOop ifNil: 0. width _ self fetchIntOrFloat: BBWidthIndex ofObject: bitBltOop ifNil: destWidth. height _ self fetchIntOrFloat: BBHeightIndex ofObject: bitBltOop ifNil: destHeight. ^interpreterProxy failed not! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/22/2000 17:45'! loadBitBltFrom: bbObj "Load context from BitBlt instance. Return false if anything is amiss" | ok | self export: true. bitBltOop _ bbObj. ok _ self loadBitBltCombinationRule. ok ifFalse:[^false]. ok _ self loadBitBltDestForm. ok ifFalse:[^false]. ok _ self loadBitBltDestRect. ok ifFalse:[^false]. ok _ self loadBitBltSourceForm. ok ifFalse:[^false]. ok _ self loadHalftoneForm. ok ifFalse:[^false]. ok _ self loadBitBltClipRect. ok ifFalse:[^false]. ok _ self loadFXColorMap. ok ifFalse:[^false]. ok _ self loadFXSourceMap. ok ifFalse:[^false]. ok _ self loadFXWarpQuad. ok ifFalse:[^false]. ok _ self loadFXWarpQuality. ok ifFalse:[^false]. ok _ self loadFXSourceMap. ok ifFalse:[^false]. ok _ self loadFXDestMap. ok ifFalse:[^false]. ok _ self loadFXAlphaValues. ok ifFalse:[^false]. ok _ self loadFXTallyMap. ok ifFalse:[^false]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/13/2000 15:39'! loadBitBltSourceForm "Load the source form for BitBlt. Return false if anything is wrong, true otherwise." | sourceBitsSize | self inline: true. sourceForm _ interpreterProxy fetchPointer: BBSourceFormIndex ofObject: bitBltOop. noSource _ self ignoreSourceOrHalftone: sourceForm. noSource ifTrue:[ sourceX _ sourceY _ 0. ^true]. ((interpreterProxy isPointers: sourceForm) and:[ (interpreterProxy slotSizeOf: sourceForm) >= 4]) ifFalse: [^ false]. sourceX _ self fetchIntOrFloat: BBSourceXIndex ofObject: bitBltOop ifNil: 0. sourceY _ self fetchIntOrFloat: BBSourceYIndex ofObject: bitBltOop ifNil: 0. sourceBits _ interpreterProxy fetchPointer: FormBitsIndex ofObject: sourceForm. sourceWidth _ interpreterProxy fetchInteger: FormWidthIndex ofObject: sourceForm. sourceHeight _ interpreterProxy fetchInteger: FormHeightIndex ofObject: sourceForm. interpreterProxy failed ifTrue:[^false]. (sourceWidth >= 0 and: [sourceHeight >= 0]) ifFalse: [^ false]. sourceDepth _ interpreterProxy fetchInteger: FormDepthIndex ofObject: sourceForm. (sourceDepth bitAnd: sourceDepth-1) = 0 ifFalse:[^false]. "Ignore an integer bits handle for Display in which case the appropriate values will be obtained by calling ioLockSurfaceBits()." (interpreterProxy isIntegerObject: sourceBits) ifTrue:[ "Query for actual surface dimensions" (self querySourceSurface: (interpreterProxy integerValueOf: sourceBits)) ifFalse:[^false]. sourcePPW _ 32 // sourceDepth. sourceBits _ sourcePitch _ 0. ] ifFalse:[ sourcePPW _ 32 // sourceDepth. sourcePitch _ sourceWidth + (sourcePPW-1) // sourcePPW * 4. sourceBitsSize _ interpreterProxy byteSizeOf: sourceBits. ((interpreterProxy isWordsOrBytes: sourceBits) and: [sourceBitsSize = (sourcePitch * sourceHeight)]) ifFalse: [^ false]. ((interpreterProxy isWords: sourceBits) and:[ (interpreterProxy fetchClassOf: sourceBits) = interpreterProxy classBitmap]) ifTrue:[sourceMSB _ true] ifFalse:[sourceMSB _ false]. "Skip header since external bits don't have one" sourceBits _ self cCoerce: (interpreterProxy firstIndexableField: sourceBits) to:'int'. ]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/22/2000 15:16'! loadFXAlphaValues "Load the source/destination transparency keys" | oop | self inline: true. oop _ interpreterProxy fetchPointer: FXSourceKeyIndex ofObject: bitBltOop. srcKeyMode _ (oop ~= interpreterProxy nilObject). srcKeyMode ifTrue:[sourceAlphaKey _ self fetchIntOrFloat: FXSourceKeyIndex ofObject: bitBltOop] ifFalse:[sourceAlphaKey _ 0]. oop _ interpreterProxy fetchPointer: FXDestKeyIndex ofObject: bitBltOop. dstKeyMode _ (oop ~= interpreterProxy nilObject). dstKeyMode ifTrue:[destAlphaKey _ self fetchIntOrFloat: FXDestKeyIndex ofObject: bitBltOop] ifFalse:[destAlphaKey _ 0]. sourceAlpha _ self fetchIntOrFloat: FXSourceAlphaIndex ofObject: bitBltOop ifNil: 255. ^interpreterProxy failed not! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 22:56'! loadFXColorMap "Load a color map from FXBlt" | oop cmSize | self inline: true. oop _ interpreterProxy fetchPointer: BBColorMapIndex ofObject: bitBltOop. oop = interpreterProxy nilObject ifTrue:[^noColorMap _ true]. "We have a colorMap even though it might be that it's just an identity mapping" noColorMap _ false. ((interpreterProxy isPointers: oop) and:[(interpreterProxy slotSizeOf: oop) >= 3]) ifFalse:[^false]. cmShiftTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 0 ofObject: oop). cmMaskTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 1 ofObject: oop). oop _ interpreterProxy fetchPointer: 2 ofObject: oop. oop = interpreterProxy nilObject ifTrue:[cmSize _ cmMask _ 0] ifFalse:[(interpreterProxy isWords: oop) ifFalse:[^false]. cmSize _ (interpreterProxy slotSizeOf: oop)]. (cmSize bitAnd: cmSize - 1) = 0 ifFalse:[^false]. cmSize = 0 ifTrue:[colorMap _ nil] ifFalse:[colorMap _ interpreterProxy firstIndexableField: oop. cmMask _ cmSize - 1]. "Check if colorMap is just identity mapping for RGBA parts" (self isIdentityMap: cmShiftTable with: cmMaskTable) ifTrue:[ cmMaskTable _ cmShiftTable _ nil ]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 22:57'! loadFXDestMap "Load a destination map from destForm to canonical 32bit RGBA values" | oop dmSize | self inline: true. oop _ interpreterProxy fetchPointer: FXDestMapIndex ofObject: bitBltOop. oop = interpreterProxy nilObject ifTrue:[^noDestMap _ true]. "We have a destMap even though it might be that it's just an identity mapping" noDestMap _ false. ((interpreterProxy isPointers: oop) and:[(interpreterProxy slotSizeOf: oop) >= 3]) ifFalse:[^false]. dmShiftTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 0 ofObject: oop). dmMaskTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 1 ofObject: oop). oop _ interpreterProxy fetchPointer: 2 ofObject: oop. oop = interpreterProxy nilObject ifTrue:[dmSize _ dmMask _ 0] ifFalse:[(interpreterProxy isWords: oop) ifFalse:[^false]. dmSize _ (interpreterProxy slotSizeOf: oop)]. (dmSize bitAnd: dmSize - 1) = 0 ifFalse:[^false]. dmSize = 0 ifTrue:[destMap _ nil] ifFalse:[destMap _ interpreterProxy firstIndexableField: oop. dmMask _ dmSize - 1]. "Check if destMap is just identity mapping for RGBA parts" (self isIdentityMap: dmShiftTable with: dmMaskTable) ifTrue:[ dmMaskTable _ dmShiftTable _ nil ]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 19:35'! loadFXShiftOrMaskFrom: mapOop self returnTypeC:'int *'. mapOop = interpreterProxy nilObject ifTrue:[^nil]. ((interpreterProxy isWords: mapOop) and:[(interpreterProxy slotSizeOf: mapOop) = 4]) ifFalse:[interpreterProxy primitiveFail. ^nil]. ^interpreterProxy firstIndexableField: mapOop! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 22:57'! loadFXSourceMap "Load a source map from sourceForm to canonical 32bit RGBA values" | oop smSize | self inline: true. noSource ifTrue:[^true]. oop _ interpreterProxy fetchPointer: FXSourceMapIndex ofObject: bitBltOop. oop = interpreterProxy nilObject ifTrue:[^noSourceMap _ true]. "We have a sourceMap even though it might be that it's just an identity mapping" noSourceMap _ false. ((interpreterProxy isPointers: oop) and:[(interpreterProxy slotSizeOf: oop) >= 3]) ifFalse:[^false]. smShiftTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 0 ofObject: oop). smMaskTable _ self loadFXShiftOrMaskFrom: (interpreterProxy fetchPointer: 1 ofObject: oop). oop _ interpreterProxy fetchPointer: 2 ofObject: oop. oop = interpreterProxy nilObject ifTrue:[smSize _ smMask _ 0] ifFalse:[(interpreterProxy isWords: oop) ifFalse:[^false]. smSize _ (interpreterProxy slotSizeOf: oop)]. (smSize bitAnd: smSize - 1) = 0 ifFalse:[^false]. smSize = 0 ifTrue:[sourceMap _ nil] ifFalse:[sourceMap _ interpreterProxy firstIndexableField: oop. smMask _ smSize - 1]. "Check if sourceMap is just identity mapping for RGBA parts" (self isIdentityMap: smShiftTable with: smMaskTable) ifTrue:[ smMaskTable _ smShiftTable _ nil ]. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 19:41'! loadFXTallyMap "Load the tally map" | map | self inline: true. map _ interpreterProxy fetchPointer: FXTallyMapIndex ofObject: bitBltOop. map = interpreterProxy nilObject ifTrue:[ tallyMap _ nil. tallyMapSize _ 0. ^true]. (interpreterProxy isWords: map) ifFalse:[^false]. tallyMapSize _ interpreterProxy slotSizeOf: map. tallyMap _ interpreterProxy firstIndexableField: map. ^interpreterProxy failed not! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/22/2000 17:50'! loadFXWarpQuad "Load the new warp quad (used, e.g., for warping)" | points oop | self inline: true. points _ interpreterProxy fetchPointer: FXWarpQuadIndex ofObject: bitBltOop. points = interpreterProxy nilObject ifTrue:[^noWarp _ true]. noWarp _ false. (interpreterProxy fetchClassOf: points) = interpreterProxy classArray ifFalse:[^false]. (interpreterProxy slotSizeOf: points) = 4 ifFalse:[^false]. 0 to: 3 do:[:i| oop _ interpreterProxy fetchPointer: i ofObject: points. (interpreterProxy fetchClassOf: oop) = interpreterProxy classPoint ifFalse:[^false]. warpQuad at: i*2 put: (self fetchIntOrFloat: 0 ofObject: oop). warpQuad at: i*2+1 put: (self fetchIntOrFloat: 1 ofObject: oop). ]. ^interpreterProxy failed not! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 1/16/2000 16:36'! loadFXWarpQuality "Load the warp quality (used, e.g., for warping)" self inline: true. warpQuality _ self fetchIntOrFloat: FXWarpQualityIndex ofObject: bitBltOop ifNil: 1. ^interpreterProxy failed not! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/9/2000 19:29'! loadHalftoneForm "Load the halftone form" | halftoneBits | self inline: true. halftoneForm _ interpreterProxy fetchPointer: BBHalftoneFormIndex ofObject: bitBltOop. noHalftone _ self ignoreSourceOrHalftone: halftoneForm. noHalftone ifTrue:[ halftoneBase _ nil. ^true]. ((interpreterProxy isPointers: halftoneForm) not and: [interpreterProxy isWords: halftoneForm]) ifFalse: [^ false]. halftoneBits _ halftoneForm. halftoneHeight _ interpreterProxy slotSizeOf: halftoneBits. halftoneBase _ self cCoerce: (interpreterProxy firstIndexableField: halftoneBits) to:'int'. ^true! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/23/2000 21:03'! showDisplayBits interpreterProxy showDisplayBits: destForm Left: affectedL Top: affectedT Right: affectedR Bottom: affectedB ! ! !FXBltSimulation methodsFor: 'interpreter interface' stamp: 'ar 2/23/2000 21:40'! version "Return the version of FXBlt" ^1! ! !FXBltSimulation methodsFor: 'accessing'! affectedBottom ^affectedB! ! !FXBltSimulation methodsFor: 'accessing'! affectedLeft ^affectedL! ! !FXBltSimulation methodsFor: 'accessing'! affectedRight ^affectedR! ! !FXBltSimulation methodsFor: 'accessing'! affectedTop ^affectedT! ! !FXBltSimulation methodsFor: 'accessing'! targetForm "Return the destination form of a copyBits or scanCharacters operation." ^destForm! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 1/9/2000 16:25'! checkSourceOverlap "check for possible overlap of source and destination" "ar 10/19/1999: This method requires surfaces to be locked." | t | self inline: true. (sourceForm = destForm and: [dy >= sy]) ifTrue: [dy > sy ifTrue: ["have to start at bottom" vDir _ -1. sy _ sy + bbH - 1. dy _ dy + bbH - 1] ifFalse: [(dy = sy) & (dx > sx) ifTrue: ["y's are equal, but x's are backward" hDir _ -1. sx _ sx + bbW - 1. "start at right" dx _ dx + bbW - 1. "and fix up masks" nWords > 1 ifTrue: [t _ mask1. mask1 _ mask2. mask2 _ t]]]. "Dest inits may be affected by this change" destIndex _ destBits + (dy * destPitch) + ((dx // destPPW) *4). destDelta _ (destPitch * vDir) - (4 * (nWords * hDir))]! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 1/10/2000 23:29'! clipDest "clip and adjust source origin and extent appropriately" "first in x" destX >= clipX ifTrue: [sx _ sourceX. dx _ destX. bbW _ width] ifFalse: [sx _ sourceX + (clipX - destX). bbW _ width - (clipX - destX). dx _ clipX]. (dx + bbW) > (clipX + clipWidth) ifTrue: [bbW _ bbW - ((dx + bbW) - (clipX + clipWidth))]. "then in y" destY >= clipY ifTrue: [sy _ sourceY. dy _ destY. bbH _ height] ifFalse: [sy _ sourceY + clipY - destY. bbH _ height - (clipY - destY). dy _ clipY]. (dy + bbH) > (clipY + clipHeight) ifTrue: [bbH _ bbH - ((dy + bbH) - (clipY + clipHeight))]. ! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 1/10/2000 23:30'! clipSource "clip and adjust source origin and extent appropriately" sx < 0 ifTrue: [dx _ dx - sx. bbW _ bbW + sx. sx _ 0]. sx + bbW > sourceWidth ifTrue: [bbW _ bbW - (sx + bbW - sourceWidth)]. sy < 0 ifTrue: [dy _ dy - sy. bbH _ bbH + sy. sy _ 0]. sy + bbH > sourceHeight ifTrue: [bbH _ bbH - (sy + bbH - sourceHeight)]! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/24/2000 22:54'! copyBits self export: true. self inline: false. self initBBOpTable. self clipDest. "Clip against source if not warping" noSource ifFalse:[ noWarp ifTrue:[self clipSource]]. (bbW <= 0 or: [bbH <= 0]) ifTrue: ["zero width or height; noop" affectedL _ affectedR _ affectedT _ affectedB _ 0. ^ nil]. "Lock the surfaces if necessary" hasSurfaceLock _ false. (destBits = 0 or:[noSource not and:[sourceBits = 0]]) ifTrue:[self lockSurfaces ifFalse:[^interpreterProxy primitiveFail]]. self copyBitsLockedAndClipped. "And unlock the surfaces if necessary" hasSurfaceLock ifTrue:[self unlockSurfaces].! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/21/2000 22:53'! copyBitsFrom: startX to: stopX at: yValue "Support for the balloon engine." self export: true. destX _ startX. destY _ yValue. sourceX _ startX. width _ (stopX - startX). self copyBits. self showDisplayBits.! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/29/2000 15:39'! copyBitsLockedAndClipped "Perform the actual copyBits operation. Assume: Surfaces have been locked and clipping was performed." | done | self inline: true. pixelDepth _ destDepth. "default assumption" bitCount _ 0. "Try a shortcut for stuff that should be run as quickly as possible" done _ self copyBitsQuickly. done ifTrue:[^nil]. self destMaskAndPointerInit. noWarp ifTrue:[ "Choose and perform the actual copy loop." noSource ifTrue: ["Simple fill loop" noDestMap ifTrue:[self copyLoopNoSource] ifFalse:[self copyLoopNoSourcePixels]. ] ifFalse: ["Loop using source and dest" self checkSourceOverlap. (sourceDepth = destDepth and:[ sourceMSB = destMSB and:[noColorMap and:[srcKeyMode not]]]) ifTrue: [ "Faster version when equal depths and no color conversion" self sourceSkewAndPointerInit. self copyLoop. ] ifFalse: [ "If we must convert between pixel depths or use color lookups use the general version" (noSourceMap and:[noDestMap]) ifTrue:[self doCopyLoopPixMap] ifFalse:[self copyLoopPixels]. ] ]. ] ifFalse:[self doWarpLoop]. (combinationRule = 22) | (combinationRule = 32) ifTrue: ["zero width and height; return the count" affectedL _ affectedR _ affectedT _ affectedB _ 0. interpreterProxy pop: interpreterProxy methodArgumentCount + 1. ^ interpreterProxy pushInteger: bitCount]. hDir > 0 ifTrue: [affectedL _ dx. affectedR _ dx + bbW] ifFalse: [affectedL _ dx - bbW + 1. affectedR _ dx + 1]. vDir > 0 ifTrue: [affectedT _ dy. affectedB _ dy + bbH] ifFalse: [affectedT _ dy - bbH + 1. affectedB _ dy + 1]! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/21/2000 23:26'! copyBitsQuickly "Shortcut for stuff that's being run from the balloon engine. Since we do this at each scan line we should avoid the expensive setup for source and destination." self inline: true. "We need rule 34, source depth 32, and no warping please." (combinationRule = 34 and:[sourceDepth = 32 and:[noWarp]]) ifFalse:[^false]. "We need a source different from destination with at least 16bit depth." (noSource or:[sourceForm = destForm or:[destDepth <= 8]]) ifTrue:[^false]. "If 8bit, then we want a dest map" (destDepth = 8 and:[noDestMap]) ifTrue:[^false]. "And *no* source map please" noSourceMap ifFalse:[^false]. destDepth = 32 ifTrue:[self alphaSourceBlendBits32]. destDepth = 16 ifTrue:[self alphaSourceBlendBits16]. destDepth = 8 ifTrue:[self alphaSourceBlendBits8]. affectedL _ dx. affectedR _ dx + bbW. affectedT _ dy. affectedB _ dy + bbH. ^true! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/13/2000 15:44'! destMaskAndPointerInit "Compute masks for left and right destination words" | startBits pixPerM1 endBits | self inline: true. pixPerM1 _ destPPW - 1. "A mask, assuming power of two" "how many pixels in first word" startBits _ destPPW - (dx bitAnd: pixPerM1). destMSB ifTrue:[ mask1 _ AllOnes >> (32 - (startBits*destDepth))] ifFalse:[ mask1 _ AllOnes << (32 - (startBits*destDepth))]. "how many pixels in last word" endBits _ ((dx + bbW - 1) bitAnd: pixPerM1) + 1. destMSB ifTrue:[mask2 _ AllOnes << (32 - (endBits*destDepth))] ifFalse:[mask2 _ AllOnes >> (32 - (endBits*destDepth))]. "determine number of words stored per line; merge masks if only 1" bbW < startBits ifTrue: [mask1 _ mask1 bitAnd: mask2. mask2 _ 0. nWords _ 1] ifFalse: [nWords _ (bbW - startBits) + pixPerM1 // destPPW + 1]. hDir _ vDir _ 1. "defaults for no overlap with source" "calculate byte addr and delta, based on first word of data" "Note pitch is bytes and nWords is longs, not bytes" destIndex _ destBits + (dy * destPitch) + ((dx // destPPW) *4). destDelta _ destPitch * vDir - (4 * (nWords * hDir)). "byte addr delta" ! ! !FXBltSimulation methodsFor: 'setup'! ignoreSourceOrHalftone: formPointer formPointer = interpreterProxy nilObject ifTrue: [ ^true ]. combinationRule = 0 ifTrue: [ ^true ]. combinationRule = 5 ifTrue: [ ^true ]. combinationRule = 10 ifTrue: [ ^true ]. combinationRule = 15 ifTrue: [ ^true ]. ^false! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/13/2000 15:47'! sourceSkewAndPointerInit "This is only used when source and dest are same depth, ie, when the barrel-shift copy loop is used." | dWid sxLowBits dxLowBits pixPerM1 | self inline: true. pixPerM1 _ destPPW - 1. "A mask, assuming power of two" sxLowBits _ sx bitAnd: pixPerM1. dxLowBits _ dx bitAnd: pixPerM1. "check if need to preload buffer (i.e., two words of source needed for first word of destination)" hDir > 0 ifTrue: ["n Bits stored in 1st word of dest" dWid _ bbW min: destPPW - dxLowBits. preload _ (sxLowBits + dWid) > pixPerM1] ifFalse: [dWid _ bbW min: dxLowBits + 1. preload _ (sxLowBits - dWid + 1) < 0]. "calculate right-shift skew from source to dest" sourceMSB "equal to destMSB so it doesn't matter" ifTrue:[skew _ (sxLowBits - dxLowBits) * destDepth] ifFalse:[skew _ (dxLowBits - sxLowBits) * destDepth]. " -32..32 " preload ifTrue: [skew < 0 ifTrue: [skew _ skew+32] ifFalse: [skew _ skew-32]]. "Calc byte addr and delta from longWord info" sourceIndex _ sourceBits + (sy * sourcePitch) + ((sx // sourcePPW) *4). "calculate increments from end of 1 line to start of next" sourceDelta _ (sourcePitch * vDir) - (4 * (nWords * hDir)). preload ifTrue: ["Compensate for extra source word fetched" sourceDelta _ sourceDelta - (4*hDir)].! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 1/10/2000 23:31'! warpBits "obsolete" self copyBits.! ! !FXBltSimulation methodsFor: 'setup' stamp: 'ar 2/13/2000 15:48'! warpSetup "Setup values for faster pixel fetching in WarpBlt." | depth | self inline: true. "warpSrcShift = log2(sourceDepth)" warpSrcShift _ 0. depth _ sourceDepth. [depth = 1] whileFalse:[ warpSrcShift _ warpSrcShift + 1. depth _ depth >> 1]. "warpSrcMask = mask for extracting one pixel from source word" warpSrcMask _ maskTable at: sourceDepth. "warpAlignShift: Shift for aligning x position to word boundary" warpAlignShift _ 5 - warpSrcShift. "warpAlignMask: Mask for extracting the pixel position from an x position" warpAlignMask _ 1 << warpAlignShift - 1. "Setup the lookup table for source bit shifts" "warpBitShiftTable: given an sub-word x value what's the bit shift?" 0 to: warpAlignMask do:[:i| sourceMSB ifTrue:[warpBitShiftTable at: i put: 32 - ( i + 1 << warpSrcShift )] ifFalse:[warpBitShiftTable at: i put: (i << warpSrcShift)]].! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 2/13/2000 15:50'! alphaSourceBlendBits16 "This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 16 sourceForm ~= destForm. " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY dstMask srcShift ditherBase ditherIndex ditherThreshold | self inline: false. "This particular method should be optimized in itself" pixelDepth _ 32. deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. srcShift _ (dx bitAnd: 1) * 16. destMSB ifTrue:[srcShift _ 16 - srcShift]. mask1 _ 16rFFFF << (16 - srcShift). "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx // 2 * 4). ditherBase _ (dstY bitAnd: 3) * 4. ditherIndex _ (sx bitAnd: 3) - 1. "For pre-increment" deltaX _ bbW + 1. "So we can pre-decrement" dstMask _ mask1. dstMask = 16rFFFF ifTrue:[srcShift _ 16] ifFalse:[srcShift _ 0]. "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ ditherThreshold _ ditherMatrix4x4 at: ditherBase + (ditherIndex _ ditherIndex + 1 bitAnd: 3). sourceWord _ self srcLongAt: srcIndex. srcAlpha _ sourceWord >> 24. srcAlpha = 255 ifTrue:[ "Dither from 32 to 16 bit" sourceWord _ self dither32To16: sourceWord threshold: ditherThreshold. sourceWord = 0 ifTrue:[sourceWord _ 1]. sourceWord _ sourceWord << srcShift. "Store masked value" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ] ifFalse:[ "srcAlpha ~= 255" srcAlpha = 0 ifTrue:[ ] ifFalse:[ "0 < srcAlpha < 255" "If we have to mix colors then just copy a single word" destWord _ self dstLongAt: dstIndex. destWord _ destWord bitAnd: dstMask bitInvert32. destWord _ destWord >> srcShift. "Expand from 16 to 32 bit by adding zero bits" destWord _ (((destWord bitAnd: 16r7C00) bitShift: 9) bitOr: ((destWord bitAnd: 16r3E0) bitShift: 6)) bitOr: (((destWord bitAnd: 16r1F) bitShift: 3) bitOr: 16rFF000000). "Mix colors" sourceWord _ self alphaBlendScaled: sourceWord with: destWord. "And dither" sourceWord _ self dither32To16: sourceWord threshold: ditherThreshold. sourceWord = 0 ifTrue:[sourceWord _ 1]. sourceWord _ sourceWord << srcShift. "Store back" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ]. ]. srcIndex _ srcIndex + 4. destMSB ifTrue:[srcShift = 0 ifTrue:[dstIndex _ dstIndex + 4]] ifFalse:[srcShift = 0 ifFalse:[dstIndex _ dstIndex + 4]]. srcShift _ srcShift bitXor: 16. "Toggle between 0 and 16" dstMask _ dstMask bitInvert32. "Mask other half word" ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 1/16/2000 17:09'! alphaSourceBlendBits32 "This version assumes combinationRule = 34 sourcePixSize = destPixSize = 32 sourceForm ~= destForm. Note: The inner loop has been optimized for dealing with the special cases of srcAlpha = 0.0 and srcAlpha = 1.0 " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY | self inline: false. "This particular method should be optimized in itself" "Give the compile a couple of hints" self var: #sourceWord declareC:'register int sourceWord'. self var: #deltaX declareC:'register int deltaX'. "The following should be declared as pointers so the compiler will notice that they're used for accessing memory locations (good to know on an Intel architecture) but then the increments would be different between ST code and C code so must hope the compiler notices what happens (MS Visual C does)" self var: #srcIndex declareC:'register int srcIndex'. self var: #dstIndex declareC:'register int dstIndex'. deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx * 4). deltaX _ bbW + 1. "So we can pre-decrement" "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ sourceWord _ self srcLongAt: srcIndex. srcAlpha _ sourceWord >> 24. srcAlpha = 255 ifTrue:[ self dstLongAt: dstIndex put: sourceWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. "Now copy as many words as possible with alpha = 255" [(deltaX _ deltaX - 1) ~= 0 and:[ (sourceWord _ self srcLongAt: srcIndex) >> 24 = 255]] whileTrue:[ self dstLongAt: dstIndex put: sourceWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. "Adjust deltaX" deltaX _ deltaX + 1. ] ifFalse:[ "srcAlpha ~= 255" srcAlpha = 0 ifTrue:[ srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. "Now skip as many words as possible," [(deltaX _ deltaX - 1) ~= 0 and:[ (sourceWord _ self srcLongAt: srcIndex) >> 24 = 0]] whileTrue:[ srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. "Adjust deltaX" deltaX _ deltaX + 1. ] ifFalse:[ "0 < srcAlpha < 255" "If we have to mix colors then just copy a single word" destWord _ self dstLongAt: dstIndex. destWord _ self alphaBlendScaled: sourceWord with: destWord. self dstLongAt: dstIndex put: destWord. srcIndex _ srcIndex + 4. dstIndex _ dstIndex + 4. ]. ]. ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 2/13/2000 15:50'! alphaSourceBlendBits8 "This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 8 sourceForm ~= destForm. Note: This is not real blending since we don't have the source colors available. " | srcIndex dstIndex sourceWord srcAlpha destWord deltaX deltaY srcY dstY dstMask srcShift adjust | self inline: false. "This particular method should be optimized in itself" deltaY _ bbH + 1. "So we can pre-decrement" srcY _ sy. dstY _ dy. mask1 _ (dx bitAnd: 3) * 8. destMSB ifTrue:[mask1 _ 24 - mask1]. mask2 _ AllOnes bitXor:(16rFF << mask1). (dx bitAnd: 1) = 0 ifTrue:[adjust _ 0] ifFalse:[adjust _ 16r1F1F1F1F]. (dy bitAnd: 1) = 0 ifTrue:[adjust _ adjust bitXor: 16r1F1F1F1F]. "This is the outer loop" [(deltaY _ deltaY - 1) ~= 0] whileTrue:[ adjust _ adjust bitXor: 16r1F1F1F1F. srcIndex _ sourceBits + (srcY * sourcePitch) + (sx * 4). dstIndex _ destBits + (dstY * destPitch) + (dx // 4 * 4). deltaX _ bbW + 1. "So we can pre-decrement" srcShift _ mask1. dstMask _ mask2. "This is the inner loop" [(deltaX _ deltaX - 1) ~= 0] whileTrue:[ sourceWord _ ((self srcLongAt: srcIndex) bitAnd: (adjust bitInvert32)) + adjust. srcAlpha _ sourceWord >> 24. srcAlpha > 31 ifTrue:["Everything below 31 is transparent" srcAlpha < 224 ifTrue:["Everything above 224 is opaque" destWord _ self dstLongAt: dstIndex. destWord _ destWord bitAnd: dstMask bitInvert32. destWord _ destWord >> srcShift. destWord _ self mapDestPixel: destWord. sourceWord _ self alphaBlendScaled: sourceWord with: destWord. ]. sourceWord _ self mapPixel: sourceWord. sourceWord _ sourceWord << srcShift. "Store back" self dstLongAt: dstIndex put: sourceWord mask: dstMask. ]. srcIndex _ srcIndex + 4. destMSB ifTrue:[ srcShift = 0 ifTrue:[dstIndex _ dstIndex + 4. srcShift _ 24. dstMask _ 16r00FFFFFF] ifFalse:[srcShift _ srcShift - 8. dstMask _ (dstMask >> 8) bitOr: 16rFF000000]. ] ifFalse:[ srcShift = 32 ifTrue:[dstIndex _ dstIndex + 4. srcShift _ 0. dstMask _ 16rFFFFFF00] ifFalse:[srcShift _ srcShift + 8. dstMask _ dstMask << 8 bitOr: 255]. ]. adjust _ adjust bitXor: 16r1F1F1F1F. ]. srcY _ srcY + 1. dstY _ dstY + 1. ].! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! copyLoop "This version of the inner loop assumes sourceDepth = destDepth, noSource = false, noColorMap = noSourceMap = noDestMap = true" | prevWord thisWord skewWord halftoneWord mergeWord hInc y unskew skewMask notSkewMask destWord mergeFn | self inline: false. mergeFn _ opTable at: combinationRule+1. hInc _ hDir*4. "Byte delta" "degenerate skew fixed for Sparc. 10/20/96 ikp" skew == -32 ifTrue: [skew _ unskew _ skewMask _ 0] ifFalse: [skew < 0 ifTrue: [unskew _ skew+32. skewMask _ AllOnes << (0-skew)] ifFalse: [skew == 0 ifTrue: [unskew _ 0. skewMask _ AllOnes] ifFalse: [unskew _ skew-32. skewMask _ AllOnes >> skew]]]. notSkewMask _ skewMask bitInvert32. noHalftone ifTrue: [halftoneWord _ AllOnes. halftoneHeight _ 0] ifFalse: [halftoneWord _ self halftoneAt: 0]. y _ dy. 1 to: bbH do: "here is the vertical loop" [ :i | halftoneHeight > 1 ifTrue: "Otherwise, its always the same" [halftoneWord _ self halftoneAt: y. y _ y + vDir]. preload ifTrue: ["load the 64-bit shifter" prevWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc] ifFalse: [prevWord _ 0]. "Note: the horizontal loop has been expanded into three parts for speed:" "This first section requires masking of the destination store..." destMask _ mask1. thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. destWord _ self dstLongAt: destIndex. mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: destWord function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + hInc. "This central horizontal loop requires no store masking" destMask _ AllOnes. combinationRule = 3 ifTrue: [noHalftone & (notSkewMask = 0) ifTrue: ["Very special inner loop for STORE mode with no skew -- just move words" 2 to: nWords-1 do: [ :word | thisWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc. self dstLongAt: destIndex put: thisWord. destIndex _ destIndex + hInc]] ifFalse: ["Special inner loop for STORE mode -- no need to call merge" 2 to: nWords-1 do: [ :word | thisWord _ self srcLongAt: sourceIndex. sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. self dstLongAt: destIndex put: (skewWord bitAnd: halftoneWord). destIndex _ destIndex + hInc]] ] ifFalse: [2 to: nWords-1 do: "Normal inner loop does merge:" [ :word | thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). prevWord _ thisWord. mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: (self dstLongAt: destIndex) function: mergeFn. self dstLongAt: destIndex put: mergeWord. destIndex _ destIndex + hInc] ]. "This last section, if used, requires masking of the destination store..." nWords > 1 ifTrue: [destMask _ mask2. thisWord _ self srcLongAt: sourceIndex. "pick up next word" sourceIndex _ sourceIndex + hInc. skewWord _ ((prevWord bitAnd: notSkewMask) bitShift: unskew) bitOr: "32-bit rotate" ((thisWord bitAnd: skewMask) bitShift: skew). destWord _ self dstLongAt: destIndex. mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: destWord function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + hInc]. sourceIndex _ sourceIndex + sourceDelta. destIndex _ destIndex + destDelta]! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! copyLoopNoSource "Faster copyLoop when source not used. hDir and vDir are both positive, and perload and skew are unused. Note: This is the common fill loop without any fancy stuff." | halftoneWord mergeWord destWord mergeFn | self inline: false. mergeFn _ opTable at: combinationRule+1. 1 to: bbH do: "here is the vertical loop" [ :i | noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. "Note: the horizontal loop has been expanded into three parts for speed:" "This first section requires masking of the destination store..." destMask _ mask1. destWord _ self dstLongAt: destIndex. mergeWord _ self merge: halftoneWord with: destWord function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4. "This central horizontal loop requires no store masking" destMask _ AllOnes. combinationRule = 3 ifTrue: ["Special inner loop for STORE" destWord _ halftoneWord. 2 to: nWords-1 do:[ :word | self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4]. ] ifFalse:[ "Normal inner loop does merge" 2 to: nWords-1 do:[ :word | "Normal inner loop does merge" destWord _ self dstLongAt: destIndex. mergeWord _ self merge: halftoneWord with: destWord function: mergeFn. self dstLongAt: destIndex put: mergeWord. destIndex _ destIndex + 4]. ]. "This last section, if used, requires masking of the destination store..." nWords > 1 ifTrue: [destMask _ mask2. destWord _ self dstLongAt: destIndex. mergeWord _ self merge: halftoneWord with: destWord function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4]. destIndex _ destIndex + destDelta]! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! copyLoopNoSourcePixels "This version of the inner loop maps dest pixels one at a time. Note: This loop is typically used for filling with alpha values. Note2: There is probably some significant speedup in here but for now I just need a version that does the job..." | nPix dstShift destWord dstIndex nLines dstMask lastDstPix destPix dstMapped resultMapped resultPix halftoneWord dstPaint mergeFn | self inline: false. mergeFn _ opTable at: combinationRule+1. dstPaint _ combinationRule = 36. "Additional inits" dstMask _ maskTable at: destDepth. dstIndex _ destIndex. "Precomputed shifts for pickSourcePixels" dstShift _ ((dx bitAnd: destPPW - 1) * destDepth). destMSB ifTrue:[dstShift _ 32 - destDepth - dstShift]. dstBitShift _ dstShift. destMask _ -1. pixelDepth _ 32. nLines _ bbH. ["this is the vertical loop" noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: bbH - nLines]. destWord _ self dstLongAt: dstIndex. "Prefetch first dest pixel" lastDstPix _ destPix _ destWord >> dstShift bitAnd: dstMask. dstMapped _ self mapDestPixel: destPix. nPix _ bbW. ["this is the horizontal loop" (dstPaint and:[destPix = destAlphaKey]) ifTrue:[resultMapped _ dstMapped] ifFalse:[resultMapped _ self merge: halftoneWord with: dstMapped function: mergeFn]. (noColorMap and:[resultMapped = dstMapped]) ifFalse:[ resultPix _ self mapPixel: resultMapped. destWord _ destWord bitAnd: (dstMask << dstShift) bitInvert32. destWord _ destWord bitOr: (resultPix bitAnd: dstMask) << dstShift. ]. destMSB ifTrue:[ "Adjust dest if at pixel boundary" (dstShift _ dstShift - destDepth) < 0 ifTrue: [dstShift _ dstShift + 32. self dstLongAt: dstIndex put: destWord. destWord _ self dstLongAt: (dstIndex _ dstIndex + 4)]. ] ifFalse:[ "Adjust dest if at pixel boundary" (dstShift _ dstShift + destDepth) > 31 ifTrue: [dstShift _ dstShift - 32. self dstLongAt: dstIndex put: destWord. destWord _ self dstLongAt: (dstIndex _ dstIndex + 4)]. ]. (nPix _ nPix - 1) = 0] whileFalse:[ "Fetch next dest pixel" destPix _ destWord >> dstShift bitAnd: dstMask. lastDstPix = destPix ifFalse:[ dstMapped _ self mapDestPixel: destPix. lastDstPix _ destPix] ]. (nLines _ nLines - 1) = 0] whileFalse:[ "Store last modified word" self dstLongAt: dstIndex put: destWord. "Advance destIndex" dstIndex _ destIndex _ destIndex + destPitch. dstShift _ dstBitShift. ]. "Store final destWord" self dstLongAt: dstIndex put: destWord.! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! copyLoopPixMap "This version of the inner loop maps source pixels to a destination form with different depth. Note: Special care is taken to handle source paint mode correctly." | skewWord halftoneWord mergeWord scrStartBits nSourceIncs startBits endBits sourcePixMask destPixMask nPix srcShift dstShift destWord words srcPaint mergeFn | "Note: This method is inlined for specialization of LSB-MSB conversion" self inline: true. mergeFn _ opTable at: combinationRule+1. srcPaint _ srcKeyMode. "Additional inits peculiar to unequal source and dest pix size..." sourcePixMask _ maskTable at: sourceDepth. destPixMask _ maskTable at: destDepth. sourceIndex _ sourceBits + (sy * sourcePitch) + ((sx // sourcePPW) *4). scrStartBits _ sourcePPW - (sx bitAnd: sourcePPW-1). bbW < scrStartBits ifTrue: [nSourceIncs _ 0] ifFalse: [nSourceIncs _ (bbW - scrStartBits)//sourcePPW + 1]. sourceDelta _ sourcePitch - (nSourceIncs * 4). "Note following two items were already calculated in destmask setup!!" startBits _ destPPW - (dx bitAnd: destPPW-1). endBits _ ((dx + bbW - 1) bitAnd: destPPW-1) + 1. bbW < startBits ifTrue:[startBits _ bbW]. "Precomputed shifts for pickSourcePixels" srcShift _ ((sx bitAnd: sourcePPW - 1) * sourceDepth). dstShift _ ((dx bitAnd: destPPW - 1) * destDepth). sourceMSB ifTrue:[srcShift _ 32 - sourceDepth - srcShift]. destMSB ifTrue:[dstShift _ 32 - destDepth - dstShift]. 1 to: bbH do: "here is the vertical loop" [ :i | noHalftone ifTrue:[halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. "setup first load" srcBitShift _ srcShift. dstBitShift _ dstShift. destMask _ mask1. nPix _ startBits. "Here is the horizontal loop..." words _ nWords. ["pick up the word" destWord _ self dstLongAt: destIndex. skewWord _ self pickSourcePixels: nPix srcMask: sourcePixMask destMask: destPixMask paintMode: srcPaint destWord: destWord. mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: (destWord bitAnd: destMask) function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4. words = 2 "e.g., is the next word the last word?" ifTrue:["set mask for last word in this row" destMask _ mask2. nPix _ endBits] ifFalse:["use fullword mask for inner loop" destMask _ AllOnes. nPix _ destPPW]. (words _ words - 1) = 0] whileFalse. "--- end of inner loop ---" sourceIndex _ sourceIndex + sourceDelta. destIndex _ destIndex + destDelta] ! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! copyLoopPixels "This version of the inner loop maps source pixels and dest pixels one at a time. This is the most general (and slowest) version which must also keep track of source and dest paint mode by itself." | nPix srcShift dstShift destWord srcIndex dstIndex nLines sourceWord lastSrcPix sourcePix srcMask dstMask srcMapped lastDstPix destPix dstMapped resultMapped resultPix srcPaint dstPaint paintMode mergeFn | self inline: false. mergeFn _ opTable at: combinationRule+1. srcPaint _ srcKeyMode. dstPaint _ dstKeyMode. paintMode _ srcPaint | dstPaint. "Additional inits" srcMask _ maskTable at: sourceDepth. dstMask _ maskTable at: destDepth. sourceIndex _ srcIndex _ sourceBits + (sy * sourcePitch) + ((sx // sourcePPW) *4). dstIndex _ destIndex. "Precomputed shifts for pickSourcePixels" srcShift _ ((sx bitAnd: sourcePPW - 1) * sourceDepth). dstShift _ ((dx bitAnd: destPPW - 1) * destDepth). sourceMSB ifTrue:[srcShift _ 32 - sourceDepth - srcShift]. destMSB ifTrue:[dstShift _ 32 - destDepth - dstShift]. srcBitShift _ srcShift. dstBitShift _ dstShift. noSourceMap ifTrue:[pixelDepth _ sourceDepth] ifFalse:[pixelDepth _ 32]. destMask _ -1. nLines _ bbH. ["this is the vertical loop" sourceWord _ self srcLongAt: srcIndex. destWord _ self dstLongAt: dstIndex. "Prefetch first source pixel" lastSrcPix _ sourcePix _ sourceWord >> srcShift bitAnd: srcMask. srcMapped _ self mapSourcePixel: sourcePix. "Prefetch first dest pixel" lastDstPix _ destPix _ destWord >> dstShift bitAnd: dstMask. dstMapped _ self mapDestPixel: destPix. nPix _ bbW. ["this is the horizontal loop" (paintMode) ifTrue:[ ((srcPaint and:[sourcePix = sourceAlphaKey]) or:[dstPaint and:[destPix ~= destAlphaKey]]) ifTrue:[resultMapped _ dstMapped] ifFalse:[ resultMapped _ self merge: srcMapped with: dstMapped function: mergeFn]. ] ifFalse:[ resultMapped _ self merge: srcMapped with: dstMapped function: mergeFn. ]. (noColorMap and:[resultMapped = dstMapped]) ifFalse:[ resultPix _ self mapPixel: resultMapped. destWord _ destWord bitAnd: (dstMask << dstShift) bitInvert32. destWord _ destWord bitOr: (resultPix bitAnd: dstMask) << dstShift. ]. sourceMSB ifTrue:[ "Adjust source if at pixel boundary" (srcShift _ srcShift - sourceDepth) < 0 ifTrue: [srcShift _ srcShift + 32. sourceWord _ self srcLongAt: (srcIndex _ srcIndex + 4)]. ] ifFalse:[ "Adjust source if at pixel boundary" (srcShift _ srcShift + sourceDepth) > 31 ifTrue: [srcShift _ srcShift - 32. sourceWord _ self srcLongAt: (srcIndex _ srcIndex + 4)]. ]. destMSB ifTrue:[ "Adjust dest if at pixel boundary" (dstShift _ dstShift - destDepth) < 0 ifTrue: [dstShift _ dstShift + 32. self dstLongAt: dstIndex put: destWord. destWord _ self dstLongAt: (dstIndex _ dstIndex + 4)]. ] ifFalse:[ "Adjust dest if at pixel boundary" (dstShift _ dstShift + destDepth) > 31 ifTrue: [dstShift _ dstShift - 32. self dstLongAt: dstIndex put: destWord. destWord _ self dstLongAt: (dstIndex _ dstIndex + 4)]. ]. (nPix _ nPix - 1) = 0] whileFalse:[ "Fetch next source/dest pixel" sourcePix _ sourceWord >> srcShift bitAnd: srcMask. lastSrcPix = sourcePix ifFalse:[ srcMapped _ self mapSourcePixel: sourcePix. lastSrcPix _ sourcePix]. destPix _ destWord >> dstShift bitAnd: dstMask. lastDstPix = destPix ifFalse:[ dstMapped _ self mapDestPixel: destPix. lastDstPix _ destPix] ]. (nLines _ nLines - 1) = 0] whileFalse:[ "Store last destWord" self dstLongAt: dstIndex put: destWord. "Advance sourceIndex, destIndex" srcIndex _ sourceIndex _ sourceIndex + sourcePitch. dstIndex _ destIndex _ destIndex + destPitch. srcShift _ srcBitShift. dstShift _ dstBitShift. ]. "Store final destWord" self dstLongAt: dstIndex put: destWord.! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 2/16/2000 18:43'! merge: sourceWord with: destinationWord function: mergeFn ^self perform: (self cCoerce: mergeFn to: 'int (*) (int, int)') with: sourceWord with: destinationWord! ! !FXBltSimulation methodsFor: 'inner loop' stamp: 'ar 5/25/2000 15:41'! warpLoop "This version of the inner loop traverses an arbirary quadrilateral source, thus producing a general affine transformation." | skewWord halftoneWord mergeWord startBits deltaP12x deltaP12y deltaP43x deltaP43y pAx pAy pBx pBy xDelta yDelta nSteps nPix words destWord endBits srcPaint mergeFn | "We want to inline this into the specialized versions" self inline: true. false ifTrue:[ "Check if we have a simple scaling and can short cut this into scaleLoop" ((warpQuad at: 0) "p1x" = (warpQuad at: 2) "p2x" and:[(warpQuad at: 4) "p3x" = (warpQuad at: 6) "p4x" and:[(warpQuad at: 1) "p1y" = (warpQuad at: 7) "p4y" and:[(warpQuad at: 3) "p2y" = (warpQuad at: 5) "p3y"]]]) ifTrue:["^self scaleLoop"]]. mergeFn _ opTable at: combinationRule+1. srcPaint _ srcKeyMode. nSteps _ height-1. nSteps <= 0 ifTrue: [nSteps _ 1]. pAx _ warpQuad at: 0. words _ warpQuad at: 2. deltaP12x _ self deltaFrom: pAx to: words nSteps: nSteps. deltaP12x < 0 ifTrue: [pAx _ words - (nSteps*deltaP12x)]. pAy _ warpQuad at: 1. words _ warpQuad at: 3. deltaP12y _ self deltaFrom: pAy to: words nSteps: nSteps. deltaP12y < 0 ifTrue: [pAy _ words - (nSteps*deltaP12y)]. pBx _ warpQuad at: 6. words _ warpQuad at: 4. deltaP43x _ self deltaFrom: pBx to: words nSteps: nSteps. deltaP43x < 0 ifTrue: [pBx _ words - (nSteps*deltaP43x)]. pBy _ warpQuad at: 7. words _ warpQuad at: 5. deltaP43y _ self deltaFrom: pBy to: words nSteps: nSteps. deltaP43y < 0 ifTrue: [pBy _ words - (nSteps*deltaP43y)]. nSteps _ width-1. nSteps <= 0 ifTrue: [nSteps _ 1]. startBits _ destPPW - (dx bitAnd: destPPW-1). endBits _ ((dx + bbW - 1) bitAnd: destPPW-1) + 1. bbW < startBits ifTrue:[startBits _ bbW]. destY < clipY ifTrue:[ "Advance increments if there was clipping in y" pAx _ pAx + (clipY - destY * deltaP12x). pAy _ pAy + (clipY - destY * deltaP12y). pBx _ pBx + (clipY - destY * deltaP43x). pBy _ pBy + (clipY - destY * deltaP43y)]. "Setup values for faster pixel fetching." self warpSetup. 1 to: bbH do: [ :i | "here is the vertical loop..." xDelta _ self deltaFrom: pAx to: pBx nSteps: nSteps. xDelta >= 0 ifTrue: [sx _ pAx] ifFalse: [sx _ pBx - (nSteps*xDelta)]. yDelta _ self deltaFrom: pAy to: pBy nSteps: nSteps. yDelta >= 0 ifTrue: [sy _ pAy] ifFalse: [sy _ pBy - (nSteps*yDelta)]. destMSB ifTrue:[dstBitShift _ 32 - ((dx bitAnd: destPPW - 1) + 1 * destDepth)] ifFalse:[dstBitShift _ (dx bitAnd: destPPW - 1) * destDepth]. (destX < clipX) ifTrue:[ "Advance increments if there was clipping in x" sx _ sx + (clipX - destX * xDelta). sy _ sy + (clipX - destX * yDelta). ]. noHalftone ifTrue: [halftoneWord _ AllOnes] ifFalse: [halftoneWord _ self halftoneAt: dy+i-1]. destMask _ mask1. nPix _ startBits. "Here is the inner loop..." words _ nWords. ["pick up word" destWord _ self dstLongAt: destIndex. warpQuality = 1 ifTrue:["Faster if not smoothing" skewWord _ self warpPickSourcePixels: nPix xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y paintMode: srcPaint destWord: destWord ] ifFalse:["more difficult with smoothing" skewWord _ self warpPickSmoothPixels: nPix xDeltah: xDelta yDeltah: yDelta xDeltav: deltaP12x yDeltav: deltaP12y paintMode: srcPaint destWord: destWord. ]. mergeWord _ self merge: (skewWord bitAnd: halftoneWord) with: (destWord bitAnd: destMask) function: mergeFn. destWord _ (destMask bitAnd: mergeWord) bitOr: (destWord bitAnd: destMask bitInvert32). self dstLongAt: destIndex put: destWord. destIndex _ destIndex + 4. words = 2 "e.g., is the next word the last word?" ifTrue:["set mask for last word in this row" destMask _ mask2. nPix _ endBits] ifFalse:["use fullword mask for inner loop" destMask _ AllOnes. nPix _ destPPW]. (words _ words - 1) = 0] whileFalse. "--- end of inner loop ---" pAx _ pAx + deltaP12x. pAy _ pAy + deltaP12y. pBx _ pBx + deltaP43x. pBy _ pBy + deltaP43y. destIndex _ destIndex + destDelta]! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/17/2000 19:55'! OLDrgbDiff: sourceWord with: destinationWord "Subract the pixels in the source and destination and return the number of differing pixels. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes." | diff pixMask | self inline: false. diff _ sourceWord bitXor: destinationWord. pixMask _ maskTable at: pixelDepth. [diff = 0] whileFalse: [(diff bitAnd: pixMask) ~= 0 ifTrue: [bitCount _ bitCount + 1]. diff _ diff >> pixelDepth]. ^ destinationWord "for no effect".! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 21:32'! OLDtallyIntoMap: sourceWord with: destinationWord "Tally pixels into the tally map. Note that the source should be specified = destination, in order for the proper color map checks to be performed at setup. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes." | mapIndex pixMask shiftWord | tallyMapSize = 0 ifTrue: [^ destinationWord "no op"]. "loop through all packed pixels." pixMask _ maskTable at: pixelDepth. shiftWord _ destinationWord. 1 to: destPPW do:[:i | mapIndex _ shiftWord bitAnd: pixMask. mapIndex _ self mapPixel: mapIndex. (mapIndex >= 0 and:[mapIndex < tallyMapSize]) ifTrue:[ tallyMap at: mapIndex put: (tallyMap at: mapIndex) + 1. ]. shiftWord _ shiftWord >> pixelDepth]. ^ destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! addWord: sourceWord with: destinationWord ^sourceWord + destinationWord! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'DSM 11/22/2000 13:45'! alphaBlend: sourceWord with: destinationWord "Blend sourceWord with destinationWord, assuming both are 32-bit pixels. The source is assumed to have 255*alpha in the high 8 bits of each pixel, while the high 8 bits of the destinationWord will be ignored. The blend produced is alpha*source + (1-alpha)*dest, with the computation being performed independently on each color component. The high byte of the result will be 0." | alpha unAlpha colorMask result blend shift | self inline: false. alpha _ sourceWord >> 24. "High 8 bits of source pixel" alpha = 0 ifTrue: [ ^ destinationWord ]. alpha = 255 ifTrue: [ ^ sourceWord ]. unAlpha _ 255 - alpha. colorMask _ 16rFF. result _ 0. "ar 9/9/2000 - include alpha in computation" 1 to: 4 do: [:i | shift _ (i-1)*8. blend _ (((sourceWord>>shift bitAnd: colorMask) * alpha) + ((destinationWord>>shift bitAnd: colorMask) * unAlpha)) + 254 // 255 bitAnd: colorMask. result _ result bitOr: blend<>shift bitAnd: rgbMask) * sourceAlpha) + ((destPixVal>>shift bitAnd: rgbMask) * unAlpha)) + 254 // 255 bitAnd: rgbMask. pixBlend _ pixBlend bitOr: blend<> pixelDepth. sourceShifted _ sourceShifted >> pixelDepth. destShifted _ destShifted >> pixelDepth]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 11/27/1998 23:56'! alphaBlendScaled: sourceWord with: destinationWord "Blend sourceWord with destinationWord using the alpha value from sourceWord. Alpha is encoded as 0 meaning 0.0, and 255 meaning 1.0. In contrast to alphaBlend:with: the color produced is srcColor + (1-srcAlpha) * dstColor e.g., it is assumed that the source color is already scaled." | unAlpha dstMask srcMask b g r a | self inline: false. "Do NOT inline this into optimized loops" unAlpha _ 255 - (sourceWord >> 24). "High 8 bits of source pixel" dstMask _ destinationWord. srcMask _ sourceWord. b _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). b > 255 ifTrue:[b _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. g _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). g > 255 ifTrue:[g _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. r _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). r > 255 ifTrue:[r _ 255]. dstMask _ dstMask >> 8. srcMask _ srcMask >> 8. a _ (dstMask bitAnd: 255) * unAlpha >> 8 + (srcMask bitAnd: 255). a > 255 ifTrue:[a _ 255]. ^(((((a << 8) + r) << 8) + g) << 8) + b! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'di 6/29/1998 19:56'! alphaPaintConst: sourceWord with: destinationWord sourceWord = 0 ifTrue: [^ destinationWord "opt for all-transparent source"]. ^ self alphaBlendConst: sourceWord with: destinationWord paintMode: true! ! !FXBltSimulation methodsFor: 'combination rules'! bitAnd: sourceWord with: destinationWord ^sourceWord bitAnd: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! bitAndInvert: sourceWord with: destinationWord ^sourceWord bitAnd: destinationWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertAnd: sourceWord with: destinationWord ^sourceWord bitInvert32 bitAnd: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertAndInvert: sourceWord with: destinationWord ^sourceWord bitInvert32 bitAnd: destinationWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertDestination: sourceWord with: destinationWord ^destinationWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertOr: sourceWord with: destinationWord ^sourceWord bitInvert32 bitOr: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertOrInvert: sourceWord with: destinationWord ^sourceWord bitInvert32 bitOr: destinationWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertSource: sourceWord with: destinationWord ^sourceWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitInvertXor: sourceWord with: destinationWord ^sourceWord bitInvert32 bitXor: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! bitOr: sourceWord with: destinationWord ^sourceWord bitOr: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! bitOrInvert: sourceWord with: destinationWord ^sourceWord bitOr: destinationWord bitInvert32! ! !FXBltSimulation methodsFor: 'combination rules'! bitXor: sourceWord with: destinationWord ^sourceWord bitXor: destinationWord! ! !FXBltSimulation methodsFor: 'combination rules'! clearWord: source with: destination ^ 0! ! !FXBltSimulation methodsFor: 'combination rules'! destinationWord: sourceWord with: destinationWord ^destinationWord! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:24'! dstPaint: sourceWord with: destinationWord "Replace those pixels in destinationWord where the pixel value is equal to destAlphaKey." | mask key result val | self inline: false. mask _ maskTable at: pixelDepth. key _ destAlphaKey. result _ 0. 0 to: destPPW-1 do:[:i| (val _ destinationWord bitAnd: mask) = key ifTrue:[result _ result bitOr: (sourceWord bitAnd: mask)] ifFalse:[result _ result bitOr: val]. mask _ mask << pixelDepth. key _ key << pixelDepth]. ^result! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:18'! partitionedAND: word1 to: word2 nBits: nBits nPartitions: nParts "AND word1 to word2 as nParts partitions of nBits each. Any field of word1 not all-ones is treated as all-zeroes. Used for erasing, eg, brush shapes prior to ORing in a color" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | (word1 bitAnd: mask) = mask ifTrue: [result _ result bitOr: (word2 bitAnd: mask)]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedAdd: word1 to: word2 nBits: nBits nPartitions: nParts "Add word1 to word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors" | mask sum result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | sum _ (word1 bitAnd: mask) + (word2 bitAnd: mask). sum <= mask "result must not carry out of partition" ifTrue: [result _ result bitOr: sum] ifFalse: [result _ result bitOr: mask]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedMax: word1 with: word2 nBits: nBits nPartitions: nParts "Max word1 to word2 as nParts partitions of nBits each" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | result _ result bitOr: ((word2 bitAnd: mask) max: (word1 bitAnd: mask)). mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:35'! partitionedMin: word1 with: word2 nBits: nBits nPartitions: nParts "Min word1 to word2 as nParts partitions of nBits each" | mask result | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | result _ result bitOr: ((word2 bitAnd: mask) min: (word1 bitAnd: mask)). mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 12/7/1998 21:36'! partitionedSub: word1 from: word2 nBits: nBits nPartitions: nParts "Subtract word1 from word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors" | mask result p1 p2 | mask _ maskTable at: nBits. "partition mask starts at the right" result _ 0. 1 to: nParts do: [:i | p1 _ word1 bitAnd: mask. p2 _ word2 bitAnd: mask. p1 < p2 "result is really abs value of thedifference" ifTrue: [result _ result bitOr: p2 - p1] ifFalse: [result _ result bitOr: p1 - p2]. mask _ mask << nBits "slide left to next partition"]. ^ result ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/22/2000 16:08'! pixMask: sourceWord with: destinationWord self inline: false. ^ self partitionedAND: sourceWord bitInvert32 to: destinationWord nBits: destDepth nPartitions: destPPW! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/22/2000 16:09'! pixPaint: sourceWord with: destinationWord self inline: false. sourceWord = 0 ifTrue: [^ destinationWord]. ^ sourceWord bitOr: (self partitionedAND: sourceWord bitInvert32 to: destinationWord nBits: destDepth nPartitions: destPPW)! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:11'! rgbAdd: sourceWord with: destinationWord self inline: false. pixelDepth < 16 ifTrue: ["Add each pixel separately" ^ self partitionedAdd: sourceWord to: destinationWord nBits: pixelDepth nPartitions: destPPW]. pixelDepth = 16 ifTrue: ["Add RGB components of each pixel separately" ^ (self partitionedAdd: sourceWord to: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedAdd: sourceWord>>16 to: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Add RGB components of the pixel separately" ^ self partitionedAdd: sourceWord to: destinationWord nBits: 8 nPartitions: 3]! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/17/2000 19:57'! rgbDiff: sourceWord with: destinationWord "Subract the pixels in the source and destination and return the number of differing pixels." | pixMask destShifted sourceShifted destPixVal sourcePixVal maskShifted | self inline: false. pixMask _ maskTable at: pixelDepth. maskShifted _ destMask. destShifted _ destinationWord. sourceShifted _ sourceWord. 1 to: destPPW do: [:i | (maskShifted bitAnd: pixMask) > 0 ifTrue: ["Only tally pixels within the destination rectangle" destPixVal _ destShifted bitAnd: pixMask. sourcePixVal _ sourceShifted bitAnd: pixMask. sourcePixVal = destPixVal ifFalse: [bitCount _ bitCount + 1]]. maskShifted _ maskShifted >> pixelDepth. sourceShifted _ sourceShifted >> pixelDepth. destShifted _ destShifted >> pixelDepth]. ^ destinationWord "For no effect on dest" ! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:11'! rgbMax: sourceWord with: destinationWord self inline: false. pixelDepth < 16 ifTrue: ["Max each pixel separately" ^ self partitionedMax: sourceWord with: destinationWord nBits: pixelDepth nPartitions: destPPW]. pixelDepth = 16 ifTrue: ["Max RGB components of each pixel separately" ^ (self partitionedMax: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMax: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Max RGB components of the pixel separately" ^ self partitionedMax: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:13'! rgbMin: sourceWord with: destinationWord self inline: false. pixelDepth < 16 ifTrue: ["Min each pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: pixelDepth nPartitions: destPPW]. pixelDepth = 16 ifTrue: ["Min RGB components of each pixel separately" ^ (self partitionedMin: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMin: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Min RGB components of the pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:14'! rgbMinInvert: wordToInvert with: destinationWord | sourceWord | self inline: false. sourceWord _ wordToInvert bitInvert32. pixelDepth < 16 ifTrue: ["Min each pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: pixelDepth nPartitions: destPPW]. pixelDepth = 16 ifTrue: ["Min RGB components of each pixel separately" ^ (self partitionedMin: sourceWord with: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedMin: sourceWord>>16 with: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Min RGB components of the pixel separately" ^ self partitionedMin: sourceWord with: destinationWord nBits: 8 nPartitions: 3]! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/16/2000 17:14'! rgbSub: sourceWord with: destinationWord self inline: false. pixelDepth < 16 ifTrue: ["Sub each pixel separately" ^ self partitionedSub: sourceWord from: destinationWord nBits: pixelDepth nPartitions: destPPW]. pixelDepth = 16 ifTrue: ["Sub RGB components of each pixel separately" ^ (self partitionedSub: sourceWord from: destinationWord nBits: 5 nPartitions: 3) + ((self partitionedSub: sourceWord>>16 from: destinationWord>>16 nBits: 5 nPartitions: 3) << 16)] ifFalse: ["Sub RGB components of the pixel separately" ^ self partitionedSub: sourceWord from: destinationWord nBits: 8 nPartitions: 3]! ! !FXBltSimulation methodsFor: 'combination rules'! sourceWord: sourceWord with: destinationWord ^sourceWord! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 2/10/2000 17:01'! srcPaint: sourceWord with: destinationWord "Replace those pixels in destinationWord where the associated pixel in sourceWord is different from sourceAlphaKey. Note: This will only work for sourceDepth = destDepth." | mask key result val | self inline: false. "Note: If sourceDepth ~= destDepth or warpMode the pixels are pre-merged" (sourceDepth = destDepth and:[noWarp]) ifFalse:[^sourceWord]. mask _ maskTable at: pixelDepth. key _ sourceAlphaKey. result _ 0. 0 to: destPPW-1 do:[:i| (val _ sourceWord bitAnd: mask) = key ifTrue:[result _ result bitOr: (destinationWord bitAnd: mask)] ifFalse:[result _ result bitOr: val]. mask _ mask << pixelDepth. key _ key << pixelDepth]. ^result! ! !FXBltSimulation methodsFor: 'combination rules'! subWord: sourceWord with: destinationWord ^sourceWord - destinationWord! ! !FXBltSimulation methodsFor: 'combination rules' stamp: 'ar 1/17/2000 19:58'! tallyIntoMap: sourceWord with: destinationWord "Tally pixels into the color map. Those tallied are exactly those in the destination rectangle. Note that the source should be specified == destination, in order for the proper color map checks to be performed at setup." | mapIndex pixMask mask shiftWord | self inline: false. tallyMapSize = 0 ifTrue: [^ destinationWord "no op"]. pixMask _ maskTable at: pixelDepth. shiftWord _ destinationWord. mask _ destMask. 1 to: destPPW do:[:i | (mask bitAnd: pixMask) = 0 ifFalse:[ mapIndex _ shiftWord bitAnd: pixMask. mapIndex _ self mapPixel: mapIndex. (mapIndex >= 0 and:[mapIndex < tallyMapSize]) ifTrue:[ tallyMap at: mapIndex put: (tallyMap at: mapIndex) + 1]]. mask _ mask >> pixelDepth. shiftWord _ shiftWord >> pixelDepth]. ^ destinationWord "For no effect on dest"! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 11/16/1998 00:23'! default8To32Table "Return the default translation table from 1..8 bit indexed colors to 32bit" "The table has been generated by the following statements" "| pvs hex | String streamContents:[:s| s nextPutAll:'static unsigned int theTable[256] = { '. pvs _ (Color colorMapIfNeededFrom: 8 to: 32) asArray. 1 to: pvs size do:[:i| i > 1 ifTrue:[s nextPutAll:', ']. (i-1 \\ 8) = 0 ifTrue:[s cr]. s nextPutAll:'0x'. hex _ (pvs at: i) printStringBase: 16. s nextPutAll: (hex copyFrom: 4 to: hex size). ]. s nextPutAll:'};'. ]." | theTable | self returnTypeC:'unsigned int *'. self var: #theTable declareC:'static unsigned int theTable[256] = { 0x0, 0xFF000001, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050, 0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7, 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, 0xFF000001, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333, 0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966, 0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99, 0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF, 0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900, 0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33, 0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399, 0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC, 0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF, 0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333, 0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966, 0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99, 0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF, 0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900, 0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33, 0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399, 0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC, 0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF, 0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333, 0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966, 0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99, 0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF, 0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900, 0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33, 0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399, 0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC, 0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF};'. ^theTable! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 10/27/1999 17:54'! deltaFrom: x1 to: x2 nSteps: n "Utility routine for computing Warp increments." self inline: true. x2 > x1 ifTrue: [^ x2 - x1 + FixedPt1 // (n+1) + 1] ifFalse: [x2 = x1 ifTrue: [^ 0]. ^ 0 - (x1 - x2 + FixedPt1 // (n+1) + 1)]! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 7/24/1999 19:16'! dither32To16: srcWord threshold: ditherValue "Dither the given 32bit word to 16 bit. Ignore alpha." | pv threshold value out | self inline: true. "You bet" pv _ srcWord bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ value + 1] ifFalse:[out _ value]. pv _ (srcWord bitShift: -8) bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ out bitOr: (value+1 bitShift:5)] ifFalse:[out _ out bitOr: (value bitShift: 5)]. pv _ (srcWord bitShift: -16) bitAnd: 255. threshold _ ditherThresholds16 at: (pv bitAnd: 7). value _ ditherValues16 at: (pv bitShift: -3). ditherValue < threshold ifTrue:[out _ out bitOr: (value+1 bitShift:10)] ifFalse:[out _ out bitOr: (value bitShift: 10)]. ^out! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 2/9/2000 20:48'! isIdentityMap: shifts with: masks "Return true if shiftTable/maskTable define an identity mapping." self var: #shifts declareC:'int *shifts'. self var: #masks declareC:'int *masks'. (shifts == nil or:[masks == nil]) ifTrue:[^true]. ((shifts at: RedIndex) = 0 and:[(shifts at: GreenIndex) = 0 and:[(shifts at: BlueIndex) = 0 and:[(shifts at: AlphaIndex) = 0 and:[((masks at: RedIndex) = 16rFF0000) and:[((masks at: GreenIndex) = 16r00FF00) and:[((masks at: BlueIndex) = 16r0000FF) and:[((masks at: AlphaIndex) = 16rFF000000)]]]]]]]) ifTrue:[^true]. ^false! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 1/10/2000 18:32'! mapDestPixel: destPixel "Expand the given destination pixel value into canonical RGBA space." | val | self inline: true. noDestMap ifTrue:[^destPixel]. destMap == nil ifTrue:[val _ destPixel] ifFalse:[val _ destMap at: (destPixel bitAnd: dmMask)]. (dmShiftTable == nil or:[dmMaskTable == nil]) ifTrue:[^val] ifFalse:[^self rgbMapPixel: val shifts: dmShiftTable masks: dmMaskTable].! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 1/13/2000 22:11'! mapPixel: sourcePixel "Color map the given source pixel." | pv | self inline: true. noColorMap ifTrue:[^sourcePixel]. (cmMaskTable == nil or:[cmShiftTable == nil]) ifTrue:[pv _ sourcePixel] ifFalse:[pv _ self rgbMapPixel: sourcePixel shifts: cmShiftTable masks: cmMaskTable]. colorMap == nil ifTrue:[^pv] ifFalse:[^colorMap at: (pv bitAnd: cmMask)]! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 1/10/2000 18:33'! mapSourcePixel: sourcePixel "Expand the given source pixel value into canonical RGBA space." | val | self inline: true. noSourceMap ifTrue:[^sourcePixel]. sourceMap == nil ifTrue:[val _ sourcePixel] ifFalse:[val _ sourceMap at: (sourcePixel bitAnd: smMask)]. (smShiftTable == nil or:[smMaskTable == nil]) ifTrue:[^val] ifFalse:[^self rgbMapPixel: val shifts: smShiftTable masks: smMaskTable].! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 2/16/2000 17:12'! pickSourcePixels: nPixels srcMask: srcMask destMask: dstMask paintMode: paintMode destWord: destinationWord "Pick nPix pixels starting at srcBitIndex from the source, map by the color map, and justify them according to dstBitIndex in the resulting destWord." | sourceWord destWord sourcePix destPix srcShift dstShift nPix lastPix | "Note: The method must be inlined for LSB-MSB conversion" self inline: true. sourceWord _ self srcLongAt: sourceIndex. destWord _ 0. srcShift _ srcBitShift. "Hint: Keep in register" dstShift _ dstBitShift. "Hint: Keep in register" nPix _ nPixels. "always > 0 so we can use do { } while(--nPix);" "Pick and map first pixel so we can avoid the color conversion" lastPix _ sourcePix _ sourceWord >> srcShift bitAnd: srcMask. destPix _ self mapPixel: sourcePix. ["Mix in pixel" (paintMode and:[sourcePix = sourceAlphaKey]) ifTrue:[ destWord _ destWord bitOr: (destinationWord bitAnd: (dstMask << dstShift)). ] ifFalse:[destWord _ destWord bitOr: (destPix bitAnd: dstMask) << dstShift]. destMSB ifTrue:[dstShift _ dstShift - destDepth] ifFalse:[dstShift _ dstShift + destDepth]. "Adjust source if at pixel boundary" sourceMSB ifTrue:[ (srcShift _ srcShift - sourceDepth) < 0 ifTrue: [srcShift _ srcShift + 32. sourceWord _ self srcLongAt: (sourceIndex _ sourceIndex + 4)]. ] ifFalse:[ (srcShift _ srcShift + sourceDepth) > 31 ifTrue: [srcShift _ srcShift - 32. sourceWord _ self srcLongAt: (sourceIndex _ sourceIndex + 4)]. ]. (nPix _ nPix - 1) = 0] whileFalse:["Pick and map next pixel" sourcePix _ sourceWord >> srcShift bitAnd: srcMask. lastPix = sourcePix ifFalse:[ "map the pixel(either into colorMap or destFormat)" destPix _ self mapPixel: sourcePix. lastPix _ sourcePix]]. srcBitShift _ srcShift. "Store back" "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" destMSB ifTrue:[dstBitShift _ 32 - destDepth] "Shift towards leftmost pixel" ifFalse:[dstBitShift _ 0]. ^destWord! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 1/11/2000 01:45'! pickWarpPixelAtX: x y: y "Pick a single pixel from the source for WarpBlt. Note: This method is crucial for WarpBlt speed w/o smoothing and still relatively important when smoothing is used." | srcIndex sourceWord sourcePix | self inline: true. "Fetch source word. Note: We should really update srcIndex with sx and sy so that we don't have to do the computation below. We might even be able to simplify the out of bounds test from above." srcIndex _ sourceBits + (y * sourcePitch) + (x >> warpAlignShift * 4). sourceWord _ self srcLongAt: srcIndex. "Extract pixel from word" srcBitShift _ warpBitShiftTable at: (x bitAnd: warpAlignMask). sourcePix _ sourceWord >> srcBitShift bitAnd: warpSrcMask. ^sourcePix! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 1/10/2000 16:16'! rgbMapPixel: sourcePixel shifts: shifts masks: masks "Perform the RGBA conversion for the given source pixel" | val mask shift | self inline: true. val _ 0. mask _ (self cCoerce: masks to:'int*') at: RedIndex. shift _ (self cCoerce: shifts to:'int*') at: RedIndex. val _ val bitOr: ((sourcePixel bitAnd: mask) bitShift: shift). mask _ (self cCoerce: masks to:'int*') at: GreenIndex. shift _ (self cCoerce: shifts to:'int*') at: GreenIndex. val _ val bitOr: ((sourcePixel bitAnd: mask) bitShift: shift). mask _ (self cCoerce: masks to:'int*') at: BlueIndex. shift _ (self cCoerce: shifts to:'int*') at: BlueIndex. val _ val bitOr: ((sourcePixel bitAnd: mask) bitShift: shift). mask _ (self cCoerce: masks to:'int*') at: AlphaIndex. mask = 0 "common case" ifFalse:[shift _ (self cCoerce: shifts to:'int*') at: AlphaIndex. val _ val bitOr: ((sourcePixel bitAnd: mask) bitShift: shift)]. "Avoid transparency by color reduction. Ugh ... check this!!!!!!" (val = 0 and:[sourcePixel ~= 0]) ifTrue:[val _ 1]. ^val! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 2/13/2000 15:47'! warpPickSmoothPixels: nPixels xDeltah: xDeltah yDeltah: yDeltah xDeltav: xDeltav yDeltav: yDeltav paintMode: paintMode destWord: destinationWord "Pick n (sub-) pixels from the source form, mapped by sourceMap, average the RGB values, map by colorMap and return the new word. This version is only called from WarpBlt with smoothingCount > 1" | rgb x y a r g b xx yy xdh ydh xdv ydv dstMask destWord i j k nPix pv n ptX ptY | self inline: false. "nope - too much stuff in here" n _ warpQuality. n = 2 "Try avoiding divides for most common n (divide by 2 is generated as shift)" ifTrue:[xdh _ xDeltah // 2. ydh _ yDeltah // 2. xdv _ xDeltav // 2. ydv _ yDeltav // 2] ifFalse:[xdh _ xDeltah // n. ydh _ yDeltah // n. xdv _ xDeltav // n. ydv _ yDeltav // n]. dstMask _ maskTable at: destDepth. destWord _ 0. i _ nPixels. [ x _ sx. y _ sy. a _ r _ g _ b _ 0. "Pick and average n*n subpixels" nPix _ 0. "actual number of pixels (not clipped and not transparent)" j _ n. [ xx _ x. yy _ y. k _ n. [ "get a single subpixel if not clipped" (xx < 0 or:[yy < 0 or:[ (ptX _ xx >> BinaryPoint) >= sourceWidth or:[ (ptY _ yy >> BinaryPoint) >= sourceHeight]]]) ifFalse:[ pv _ self pickWarpPixelAtX: ptX y: ptY. (paintMode and: [pv = sourceAlphaKey]) ifFalse:[ "If not clipped and not transparent, then tally rgb values" nPix _ nPix + 1. rgb _ self mapSourcePixel: pv. b _ b + (rgb bitAnd: 255). g _ g + (rgb >> 8 bitAnd: 255). r _ r + (rgb >> 16 bitAnd: 255). a _ a + (rgb >> 24)]]. xx _ xx + xdh. yy _ yy + ydh. (k _ k - 1) = 0] whileFalse. x _ x + xdv. y _ y + ydv. (j _ j - 1) = 0] whileFalse. (nPix = 0 or: [paintMode and: [nPix < (n * n // 2)]]) ifTrue:[ nPix _ pv _ 0 "All pixels were 0, or most were transparent" ] ifFalse:[ "normalize rgba sums" nPix = 4 "Try to avoid divides for most common n" ifTrue:[r _ r >> 2. g _ g >> 2. b _ b >> 2. a _ a >> 2] ifFalse:[ r _ r // nPix. g _ g // nPix. b _ b // nPix. a _ a // nPix]. rgb _ (a << 24) + (r << 16) + (g << 8) + b. "map the pixel" rgb = 0 ifTrue: [ "only generate zero if pixel is really transparent" (r + g + b + a) > 0 ifTrue: [rgb _ 1]]. pv _ self mapPixel: rgb. ]. "Mix it in" nPix = 0 ifTrue:[destWord _ destWord bitOr: (destinationWord bitAnd: (dstMask << dstBitShift))] ifFalse:[destWord _ destWord bitOr: (pv bitAnd: dstMask) << dstBitShift]. destMSB ifTrue:[dstBitShift _ dstBitShift - destDepth] ifFalse:[dstBitShift _ dstBitShift + destDepth]. sx _ sx + xDeltah. sy _ sy + yDeltah. (i _ i - 1) = 0] whileFalse. "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" destMSB "Shift towards leftmost pixel" ifTrue:[dstBitShift _ 32 - destDepth] ifFalse:[dstBitShift _ 0]. ^destWord! ! !FXBltSimulation methodsFor: 'pixel mapping' stamp: 'ar 2/13/2000 15:48'! warpPickSourcePixels: nPixels xDeltah: xDeltah yDeltah: yDeltah xDeltav: xDeltav yDeltav: yDeltav paintMode: paintMode destWord: destinationWord "Pick n pixels from the source form, map by colorMap and return aligned by dstBitShift. This version is only called from WarpBlt with smoothingCount = 1" | dstMask resultWord nPix sourcePix mappedPix lastPix x y hasPix | self inline: true. "This should go into warpLoop" dstMask _ maskTable at: destDepth. resultWord _ 0. nPix _ nPixels. "Pick and map first pixel to avoid color conversion" lastPix _ 0. hasPix _ false. (sx < 0 or:[sy < 0 or:[ (x _ sx >> BinaryPoint) >= sourceWidth or:[ (y _ sy >> BinaryPoint) >= sourceHeight]]]) ifFalse:[ lastPix _ sourcePix _ self pickWarpPixelAtX: x y: y. (paintMode and:[sourcePix = sourceAlphaKey]) ifFalse:[hasPix _ true]]. "Note: lastPix might be zero here but who cares..." mappedPix _ self mapPixel: lastPix. ["Mix it in" hasPix ifTrue:[resultWord _ resultWord bitOr: (mappedPix bitAnd: dstMask) << dstBitShift] ifFalse:[resultWord _ resultWord bitOr: (destinationWord bitAnd: (dstMask << dstBitShift))]. destMSB ifTrue:[dstBitShift _ dstBitShift - destDepth] ifFalse:[dstBitShift _ dstBitShift + destDepth]. sx _ sx + xDeltah. sy _ sy + yDeltah. (nPix _ nPix - 1) = 0] whileFalse:["Pick and map next pixel" hasPix _ false. (sx < 0 or:[sy < 0 or:[ (x _ sx >> BinaryPoint) >= sourceWidth or:[ (y _ sy >> BinaryPoint) >= sourceHeight]]]) ifFalse:[ sourcePix _ self pickWarpPixelAtX: x y: y. (paintMode and:[sourcePix = sourceAlphaKey]) ifFalse:[ hasPix _ true. lastPix = sourcePix ifFalse:[ "map the pixel(either into colorMap or destFormat)" mappedPix _ self mapPixel: sourcePix. lastPix _ sourcePix]]]. ]. "*** side effect ***" "*** only the first pixel fetch can be unaligned ***" "*** prepare the next one for aligned access ***" destMSB "Shift towards leftmost pixel" ifTrue:[dstBitShift _ 32 - destDepth] ifFalse:[dstBitShift _ 0]. ^resultWord! ! !FXBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:23'! dstLongAt: idx ^interpreterProxy longAt: idx! ! !FXBltSimulation methodsFor: 'memory access' stamp: 'ar 10/26/1999 18:08'! dstLongAt: idx put: value ^interpreterProxy longAt: idx put: value! ! !FXBltSimulation methodsFor: 'memory access' stamp: 'ar 12/7/1999 21:09'! dstLongAt: idx put: srcValue mask: dstMask "Store the given value back into destination form, using dstMask to mask out the bits to be modified. This is an essiantial read-modify-write operation on the destination form." | dstValue | self inline: true. dstValue _ self dstLongAt: idx. dstValue _ dstValue bitAnd: dstMask. dstValue _ dstValue bitOr: srcValue. self dstLongAt: idx put: dstValue.! ! !FXBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:22'! halftoneAt: idx "Return a value from the halftone pattern." ^interpreterProxy longAt: halftoneBase + (idx \\ halftoneHeight * 4)! ! !FXBltSimulation methodsFor: 'memory access' stamp: 'ar 10/25/1999 19:23'! srcLongAt: idx ^interpreterProxy longAt: idx! ! !FXBltSimulation methodsFor: 'surface support' stamp: 'ar 5/25/2000 16:10'! loadSurfacePlugin "Load the surface support plugin" querySurfaceFn _ interpreterProxy ioLoadFunction:'ioGetSurfaceFormat' From:'SurfacePlugin'. lockSurfaceFn _ interpreterProxy ioLoadFunction:'ioLockSurface' From:'SurfacePlugin'. unlockSurfaceFn _ interpreterProxy ioLoadFunction:'ioUnlockSurface' From:'SurfacePlugin'. ^querySurfaceFn ~= 0 and:[lockSurfaceFn ~= 0 and:[unlockSurfaceFn ~= 0]]! ! !FXBltSimulation methodsFor: 'surface support' stamp: 'ar 5/25/2000 16:39'! lockSurfaces "Get a pointer to the bits of any OS surfaces." "Notes: * For equal source/dest handles only one locking operation is performed. This is to prevent locking of overlapping areas which does not work with certain APIs (e.g., DirectDraw prevents locking of overlapping areas). A special case for non-overlapping but equal source/dest handle would be possible but we would have to transfer this information over to unlockSurfaces somehow (currently, only one unlock operation is performed for equal source and dest handles). Also, this would require a change in the notion of ioLockSurface() which is right now interpreted as a hint and not as a requirement to lock only the specific portion of the surface. * The arguments in ioLockSurface() provide the implementation with an explicit hint what area is affected. It can be very useful to know the max. affected area beforehand if getting the bits requires expensive copy operations (e.g., like a roundtrip to the X server or a glReadPixel op). However, the returned pointer *MUST* point to the virtual origin of the surface and not to the beginning of the rectangle. The promise made by BitBlt is to never access data outside the given rectangle (aligned to 4byte boundaries!!) so it is okay to return a pointer to the virtual origin that is actually outside the valid memory area. * The area provided in ioLockSurface() is already clipped (e.g., it will always be inside the source and dest boundingBox) but it is not aligned to word boundaries yet. It is up to the support code to compute accurate alignment if necessary. * Warping always requires the entire source surface to be locked because there is no beforehand knowledge about what area will actually be traversed. " | sourceHandle destHandle l r t b fn | self inline: true. "If the CCodeGen learns how to inline #cCode: methods" self var: #fn declareC:'int (*fn)(int, int*, int, int, int, int)'. hasSurfaceLock _ false. destBits = 0 ifTrue:["Blitting *to* OS surface" lockSurfaceFn = 0 ifTrue:[self loadSurfacePlugin ifFalse:[^nil]]. fn _ self cCoerce: lockSurfaceFn to: 'int (*)(int, int*, int, int, int, int)'. destHandle _ interpreterProxy fetchInteger: FormBitsIndex ofObject: destForm. (sourceBits = 0 and:[noSource not]) ifTrue:[ sourceHandle _ interpreterProxy fetchInteger: FormBitsIndex ofObject: sourceForm. "Handle the special case of equal source and dest handles" (sourceHandle = destHandle) ifTrue:[ "If we have overlapping source/dest we lock the entire area so that there is only one area transmitted" noWarp ifTrue:[ l _ sx min: dx. r _ (sx max: dx) + bbW. t _ sy min: dy. b _ (sy max: sy) + bbH. sourceBits _ self cCode:'fn(sourceHandle, &sourcePitch, l, t, r-l, b-t)'. ] ifFalse:[ "When warping we always need the entire surface for the source" sourceBits _ self cCode:'fn(sourceHandle, &sourcePitch, 0,0, sourceWidth, sourceHeight)'. ]. destBits _ sourceBits. destPitch _ sourcePitch. hasSurfaceLock _ true. ^destBits ~~ 0 ]. "Fall through - if not equal it'll be handled below" ]. destBits _ self cCode:'fn(destHandle, &destPitch, dx, dy, bbW, bbH)'. hasSurfaceLock _ true. ]. (sourceBits == 0 and:[noSource not]) ifTrue:["Blitting *from* OS surface" sourceHandle _ interpreterProxy fetchInteger: FormBitsIndex ofObject: sourceForm. lockSurfaceFn = 0 ifTrue:[self loadSurfacePlugin ifFalse:[^nil]]. fn _ self cCoerce: lockSurfaceFn to: 'int (*)(int, int*, int, int, int, int)'. "Warping requiring the entire surface" noWarp ifTrue:[ sourceBits _ self cCode:'fn(sourceHandle, &sourcePitch, sx, sy, bbW, bbH)'. ] ifFalse:[ sourceBits _ self cCode:'fn(sourceHandle, &sourcePitch, 0, 0, sourceWidth, sourceHeight)'. ]. hasSurfaceLock _ true. ]. ^destBits ~~ 0 and:[sourceBits ~~ 0 or:[noSource]].! ! !FXBltSimulation methodsFor: 'surface support' stamp: 'ar 5/25/2000 16:01'! queryDestSurface: handle "Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller." querySurfaceFn = 0 ifTrue:[self loadSurfacePlugin ifFalse:[^false]]. ^(self cCode:' ((int (*) (int, int*, int*, int*, int*))querySurfaceFn) (handle, &destWidth, &destHeight, &destDepth, &destMSB)' inSmalltalk:[false])! ! !FXBltSimulation methodsFor: 'surface support' stamp: 'ar 5/25/2000 16:00'! querySourceSurface: handle "Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller." querySurfaceFn = 0 ifTrue:[self loadSurfacePlugin ifFalse:[^false]]. ^(self cCode:' ((int (*) (int, int*, int*, int*, int*))querySurfaceFn) (handle, &sourceWidth, &sourceHeight, &sourceDepth, &sourceMSB)' inSmalltalk:[false])! ! !FXBltSimulation methodsFor: 'surface support' stamp: 'ar 5/25/2000 16:07'! unlockSurfaces "Unlock the bits of any OS surfaces." "See the comment in lockSurfaces. Similar rules apply. That is, the area provided in ioUnlockSurface can be used to determine the dirty region after drawing. If a source is unlocked, then the area will be (0,0,0,0) to indicate that no portion is dirty." | sourceHandle destHandle destLocked fn | self var: #fn declareC:'int (*fn)(int, int, int, int, int)'. hasSurfaceLock ifTrue:[ unlockSurfaceFn = 0 ifTrue:[self loadSurfacePlugin ifFalse:[^nil]]. fn _ self cCoerce: unlockSurfaceFn to: 'int (*)(int, int, int, int, int)'. destLocked _ false. destHandle _ interpreterProxy fetchPointer: FormBitsIndex ofObject: destForm. (interpreterProxy isIntegerObject: destHandle) ifTrue:[ destHandle _ interpreterProxy integerValueOf: destHandle. "The destBits are always assumed to be dirty" self cCode:'fn(destHandle, affectedL, affectedT, affectedR-affectedL, affectedB-affectedT)'. destBits _ destPitch _ 0. destLocked _ true. ]. noSource ifFalse:[ sourceHandle _ interpreterProxy fetchPointer: FormBitsIndex ofObject: sourceForm. (interpreterProxy isIntegerObject: sourceHandle) ifTrue:[ sourceHandle _ interpreterProxy integerValueOf: sourceHandle. "Only unlock sourceHandle if different from destHandle" (destLocked and:[sourceHandle = destHandle]) ifFalse:[self cCode: 'fn(sourceHandle, 0, 0, 0, 0)']. sourceBits _ sourcePitch _ 0. ]. ]. hasSurfaceLock _ false. ].! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! copyLoopPixMapLsbLsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self copyLoopPixMap. ! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! copyLoopPixMapLsbMsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self copyLoopPixMap. ! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! copyLoopPixMapMsbLsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self copyLoopPixMap. ! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! copyLoopPixMapMsbMsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self copyLoopPixMap. ! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/16/2000 17:41'! doCopyLoopPixMap self inline: true. sourceMSB ifTrue:[ destMSB ifTrue:[self copyLoopPixMapMsbMsb] ifFalse:[self copyLoopPixMapMsbLsb] ] ifFalse:[ destMSB ifTrue:[self copyLoopPixMapLsbMsb] ifFalse:[self copyLoopPixMapLsbLsb] ].! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/16/2000 17:41'! doWarpLoop self inline: true. sourceMSB ifTrue:[ destMSB ifTrue:[self warpLoopMsbMsb] ifFalse:[self warpLoopMsbLsb] ] ifFalse:[ destMSB ifTrue:[self warpLoopLsbMsb] ifFalse:[self warpLoopLsbLsb] ].! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! warpLoopLsbLsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self warpLoop.! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:19'! warpLoopLsbMsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self warpLoop.! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:20'! warpLoopMsbLsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self warpLoop.! ! !FXBltSimulation methodsFor: 'special inner loops' stamp: 'ar 2/19/2000 20:20'! warpLoopMsbMsb "Note: This loop is specialized by the CCodeGenerator." self inline: false. self warpLoop.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FXBltSimulation class instanceVariableNames: ''! !FXBltSimulation class methodsFor: 'initialization' stamp: 'ar 2/16/2000 19:17'! initialize "BitBltSimulation initialize" self initializeRuleTable. "Mask constants" AllOnes _ 16rFFFFFFFF. BinaryPoint _ 14. FixedPt1 _ 1 << BinaryPoint. "Value of 1.0 in Warp's fixed-point representation" "Form fields" FormBitsIndex _ 0. FormWidthIndex _ 1. FormHeightIndex _ 2. FormDepthIndex _ 3. "BitBlt fields" BBDestFormIndex _ 0. BBSourceFormIndex _ 1. BBHalftoneFormIndex _ 2. BBRuleIndex _ 3. BBDestXIndex _ 4. BBDestYIndex _ 5. BBWidthIndex _ 6. BBHeightIndex _ 7. BBSourceXIndex _ 8. BBSourceYIndex _ 9. BBClipXIndex _ 10. BBClipYIndex _ 11. BBClipWidthIndex _ 12. BBClipHeightIndex _ 13. BBColorMapIndex _ 14. FXSourceMapIndex _ 15. FXDestMapIndex _ 16. FXWarpQuadIndex _ 17. FXWarpQualityIndex _ 18. FXSourceKeyIndex _ 19. FXDestKeyIndex _ 20. FXSourceAlphaIndex _ 21. FXTallyMapIndex _ 22. "RGBA indexes" RedIndex _ 0. GreenIndex _ 1. BlueIndex _ 2. AlphaIndex _ 3. "Color cache" ColorCacheSize _ 512. "Should be at least 256 and must be power of two" ColorCacheMask _ ColorCacheSize - 1. InvalidColorCacheEntry _ 16r01EC1B55. ! ! !FXBltSimulation class methodsFor: 'initialization' stamp: 'ar 1/22/2000 15:46'! initializeRuleTable "BitBltSimulation initializeRuleTable" "**WARNING** You MUST change initBBOpTable if you change this" OpTable _ #( "0" clearWord:with: "1" bitAnd:with: "2" bitAndInvert:with: "3" sourceWord:with: "4" bitInvertAnd:with: "5" destinationWord:with: "6" bitXor:with: "7" bitOr:with: "8" bitInvertAndInvert:with: "9" bitInvertXor:with: "10" bitInvertDestination:with: "11" bitOrInvert:with: "12" bitInvertSource:with: "13" bitInvertOr:with: "14" bitInvertOrInvert:with: "15" destinationWord:with: "16" destinationWord:with: "unused - was old paint" "17" destinationWord:with: "unused - was old mask" "18" addWord:with: "19" subWord:with: "20" rgbAdd:with: "21" rgbSub:with: "22" OLDrgbDiff:with: "23" OLDtallyIntoMap:with: "24" alphaBlend:with: "25" pixPaint:with: "26" pixMask:with: "27" rgbMax:with: "28" rgbMin:with: "29" rgbMinInvert:with: "30" alphaBlendConst:with: "31" alphaPaintConst:with: "32" rgbDiff:with: "33" tallyIntoMap:with: "34" alphaBlendScaled:with: "35" srcPaint:with: "36" dstPaint:with: ). OpTableSize _ OpTable size + 1. "0-origin indexing" ! ! !FXBltSimulation class methodsFor: 'initialization'! test2 "BitBltSimulation test2" | f | Display fillWhite: (0@0 extent: 300@140). 1 to: 12 do: [:i | f _ (Form extent: i@5) fillBlack. 0 to: 20 do: [:x | f displayOn: Display at: (x*13) @ (i*10)]]! ! !FXBltSimulation class methodsFor: 'initialization'! timingTest: extent "BitBltSimulation timingTest: 640@480" | f f2 map | f _ Form extent: extent depth: 8. f2 _ Form extent: extent depth: 8. map _ Bitmap new: 1 << f2 depth. ^ Array with: (Time millisecondsToRun: [100 timesRepeat: [f fillWithColor: Color white]]) with: (Time millisecondsToRun: [100 timesRepeat: [f copy: f boundingBox from: 0@0 in: f2 rule: Form over]]) with: (Time millisecondsToRun: [100 timesRepeat: [f copyBits: f boundingBox from: f2 at: 0@0 colorMap: map]])! ! !FXBltSimulation class methodsFor: 'translation' stamp: 'ar 5/25/2000 16:29'! declareCVarsIn: aCCodeGenerator aCCodeGenerator var: 'colorMap' declareC:'int *colorMap'; var: 'cmShiftTable' declareC:'int *cmShiftTable'; var: 'cmMaskTable' declareC:'int *cmMaskTable'; var: 'sourceMap' declareC:'int *sourceMap'; var: 'smShiftTable' declareC:'int *smShiftTable'; var: 'smMaskTable' declareC:'int *smMaskTable'; var: 'destMap' declareC:'int *destMap'; var: 'dmShiftTable' declareC:'int *dmShiftTable'; var: 'dmMaskTable' declareC:'int *dmMaskTable'; var: 'warpQuad' declareC:'int warpQuad[8]'; var: 'tallyMap' declareC:'int *tallyMap'. aCCodeGenerator var: 'opTable' declareC: 'int opTable[' , OpTableSize printString , ']'. aCCodeGenerator var: 'maskTable' declareC:'int maskTable[33] = { 0, 1, 3, 0, 15, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 }'. aCCodeGenerator var: 'ditherMatrix4x4' declareC:'const int ditherMatrix4x4[16] = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 }'. aCCodeGenerator var: 'ditherThresholds16' declareC:'const int ditherThresholds16[8] = { 0, 2, 4, 6, 8, 12, 14, 16 }'. aCCodeGenerator var: 'ditherValues16' declareC:'const int ditherValues16[32] = { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }'. aCCodeGenerator var: 'warpBitShiftTable' declareC:'int warpBitShiftTable[32]'.! ! !FXBltSimulation class methodsFor: 'translation' stamp: 'ar 2/21/2000 20:29'! moduleName "FXBltScanner translateLocally" ^'FXBltPlugin'! ! !FXBltSimulation class methodsFor: 'translation' stamp: 'jm 5/12/1999 12:02'! opTable ^ OpTable ! ! !FXBltSimulation class methodsFor: 'translation' stamp: 'ar 2/16/2000 19:32'! specializeLoopsIn: cg "FXBltScanner translate" "Specialize those loops that benefit from a distinction of LSB vs. MSB variants" | i s | 'Specializing inner loops' displayProgressAt: Sensor cursorPoint from: 1 to: 8 during:[:bar| i _ 0. {true. false} do:[:srcMsb| {true. false} do:[:dstMsb| #(copyLoopPixMap warpLoop) do:[:sel| bar value: (i_i+1). s _ sel, (srcMsb ifTrue:['Msb'] ifFalse:['Lsb']), (dstMsb ifTrue:['Msb'] ifFalse:['Lsb']). s _ s asSymbol. cg specializeMethod: s variable: 'sourceMSB' value: srcMsb. cg specializeMethod: s variable: 'destMSB' value: dstMsb]]]. ].! ! !FXBltSimulation class methodsFor: 'system simulation' stamp: 'ar 10/27/1999 23:34'! copyBitsFrom: aBitBlt "Simulate the copyBits primitive" | proxy bb | proxy _ InterpreterProxy new. proxy loadStackFrom: thisContext sender. bb _ self simulatorClass new. bb setInterpreter: proxy. proxy success: (bb loadBitBltFrom: aBitBlt). bb copyBits. proxy failed ifFalse:[ proxy showDisplayBits: aBitBlt destForm Left: bb affectedLeft Top: bb affectedTop Right: bb affectedRight Bottom: bb affectedBottom]. ^proxy stackValue: 0! ! !FXBltSimulation class methodsFor: 'system simulation' stamp: 'ar 2/21/2000 20:24'! fxCopyBitsFrom: aBitBlt "Simulate the copyBits primitive" | proxy bb | proxy _ InterpreterProxy new. proxy loadStackFrom: thisContext sender. bb _ self simulatorClass new. bb setInterpreter: proxy. proxy success: (bb loadBitBltFrom: aBitBlt). bb copyBits. proxy failed ifFalse:[ proxy showDisplayBits: aBitBlt destForm Left: bb affectedLeft Top: bb affectedTop Right: bb affectedRight Bottom: bb affectedBottom]. ^proxy stackValue: 0! ! !FXBltSimulation class methodsFor: 'system simulation' stamp: 'ar 1/22/2000 15:45'! simulatorClass ^FXBltSimulator! ! !FXBltSimulation class methodsFor: 'system simulation' stamp: 'ar 1/10/2000 17:46'! warpBitsFrom: aBitBlt "Simulate the warpBits primitive" | proxy bb | proxy _ InterpreterProxy new. proxy loadStackFrom: thisContext sender. bb _ self simulatorClass new. bb setInterpreter: proxy. proxy success: (bb loadWarpBltFrom: aBitBlt). bb copyBits. proxy failed ifFalse:[ proxy showDisplayBits: aBitBlt destForm Left: bb affectedLeft Top: bb affectedTop Right: bb affectedRight Bottom: bb affectedBottom]. ^proxy stackValue: 0! ! FXBltSimulation subclass: #FXBltSimulator instanceVariableNames: '' classVariableNames: 'ColorCacheHits ColorCacheMisses ' poolDictionaries: '' category: 'Graphics-FXBlt'! !FXBltSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/25/2000 15:42'! initBBOpTable opTable _ OpTable. maskTable _ Array new: 32. #(1 2 4 8 16 32) do:[:i| maskTable at: i put: (1 << i)-1]. self initializeDitherTables. warpBitShiftTable _ CArrayAccessor on: (Array new: 32). cmCache _ CArrayAccessor on: (Array new: ColorCacheSize*2). warpQuad _ CArrayAccessor on: (Array new: 8).! ! !FXBltSimulator methodsFor: 'as yet unclassified' stamp: 'ar 7/24/1999 23:20'! initializeDitherTables ditherMatrix4x4 _ CArrayAccessor on: #( 0 8 2 10 12 4 14 6 3 11 1 9 15 7 13 5). ditherThresholds16 _ CArrayAccessor on:#(0 2 4 6 8 10 12 14 16). ditherValues16 _ CArrayAccessor on: #(0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30).! ! !FXBltSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/25/2000 16:37'! mergeFn: arg1 with: arg2 ^ self perform: (self cCoerce:(opTable at: combinationRule+1) to:'(int (*) (int,int))') with: arg1 with: arg2! ! !FXBltSimulator methodsFor: 'debug support' stamp: 'ar 10/27/1999 14:22'! dstLongAt: dstIndex interpreterProxy isInterpreterProxy ifTrue:[^dstIndex longAt: 0]. ((dstIndex anyMask: 3) or:[dstIndex + 4 < destBits or:[ dstIndex > (destBits + (destPitch * destHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: dstIndex! ! !FXBltSimulator methodsFor: 'debug support' stamp: 'ar 10/27/1999 14:23'! dstLongAt: dstIndex put: value interpreterProxy isInterpreterProxy ifTrue:[^dstIndex longAt: 0 put: value]. ((dstIndex anyMask: 3) or:[dstIndex < destBits or:[ dstIndex >= (destBits + (destPitch * destHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: dstIndex put: value! ! !FXBltSimulator methodsFor: 'debug support' stamp: 'ar 1/9/2000 16:26'! srcLongAt: srcIndex interpreterProxy isInterpreterProxy ifTrue:[^srcIndex longAt: 0]. ((srcIndex anyMask: 3) or:[srcIndex + 4 < sourceBits or:[ srcIndex > (sourceBits + (sourcePitch * sourceHeight))]]) ifTrue:[self error:'Out of bounds']. ^interpreterProxy longAt: srcIndex! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FXBltSimulator class instanceVariableNames: ''! !FXBltSimulator class methodsFor: 'translation' stamp: 'ar 1/22/2000 15:44'! translate: fileName doInlining: inlineFlag "Time millisecondsToRun: [ Interpreter translate: 'interp.c' doInlining: true. Smalltalk beep] 164760 167543 171826 174510" | cg | FXBltSimulation initialize. Interpreter initialize. ObjectMemory initialize. cg _ CCodeGenerator new initialize. cg addClass: FXBltSimulation. cg addClass: Interpreter. cg addClass: ObjectMemory. FXBltSimulation declareCVarsIn: cg. Interpreter declareCVarsIn: cg. ObjectMemory declareCVarsIn: cg. cg storeCodeOnFile: fileName doInlining: inlineFlag.! ! !FXBltSimulator class methodsFor: 'instance creation' stamp: 'ar 10/27/1999 14:16'! new ^super new initBBOpTable.! ! !FXBltSimulator class methodsFor: 'initialization' stamp: 'ar 1/10/2000 14:04'! resetCacheStats "BitBltSimulator resetCacheStats" ColorCacheHits _ ColorCacheMisses _ 0.! ! FXBlt subclass: #FXGrafPort instanceVariableNames: 'fillPattern ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-FXBlt'! !FXGrafPort methodsFor: 'copying' stamp: 'ar 2/17/2000 21:44'! clippedBy: aRectangle ^ self copy clipRect: (self clipRect intersect: aRectangle)! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 21:44'! fillOval: rect | centerX centerY nextY yBias xBias outer nextOuterX | rect area <= 0 ifTrue: [^ self]. height _ 1. yBias _ rect height odd ifTrue: [0] ifFalse: [-1]. xBias _ rect width odd ifTrue: [1] ifFalse: [0]. centerX _ rect center x. centerY _ rect center y. outer _ EllipseMidpointTracer new on: rect. nextY _ rect height // 2. [nextY > 0] whileTrue:[ nextOuterX _ outer stepInY. width _ (nextOuterX bitShift: 1) + xBias. destX _ centerX - nextOuterX. destY _ centerY - nextY. self copyBits. destY _ centerY + nextY + yBias. self copyBits. nextY _ nextY - 1. ]. destY _ centerY. height _ 1 + yBias. width _ rect width. destX _ rect left. self copyBits. ! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 5/27/2000 22:00'! fillRect: rect offset: aPoint "The offset is really just for stupid InfiniteForms." | fc | fillPattern class == InfiniteForm ifTrue:[ fc _ halftoneForm. self fillColor: nil. fillPattern displayOnPort: ((self clippedBy: rect) colorMap: nil) at: aPoint. halftoneForm _ fc. ^self]. "Let's try it the fast way first" (fillPattern isColor and:[destForm isFillAccelerated: combinationRule for: fillPattern]) ifTrue:[^destForm fill: (rect truncated intersect: self clipRect) rule: combinationRule fillColor: fillPattern]. destX _ rect left. destY _ rect top. sourceX _ 0. sourceY _ 0. width _ rect width. height _ rect height. self copyBits.! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 21:44'! frameOval: rect borderWidth: borderWidth | centerX centerY nextY yBias xBias wp outer inner nextOuterX nextInnerX fillAlpha | rect area <= 0 ifTrue: [^ self]. height _ 1. wp _ borderWidth asPoint. yBias _ rect height odd ifTrue: [0] ifFalse: [-1]. xBias _ rect width odd ifTrue: [1] ifFalse: [0]. centerX _ rect center x. centerY _ rect center y. outer _ EllipseMidpointTracer new on: rect. inner _ EllipseMidpointTracer new on: (rect insetBy: wp). nextY _ rect height // 2. 1 to: (wp y min: nextY) do:[:i| nextOuterX _ outer stepInY. width _ (nextOuterX bitShift: 1) + xBias. destX _ centerX - nextOuterX. destY _ centerY - nextY. self copyBits. destY _ centerY + nextY + yBias. self copyBits. nextY _ nextY - 1. ]. [nextY > 0] whileTrue:[ nextOuterX _ outer stepInY. nextInnerX _ inner stepInY. destX _ centerX - nextOuterX. destY _ centerY - nextY. width _ nextOuterX - nextInnerX. self copyBits. destX _ centerX + nextInnerX + xBias. self copyBits. destX _ centerX - nextOuterX. destY _ centerY + nextY + yBias. self copyBits. destX _ centerX + nextInnerX + xBias. self copyBits. nextY _ nextY - 1. ]. destY _ centerY. height _ 1 + yBias. width _ wp x. destX _ rect left. self copyBits. destX _ rect right - wp x. self copyBits. ! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/22/2000 19:54'! frameRect: rect borderWidth: borderWidth sourceX _ 0. sourceY _ 0. (rect areasOutside: (rect insetBy: borderWidth)) do: [:edgeStrip | self destRect: edgeStrip; copyBits]. ! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 21:44'! frameRectBottom: rect height: h destX _ rect left + 1. destY _ rect bottom - 1. width _ rect width - 2. height _ 1. 1 to: h do: [:i | self copyBits. destX _ destX + 1. destY _ destY - 1. width _ width - 2]. ! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 21:44'! frameRectRight: rect width: w width _ 1. height _ rect height - 1. destX _ rect right - 1. destY _ rect top + 1. 1 to: w do: [:i | self copyBits. destX _ destX - 1. destY _ destY + 1. height _ height - 2]. ! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 5/28/2000 15:58'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Draw the portion of the given Form defined by sourceRect at the given point using the given BitBlt combination rule." (sourceMap isNil and:[destMap isNil and:[colorMap isNil and:[fillPattern isNil]]]) ifTrue:[ "Let's try it the fast way if possible" (destForm isBltAccelerated: rule for: aForm) ifTrue:[ ^destForm copyBits: sourceRect truncated from: aForm at: aPoint asIntegerPoint clippingBox: self clipRect truncated rule: rule fillColor: fillPattern map: colorMap]]. sourceForm _ aForm. combinationRule _ rule. self sourceRect: sourceRect. self destOrigin: aPoint. self copyBits! ! !FXGrafPort methodsFor: 'drawing support' stamp: 'ar 2/22/2000 16:47'! stencil: stencilForm at: aPoint sourceRect: aRect "Paint using aColor wherever stencilForm has non-zero pixels" self sourceForm: stencilForm; destOrigin: aPoint; sourceRect: aRect. self copyBits! ! !FXGrafPort methodsFor: 'accessing' stamp: 'ar 2/17/2000 21:45'! alphaBits: a sourceAlpha _ a! ! !FXGrafPort methodsFor: 'accessing' stamp: 'ar 5/28/2000 15:51'! contentsOfArea: aRectangle into: aForm destForm displayOn: aForm at: aRectangle origin clippingBox: (0@0 extent: aRectangle extent). ^aForm! ! !FXGrafPort methodsFor: 'accessing' stamp: 'ar 5/25/2000 17:20'! displayScannerFor: para foreground: foreColor background: backColor ignoreColorChanges: shadowMode ^ (DisplayScanner new text: para text textStyle: para textStyle foreground: foreColor background: backColor fillBlt: self ignoreColorChanges: shadowMode) setPort: self clone! ! !FXGrafPort methodsFor: 'accessing' stamp: 'ar 2/17/2000 21:44'! fillPattern: anObject fillPattern _ anObject. self fillColor: anObject.! ! Morph subclass: #FaceMorph instanceVariableNames: 'leftEye leftEyebrow rightEye rightEyebrow lips ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Gestures'! !FaceMorph methodsFor: 'initialization' stamp: 'len 8/22/1999 23:27'! initialize super initialize. color _ Color transparent. self addMorph: (leftEye _ EyeMorph new). self addMorph: (rightEye _ EyeMorph new). self addMorph: (lips _ LipsMorph new). leftEye position: self position. rightEye position: leftEye extent x @ 0 + leftEye position. lips position: (0 @ 20 + (leftEye bottomRight + rightEye bottomLeft - lips extent // 2)). self bounds: self fullBounds! ! !FaceMorph methodsFor: 'accessing' stamp: 'len 8/22/1999 22:21'! leftEye ^ leftEye! ! !FaceMorph methodsFor: 'accessing' stamp: 'len 8/22/1999 19:47'! lips ^ lips! ! !FaceMorph methodsFor: 'accessing' stamp: 'len 8/22/1999 22:21'! rightEye ^ rightEye! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/22/1999 19:23'! closeEyelids leftEye closeEyelid. rightEye closeEyelid! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/24/1999 01:18'! grin self leftEye openness: (0.2 to: 1.0 by: 0.1) atRandom. self rightEye openness: (0.2 to: 1.0 by: 0.1) atRandom. self lips grin! ! !FaceMorph methodsFor: 'actions' stamp: 'len 9/7/1999 02:29'! happy self lips smile! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/22/1999 22:00'! hideTonge self lips hideTonge! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/22/1999 22:21'! lookAt: aPoint self leftEye lookAt: aPoint. self rightEye lookAt: aPoint! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/23/1999 22:51'! lookAtFront self leftEye lookAtFront. self rightEye lookAtFront! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/24/1999 01:05'! lookAtHand | hand | self isInWorld ifFalse: [^ self]. hand _ (self world activeHand) ifNil: [self world primaryHand]. self lookAtMorph: hand! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/24/1999 01:05'! lookAtMorph: aMorph self leftEye lookAtMorph: aMorph. self rightEye lookAtMorph: aMorph! ! !FaceMorph methodsFor: 'actions' stamp: 'len 9/7/1999 02:25'! neutral self lips neutral! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/22/1999 19:23'! openEyelids leftEye openEyelid. rightEye openEyelid! ! !FaceMorph methodsFor: 'actions' stamp: 'len 8/22/1999 23:18'! say: aString self lips showBalloon: aString! ! !FaceMorph methodsFor: 'geometry' stamp: 'len 8/24/1999 01:27'! mustachePosition ^ self nosePosition + self lips center // 2! ! !FaceMorph methodsFor: 'geometry' stamp: 'len 8/24/1999 01:26'! nosePosition ^ self center * 2 + self lips center // 3! ! !FaceMorph methodsFor: 'drawing' stamp: 'len 8/22/1999 21:56'! drawNoseOn: aCanvas | nosePosition | nosePosition _ self center * 2 + self lips center // 3. aCanvas fillOval: (nosePosition- (3@0) extent: 2 @ 2) color: Color black. aCanvas fillOval: (nosePosition + (3@0) extent: 2 @ 2) color: Color black! ! !FaceMorph methodsFor: 'drawing' stamp: 'len 8/22/1999 19:02'! drawOn: aCanvas super drawOn: aCanvas. self drawNoseOn: aCanvas! ! !FaceMorph methodsFor: 'stepping and presenter' stamp: 'len 8/24/1999 01:22'! step | amount | super step. 10 atRandom = 1 ifTrue: [[self lips perform: #(smile horror surprise sad grin) atRandom. (Delay forMilliseconds: 2000 atRandom) wait. self lips perform: #(neutral neutral smile sad) atRandom] fork]. 5 atRandom = 1 ifTrue: [[self closeEyelids. (Delay forMilliseconds: 180) wait. self openEyelids. 2 atRandom = 1 ifTrue: [self lookAtFront]] fork. ^ self]. "20 atRandom = 1 ifTrue: [(self perform: #(leftEye rightEye) atRandom) closeEyelid]." 20 atRandom = 1 ifTrue: [amount _ (0.2 to: 1.0 by: 0.01) atRandom. self leftEye openness: amount. self rightEye openness: amount]. 3 atRandom = 1 ifTrue: [self lookAtHand. ^ self]. 3 atRandom = 1 ifTrue: [self lookAtFront. ^ self]. 3 atRandom = 1 ifTrue: [self lookAtMorph: self world submorphs atRandom]! ! !FaceMorph methodsFor: 'stepping and presenter' stamp: 'len 9/13/1999 00:18'! stepTime ^ 1000! ! Object subclass: #FakeClassPool instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser'! !FakeClassPool commentStamp: '' prior: 0! The sole purpose of this class is to allow the Browser code pane to evaluate the class variables of the class whose method it is showing. It does this by stuffing a pointer to the classpool dictionary of the class being shown into its own classpool. It does this just around a doIt in the code pane. An instance of FakeClasspool is then used as the receiver of the doIt.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FakeClassPool class instanceVariableNames: ''! !FakeClassPool class methodsFor: 'as yet unclassified' stamp: 'di 5/10/1998 21:32'! adopt: classOrNil "Temporarily use the classPool and sharedPools of another class" classOrNil == nil ifTrue: [classPool _ nil. sharedPools _ nil] ifFalse: [classPool _ classOrNil classPool. sharedPools _ classOrNil sharedPools] ! ! Boolean subclass: #False instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Objects'! !False commentStamp: '' prior: 0! False defines the behavior of its single instance, false -- logical negation. Notice how the truth-value checks become direct message sends, without the need for explicit testing. Be aware however that most of these methods are not sent as real messages in normal use. Most are inline coded by the compiler as test and jump bytecodes - avoiding the overhead of the full message sends. So simply redefining these methods here will have no effect.! !False methodsFor: 'logical operations'! & alternativeObject "Evaluating conjunction -- answer false since receiver is false." ^self! ! !False methodsFor: 'logical operations'! not "Negation -- answer true since the receiver is false." ^true! ! !False methodsFor: 'logical operations'! | aBoolean "Evaluating disjunction (OR) -- answer with the argument, aBoolean." ^aBoolean! ! !False methodsFor: 'controlling'! and: alternativeBlock "Nonevaluating conjunction -- answer with false since the receiver is false." ^self! ! !False methodsFor: 'controlling'! ifFalse: alternativeBlock "Answer the value of alternativeBlock. Execution does not actually reach here because the expression is compiled in-line." ^alternativeBlock value! ! !False methodsFor: 'controlling'! ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock "Answer the value of falseAlternativeBlock. Execution does not actually reach here because the expression is compiled in-line." ^falseAlternativeBlock value! ! !False methodsFor: 'controlling'! ifTrue: alternativeBlock "Since the condition is false, answer the value of the false alternative, which is nil. Execution does not actually reach here because the expression is compiled in-line." ^nil! ! !False methodsFor: 'controlling'! ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock "Answer the value of falseAlternativeBlock. Execution does not actually reach here because the expression is compiled in-line." ^falseAlternativeBlock value! ! !False methodsFor: 'controlling'! or: alternativeBlock "Nonevaluating disjunction -- answer value of alternativeBlock." ^alternativeBlock value! ! !False methodsFor: 'printing'! printOn: aStream aStream nextPutAll: 'false'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! False class instanceVariableNames: ''! !False class methodsFor: 'as yet unclassified' stamp: 'sw 5/8/2000 11:09'! initializedInstance ^ false! ! CelesteComposition subclass: #FancyCelesteComposition instanceVariableNames: 'theLinkToInclude to subject textFields ' classVariableNames: '' poolDictionaries: '' category: 'Network-Mail Reader'! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 17:51'! borderAndButtonColor ^Color r: 0.729 g: 0.365 b: 0.729! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:14'! buttonWithAction: aSymbol label: labelString help: helpString ^self newColumn wrapCentering: #center; cellPositioning: #topCenter; addMorph: ( SimpleButtonMorph new color: self borderAndButtonColor; target: self; actionSelector: aSymbol; label: labelString; setBalloonText: helpString ) ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 18:55'! celeste: aCeleste to: argTo subject: argSubject initialText: aText theLinkToInclude: linkText celeste _ aCeleste. to _ argTo. subject _ argSubject. messageText _ aText. theLinkToInclude _ linkText. textFields _ #(). ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 19:03'! completeTheMessage | newText strm | textFields do: [ :each | each hasUnacceptedEdits ifTrue: [ each accept ] ]. newText _ String new: 200. strm _ WriteStream on: newText. strm nextPutAll: 'Content-Type: text/html'; cr; nextPutAll: 'From: ', Celeste userName; cr; nextPutAll: 'To: ',to; cr; nextPutAll: 'Subject: ',subject; cr; cr; nextPutAll: '
'; nextPutAll: messageText asString asHtml; nextPutAll: '

',theLinkToInclude,'
'. ^strm contents ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 17:35'! forgetIt morphicWindow ifNotNil: [ morphicWindow delete ]. mvcWindow ifNotNil: [ mvcWindow controller close ]. ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 17:42'! newColumn ^AlignmentMorph newColumn color: self staticBackgroundColor! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 17:41'! newRow ^AlignmentMorph newRow color: self staticBackgroundColor! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'ar 11/10/2000 15:46'! openInMorphic "open an interface for sending a mail message with the given initial text " | buttonsList container toField subjectField | buttonsList _ self newRow. buttonsList wrapCentering: #center; cellPositioning: #leftCenter. buttonsList addMorphBack: ( (self buttonWithAction: #submit label: 'send later' help: 'add this to the queue of messages to be sent') ); addMorphBack: ( (self buttonWithAction: #sendNow label: 'send now' help: 'send this message immediately') ); addMorphBack: ( (self buttonWithAction: #forgetIt label: 'forget it' help: 'forget about sending this message') ). morphicWindow _ container _ AlignmentMorphBob1 new borderWidth: 8; borderColor: self borderAndButtonColor; color: Color white. container addMorphBack: (buttonsList vResizing: #shrinkWrap; minHeight: 25; yourself); addMorphBack: ((self simpleString: 'To:') vResizing: #shrinkWrap; minHeight: 18; yourself); addMorphBack: ((toField _ PluggableTextMorph on: self text: #to accept: #to:) hResizing: #spaceFill; vResizing: #rigid; height: 50; yourself ); addMorphBack: ((self simpleString: 'Subject:') vResizing: #shrinkWrap; minHeight: 18; yourself); addMorphBack: ((subjectField _ PluggableTextMorph on: self text: #subject accept: #subject:) hResizing: #spaceFill; vResizing: #rigid; height: 50; yourself ); addMorphBack: ((self simpleString: 'Message:') vResizing: #shrinkWrap; minHeight: 18; yourself); addMorphBack: ((textEditor _ PluggableTextMorph on: self text: #messageText accept: #messageText:) hResizing: #spaceFill; vResizing: #spaceFill; yourself ). textFields _ {toField. subjectField. textEditor}. container extent: 300@400; openInWorld.! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 12:53'! sendNow self submit: true ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:39'! simpleString: aString ^self newRow layoutInset: 2; addMorphBack: (StringMorph contents: aString) lock! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 7/7/2000 17:38'! staticBackgroundColor ^Color veryLightGray! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 18:48'! subject ^subject ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 19:02'! subject: x subject _ x. self changed: #subject. ^true! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 12:53'! submit self submit: false! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 9/8/2000 18:18'! submit: sendNow | newMessageNumber personalCeleste windows | personalCeleste _ false. celeste ifNil: [ personalCeleste _ true. celeste _ Celeste open. ]. newMessageNumber _ celeste PROTOqueueMessageWithText: ( self breakLines: self completeTheMessage atWidth: 999 ). sendNow ifTrue: [celeste sendMail: {newMessageNumber}]. personalCeleste ifTrue: [ windows _ SystemWindow windowsIn: self currentWorld satisfying: [ :each | each model == celeste]. celeste close. windows do: [ :each | each delete]. ]. self forgetIt. ! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 18:47'! to ^to! ! !FancyCelesteComposition methodsFor: 'as yet unclassified' stamp: 'RAA 5/19/2000 19:02'! to: x to _ x. self changed: #to. ^true ! ! InflateStream subclass: #FastInflateStream instanceVariableNames: '' classVariableNames: 'DistanceMap FixedDistTable FixedLitTable LiteralLengthMap ' poolDictionaries: '' category: 'System-Compression'! !FastInflateStream commentStamp: '' prior: 0! This class adds the following optimizations to the basic Inflate decompression: a) Bit reversed access If we want to fetch the bits efficiently then we have them in the wrong bit order (e.g., when we should fetch 2r100 we would get 2r001). But since the huffman tree lookup determines the efficiency of the decompression, reversing the bits before traversal is expensive. Therefore the entries in each table are stored in REVERSE BIT ORDER. This is achieved by a reverse increment of the current table index in the huffman table construction phase (see method increment:bits:). According to my measures this speeds up the implementation by about 30-40%. b) Inplace storage of code meanings and extra bits Rather than looking up the meaning for each code during decompression of blocks we store the appropriate values directly in the huffman tables, using a pre-defined mapping. Even though this does not make a big difference in speed, it cleans up the code and allows easier translation into primitive code (which is clearly one goal of this implementation). c) Precomputed huffman tables for fixed blocks So we don't have to compute the huffman tables from scratch. The precomputed tables are not in our superclass to avoid double storage (and my superclass is more intended for documentation anyways).! !FastInflateStream methodsFor: 'inflating' stamp: 'ar 2/2/2001 15:47'! decompressBlock: llTable with: dTable "Process the compressed data in the block. llTable is the huffman table for literal/length codes and dTable is the huffman table for distance codes." | value extra length distance oldPos oldBits oldBitPos | [readLimit < collection size and:[sourcePos <= sourceLimit]] whileTrue:[ "Back up stuff if we're running out of space" oldBits _ bitBuf. oldBitPos _ bitPos. oldPos _ sourcePos. value _ self decodeValueFrom: llTable. value < 256 ifTrue:[ "A literal" collection byteAt: (readLimit _ readLimit + 1) put: value. ] ifFalse:["length/distance or end of block" value = 256 ifTrue:["End of block" state _ state bitAnd: StateNoMoreData. ^self]. "Compute the actual length value (including possible extra bits)" extra _ (value bitShift: -16) - 1. length _ value bitAnd: 16rFFFF. extra > 0 ifTrue:[length _ length + (self nextBits: extra)]. "Compute the distance value" value _ self decodeValueFrom: dTable. extra _ (value bitShift: -16). distance _ value bitAnd: 16rFFFF. extra > 0 ifTrue:[distance _ distance + (self nextBits: extra)]. (readLimit + length >= collection size) ifTrue:[ bitBuf _ oldBits. bitPos _ oldBitPos. sourcePos _ oldPos. ^self]. collection replaceFrom: readLimit+1 to: readLimit + length + 1 with: collection startingAt: readLimit - distance + 1. readLimit _ readLimit + length. ]. ].! ! !FastInflateStream methodsFor: 'inflating' stamp: 'ar 12/4/1998 19:15'! processFixedBlock litTable _ FixedLitTable. distTable _ FixedDistTable. state _ state bitOr: BlockProceedBit. self proceedFixedBlock.! ! !FastInflateStream methodsFor: 'huffman trees' stamp: 'ar 12/4/1998 02:26'! distanceMap ^DistanceMap! ! !FastInflateStream methodsFor: 'huffman trees' stamp: 'ar 12/4/1998 01:48'! increment: value bits: nBits "Increment value in reverse bit order, e.g. for a 3 bit value count as follows: 000 / 100 / 010 / 110 001 / 101 / 011 / 111 See the class comment why we need this." | result bit | result _ value. "Test the lowest bit first" bit _ 1 << (nBits - 1). "If the currently tested bit is set then we need to turn this bit off and test the next bit right to it" [(result bitAnd: bit) = 0] whileFalse:[ "Turn off current bit" result _ result bitXor: bit. "And continue testing the next bit" bit _ bit bitShift: -1]. "Turn on the right-most bit that we haven't touched in the loop above" ^result bitXor: bit! ! !FastInflateStream methodsFor: 'huffman trees' stamp: 'ar 12/4/1998 02:26'! literalLengthMap ^LiteralLengthMap! ! !FastInflateStream methodsFor: 'bit access' stamp: 'ar 12/4/1998 02:02'! nextSingleBits: n "Fetch the bits all at once" ^self nextBits: n.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FastInflateStream class instanceVariableNames: ''! !FastInflateStream class methodsFor: 'class initialization' stamp: 'ar 12/21/1999 23:00'! initialize "FastInflateStream initialize" | low high | "Init literal/length map" low _ #(3 4 5 6 7 8 9 10 11 13 15 17 19 23 27 31 35 43 51 59 67 83 99 115 131 163 195 227 258 ). high _ #(0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 0 0). LiteralLengthMap _ WordArray new: 256 + 32. 1 to: 257 do:[:i| LiteralLengthMap at: i put: i-1]. 1 to: 29 do:[:i| LiteralLengthMap at: 257+i put: (low at:i) + ( (high at: i) + 1 << 16)]. "Init distance map" high _ #(0 0 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13). low _ #(1 2 3 4 5 7 9 13 17 25 33 49 65 97 129 193 257 385 513 769 1025 1537 2049 3073 4097 6145 8193 12289 16385 24577). DistanceMap _ WordArray new: 32. 1 to: 30 do:[:i| DistanceMap at: i put: (low at: i) + ( (high at: i) << 16)]. "Init fixed block huffman tables" FixedLitTable _ self basicNew huffmanTableFrom: FixedLitCodes mappedBy: LiteralLengthMap. FixedDistTable _ self basicNew huffmanTableFrom: FixedDistCodes mappedBy: DistanceMap.! ! SketchMorph subclass: #FatBitsPaint instanceVariableNames: 'formToEdit magnification brush brushSize brushColor lastMouse currentTools currentSelectionMorph selectionAnchor backgroundColor ' classVariableNames: 'FormClipboard ' poolDictionaries: '' category: 'Morphic-Widgets'! !FatBitsPaint commentStamp: '' prior: 0! Extensions to FatBitsPaint With the goal of making FatBitsPaint a fairly nifty Form fixer-upper in the Squeak/morphic environment, I have started this set of extensions. It will probably be updated as the mood strikes, so keep an eye out for new versions. First, some basic operating instructions: Get a Form and send it the message #morphEdit. To get started, you can try: (Form fromUser) morphEdit And there is the form in all its glory. Control click on the form to get theFatBitsPaint menu and choose the "keep this menu up" item. This will be your main tool/command palette. With it you can: ´ Change the magnification ´ Change the brush size (in original scale pixels) ´ Change the brush color (via a ColorPickerMorph) Now to some of the enhancements: (25 September 1999 2:38:25 pm ) ´ ColorPickerMorphs now have a label below that indicates their use (you might have more than one open) ´ A quirk that could get the brush size out of alignment with the pixel size is fixed. ´ A background has been added so that you can see the full extent of the Form and so that you can observe the effect of translucent pixels in the form. ´ A menu item has been added to change the background color so that you can simulate the real environment the form will be displayed in. ´ The magnification and brush size menus now highlight their current value. ´ An inspect option has been added to the menu so that you can do arbitrary things to the form. ´ A file out option has been added to write the form to a file. (25 September 1999 10:02:13 pm ) ´ New menu item: Tools allows you to choose between (for now) Paint Brush (all there was before) and Selections. Selections allows you to select rectangular regions of the form where the next menu takes over. ´ New menu item: Selections gives you choices: ´ edit separately - opens a new editor on the selected rectangle. Useful for cropping. ´ copy - copies the selection rectangle to a clipboard. Can be pasted to this or another FatBitsPaint. ´ cut - does a copy and clears the selection to transparent. ´ paste - paints the contents of the clipboard over the current selection. Only the starting point of the selection matters - the extent is controlled by the clipboard. ! !FatBitsPaint methodsFor: 'drawing' stamp: 'ar 2/12/2000 18:40'! drawOn: aCanvas | f | f _ self rotatedForm. backgroundColor ifNotNil: [aCanvas fillRectangle: bounds fillStyle: backgroundColor]. aCanvas translucentImage: f at: bounds origin.! ! !FatBitsPaint methodsFor: 'events' stamp: 'jm 12/1/97 12:00'! griddedPoint: evt | relativePt | relativePt _ evt cursorPoint - self position. ^ (relativePt x truncateTo: magnification)@(relativePt y truncateTo: magnification) ! ! !FatBitsPaint methodsFor: 'events' stamp: 'jm 11/4/97 07:15'! handlesMouseDown: evt ^ true ! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 9/25/1999 15:24'! mouseDown: evt ^ self perform: (currentTools at: #mouseDown: ifAbsent: [^nil]) with: evt! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 12/28/1999 11:35'! mouseDownDefault: evt lastMouse _ nil. formToEdit depth = 1 ifTrue: [self brushColor: (originalForm colorAt: (self griddedPoint: evt)) negated]! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 9/25/1999 15:39'! mouseDownSelection: evt lastMouse _ nil. currentSelectionMorph ifNotNil: [currentSelectionMorph delete. currentSelectionMorph _ nil]. selectionAnchor _ self griddedPoint: evt! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 9/25/1999 15:24'! mouseMove: evt ^ self perform: (currentTools at: #mouseMove: ifAbsent: [^nil]) with: evt! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 9/25/1999 15:20'! mouseMovePaintBrushMode: evt | p p2 | p _ self griddedPoint: evt. lastMouse = p ifTrue: [^ self]. lastMouse ifNil: [lastMouse _ p]. "first point in a stroke" "draw etch-a-sketch style-first horizontal, then vertical" p2 _ p x@lastMouse y. brush drawFrom: lastMouse to: p2. brush drawFrom: p2 to: p. self revealPenStrokes. lastMouse _ p! ! !FatBitsPaint methodsFor: 'events' stamp: 'RAA 6/12/2000 08:58'! toolMenu: evt | menu | menu _ MenuMorph new. menu addTitle: 'Tools'; addStayUpItem. { {'paint brush'. self toolsForPaintBrush}. {'selections'. self toolsForSelection} } do: [:each | menu add: each first target: self selector: #setCurrentToolTo: argumentList: {each second}]. menu toggleStayUp: nil. menu popUpEvent: evt in: self world! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 15:54'! editForm: aForm formToEdit _ aForm. brushSize _ magnification _ 64 // (aForm height min: aForm width) max: 4. self revert! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 22:27'! initialize super initialize. self setCurrentToolTo: self toolsForPaintBrush. formToEdit _ Form extent: 50@40 depth: 8. formToEdit fill: formToEdit boundingBox fillColor: Color veryVeryLightGray. brushSize _ magnification _ 4. color _ Color veryVeryLightGray. brushColor _ Color red. backgroundColor _ Color white. self revert! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 22:26'! openWith: aForm self editForm: aForm; openInWorld! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 21:44'! setCurrentToolTo: aDictionary currentTools _ aDictionary. currentSelectionMorph ifNotNil: [currentSelectionMorph delete. currentSelectionMorph _ nil]! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 15:25'! toolsForPaintBrush ^Dictionary new at: #mouseMove: put: #mouseMovePaintBrushMode:; at: #mouseDown: put: #mouseDownDefault:; yourself! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'RAA 9/25/1999 15:27'! toolsForSelection ^ Dictionary new at: #mouseMove: put: #mouseMoveSelectionMode:; at: #mouseDown: put: #mouseDownSelection:; yourself! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 5/14/2000 12:42'! accept | f | f _ self unmagnifiedForm. f boundingBox = formToEdit boundingBox ifFalse: [^ self error: 'implementation error; form dimensions should match']. f displayOn: formToEdit. "modify formToEdit in place"! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 21:17'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'background color' action: #setBackgroundColor:; add: 'pen color' action: #setPenColor:; add: 'pen size' action: #setPenSize:; add: 'fill' action: #fill; add: 'magnification' action: #setMagnification:; add: 'accept' action: #accept; add: 'revert' action: #revert; add: 'inspect' action: #inspectForm; add: 'file out' action: #fileOut; add: 'selection...' action: #selectionMenu:; add: 'tools...' action: #toolMenu:; yourself! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 22:27'! backgroundColor: aColor backgroundColor _ aColor. self changed! ! !FatBitsPaint methodsFor: 'menu' stamp: 'jm 11/4/97 07:15'! brushColor: aColor brushColor _ aColor. brush color: aColor. ! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 21:50'! copySelection | relativeBounds scaledBounds | currentSelectionMorph ifNil: [^ nil]. relativeBounds _ currentSelectionMorph bounds translateBy: self position negated. scaledBounds _ relativeBounds scaleBy: 1 / magnification. FormClipboard _ (self unmagnifiedForm copy: scaledBounds). ^ relativeBounds! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 16:15'! cutSelection | relativeBounds | relativeBounds _ self copySelection ifNil: [^ nil]. originalForm fill: relativeBounds rule: Form over fillColor: Color transparent. self revealPenStrokes! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 16:19'! editSelection (self selectionAsForm ifNil: [^ nil]) morphEdit! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 15:48'! fileOut | fileName result | result _ StandardFileMenu newFile ifNil: [^ 1 beep]. fileName _ result directory fullNameFor: result name. Cursor normal showWhile: [self unmagnifiedForm writeOnFileNamed: fileName]! ! !FatBitsPaint methodsFor: 'menu' stamp: 'bf 1/5/2000 18:48'! fill | fillPt | Cursor blank show. Cursor crossHair showWhile: [fillPt _ Sensor waitButton - self position]. originalForm shapeFill: brushColor interiorPoint: fillPt. self changed. ! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 15:49'! inspectForm self unmagnifiedForm inspect! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 15:49'! magnification: aNumber | oldPenSize oldForm | oldPenSize _ brushSize / magnification. oldForm _ self unmagnifiedForm. magnification _ aNumber asInteger max: 1. self form: (oldForm magnify: oldForm boundingBox by: magnification). brush _ Pen newOnForm: originalForm. self penSize: oldPenSize. brush color: brushColor! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 21:39'! mouseMoveSelectionMode: evt | p | p _ self griddedPoint: evt. lastMouse = p ifTrue: [^ self]. currentSelectionMorph ifNil: [currentSelectionMorph _ MarqueeMorph new color: Color transparent; borderWidth: 2; lock. self addMorphFront: currentSelectionMorph. currentSelectionMorph startStepping]. currentSelectionMorph bounds: ((Rectangle encompassing: {p. selectionAnchor}) translateBy: self position). lastMouse _ p! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 21:50'! pasteSelection | relativeBounds tempForm | currentSelectionMorph ifNil: [^ nil]. FormClipboard ifNil: [^nil]. relativeBounds _ currentSelectionMorph bounds translateBy: self position negated. tempForm _ (FormClipboard magnify: FormClipboard boundingBox by: magnification). self form copy: (relativeBounds origin extent: tempForm boundingBox extent) from: 0@0 in: tempForm rule: Form over. self revealPenStrokes! ! !FatBitsPaint methodsFor: 'menu' stamp: 'jm 12/1/97 12:09'! penSize: aNumber brushSize _ (aNumber * magnification) asInteger. brush squareNib: brushSize. ! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/28/1999 13:03'! revert "since WarpBits may mangle an 8-bit ColorForm, make it 32 first" self form: ((formToEdit asFormOfDepth: 32) magnify: formToEdit boundingBox by: magnification smoothing: 1). brush _ Pen newOnForm: originalForm. brush squareNib: brushSize. brush color: brushColor! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 16:18'! selectionAsForm | relativeBounds scaledBounds | currentSelectionMorph ifNil: [^nil]. relativeBounds _ currentSelectionMorph bounds translateBy: self position negated. scaledBounds _ relativeBounds scaleBy: 1 / magnification. ^ self unmagnifiedForm copy: scaledBounds! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! selectionMenu: evt | menu | (menu _ MenuMorph new) addTitle: 'Edit'; addStayUpItem. { {'edit separately'. #editSelection}. {'copy'. #copySelection}. {'cut'. #cutSelection}. {'paste'. #pasteSelection} } do: [:each | menu add: each first target: self selector: each second argumentList: #()]. menu toggleStayUp: nil. menu popUpEvent: evt in: self world! ! !FatBitsPaint methodsFor: 'menu' stamp: 'ar 10/5/2000 18:51'! setBackgroundColor: evt self changeColorTarget: self selector: #backgroundColor: originalColor: backgroundColor hand: evt hand! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! setMagnification: evt | menu | menu _ MenuMorph new. ((1 to: 8), #(16 24 32)) do: [:w | menu add: w printString target: self selector: #magnification: argumentList: (Array with: w). magnification = w ifTrue: [menu lastSubmorph color: Color red]]. menu popUpEvent: evt in: self world! ! !FatBitsPaint methodsFor: 'menu' stamp: 'ar 10/5/2000 18:51'! setPenColor: evt self changeColorTarget: self selector: #brushColor: originalColor: brushColor hand: evt hand.! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 6/12/2000 08:58'! setPenSize: evt | menu sizes | menu _ MenuMorph new. sizes _ (1 to: 5), (6 to: 12 by: 2), (15 to: 40 by: 5). sizes do: [:w | menu add: w printString target: self selector: #penSize: argumentList: (Array with: w). (brushSize // magnification) = w ifTrue: [menu lastSubmorph color: Color red]]. menu popUpEvent: evt in: self world! ! !FatBitsPaint methodsFor: 'menu' stamp: 'RAA 9/25/1999 15:48'! unmagnifiedForm ^ self form shrink: self form boundingBox by: magnification! ! !FatBitsPaint methodsFor: 'testing' stamp: 'RAA 9/25/1999 21:14'! containsPoint: aPoint ^ self bounds containsPoint: aPoint "even if we are transparent" ! ! Browser subclass: #FileContentsBrowser instanceVariableNames: 'packages infoString ' classVariableNames: '' poolDictionaries: '' category: 'Tools-File Contents Browser'! !FileContentsBrowser methodsFor: 'accessing'! contents self updateInfoView. (editSelection == #newClass and:[self selectedPackage notNil]) ifTrue: [^self selectedPackage packageInfo]. editSelection == #editClass ifTrue:[^self modifiedClassDefinition]. ^super contents! ! !FileContentsBrowser methodsFor: 'accessing'! contents: input notifying: aController "The retrieved information has changed and its source must now be updated. The information can be a variety of things, depending on the list selections (such as templates for class or message definition, methods) or the user menu commands (such as definition, comment, hierarchy). Answer the result of updating the source." | aString aText theClass | aString _ input asString. aText _ input asText. editSelection == #editComment ifTrue: [theClass _ self selectedClass. theClass ifNil: [PopUpMenu notify: 'You must select a class before giving it a comment.'. ^ false]. theClass comment: aText. ^ true]. editSelection == #editMessageCategories ifTrue: [^ self changeMessageCategories: aString]. self inform:'You cannot change the current selection'. ^false ! ! !FileContentsBrowser methodsFor: 'accessing'! packages ^packages! ! !FileContentsBrowser methodsFor: 'accessing'! packages: aDictionary packages := aDictionary.! ! !FileContentsBrowser methodsFor: 'accessing'! selectedPackage | cat | cat := self selectedSystemCategoryName. cat isNil ifTrue:[^nil]. ^self packages at: cat asString ifAbsent:[nil]! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 5/24/1998 20:37'! removeClass | class | classListIndex = 0 ifTrue: [^ self]. class _ self selectedClass. (self confirm:'Are you certain that you want to delete the class ', class name, '?') ifFalse:[^self]. self selectedPackage removeClass: class. self classListIndex: 0. self changed: #classList.! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 5/24/1998 20:46'! removeMessage | messageName | messageListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. messageName _ self selectedMessageName. (self selectedClassOrMetaClass confirmRemovalOf: messageName) ifFalse:[^false]. self selectedClassOrMetaClass removeMethod: self selectedMessageName. self messageListIndex: 0. self setClassOrganizer. "In case organization not cached" self changed: #messageList.! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 5/24/1998 20:51'! removeMessageCategory "If a message category is selected, create a Confirmer so the user can verify that the currently selected message category should be removed from the system. If so, remove it." | messageCategoryName | messageCategoryListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. messageCategoryName _ self selectedMessageCategoryName. (self messageList size = 0 or: [self confirm: 'Are you sure you want to remove this method category and all its methods?']) ifFalse: [^ self]. self selectedClassOrMetaClass removeCategory: messageCategoryName. self messageCategoryListIndex: 0. self changed: #messageCategoryList.! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 5/24/1998 20:52'! removePackage systemCategoryListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. (self confirm: 'Are you sure you want to remove this package and all its classes?') ifFalse:[^self]. (systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) do:[:el| systemOrganizer removeElement: el]. self packages removeKey: self selectedPackage packageName. systemOrganizer removeCategory: self selectedSystemCategoryName. self systemCategoryListIndex: 0. self changed: #systemCategoryList! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 2/3/1999 18:47'! removeUnmodifiedCategories | theClass | self okToChange ifFalse: [^self]. theClass _ self selectedClass. theClass isNil ifTrue: [^self]. Cursor wait showWhile: [theClass removeUnmodifiedMethods: theClass selectors. theClass metaClass removeUnmodifiedMethods: theClass metaClass selectors]. self messageCategoryListIndex: 0. self changed: #messageCategoryList.! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 5/24/1998 20:37'! removeUnmodifiedClasses | packageList | self okToChange ifFalse:[^self]. packageList := self selectedPackage isNil ifTrue:[self packages] ifFalse:[Array with: self selectedPackage]. packageList do:[:package| package classes copy do:[:theClass| Cursor wait showWhile:[ theClass removeAllUnmodified. ]. theClass hasChanges ifFalse:[ package removeClass: theClass. ]. ]]. self classListIndex: 0. self changed: #classList.! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'wod 2/3/1999 18:47'! removeUnmodifiedMethods | theClass cat | self okToChange ifFalse:[^self]. theClass := self selectedClassOrMetaClass. theClass isNil ifTrue:[^self]. cat := self selectedMessageCategoryName. cat isNil ifTrue:[^self]. Cursor wait showWhile:[ theClass removeUnmodifiedMethods: (theClass organization listAtCategoryNamed: cat). ]. self messageListIndex: 0. self changed: #messageList.! ! !FileContentsBrowser methodsFor: 'class list' stamp: 'sma 5/6/2000 18:48'! browseMethodFull | myClass | (myClass _ self selectedClassOrMetaClass) ifNotNil: [Browser fullOnClass: myClass realClass selector: self selectedMessageName]! ! !FileContentsBrowser methodsFor: 'class list'! classList "Answer an array of the class names of the selected category. Answer an empty array if no selection exists." (systemCategoryListIndex = 0 or:[self selectedPackage isNil]) ifTrue: [^Array new] ifFalse: [^self selectedPackage classes keys asSortedCollection].! ! !FileContentsBrowser methodsFor: 'class list'! findClass | pattern foundClass classNames index foundPackage | self okToChange ifFalse: [^ self classNotFound]. pattern _ (FillInTheBlank request: 'Class Name?') asLowercase. pattern isEmpty ifTrue: [^ self]. classNames := Set new. self packages do:[:p| classNames addAll: p classes keys]. classNames := classNames asArray select: [:n | (n asLowercase indexOfSubCollection: pattern startingAt: 1) > 0]. classNames isEmpty ifTrue: [^ self]. index _ classNames size == 1 ifTrue: [1] ifFalse: [(PopUpMenu labelArray: classNames lines: #()) startUp]. index = 0 ifTrue: [^ self]. foundPackage := nil. foundClass := nil. self packages do:[:p| (p classes includesKey: (classNames at: index)) ifTrue:[ foundClass := p classes at: (classNames at: index). foundPackage := p]]. foundClass isNil ifTrue:[^self]. self systemCategoryListIndex: (self systemCategoryList indexOf: foundPackage packageName asSymbol). self classListIndex: (self classList indexOf: foundClass name). ! ! !FileContentsBrowser methodsFor: 'class list' stamp: 'wod 5/24/1998 20:37'! renameClass | oldName newName | classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. oldName _ self selectedClass name. newName _ (self request: 'Please type new class name' initialAnswer: oldName) asSymbol. (newName isEmpty or:[newName = oldName]) ifTrue: [^ self]. (self selectedPackage classes includesKey: newName) ifTrue: [^ self error: newName , ' already exists in the package']. systemOrganizer classify: newName under: self selectedSystemCategoryName. systemOrganizer removeElement: oldName. self selectedPackage renameClass: self selectedClass to: newName. self changed: #classList. self classListIndex: ((systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) indexOf: newName). ! ! !FileContentsBrowser methodsFor: 'class list'! selectedClass "Answer the class that is currently selected. Answer nil if no selection exists." self selectedClassName == nil ifTrue: [^nil]. ^self selectedPackage classAt: self selectedClassName! ! !FileContentsBrowser methodsFor: 'edit pane' stamp: 'sw 11/9/1999 19:26'! selectedMessage "Answer a copy of the source code for the selected message selector." | class selector | class _ self selectedClassOrMetaClass. selector _ self selectedMessageName. contents _ class sourceCodeAt: selector. Preferences browseWithPrettyPrint ifTrue: [contents _ Compiler new format: contents in: class notifying: nil decorated: Preferences colorWhenPrettyPrinting]. self showDiffs ifTrue: [contents _ self methodDiffFor: contents class: self selectedClass selector: self selectedMessageName meta: self metaClassIndicated]. ^ contents asText makeSelectorBoldIn: class! ! !FileContentsBrowser methodsFor: 'diffs' stamp: 'nk 10/29/2000 12:43'! methodDiffFor: aString class: aPseudoClass selector: selector meta: meta | theClass source | theClass _ Smalltalk at: aPseudoClass name ifAbsent: [^ aString copy]. meta ifTrue: [theClass _ theClass class]. (theClass includesSelector: selector) ifFalse: [^ aString copy]. source _ theClass sourceCodeAt: selector. ^ Cursor wait showWhile: [TextDiffBuilder buildDisplayPatchFrom: source to: aString inClass: theClass]! ! !FileContentsBrowser methodsFor: 'diffs'! modifiedClassDefinition | pClass rClass old new diff | pClass := self selectedClassOrMetaClass. pClass hasDefinition ifFalse:[^pClass definition]. rClass := Smalltalk at: self selectedClass name asSymbol ifAbsent:[nil]. rClass isNil ifTrue:[^pClass definition]. self metaClassIndicated ifTrue:[ rClass := rClass class]. old := rClass definition. new := pClass definition. Cursor wait showWhile:[ diff := ClassDiffBuilder buildDisplayPatchFrom: old to: new ]. ^diff! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut'! fileInClass Cursor read showWhile:[ self selectedClass fileIn. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 6/16/1998 17:14'! fileInMessage self selectedMessageName ifNil: [^self]. Cursor read showWhile: [ self selectedClassOrMetaClass fileInMethod: self selectedMessageName. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 2/3/1999 18:46'! fileInMessageCategories Cursor read showWhile:[ self selectedClassOrMetaClass fileInCategory: self selectedMessageCategoryName. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 5/13/1998 12:50'! fileInPackage Cursor read showWhile:[ self selectedPackage fileIn. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'sma 4/22/2000 20:51'! fileIntoNewChangeSet | p ff | (p _ self selectedPackage) ifNil: [^ self beep]. ff _ StandardFileStream readOnlyFileNamed: p fullPackageName. ChangeSorter newChangesFromStream: ff named: p packageName! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut'! fileOutClass Cursor write showWhile:[ self selectedClass fileOut. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 6/16/1998 17:14'! fileOutMessage self selectedMessageName ifNil: [^self]. Cursor write showWhile: [ self selectedClassOrMetaClass fileOutMethod: self selectedMessageName].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 2/3/1999 18:46'! fileOutMessageCategories Cursor write showWhile:[ self selectedClassOrMetaClass fileOutCategory: self selectedMessageCategoryName. ].! ! !FileContentsBrowser methodsFor: 'fileIn/fileOut' stamp: 'wod 5/13/1998 14:19'! fileOutPackage Cursor write showWhile:[ self selectedPackage fileOut. ].! ! !FileContentsBrowser methodsFor: 'infoView' stamp: 'sma 5/6/2000 19:19'! extraInfo ^ (self methodDiffFor: (self selectedClassOrMetaClass sourceCodeAt: self selectedMessageName) class: self selectedClass selector: self selectedMessageName meta: self metaClassIndicated) unembellished ifTrue: [' - identical'] ifFalse: [' - modified']! ! !FileContentsBrowser methodsFor: 'infoView'! infoString ^infoString isNil ifTrue:[infoString := StringHolder new] ifFalse:[infoString]! ! !FileContentsBrowser methodsFor: 'infoView' stamp: 'sma 5/6/2000 18:26'! infoViewContents | theClass | editSelection == #newClass ifTrue: [^ self packageInfo: self selectedPackage]. self selectedClass isNil ifTrue: [^ '']. theClass _ Smalltalk at: self selectedClass name asSymbol ifAbsent: []. editSelection == #editClass ifTrue: [^ theClass notNil ifTrue: ['Class exists already in the system'] ifFalse: ['New class']]. editSelection == #editMessage ifFalse: [^ '']. (theClass notNil and: [self metaClassIndicated]) ifTrue: [theClass _ theClass class]. ^ (theClass notNil and: [theClass includesSelector: self selectedMessageName]) ifTrue: ['Method already exists' , self extraInfo] ifFalse: ['New method']! ! !FileContentsBrowser methodsFor: 'infoView'! packageInfo: p | nClasses newClasses oldClasses | p isNil ifTrue:[^'']. nClasses := newClasses := oldClasses := 0. p classes do:[:cls| nClasses := nClasses + 1. (Smalltalk includesKey: (cls name asSymbol)) ifTrue:[oldClasses := oldClasses + 1] ifFalse:[newClasses := newClasses + 1]]. ^nClasses printString,' classes (', newClasses printString, ' new / ', oldClasses printString, ' modified)'! ! !FileContentsBrowser methodsFor: 'infoView' stamp: 'wod 5/19/1998 17:34'! updateInfoView Smalltalk isMorphic ifTrue: [self changed: #infoViewContents] ifFalse: [ self infoString contents: self infoViewContents. self infoString changed].! ! !FileContentsBrowser methodsFor: 'metaclass'! selectedClassOrMetaClass "Answer the selected class or metaclass." self metaClassIndicated ifTrue: [^ self selectedClass metaClass] ifFalse: [^ self selectedClass]! ! !FileContentsBrowser methodsFor: 'metaclass'! setClassOrganizer "Install whatever organization is appropriate" | theClass | classOrganizer _ nil. metaClassOrganizer _ nil. classListIndex = 0 ifTrue: [^ self]. classOrganizer _ (theClass _ self selectedClass) organization. metaClassOrganizer _ theClass metaClass organization. ! ! !FileContentsBrowser methodsFor: 'other' stamp: 'wod 5/25/1998 00:46'! browseSenders "Create and schedule a message set browser on all senders of the currently selected message selector. Do nothing if no message is selected." messageListIndex ~= 0 ifTrue: [Smalltalk browseAllCallsOn: self selectedMessageName]! ! !FileContentsBrowser methodsFor: 'other' stamp: 'sw 10/12/1999 17:42'! browseVersions "Create and schedule a message set browser on all versions of the currently selected message selector." | class selector | (selector _ self selectedMessageName) ifNotNil: [class _ self selectedClassOrMetaClass. (class exists and: [class realClass includesSelector: selector]) ifTrue: [VersionsBrowser browseVersionsOf: (class realClass compiledMethodAt: selector) class: class realClass meta: class realClass isMeta category: self selectedMessageCategoryName selector: selector]]! ! !FileContentsBrowser methodsFor: 'other'! changeMessageCategories: aString "The characters in aString represent an edited version of the the message categories for the selected class. Update this information in the system and inform any dependents that the categories have been changed. This message is invoked because the user had issued the categories command and edited the message categories. Then the user issued the accept command." self classOrMetaClassOrganizer changeFromString: aString. self unlock. self editClass. self classListIndex: classListIndex. ^ true! ! !FileContentsBrowser methodsFor: 'other' stamp: 'sma 2/6/2000 12:27'! methodHierarchy (self selectedClassOrMetaClass isNil or: [self selectedClassOrMetaClass hasDefinition]) ifFalse: [super methodHierarchy]! ! !FileContentsBrowser methodsFor: 'creation' stamp: 'JW 2/3/2001 10:24'! addLowerPanesTo: window at: nominalFractions with: editString | verticalOffset row innerFractions codePane infoPane infoHeight | row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 1; borderColor: Color black; layoutPolicy: ProportionalLayout new. codePane _ PluggableTextMorph on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. infoPane _ PluggableTextMorph on: self text: #infoViewContents accept: nil readSelection: nil menu: nil. verticalOffset _ 0. innerFractions _ 0@0 corner: 1@0. ">>not with this browser--- at least not yet --- verticalOffset _ self addOptionalAnnotationsTo: row at: innerFractions plus: verticalOffset. verticalOffset _ self addOptionalButtonsTo: row at: innerFractions plus: verticalOffset. <<<<" infoHeight _ 20. row addMorph: (codePane borderWidth: 0) fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@1) offsets: (0@verticalOffset corner: 0@infoHeight negated) ). row addMorph: (SubpaneDividerMorph forTopEdge) fullFrame: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@infoHeight negated corner: 0@(1-infoHeight)) ). row addMorph: (infoPane borderWidth: 0; hideScrollBarIndefinitely) fullFrame: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@(1-infoHeight) corner: 0@0) ). window addMorph: row frame: nominalFractions. row on: #mouseEnter send: #paneTransition: to: window. row on: #mouseLeave send: #paneTransition: to: window. ! ! !FileContentsBrowser methodsFor: 'creation' stamp: 'sma 2/6/2000 12:03'! createViews "Create a pluggable version of all the views for a Browser, including views and controllers." | hasSingleFile width topView packageListView classListView switchView messageCategoryListView messageListView browserCodeView infoView | showDiffs _ true. Smalltalk isMorphic ifTrue: [^ self openAsMorph]. (hasSingleFile _ self packages size = 1) ifTrue: [width _ 150] ifFalse: [width _ 200]. (topView _ StandardSystemView new) model: self; borderWidth: 1. "label and minSize taken care of by caller" hasSingleFile ifTrue: [ self systemCategoryListIndex: 1. packageListView _ PluggableListView on: self list: #systemCategorySingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #packageListMenu: keystroke: #packageListKey:from:. packageListView window: (0 @ 0 extent: width @ 12)] ifFalse: [ packageListView _ PluggableListView on: self list: #systemCategoryList selected: #systemCategoryListIndex changeSelected: #systemCategoryListIndex: menu: #packageListMenu: keystroke: #packageListKey:from:. packageListView window: (0 @ 0 extent: 50 @ 70)]. topView addSubView: packageListView. classListView _ PluggableListView on: self list: #classList selected: #classListIndex changeSelected: #classListIndex: menu: #classListMenu: keystroke: #classListKey:from:. classListView window: (0 @ 0 extent: 50 @ 62). hasSingleFile ifTrue: [topView addSubView: classListView below: packageListView] ifFalse: [topView addSubView: classListView toRightOf: packageListView]. switchView _ self buildInstanceClassSwitchView. switchView borderWidth: 1. topView addSubView: switchView below: classListView. messageCategoryListView _ PluggableListView on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu:. messageCategoryListView window: (0 @ 0 extent: 50 @ 70). topView addSubView: messageCategoryListView toRightOf: classListView. messageListView _ PluggableListView on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu: keystroke: #messageListKey:from:. messageListView window: (0 @ 0 extent: 50 @ 70). topView addSubView: messageListView toRightOf: messageCategoryListView. browserCodeView _ PluggableTextView on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. browserCodeView window: (0@0 extent: width@110). topView addSubView: browserCodeView below: (hasSingleFile ifTrue: [switchView] ifFalse: [packageListView]). infoView _ StringHolderView new model: self infoString; window: (0@0 extent: width@12); borderWidth: 1. topView addSubView: infoView below: browserCodeView. ^ topView ! ! !FileContentsBrowser methodsFor: 'creation' stamp: 'RAA 1/11/2001 08:06'! openAsMorph "Create a pluggable version of all the views for a Browser, including views and controllers." | window aListExtent next | window _ (SystemWindow labelled: 'later') model: self. self packages size = 1 ifTrue: [ aListExtent _ 0.333333 @ 0.34. self systemCategoryListIndex: 1. window addMorph: (PluggableListMorph on: self list: #systemCategorySingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #packageListMenu: keystroke: #packageListKey:from:) frame: (0@0 extent: 1.0@0.06). next := 0@0.06] ifFalse: [ aListExtent _ 0.25 @ 0.4. window addMorph: (PluggableListMorph on: self list: #systemCategoryList selected: #systemCategoryListIndex changeSelected: #systemCategoryListIndex: menu: #packageListMenu: keystroke: #packageListKey:from:) frame: (0@0 extent: aListExtent). next := aListExtent x @ 0]. self addClassAndSwitchesTo: window at: (next extent: aListExtent) plus: 0. next := next + (aListExtent x @ 0). window addMorph: (PluggableListMorph on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu:) frame: (next extent: aListExtent). next := next + (aListExtent x @ 0). window addMorph: (PluggableListMorph on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu: keystroke: #messageListKey:from:) frame: (next extent: aListExtent). self addLowerPanesTo: window at: (0@0.4 corner: 1@1) with: nil. ^ window ! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'sw 11/9/1999 18:26'! addModelItemsToWindowMenu: aMenu aMenu addLine. aMenu add: (self showDiffs ifTrue: ['stop showing diffs'] ifFalse: ['start showing diffs']) target: self action: #toggleDiffing ! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'sma 5/6/2000 18:36'! classListMenu: aMenu ^ aMenu labels: 'definition comment browse full (b) class refs (N) fileIn fileOut rename... remove remove existing' lines: #(2 4 6 8) selections: #(editClass editComment browseMethodFull browseClassRefs fileInClass fileOutClass renameClass removeClass removeUnmodifiedCategories) ! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'wod 5/13/1998 17:39'! messageCategoryMenu: aMenu ^ aMenu labels: 'fileIn fileOut reorganize add item... rename... remove remove existing' lines: #(2 3 6) selections: #(fileInMessageCategories fileOutMessageCategories editMessageCategories addCategory renameCategory removeMessageCategory removeUnmodifiedMethods)! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'sma 2/6/2000 12:28'! messageListMenu: aMenu ^ aMenu labels: 'fileIn fileOut senders (n) implementors (m) method inheritance (h) versions (v) remove' lines: #(2 6) selections: #(fileInMessage fileOutMessage browseSenders browseImplementors methodHierarchy browseVersions removeMessage).! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'sma 4/22/2000 20:52'! packageListMenu: aMenu ^ aMenu labels: 'find class... (f) fileIn file into new changeset fileOut remove remove existing' lines: #(1 4 5) selections: #(findClass fileInPackage fileIntoNewChangeSet fileOutPackage removePackage removeUnmodifiedClasses)! ! !FileContentsBrowser methodsFor: 'keys' stamp: 'sma 5/6/2000 18:48'! classListKey: aChar from: view aChar == $b ifTrue: [^ self browseMethodFull]. aChar == $N ifTrue: [^ self browseClassRefs]. self packageListKey: aChar from: view! ! !FileContentsBrowser methodsFor: 'keys' stamp: 'sma 5/6/2000 18:50'! messageListKey: aChar from: view aChar == $b ifTrue: [^ self browseMethodFull]. super messageListKey: aChar from: view! ! !FileContentsBrowser methodsFor: 'keys' stamp: 'sma 2/6/2000 12:05'! packageListKey: aChar from: view aChar == $f ifTrue: [^ self findClass]. self arrowKey: aChar from: view! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileContentsBrowser class instanceVariableNames: ''! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'wod 5/13/1998 18:17'! browseFile: aFilename self browseFiles: (Array with: aFilename).! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'dew 8/2/2000 20:02'! browseFiles: fileList | package organizer packageDict browser | Cursor wait showWhile: [ packageDict _ Dictionary new. organizer _ SystemOrganizer defaultList: Array new. fileList do: [:fileName | package _ FilePackage fromFileNamed: fileName. packageDict at: package packageName put: package. organizer classifyAll: package classes keys under: package packageName]. (browser := self new) systemOrganizer: organizer; packages: packageDict]. self openBrowserView: browser createViews label: 'File Contents Browser'. ! ! Object subclass: #FileDirectory instanceVariableNames: 'pathName ' classVariableNames: 'DefaultDirectory DirectoryClass StandardMIMEMappings ' poolDictionaries: '' category: 'System-Files'! !FileDirectory commentStamp: '' prior: 0! A FileDirectory represents a folder or directory in the underlying platform's file system. It carries a fully-qualified path name for the directory it represents, and can enumerate the files and directories within that directory. A FileDirectory can be thought of as a Dictionary whose keys are the local names of files in that directory, and whose values are directory "entries". Each entry is an array of five items: The times are given in seconds, and can be converted to a time and date via Time>dateAndTimeFromSeconds:. See the comment in lookupEntry:... which provides primitive access to this information. ! !FileDirectory methodsFor: 'path access' stamp: 'ar 12/18/1999 01:01'! fullPathFor: path ^path isEmpty ifTrue:[pathName] ifFalse:[path]! ! !FileDirectory methodsFor: 'path access' stamp: 'tk 5/18/1998 22:29'! on: fullPath "Return another instance" ^ self class on: fullPath! ! !FileDirectory methodsFor: 'path access' stamp: 'jm 12/5/97 12:18'! pathName "Return the path from the root of the file system to this directory." ^ pathName ! ! !FileDirectory methodsFor: 'path access' stamp: 'jm 12/5/97 12:19'! pathNameDelimiter "Return the delimiter character for this kind of directory. This depends on the current platform." ^ self class pathNameDelimiter ! ! !FileDirectory methodsFor: 'path access' stamp: 'jm 12/5/97 12:17'! pathParts "Return the path from the root of the file system to this directory as an array of directory names." ^ pathName findTokens: self pathNameDelimiter asString ! ! !FileDirectory methodsFor: 'path access' stamp: 'ar 12/18/1999 00:36'! slash ^self class slash! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'tk 5/19/1998 09:03'! fileNamed: localFileName "Open the file with the given name in this directory for writing." ^ FileStream concreteStream fileNamed: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'dew 10/26/2000 02:08'! forceNewFileNamed: localFileName "Open the file with the given name in this directory for writing. If it already exists, delete it first without asking." ^ FileStream concreteStream forceNewFileNamed: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'tk 5/19/1998 09:03'! newFileNamed: localFileName "Create a new file with the given name in this directory." ^ FileStream concreteStream newFileNamed: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'tk 5/19/1998 09:03'! oldFileNamed: localFileName "Open the existing file with the given name in this directory." ^ FileStream concreteStream oldFileNamed: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'tk 5/18/1998 16:19'! oldFileOrNoneNamed: fileName "If the file exists, answer a read-only FileStream on it. If it doesn't, answer nil." | fullName | fullName _ FileStream fullName: fileName. (FileStream concreteStream isAFileNamed: fullName) ifTrue: [^ FileStream concreteStream readOnlyFileNamed: fullName] ifFalse: [^ nil]. ! ! !FileDirectory methodsFor: 'file stream creation' stamp: 'tk 5/19/1998 09:03'! readOnlyFileNamed: localFileName "Open the existing file with the given name in this directory for read-only access." ^ FileStream concreteStream readOnlyFileNamed: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 1/5/98 20:49'! containingDirectory "Return the directory containing this directory." ^ FileDirectory on: (FileDirectory dirPathFor: pathName) ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 15:46'! directoryNamed: localFileName "Return the subdirectory of this directory with the given name." ^ FileDirectory on: (self fullNameFor: localFileName) ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 15:44'! directoryNames "Return a collection of names for the subdirectories of this directory." "FileDirectory default directoryNames" ^ (self entries select: [:entry | entry at: 4]) collect: [:entry | entry first] ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 12:23'! entries "Return a collection of directory entries for the files and directories in this directory. Each entry is a five-element array: (). See primLookupEntryIn:index: for further details." "FileDirectory default entries" ^ self directoryContentsFor: pathName ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 15:39'! fileAndDirectoryNames "FileDirectory default fileAndDirectoryNames" ^ self entries collect: [:entry | entry first] ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 15:44'! fileNames "Return a collection of names for the files (but not directories) in this directory." "FileDirectory default fileNames" ^ (self entries select: [:entry | (entry at: 4) not]) collect: [:entry | entry first] ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 9/27/1998 21:34'! fullNamesOfAllFilesInSubtree "Answer a collection containing the full names of all the files in the subtree of the file system whose root is this directory." | result todo dir | result _ OrderedCollection new: 100. todo _ OrderedCollection with: self. [todo size > 0] whileTrue: [ dir _ todo removeFirst. dir fileNames do: [:n | result add: (dir fullNameFor: n)]. dir directoryNames do: [:n | todo add: (dir directoryNamed: n)]]. ^ result asArray ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'jm 12/5/97 15:39'! keysDo: nameBlock "Evaluate the given block for each file or directory name in this directory." ^ self fileAndDirectoryNames do: nameBlock ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'wod 6/16/1998 15:07'! statsForDirectoryTree: rootedPathName "Return the size statistics for the entire directory tree starting at the given root. The result is a three element array of the form: (). This method also serves as an example of how recursively enumerate a directory tree." "wod 6/16/1998: add Cursor wait, and use 'self pathNameDelimiter asString' rather than hardwired ':' " "FileDirectory default statsForDirectoryTree: '\smalltalk'" | dirs files bytes todo p entries | Cursor wait showWhile: [ dirs _ files _ bytes _ 0. todo _ OrderedCollection with: rootedPathName. [todo isEmpty] whileFalse: [ p _ todo removeFirst. entries _ self directoryContentsFor: p. entries do: [:entry | (entry at: 4) ifTrue: [ todo addLast: (p, self pathNameDelimiter asString, (entry at: 1)). dirs _ dirs + 1] ifFalse: [ files _ files + 1. bytes _ bytes + (entry at: 5)]]]]. ^ Array with: dirs with: files with: bytes ! ! !FileDirectory methodsFor: 'testing' stamp: 'di 11/21/1999 20:16'! directoryExists: filenameOrPath "Answer true if a directory of the given name exists. The given name may be either a full path name or a local directory within this directory." "FileDirectory default directoryExists: FileDirectory default pathName" | fName dir | FileDirectory splitName: filenameOrPath to: [:filePath :name | fName _ name. filePath isEmpty ifTrue: [dir _ self] ifFalse: [dir _ FileDirectory on: filePath]]. self isCaseSensitive ifTrue:[^dir directoryNames includes: fName] ifFalse:[^dir directoryNames anySatisfy: [:name| name sameAs: fName]]. ! ! !FileDirectory methodsFor: 'testing' stamp: 'di 11/21/1999 20:17'! fileExists: filenameOrPath "Answer true if a file of the given name exists. The given name may be either a full path name or a local file within this directory." "FileDirectory default fileExists: Smalltalk sourcesName" | fName dir | FileDirectory splitName: filenameOrPath to: [:filePath :name | fName _ name. filePath isEmpty ifTrue: [dir _ self] ifFalse: [dir _ FileDirectory on: filePath]]. self isCaseSensitive ifTrue:[^dir fileNames includes: fName] ifFalse:[^dir fileNames anySatisfy: [:name| name sameAs: fName]]. ! ! !FileDirectory methodsFor: 'testing' stamp: 'jm 4/9/1999 17:48'! fileOrDirectoryExists: filenameOrPath "Answer true if either a file or a directory file of the given name exists. The given name may be either a full path name or a local name within this directory." "FileDirectory default fileOrDirectoryExists: Smalltalk sourcesName" | fName dir | FileDirectory splitName: filenameOrPath to: [:filePath :name | fName _ name. filePath isEmpty ifTrue: [dir _ self] ifFalse: [dir _ FileDirectory on: filePath]]. ^ dir includesKey: fName ! ! !FileDirectory methodsFor: 'testing' stamp: 'di 11/21/1999 20:17'! includesKey: localName "Answer true if this directory includes a file or directory of the given name. Note that the name should be a local file name, in contrast with fileExists:, which takes either local or full-qualified file names." "(FileDirectory on: Smalltalk vmPath) includesKey: 'SqueakV2.sources'" self isCaseSensitive ifTrue:[^ self fileAndDirectoryNames includes: localName] ifFalse:[^ self fileAndDirectoryNames anySatisfy: [:str| str sameAs: localName]].! ! !FileDirectory methodsFor: 'testing' stamp: 'ar 5/1/1999 01:51'! isCaseSensitive "Return true if file names are treated case sensitive" ^self class isCaseSensitive! ! !FileDirectory methodsFor: 'file operations' stamp: 'MPH 10/15/2000 12:43'! copyFile: fileStream1 toFile: fileStream2 | buffer | buffer _ String new: 50000. [fileStream1 atEnd] whileFalse: [fileStream2 nextPutAll: (fileStream1 nextInto: buffer)]. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'MPH 10/23/2000 13:31'! copyFileNamed: fileName1 toFileNamed: fileName2 "Copy the contents of the existing file with the first name into a new file with the second name. Both files are assumed to be in this directory." "FileDirectory default copyFileNamed: 'todo.txt' toFileNamed: 'todocopy.txt'" | file1 file2 | file1 _ (self readOnlyFileNamed: fileName1) binary. file2 _ (self newFileNamed: fileName2) binary. self copyFile: file1 toFile: file2. file1 close. file2 close. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'dew 10/26/2000 02:23'! copyFileWithoutOverwriteConfirmationNamed: fileName1 toFileNamed: fileName2 "Copy the contents of the existing file with the first name into a file with the second name (which may or may not exist). If the second file exists, force an overwrite without confirming. Both files are assumed to be in this directory." "FileDirectory default copyFileWithoutOverwriteConfirmationNamed: 'todo.txt' toFileNamed: 'todocopy.txt'" | file1 file2 | fileName1 = fileName2 ifTrue: [^ self]. file1 _ (self readOnlyFileNamed: fileName1) binary. file2 _ (self forceNewFileNamed: fileName2) binary. self copyFile: file1 toFile: file2. file1 close. file2 close.! ! !FileDirectory methodsFor: 'file operations' stamp: 'jm 12/4/97 22:55'! createDirectory: localFileName "Create a directory with the given name in this directory. Fail if the name is bad or if a file or directory with that name already exists." self primCreateDirectory: (self fullNameFor: localFileName). ! ! !FileDirectory methodsFor: 'file operations' stamp: 'jm 4/9/1999 18:02'! deleteDirectory: localDirName "Delete the directory with the given name in this directory. Fail if the path is bad or if a directory by that name does not exist." self primDeleteDirectory: (self fullNameFor: localDirName). ! ! !FileDirectory methodsFor: 'file operations' stamp: 'jm 12/5/97 16:33'! deleteFileNamed: localFileName "Delete the file with the given name in this directory." self deleteFileNamed: localFileName ifAbsent: []. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'ar 3/21/98 18:08'! deleteFileNamed: localFileName ifAbsent: failBlock "Delete the file of the given name if it exists, else evaluate failBlock. If the first deletion attempt fails do a GC to force finalization of any lost references. ar 3/21/98 17:53" (self retryWithGC:[self primDeleteFileNamed: (self fullNameFor: localFileName)] until:[:result| result notNil]) == nil ifTrue: [^failBlock value]. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'JMM 1/29/2001 23:02'! getMacFileTypeAndCreator: fileName | results typeString creatorString | "get the Macintosh file type and creator info for the file with the given name. Fails if the file does not exist or if the type and creator type arguments are not strings of length 4. Does nothing on other platforms (where the underlying primitive is a noop)." "FileDirectory default getMacFileNamed: 'foo'" typeString _ ByteArray new: 4 withAll: ($? asInteger). creatorString _ ByteArray new: 4 withAll: ($? asInteger). [self primGetMacFileNamed: (self fullNameFor: fileName) type: typeString creator: creatorString.] ensure: [typeString _ typeString asString. creatorString _ creatorString asString]. results _ Array with: typeString with: creatorString. ^results ! ! !FileDirectory methodsFor: 'file operations' stamp: 'ar 2/2/2001 13:11'! mimeTypesFor: fileName "Return a list of MIME types applicable to the receiver. This default implementation uses the file name extension to figure out what we're looking at but specific subclasses may use other means of figuring out what the type of some file is. Some systems like the macintosh use meta data on the file to indicate data type" | idx ext dot | ext _ ''. dot _ self class extensionDelimiter. idx _ (self fullNameFor: fileName) findLast: [:ch| ch = dot]. idx = 0 ifFalse:[ext _ fileName copyFrom: idx+1 to: fileName size]. ^StandardMIMEMappings at: ext ifAbsent:[nil]! ! !FileDirectory methodsFor: 'file operations' stamp: 'MPH 10/23/2000 13:31'! putFile: file1 named: destinationFileName "Copy the contents of the existing fileStream into the file destinationFileName in this directory. fileStream can be anywhere in the fileSystem." | file2 | file1 binary. (file2 _ self newFileNamed: destinationFileName) ifNil: [^ false]. file2 binary. self copyFile: file1 toFile: file2. file1 close. file2 close. ^ true ! ! !FileDirectory methodsFor: 'file operations' stamp: 'tk 2/26/2000 12:54'! putFile: file1 named: destinationFileName retry: aBool "Copy the contents of the existing fileStream into the file destinationFileName in this directory. fileStream can be anywhere in the fileSystem. No retrying for local file systems." ^ self putFile: file1 named: destinationFileName ! ! !FileDirectory methodsFor: 'file operations' stamp: 'tk 3/31/2000 21:09'! rename: oldFileName toBe: newFileName | selection | "Rename the file of the given name to the new name. Fail if there is no file of the old name or if there is an existing file with the new name." "Modified for retry after GC ar 3/21/98 18:09" (self retryWithGC:[self primRename: (self fullNameFor: oldFileName) to: (self fullNameFor: newFileName)] until:[:result| result notNil]) ~~ nil ifTrue:[^self]. (self fileExists: oldFileName) ifFalse:[ ^self error:'Attempt to rename a non-existent file'. ]. (self fileExists: newFileName) ifTrue:[ selection _ (PopUpMenu labels: 'delete old version cancel') startUpWithCaption: 'Trying to rename a file to be ', newFileName , ' and it already exists.'. selection = 1 ifTrue: [self deleteFileNamed: newFileName. ^ self rename: oldFileName toBe: newFileName]]. ^self error:'Failed to rename file'.! ! !FileDirectory methodsFor: 'file operations' stamp: 'jm 3/27/98 06:40'! setMacFileNamed: fileName type: typeString creator: creatorString "Set the Macintosh file type and creator info for the file with the given name. Fails if the file does not exist or if the type and creator type arguments are not strings of length 4. Does nothing on other platforms (where the underlying primitive is a noop)." "FileDirectory default setMacFileNamed: 'foo' type: 'TEXT' creator: 'ttxt'" self primSetMacFileNamed: (self fullNameFor: fileName) type: typeString creator: creatorString. ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'jm 5/8/1998 20:48'! checkName: aFileName fixErrors: fixing "Check a string aFileName for validity as a file name. Answer the original file name if it is valid. If the name is not valid (e.g., it is too long or contains illegal characters) and fixing is false, raise an error. If fixing is true, fix the name (usually by truncating and/or tranforming characters), and answer the corrected name. The default behavior is just to truncate the name to the maximum length for this platform. Subclasses can do any kind of checking and correction appropriate for their platform." | maxLength | aFileName size = 0 ifTrue: [self error: 'zero length file name']. maxLength _ self class maxFileNameLength. aFileName size > maxLength ifTrue: [ fixing ifTrue: [^ aFileName contractTo: maxLength] ifFalse: [self error: 'file name is too long']]. ^ aFileName ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'jm 12/4/97 21:01'! fileNamesMatching: pat "FileDirectory default fileNamesMatching: '*'" ^ self fileNames select: [:name | pat match: name] ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'acg 1/7/2000 08:00'! fullNameFor: fileName "Return a corrected, fully-qualified name for the given file name. If the given name is already a full path (i.e., it contains a delimiter character), assume it is already a fully-qualified name. Otherwise, prefix it with the path to this directory. In either case, correct the local part of the file name." "Details: Note that path relative to a directory, such as '../../foo' are disallowed by this algorithm." | correctedLocalName prefix | self class splitName: fileName to: [:filePath :localName | correctedLocalName _ localName isEmpty ifFalse: [self checkName: localName fixErrors: true] ifTrue: [localName]. prefix _ self fullPathFor: filePath]. prefix isEmpty ifTrue: [^correctedLocalName]. prefix last = self pathNameDelimiter ifTrue:[^ prefix, correctedLocalName] ifFalse:[^ prefix, self slash, correctedLocalName]! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'jm 12/4/97 21:19'! isLegalFileName: aString "Answer true if the given string is a legal file name." ^ (self checkName: aString fixErrors: true) = aString ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'djp 10/27/1999 09:01'! nextNameFor: baseFileName extension: extension "Assumes a file name includes a version number encoded as '.' followed by digits preceding the file extension. Increment the version number and answer the new file name. If a version number is not found, set the version to 1 and answer a new file name" | files splits version | files _ self fileNamesMatching: (baseFileName,'*', self class dot, extension). splits _ files collect: [:file | self splitNameVersionExtensionFor: file] thenSelect: [:split | (split at: 1) = baseFileName]. splits _ splits asSortedCollection: [:a :b | (a at: 2) < (b at: 2)]. splits isEmpty ifTrue: [version _ 1] ifFalse: [version _ (splits last at: 2) + 1]. ^ (baseFileName, '.', version asString, self class dot, extension) asFileName! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'tk 2/25/2000 13:40'! realUrl ^ self url! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'djp 10/27/1999 08:58'! splitNameVersionExtensionFor: fileName " answer an array with the root name, version # and extension. See comment in nextSequentialNameFor: for more details" | baseName version extension i j | baseName _ self class baseNameFor: fileName. extension _ self class extensionFor: fileName. i _ j _ baseName findLast: [:c | c isDigit not]. i = 0 ifTrue: [version _ 0] ifFalse: [(baseName at: i) = $. ifTrue: [version _ (baseName copyFrom: i+1 to: baseName size) asNumber. j _ j - 1] ifFalse: [version _ 0]. baseName _ baseName copyFrom: 1 to: j]. ^ Array with: baseName with: version with: extension! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'tk 2/25/2000 13:40'! url "Convert my path into a file:// type url. Use slash instead of the local delimiter (:), and convert odd characters to %20 notation." "If slash (/) is not the file system delimiter, encode slashes before converting." | list | list _ self pathParts. ^ String streamContents: [:strm | strm nextPutAll: 'file:/'. list do: [:each | strm nextPut: $/; nextPutAll: each encodeForHTTP]. strm nextPut: $/]! ! !FileDirectory methodsFor: 'printing' stamp: 'jm 12/4/97 19:41'! printOn: aStream "Refer to the comment in Object|printOn:." aStream nextPutAll: self class name. aStream nextPutAll: ' on '. pathName printOn: aStream. ! ! !FileDirectory methodsFor: 'private' stamp: 'jm 8/14/1998 16:44'! directoryContentsFor: fullPath "Return a collection of directory entries for the files and directories in the directory with the given path. See primLookupEntryIn:index: for further details." "FileDirectory default directoryContentsFor: ''" | entries index done entryArray | entries _ OrderedCollection new: 200. index _ 1. done _ false. [done] whileFalse: [ entryArray _ self primLookupEntryIn: fullPath index: index. #badDirectoryPath = entryArray ifTrue: [^ OrderedCollection new]. entryArray == nil ifTrue: [done _ true] ifFalse: [entries addLast: (DirectoryEntry fromArray: entryArray)]. index _ index + 1]. ^ entries asArray ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primCreateDirectory: fullPath "Create a directory named by the given path. Fail if the path is bad or if a file or directory by that name already exists." self primitiveFailed ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primDeleteDirectory: fullPath "Delete the directory named by the given path. Fail if the path is bad or if a directory by that name does not exist." self primitiveFailed ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primDeleteFileNamed: aFileName "Delete the file of the given name. Return self if the primitive succeeds, nil otherwise." ^ nil ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primGetMacFileNamed: fileName type: typeString creator: creatorString "Get the Macintosh file type and creator info for the file with the given name. Fails if the file does not exist or if the type and creator type arguments are not strings of length 4. This primitive is Mac specific; it is a noop on other platforms." ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primLookupEntryIn: fullPath index: index "Look up the index-th entry of the directory with the given fully-qualified path (i.e., starting from the root of the file hierarchy) and return an array containing: The empty string enumerates the top-level files or drives. (For example, on Unix, the empty path enumerates the contents of '/'. On Macs and PCs, it enumerates the mounted volumes/drives.) The creation and modification times are in seconds since the start of the Smalltalk time epoch. DirFlag is true if the entry is a directory. FileSize the file size in bytes or zero for directories. The primitive returns nil when index is past the end of the directory. It fails if the given path is bad." ^ #badDirectoryPath ! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primRename: oldFileFullName to: newFileFullName "Rename the file of the given name to the new name. Fail if there is no file of the old name or if there is an existing file with the new name. Changed to return nil instead of failing ar 3/21/98 18:04" ^nil! ! !FileDirectory methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primSetMacFileNamed: fileName type: typeString creator: creatorString "Set the Macintosh file type and creator info for the file with the given name. Fails if the file does not exist or if the type and creator type arguments are not strings of length 4. This primitive is Mac specific; it is a noop on other platforms." self primitiveFailed ! ! !FileDirectory methodsFor: 'private' stamp: 'jm 12/4/97 22:44'! setPathName: pathString pathName _ pathString. ! ! !FileDirectory methodsFor: 'file status' stamp: 'mdr 1/14/2000 21:16'! entryAt: fileName "find the entry with local name fileName" ^self entryAt: fileName ifAbsent: [ self error: 'file not in directory: ', fileName ].! ! !FileDirectory methodsFor: 'file status' stamp: 'sma 6/3/2000 22:11'! entryAt: fileName ifAbsent: aBlock "Find the entry with local name fileName and answer it. If not found, answer the result of evaluating aBlock." | comparisonBlock | self isCaseSensitive ifTrue: [comparisonBlock _ [:entry | (entry at: 1) = fileName]] ifFalse: [comparisonBlock _ [:entry | (entry at: 1) sameAs: fileName]]. ^ self entries detect: comparisonBlock ifNone: [aBlock value]! ! !FileDirectory methodsFor: 'file directory' stamp: 'RAA 7/28/2000 13:47'! localNameFor: fullName "Return the local part the given name." ^self class localNameFor: fullName! ! !FileDirectory methodsFor: 'file directory' stamp: 'tk 12/13/1999 18:55'! sleep "Leave the FileList window. Do nothing. Disk directories do not have to be shut down." ! ! !FileDirectory methodsFor: 'file directory' stamp: 'di 2/11/2000 22:37'! wakeUp "Entering a FileList window. Do nothing. Disk directories do not have to be awakened." ! ! !FileDirectory methodsFor: 'searching' stamp: 'sw 6/2/2000 21:55'! filesContaining: searchString caseSensitive: aBoolean | aList | "Search the contents of all files in the receiver and its subdirectories for the search string. Return a list of paths found. Make the search case sensitive if aBoolean is true." aList _ OrderedCollection new. self withAllFilesDo: [:stream | (stream contentsOfEntireFile includesSubstring: searchString caseSensitive: aBoolean) ifTrue: [aList add: stream name]] andDirectoriesDo: [:d | d pathName]. ^ aList "FileDirectory default filesContaining: 'includesSubstring:' caseSensitive: true"! ! !FileDirectory methodsFor: 'searching' stamp: 'SIM 5/22/2000 13:33'! withAllFilesDo: fileStreamBlock andDirectoriesDo: directoryBlock "For the receiver and all it's subdirectories evaluate directoryBlock. For a read only file stream on each file within the receiver and it's subdirectories evaluate fileStreamBlock." | todo dir | todo _ OrderedCollection with: self. [todo size > 0] whileTrue: [ dir _ todo removeFirst. directoryBlock value: dir. dir fileNames do: [: n | fileStreamBlock value: (FileStream readOnlyFileNamed: (dir fullNameFor: n))]. dir directoryNames do: [: n | todo add: (dir directoryNamed: n)]] ! ! !FileDirectory methodsFor: 'squeaklets' stamp: 'RAA 10/17/2000 14:57'! directoryObject ^self! ! !FileDirectory methodsFor: 'squeaklets' stamp: 'RAA 10/12/2000 17:18'! updateProjectInfoFor: aProject "only swiki servers for now"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileDirectory class instanceVariableNames: ''! !FileDirectory class methodsFor: 'instance creation' stamp: 'jm 12/4/97 19:24'! default "Answer the default directory." ^ DefaultDirectory ! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'ls 9/10/1998 00:59'! forFileName: aString | path | path _ self dirPathFor: aString. path isEmpty ifTrue: [^ self default]. ^ self on: path ! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'tk 5/6/1998 14:31'! on: pathString "Return a new file directory for the given path, of the appropriate FileDirectory subclass for the current OS platform." | pathName | DirectoryClass ifNil: [DirectoryClass _ self activeDirectoryClass]. "If path ends with a delimiter (: or /) then remove it" ((pathName _ pathString) endsWith: self pathNameDelimiter asString) ifTrue: [ pathName _ pathName copyFrom: 1 to: pathName size - 1]. ^ DirectoryClass new setPathName: pathName ! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'jm 12/4/97 23:29'! root "Answer the root directory." ^ self on: '' ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'jm 3/28/98 06:16'! baseNameFor: fileName "Return the given file name without its extension, if any." | delim i | delim _ DirectoryClass extensionDelimiter. i _ fileName findLast: [:c | c = delim]. i = 0 ifTrue: [^ fileName] ifFalse: [^ fileName copyFrom: 1 to: i - 1]. ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'TPR 5/10/1998 21:32'! changeSuffix "if 'changes' is not suitable, override this message to return something that is ok" ^'changes'! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'jm 12/4/97 23:38'! checkName: fullName fixErrors: flag "Check a string aFileName for validity as a file name. Answer the original file name if it is valid. If the name is not valid (e.g., it is too long or contains illegal characters) and fixing is false, raise an error. If fixing is true, fix the name (usually by truncating and/or tranforming characters), and answer the corrected name. The default behavior is to truncate the name to 31 chars. Subclasses can do any kind of checking and correction appropriate to the underlying platform." ^ DefaultDirectory checkName: (self localNameFor: fullName) fixErrors: flag ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'jm 12/5/97 13:30'! dirPathFor: fullName "Return the directory part the given name." self splitName: fullName to: [:dirPath :localName | ^ dirPath] ! ! !FileDirectory class methodsFor: 'name utilities'! extensionFor: fileName "Return the extension of given file name, if any." | delim i | delim _ DirectoryClass extensionDelimiter. i _ fileName findLast: [:c | c = delim]. i = 0 ifTrue: [^ ''] ifFalse: [^ fileName copyFrom: i + 1 to: fileName size]. ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'mir 10/11/2000 17:38'! fileName: fileName extension: fileExtension | extension | extension _ FileDirectory dot , fileExtension. ^(fileName endsWith: extension) ifTrue: [fileName] ifFalse: [fileName , extension].! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'TPR 5/10/1998 21:31'! imageSuffix "if 'image' is not suitable, override this message to return something that is ok" ^'image'! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'jm 12/4/97 23:40'! isLegalFileName: fullName "Return true if the given string is a legal file name." ^ DefaultDirectory isLegalFileName: (self localNameFor: fullName) ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'jm 12/5/97 13:30'! localNameFor: fullName "Return the local part the given name." self splitName: fullName to: [:dirPath :localName | ^ localName] ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'sma 6/17/2000 00:11'! searchAllFilesForAString "Prompt the user for a search string, and a starting directory. Search the contents of all files in the starting directory and its subdirectories for the search string (case-insensitive search.) List the paths of files in which it is found on the Transcript. By Stewart MacLean 5/00; subsequently moved to FileDirectory class-side, and refactored to call FileDirectory.filesContaining:caseSensitive:" | searchString dir | searchString _ FillInTheBlank request: 'Enter search string'. searchString isEmpty ifTrue: [^nil]. Transcript cr; show: 'Searching for ', searchString printString, ' ...'. (dir _ PluggableFileList getFolderDialog open) ifNotNil: [(dir filesContaining: searchString caseSensitive: false) do: [:pathname | Transcript cr; show: pathname]]. Transcript cr; show: 'Finished searching for ', searchString printString "FileDirectory searchAllFilesForAString"! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'bf 3/22/2000 18:04'! splitName: fullName to: pathAndNameBlock "Take the file name and convert it to the path name of a directory and a local file name within that directory. FileName must be of the form: , where is optional. The part may contain delimiters." | delimiter i dirName localName | delimiter _ self pathNameDelimiter. (i _ fullName findLast: [:c | c = delimiter]) = 0 ifTrue: [dirName _ String new. localName _ fullName] ifFalse: [dirName _ fullName copyFrom: 1 to: (i - 1 max: 1). localName _ fullName copyFrom: i + 1 to: fullName size]. ^ pathAndNameBlock value: dirName value: localName! ! !FileDirectory class methodsFor: 'create/delete file' stamp: 'tk 1/13/98 17:21'! deleteFilePath: fullPathToAFile "Delete the file after finding its directory" | dir | dir _ self on: (self dirPathFor: fullPathToAFile). dir deleteFileNamed: (self localNameFor: fullPathToAFile). ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'sma 6/25/2000 10:39'! openSources: sourcesName andChanges: changesName forImage: imageName "Initialize the default directory to the image directory and open the sources and changes files, if possible. Look for the changes file in image directory. Look for the system sources (or an alias to it) first in the VM directory, then in the image directory. Open the changes and sources files and install them in SourceFiles." "Note: SourcesName and imageName are full paths; changesName is a local name." | sources changes sourceAlias msg wmsg localSourcesName | msg _ 'Squeak cannot locate &fileRef. Please check that the file is named properly and is in the same directory as this image. Further explanation can found in the startup window, ''How Squeak Finds Source Code''.'. wmsg _ 'Squeak cannot write to &fileRef. Please check that you have write permission for this file. You won''t be able to save this image correctly until you fix this.'. self setDefaultDirectoryFrom: imageName. sources _ changes _ nil. "look for the sources file or an alias to it in the VM's directory" (DefaultDirectory fileExists: sourcesName) ifTrue: [sources _ DefaultDirectory readOnlyFileNamed: sourcesName] ifFalse: ["look for an un-renamed Macintosh alias to the sources file" sourceAlias _ sourcesName , ' alias'. (DefaultDirectory fileExists: sourceAlias) ifTrue: [sources _ DefaultDirectory readOnlyFileNamed: sourceAlias]]. sources ifNil: ["look for the sources file or an alias to it in the image directory" localSourcesName _ FileDirectory localNameFor: sourcesName. (DefaultDirectory fileExists: localSourcesName) ifTrue: [sources _ DefaultDirectory readOnlyFileNamed: localSourcesName] ifFalse: ["look for an un-renamed Macintosh alias to the sources file" sourceAlias _ localSourcesName , ' alias'. (DefaultDirectory fileExists: sourceAlias) ifTrue: [sources _ DefaultDirectory readOnlyFileNamed: sourceAlias]]]. (DefaultDirectory fileExists: changesName) ifTrue: [changes _ DefaultDirectory oldFileNamed: changesName. changes isNil ifTrue: [PopUpMenu notify: (wmsg copyReplaceAll: '&fileRef' with: 'the changes file named ' , changesName). changes _ DefaultDirectory readOnlyFileNamed: changesName]]. ((sources == nil or: [sources atEnd]) and: [Preferences valueOfFlag: #warnIfNoSourcesFile]) ifTrue: [PopUpMenu notify: (msg copyReplaceAll: '&fileRef' with: 'the sources file named ' , sourcesName). Smalltalk platformName = 'Mac OS' ifTrue: [PopUpMenu notify: 'Make sure the sources file is not an Alias.']]. (changes == nil and: [Preferences valueOfFlag: #warnIfNoChangesFile]) ifTrue: [PopUpMenu notify: (msg copyReplaceAll: '&fileRef' with: 'the changes file named ' , changesName)]. (Preferences valueOfFlag: #warnIfNoChangesFile) ifTrue: [ ((changes next: 200) includesSubString: String crlf) ifTrue: [ PopUpMenu notify: 'The changes file named ' , changesName, ' has been injured by an unpacking utility. Crs were changed to CrLfs. Please set the preferences in your decompressing program to "do not convert text files" and unpack the system again.']]. SourceFiles _ Array with: sources with: changes! ! !FileDirectory class methodsFor: 'system start up' stamp: 'jm 3/27/98 06:53'! setDefaultDirectoryFrom: imageName "Initialize the default directory to the directory containing the Squeak image file. This method is called when the image starts up." DirectoryClass _ self activeDirectoryClass. DefaultDirectory _ self on: (self dirPathFor: imageName). ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'di 2/4/1999 15:27'! shutDown Smalltalk closeSourceFiles. ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'di 2/4/1999 08:50'! startUp "Establish the platform-specific FileDirectory subclass. Do any platform-specific startup." self setDefaultDirectoryFrom: Smalltalk imageName. Smalltalk openSourceFiles. ! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'jm 3/27/98 08:17'! dot "Return a one-character string containing the filename extension delimiter for this platform (i.e., the local equivalent of 'dot')" ^ self extensionDelimiter asString ! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'jm 3/27/98 06:57'! extensionDelimiter "Return the character used to delimit filename extensions on this platform. Most platforms use the period (.) character." ^ $. ! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'ar 5/1/1999 01:48'! isCaseSensitive "Return true if file names are treated case sensitive" ^true! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'jm 5/8/1998 20:45'! maxFileNameLength ^ 31 ! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'TPR 5/12/1998 22:49'! pathNameDelimiter "return the active directory class's directory seperator character" ^ DirectoryClass pathNameDelimiter! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'ar 4/18/1999 18:18'! slash ^ self pathNameDelimiter asString! ! !FileDirectory class methodsFor: 'private' stamp: 'TPR 5/10/1998 21:47'! activeDirectoryClass "Return the concrete FileDirectory subclass for the platform on which we are currently running." FileDirectory allSubclasses do: [:class | class isActiveDirectoryClass ifTrue: [^ class]]. "no responding subclass; use FileDirectory" ^ FileDirectory ! ! !FileDirectory class methodsFor: 'private' stamp: 'TPR 5/10/1998 21:40'! isActiveDirectoryClass "Does this class claim to be that properly active subclass of FileDirectory for this platform? Default test is whether the primPathNameDelimiter matches the one for this class. Other tests are possible" ^self pathNameDelimiter = self primPathNameDelimiter ! ! !FileDirectory class methodsFor: 'private' stamp: 'ar 2/2/2001 15:09'! primPathNameDelimiter "Return the path delimiter for the underlying platform's file system." self primitiveFailed ! ! !FileDirectory class methodsFor: 'class initialization' stamp: 'ar 2/2/2001 13:10'! initializeStandardMIMETypes "FileDirectory initializeStandardMIMETypes" StandardMIMEMappings _ Dictionary new. #( (gif ('image/gif')) (pdf ('application/pdf')) (aiff ('audio/aiff')) (bmp ('image/bmp')) (swf ('application/x-shockwave-flash')) (htm ('text/html' 'text/plain')) (html ('text/html' 'text/plain')) (jpg ('image/jpeg')) (jpeg ('image/jpeg')) (mid ('audio/midi')) (midi ('audio/midi')) (mp3 ('audio/mpeg')) (mpeg ('video/mpeg')) (mpg ('video/mpg')) (txt ('text/plain')) (text ('text/plain')) (mov ('video/quicktime')) (qt ('video/quicktime')) (tif ('image/tiff')) (tiff ('image/tiff')) (ttf ('application/x-truetypefont')) (wrl ('model/vrml')) (vrml ('model/vrml')) (wav ('audio/wav')) ) do:[:spec| StandardMIMEMappings at: spec first asString put: spec last. ].! ! ListItemWrapper subclass: #FileDirectoryWrapper instanceVariableNames: 'itemName balloonText ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Explorer'! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 6/15/2000 18:07'! asString ^itemName! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/21/2000 11:00'! balloonText ^balloonText! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/21/2000 11:01'! balloonText: aStringOrNil balloonText _ aStringOrNil! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 16:02'! contents ^((model directoryNamesFor: itemName) sortBy: [ :a :b | a caseInsensitiveLessOrEqual: b]) collect: [ :n | FileDirectoryWrapper with: (item directoryNamed: n) name: n model: self ] ! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 16:03'! directoryNamesFor: fullString ^model directoryNamesFor: itemName, FileDirectory slash, fullString! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 6/15/2000 18:03'! hasContents ^true ! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 6/15/2000 18:01'! setItem: anObject name: aString model: aModel item _ anObject. model _ aModel. itemName _ aString.! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 18:30'! settingSelector ^#setSelectedDirectoryTo:! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileDirectoryWrapper class instanceVariableNames: ''! !FileDirectoryWrapper class methodsFor: 'as yet unclassified' stamp: 'RAA 6/15/2000 18:01'! with: anObject name: aString model: aModel ^self new setItem: anObject name: aString model: aModel! ! FileStreamException subclass: #FileDoesNotExistException instanceVariableNames: 'readOnly ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !FileDoesNotExistException methodsFor: 'accessing' stamp: 'mir 7/25/2000 16:41'! readOnly ^readOnly == true! ! !FileDoesNotExistException methodsFor: 'accessing' stamp: 'mir 7/25/2000 16:40'! readOnly: aBoolean readOnly _ aBoolean! ! !FileDoesNotExistException methodsFor: 'exceptionDescription' stamp: 'mir 7/25/2000 18:22'! defaultAction "The default action taken if the exception is signaled." ^self readOnly ifTrue: [StandardFileStream readOnlyFileDoesNotExistUserHandling: self fileName] ifFalse: [StandardFileStream fileDoesNotExistUserHandling: self fileName] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileDoesNotExistException class instanceVariableNames: ''! !FileDoesNotExistException class methodsFor: 'examples' stamp: 'mir 2/29/2000 11:44'! example "FileDoesNotExistException example" | result | result _ [(StandardFileStream readOnlyFileNamed: 'error42.log') contentsOfEntireFile] on: FileDoesNotExistException do: [:ex | 'No error log']. Transcript show: result; cr! ! FileStreamException subclass: #FileExistsException instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !FileExistsException methodsFor: 'exceptionDescription' stamp: 'mir 7/25/2000 18:22'! defaultAction "The default action taken if the exception is signaled." ^StandardFileStream fileExistsUserHandling: self fileName ! ! TextInput subclass: #FileInput instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-HTML Forms'! !FileInput commentStamp: '' prior: 0! An input field for Support for uploading files using HTTP/multipart forms Appearance/behavior as in NS/MS browsers (i.e., separate filename entry box and browse files button)! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 19:37'! browse | file | file _ (StandardFileMenu oldFileFrom: self directory) ifNil: [^nil]. file directory isNil ifTrue: [^ nil]. textMorph setText: (file directory pathName, FileDirectory slash, file name); hasUnacceptedEdits: true; accept! ! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 18:56'! directory ^FileDirectory forFileName: self filename! ! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 18:43'! filename textMorph hasUnacceptedEdits ifTrue: [ textMorph accept ]. ^textMorph getText asString withInternetLineEndings! ! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 18:58'! localFilename ^FileDirectory localNameFor: self filename! ! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 19:23'! url ^FileUrl new path: (self directory pathParts), {self localFilename} isAbsolute: true! ! !FileInput methodsFor: 'accessing' stamp: 'bolot 11/27/1999 18:55'! value ^MIMEDocument contentType: (MIMEDocument guessTypeFromName: self filename) content: nil url: self url! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileInput class instanceVariableNames: ''! !FileInput class methodsFor: 'instance creation' stamp: 'bolot 11/27/1999 18:36'! name: aString textMorph: aTextMorph ^self name: aString defaultValue: '' textMorph: aTextMorph! ! StringHolder subclass: #FileList instanceVariableNames: 'fileName directory volList volListIndex list listIndex pattern sortMode brevityState ' classVariableNames: 'RecentDirs ' poolDictionaries: '' category: 'Tools-FileList'! !FileList commentStamp: '' prior: 0! I am model that can be used to navigate the host file system. By omitting the volume list, file list, and template panes from the view, I can also be used as the model for an editor on an individual file. FileLists can now see FTP servers anywhere on the net. In the volume list menu: fill in server info... Gives you a form to register a new ftp server you want to use. open server... Choose a server to connect to. local disk Go back to looking at your local volume. Still undone (you can contribute code): [ ] Using a Proxy server to get out through a firewall. What is the convention for proxy servers with FTP? [ ] Fill in the date and size info in the list of remote files. Allow sorting by it. New smarts needed in (ServerDirectory fileNameFormattedFrom:sizePad:sortMode:). [ ] Currently the FileList has no way to delete a directory. Since you can't select a directory without going into it, it would have to be deleting the current directory. Which would usually be empty. ! !FileList methodsFor: 'initialization' stamp: 'di 5/16/2000 09:42'! directory: dir "Set the path of the volume to be displayed." self okToChange ifFalse: [^ self]. self modelSleep. directory _ dir. self modelWakeUp. sortMode == nil ifTrue: [sortMode _ #date]. volList _ ((Array with: '[]'), directory pathParts) "Nesting suggestion from RvL" withIndexCollect: [:each :i | ( String new: i-1 withAll: $ ), each]. self changed: #relabel. self changed: #volumeList. self pattern: pattern! ! !FileList methodsFor: 'initialization' stamp: 'tk 5/18/1998 17:22'! labelString ^ directory pathName contractTo: 50! ! !FileList methodsFor: 'initialization' stamp: 'tk 12/17/1999 18:00'! modelSleep "User has exited or collapsed the window -- close any remote connection." directory ifNotNil: [directory sleep]! ! !FileList methodsFor: 'initialization' stamp: 'di 2/16/2000 10:50'! modelWakeUp "User has entered or expanded the window -- reopen any remote connection." (directory isKindOf: ServerDirectory) ifTrue: [directory wakeUp]! ! !FileList methodsFor: 'initialization' stamp: 'sbw 12/30/1999 15:53'! optionalButtonHeight ^ 15! ! !FileList methodsFor: 'initialization' stamp: 'RAA 1/17/2001 14:26'! optionalButtonRow | aRow aButton | aRow _ AlignmentMorph newRow beSticky. aRow clipSubmorphs: true. aRow addTransparentSpacerOfSize: (5@0). self optionalButtonSpecs do: [:spec | aButton _ PluggableButtonMorph on: self getState: nil action: spec second. aButton hResizing: #spaceFill; vResizing: #spaceFill; useRoundedCorners; label: spec first asString; askBeforeChanging: true; onColor: Color transparent offColor: Color transparent. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0). aButton setBalloonText: spec fourth. aRow addTransparentSpacerOfSize: (3 @ 0). (spec second == #sortBySize) ifTrue: [aRow addTransparentSpacerOfSize: (4@0)]]. ^ aRow! ! !FileList methodsFor: 'initialization' stamp: 'sw 1/7/2000 15:55'! optionalButtonSpecs ^ #( ('Name' sortByName sortingByName 'sort entries by name') ('Date' sortByDate sortingByDate 'sort entries by date') ('Size' sortBySize sortingBySize 'sort entries by size') ('Changes' browseChanges none 'open a changelist browser on selected file') ('File-in' fileInSelection none 'fileIn the selected file') ('File-in to New' fileIntoNewChangeSet none 'fileIn the selected file into a new change set') ('Delete' deleteFile none 'delete the seleted item')) ! ! !FileList methodsFor: 'initialization' stamp: 'sw 1/7/2000 15:56'! optionalButtonView | aView bHeight windowWidth offset previousView aButtonView wid specs | aView _ View new model: self. bHeight _ self optionalButtonHeight. windowWidth _ 120. aView window: (0@0 extent: windowWidth@bHeight). offset _ 0. specs _ self optionalButtonSpecs copyFrom: 1 to: 6. "Too cramped for the seventh!!" previousView _ nil. specs do: [:quad | aButtonView _ PluggableButtonView on: self getState: (quad third == #none ifTrue: [nil] ifFalse: [quad third]) action: quad second. quad second = specs last second ifTrue: [wid _ windowWidth - offset] ifFalse: [aButtonView borderWidthLeft: 0 right: 1 top: 0 bottom: 0. wid _ (windowWidth // (specs size)) - 2]. aButtonView label: quad first asParagraph; window: (offset@0 extent: wid@bHeight). offset _ offset + wid. quad second = specs first second ifTrue: [aView addSubView: aButtonView] ifFalse: [aView addSubView: aButtonView toRightOf: previousView]. previousView _ aButtonView]. ^aView ! ! !FileList methodsFor: 'initialization' stamp: 'di 5/11/1999 22:25'! release self modelSleep! ! !FileList methodsFor: 'initialization' stamp: 'tk 5/21/1998 12:28'! setFileStream: aStream "Used to initialize a spawned file editor. Sets directory too." self directory: aStream directory. fileName _ aStream localName. pattern _ '*'. listIndex _ 1. "pretend a file is selected" aStream close. brevityState _ #needToGetBrief. self changed: #contents. ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'di 5/26/1998 21:05'! askServerInfo "Get the user to create a ServerDirectory for a new server. Fill in and say Accept." | template | template _ '"Please fill in the following info, then select all text and choose DoIt." | aa | aa _ ServerDirectory new. aa server: ''st.cs.uiuc.edu''. "host" aa user: ''anonymous''. aa password: ''yourEmail@school.edu''. aa directory: ''/Smalltalk/Squeak/Goodies''. aa url: ''''. "<- this is optional. Only used when *writing* update files." ServerDirectory addServer: aa named: ''UIUCArchive''. "<- known by this name in Squeak"'. (StringHolder new contents: template) openLabel: 'FTP Server Form' ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'ar 6/16/1999 06:58'! deleteDirectory "Remove the currently selected directory" | localDir | directory entries size = 0 ifFalse:[^self inform:'Directory must be empty']. localDir _ directory pathParts last. (self confirm: 'Really delete ' , localDir printString , '?') ifFalse: [^ self]. self volumeListIndex: self volumeListIndex-1. directory deleteDirectory: localDir. self updateFileList.! ! !FileList methodsFor: 'volume list and pattern' stamp: 'ls 7/25/1998 01:15'! fileNameFormattedFrom: entry sizePad: sizePad "entry is a 5-element array of the form: (name creationTime modificationTime dirFlag fileSize)" | sizeStr nameStr dateStr | nameStr _ (entry at: 4) ifTrue: [entry first , self folderString] ifFalse: [entry first]. dateStr _ ((Date fromSeconds: (entry at: 3) ) printFormat: #(3 2 1 $. 1 1 2)) , ' ' , (String streamContents: [:s | (Time fromSeconds: (entry at: 3) \\ 86400) print24: true on: s]). sizeStr _ (entry at: 5) asStringWithCommas. sortMode = #name ifTrue: [^ nameStr , ' (' , dateStr , ' ' , sizeStr , ')']. sortMode = #date ifTrue: [^ '(' , dateStr , ' ' , sizeStr , ') ' , nameStr]. sortMode = #size ifTrue: [^ '(' , ((sizeStr size to: sizePad) collect: [:i | $ ]) , sizeStr , ' ' , dateStr , ') ' , nameStr]. ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'sma 11/11/2000 18:06'! listForPattern: pat "Make the list be those file names which match the pattern." | sizePad newList | newList _ (self entriesMatching: pat) asSortedCollection: self sortBlock. sizePad _ (newList inject: 0 into: [:mx :entry | mx max: (entry at: 5)]) asStringWithCommas size - 1. newList _ newList collect: [ :e | self fileNameFormattedFrom: e sizePad: sizePad ]. volList size = 1 ifTrue: ["Include known servers along with other desktop volumes" ^ newList asArray , (ServerDirectory serverNames collect: [:n | '^' , n , self folderString])]. ^ newList asArray! ! !FileList methodsFor: 'volume list and pattern' stamp: 'tk 4/7/98 15:26'! pattern ^ pattern ifNil: ['*'] ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'jm 5/3/1998 19:01'! pattern: textOrStringOrNil textOrStringOrNil ifNil: [pattern _ '*'] ifNotNil: [pattern _ textOrStringOrNil asString]. self updateFileList. ^ true ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'di 5/26/1998 21:08'! removeServer | choice names | names _ ServerDirectory serverNames asSortedArray. choice _ (SelectionMenu labelList: names selections: names) startUp. choice == nil ifTrue: [^ self]. ServerDirectory removeServerNamed: choice! ! !FileList methodsFor: 'volume list and pattern' stamp: 'sw 3/6/1999 11:39'! veryDeepFixupWith: deepCopier super veryDeepFixupWith: deepCopier. volListIndex _ 1. self directory: FileDirectory default. self updateFileList! ! !FileList methodsFor: 'volume list and pattern' stamp: 'jm 5/3/1998 18:20'! volumeList "Answer the current list of volumes." ^ volList ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'jm 5/3/1998 18:21'! volumeListIndex "Answer the index of the currently selected volume." ^ volListIndex ! ! !FileList methodsFor: 'volume list and pattern' stamp: 'stp 12/11/1999 19:37'! volumeListIndex: index "Select the volume name having the given index." | delim path | volListIndex := index. index = 1 ifTrue: [self directory: (FileDirectory on: '')] ifFalse: [delim := directory pathNameDelimiter. path := String streamContents: [:strm | 2 to: index do: [:i | strm nextPutAll: (volList at: i) withBlanksTrimmed. i < index ifTrue: [strm nextPut: delim]]]. self directory: (directory on: path)]. brevityState := #FileList. self addPath: path. self changed: #fileList. self changed: #contents! ! !FileList methodsFor: 'volume list and pattern' stamp: 'stp 12/11/1999 19:34'! volumeMenu: aMenu ^ aMenu labels: 'recent... add server... remove server... delete directory...' lines: # (1 3) selections: #(recentDirs askServerInfo removeServer deleteDirectory) ! ! !FileList methodsFor: 'file list'! fileList "Answer the list of files in the current volume." ^ list! ! !FileList methodsFor: 'file list'! fileListIndex "Answer the index of the currently selected file." ^ listIndex! ! !FileList methodsFor: 'file list' stamp: 'tk 1/2/2000 13:07'! fileListIndex: anInteger "Select the file name having the given index, and display its contents." | item name | self okToChange ifFalse: [^ self]. listIndex := anInteger. listIndex = 0 ifTrue: [fileName := nil] ifFalse: [item := self fileNameFromFormattedItem: (list at: anInteger). (item endsWith: self folderString) ifTrue: ["remove [...] folder string and open the folder" name := item copyFrom: 1 to: item size - self folderString size. listIndex := 0. brevityState := #FileList. self addPath: name. name first = $^ ifTrue: [self directory: (ServerDirectory serverNamed: name allButFirst)] ifFalse: [volListIndex = 1 ifTrue: [name _ name, directory slash]. self directory: (directory directoryNamed: name)]] ifFalse: [fileName := item]]. "open the file selected" brevityState := #needToGetBrief. self changed: #fileListIndex. self changed: #contents! ! !FileList methodsFor: 'file list menu' stamp: 'sge 11/28/1999 09:03'! addNew: aString byEvaluating: aBlock "A parameterization of earlier versions of #addNewDirectory and #addNewFile. Fixes the bug in each that pushing the cancel button in the FillInTheBlank dialog gave a walkback." | response newName index ending | self okToChange ifFalse: [^ self]. (response _ FillInTheBlank request: 'New ',aString,' Name?' initialAnswer: aString,'Name') isEmpty ifTrue: [^ self]. newName _ response asFileName. Cursor wait showWhile: [ aBlock value: newName]. self updateFileList. index _ list indexOf: newName. index = 0 ifTrue: [ending _ ') ',newName. index _ list findFirst: [:line | line endsWith: ending]]. self fileListIndex: index. ! ! !FileList methodsFor: 'file list menu' stamp: 'sge 11/28/1999 09:04'! addNewDirectory self addNew: 'Directory' byEvaluating: [:newName | directory createDirectory: newName] ! ! !FileList methodsFor: 'file list menu' stamp: 'sge 11/28/1999 09:04'! addNewFile self addNew: 'File' byEvaluating: [:newName | (directory newFileNamed: newName) close] ! ! !FileList methodsFor: 'file list menu' stamp: 'mdr 8/31/2000 18:50'! browseChanges "Browse the selected file in fileIn format." fileName ifNotNil: [ChangeList browseStream: (directory readOnlyFileNamed: fileName)] ifNil: [self beep]. ! ! !FileList methodsFor: 'file list menu' stamp: 'tk 3/15/2000 10:32'! compressFile "Compress the currently selected file" (directory readOnlyFileNamed: self fullName) compressFile. self updateFileList! ! !FileList methodsFor: 'file list menu' stamp: 'ar 1/15/2001 18:38'! copyName listIndex = 0 ifTrue: [^ self]. Clipboard clipboardText: self fullName asText. ! ! !FileList methodsFor: 'file list menu' stamp: 'di 8/16/1998 12:22'! deleteFile "Delete the currently selected file" listIndex = 0 ifTrue: [^ self]. (self confirm: 'Really delete ' , fileName , '?') ifFalse: [^ self]. directory deleteFileNamed: fileName. self updateFileList. brevityState _ #FileList. self get! ! !FileList methodsFor: 'file list menu'! editFile "Open a simple Edit window" listIndex = 0 ifTrue: [^ self]. (directory oldFileNamed: fileName) edit! ! !FileList methodsFor: 'file list menu' stamp: 'sma 5/20/2000 18:30'! fileAllIn "File in all of the currently selected file, if any." "wod 5/24/1998: open the file read only." | fn ff | listIndex = 0 ifTrue: [^ self]. ff _ directory readOnlyFileNamed: (fn _ self uncompressedFileName). ((self getSuffix: fn) sameAs: 'html') ifTrue: [ff _ ff asHtml]. ff fileIn! ! !FileList methodsFor: 'file list menu' stamp: 'di 4/26/2000 20:33'! fileContentsMenu: aMenu shifted: shifted | shiftMenu | ^ shifted ifFalse: [aMenu labels: 'get entire file view as hex browse changes find...(f) find again (g) set search string (h) do again (j) undo (z) copy (c) cut (x) paste (v) paste... do it (d) print it (p) inspect it (i) fileIn selection accept (s) cancel (l) more...' lines: #(3 6 8 12 16 18) selections: #(get getHex browseChanges find findAgain setSearchString again undo copySelection cut paste pasteRecent doIt printIt inspectIt fileItIn accept cancel shiftedYellowButtonActivity)] ifTrue: [shiftMenu _ ParagraphEditor shiftedYellowButtonMenu. aMenu labels: shiftMenu labelString lines: shiftMenu lineArray selections: shiftMenu selections] ! ! !FileList methodsFor: 'file list menu' stamp: 'tk 4/13/1998 23:10'! fileInSelection "FileIn all of the selected file." self canDiscardEdits ifFalse: [^ self changed: #flash]. self fileAllIn. ! ! !FileList methodsFor: 'file list menu' stamp: 'RAA 7/28/2000 13:49'! fileIntoNewChangeSet "File in all of the contents of the currently selected file, if any, into a new change set." | fn ff | listIndex = 0 ifTrue: [^ self]. ff _ directory readOnlyFileNamed: (fn _ self uncompressedFileName). ((self getSuffix: fn) sameAs: 'html') ifTrue: [ff _ ff asHtml]. ChangeSorter newChangesFromStream: ff named: (directory localNameFor: fn)! ! !FileList methodsFor: 'file list menu' stamp: 'tk 4/7/98 15:18'! fileListMenu: aMenu fileName ifNil: [^ self noFileSelectedMenu: aMenu] ifNotNil: [^ self fileSelectedMenu: aMenu]. ! ! !FileList methodsFor: 'file list menu' stamp: 'sma 5/20/2000 18:29'! fileNameSuffix ^ self getSuffix: self fullName! ! !FileList methodsFor: 'file list menu' stamp: 'di 8/20/1998 16:06'! fileSelectedMenu: aMenu | firstItems secondItems thirdItems n1 n2 n3 | firstItems _ self itemsForFileEnding: self fileNameSuffix asLowercase. secondItems _ self itemsForAnyFile. thirdItems _ self itemsForNoFile. n1 _ firstItems first size. n2 _ n1 + secondItems first size. n3 _ n2 + thirdItems first size. ^ aMenu labels: firstItems first , secondItems first , thirdItems first , #('more...') lines: firstItems second , (Array with: n1 with: n2) , (thirdItems second collect: [:n | n + n2]) , (Array with: n3) selections: firstItems third , secondItems third , thirdItems third , #(offerAllFileOptions)! ! !FileList methodsFor: 'file list menu' stamp: 'jm 5/3/1998 18:03'! get "Get contents of file again, it may have changed. Do this by making the cancel string be the contents, and doing a cancel." Cursor read showWhile: [ self okToChange ifFalse: [^ nil]. brevityState == #briefHex ifTrue: [brevityState _ #needToGetFullHex] ifFalse: [brevityState _ #needToGetFull]. self changed: #contents]. ! ! !FileList methodsFor: 'file list menu' stamp: 'jm 5/3/1998 18:04'! getHex "Get contents of file again, and display in Hex. Do this by making the cancel string be the contents, and doing a cancel." Cursor read showWhile: [ brevityState _ #needToGetBriefHex. self changed: #contents]. ! ! !FileList methodsFor: 'file list menu' stamp: 'di 9/15/1998 10:02'! importImage "Import the given image file and store the resulting Form in the global dictionary ImageImports, at a key consisting of the short filename up to the first period. " | key image | key _ fileName sansPeriodSuffix. image _ Form fromFileNamed: self fullName. Smalltalk imageImports at: key put: image. ! ! !FileList methodsFor: 'file list menu' stamp: 'ar 1/2/2000 15:30'! itemsForAnyFile ^ #(('copy name to clipboard' 'rename' 'delete' 'compress') () (copyName renameFile deleteFile compressFile) )! ! !FileList methodsFor: 'file list menu' stamp: 'di 5/13/2000 15:56'! itemsForFileEnding: suffix | labels lines selectors | labels _ OrderedCollection new. lines _ OrderedCollection new. selectors _ OrderedCollection new. (suffix = 'bmp') | (suffix = 'gif') | (suffix = 'jpg') | (suffix = 'form') | (suffix = '*') | (suffix = 'png') ifTrue: [labels addAll: #('open image in a window' 'read image into ImageImports' 'open image as background'). selectors addAll: #(openImageInWindow importImage openAsBackground)]. (suffix = 'morph') | (suffix = 'morphs') | (suffix = 'sp') | (suffix = '*') ifTrue: [labels add: 'load as morph'. selectors add: #openMorphFromFile. labels add: 'load as project'. selectors add: #openProjectFromFile]. (suffix = 'extseg') | (suffix = 'project') | (suffix = 'pr') ifTrue: [labels add: 'load as project'. selectors add: #openProjectFromFile]. (suffix = 'bo') | (suffix = '*') ifTrue:[ labels add: 'load as book'. selectors add: #openBookFromFile]. (suffix = 'mid') | (suffix = '*') ifTrue: [labels add: 'play midi file'. selectors add: #playMidiFile]. (suffix = 'movie') | (suffix = '*') ifTrue: [labels add: 'open as movie'. selectors add: #openAsMovie]. (suffix = 'st') | (suffix = 'cs') | (suffix = '*') ifTrue: [suffix = '*' ifTrue: [lines add: labels size]. labels addAll: #('fileIn' 'file into new change set' 'browse changes' 'browse code' 'remove line feeds' 'broadcast as update'). lines add: labels size - 1. selectors addAll: #(fileInSelection fileIntoNewChangeSet browseChanges browseFile removeLinefeeds putUpdate)]. (suffix = 'swf') | (suffix = '*') ifTrue:[ labels add:'open as Flash'. selectors add: #openAsFlash]. (suffix = 'ttf') | (suffix = '*') ifTrue:[ labels add: 'open true type font'. selectors add: #openAsTTF]. (suffix = 'gz') | (suffix = '*') ifTrue:[ labels addAll: #('view decompressed' 'decompress to file'). selectors addAll: #(viewGZipContents saveGZipContents)]. (suffix = '3ds') | (suffix = '*') ifTrue:[ labels add: 'Open 3DS file'. selectors add: #open3DSFile]. (suffix = 'tape') | (suffix = '*') ifTrue: [labels add: 'open for playback'. selectors add: #openTapeFromFile]. (suffix = 'wrl') | (suffix = '*') ifTrue: [labels add: 'open in Wonderland'. selectors add: #openVRMLFile]. (suffix = 'htm') | (suffix = 'html') ifTrue: [labels add: 'open in browser'. selectors add: #openInBrowser]. (suffix = '*') ifTrue: [labels addAll: #('generate HTML'). lines add: labels size - 1. selectors addAll: #(renderFile)]. ^ Array with: labels with: lines with: selectors! ! !FileList methodsFor: 'file list menu' stamp: 'di 11/19/1998 14:25'! itemsForNoFile ^ #( ('sort by name' 'sort by size' 'sort by date' 'browse code files' 'add new file' 'add new directory') (3 4) (sortByName sortBySize sortByDate browseFiles addNewFile addNewDirectory) )! ! !FileList methodsFor: 'file list menu' stamp: 'di 8/20/1998 09:34'! noFileSelectedMenu: aMenu | items | items _ self itemsForNoFile. ^ aMenu labels: items first lines: items second selections: items third ! ! !FileList methodsFor: 'file list menu' stamp: 'di 8/20/1998 16:05'! offerAllFileOptions | items action | items _ self itemsForFileEnding: '*'. action _ (SelectionMenu labels: items first lines: items second selections: items third) startUp. action ifNotNil: [self perform: action]! ! !FileList methodsFor: 'file list menu' stamp: 'DSM 10/14/1999 15:17'! open3DSFile "Open a MoviePlayerMorph on the given file (must be in .3ds format)." | scene | scene _ B3DScene from3DS: (ThreeDSParser parseFileNamed: self fullName). Smalltalk isMorphic ifTrue:[ (B3DPrimitiveEngine isAvailable) ifFalse:[ (self confirm:'WARNING: YOU HAVE NO REAL SUPPORT FOR 3D!! Opening this guy in Morphic will EXTREMELY time consuming. Are you sure you want to do this?!! (NO is probably the right answer :-)') ifFalse:[^scene inspect]]. scene defaultCamera moveToFit: scene. (B3DSceneMorph new scene: scene) openInWorld. ] ifFalse:[scene inspect].! ! !FileList methodsFor: 'file list menu' stamp: 'jcg 5/1/2000 23:09'! openAsBackground "Set an image as a background image. Support Squeak's common file format (GIF, JPG, PNG, 'Form stoteOn: (run coded)' and BMP)" (Form fromFileNamed: self fullName) setAsBackground! ! !FileList methodsFor: 'file list menu' stamp: 'ar 2/13/1999 21:25'! openAsFlash "Open a MoviePlayerMorph on the given file (must be in .movie format)." | f player | Smalltalk at: #Morph ifAbsent: [^ self beep]. f _ (directory readOnlyFileNamed: self fullName) binary. player _ (FlashMorphReader on: f) processFile. player startPlaying. player open.! ! !FileList methodsFor: 'file list menu' stamp: 'di 8/17/1998 17:31'! openAsMovie "Open a MoviePlayerMorph on the given file (must be in .movie format)." Smalltalk at: #Morph ifAbsent: [^ self beep]. (MoviePlayerMorph new openFileNamed: self fullName) openInWorld! ! !FileList methodsFor: 'file list menu' stamp: 'ar 11/14/1998 23:46'! openAsTTF (TTFontReader parseFileNamed: self fullName) asMorph open! ! !FileList methodsFor: 'file list menu' stamp: 'mdr 8/31/2000 18:58'! openBookFromFile "Reconstitute a Morph from the selected file, presumed to be represent a Morph saved via the SmartRefStream mechanism, and open it in an appropriate Morphic world" | book aFileStream | Smalltalk verifyMorphicAvailability ifFalse: [^ self]. aFileStream _ directory readOnlyFileNamed: self fullName. book _ BookMorph new. book setProperty: #url toValue: aFileStream url. book fromRemoteStream: aFileStream. aFileStream close. Smalltalk isMorphic ifTrue: [Display getCurrentMorphicWorld addMorphsAndModel: book] ifFalse: [book isMorph ifFalse: [^ self errorMustBeMorph]. book openInWorld]. book goToPage: 1! ! !FileList methodsFor: 'file list menu' stamp: 'RAA 6/2/2000 17:58'! openImageInWindow "Handle five file formats: GIF, JPG, PNG, Form stoteOn: (run coded), and BMP. Fail if file format is not recognized." | image myStream | myStream _ (directory readOnlyFileNamed: fileName) binary. image _ Form fromBinaryStream: myStream. myStream close. Smalltalk isMorphic ifTrue: [(SketchMorph withForm: image) openInWorld] ifFalse: [FormView open: image named: fileName]! ! !FileList methodsFor: 'file list menu' stamp: 'sma 4/30/2000 11:32'! openInBrowser Scamper openOnUrl: (directory url , fileName encodeForHTTP)! ! !FileList methodsFor: 'file list menu' stamp: 'mdr 8/31/2000 18:59'! openMorphFromFile "Reconstitute a Morph from the selected file, presumed to be represent a Morph saved via the SmartRefStream mechanism, and open it in an appropriate Morphic world" | aFileStream morphOrList | Smalltalk verifyMorphicAvailability ifFalse: [^ self]. aFileStream _ directory readOnlyFileNamed: self fullName. morphOrList _ aFileStream fileInObjectAndCode. (morphOrList isKindOf: SqueakPage) ifTrue: [morphOrList _ morphOrList contentsMorph]. Smalltalk isMorphic ifTrue: [Display getCurrentMorphicWorld addMorphsAndModel: morphOrList] ifFalse: [morphOrList isMorph ifFalse: [^ self errorMustBeMorph]. morphOrList openInWorld]! ! !FileList methodsFor: 'file list menu' stamp: 'RAA 10/15/2000 18:02'! openProjectFromFile "Reconstitute a Morph from the selected file, presumed to be represent a Morph saved via the SmartRefStream mechanism, and open it in an appropriate Morphic world." fileName ifNil: [^self]. Project canWeLoadAProjectNow ifFalse: [^ self]. ProjectLoading openFromDirectory: directory andFileName: fileName ! ! !FileList methodsFor: 'file list menu' stamp: 'di 1/31/1999 11:05'! openTapeFromFile "Open an eventRecorder tape for playback." (EventRecorderMorph new readTape: self fullName) rewind openInWorld! ! !FileList methodsFor: 'file list menu' stamp: 'ar 9/16/1999 14:48'! openVRMLFile | scene | scene _ Wonderland new. scene makeActorFromVRML: self fullName. ! ! !FileList methodsFor: 'file list menu' stamp: 'di 5/20/1998 23:25'! perform: selector orSendTo: otherTarget "Selector was just chosen from a menu by a user. If can respond, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked." (#(get getHex browseChanges sortByDate sortBySize sortByName fileInSelection fileIntoNewChangeSet browseChanges copyName openImageInWindow importImage playMidiFile renameFile deleteFile addNewFile putUpdate) includes: selector) ifTrue: [^ self perform: selector] ifFalse: [^ super perform: selector orSendTo: otherTarget]! ! !FileList methodsFor: 'file list menu' stamp: 'mdr 8/31/2000 18:52'! playMidiFile "Play a MIDI file." | f score | Smalltalk at: #MIDIFileReader ifPresent: [:midiReader | Smalltalk at: #ScorePlayerMorph ifPresent: [:scorePlayer | f _ (directory readOnlyFileNamed: self fullName) binary. score _ (midiReader new readMIDIFrom: f) asScore. f close. scorePlayer openOn: score title: fileName]]. ! ! !FileList methodsFor: 'file list menu' stamp: 'tk 3/12/1999 18:07'! putUpdate "Put this file out as an Update on the servers." | names choice | self canDiscardEdits ifFalse: [^ self changed: #flash]. names _ ServerDirectory groupNames asSortedArray. choice _ (SelectionMenu labelList: names selections: names) startUp. choice == nil ifTrue: [^ self]. (ServerDirectory groupNamed: choice) putUpdate: (directory oldFileNamed: self fullName). self volumeListIndex: volListIndex. ! ! !FileList methodsFor: 'file list menu' stamp: 'ar 9/3/1999 13:05'! removeLinefeeds "Remove any line feeds by converting to CRs instead" | fileContents | fileContents _ (CrLfFileStream readOnlyFileNamed: self fullName) contentsOfEntireFile. (StandardFileStream newFileNamed: self fullName) nextPutAll: fileContents; close.! ! !FileList methodsFor: 'file list menu' stamp: 'sge 2/13/2000 04:36'! renameFile "Rename the currently selected file" | newName response | listIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. (response _ FillInTheBlank request: 'NewFileName?' initialAnswer: fileName) isEmpty ifTrue: [^ self]. newName _ response asFileName. newName = fileName ifTrue: [^ self]. directory rename: fileName toBe: newName. self updateFileList. listIndex _ list findFirst: [:item | (self fileNameFromFormattedItem: item) = newName]. listIndex > 0 ifTrue: [fileName _ newName]. self changed: #fileListIndex. ! ! !FileList methodsFor: 'file list menu' stamp: 'mjg 9/3/1998 17:24'! renderFile "Render the currently selected file" | map action file renderedFile formatPage | listIndex = 0 ifTrue: [^ self]. map _ URLmap new. action _ RenderedSwikiAction new. action name: '.'. "For image references, refer to this directory" map action: action. map directory: directory. (directory fileExists: 'glossary') ifFalse: [Cursor wait showWhile: [ (directory newFileNamed: 'glossary') close].]. map readGlossary: (directory oldFileNamed: 'glossary'). formatPage _ SwikiPage new. formatPage map: map. formatPage coreID: (fileName allButFirst). formatPage formatted: (HTMLformatter evalEmbedded: (directory oldFileNamed: fileName) contentsOfEntireFile with: formatPage unlessContains: (Set new)). formatPage name isNil ifTrue: [self notify: 'You forgot to name the page!! '. formatPage name: 'defaultName'.]. map pages at: (formatPage name asLowercase) put: formatPage. formatPage formatted: (LessHTMLformatter swikify: (formatPage formatted) linkhandler: [:link | map linkFor: link from: 'Nowhere' storingTo: OrderedCollection new]). "Make a template if one does not exist" (directory fileExists: 'template.html') ifFalse: [Cursor wait showWhile: [ (directory newFileNamed: 'template.html') nextPutAll: (self templateFile); close].]. renderedFile _ (directory pathName),(ServerAction pathSeparator),(formatPage coreID). (directory fileExists: renderedFile) ifTrue: [directory deleteFileNamed: renderedFile]. file _ FileStream fileNamed: renderedFile. file nextPutAll: (HTMLformatter evalEmbedded: (directory oldFileNamed: 'template.html') contentsOfEntireFile with: formatPage). file close. FileDirectory default setMacFileNamed: renderedFile type: 'TEXT' creator: 'MOSS'. map writeGlossary. "Directory is already in the map, so write to the glossary there" self updateFileList. ! ! !FileList methodsFor: 'file list menu' stamp: 'sma 5/20/2000 18:19'! saveGZipContents "Save the contents of a gzipped file" | zipped buffer unzipped newName | newName _ fileName copyUpToLast: FileDirectory extensionDelimiter. unzipped _ directory newFileNamed: newName. zipped _ GZipReadStream on: (directory readOnlyFileNamed: self fullName). buffer _ String new: 50000. 'Extracting ' , self fullName displayProgressAt: Sensor cursorPoint from: 0 to: zipped sourceStream size during: [:bar | [zipped atEnd] whileFalse: [bar value: zipped sourceStream position. unzipped nextPutAll: (zipped nextInto: buffer)]. zipped close. unzipped close]. self updateFileList. ^ newName! ! !FileList methodsFor: 'file list menu' stamp: 'di 4/15/98 12:36'! sortByDate self resort: #date! ! !FileList methodsFor: 'file list menu' stamp: 'di 4/15/98 12:37'! sortByName self resort: #name! ! !FileList methodsFor: 'file list menu' stamp: 'di 4/15/98 12:36'! sortBySize self resort: #size! ! !FileList methodsFor: 'file list menu' stamp: 'tk 5/21/1998 12:39'! spawn: code "Open a simple Edit window" listIndex = 0 ifTrue: [^ self]. FileList openEditorOn: (directory readOnlyFileNamed: fileName) "read only just for initial look" editString: code! ! !FileList methodsFor: 'file list menu' stamp: 'mjg 9/1/1998 14:10'! templateFile ^' <?request name?> ' ! ! !FileList methodsFor: 'file list menu' stamp: 'ar 1/2/2000 15:31'! viewGZipContents "View the contents of a gzipped file" | f | f _ (directory readOnlyFileNamed: self fullName). contents _ f contentsOfEntireFile. Cursor wait showWhile:[contents _ (GZipReadStream on: contents) upToEnd]. contents replaceAll: Character lf with: Character cr. (StringHolder new) contents: contents; openLabel:'Contents of ', fileName printString! ! !FileList methodsFor: 'private' stamp: 'stp 12/11/1999 20:05'! addPath: aString "Add the given string to the list of recently visited directories." | full | aString ifNil: [^self]. full := String streamContents: [ :strm | 2 to: volList size do: [ :i | strm nextPutAll: (volList at: i) withBlanksTrimmed. strm nextPut: FileDirectory pathNameDelimiter]]. full := full, aString. "Remove and super-directories of aString from the collection." RecentDirs removeAllSuchThat: [ :aDir | ((aDir, '*') match: full)]. "If a sub-directory is in the list, do nothing." (RecentDirs detect: [ :aDir | ((full, '*') match: aDir)] ifNone: [nil]) ifNotNil: [^self]. [RecentDirs size >= 10] whileTrue: [RecentDirs removeFirst]. RecentDirs addLast: full! ! !FileList methodsFor: 'private' stamp: 'di 8/16/1998 09:26'! contents "Answer the contents of the file, reading it first if needed." "Possible brevityState values: FileList, fullFile, briefFile, needToGetFull, needToGetBrief, fullHex, briefHex, needToGetFullHex, needToGetBriefHex" (listIndex = 0) | (brevityState == #FileList) ifTrue: [^ self defaultContents]. "no file selected" brevityState == #fullFile ifTrue: [^ contents]. brevityState == #fullHex ifTrue: [^ contents]. brevityState == #briefFile ifTrue: [^ contents]. brevityState == #briefHex ifTrue: [^ contents]. brevityState == #needToGetFullHex ifTrue: [^ self readContentsHex: false]. brevityState == #needToGetBriefHex ifTrue: [^ self readContentsHex: true]. brevityState == #needToGetFull ifTrue: [^ self readContentsBrief: false]. brevityState == #needToGetBrief ifTrue: [^ self readContentsBrief: true]. "default" self halt: 'unknown state ' , brevityState printString! ! !FileList methodsFor: 'private' stamp: 'di 8/16/1998 09:25'! defaultContents contents _ list == nil ifTrue: [String new] ifFalse: [String streamContents: [:s | s nextPutAll: 'NO FILE SELECTED'; cr. s nextPutAll: ' -- Folder Summary --'; cr. list do: [:item | s nextPutAll: item; cr]]]. brevityState _ #FileList. ^ contents! ! !FileList methodsFor: 'private' stamp: 'sma 11/11/2000 17:00'! entriesMatching: patternString "Answer a list of directory entries which match the patternString. The patternString may consist of multiple patterns separated by ';'. Each pattern can include a '*' or '#' as wildcards - see String>>match:" | entries patterns | entries _ directory entries. patterns _ patternString findTokens: ';'. (patterns anySatisfy: [:each | each = '*']) ifTrue: [^ entries]. ^ entries select: [:entry | entry isDirectory or: [patterns anySatisfy: [:each | each match: entry first]]]! ! !FileList methodsFor: 'private' stamp: 'sma 4/30/2000 09:41'! errorMustBeMorph self inform: 'Can only load a single morph into an mvc project via this mechanism.'! ! !FileList methodsFor: 'private'! fileNameFromFormattedItem: item "Extract fileName and folderString from a formatted fileList item string" | i | (i _ item indexOf: $( ifAbsent: [0]) = 0 ifTrue: [^ item withBlanksTrimmed]. ^ (item copyReplaceFrom: i to: (item findFirst: [:c | c = $)]) with: '') withBlanksTrimmed! ! !FileList methodsFor: 'private'! folderString ^ ' [...]'! ! !FileList methodsFor: 'private' stamp: 'jm 5/3/1998 18:29'! fullName "Answer the full name for the currently selected file." ^ directory fullNameFor: fileName ! ! !FileList methodsFor: 'private' stamp: 'sma 5/20/2000 18:29'! getSuffix: aString | i | i _ aString findLast: [:each | $. = each]. ^ i = 0 ifTrue: [''] ifFalse: [aString copyFrom: i + 1 to: aString size]! ! !FileList methodsFor: 'private' stamp: 'di 8/16/1998 09:46'! put: aText | ff type | brevityState == #fullFile ifTrue: [ff _ directory newFileNamed: self fullName. Cursor write showWhile: [ff nextPutAll: aText asString; close]. fileName = ff localName ifTrue: [contents _ aText asString] ifFalse: [self updateFileList]. "user renamed the file" ^ true "accepted"]. listIndex = 0 ifTrue: [PopUpMenu notify: 'No fileName is selected'. ^ false "failed"]. type _ 'These'. brevityState = #briefFile ifTrue: [type _ 'Abbreviated']. brevityState = #briefHex ifTrue: [type _ 'Abbreviated']. brevityState = #fullHex ifTrue: [type _ 'Hexadecimal']. brevityState = #FileList ifTrue: [type _ 'Directory']. PopUpMenu notify: type , ' contents cannot meaningfully be saved at present.'. ^ false "failed" ! ! !FileList methodsFor: 'private' stamp: 'tk 3/23/2000 16:21'! readContentsBrief: brevityFlag "Read the contents of the receiver's selected file, unless it is too long, in which case show just the first 5000 characters. Don't create a file if it doesn't already exist." | f fileSize first5000 | brevityFlag ifTrue: [ (directory isKindOf: ServerDirectory) ifTrue: [^ self readServerBrief]]. f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. (brevityFlag not or: [(fileSize _ f size) <= 100000]) ifTrue: [contents _ f contentsOfEntireFile. brevityState _ #fullFile. "don't change till actually read" ^ contents]. "if brevityFlag is true, don't display long files when first selected" first5000 _ f next: 5000. f close. contents _ 'File ''', fileName, ''' is ', fileSize printString, ' bytes long. You may use the ''get'' command to read the entire file. Here are the first 5000 characters... ------------------------------------------ ', first5000 , ' ------------------------------------------ ... end of the first 5000 characters.'. brevityState _ #briefFile. "don't change till actually read" ^ contents. ! ! !FileList methodsFor: 'private' stamp: 'di 8/16/1998 09:20'! readContentsHex: brevity "retrieve the contents from the external file unless it is too long. Don't create a file here. Check if exists." | f size data hexData s | f _ directory oldFileOrNoneNamed: self fullName. f == nil ifTrue: [^ 'For some reason, this file cannot be read']. ((size _ f size)) > 5000 & brevity ifTrue: [data _ f next: 10000. f close. brevityState _ #briefHex] ifFalse: [data _ f contentsOfEntireFile. brevityState _ #fullHex]. s _ WriteStream on: (String new: data size*4). 0 to: data size-1 by: 16 do: [:loc | s nextPutAll: loc hex; space; nextPut: $(; print: loc; nextPut: $); space; tab. loc+1 to: (loc+16 min: data size) do: [:i | s nextPutAll: (data at: i) hex; space]. s cr]. hexData _ s contents. ^ contents _ ((size > 5000) & brevity ifTrue: ['File ''', fileName, ''' is ', size printString, ' bytes long. You may use the ''get'' command to read the entire file. Here are the first 5000 characters... ------------------------------------------ ', hexData , ' ------------------------------------------ ... end of the first 5000 characters.'] ifFalse: [hexData]). ! ! !FileList methodsFor: 'private' stamp: 'tk 3/24/2000 10:52'! readServerBrief | lString sizeStr fsize ff first5000 parts | "If file on server is known to be long, just read the beginning. Cheat badly by reading the fileList string." listIndex = 0 ifTrue: [^ self]. "Get size from file list entry" lString _ list at: listIndex. parts _ lString findTokens: '()'. sortMode = #name ifTrue: [sizeStr _ (parts second findTokens: ' ') third]. sortMode = #date ifTrue: [sizeStr _ (parts first findTokens: ' ') third]. sortMode = #size ifTrue: [sizeStr _ (parts first findTokens: ' ') first]. fsize _ (sizeStr copyWithout: $,) asNumber. fsize <= 50000 ifTrue: [ff _ directory oldFileOrNoneNamed: self fullName. ff ifNil: [^ 'For some reason, this file cannot be read']. contents _ ff contentsOfEntireFile. brevityState _ #fullFile. "don't change till actually read" ^ contents]. "if brevityFlag is true, don't display long files when first selected" first5000 _ directory getOnly: 3500 from: fileName. contents _ 'File ''', fileName, ''' is ', sizeStr, ' bytes long. You may use the ''get'' command to read the entire file. Here are the first 3500 characters... ------------------------------------------ ', first5000 , ' ------------------------------------------ ... end of the first 3500 characters.'. brevityState _ #briefFile. "don't change till actually read" ^ contents. ! ! !FileList methodsFor: 'private' stamp: 'stp 12/11/1999 20:03'! recentDirs "Put up a menu and let the user select from the list of recently visited directories." | dirName | RecentDirs isEmpty ifTrue: [^self]. dirName := (SelectionMenu selections: RecentDirs) startUp. dirName == nil ifTrue: [^self]. self directory: (FileDirectory on: dirName)! ! !FileList methodsFor: 'private' stamp: 'ls 9/11/1998 04:15'! resort: newMode "Re-sort the list of files." | name | listIndex > 0 ifTrue: [name _ self fileNameFromFormattedItem: (list at: listIndex)]. sortMode _ newMode. self pattern: pattern. name ifNotNil: [ fileName _ name. listIndex _ list findFirst: [:item | (self fileNameFromFormattedItem: item) = name. ]. self changed: #fileListIndex]. listIndex = 0 ifTrue: [self changed: #contents] ! ! !FileList methodsFor: 'private' stamp: 'sma 11/11/2000 17:04'! sortBlock "Answer block to decide what order to display the directory entries." ^ [ :x :y | (x isDirectory = y isDirectory) ifTrue: [ "sort by user-specified criterion" sortMode = #name ifTrue: [(x name compare: y name) <= 2] ifFalse: [ sortMode = #date ifTrue: [ x modificationTime = y modificationTime ifTrue: [ (x name compare: y name) <= 2 ] ifFalse: [ x modificationTime > y modificationTime ] ] ifFalse: [ "size" x fileSize = y fileSize ifTrue: [ (x name compare: y name) <= 2 ] ifFalse: [ x fileSize > y fileSize ] ] ] ] ifFalse: [ "directories always precede files" x isDirectory ] ]! ! !FileList methodsFor: 'private' stamp: 'sw 1/7/2000 15:58'! sortingByDate ^ sortMode == #date! ! !FileList methodsFor: 'private' stamp: 'sw 1/7/2000 15:57'! sortingByName ^ sortMode == #name! ! !FileList methodsFor: 'private' stamp: 'sw 1/7/2000 15:58'! sortingBySize ^ sortMode == #size! ! !FileList methodsFor: 'private' stamp: 'sma 5/20/2000 18:31'! uncompressedFileName | f | f _ self fullName. ((f endsWith: '.gz') and: [self confirm: f , ' appears to be a compressed file. Do you want to uncompress it?']) ifFalse: [^ f]. ^ self saveGZipContents! ! !FileList methodsFor: 'private' stamp: 'wod 5/27/1998 17:47'! updateFileList "Update my files list with file names in the current directory that match the pattern." "wod 5/27/1998: nil out the fileName." Cursor execute showWhile: [list _ (pattern includes: $*) | (pattern includes: $#) ifTrue: [self listForPattern: pattern] ifFalse: [ pattern isEmpty ifTrue: [self listForPattern: '*'] ifFalse: [self listForPattern: '*', pattern, '*']]. listIndex _ 0. volListIndex _ volList size. fileName _ nil. contents _ ''. self changed: #volumeListIndex. self changed: #fileList]. ! ! !FileList methodsFor: 'menu messages' stamp: 'wod 5/13/1998 04:10'! browseFile FileContentsBrowser browseFile: self fullName.! ! !FileList methodsFor: 'menu messages' stamp: 'wod 5/13/1998 04:10'! browseFiles | selectionPattern fileList | selectionPattern := FillInTheBlank request:'What files?' initialAnswer: self pattern. fileList _ (directory fileNamesMatching: selectionPattern) collect: [:each | directory fullNameFor: each]. FileContentsBrowser browseFiles: fileList. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileList class instanceVariableNames: ''! !FileList class methodsFor: 'instance creation' stamp: 'sw 1/25/2001 08:45'! open "Open a view of an instance of me on the default directory." "FileList open" | dir aFileList topView volListView templateView fileListView fileContentsView underPane pHeight | Smalltalk isMorphic ifTrue: [^ self openAsMorph]. dir _ FileDirectory default. aFileList _ self new directory: dir. topView _ StandardSystemView new. topView model: aFileList; label: dir pathName; minimumSize: 200@200. topView borderWidth: 1. volListView _ PluggableListView on: aFileList list: #volumeList selected: #volumeListIndex changeSelected: #volumeListIndex: menu: #volumeMenu:. volListView autoDeselect: false. volListView window: (0@0 extent: 80@45). topView addSubView: volListView. templateView _ PluggableTextView on: aFileList text: #pattern accept: #pattern:. templateView askBeforeDiscardingEdits: false. templateView window: (0@0 extent: 80@15). topView addSubView: templateView below: volListView. aFileList wantsOptionalButtons ifTrue: [underPane _ aFileList optionalButtonView. underPane isNil ifTrue: [pHeight _ 60] ifFalse: [ topView addSubView: underPane toRightOf: volListView. pHeight _ 60 - aFileList optionalButtonHeight]] ifFalse: [underPane _ nil. pHeight _ 60]. fileListView _ PluggableListView on: aFileList list: #fileList selected: #fileListIndex changeSelected: #fileListIndex: menu: #fileListMenu:. fileListView window: (0@0 extent: 120@pHeight). underPane isNil ifTrue: [topView addSubView: fileListView toRightOf: volListView] ifFalse: [topView addSubView: fileListView below: underPane]. fileListView controller terminateDuringSelect: true. "Pane to left may change under scrollbar" fileContentsView _ PluggableTextView on: aFileList text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted:. fileContentsView window: (0@0 extent: 200@140). topView addSubView: fileContentsView below: templateView. topView controller open! ! !FileList class methodsFor: 'instance creation' stamp: 'sw 1/24/2001 21:09'! openAsMorph "Open a morphic view of a FileList on the default directory." | dir aFileList window fileListTop | dir _ FileDirectory default. aFileList _ self new directory: dir. window _ (SystemWindow labelled: dir pathName) model: aFileList. window addMorph: ((PluggableListMorph on: aFileList list: #volumeList selected: #volumeListIndex changeSelected: #volumeListIndex: menu: #volumeMenu:) autoDeselect: false) frame: (0@0 corner: 0.3@0.2). window addMorph: (PluggableTextMorph on: aFileList text: #pattern accept: #pattern:) frame: (0@0.2 corner: 0.3@0.3). aFileList wantsOptionalButtons ifTrue: [window addMorph: aFileList optionalButtonRow frame: (0.3 @ 0 corner: 1 @ 0.08). fileListTop _ 0.08] ifFalse: [fileListTop _ 0]. window addMorph: (PluggableListMorph on: aFileList list: #fileList selected: #fileListIndex changeSelected: #fileListIndex: menu: #fileListMenu:) frame: (0.3 @ fileListTop corner: 1@0.3). window addMorph: (PluggableTextMorph on: aFileList text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted:) frame: (0@0.3 corner: 1@1). ^ window! ! !FileList class methodsFor: 'instance creation' stamp: 'sma 4/30/2000 09:31'! openEditorOn: aFileStream editString: editString "Open an editor on the given FileStream." | fileModel topView fileContentsView | Smalltalk isMorphic ifTrue: [^ (self openMorphOn: aFileStream editString: editString) openInWorld]. fileModel _ FileList new setFileStream: aFileStream. "closes the stream" topView _ StandardSystemView new. topView model: fileModel; label: aFileStream fullName; minimumSize: 180@120. topView borderWidth: 1. fileContentsView _ PluggableTextView on: fileModel text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted:. fileContentsView window: (0@0 extent: 180@120). topView addSubView: fileContentsView. editString ifNotNil: [fileContentsView editString: editString. fileContentsView hasUnacceptedEdits: true]. topView controller open. ! ! !FileList class methodsFor: 'instance creation' stamp: 'RAA 5/24/2000 19:26'! openFileDirectly | aResult | (aResult _ StandardFileMenu oldFile) ifNotNil: [self openEditorOn: (aResult directory readOnlyFileNamed: aResult name) editString: nil]! ! !FileList class methodsFor: 'instance creation' stamp: 'di 10/18/1999 22:34'! openMorphOn: aFileStream editString: editString "Open a morphic view of a FileList on the given file." | fileModel window fileContentsView | fileModel _ FileList new setFileStream: aFileStream. "closes the stream" window _ (SystemWindow labelled: aFileStream fullName) model: fileModel. window addMorph: (fileContentsView _ PluggableTextMorph on: fileModel text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted:) frame: (0@0 corner: 1@1). editString ifNotNil: [fileContentsView editString: editString. fileContentsView hasUnacceptedEdits: true]. ^ window! ! !FileList class methodsFor: 'class initialization' stamp: 'stp 12/11/1999 19:47'! initialize "FileList initialize" RecentDirs := OrderedCollection new! ! FileList subclass: #FileList2 instanceVariableNames: 'showDirsInFileList currentDirectorySelected fileSelectionBlock dirSelectionBlock optionalButtonSpecs modalView directoryChangeBlock ' classVariableNames: '' poolDictionaries: '' category: 'Tools-FileList'! !FileList2 commentStamp: '' prior: 0! Some variations on FileList that - use a hierarchical pane to show folder structure - use different pane combinations, button layouts and prefiltering for specific uses FileList2 morphicView openInWorld "an alternative to the standard FileList" FileList2 morphicViewNoFile openInWorld "useful for selecting, but not viewing" FileList2 morphicViewProjectLoader openInWorld "useful for finding and loading projects" FileList2 morphicViewImageViewer openInWorld "useful for finding and using images" FileList2 modalFolderSelector "allows the user to select a folder" ! ]style[(169 38 41 43 39 48 41 46 37 36 36 4)f1cblue;,f1,f1cblue;,f1,f1cblue;,f1,f1cblue;,f1,f1cblue;,f1,f1cblue;,f1! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/21/2000 12:10'! cancelHit modalView delete. currentDirectorySelected _ nil.! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/15/2000 23:01'! defaultBackgroundColor ^Color white! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 16:00'! dirSelectionBlock: aBlock dirSelectionBlock _ aBlock! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 13:59'! directory ^directory! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 13:26'! directory: dir "Set the path of the volume to be displayed." self okToChange ifFalse: [^ self]. self modelSleep. directory _ dir. self modelWakeUp. sortMode == nil ifTrue: [sortMode _ #date]. volList _ Array with: '[]'. directory ifNotNil: [ volList _ volList, directory pathParts. "Nesting suggestion from RvL" ]. volList _ volList withIndexCollect: [:each :i | ( String new: i-1 withAll: $ ), each]. self changed: #relabel. self changed: #volumeList. self pattern: pattern. directoryChangeBlock ifNotNil: [directoryChangeBlock value: directory].! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 8/17/2000 13:22'! directoryChangeBlock: aBlockOrNil directoryChangeBlock _ aBlockOrNil.! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 16:03'! directoryNamesFor: fullString | entries | entries _ (FileDirectory on: fullString) directoryNames. dirSelectionBlock ifNotNil:[entries _ entries select: dirSelectionBlock]. ^entries! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 13:08'! fileSelectionBlock: aBlock fileSelectionBlock _ aBlock! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 18:29'! getSelectedDirectory ^ currentDirectorySelected ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 7/13/2000 14:07'! getSelectedFile directory ifNil: [^nil]. fileName ifNil: [^nil]. ^ directory oldFileNamed: fileName ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 2/2/2001 08:30'! initialDirectoryList | dir nameToShow | ^ ((FileDirectory on: '') directoryNames collect: [ :each | FileDirectoryWrapper with: (FileDirectory on: each) name: each model: self ]), ( ServerDirectory serverNames collect: [ :n | dir _ ServerDirectory serverNamed: n. nameToShow _ n. (dir directoryWrapperClass with: dir name: nameToShow model: self) balloonText: dir realUrl ] ) ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 15:57'! initialize showDirsInFileList _ false. fileSelectionBlock _ [ :entry :myPattern | entry isDirectory ifTrue: [ showDirsInFileList ] ifFalse: [ myPattern = '*' or: [myPattern match: entry name] ] ] fixTemps. dirSelectionBlock _ [ :dirName | true].! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 10:40'! labelString ^ (directory ifNil: [^'[]']) pathName contractTo: 50! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'sma 11/11/2000 18:10'! listForPattern: pat "Make the list be those file names which match the pattern." | sizePad newList | directory ifNil: [^#()]. newList _ directory entries select: [:entry | fileSelectionBlock value: entry value: pat]. newList _ newList asSortedCollection: self sortBlock. sizePad _ (newList inject: 0 into: [:mx :entry | mx max: (entry at: 5)]) asStringWithCommas size - 1. newList _ newList collect: [ :e | self fileNameFormattedFrom: e sizePad: sizePad ]. ^ newList asArray! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/21/2000 12:06'! modalView: aSystemWindowOrSuch modalView _ aSystemWindowOrSuch! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 18:31'! morphicDirectoryTreePane ^(SimpleHierarchicalListMorph on: self list: #initialDirectoryList selected: #getSelectedDirectory changeSelected: #setSelectedDirectoryTo: menu: nil keystroke: nil) autoDeselect: false ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 10:53'! morphicFileContentsPane ^PluggableTextMorph on: self text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted: ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 10:54'! morphicFileListPane ^PluggableListMorph on: self list: #fileList selected: #fileListIndex changeSelected: #fileListIndex: menu: #fileListMenu: ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 10:57'! morphicPatternPane ^PluggableTextMorph on: self text: #pattern accept: #pattern: ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/21/2000 12:06'! okHit modalView delete.! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 7/14/2000 14:32'! okHitForProjectLoader | areaOfProgress | areaOfProgress _ modalView firstSubmorph. [ areaOfProgress setProperty: #deleteOnProgressCompletion toValue: modalView. self openProjectFromFile. modalView delete. "probably won't get here" ] on: ProgressTargetRequestNotification do: [ :ex | ex resume: areaOfProgress]. ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 13:00'! optionalButtonSpecs ^optionalButtonSpecs ifNil: [super optionalButtonSpecs]! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 13:01'! optionalButtonSpecs: anArray optionalButtonSpecs _ anArray! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 10:48'! postOpen directory ifNotNil: [ self changed: #(openPath) , directory pathParts. ]. ! ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 18:30'! setSelectedDirectoryTo: aFileDirectoryWrapper currentDirectorySelected _ aFileDirectoryWrapper. self directory: aFileDirectoryWrapper withoutListWrapper. brevityState := #FileList. "self addPath: path." self changed: #fileList. self changed: #contents. self changed: #getSelectedDirectory.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileList2 class instanceVariableNames: ''! !FileList2 class methodsFor: 'as yet unclassified' stamp: 'ar 10/10/2000 15:59'! hideSqueakletDirectoryBlock ^[:dirName| (dirName sameAs: 'Squeaklets') not]! ! !FileList2 class methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 13:10'! new ^super new initialize! ! !FileList2 class methodsFor: 'as yet unclassified' stamp: 'RAA 7/21/2000 11:40'! projectOnlySelectionBlock ^[ :entry :myPattern | entry isDirectory ifTrue: [ false ] ifFalse: [ #('*.pr' '*.pr.gz' '*.project') anySatisfy: [ :each | each match: entry name] ] ] fixTemps! ! !FileList2 class methodsFor: 'as yet unclassified' stamp: 'RAA 7/24/2000 19:13'! selectionBlockForSuffixes: anArray ^[ :entry :myPattern | entry isDirectory ifTrue: [ false ] ifFalse: [ anArray anySatisfy: [ :each | each match: entry name] ] ] fixTemps! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 11/14/2000 11:05'! blueButtonText: aString textColor: textColor inWindow: window ^(window fancyText: aString ofSize: 15 color: textColor) setProperty: #buttonText toValue: aString; hResizing: #rigid; extent: 100@20; layoutInset: 4; borderWidth: 0; useRoundedCorners ! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 7/23/2000 16:36'! blueRamp1 ^{ 0.0->(Color r: 0.516 g: 0.645 b: 1.0). 1.0->(Color r: 0.742 g: 0.871 b: 1.0) }! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 7/23/2000 16:38'! blueRamp2 ^{ 0.0->(Color r: 0.516 g: 0.645 b: 1.0). 1.0->(TranslucentColor r: 0.645 g: 0.968 b: 1.0 alpha: 0.56078431372549) }! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 7/23/2000 16:37'! blueRamp3 ^{ 0.0->(Color r: 0.742 g: 0.871 b: 1.0). 1.0->(Color r: 0.516 g: 0.645 b: 1.0). }! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 8/17/2000 13:53'! enableTypeButtons: typeButtons info: fileTypeInfo forDir: aDirectory | foundSuffixes fileSuffixes firstEnabled enableIt | firstEnabled _ nil. foundSuffixes _ aDirectory fileNames collect: [ :each | (each findTokens: '.') last asLowercase]. foundSuffixes _ foundSuffixes asSet. fileTypeInfo with: typeButtons do: [ :info :button | fileSuffixes _ info second. enableIt _ fileSuffixes anySatisfy: [ :patt | foundSuffixes includes: patt]. button setProperty: #enabled toValue: enableIt. enableIt ifTrue: [firstEnabled ifNil: [firstEnabled _ button]]. ]. firstEnabled ifNotNil: [^firstEnabled mouseUp: nil]. typeButtons do: [ :each | each color: Color gray]. ! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 7/24/2000 19:08'! endingSpecs ^#( ('Art' ('bmp' 'gif' 'jpg' 'form' 'png') (('open image in a window' openImageInWindow 'View') ('read image into ImageImports' importImage 'Import') ('open image as background' openAsBackground 'World')) ) ('Morphs' ('morph' 'morphs' 'sp') (('load as morph' openMorphFromFile 'Morph') ('load as project' openProjectFromFile 'Project')) ) ('Projects' ('extseg' 'project' 'pr') (('load as project' openProjectFromFile 'Load'))) ('Books' ('bo') (('load as book' openBookFromFile 'Open'))) ('Music' ('mid') (('play midi file' playMidiFile 'Play'))) ('Movies' ('movie') (('open as movie' openAsMovie 'Open'))) "('Code' ('st' 'cs') (('fileIn' fileInSelection) ('file into new change set' fileIntoNewChangeSet) ('browse changes' browseChanges) ('browse code' browseFile) ('remove line feeds' removeLinefeeds) ('broadcast as update' putUpdate)) )" ('Flash' ('swf') (('open as Flash' openAsFlash 'Open'))) ('TrueType' ('ttf') (('open true type font' openAsTTF 'Open'))) ('3ds' ('3ds') (('Open 3DS file' open3DSFile' Open'))) ('Tape' ('tape') (('open for playback' openTapeFromFile 'Open'))) ('Wonderland' ('wrl') (('open in Wonderland' openVRMLFile 'Open'))) ('HTML' ('htm' 'html') (('open in browser' openInBrowser 'Open'))) )! ! !FileList2 class methodsFor: 'blue ui' stamp: 'ar 11/9/2000 22:46'! morphicViewGeneralLoaderInWorld: aWorld " FileList2 morphicViewGeneralLoaderInWorld: self currentWorld " | window aFileList buttons treePane textColor1 fileListPane pane2a pane2b fileTypeInfo fileTypeButtons fileTypeRow actionRow | fileTypeInfo _ self endingSpecs. window _ AlignmentMorphBob1 newColumn. window hResizing: #shrinkWrap; vResizing: #shrinkWrap. textColor1 _ Color r: 0.742 g: 0.839 b: 1.0. aFileList _ self new directory: FileDirectory default. aFileList fileSelectionBlock: self projectOnlySelectionBlock; modalView: window. window setProperty: #FileList toValue: aFileList; wrapCentering: #center; cellPositioning: #topCenter; borderWidth: 4; borderColor: (Color r: 0.355 g: 0.516 b: 1.0); useRoundedCorners. fileTypeButtons _ fileTypeInfo collect: [ :each | (self blueButtonText: each first textColor: Color gray inWindow: window) setProperty: #enabled toValue: true; hResizing: #shrinkWrap ]. buttons _ #('OK' 'Cancel') collect: [ :each | self blueButtonText: each textColor: textColor1 inWindow: window ]. treePane _ aFileList morphicDirectoryTreePane extent: 250@300; retractable: false; borderWidth: 0. fileListPane _ aFileList morphicFileListPane extent: 350@300; retractable: false; borderWidth: 0. window addARow: {window fancyText: 'Find...' ofSize: 21 color: textColor1}. fileTypeRow _ window addARowCentered: fileTypeButtons. actionRow _ window addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second }. window addARow: { (window inAColumn: {(pane2a _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 6}) layoutInset: 10. (window inAColumn: {(pane2b _ window inARow: {window inAColumn: {fileListPane}}) useRoundedCorners; layoutInset: 6}) layoutInset: 10. }. window fullBounds. window fillWithRamp: self blueRamp1 oriented: 0.65. pane2a fillWithRamp: self blueRamp3 oriented: (0.7 @ 0.35). pane2b fillWithRamp: self blueRamp3 oriented: (0.7 @ 0.35). buttons do: [ :each | each fillWithRamp: self blueRamp2 oriented: (0.75 @ 0). ]. fileTypeButtons do: [ :each | each on: #mouseUp send: #value:value: to: [ :evt :morph | self update: actionRow in: window fileTypeRow: fileTypeRow morphUp: morph. ] fixTemps ]. buttons first on: #mouseUp send: #okHit to: aFileList. buttons second on: #mouseUp send: #cancelHit to: aFileList. aFileList postOpen. window position: aWorld topLeft + (aWorld extent - window extent // 2). aFileList directoryChangeBlock: [ :newDir | self enableTypeButtons: fileTypeButtons info: fileTypeInfo forDir: newDir ] fixTemps. aFileList directory: aFileList directory. ^ window openInWorld: aWorld.! ! !FileList2 class methodsFor: 'blue ui' stamp: 'RAA 7/15/2000 19:21'! morphicViewProjectLoader2InWorld: aWorld ^self morphicViewProjectLoader2InWorld: aWorld reallyLoad: true! ! !FileList2 class methodsFor: 'blue ui' stamp: 'ar 11/9/2000 22:46'! morphicViewProjectLoader2InWorld: aWorld reallyLoad: aBoolean | window aFileList buttons treePane textColor1 fileListPane pane2a pane2b | window _ AlignmentMorphBob1 newColumn. window hResizing: #shrinkWrap; vResizing: #shrinkWrap. textColor1 _ Color r: 0.742 g: 0.839 b: 1.0. aFileList _ self new directory: FileDirectory default. aFileList optionalButtonSpecs: self specsForProjectLoader; fileSelectionBlock: self projectOnlySelectionBlock; "dirSelectionBlock: self hideSqueakletDirectoryBlock;" modalView: window. window setProperty: #FileList toValue: aFileList; wrapCentering: #center; cellPositioning: #topCenter; borderWidth: 4; borderColor: (Color r: 0.355 g: 0.516 b: 1.0); useRoundedCorners. buttons _ #('OK' 'Cancel') collect: [ :each | self blueButtonText: each textColor: textColor1 inWindow: window ]. treePane _ aFileList morphicDirectoryTreePane extent: 250@300; retractable: false; borderWidth: 0. fileListPane _ aFileList morphicFileListPane extent: 350@300; retractable: false; borderWidth: 0. window addARow: { window fancyText: 'Load A Project' ofSize: 21 color: textColor1 }; addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second }; addARow: { window fancyText: 'Please select a project' ofSize: 21 color: Color blue }; addARow: { (window inAColumn: {(pane2a _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 6}) layoutInset: 10. (window inAColumn: {(pane2b _ window inARow: {window inAColumn: {fileListPane}}) useRoundedCorners; layoutInset: 6}) layoutInset: 10. }. window fullBounds. window fillWithRamp: self blueRamp1 oriented: 0.65. pane2a fillWithRamp: self blueRamp3 oriented: (0.7 @ 0.35). pane2b fillWithRamp: self blueRamp3 oriented: (0.7 @ 0.35). buttons do: [ :each | each fillWithRamp: self blueRamp2 oriented: (0.75 @ 0). ]. buttons first on: #mouseUp send: (aBoolean ifTrue: [#okHitForProjectLoader] ifFalse: [#okHit]) to: aFileList. buttons second on: #mouseUp send: #cancelHit to: aFileList. aFileList postOpen. window position: aWorld topLeft + (aWorld extent - window extent // 2). ^ window openInWorld: aWorld.! ! !FileList2 class methodsFor: 'blue ui' stamp: 'ar 11/9/2000 22:46'! morphicViewProjectSaverFor: aProject " (FileList2 morphicViewProjectSaverFor: Project current) openInWorld " | window aFileList buttons treePane pane2 textColor1 | textColor1 _ Color r: 0.742 g: 0.839 b: 1.0. aFileList _ self new directory: FileDirectory default. aFileList dirSelectionBlock: self hideSqueakletDirectoryBlock. window _ AlignmentMorphBob1 newColumn. window hResizing: #shrinkWrap; vResizing: #shrinkWrap. aFileList modalView: window. window setProperty: #FileList toValue: aFileList; wrapCentering: #center; cellPositioning: #topCenter; borderWidth: 4; borderColor: (Color r: 0.355 g: 0.516 b: 1.0); useRoundedCorners. buttons _ #( ('OK' okHit) ('Cancel' cancelHit) ) collect: [ :each | (self blueButtonText: each first textColor: textColor1 inWindow: window) on: #mouseUp send: each second to: aFileList ]. treePane _ aFileList morphicDirectoryTreePane extent: 350@300; retractable: false; borderWidth: 0. window addARowCentered: { window fancyText: 'Publish This Project' ofSize: 21 color: textColor1 }; addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second }; addARowCentered: { (window inAColumn: {(ProjectViewMorph on: aProject) lock}) layoutInset: 4}; addARowCentered: { window fancyText: 'Please select a folder' ofSize: 21 color: Color blue }; addARow: { ( window inAColumn: { (pane2 _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 6 } ) layoutInset: 10 }. window fullBounds. window fillWithRamp: self blueRamp1 oriented: 0.65. pane2 fillWithRamp: self blueRamp3 oriented: (0.7 @ 0.35). buttons do: [ :each | each fillWithRamp: self blueRamp2 oriented: (0.75 @ 0). ]. window setProperty: #morphicLayerNumber toValue: 11. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'button specs' stamp: 'RAA 6/21/2000 12:03'! specsForFolderSelector ^ #( ('OK' okHit sortingByName 'use the currently selected directory') ('Cancel' cancelHit sortingByDate 'cancel this request') ) ! ! !FileList2 class methodsFor: 'button specs' stamp: 'RAA 6/17/2000 18:47'! specsForImageViewer ^ #( ('Name' sortByName sortingByName 'sort entries by name') ('Date' sortByDate sortingByDate 'sort entries by date') ('Size' sortBySize sortingBySize 'sort entries by size') ('View' openImageInWindow none 'open image in a window') ('Import' importImage none 'read image into ImageImports') ('Bgnd' openAsBackground none 'open image as background') ) ! ! !FileList2 class methodsFor: 'button specs' stamp: 'RAA 6/16/2000 13:03'! specsForProjectLoader ^ #( ('Name' sortByName sortingByName 'sort entries by name') ('Date' sortByDate sortingByDate 'sort entries by date') ('Size' sortBySize sortingBySize 'sort entries by size') ('Load' openProjectFromFile none 'load the selected project') ) ! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'RAA 6/23/2000 16:30'! modalFolderSelector | window fileModel | window _ self morphicViewFolderSelector. fileModel _ window model. window openInWorld: self currentWorld extent: 300@400. [window world notNil] whileTrue: [ window outermostWorldMorph doOneCycleNow. ]. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'RAA 6/30/2000 11:01'! modalFolderSelectorForProject: aProject " FileList2 modalFolderSelectorForProject: Project current " | window fileModel w | window _ FileList2 morphicViewProjectSaverFor: aProject. fileModel _ window valueOfProperty: #FileList. w _ self currentWorld. window position: w topLeft + (w extent - window extent // 2). window openInWorld: w. [window world notNil] whileTrue: [ window outermostWorldMorph doOneCycleNow. ]. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'RAA 7/15/2000 19:22'! modalFolderSelectorForProjectLoad | window fileModel w | window _ self morphicViewProjectLoader2InWorld: self currentWorld reallyLoad: false. fileModel _ window valueOfProperty: #FileList. w _ self currentWorld. window position: w topLeft + (w extent - window extent // 2). window openInWorld: w. [window world notNil] whileTrue: [ window outermostWorldMorph doOneCycleNow. ]. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'utility' stamp: 'RAA 1/8/2001 21:23'! addFullPanesTo: window from: aCollection | frame | aCollection do: [ :each | frame _ LayoutFrame fractions: each second offsets: each third. window addMorph: each first fullFrame: frame. ]! ! !FileList2 class methodsFor: 'utility' stamp: 'RAA 6/17/2000 18:35'! addPanesTo: window from: aCollection aCollection do: [ :each | window addMorph: each first frame: each second. ]! ! !FileList2 class methodsFor: 'utility' stamp: 'ar 11/9/2000 21:26'! textRow: aString ^AlignmentMorph newRow wrapCentering: #center; cellPositioning: #leftCenter; layoutInset: 0; addMorph: ( AlignmentMorph newColumn wrapCentering: #center; cellPositioning: #topCenter; color: Color transparent; vResizing: #shrinkWrap; layoutInset: 0; addMorph: ( AlignmentMorph newRow wrapCentering: #center; cellPositioning: #leftCenter; color: Color transparent; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 0; addMorph: ((StringMorph contents: aString) color: Color blue; lock) ) )! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 1/8/2001 21:29'! morphicView | dir aFileList window fileListBottom midLine fileListTopOffset | dir _ FileDirectory default. aFileList _ self new directory: dir. window _ (SystemWindow labelled: dir pathName) model: aFileList. fileListTopOffset _ 25. fileListBottom _ 0.4. midLine _ 0.4. self addFullPanesTo: window from: { {aFileList morphicPatternPane. 0@0 corner: 0.3@0. 0@0 corner: 0@fileListTopOffset}. {aFileList optionalButtonRow. 0.3 @ 0 corner: 1 @ 0. 0@0 corner: 0@fileListTopOffset}. {aFileList morphicDirectoryTreePane. 0@0 corner: midLine@fileListBottom. 0@fileListTopOffset corner: 0@0}. {aFileList morphicFileListPane. midLine @ 0 corner: 1@fileListBottom. 0@fileListTopOffset corner: 0@0}. {aFileList morphicFileContentsPane. 0@fileListBottom corner: 1@1. nil}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 1/8/2001 21:34'! morphicViewFolderSelector | dir aFileList window fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. aFileList optionalButtonSpecs: self specsForFolderSelector. window _ (SystemWindow labelled: dir pathName) model: aFileList. aFileList modalView: window. fixedSize _ 25. self addFullPanesTo: window from: { {self textRow: 'Please select a folder'. 0 @ 0 corner: 1 @ 0. 0@0 corner: 0@fixedSize}. {aFileList optionalButtonRow. 0 @ 0 corner: 1 @ 0. 0@fixedSize corner: 0@(fixedSize * 2)}. {aFileList morphicDirectoryTreePane. 0@0 corner: 1@1. 0@(fixedSize * 2) corner: 0@0}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 1/8/2001 21:36'! morphicViewImageViewer | dir aFileList window midLine fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. aFileList optionalButtonSpecs: self specsForImageViewer. aFileList fileSelectionBlock: [ :entry :myPattern | entry isDirectory ifTrue: [ false ] ifFalse: [ #('bmp' 'gif' 'jpg' 'form' suffix = 'png') includes: (aFileList getSuffix: entry name asLowercase) ] ] fixTemps. window _ (SystemWindow labelled: dir pathName) model: aFileList. fixedSize _ 25. midLine _ 0.4. self addFullPanesTo: window from: { {aFileList optionalButtonRow. 0 @ 0 corner: 1 @ 0. 0@0 corner: 0@fixedSize}. {aFileList morphicDirectoryTreePane. 0@0 corner: midLine@1. 0@fixedSize corner: 0@0}. {aFileList morphicFileListPane. midLine @ 0 corner: 1@1. 0@fixedSize corner: 0@0}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 1/8/2001 21:39'! morphicViewNoFile | dir aFileList window midLine fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. window _ (SystemWindow labelled: dir pathName) model: aFileList. fixedSize _ 25. midLine _ 0.4. self addFullPanesTo: window from: { {aFileList morphicPatternPane. 0@0 corner: 0.3@0. 0@0 corner: 0@fixedSize}. {aFileList optionalButtonRow. 0.3 @ 0 corner: 1@0. 0@0 corner: 0@fixedSize}. {aFileList morphicDirectoryTreePane. 0@0 corner: midLine@1. 0@fixedSize corner: 0@0}. {aFileList morphicFileListPane. midLine @ 0 corner: 1@1. 0@fixedSize corner: 0@0}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 1/8/2001 21:39'! morphicViewProjectLoader | dir aFileList window midLine fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. aFileList optionalButtonSpecs: self specsForProjectLoader. aFileList fileSelectionBlock: self projectOnlySelectionBlock. window _ (SystemWindow labelled: dir pathName) model: aFileList. fixedSize _ 25. midLine _ 0.4. self addFullPanesTo: window from: { {aFileList optionalButtonRow. 0 @ 0 corner: 1 @ 0. 0@0 corner: 0@fixedSize}. {aFileList morphicDirectoryTreePane. 0@0 corner: midLine@1. 0@fixedSize corner: 0@0}. {aFileList morphicFileListPane. midLine @ 0 corner: 1@1. 0@fixedSize corner: 0@0}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 11/14/2000 11:05'! update: actionRow in: window fileTypeRow: fileTypeRow morphUp: morph | fileTypeInfo info2 buttons textColor1 fileSuffixes fileActions aFileList fileTypeString | (morph valueOfProperty: #enabled) ifFalse: [^self]. fileTypeRow submorphsDo: [ :sub | sub color: ( sub == morph ifTrue: [Color white] ifFalse: [(sub valueOfProperty: #enabled) ifTrue: [Color transparent] ifFalse: [Color gray]] ). ]. fileTypeString _ morph valueOfProperty: #buttonText. aFileList _ window valueOfProperty: #FileList. textColor1 _ Color r: 0.742 g: 0.839 b: 1.0. actionRow removeAllMorphs. fileTypeInfo _ self endingSpecs. info2 _ fileTypeInfo detect: [ :each | each first = fileTypeString] ifNone: [self halt]. fileSuffixes _ info2 second. fileActions _ info2 third. buttons _ fileActions, #(('Cancel this search' cancelHit 'Cancel')) collect: [ :each | (self blueButtonText: each third textColor: textColor1 inWindow: window) setBalloonText: each first; on: #mouseUp send: each second to: aFileList. ]. buttons do: [ :each | actionRow addMorphBack: each]. window fullBounds. buttons do: [ :each | each fillWithRamp: self blueRamp2 oriented: (0.75 @ 0). ]. aFileList fileSelectionBlock: ( self selectionBlockForSuffixes: (fileSuffixes collect: [ :each | '*.',each]) ). aFileList updateFileList. ! ! Object subclass: #FilePackage instanceVariableNames: 'fullName sourceSystem classes doIts classOrder ' classVariableNames: '' poolDictionaries: '' category: 'Tools-File Contents Browser'! !FilePackage methodsFor: 'accessing'! classAt: className ^self classes at: className! ! !FilePackage methodsFor: 'accessing'! classes ^classes! ! !FilePackage methodsFor: 'accessing' stamp: 'pnm 8/23/2000 17:10'! fullName: aString fullName := aString! ! !FilePackage methodsFor: 'accessing'! fullPackageName ^fullName! ! !FilePackage methodsFor: 'accessing'! packageInfo ^String streamContents:[:s| s nextPutAll:'Package: '. s nextPutAll: self fullPackageName; cr; cr. sourceSystem isEmpty ifFalse:[ s nextPutAll: sourceSystem; cr; cr]. doIts isEmpty ifFalse:[ s nextPutAll:'Unresolvable doIts:'; cr; cr. doIts do:[:chgRec| s nextPut:$!!; nextPutAll: chgRec string; nextPut: $!!; cr]]].! ! !FilePackage methodsFor: 'accessing' stamp: 'pnm 8/23/2000 17:12'! packageName ^FileDirectory localNameFor: self fullPackageName! ! !FilePackage methodsFor: 'accessing'! removeClass: aPseudoClass (self classes removeKey: aPseudoClass name). classOrder copy do:[:cls| cls name = aPseudoClass name ifTrue:[ classOrder remove: cls]. ].! ! !FilePackage methodsFor: 'accessing'! renameClass: aPseudoClass to: newName | oldName | oldName := aPseudoClass name. self classes removeKey: oldName. self classes at: newName put: aPseudoClass. aPseudoClass renameTo: newName.! ! !FilePackage methodsFor: 'initialize' stamp: 'pnm 8/23/2000 17:13'! fromFileNamed: aName | stream | fullName := aName. stream := FileStream readOnlyFileNamed: aName. self fileInFrom: stream.! ! !FilePackage methodsFor: 'initialize' stamp: 'pnm 8/23/2000 14:48'! initialize classes := Dictionary new. classOrder := OrderedCollection new. sourceSystem := ''. doIts := OrderedCollection new.! ! !FilePackage methodsFor: 'private'! classDefinition: string with: chgRec | tokens theClass | tokens := Scanner new scanTokens: string. tokens size = 11 ifFalse:[^doIts add: chgRec]. theClass := self getClass: (tokens at: 3). theClass definition: string. classOrder add: theClass.! ! !FilePackage methodsFor: 'private'! getClass: className | pseudoClass | (classes includesKey: className) ifTrue:[ ^classes at: className. ]. pseudoClass := PseudoClass new. pseudoClass name: className. classes at: className put: pseudoClass. ^pseudoClass.! ! !FilePackage methodsFor: 'private'! metaClassDefinition: string with: chgRec | tokens theClass | tokens := Scanner new scanTokens: string. theClass := self getClass: (tokens at: 1). theClass metaClass definition: string. classOrder add: theClass metaClass.! ! !FilePackage methodsFor: 'private'! msgClassComment: string with: chgRec | tokens theClass | tokens := Scanner new scanTokens: string. (tokens size = 3 and:[(tokens at: 3) class == String]) ifTrue:[ theClass := self getClass: tokens first. ^theClass commentString: tokens last]. (tokens size = 4 and:[(tokens at: 3) asString = 'class' and:[(tokens at: 4) class == String]]) ifTrue:[ theClass := self getClass: tokens first. theClass metaClass commentString: tokens last]. ! ! !FilePackage methodsFor: 'private'! possibleSystemSource: chgRec | tokens | sourceSystem isEmpty ifTrue:[ tokens := Scanner new scanTokens: chgRec string. (tokens size = 1 and:[tokens first class == String]) ifTrue:[ sourceSystem := tokens first. ^self]]. doIts add: chgRec.! ! !FilePackage methodsFor: 'private'! removedMethod: string with: chgRec | class tokens | tokens := Scanner new scanTokens: string. (tokens size = 3 and:[(tokens at: 2) == #removeSelector: ]) ifTrue:[ class := self getClass: (tokens at: 1). ^class removeSelector: (tokens at: 3). ]. (tokens size = 4 and:[(tokens at: 2) == #class and:[(tokens at: 3) == #removeSelector:]]) ifTrue:[ class := self getClass: (tokens at: 1). ^class metaClass removeSelector: (tokens at: 4). ]. doIts add: chgRec! ! !FilePackage methodsFor: 'private'! sampleMethod " In an existing method there are always a number of changes. Other stuff will be deleted Or even better, some things may be just modified. "! ! !FilePackage methodsFor: 'change record types'! classComment: chgRec (self getClass: chgRec methodClassName) classComment: chgRec! ! !FilePackage methodsFor: 'change record types'! doIt: chgRec | string | string := chgRec string. ('*ubclass:*instanceVariableNames:*classVariableNames:*poolDictionaries:*category:*' match: string) ifTrue:[^self classDefinition: string with: chgRec]. ('* class*instanceVariableNames:*' match: string) ifTrue:[^self metaClassDefinition: string with: chgRec]. ('* removeSelector: *' match: string) ifTrue:[^self removedMethod: string with: chgRec]. ('* comment:*' match: string) ifTrue:[^self msgClassComment: string with: chgRec]. ('* initialize' match: string) ifTrue:[^self]. "Initialization is done based on class>>initialize" ('''From *' match: string) ifTrue:[^self possibleSystemSource: chgRec]. doIts add: chgRec.! ! !FilePackage methodsFor: 'change record types'! method: chgRec (self getClass: chgRec methodClassName) methodChange: chgRec! ! !FilePackage methodsFor: 'change record types'! preamble: chgRec self doIt: chgRec! ! !FilePackage methodsFor: 'fileIn/fileOut' stamp: 'wod 4/15/98 15:57'! askForDoits | menu choice choices | choices := #('do not process' 'at the beginning' 'at the end' 'cancel'). menu _ SelectionMenu selections: choices. choice := nil. [choices includes: choice] whileFalse: [ choice _ menu startUpWithCaption: 'The package contains unprocessed doIts. When would like to process those?']. ^choices indexOf: choice! ! !FilePackage methodsFor: 'fileIn/fileOut' stamp: 'wod 4/15/98 16:00'! fileIn | doitsMark | doitsMark := 1. doIts isEmpty ifFalse:[doitsMark := self askForDoits]. doitsMark = 4 ifTrue: [^nil]. doitsMark = 2 ifTrue:[self fileInDoits]. classOrder do:[:cls| cls fileInDefinition. ]. classes do:[:cls| Transcript cr; show:'Filing in ', cls name. cls fileInMethods. cls hasMetaclass ifTrue:[cls metaClass fileInMethods]. ]. doitsMark = 3 ifTrue:[self fileInDoits].! ! !FilePackage methodsFor: 'fileIn/fileOut'! fileInDoits doIts do:[:chgRec| chgRec fileIn].! ! !FilePackage methodsFor: 'fileIn/fileOut' stamp: 'tk 1/8/1999 08:00'! fileOut | fileName stream | fileName := FillInTheBlank request: 'Enter the file name' initialAnswer:''. stream := FileStream newFileNamed: fileName. sourceSystem isEmpty ifFalse:[ stream nextChunkPut: sourceSystem printString;cr ]. self fileOutOn: stream. stream cr; cr. self classes do:[:cls| cls needsInitialize ifTrue:[ stream cr; nextChunkPut: cls name,' initialize']]. stream cr. stream close. DeepCopier new checkVariables. ! ! !FilePackage methodsFor: 'fileIn/fileOut'! fileOutDoits: aStream doIts do:[:chgRec| chgRec fileOutOn: aStream].! ! !FilePackage methodsFor: 'fileIn/fileOut' stamp: 'wod 4/15/98 15:59'! fileOutOn: aStream | doitsMark | doitsMark := 1. doIts isEmpty ifFalse:[doitsMark := self askForDoits]. doitsMark = 4 ifTrue: [^nil]. doitsMark = 2 ifTrue:[self fileOutDoits: aStream]. classOrder do:[:cls| cls fileOutDefinitionOn: aStream. ]. classes do:[:cls| cls fileOutMethodsOn: aStream. cls hasMetaclass ifTrue:[cls metaClass fileOutMethodsOn: aStream]. ]. doitsMark = 3 ifTrue:[self fileOutDoits: aStream].! ! !FilePackage methodsFor: 'reading' stamp: 'pnm 8/23/2000 17:24'! fileInFrom: aStream | chgRec changes | changes := (ChangeList new scanFile: aStream from: 0 to: aStream size) changeList. aStream close. ('Processing ', self packageName) displayProgressAt: Sensor cursorPoint from: 1 to: changes size during:[:bar| 1 to: changes size do:[:i| bar value: i. chgRec := changes at: i. self perform: (chgRec type copyWith: $:) asSymbol with: chgRec. ]. ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FilePackage class instanceVariableNames: ''! !FilePackage class methodsFor: 'instance creation'! fromFileNamed: aName ^self new fromFileNamed: aName! ! !FilePackage class methodsFor: 'instance creation' stamp: 'pnm 8/23/2000 17:16'! new ^super new initialize! ! InterpreterPlugin subclass: #FilePlugin instanceVariableNames: '' classVariableNames: 'DirBadPath DirEntryFound DirNoMoreEntries ' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FilePlugin methodsFor: 'initialize-release' stamp: 'ar 5/12/2000 16:53'! initialiseModule self export: true. ^self cCode: 'sqFileInit()' inSmalltalk:[true]! ! !FilePlugin methodsFor: 'initialize-release' stamp: 'ar 5/12/2000 16:54'! shutdownModule self export: true. ^self cCode: 'sqFileShutdown()' inSmalltalk:[true]! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/12/2000 01:24'! fileRecordSize "Return the size of a Smalltalk file record in bytes." self static: false. ^ self cCode: 'sizeof(SQFile)'.! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/12/2000 01:24'! fileValueOf: objectPointer "Return a pointer to the first byte of of the file record within the given Smalltalk object, or nil if objectPointer is not a file record." self returnTypeC: 'SQFile *'. self static: false. (((interpreterProxy isBytes: objectPointer) and: [(interpreterProxy byteSizeOf: objectPointer) = self fileRecordSize])) ifFalse:[interpreterProxy primitiveFail. ^nil]. ^interpreterProxy firstIndexableField: objectPointer! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:17'! primitiveFileAtEnd | file atEnd | self export: true. self var: 'file' declareC: 'SQFile *file'. file _ self fileValueOf: (interpreterProxy stackValue: 0). interpreterProxy failed ifFalse:[atEnd _ self sqFileAtEnd: file ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 2. "rcvr, file" interpreterProxy pushBool: atEnd. ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:17'! primitiveFileClose | file | self export: true. self var: 'file' declareC: 'SQFile *file'. file _ self fileValueOf: (interpreterProxy stackValue: 0). interpreterProxy failed ifFalse: [ self sqFileClose: file ]. interpreterProxy failed ifFalse: [ interpreterProxy pop: 1 "pop file; leave rcvr on stack" ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:32'! primitiveFileDelete | namePointer nameIndex nameSize | self var: 'nameIndex' type: 'char *'. self export: true. namePointer _ interpreterProxy stackValue: 0. (interpreterProxy isBytes: namePointer) ifFalse:[^interpreterProxy primitiveFail]. nameIndex _ interpreterProxy firstIndexableField: namePointer. nameSize _ interpreterProxy byteSizeOf: namePointer. self sqFileDeleteName: (self cCoerce: nameIndex to: 'int') Size: nameSize. interpreterProxy failed ifFalse:[interpreterProxy pop: 1. "pop name, leave rcvr on stack" ]. ! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:17'! primitiveFileGetPosition | file position | self var: 'file' declareC: 'SQFile *file'. self export: true. file _ self fileValueOf: (interpreterProxy stackValue: 0). interpreterProxy failed ifFalse: [position _ self sqFileGetPosition: file]. interpreterProxy failed ifFalse: [ interpreterProxy pop: 2. interpreterProxy push: (interpreterProxy positive32BitIntegerFor: position)].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:33'! primitiveFileOpen | writeFlag namePointer filePointer file nameIndex nameSize | self var: 'file' declareC: 'SQFile *file'. self var: 'nameIndex' type:'char *'. self export: true. writeFlag _ interpreterProxy booleanValueOf: (interpreterProxy stackValue: 0). namePointer _ interpreterProxy stackValue: 1. (interpreterProxy isBytes: namePointer) ifFalse:[^interpreterProxy primitiveFail]. filePointer _ interpreterProxy instantiateClass: (interpreterProxy classByteArray) indexableSize: self fileRecordSize. file _ self fileValueOf: filePointer. nameIndex _ interpreterProxy firstIndexableField: namePointer. nameSize _ interpreterProxy byteSizeOf: namePointer. interpreterProxy failed ifFalse:[ self cCode: 'sqFileOpen(file, (int)nameIndex, nameSize, writeFlag)'. ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 3. "rcvr, name, writeFlag" interpreterProxy push: filePointer. ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:33'! primitiveFileRead | count startIndex array file byteSize arrayIndex bytesRead | self var: 'file' declareC: 'SQFile *file'. self var: 'arrayIndex' type:'char *'. self export: true. count _ interpreterProxy stackIntegerValue: 0. startIndex _ interpreterProxy stackIntegerValue: 1. array _ interpreterProxy stackValue: 2. file _ self fileValueOf: (interpreterProxy stackValue: 3). "buffer can be any indexable words or bytes object except CompiledMethod" (interpreterProxy isWordsOrBytes: array) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: array) ifTrue: [ byteSize _ 4 ] ifFalse: [ byteSize _ 1 ]. ((startIndex >= 1) and: [(startIndex + count - 1) <= (interpreterProxy slotSizeOf: array)]) ifFalse:[^interpreterProxy primitiveFail]. arrayIndex _ interpreterProxy firstIndexableField: array. "Note: adjust startIndex for zero-origin indexing" bytesRead _ self sqFile: file Read: (count * byteSize) Into: (self cCoerce: arrayIndex to: 'int') At: ((startIndex - 1) * byteSize). interpreterProxy failed ifFalse:[ interpreterProxy pop: 5. "pop rcvr, file, array, startIndex, count" interpreterProxy pushInteger: bytesRead // byteSize. "push # of elements read" ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:34'! primitiveFileRename | oldNamePointer newNamePointer oldNameIndex oldNameSize newNameIndex newNameSize | self var: 'oldNameIndex' type: 'char *'. self var: 'newNameIndex' type: 'char *'. self export: true. newNamePointer _ interpreterProxy stackValue: 0. oldNamePointer _ interpreterProxy stackValue: 1. ((interpreterProxy isBytes: newNamePointer) and:[ (interpreterProxy isBytes: oldNamePointer)]) ifFalse:[^interpreterProxy primitiveFail]. newNameIndex _ interpreterProxy firstIndexableField: newNamePointer. newNameSize _ interpreterProxy byteSizeOf: newNamePointer. oldNameIndex _ interpreterProxy firstIndexableField: oldNamePointer. oldNameSize _ interpreterProxy byteSizeOf: oldNamePointer. self sqFileRenameOld: (self cCoerce: oldNameIndex to: 'int') Size: oldNameSize New: (self cCoerce: newNameIndex to: 'int') Size: newNameSize. interpreterProxy failed ifFalse:[ interpreterProxy pop: 2. "pop new and old names, leave rcvr on stack" ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:17'! primitiveFileSetPosition | newPosition file | self var: 'file' declareC: 'SQFile *file'. self export: true. newPosition _ interpreterProxy positive32BitValueOf: (interpreterProxy stackValue: 0). file _ self fileValueOf: (interpreterProxy stackValue: 1). interpreterProxy failed ifFalse:[ self sqFile: file SetPosition: newPosition ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 2 "pop position, file; leave rcvr on stack" ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:17'! primitiveFileSize | file size | self var: 'file' declareC: 'SQFile *file'. self export: true. file _ self fileValueOf: (interpreterProxy stackValue: 0). interpreterProxy failed ifFalse:[size _ self sqFileSize: file]. interpreterProxy failed ifFalse: [ interpreterProxy pop: 2. interpreterProxy push: (interpreterProxy positive32BitIntegerFor: size)].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/11/2000 22:34'! primitiveFileWrite | count startIndex array file byteSize arrayIndex bytesWritten | self var: 'file' declareC: 'SQFile *file'. self var: 'arrayIndex' type: 'char *'. self export: true. count _ interpreterProxy stackIntegerValue: 0. startIndex _ interpreterProxy stackIntegerValue: 1. array _ interpreterProxy stackValue: 2. file _ self fileValueOf: (interpreterProxy stackValue: 3). "buffer can be any indexable words or bytes object except CompiledMethod" (interpreterProxy isWordsOrBytes: array) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isWords: array) ifTrue: [ byteSize _ 4 ] ifFalse: [ byteSize _ 1 ]. ((startIndex >= 1) and: [(startIndex + count - 1) <= (interpreterProxy slotSizeOf: array)]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy failed ifFalse:[ arrayIndex _ interpreterProxy firstIndexableField: array. "Note: adjust startIndex for zero-origin indexing" bytesWritten _ self sqFile: file Write: (count * byteSize) From: (self cCoerce: arrayIndex to: 'int') At: ((startIndex - 1) * byteSize). ]. interpreterProxy failed ifFalse:[ interpreterProxy pop: 5. "pop rcvr, file, array, startIndex, count" interpreterProxy pushInteger: bytesWritten // byteSize. "push # of elements written" ].! ! !FilePlugin methodsFor: 'file primitives' stamp: 'ar 5/13/2000 14:51'! setMacFile: fileName Type: typeString AndCreator: creatorString "Exported entry point for the VM. Needed for image saving only and no-op on anything but Macs." self export: true. "Must be exported for image file write" self var: #fileName type: 'char *'. self var: #typeString type: 'char *'. self var: #creatorString type: 'char *'. ^self cCode: 'dir_SetMacFileTypeAndCreator(fileName, strlen(fileName), typeString, creatorString)'.! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/11/2000 21:31'! asciiDirectoryDelimiter ^ self cCode: 'dir_Delimitor()' inSmalltalk: [FileDirectory pathNameDelimiter asciiValue]! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/11/2000 22:11'! makeDirEntryName: entryName size: entryNameSize createDate: createDate modDate: modifiedDate isDir: dirFlag fileSize: fileSize | modDateOop createDateOop nameString results stringPtr | self var: 'entryName' declareC: 'char *entryName'. self var: 'stringPtr' declareC:'char *stringPtr'. "allocate storage for results, remapping newly allocated oops in case GC happens during allocation" interpreterProxy pushRemappableOop: (interpreterProxy instantiateClass: (interpreterProxy classArray) indexableSize: 5). interpreterProxy pushRemappableOop: (interpreterProxy instantiateClass: (interpreterProxy classString) indexableSize: entryNameSize).. interpreterProxy pushRemappableOop: (interpreterProxy positive32BitIntegerFor: createDate). interpreterProxy pushRemappableOop: (interpreterProxy positive32BitIntegerFor: modifiedDate). modDateOop _ interpreterProxy popRemappableOop. createDateOop _ interpreterProxy popRemappableOop. nameString _ interpreterProxy popRemappableOop. results _ interpreterProxy popRemappableOop. "copy name into Smalltalk string" stringPtr _ interpreterProxy firstIndexableField: nameString. 0 to: entryNameSize - 1 do: [ :i | stringPtr at: i put: (entryName at: i). ]. interpreterProxy storePointer: 0 ofObject: results withValue: nameString. interpreterProxy storePointer: 1 ofObject: results withValue: createDateOop. interpreterProxy storePointer: 2 ofObject: results withValue: modDateOop. dirFlag ifTrue: [ interpreterProxy storePointer: 3 ofObject: results withValue: interpreterProxy trueObject ] ifFalse: [ interpreterProxy storePointer: 3 ofObject: results withValue: interpreterProxy falseObject ]. interpreterProxy storePointer: 4 ofObject: results withValue: (interpreterProxy integerObjectOf: fileSize). ^ results! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/13/2000 14:42'! primitiveDirectoryCreate | dirName dirNameIndex dirNameSize | self var: #dirNameIndex type: 'char *'. self export: true. dirName _ interpreterProxy stackValue: 0. (interpreterProxy isBytes: dirName) ifFalse:[^interpreterProxy primitiveFail]. dirNameIndex _ interpreterProxy firstIndexableField: dirName. dirNameSize _ interpreterProxy byteSizeOf: dirName. (self cCode: 'dir_Create((char *) dirNameIndex, dirNameSize)' inSmalltalk:[false]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop dirName; leave rcvr on stack"! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/13/2000 14:42'! primitiveDirectoryDelete | dirName dirNameIndex dirNameSize | self var: #dirNameIndex type: 'char *'. self export: true. dirName _ interpreterProxy stackValue: 0. (interpreterProxy isBytes: dirName) ifFalse:[^interpreterProxy primitiveFail]. dirNameIndex _ interpreterProxy firstIndexableField: dirName. dirNameSize _ interpreterProxy byteSizeOf: dirName. (self cCode: 'dir_Delete((char *) dirNameIndex, dirNameSize)' inSmalltalk:[false]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop dirName; leave rcvr on stack"! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/11/2000 22:18'! primitiveDirectoryDelimitor | ascii | self export: true. ascii _ self asciiDirectoryDelimiter. ((ascii >= 0) and: [ascii <= 255]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 1. "pop rcvr" interpreterProxy push: (interpreterProxy fetchPointer: ascii ofObject: (interpreterProxy characterTable)).! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'JMM 1/28/2001 20:12'! primitiveDirectoryGetMacTypeAndCreator | creatorString typeString fileName creatorStringIndex typeStringIndex fileNameIndex fileNameSize | self var: 'creatorStringIndex' type: 'char *'. self var: 'typeStringIndex' type: 'char *'. self var: 'fileNameIndex' type: 'char *'. self export: true. creatorString _ interpreterProxy stackValue: 0. typeString _ interpreterProxy stackValue: 1. fileName _ interpreterProxy stackValue: 2. ((interpreterProxy isBytes: creatorString) and: [(interpreterProxy byteSizeOf: creatorString) = 4]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isBytes: typeString) and: [(interpreterProxy byteSizeOf: typeString) = 4]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isBytes: fileName) ifFalse:[^interpreterProxy primitiveFail]. creatorStringIndex _ interpreterProxy firstIndexableField: creatorString. typeStringIndex _ interpreterProxy firstIndexableField: typeString. fileNameIndex _ interpreterProxy firstIndexableField: fileName. fileNameSize _ interpreterProxy byteSizeOf: fileName. (self cCode: 'dir_GetMacFileTypeAndCreator( (char *) fileNameIndex, fileNameSize, (char *) typeStringIndex, (char *) creatorStringIndex)' inSmalltalk:[true]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 3. "pop filename, type, creator; leave rcvr on stack" ! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/11/2000 22:18'! primitiveDirectoryLookup | index pathName pathNameIndex pathNameSize status entryName entryNameSize createDate modifiedDate dirFlag fileSize | self var: 'entryName' declareC: 'char entryName[256]'. self var: 'pathNameIndex' type: 'char *'. self export: true. index _ interpreterProxy stackIntegerValue: 0. pathName _ interpreterProxy stackValue: 1. (interpreterProxy isBytes: pathName) ifFalse:[^interpreterProxy primitiveFail]. pathNameIndex _ interpreterProxy firstIndexableField: pathName. pathNameSize _ interpreterProxy byteSizeOf: pathName. status _ self cCode: 'dir_Lookup( (char *) pathNameIndex, pathNameSize, index, entryName, &entryNameSize, &createDate, &modifiedDate, &dirFlag, &fileSize)'. interpreterProxy failed ifTrue:[^nil]. status = DirNoMoreEntries ifTrue: [ "no more entries; return nil" interpreterProxy pop: 3. "pop pathName, index, rcvr" interpreterProxy push: interpreterProxy nilObject. ^ nil ]. status = DirBadPath ifTrue: [ ^ interpreterProxy primitiveFail ]. "bad path" interpreterProxy pop: 3. "pop pathName, index, rcvr" interpreterProxy push: (self makeDirEntryName: entryName size: entryNameSize createDate: createDate modDate: modifiedDate isDir: dirFlag fileSize: fileSize).! ! !FilePlugin methodsFor: 'directory primitives' stamp: 'ar 5/11/2000 22:18'! primitiveDirectorySetMacTypeAndCreator | creatorString typeString fileName creatorStringIndex typeStringIndex fileNameIndex fileNameSize | self var: 'creatorStringIndex' type: 'char *'. self var: 'typeStringIndex' type: 'char *'. self var: 'fileNameIndex' type: 'char *'. self export: true. creatorString _ interpreterProxy stackValue: 0. typeString _ interpreterProxy stackValue: 1. fileName _ interpreterProxy stackValue: 2. ((interpreterProxy isBytes: creatorString) and: [(interpreterProxy byteSizeOf: creatorString) = 4]) ifFalse:[^interpreterProxy primitiveFail]. ((interpreterProxy isBytes: typeString) and: [(interpreterProxy byteSizeOf: typeString) = 4]) ifFalse:[^interpreterProxy primitiveFail]. (interpreterProxy isBytes: fileName) ifFalse:[^interpreterProxy primitiveFail]. creatorStringIndex _ interpreterProxy firstIndexableField: creatorString. typeStringIndex _ interpreterProxy firstIndexableField: typeString. fileNameIndex _ interpreterProxy firstIndexableField: fileName. fileNameSize _ interpreterProxy byteSizeOf: fileName. (self cCode: 'dir_SetMacFileTypeAndCreator( (char *) fileNameIndex, fileNameSize, (char *) typeStringIndex, (char *) creatorStringIndex)' inSmalltalk:[true]) ifFalse:[^interpreterProxy primitiveFail]. interpreterProxy pop: 3. "pop filename, type, creator; leave rcvr on stack" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FilePlugin class instanceVariableNames: ''! !FilePlugin class methodsFor: 'class initialization' stamp: 'ar 5/12/2000 16:04'! initialize "FilePlugin initialize" DirEntryFound _ 0. DirNoMoreEntries _ 1. DirBadPath _ 2.! ! !FilePlugin class methodsFor: 'instance creation' stamp: 'ar 5/11/2000 22:13'! simulatorClass ^FilePluginSimulator! ! !FilePlugin class methodsFor: 'translation' stamp: 'JMM 1/28/2001 20:13'! headerFile ^'/* File support definitions */ /* squeak file record; see sqFilePrims.c for details */ typedef struct { FILE *file; int sessionID; int writable; int fileSize; int lastOp; /* 0 = uncommitted, 1 = read, 2 = write */ } SQFile; /* file i/o */ int sqFileAtEnd(SQFile *f); int sqFileClose(SQFile *f); int sqFileDeleteNameSize(int sqFileNameIndex, int sqFileNameSize); int sqFileGetPosition(SQFile *f); int sqFileInit(void); int sqFileShutdown(void); int sqFileOpen(SQFile *f, int sqFileNameIndex, int sqFileNameSize, int writeFlag); int sqFileReadIntoAt(SQFile *f, int count, int byteArrayIndex, int startIndex); int sqFileRenameOldSizeNewSize(int oldNameIndex, int oldNameSize, int newNameIndex, int newNameSize); int sqFileSetPosition(SQFile *f, int position); int sqFileSize(SQFile *f); int sqFileValid(SQFile *f); int sqFileWriteFromAt(SQFile *f, int count, int byteArrayIndex, int startIndex); /* directories */ int dir_Create(char *pathString, int pathStringLength); int dir_Delete(char *pathString, int pathStringLength); int dir_Delimitor(void); int dir_Lookup(char *pathString, int pathStringLength, int index, /* outputs: */ char *name, int *nameLength, int *creationDate, int *modificationDate, int *isDirectory, int *sizeIfFile); int dir_PathToWorkingDir(char *pathName, int pathNameMax); int dir_SetMacFileTypeAndCreator(char *filename, int filenameSize, char *fType, char *fCreator); int dir_GetMacFileTypeAndCreator(char *filename, int filenameSize, char *fType, char *fCreator); '.! ! FilePlugin subclass: #FilePluginSimulator instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:10'! fileValueOf: objectPointer ^interpreterProxy fileValueOf: objectPointer! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:11'! makeDirEntryName: entryName size: entryNameSize createDate: createDate modDate: modifiedDate isDir: dirFlag fileSize: fileSize ^interpreterProxy makeDirEntryName: entryName size: entryNameSize createDate: createDate modDate: modifiedDate isDir: dirFlag fileSize: fileSize ! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:11'! primitiveDirectoryLookup ^interpreterProxy primitiveDirectoryLookup! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:12'! primitiveFileDelete ^interpreterProxy primitiveFileDelete ! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:12'! primitiveFileOpen ^interpreterProxy primitiveFileOpen! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:12'! primitiveFileRename ^interpreterProxy primitiveFileRename! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:09'! sqFile: file Read: count Into: byteArrayIndex At: startIndex ^interpreterProxy sqFile: file Read: count Into: byteArrayIndex At: startIndex! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:09'! sqFile: file SetPosition: newPosition ^interpreterProxy sqFile: file SetPosition: newPosition! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:09'! sqFile: file Write: count From: byteArrayIndex At: startIndex ^interpreterProxy sqFile: file Write: count From: byteArrayIndex At: startIndex! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:09'! sqFileAtEnd: file ^interpreterProxy sqFileAtEnd: file! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:09'! sqFileClose: file ^interpreterProxy sqFileClose: file! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:10'! sqFileGetPosition: file ^interpreterProxy sqFileGetPosition: file! ! !FilePluginSimulator methodsFor: 'as yet unclassified' stamp: 'ar 5/11/2000 22:10'! sqFileSize: file ^interpreterProxy sqFileSize: file! ! ReadWriteStream subclass: #FileStream instanceVariableNames: 'rwmode ' classVariableNames: '' poolDictionaries: '' category: 'System-Files'! !FileStream commentStamp: '' prior: 0! I represent a Stream that accesses a FilePage from a File. One use for my instance is to access larger "virtual Strings" than can be stored contiguously in main memory. I restrict the objects stored and retrieved to be Integers or Characters. An end of file pointer terminates reading; it can be extended by writing past it, or the file can be explicitly truncated. To use the file system for most applications, you typically create a FileStream. This is done by sending a message to a FileDirectory (file:, oldFile:, newFile:, rename:newName:) which creates an instance of me. Accesses to the file are then done via my instance. *** On Mac and DOS, files cannot be shortened!! *** To overwrite a file with a shorter one, first delete the old file (FileDirectory deleteFilePath: 'Hard Disk:aFolder:dataFolder:foo') or (aFileDirectory deleteFileNamed: 'foo'). Then write your new shorter version.! !FileStream methodsFor: 'accessing'! contentsOfEntireFile "Read all of the contents of the receiver." | s binary | self readOnly. binary _ self isBinary. self reset. "erases knowledge of whether it is binary" binary ifTrue: [self binary]. s _ self next: self size. self close. ^s! ! !FileStream methodsFor: 'accessing' stamp: 'ar 1/25/2001 19:33'! mimeTypes ^FileDirectory default mimeTypesFor: self name.! ! !FileStream methodsFor: 'accessing'! next (position >= readLimit and: [self atEnd]) ifTrue: [^nil] ifFalse: [^collection at: (position _ position + 1)]! ! !FileStream methodsFor: 'accessing'! next: anInteger | newCollection howManyRead increment | newCollection _ collection species new: anInteger. howManyRead _ 0. [howManyRead < anInteger] whileTrue: [self atEnd ifTrue: [(howManyRead + 1) to: anInteger do: [:i | newCollection at: i put: (self next)]. ^newCollection]. increment _ (readLimit - position) min: (anInteger - howManyRead). newCollection replaceFrom: (howManyRead + 1) to: (howManyRead _ howManyRead + increment) with: collection startingAt: (position + 1). position _ position + increment]. ^newCollection! ! !FileStream methodsFor: 'accessing'! nextPut: aByte "1/31/96 sw: subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'accessing'! nextPutAll: aCollection "1/31/96 sw: made subclass responsibility" self subclassResponsibility! ! !FileStream methodsFor: 'accessing'! size "Answer the size of the file in characters. 1/31/96 sw: made subclass responsibility" self subclassResponsibility! ! !FileStream methodsFor: 'testing'! atEnd "Answer true if the current position is >= the end of file position. 1/31/96 sw: subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'positioning'! position "Answer the current character position in the file. 1/31/96 sw: subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'positioning'! position: pos "Set the current character position in the file to pos. 1/31/96 sw: made subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'positioning'! reset "Set the current character position to the beginning of the file. 1/31/96 sw: subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'positioning'! setToEnd "Set the current character position to the end of the File. The same as self position: self size. 1/31/96 sw: made subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'positioning'! skip: n "Set the character position to n characters from the current position. Error if not enough characters left in the file 1/31/96 sw: made subclassResponsibility." self subclassResponsibility! ! !FileStream methodsFor: 'printing'! printOn: aStream super printOn: aStream. aStream nextPutAll: ' on '. self file printOn: aStream! ! !FileStream methodsFor: 'editing' stamp: 'di 5/20/1998 23:20'! edit "Create and schedule an editor on this file." FileList openEditorOn: self editString: nil. ! ! !FileStream methodsFor: 'file open/close' stamp: 'jm 9/21/1998 13:02'! close "Close this file." self subclassResponsibility ! ! !FileStream methodsFor: 'file open/close' stamp: 'jm 9/21/1998 13:02'! closed "Answer true if this file is closed." self subclassResponsibility ! ! !FileStream methodsFor: 'file open/close' stamp: 'jm 9/21/1998 13:03'! flush "When writing, flush the current buffer out to disk." self subclassResponsibility ! ! !FileStream methodsFor: 'file open/close' stamp: 'jm 9/21/1998 13:04'! reopen "Ensure that the receiver is open, re-open it if necessary." "Details: Files that were open when a snapshot occurs are no longer valid when the snapshot is resumed. This operation re-opens the file if that has happened." self subclassResponsibility ! ! !FileStream methodsFor: 'file modes' stamp: 'jm 9/21/1998 13:01'! ascii "Set this file to ascii (text) mode." self subclassResponsibility ! ! !FileStream methodsFor: 'file modes' stamp: 'jm 9/21/1998 12:59'! binary "Set this file to binary mode." self subclassResponsibility ! ! !FileStream methodsFor: 'file modes' stamp: 'jm 9/21/1998 12:59'! readOnly "Set this file's mode to read-only." self subclassResponsibility ! ! !FileStream methodsFor: 'file modes' stamp: 'jm 9/21/1998 13:00'! readWrite "Set this file's mode to read-write." self subclassResponsibility ! ! !FileStream methodsFor: 'file modes' stamp: 'jm 9/21/1998 13:01'! text "Set this file to text (ascii) mode." self ascii. ! ! !FileStream methodsFor: 'file accessing'! file "Answer the file for the page the receiver is streaming over. 1/31/96 sw: made subclass responsibility" self subclassResponsibility! ! !FileStream methodsFor: 'file accessing' stamp: 'jm 12/5/97 12:53'! localName ^ FileDirectory localNameFor: self name ! ! !FileStream methodsFor: 'file accessing'! name "Answer the name of the file for the page the receiver is streaming over. 1/31/96 sw: made subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: 'file accessing' stamp: 'tk 1/15/1999 11:38'! url "Convert my path into a file:// type url. Use slash instead of the local delimiter (:), and convert odd characters to %32 notation." "If / is not the file system delimiter, encode / before converting." | list | list _ self directory pathParts. ^ String streamContents: [:strm | strm nextPutAll: 'file:/'. list do: [:each | strm nextPut: $/; nextPutAll: each encodeForHTTP]. strm nextPut: $/; nextPutAll: self localName encodeForHTTP]! ! !FileStream methodsFor: 'fileIn/Out' stamp: 'sw 11/19/1998 16:42'! fileIn "Guarantee that the receiver is readOnly before fileIn for efficiency and to eliminate remote sharing conflicts." self readOnly. self fileInAnnouncing: 'Loading ', self localName! ! !FileStream methodsFor: 'fileIn/Out' stamp: 'tk 1/21/2000 16:38'! fileInObjectAndCode "Read the file directly, do not use an RWBinaryOrTextStream." self text. ^ super fileInObjectAndCode ! ! !FileStream methodsFor: 'converting' stamp: 'tk 2/4/2000 09:16'! asBinaryOrTextStream "I can switch between binary and text data" ^ self! ! !FileStream methodsFor: 'remote file compatibility' stamp: 'RAA 9/24/2000 18:00'! dataIsValid self flag: #bob. "we needed this if a remote stream, but could be local as well"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileStream class instanceVariableNames: ''! !FileStream class methodsFor: 'instance creation'! fileNamed: fileName ^ self concreteStream fileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'instance creation'! fullName: fileName ^ FileDirectory default fullNameFor: fileName! ! !FileStream class methodsFor: 'instance creation' stamp: 'TPR 8/26/1999 10:49'! isAFileNamed: fName "return whether a file exists with the given name" ^self concreteStream isAFileNamed: (self fullName: fName)! ! !FileStream class methodsFor: 'instance creation' stamp: 'di 2/15/98 14:03'! new ^ self basicNew! ! !FileStream class methodsFor: 'instance creation'! newFileNamed: fileName ^ self concreteStream newFileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'instance creation'! oldFileNamed: fileName ^ self concreteStream oldFileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'instance creation' stamp: 'jm 5/8/1998 21:53'! oldFileOrNoneNamed: fileName "If the file exists, answer a read-only FileStream on it. If it doesn't, answer nil." | fullName | fullName _ self fullName: fileName. (self concreteStream isAFileNamed: fullName) ifTrue: [^ self concreteStream readOnlyFileNamed: fullName] ifFalse: [^ nil]. ! ! !FileStream class methodsFor: 'instance creation'! readOnlyFileNamed: fileName ^ self concreteStream readOnlyFileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'concrete classes' stamp: 'ls 7/11/1998 02:58'! concreteStream "Who should we really direct class queries to? " ^ StandardFileStream "may change this to CrLfFileStream"! ! !FileStream class methodsFor: 'browser requests' stamp: 'ar 3/2/2000 17:48'! post: data url: url ifError: errorBlock ^self concreteStream new post: data url: url ifError: errorBlock! ! !FileStream class methodsFor: 'browser requests'! requestURLStream: url "FileStream requestURLStream:'http://isgwww.cs.uni-magdeburg.de/~raab'" ^self concreteStream new requestURLStream: url! ! !FileStream class methodsFor: 'browser requests'! requestURLStream: url ifError: errorBlock "FileStream requestURLStream:'http://isgwww.cs.uni-magdeburg.de/~raab'" ^self concreteStream new requestURLStream: url ifError: errorBlock! ! !FileStream class methodsFor: 'dnd requests' stamp: 'ar 1/10/2001 19:41'! requestDropStream: dropIndex "Request a read-only stream for some file that was dropped onto Squeak" ^self concreteStream new requestDropStream: dropIndex.! ! Error subclass: #FileStreamException instanceVariableNames: 'fileName ' classVariableNames: '' poolDictionaries: '' category: 'System-Exceptions Kernel'! !FileStreamException methodsFor: 'exceptionBuilder' stamp: 'mir 2/23/2000 20:13'! fileName: aFileName fileName _ aFileName! ! !FileStreamException methodsFor: 'exceptionDescription' stamp: 'mir 2/25/2000 17:29'! fileName ^fileName! ! !FileStreamException methodsFor: 'exceptionDescription' stamp: 'mir 2/23/2000 20:13'! isResumable "Determine whether an exception is resumable." ^true! ! !FileStreamException methodsFor: 'exceptionDescription' stamp: 'mir 2/23/2000 20:14'! messageText "Return an exception's message text." ^messageText == nil ifTrue: [fileName printString] ifFalse: [messageText]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileStreamException class instanceVariableNames: ''! !FileStreamException class methodsFor: 'exceptionInstantiator' stamp: 'mir 2/23/2000 20:12'! fileName: aFileName ^self new fileName: aFileName! ! Url subclass: #FileUrl instanceVariableNames: 'path isAbsolute ' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !FileUrl commentStamp: '' prior: 0! A reference to a local file. The url itself uses the UNIX notation of %20 for space. path has the tokens translated to ascii, except if we have a UNIX file system. path is OC of path parts. If last is empty string, then referring to a directory. ! !FileUrl methodsFor: 'parsing' stamp: 'tk 9/6/1998 13:05'! privateInitializeFromText: text | bare schemeName pathString | bare _ text withBlanksTrimmed. schemeName _ Url schemeNameForString: bare. schemeName ifNil: [ pathString _ bare ] ifNotNil: [ pathString _ bare copyFrom: (schemeName size + 2) to: bare size ]. path _ pathString findTokens: '/'. path _ path collect: [:token | token unescapePercents]. (pathString endsWith: '/') ifTrue: [ path addLast: '' ]. isAbsolute _ pathString beginsWith: '/'.! ! !FileUrl methodsFor: 'parsing' stamp: 'tk 9/6/1998 16:40'! privateInitializeFromText: aString relativeTo: aUrl | bare | bare _ aString. (bare beginsWith: (self schemeName, ':')) ifTrue: [ bare _ bare copyFrom: (self schemeName size + 2) to: bare size ]. (bare beginsWith: '/') ifTrue: [ ^self privateInitializeFromText: aString ]. isAbsolute _ aUrl isAbsolute. path _ aUrl path copy. path removeLast. "empty string that says its a directory" (bare findTokens: '/') do: [ :token | ((token ~= '..') and: [token ~= '.']) ifTrue: [ path addLast: token unescapePercents ]. token = '..' ifTrue: [ path isEmpty ifFalse: [ path last = '..' ifFalse: [ path removeLast ] ] ]. "token = '.' do nothing" ]. (bare endsWith: '/') ifTrue: [ path add: '' ].! ! !FileUrl methodsFor: 'printing' stamp: 'tk 1/14/1999 21:22'! toText | s | s _ WriteStream on: String new. s nextPutAll: self schemeName. s nextPut: $:. isAbsolute ifTrue:[ s nextPut: $/ ]. "the extra one" s nextPutAll: self pathString. fragment ifNotNil: [ s nextPut: $#. s nextPutAll: fragment encodeForHTTP ]. ^s contents! ! !FileUrl methodsFor: 'access' stamp: 'ls 8/2/1998 05:39'! isAbsolute ^isAbsolute! ! !FileUrl methodsFor: 'access' stamp: 'ls 7/23/1998 07:29'! path "return an ordered collection of the path elements" ^path! ! !FileUrl methodsFor: 'access' stamp: 'tk 9/19/1998 18:57'! path: anArray path _ anArray! ! !FileUrl methodsFor: 'access' stamp: 'tk 11/24/1998 14:59'! pathDirString "Path to directory as url, using slash as delimiter" ^ String streamContents: [ :s | 1 to: self path size - 1 do: [ :ii | s nextPutAll: (path at: ii); nextPut: $/ ] ]! ! !FileUrl methodsFor: 'access' stamp: 'tk 11/23/1998 17:17'! pathForDirectory "Path using local file system's delimiter. $\ or $:" ^ String streamContents: [ :s | 1 to: self path size - 1 do: [ :ii | s nextPutAll: (path at: ii); nextPut: FileDirectory default pathNameDelimiter ] ]! ! !FileUrl methodsFor: 'access' stamp: 'tk 9/6/1998 00:35'! pathForFile "Path using local file system's delimiter. $\ or $:" | first | ^String streamContents: [ :s | first _ true. self path do: [ :p | first ifFalse: [ s nextPut: FileDirectory default pathNameDelimiter ]. first _ false. s nextPutAll: p ] ]! ! !FileUrl methodsFor: 'access' stamp: 'tk 9/6/1998 13:03'! pathString "Path as it appears in a URL with $/ as delimiter" | first | ^String streamContents: [ :s | isAbsolute ifTrue:[ s nextPut: $/ ]. first _ true. self path do: [ :p | first ifFalse: [ s nextPut: $/ ]. first _ false. s nextPutAll: p encodeForHTTP ] ]! ! !FileUrl methodsFor: 'downloading' stamp: 'tk 9/6/1998 00:36'! default "Use the default local Squeak file directory" | local | local _ FileUrl new path: (FileDirectory default pathParts), #('') isAbsolute: true. self privateInitializeFromText: self pathString relativeTo: local. "sets absolute also"! ! !FileUrl methodsFor: 'downloading' stamp: 'ls 8/4/1998 20:42'! hasContents ^true! ! !FileUrl methodsFor: 'downloading' stamp: 'tk 9/5/1998 20:42'! retrieveContents | file pathString s dir | pathString _ self pathForFile. path last size > 0 ifTrue: [ file _ FileStream oldFileOrNoneNamed: pathString. file ifNotNil: [ ^MIMEDocument contentType: (MIMEDocument guessTypeFromName: self path last) content: file contentsOfEntireFile ] ]. "assume it's a directory..." s _ WriteStream on: String new. dir _ FileDirectory on: pathString. (pathString endsWith: '/') ifFalse: [ pathString _ pathString, '/' ]. s nextPutAll: 'Directory Listing for ', pathString, ''. s nextPutAll: '

Directory Listing for ', pathString, '

'. s nextPutAll: ''. ^MIMEDocument contentType: 'text/html' content: s contents url: ('file:', pathString)! ! !FileUrl methodsFor: 'private-initialization' stamp: 'ls 7/26/1998 20:43'! path: aCollection isAbsolute: aBoolean path _ aCollection. isAbsolute _ aBoolean! ! !FileUrl methodsFor: 'classification' stamp: 'ls 7/26/1998 21:11'! schemeName ^'file'! ! !FileUrl methodsFor: 'copying' stamp: 'tk 2/1/2001 22:30'! copy "Be sure not to share the path with the copy" ^ (self clone) path: path copy! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileUrl class instanceVariableNames: ''! !FileUrl class methodsFor: 'parsing' stamp: 'sma 4/30/2000 11:28'! absoluteFromText: text "(how does this method fit with FileUrl|privateInitializeFromText:?)" | schemeName pathString bare thePath | bare _ text withBlanksTrimmed. schemeName _ Url schemeNameForString: bare. pathString _ schemeName ifNil: [bare] ifNotNil: [bare copyFrom: (schemeName size + 2) to: bare size]. thePath _ (pathString findTokens: '/') collect: [:token | token unescapePercents]. (pathString endsWith: '/') ifTrue: [thePath add: '']. "Hey, this only works on Unix!!" ^ self new path: thePath isAbsolute: (pathString beginsWith: '/')! ! StringHolder subclass: #FillInTheBlank instanceVariableNames: 'acceptOnCR done responseUponCancel ' classVariableNames: '' poolDictionaries: '' category: 'Tools-Menus'! !FillInTheBlank commentStamp: '' prior: 0! I represent a prompt for string input from the user. The user is asked to type in and edit a string. The resulting string is supplied as the argument to a client-supplied action block. ! !FillInTheBlank methodsFor: 'initialize-release' stamp: 'sw 1/31/2000 14:42'! initialize super initialize. acceptOnCR _ false. done _ false. responseUponCancel _ '' ! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'jm 4/28/1998 06:18'! acceptOnCR "Answer whether a carriage return should cause input to be accepted." ^ acceptOnCR ! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'jm 4/28/1998 06:18'! acceptOnCR: aBoolean acceptOnCR _ aBoolean. ! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'jm 5/6/1998 15:13'! done "Answer whether the user has ended the interaction." ^ done ! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'jm 5/6/1998 15:13'! done: aBoolean done _ aBoolean. ! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'sw 1/31/2000 14:45'! responseUponCancel: resp responseUponCancel _ resp! ! !FillInTheBlank methodsFor: 'accessing' stamp: 'sw 1/31/2000 14:47'! setResponseForCancel self contents: responseUponCancel! ! !FillInTheBlank methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:59'! convertToCurrentVersion: varDict refStream: smartRefStrm varDict at: 'responseUponCancel' ifAbsent: [responseUponCancel _ '']. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !FillInTheBlank methodsFor: 'private' stamp: 'sma 6/18/2000 10:54'! show: fillInView | savedArea | savedArea _ Form fromDisplay: fillInView displayBox. fillInView display. contents isEmpty ifFalse: [fillInView lastSubView controller selectFrom: 1 to: contents size]. (fillInView lastSubView containsPoint: Sensor cursorPoint) ifFalse: [fillInView lastSubView controller centerCursorInView]. fillInView controller startUp. fillInView release. savedArea displayOn: Display at: fillInView viewport topLeft. ^ contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FillInTheBlank class instanceVariableNames: ''! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'sma 6/18/2000 10:56'! multiLineRequest: queryString centerAt: aPoint initialAnswer: defaultAnswer answerHeight: answerHeight "Create a multi-line instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer nil if the user cancels. An empty string returned means that the ussr cleared the editing area and then hit 'accept'. Because multiple lines are invited, we ask that the user use the ENTER key, or (in morphic anyway) hit the 'accept' button, to submit; that way, the return key can be typed to move to the next line. NOTE: The ENTER key does not work on Windows platforms." "FillInTheBlank multiLineRequest: 'Enter several lines; end input by accepting or canceling via menu or press Alt+s/Alt+l' centerAt: Display center initialAnswer: 'Once upon a time...' answerHeight: 200" | model fillInView | Smalltalk isMorphic ifTrue: [^ self fillInTheBlankMorphClass request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: self currentWorld onCancelReturn: nil acceptOnCR: false]. model _ self new initialize. model contents: defaultAnswer. model responseUponCancel: nil. model acceptOnCR: false. fillInView _ self fillInTheBlankViewClass multiLineOn: model message: queryString centerAt: aPoint answerHeight: answerHeight. ^ model show: fillInView! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'sma 6/18/2000 10:27'! request: queryString "Create an instance of me whose question is queryString. Invoke it centered at the cursor, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlank request: 'Your name?'" ^ self request: queryString initialAnswer: '' centerAt: Sensor cursorPoint! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'sma 6/18/2000 10:28'! request: queryString initialAnswer: defaultAnswer "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlank request: 'What is your favorite color?' initialAnswer: 'red, no blue. Ahhh!!'" ^ self request: queryString initialAnswer: defaultAnswer centerAt: Sensor cursorPoint! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'sma 6/18/2000 10:53'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlank request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" | model fillInView | Smalltalk isMorphic ifTrue: [^ self fillInTheBlankMorphClass request: queryString initialAnswer: defaultAnswer centerAt: aPoint]. model _ self new initialize. model contents: defaultAnswer. fillInView _ self fillInTheBlankViewClass on: model message: queryString centerAt: aPoint. ^ model show: fillInView! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'sma 6/18/2000 10:57'! requestPassword: queryString "Create an instance of me whose question is queryString. Invoke it centered at the cursor, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlank requestPassword: 'POP password'" | model fillInView | Smalltalk isMorphic ifTrue: [^ self fillInTheBlankMorphClass requestPassword: queryString]. model _ self new initialize. model contents: ''. fillInView _ self fillInTheBlankViewClass requestPassword: model message: queryString centerAt: Sensor cursorPoint answerHeight: 40. ^ model show: fillInView! ! !FillInTheBlank class methodsFor: 'private' stamp: 'sma 6/18/2000 10:39'! fillInTheBlankMorphClass "By factoring out this class references, it becomes possible to discard morphic by simply removing this class. All calls to this method needs to be protected by 'Smalltalk isMorphic' tests." ^ FillInTheBlankMorph! ! !FillInTheBlank class methodsFor: 'private' stamp: 'sma 6/18/2000 10:47'! fillInTheBlankViewClass "By factoring out this class references, it becomes possible to discard MVC by simply removing this class. All calls to this method needs to be protected by 'Smalltalk isMorphic' tests." ^ FillInTheBlankView! ! StringHolderController subclass: #FillInTheBlankController instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Support'! !FillInTheBlankController commentStamp: '' prior: 0! I am the controller for a FillInTheBlankView. Based on a flag in the view, I can either accept the input string when a carriage return is typed, or I can allow multiple lines of input that is accepted by either typing enter or by invoking the 'accept' command. ! !FillInTheBlankController methodsFor: 'basic control sequence' stamp: 'jm 4/28/1998 07:57'! controlInitialize model acceptOnCR ifFalse: [^ super controlInitialize]. startBlock _ paragraph characterBlockForIndex: startBlock stringIndex. stopBlock _ paragraph characterBlockForIndex: stopBlock stringIndex. self initializeSelection. beginTypeInBlock _ nil. ! ! !FillInTheBlankController methodsFor: 'basic control sequence' stamp: 'jm 5/6/1998 15:11'! controlTerminate | topController | super controlTerminate. topController _ view topView controller. topController ifNotNil: [topController close]. ! ! !FillInTheBlankController methodsFor: 'control defaults' stamp: 'sma 3/11/2000 14:45'! isControlActive ^ self isControlWanted! ! !FillInTheBlankController methodsFor: 'control defaults' stamp: 'sma 3/11/2000 14:45'! isControlWanted ^ model done not! ! !FillInTheBlankController methodsFor: 'other' stamp: 'jm 5/6/1998 15:13'! accept super accept. model done: true. ! ! !FillInTheBlankController methodsFor: 'other' stamp: 'sw 1/31/2000 14:47'! cancel model setResponseForCancel. super cancel. model done: true. ! ! !FillInTheBlankController methodsFor: 'other' stamp: 'jm 4/28/1998 06:25'! dispatchOnCharacter: char with: typeAheadStream "Accept the current input if the user hits the carriage return or the enter key." (model acceptOnCR and: [(char = Character cr) | (char = Character enter)]) ifTrue: [ sensor keyboard. "absorb the character" self accept. ^ true] ifFalse: [ ^ super dispatchOnCharacter: char with: typeAheadStream]. ! ! !FillInTheBlankController methodsFor: 'other' stamp: 'jm 4/28/1998 08:01'! processYellowButton "Suppress yellow-button menu if acceptOnCR is true." model acceptOnCR ifFalse: [^ super processYellowButton]. ! ! RectangleMorph subclass: #FillInTheBlankMorph instanceVariableNames: 'response done textPane responseUponCancel ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'ar 10/10/2000 22:35'! delete self breakDependents. super delete.! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'ssa 2/14/2000 13:14'! initialize super initialize. Preferences roundedWindowCorners ifTrue: [self useRoundedCorners]. color _ Color white. borderWidth _ 2. self extent: 200@70. responseUponCancel _ ''. "Caller can reset this to return something else, e.g. nil, upon cancel" ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sw 1/31/2000 11:01'! responseUponCancel: anObject responseUponCancel _ anObject ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'ar 11/4/2000 23:21'! setPasswordQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight acceptOnCR: acceptBoolean | pane | self setQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight acceptOnCR: acceptBoolean. pane _ self submorphNamed: 'textPane'. pane font: (StrikeFont passwordFontSize: 12).! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sw 2/2/2000 22:41'! setQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight self setQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight acceptOnCR: true ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'JW 2/1/2001 13:28'! setQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight acceptOnCR: acceptBoolean | query frame topOffset accept cancel buttonAreaHeight | response _ initialAnswer. done _ false. self removeAllMorphs. self layoutPolicy: ProportionalLayout new. query _ TextMorph new contents: queryString. query setNameTo: 'query'. query lock. frame _ LayoutFrame new. frame topFraction: 0.0; topOffset: 2. frame leftFraction: 0.5; leftOffset: (query width // 2) negated. query layoutFrame: frame. self addMorph: query. topOffset _ query height + 4. accept _ SimpleButtonMorph new target: self; color: Color veryLightGray. accept label: 'Accept(s)'; actionSelector: #accept. accept setNameTo: 'accept'. frame _ LayoutFrame new. frame rightFraction: 0.5; rightOffset: -10; bottomFraction: 1.0; bottomOffset: -2. accept layoutFrame: frame. self addMorph: accept. cancel _ SimpleButtonMorph new target: self; color: Color veryLightGray. cancel label: 'Cancel(l)'; actionSelector: #cancel. cancel setNameTo: 'cancel'. frame _ LayoutFrame new. frame leftFraction: 0.5; leftOffset: 10; bottomFraction: 1.0; bottomOffset: -2. cancel layoutFrame: frame. self addMorph: cancel. buttonAreaHeight _ (accept height max: cancel height) + 4. textPane _ PluggableTextMorph on: self text: #response accept: #response: readSelection: #selectionInterval menu: #codePaneMenu:shifted:. textPane hResizing: #spaceFill; vResizing: #spaceFill. textPane borderWidth: 2. textPane hasUnacceptedEdits: true. textPane acceptOnCR: acceptBoolean. textPane setNameTo: 'textPane'. frame _ LayoutFrame new. frame leftFraction: 0.0; rightFraction: 1.0; topFraction: 0.0; topOffset: topOffset; bottomFraction: 1.0; bottomOffset: buttonAreaHeight negated. textPane layoutFrame: frame. self addMorph: textPane. self extent: (200 max: query width) + 4 @ (topOffset + answerHeight + 4 + buttonAreaHeight). ! ! !FillInTheBlankMorph methodsFor: 'accessing' stamp: 'jm 5/4/1998 14:03'! response ^ response ! ! !FillInTheBlankMorph methodsFor: 'accessing' stamp: 'jm 5/4/1998 14:22'! response: aText "Sent when text pane accepts." response _ aText asString. done _ true. ^ true ! ! !FillInTheBlankMorph methodsFor: 'accessing' stamp: 'di 5/22/1998 00:58'! selectionInterval ^ 1 to: response size ! ! !FillInTheBlankMorph methodsFor: 'menu' stamp: 'jm 5/4/1998 14:21'! accept "Sent by the accept button." textPane accept. ! ! !FillInTheBlankMorph methodsFor: 'menu' stamp: 'sw 1/31/2000 11:11'! cancel "Sent by the cancel button." response _ responseUponCancel. done _ true. ! ! !FillInTheBlankMorph methodsFor: 'menu' stamp: 'jm 5/4/1998 15:15'! codePaneMenu: aMenu shifted: shifted ^ StringHolder new codePaneMenu: aMenu shifted: shifted. ! ! !FillInTheBlankMorph methodsFor: 'menu' stamp: 'jm 5/4/1998 15:17'! perform: selector orSendTo: otherTarget ^ otherTarget perform: selector ! ! !FillInTheBlankMorph methodsFor: 'invoking' stamp: 'ar 10/10/2000 22:37'! getUserResponse "Wait for the user to accept or cancel, and answer the result string. Answers the empty string if the user cancels." "Details: This is invoked synchronously from the caller. In order to keep processing inputs and updating the screen while waiting for the user to respond, this method has its own version of the World's event loop." | w | w _ self world. w ifNil: [^ response]. done _ false. [done] whileFalse: [Display doOneCycleMorphic]. self delete. Display doOneCycleMorphic. ^ response ! ! !FillInTheBlankMorph methodsFor: 'invoking' stamp: 'RAA 7/19/2000 20:40'! morphicLayerNumber ^10.6! ! !FillInTheBlankMorph methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:59'! convertToCurrentVersion: varDict refStream: smartRefStrm varDict at: 'responseUponCancel' ifAbsent: [responseUponCancel _ '']. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !FillInTheBlankMorph methodsFor: 'event handling' stamp: 'ar 10/7/2000 15:47'! handlesMouseDown: evt ^true! ! !FillInTheBlankMorph methodsFor: 'event handling' stamp: 'ar 10/7/2000 16:00'! mouseDown: evt (self containsPoint: evt position) ifFalse:[^Smalltalk beep]. "sent in response to outside modal click" evt hand grabMorph: self. "allow repositioning"! ! !FillInTheBlankMorph methodsFor: 'grabbing/dropping' stamp: 'ar 10/7/2000 15:50'! undoGrabCommand ^nil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FillInTheBlankMorph class instanceVariableNames: ''! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'jm 5/6/1998 15:26'! request: queryString "Create an instance of me whose question is queryString. Invoke it centered at the cursor, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlankMorph request: 'What is your favorite color?'" ^ self request: queryString initialAnswer: '' centerAt: Sensor cursorPoint. ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'jm 5/6/1998 15:26'! request: queryString initialAnswer: defaultAnswer "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlankMorph request: 'What is your favorite color?' initialAnswer: 'red, no blue. Ahhh!!'" ^ self request: queryString initialAnswer: defaultAnswer centerAt: Sensor cursorPoint. ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'RAA 6/2/2000 11:03'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer the empty string if the user cancels. This variant is only for calling from within a Morphic project." "FillInTheBlankMorph request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" ^ self request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: Display getCurrentMorphicWorld ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'sw 1/31/2000 11:03'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. Answer the empty string if the user cancels." "FillInTheBlankMorph request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" ^ self request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: ''! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'sw 2/2/2000 22:43'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. If the user cancels, answer returnOnCancel. If user hits cr, treat it as a normal accept." "FillInTheBlankMorph request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" ^ self request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: true! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'sw 2/2/2000 22:34'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. If the user cancels, answer returnOnCancel." "FillInTheBlankMorph request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" | aFillInTheBlankMorph | aFillInTheBlankMorph _ self new setQuery: queryString initialAnswer: defaultAnswer answerHeight: 50 acceptOnCR: acceptBoolean. aFillInTheBlankMorph responseUponCancel: returnOnCancel. aWorld addMorph: aFillInTheBlankMorph centeredNear: aPoint. ^ aFillInTheBlankMorph getUserResponse ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'bolot 5/18/2000 13:57'! requestPassword: queryString "Create an instance of me whose question is queryString. Invoke it centered at the cursor, and answer the string the user accepts. Answer the empty string if the user cancels." "use password font" "FillInTheBlankMorph requestPassword: 'Password?'" ^ self requestPassword: queryString initialAnswer: '' centerAt: Sensor cursorPoint inWorld: World onCancelReturn: '' acceptOnCR: true ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'bolot 5/18/2000 13:53'! requestPassword: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean "Create an instance of me whose question is queryString with the given initial answer. Invoke it centered at the given point, and answer the string the user accepts. If the user cancels, answer returnOnCancel." "FillInTheBlankMorph request: 'Type something, then type CR.' initialAnswer: 'yo ho ho!!' centerAt: Display center" | aFillInTheBlankMorph | aFillInTheBlankMorph _ self new setPasswordQuery: queryString initialAnswer: defaultAnswer answerHeight: 50 acceptOnCR: acceptBoolean. aFillInTheBlankMorph responseUponCancel: returnOnCancel. aWorld addMorph: aFillInTheBlankMorph centeredNear: aPoint. ^ aFillInTheBlankMorph getUserResponse ! ! StringHolderView subclass: #FillInTheBlankView instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Support'! !FillInTheBlankView commentStamp: '' prior: 0! I am a view of a FillInTheBlank. I display a query and an editable view of the user's reply string. ! !FillInTheBlankView methodsFor: 'controller access' stamp: 'jm 4/28/1998 06:37'! defaultControllerClass ^ FillInTheBlankController ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FillInTheBlankView class instanceVariableNames: ''! !FillInTheBlankView class methodsFor: 'instance creation' stamp: 'jm 4/28/1998 08:35'! multiLineOn: aFillInTheBlank message: queryString centerAt: aPoint answerHeight: answerHeight "Answer an instance of me on aFillInTheBlank asking the question queryString. Allow the reply to be multiple lines, and make the user input view the given height." | messageView answerView topView | messageView _ DisplayTextView new model: queryString asDisplayText; borderWidthLeft: 2 right: 2 top: 2 bottom: 0; controller: NoController new. messageView window: (0@0 extent: (messageView window extent max: 200@30)); centered. answerView _ self new model: aFillInTheBlank; window: (0@0 extent: (messageView window width@answerHeight)); borderWidth: 2. topView _ View new model: aFillInTheBlank. topView controller: ModalController new. topView addSubView: messageView. topView addSubView: answerView below: messageView. topView align: topView viewport center with: aPoint. topView window: (0 @ 0 extent: (messageView window width) @ (messageView window height + answerView window height)). topView translateBy: (topView displayBox amountToTranslateWithin: Display boundingBox). ^ topView ! ! !FillInTheBlankView class methodsFor: 'instance creation' stamp: 'jm 4/28/1998 08:22'! on: aFillInTheBlank message: queryString centerAt: aPoint "Answer an instance of me on aFillInTheBlank for a single line of input in response to the question queryString." aFillInTheBlank acceptOnCR: true. ^ self multiLineOn: aFillInTheBlank message: queryString centerAt: aPoint answerHeight: 40 ! ! !FillInTheBlankView class methodsFor: 'instance creation' stamp: 'jdr 6/4/2000 15:03'! requestPassword: aFillInTheBlank message: queryString centerAt: aPoint answerHeight: answerHeight "Answer an instance of me on aFillInTheBlank asking the question queryString. Allow the reply to be multiple lines, and make the user input view the given height." | messageView answerView topView myPar pwdFont myArray myStyle | aFillInTheBlank acceptOnCR: true. messageView _ DisplayTextView new model: queryString asDisplayText; borderWidthLeft: 2 right: 2 top: 2 bottom: 0; controller: NoController new. messageView window: (0@0 extent: (messageView window extent max: 200@30)); centered. answerView _ self new model: aFillInTheBlank; window: (0@0 extent: (messageView window width@answerHeight)); borderWidth: 2. " now answerView to use the password font" myPar _ answerView displayContents. pwdFont _ (StrikeFont passwordFontSize: 12). myArray _ Array new: 1. myArray at: 1 put: pwdFont. myStyle _ TextStyle fontArray: myArray. myPar setWithText: (myPar text) style: myStyle. topView _ View new model: aFillInTheBlank. topView controller: ModalController new. topView addSubView: messageView. topView addSubView: answerView below: messageView. topView align: topView viewport center with: aPoint. topView window: (0 @ 0 extent: (messageView window width) @ (messageView window height + answerView window height)). topView translateBy: (topView displayBox amountToTranslateWithin: Display boundingBox). ^ topView ! ! Object subclass: #FillStyle instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Fills'! !FillStyle methodsFor: 'accessing' stamp: 'ar 1/14/1999 15:23'! scaledPixelValue32 "Return a pixel value of depth 32 for the primary color in the fill style" ^self asColor scaledPixelValue32! ! !FillStyle methodsFor: 'testing' stamp: 'ar 11/9/1998 13:54'! isBitmapFill ^false! ! !FillStyle methodsFor: 'testing' stamp: 'ar 11/9/1998 13:54'! isGradientFill ^false! ! !FillStyle methodsFor: 'testing' stamp: 'ar 6/18/1999 07:57'! isOrientedFill "Return true if the receiver keeps an orientation (e.g., origin, direction, and normal)" ^false! ! !FillStyle methodsFor: 'testing' stamp: 'ar 11/9/1998 13:54'! isSolidFill ^false! ! !FillStyle methodsFor: 'testing' stamp: 'ar 9/2/1999 14:28'! isTranslucent ^true "Since we don't know better"! ! !FillStyle methodsFor: 'testing' stamp: 'ar 10/26/2000 19:24'! isTransparent ^false! ! !FillStyle methodsFor: 'converting' stamp: 'ar 2/1/1999 22:12'! asB3DColor ^self asColor asB3DColor! ! !FillStyle methodsFor: 'converting' stamp: 'ar 11/9/1998 13:53'! asColor ^self subclassResponsibility! ! MagnifierMorph subclass: #FishEyeMorph instanceVariableNames: 'gridNum d clipRects toRects quads savedExtent ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !FishEyeMorph methodsFor: 'private' stamp: 'yo 12/17/1999 11:15'! gridSizeFor: aPoint "returns appropriate size for specified argument" | g | g _ aPoint x min: aPoint y. g <= 256 ifTrue: [^8]. ^16.! ! !FishEyeMorph methodsFor: 'private' stamp: 'ar 5/28/2000 12:12'! magnifiedForm | warp warpForm fromForm | savedExtent ~= srcExtent ifTrue: [ savedExtent _ srcExtent. self calculateTransform]. warpForm _ Form extent: srcExtent depth: Display depth. fromForm _ super magnifiedForm. warp _ (WarpBlt current toForm: warpForm) sourceForm: fromForm; colorMap: nil; cellSize: 2; combinationRule: Form over. 1 to: gridNum y do: [:j | 1 to: gridNum x do: [:i | warp clipRect: ((clipRects at: j) at: i); copyQuad: ((quads at: j) at: i) toRect: ((toRects at: j) at: i). ]. ]. ^warpForm ! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'yo 12/17/1999 12:00'! calculateTransform | stepX stepY rect tx ty arrayX arrayY | (gridNum x = 0 or: [gridNum y = 0]) ifTrue: [^self]. stepX _ srcExtent x // gridNum x. stepY _ srcExtent y // gridNum y. arrayX _ (1 to: gridNum y + 1) collect: [:j | FloatArray new: gridNum x + 1]. arrayY _ (1 to: gridNum y + 1) collect: [:j | FloatArray new: gridNum x + 1]. 0 to: gridNum y do: [:j | 0 to: gridNum x do: [:i | (arrayX at: (j + 1)) at: (i + 1) put: i*stepX. (arrayY at: (j + 1)) at: (i + 1) put: j*stepY. ]. ]. 0 to: gridNum y do: [:j | self transformX: (arrayX at: (j+1)). self transformY: (arrayY at: (j+1)). ]. 0 to: gridNum y do: [:j | arrayX at: (j+1) put: ((1 to: gridNum x +1) collect: [:i | ((arrayX at: (j+1)) at: i) asInteger]). arrayY at: (j+1) put: ((1 to: gridNum x +1) collect: [:i | ((arrayY at: (j+1)) at: i) asInteger]). ]. clipRects _ (1 to: gridNum y) collect: [:j | Array new: gridNum x]. toRects _ (1 to: gridNum y) collect: [:j | Array new: gridNum x]. quads _ (1 to: gridNum y) collect: [:j | Array new: gridNum x]. 0 to: gridNum y - 1 do: [:j | 0 to: gridNum x- 1 do: [:i | rect _ (((arrayX at: (j+1)) at: (i+1))@((arrayY at: (j+1)) at: (i+1))) corner: ((arrayX at: (j+2)) at: (i+2))@((arrayY at: (j+2)) at: (i+2)). (clipRects at: j+1) at: i+1 put: rect. rect width >= stepX ifTrue: [rect _ rect expandBy: (1@0)]. rect height >= stepY ifTrue: [rect _ rect expandBy: (0@1)]. (toRects at: j+1) at: i+1 put: rect. tx _ (i)*stepX. ty _ (j)*stepY. (quads at: j+1) at: i+1 put: {(tx)@(ty). (tx)@(ty+stepY). (tx+stepX)@(ty+stepY). (tx+stepX)@(ty)}. ]. ]. ! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'yo 12/17/1999 10:15'! g: aFloatArray max: max focus: focus | dNormX array | dNormX _ aFloatArray - focus. array _ dNormX / max. array *= d. array += 1.0. array _ 1.0 / array. dNormX *= (d+1.0). array *= dNormX. ^array += focus. ! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'yo 12/17/1999 12:41'! initialize super initialize. "magnification should be always 1" magnification _ 1. d _ 1.3. self extent: 130@130. ! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'yo 12/17/1999 12:32'! transformX: aFloatArray | focus gridNum2 subArray dMaxX | focus _ srcExtent x asFloat / 2. gridNum2 _ (aFloatArray findFirst: [:x | x > focus]) - 1. dMaxX _ 0.0 - focus. subArray _ self g: (aFloatArray copyFrom: 1 to: gridNum2) max: dMaxX focus: focus. aFloatArray replaceFrom: 1 to: gridNum2 with: subArray startingAt: 1. dMaxX _ focus. " = (size - focus)". subArray _ self g: (aFloatArray copyFrom: gridNum2 + 1 to: gridNum x + 1) max: dMaxX focus: focus. aFloatArray replaceFrom: gridNum2 + 1 to: gridNum x + 1 with: subArray startingAt: 1. ! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'yo 12/17/1999 11:42'! transformY: aFloatArray | focus subArray dMaxY | focus _ srcExtent y asFloat / 2. (aFloatArray at: 1) <= focus ifTrue: [ dMaxY _ 0.0 - focus. ] ifFalse: [ dMaxY _ focus. " = (size - focus)". ]. subArray _ self g: (aFloatArray copyFrom: 1 to: gridNum x + 1) max: dMaxY focus: focus. aFloatArray replaceFrom: 1 to: gridNum x + 1 with: subArray startingAt: 1. ! ! !FishEyeMorph methodsFor: 'menus' stamp: 'yo 12/17/1999 12:32'! chooseMagnification self inform: 'Magnification is fixed, sorry.'! ! !FishEyeMorph methodsFor: 'events' stamp: 'yo 12/17/1999 12:03'! chooseMagnification: evt ! ! !FishEyeMorph methodsFor: 'geometry' stamp: 'yo 12/17/1999 12:27'! extent: aPoint "Round to a number divisible by grid. Note that the superclass has its own implementation." | g gridSize | gridSize _ self gridSizeFor: aPoint. "self halt." g _ (aPoint - (2 * borderWidth)) // gridSize. srcExtent _ g * gridSize. gridNum _ g. ^super extent: self defaultExtent! ! ReferenceMorph subclass: #FlapTab instanceVariableNames: 'flapShowing edgeToAdhereTo slidesOtherObjects popOutOnDragOver popOutOnMouseOver inboard dragged lastReferentThickness ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Palettes'! !FlapTab commentStamp: '' prior: 0! The tab associated with a flap. nb: slidesOtherObjects and inboard are instance variables relating to disused features. The feature implementations still exist in the system, but the UI to them has been sealed off.! !FlapTab methodsFor: 'initialization' stamp: 'tk 12/11/2000 16:29'! adaptToWorld | wasShowing new | (wasShowing _ self flapShowing) ifTrue: [self hideFlap]. (self respondsTo: #unhibernate) ifTrue: [ (new _ self unhibernate) == self ifFalse: [ ^ new adaptToWorld]]. self spanWorld. self positionObject: self. wasShowing ifTrue: [self showFlap]! ! !FlapTab methodsFor: 'initialization' stamp: 'sw 2/16/1999 23:44'! initialize super initialize. edgeToAdhereTo _ #left. flapShowing _ false. slidesOtherObjects _ false. popOutOnDragOver _ false. popOutOnMouseOver _ false. inboard _ false. dragged _ false! ! !FlapTab methodsFor: 'initialization' stamp: 'sw 6/20/1999 19:17'! preserveDetails "The receiver is being switched to use a different format. Preserve the existing details (e.g. wording if textual, grapheme if graphical) so that if the user reverts back to the current format, the details will be right" | thickness | color = Color transparent ifFalse: [self setProperty: #priorColor toValue: color]. self isCurrentlyTextual ifTrue: [self setProperty: #priorWording toValue: self existingWording] ifFalse: [self isCurrentlyGraphical ifTrue: [self setProperty: #priorGraphic toValue: submorphs first form] ifFalse: [thickness _ (self orientation == #vertical) ifTrue: [self width] ifFalse: [self height]. self setProperty: #priorThickness toValue: thickness]]! ! !FlapTab methodsFor: 'access' stamp: 'sw 2/9/1999 14:44'! flapShowing ^ flapShowing == true! ! !FlapTab methodsFor: 'access' stamp: 'sw 1/25/2000 11:07'! isCandidateForAutomaticViewing ^ false! ! !FlapTab methodsFor: 'access' stamp: 'sw 12/13/1999 11:51'! isGlobal ^ Utilities globalFlapTabsIfAny includes: self! ! !FlapTab methodsFor: 'access' stamp: 'RAA 10/3/2000 09:24'! morphicLayerNumber ^self flapShowing ifTrue: [26] ifFalse: [25] "As navigators"! ! !FlapTab methodsFor: 'access' stamp: 'sw 2/26/1999 20:37'! orientation ^ (#(left right) includes: edgeToAdhereTo) ifTrue: [#vertical] ifFalse: [#horizontal]! ! !FlapTab methodsFor: 'access' stamp: 'sw 6/16/1999 11:29'! permitsThumbnailing ^ false! ! !FlapTab methodsFor: 'access' stamp: 'sw 6/18/1999 13:38'! referentThickness ^ (self orientation == #horizontal) ifTrue: [referent height] ifFalse: [referent width]! ! !FlapTab methodsFor: 'access' stamp: 'sw 2/27/1999 13:14'! tabThickness ^ (self orientation == #vertical) ifTrue: [self width] ifFalse: [self height]! ! !FlapTab methodsFor: 'parts bin' stamp: 'sw 9/21/2000 22:49'! partsBinString "Answer the string to be shown in a menu to represent the parts-bin status" ^ referent isPartsBin ifTrue: ['parts-bin'] ifFalse: ['parts-bin']! ! !FlapTab methodsFor: 'parts bin' stamp: 'sw 2/25/1999 13:17'! togglePartsBinMode referent setPartsBinStatusTo: referent isPartsBin not! ! !FlapTab methodsFor: 'edge' stamp: 'sw 6/21/1999 11:13'! edgeString ^ 'cling to edge... (current: ', edgeToAdhereTo, ')'! ! !FlapTab methodsFor: 'edge' stamp: 'sw 2/11/1999 00:41'! edgeToAdhereTo ^ edgeToAdhereTo! ! !FlapTab methodsFor: 'edge' stamp: 'sw 2/11/1999 00:32'! edgeToAdhereTo: e edgeToAdhereTo _ e! ! !FlapTab methodsFor: 'edge' stamp: 'sw 12/8/1999 15:01'! setEdge: anEdge | changedOrientation | changedOrientation _ nil. self orientation == #vertical ifTrue: [(#(top bottom) includes: anEdge) ifTrue: [changedOrientation _ #horizontal]] ifFalse: [(#(top bottom) includes: anEdge) ifFalse: [changedOrientation _ #vertical]]. changedOrientation ifNotNil: [^ self inform: 'SORRY -- this sort of switch from horizontal to vertical or vice-versa is not yet permitted. For now, to achieve the desired effect, create a new flap and copy over the elements you want.']. self isCurrentlyTextual ifTrue: [changedOrientation ifNotNil: [self assumeString: (submorphs first contents copyWithout: Character cr) font: Preferences standardFlapFont orientation: changedOrientation color: self color]]. self edgeToAdhereTo: anEdge. changedOrientation ifNotNil: [self transposeParts]. referent isInWorld ifTrue: [self positionReferent]. self adjustPositionVisAVisFlap! ! !FlapTab methodsFor: 'edge' stamp: 'RAA 6/12/2000 08:58'! setEdgeToAdhereTo | aMenu | aMenu _ MenuMorph new defaultTarget: self. #(left top right bottom) do: [:sym | aMenu add: sym asString target: self selector: #setEdge: argument: sym]. aMenu popUpEvent: self currentEvent in: self world! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/20/1999 22:30'! addAddHandMenuItemsForHalo: aMenu hand: aHandMorph aMenu add: 'tab color...' target: self action: #changeColor. aMenu add: 'flap color...' target: self action: #changeFlapColor ! ! !FlapTab methodsFor: 'menu' stamp: 'sw 11/6/2000 14:52'! addCustomMenuItems: aMenu hand: aHandMorph "Add further items to the menu as appropriate" aMenu addUpdating: #edgeString action: #setEdgeToAdhereTo. aMenu addLine. aMenu addUpdating: #textualTabString action: #textualTab. aMenu addUpdating: #graphicalTabString action: #graphicalTab. aMenu addUpdating: #solidTabString enablement: #notSolid action: #solidTab. aMenu addLine. (referent isKindOf: PasteUpMorph) ifTrue: [aMenu addUpdating: #partsBinString action: #togglePartsBinMode]. aMenu addUpdating: #dragoverString action: #toggleDragOverBehavior. aMenu addUpdating: #mouseoverString action: #toggleMouseOverBehavior. aMenu addLine. aMenu addUpdating: #isGlobalFlapString action: #toggleIsGlobalFlap. aMenu balloonTextForLastItem: 'If checked, this flap will be available in all morphic projects; if not, it will be private to this project.,'. aMenu addLine. aMenu add: 'destroy this flap' action: #destroyFlap. "aMenu addUpdating: #slideString action: #toggleSlideBehavior. aMenu addUpdating: #inboardString action: #toggleInboardness. aMenu addUpdating: #thicknessString ('thickness... (current: ', self thickness printString, ')') action: #setThickness." ! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/19/1999 23:16'! addTitleForHaloMenu: aMenu aMenu addTitle: self externalName updatingSelector: #flapMenuTitle updateTarget: self! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/20/1999 23:41'! applyThickness: newThickness | toUse | toUse _ newThickness asNumber max: 0. (self orientation == #vertical) ifTrue: [referent width: toUse] ifFalse: [referent height: toUse]. self positionReferent. self adjustPositionVisAVisFlap! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/20/1999 22:29'! changeColor self isCurrentlyGraphical ifTrue: [^ self inform: 'Color only pertains to a flap tab when the tab is textual or "solid". This tab is currently graphical, so color-choice does not apply.' ]. super changeColor ! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/20/1999 23:14'! changeFlapColor (self flapShowing) ifTrue: [referent changeColor] ifFalse: [self inform: 'The flap itself needs to be open before you can change its color.']! ! !FlapTab methodsFor: 'menu' stamp: 'sw 3/25/1999 10:52'! destroyFlap | reply request | request _ self isGlobal ifTrue: ['Caution -- this would permanently remove this flap, so it would no longer be available in this or any other project. Do you really want to this? '] ifFalse: ['Caution -- this is permanent!! Do you really want to do this? ']. reply _ self confirm: request orCancel: [^ self]. reply ifTrue: [self isGlobal ifTrue: [Utilities removeFlapTab: self keepInList: false] ifFalse: [referent isInWorld ifTrue: [referent delete]. self delete]]! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/17/1999 14:20'! dismissViaHalo referent delete. self delete.! ! !FlapTab methodsFor: 'menu' stamp: 'sw 7/8/1999 15:44'! flapMenuTitle ^ 'flap: ', self wording! ! !FlapTab methodsFor: 'menu' stamp: 'sw 6/14/1999 16:38'! thicknessString ^ 'thickness... (currently ', self thickness printString, ')'! ! !FlapTab methodsFor: 'menu' stamp: 'ar 12/18/2000 16:38'! wording ^ self isCurrentlyTextual ifTrue: [self existingWording] ifFalse: [self valueOfProperty: #priorWording ifAbsent: ['---']]! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 4/7/2000 07:52'! arrangeToPopOutOnDragOver: aBoolean aBoolean ifTrue: [self on: #mouseEnterDragging send: #showFlapIfHandLaden: to: self. referent on: #mouseLeaveDragging send: #maybeHideFlapOnMouseLeaveDragging to: self. self on: #mouseLeaveDragging send: #maybeHideFlapOnMouseLeaveDragging to: self] ifFalse: [self on: #mouseEnterDragging send: nil to: nil. referent on: #mouseLeaveDragging send: nil to: nil. self on: #mouseLeaveDragging send: nil to: nil]! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 2/16/1999 23:31'! arrangeToPopOutOnMouseOver: aBoolean aBoolean ifTrue: [self on: #mouseEnter send: #showFlap to: self. referent on: #mouseLeave send: #hideFlapUnlessBearingHalo to: self. self on: #mouseLeave send: #maybeHideFlapOnMouseLeave to: self] ifFalse: [self on: #mouseEnter send: nil to: nil. self on: #mouseLeave send: #nil to: nil. referent on: #mouseLeave send: nil to: nil]! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 9/21/2000 22:49'! dragoverString "Answer the string to be shown in a menu to represent the dragover status" ^ popOutOnDragOver ifTrue: ['pop out on dragover'] ifFalse: ['pop out on dragover']! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 9/21/2000 22:49'! mouseoverString "Answer the string to be shown in a menu to represent the mouseover status" ^ popOutOnMouseOver ifTrue: ['pop out on mouseover'] ifFalse: ['pop out on mouseover']! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 2/25/1999 14:53'! setToPopOutOnDragOver: aBoolean self arrangeToPopOutOnDragOver: (popOutOnDragOver _ aBoolean)! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 2/25/1999 14:52'! setToPopOutOnMouseOver: aBoolean self arrangeToPopOutOnMouseOver: (popOutOnMouseOver _ aBoolean)! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 2/15/1999 14:10'! toggleDragOverBehavior self arrangeToPopOutOnDragOver: (popOutOnDragOver _ popOutOnDragOver not)! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'sw 2/15/1999 14:07'! toggleMouseOverBehavior self arrangeToPopOutOnMouseOver: (popOutOnMouseOver _ popOutOnMouseOver not)! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 2/16/1999 18:13'! adjustPositionVisAVisFlap | sideToAlignTo opposite | opposite _ Utilities oppositeSideTo: edgeToAdhereTo. sideToAlignTo _ inboard ifTrue: [opposite] ifFalse: [edgeToAdhereTo]. self perform: (Utilities simpleSetterFor: sideToAlignTo) with: (referent perform: opposite)! ! !FlapTab methodsFor: 'positioning' stamp: 'ar 10/26/2000 17:52'! fitOnScreen "19 sept 2000 - allow flaps in any paste up" | constrainer | constrainer _ owner ifNil: [self]. self flapShowing "otherwise no point in doing this" ifTrue:[self spanWorld]. self orientation == #vertical ifTrue: [ self top: ((self top min: (constrainer bottom- self height)) max: constrainer top). ] ifFalse: [ self left: ((self left min: (constrainer right - self width)) max: constrainer left). ]. self flapShowing ifFalse: [self positionObject: self atEdgeOf: constrainer]. ! ! !FlapTab methodsFor: 'positioning' stamp: 'ar 10/26/2000 17:36'! layoutChanged self fitOnScreen. ^super layoutChanged! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 7/6/1999 10:08'! mouseMove: evt | aPosition newReferentThickness adjustedPosition thick | dragged ifFalse: [(thick _ self referentThickness) > 0 ifTrue: [lastReferentThickness _ thick]]. ((self containsPoint: (aPosition _ evt cursorPoint)) and: [dragged not]) ifFalse: [flapShowing ifFalse: [self showFlap]. adjustedPosition _ aPosition - evt hand targetOffset. (edgeToAdhereTo == #bottom) ifTrue: [newReferentThickness _ inboard ifTrue: [self world height - adjustedPosition y] ifFalse: [self world height - adjustedPosition y - self height]]. (edgeToAdhereTo == #left) ifTrue: [newReferentThickness _ inboard ifTrue: [adjustedPosition x + self width] ifFalse: [adjustedPosition x]]. (edgeToAdhereTo == #right) ifTrue: [newReferentThickness _ inboard ifTrue: [self world width - adjustedPosition x] ifFalse: [self world width - adjustedPosition x - self width]]. (edgeToAdhereTo == #top) ifTrue: [newReferentThickness _ inboard ifTrue: [adjustedPosition y + self height] ifFalse: [adjustedPosition y]]. self isCurrentlySolid ifFalse: [(#(left right) includes: edgeToAdhereTo) ifFalse: [self left: adjustedPosition x] ifTrue: [self top: adjustedPosition y]]. self applyThickness: newReferentThickness. dragged _ true. self fitOnScreen]! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 7/6/1999 08:54'! mouseUp: evt super mouseUp: evt. self referentThickness <= 0 ifTrue: [flapShowing _ false]. dragged _ false. self fitOnScreen.! ! !FlapTab methodsFor: 'positioning' stamp: 'ar 10/26/2000 17:36'! ownerChanged self fitOnScreen. ^super ownerChanged.! ! !FlapTab methodsFor: 'positioning' stamp: 'RAA 9/19/2000 12:16'! positionObject: anObject "anObject could be myself or my referent" "Could consider container _ referent pasteUpMorph, to allow flaps on things other than the world, but for the moment, let's skip it!!" "19 sept 2000 - going for all paste ups" ^self positionObject: anObject atEdgeOf: (self pasteUpMorph ifNil: [^ self])! ! !FlapTab methodsFor: 'positioning' stamp: 'RAA 6/14/2000 19:35'! positionObject: anObject atEdgeOf: container "anObject could be myself or my referent" edgeToAdhereTo == #left ifTrue: [^ anObject left: container left]. edgeToAdhereTo == #right ifTrue: [^ anObject right: container right]. edgeToAdhereTo == #top ifTrue: [^ anObject top: container top]. edgeToAdhereTo == #bottom ifTrue: [^ anObject bottom: container bottom]! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 2/16/1999 17:58'! positionReferent self positionObject: referent! ! !FlapTab methodsFor: 'positioning' stamp: 'ar 12/17/2000 22:28'! spanWorld | container | container _ self pasteUpMorph ifNil: [self currentWorld]. (self orientation == #vertical) ifTrue: [ referent vResizing == #rigid ifTrue:[referent height: container height]. referent hResizing == #rigid ifTrue:[referent width: (referent width min: container width - self width)]. referent top: container top. ] ifFalse: [ referent hResizing == #rigid ifTrue:[referent width: container width]. referent vResizing == #rigid ifTrue:[referent height: (referent height min: container height - self height)]. referent left: container left. ] ! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 2/11/1999 14:46'! stickOntoReferent "Place the receiver directly onto the referent -- for use when the referent is being shown as a flap" | newPosition | referent addMorph: self. edgeToAdhereTo == #left ifTrue: [newPosition _ (referent width - self width) @ self top]. edgeToAdhereTo == #right ifTrue: [newPosition _ (referent left @ self top)]. edgeToAdhereTo == #top ifTrue: [newPosition _ self left @ (referent height - self height)]. edgeToAdhereTo == #bottom ifTrue: [newPosition _ self left @ referent top]. self position: newPosition! ! !FlapTab methodsFor: 'positioning' stamp: 'sw 3/2/1999 12:27'! transposeParts "The receiver's orientation has just been changed from vertical to horizontal or vice-versa. One could imagine trying to be smart about transposition, though the variety of possibilities is daunting." self flag: #deferred. "edgeToAdhereTo == #vertical ifTrue: ..."! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 2/16/1999 17:58'! adjustPositionAfterHidingFlap self positionObject: self! ! !FlapTab methodsFor: 'show & hide' stamp: 'tk 1/31/2001 12:27'! hideFlap | aWorld | aWorld _ self world ifNil: [self currentWorld]. referent privateDelete. aWorld removeAccommodationForFlap: self. flapShowing _ false. self isInWorld ifFalse: [aWorld addMorphFront: self]. self adjustPositionAfterHidingFlap. aWorld haloMorphs do: [:m | m target isInWorld ifFalse: [m delete]]! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 12/29/1999 12:41'! hideFlapUnlessBearingHalo self hasHalo ifFalse: [self hideFlapUnlessOverReferent]! ! !FlapTab methodsFor: 'show & hide' stamp: 'RAA 6/16/2000 18:46'! hideFlapUnlessOverReferent | aWorld where | (referent isInWorld and: [ where _ self outermostWorldMorph activeHand lastEvent cursorPoint. referent bounds containsPoint: (referent globalPointToLocal: where) ] ) ifTrue: [^self]. aWorld _ self world. self referent delete. aWorld removeAccommodationForFlap: self. flapShowing _ false. self isInWorld ifFalse: [self inboard ifTrue: [aWorld addMorphFront: self]]. self adjustPositionAfterHidingFlap! ! !FlapTab methodsFor: 'show & hide' stamp: 'RAA 6/2/2000 14:07'! maybeHideFlapOnMouseLeave self hasHalo ifTrue: [^ self]. referent isInWorld ifFalse: [^ self]. self hideFlapUnlessOverReferent. ! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 3/5/1999 17:42'! maybeHideFlapOnMouseLeaveDragging | aWorld | self hasHalo ifTrue: [^ self]. referent isInWorld ifFalse: [^ self]. (dragged or: [referent bounds containsPoint: self cursorPoint]) ifTrue: [^ self]. aWorld _ self world. referent privateDelete. "could make me worldless if I'm inboard" aWorld ifNotNil: [aWorld removeAccommodationForFlap: self]. flapShowing _ false. self isInWorld ifFalse: [aWorld addMorphFront: self]. self adjustPositionAfterHidingFlap! ! !FlapTab methodsFor: 'show & hide' stamp: 'RAA 9/19/2000 12:17'! showFlap | thicknessToUse flapOwner | "19 sept 2000 - going for all paste ups" flapOwner _ self pasteUpMorph. self referentThickness <= 0 ifTrue: [thicknessToUse _ lastReferentThickness ifNil: [100]. self orientation == #horizontal ifTrue: [referent height: thicknessToUse] ifFalse: [referent width: thicknessToUse]]. inboard ifTrue: [self stickOntoReferent]. "makes referent my owner, and positions me accordingly" referent pasteUpMorph == flapOwner ifFalse: [flapOwner accommodateFlap: self. "Make room if needed" flapOwner addMorphFront: referent. flapOwner startSteppingSubmorphsOf: referent. self positionReferent. referent adaptToWorld: flapOwner]. inboard ifFalse: [self adjustPositionVisAVisFlap]. flapShowing _ true. flapOwner bringFlapTabsToFront! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 4/7/2000 07:51'! showFlapIfHandLaden: evt "The hand has drifted over the receiver with the button down. If the hand is carrying anything, show the flap. If the hand is empty, the likely cause is that it's manipulating a scrollbar or some such, so in that case don't pop the flap out." evt hand hasSubmorphs ifTrue: [self showFlap]! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 12/29/1999 15:30'! succeededInRevealing: aPlayer (super succeededInRevealing: aPlayer) ifTrue: [^ true]. self flapShowing ifTrue: [^ false]. (referent succeededInRevealing: aPlayer) ifTrue: [self showFlap. aPlayer costume addHalo. ^ true]. ^ false! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 2/11/1999 15:52'! tabSelected dragged == true ifTrue: [^ dragged _ false]. self flapShowing ifTrue: [self hideFlap] ifFalse: [self showFlap]! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 6/21/1999 11:09'! assumeString: aString font: aFont orientation: anOrientation color: aColor | aTextMorph workString pad | pad _ (anOrientation == #vertical) ifTrue: [Character cr] ifFalse: [$ ]. workString _ pad asString. aString do: [:ch | workString _ workString copyWith: ch. workString _ workString copyWith: pad]. (anOrientation == #vertical) ifTrue: [workString _ workString copyFrom: 2 to: workString size - 1]. aTextMorph _ (TextMorph new beAllFont: aFont) width: 10; contents: workString; yourself. self removeAllMorphs. self addMorph: aTextMorph centered. aTextMorph lock. anOrientation == #horizontal ifTrue: [self borderWidth: 0] ifFalse: [self borderWidth: 3; borderColor: #raised]. self fitContents. aColor ifNotNil: [self color: aColor]. aTextMorph position: self position. self layoutChanged! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sma 6/18/2000 10:35'! changeTabText | reply | reply _ FillInTheBlank request: 'new wording for this tab:' initialAnswer: self existingWording. reply isEmptyOrNil ifTrue: [^ self]. self useStringTab: reply. submorphs first delete. self assumeString: reply font: Preferences standardFlapFont orientation: (Utilities orientationForEdge: edgeToAdhereTo) color: nil! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 6/17/1999 13:33'! existingWording | longForm | longForm _ submorphs first contents. ^ self orientation == #vertical ifTrue: [longForm asString copyWithout: Character cr] ifFalse: [(longForm asString collectWithIndex: [:ch :i | i even ifFalse: [$»] ifTrue: [ch]]) copyWithout: $»]! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 6/20/1999 19:19'! isCurrentlyTextual | first | ^ submorphs size > 0 and: [((first _ submorphs first) isKindOf: StringMorph) or: [first isKindOf: TextMorph]]! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 12/8/1999 18:16'! reformatTextualTab "The font choice possibly having changed, reformulate the receiver" self isCurrentlyTextual ifFalse: [^ self]. self assumeString: self existingWording font: Preferences standardFlapFont orientation: self orientation color: self color! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 6/17/1999 13:21'! textualTab self isCurrentlyTextual ifTrue: [self changeTabText] ifFalse: [self useTextualTab]! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 6/20/1999 19:09'! textualTabString ^ self isCurrentlyTextual ifTrue: ['change tab wording...'] ifFalse: ['use textual tab']! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 2/11/1999 00:29'! useStringTab: aString | aLabel | aLabel _ StringMorph new contents: aString asString. self addMorph: aLabel. aLabel position: self position. aLabel highlightColor: self highlightColor; regularColor: self regularColor. aLabel lock. self fitContents. self layoutChanged! ! !FlapTab methodsFor: 'textual tabs' stamp: 'sw 12/8/1999 15:01'! useTextualTab | stringToUse colorToUse | self preserveDetails. colorToUse _ self valueOfProperty: #priorColor ifAbsent: [Color green muchLighter]. submorphs size > 0 ifTrue: [self removeAllMorphs]. stringToUse _ self valueOfProperty: #priorWording ifAbsent: ['Unnamed Flap']. self assumeString: stringToUse font: Preferences standardFlapFont orientation: self orientation color: colorToUse! ! !FlapTab methodsFor: 'graphical tabs' stamp: 'sw 6/17/1999 16:07'! graphicalTab self isCurrentlyGraphical ifTrue: [self changeTabGraphic] ifFalse: [self useGraphicalTab]! ! !FlapTab methodsFor: 'graphical tabs' stamp: 'sw 6/20/1999 19:10'! graphicalTabString ^ self isCurrentlyGraphical ifTrue: ['choose new graphic...'] ifFalse: ['use graphical tab']! ! !FlapTab methodsFor: 'graphical tabs' stamp: 'sw 6/20/1999 19:20'! isCurrentlyGraphical | first | ^ submorphs size > 0 and: [((first _ submorphs first) isKindOf: ImageMorph) or: [first isKindOf: SketchMorph]]! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 2/27/1999 13:16'! applyTabThickness: newThickness (self orientation == #vertical) ifTrue: [submorphs first width: newThickness asNumber] ifFalse: [submorphs first height: newThickness asNumber]. self fitContents. self positionReferent. self adjustPositionVisAVisFlap! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/21/1999 11:40'! changeTabSolidity "Presently no actual options associated with this menu item if the flap is currently alreadly solid, so entertain the user with an anuran sound. However, in latest scheme, the corresponding menu item is disabled in this circumstance, so this method is effectively unreachable." self playSoundNamed: 'croak'! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 2/27/1999 13:14'! changeTabThickness | newThickness | newThickness _ FillInTheBlank request: 'New thickness:' initialAnswer: self tabThickness printString. newThickness size > 0 ifTrue: [self applyTabThickness: newThickness]! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/21/1999 11:39'! isCurrentlySolid "Don't never use double negatives" ^ self notSolid not! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/21/1999 11:36'! notSolid "Answer whether the receiver is currenty not solid. Used for determining whether the #solidTab menu item should be enabled" ^ self isCurrentlyTextual or: [self isCurrentlyGraphical]! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/20/1999 21:34'! solidTab self isCurrentlySolid ifFalse: [self useSolidTab] ifTrue: [self changeTabSolidity]! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/20/1999 19:09'! solidTabString ^ self isCurrentlySolid ifTrue: ['currently using solid tab'] ifFalse: ['use solid tab']! ! !FlapTab methodsFor: 'solid tabs' stamp: 'sw 6/20/1999 20:55'! useSolidTab | thickness colorToUse | self preserveDetails. thickness _ self valueOfProperty: #priorThickness ifAbsent: [20]. colorToUse _ self valueOfProperty: #priorColor ifAbsent: [Color red muchLighter]. self color: colorToUse. self removeAllMorphs. (self orientation == #vertical) ifTrue: [self width: thickness. self height: self currentWorld height. self position: (self position x @ 0)] ifFalse: [self height: thickness. self width: self currentWorld width. self position: (0 @ self position y)]. self borderWidth: 0. self layoutChanged.! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 6/21/1999 13:03'! inboard ^ inboard == true! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 2/15/1999 12:57'! inboard: aBoolean inboard _ aBoolean! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 6/14/1999 16:37'! inboardString ^ inboard ifTrue: ['switch to being outboard'] ifFalse: ['switch to being inboard']! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 6/14/1999 16:35'! slideString ^ slidesOtherObjects ifTrue: ['cease slide behavior'] ifFalse: ['start slide behavior']! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 2/11/1999 10:55'! slidesOtherObjects ^ slidesOtherObjects! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 2/11/1999 14:18'! toggleInboardness self hideFlap. inboard _ inboard not. self showFlap! ! !FlapTab methodsFor: 'disused options' stamp: 'sw 2/11/1999 12:21'! toggleSlideBehavior slidesOtherObjects _ slidesOtherObjects not! ! !FlapTab methodsFor: 'rounding' stamp: 'ar 9/1/2000 13:49'! roundedCorners ^self orientation == #vertical ifTrue: [edgeToAdhereTo == #right ifTrue: [#(1 2)] ifFalse: [#(3 4)]] ifFalse: [edgeToAdhereTo == #top ifTrue: [#(2 3)] ifFalse: [#(1 4)]].! ! !FlapTab methodsFor: 'rounding' stamp: 'ar 9/1/2000 13:53'! wantsRoundedCorners ^self isCurrentlyTextual! ! !FlapTab methodsFor: 'globalness' stamp: 'sw 11/6/2000 14:15'! isGlobalFlap "Answer whether the receiver is currently a Global flap" ^ Utilities globalFlapTabsIfAny includes: self! ! !FlapTab methodsFor: 'globalness' stamp: 'sw 11/6/2000 15:38'! isGlobalFlapString "Answer a string to construct a menu item representing control over whether the receiver is or is not a global flap" ^ (self isGlobalFlap ifTrue: [''] ifFalse: ['']), 'global (sharable by all projects)'! ! !FlapTab methodsFor: 'globalness' stamp: 'sw 11/6/2000 14:56'! toggleIsGlobalFlap "Toggle whether the receiver is currently a global flap or not" | oldWorld | self hideFlap. "In case showing" oldWorld _ self currentWorld. self isGlobalFlap ifTrue: [Utilities removeFromGlobalFlapTabList: self. oldWorld addMorphFront: self] ifFalse: [self delete. Utilities addGlobalFlap: self. self currentWorld addGlobalFlaps] ! ! !FlapTab methodsFor: 'miscellaneous' stamp: 'ar 9/28/2000 13:53'! isFlapTab ^true! ! !FlapTab methodsFor: 'miscellaneous' stamp: 'sw 11/6/2000 14:07'! mightEntertainDirectionHandles "Answer whether the receiver, by its very nature, might possibly be happy to have direction handles shown for it in its halo" ^ false! ! !FlapTab methodsFor: 'miscellaneous' stamp: 'sw 11/6/2000 15:41'! printOn: aStream "Append a textual representation of the receiver to aStream" super printOn: aStream. aStream nextPutAll: ' "', self wording, '"'! ! !FlapTab methodsFor: 'fileIn/out' stamp: 'tk 9/28/2000 15:42'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. If I am a global flap, write a proxy instead." (Utilities globalFlapTabsIfAny includes: self) ifTrue: [ dp _ DiskProxy global: #Utilities selector: #globalFlapTabOrDummy: args: {self flapMenuTitle}. refStrm replace: self with: dp. ^ dp]. ^ super objectForDataStream: refStrm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlapTab class instanceVariableNames: ''! !FlapTab class methodsFor: 'as yet unclassified' stamp: 'sw 2/11/1999 14:39'! defaultNameStemForInstances ^ 'flap tab'! ! !FlapTab class methodsFor: 'as yet unclassified' stamp: 'kfr 5/3/2000 12:51'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! CompressedBoundaryShape subclass: #FlashBoundaryShape instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashBoundaryShape methodsFor: 'private' stamp: 'ar 11/3/1998 21:54'! remapFills "Replace the fill style dictionary with an array" | indexMap newFillStyles index | (fillStyles isKindOf: Dictionary) ifFalse:[^false]. indexMap _ Dictionary new. indexMap at: 0 put: 0. "Map zero to zero" newFillStyles _ Array new: fillStyles size. index _ 1. fillStyles associationsDo:[:assoc| indexMap at: assoc key put: index. newFillStyles at: index put: assoc value. index _ index + 1. ]. leftFills _ leftFills valuesCollect:[:value| indexMap at: value ifAbsent:[0]]. rightFills _ rightFills valuesCollect:[:value| indexMap at: value ifAbsent:[0]]. lineFills _ lineFills valuesCollect:[:value| indexMap at: value ifAbsent:[0]]. fillStyles _ newFillStyles! ! !FlashBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/9/1998 02:30'! complexity ^points size // 3! ! !FlashBoundaryShape methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:44'! compress (points isKindOf: String) ifFalse:[ points _ FlashCodec compress: self. leftFills _ rightFills _ lineWidths _ lineFills _ fillStyles _ nil].! ! !FlashBoundaryShape methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:46'! decompress | newShape | (points isKindOf: String) ifTrue:[ newShape _ FlashCodec decompress: (ReadStream on: points). points _ newShape points. leftFills _ newShape leftFills. rightFills _ newShape rightFills. lineWidths _ newShape lineWidths. lineFills _ newShape lineFills. fillStyles _ newShape fillStyles].! ! FlashCharacterMorph subclass: #FlashButtonMorph instanceVariableNames: 'events sounds target ' classVariableNames: 'ActionHelpText ' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashButtonMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 21:04'! defaultLook: aMorph "Assign the default look" aMorph setProperty: #defaultLook toValue: true. self addMorph: aMorph.! ! !FlashButtonMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 21:26'! loadInitialFrame "Resort our children" super loadInitialFrame. submorphs _ submorphs sortBy:[:m1 :m2| m1 depth > m2 depth]. self lookEnable: #(defaultLook) disable:#(sensitive overLook pressLook)! ! !FlashButtonMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 21:25'! overLook: aMorph "Assign the look if the mouse if over" aMorph setProperty: #overLook toValue: true. self addMorph: aMorph.! ! !FlashButtonMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 21:25'! pressLook: aMorph "Assign the look if the mouse is pressed" aMorph setProperty: #pressLook toValue: true. self addMorph: aMorph.! ! !FlashButtonMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 21:04'! sensitiveLook: aMorph "Assign the look for the sensitive area." aMorph setProperty: #sensitive toValue: true. self addMorph: aMorph! ! !FlashButtonMorph methodsFor: 'accessing' stamp: 'ar 10/15/1998 21:16'! addSound: aSound forState: state sounds ifNil:[sounds _ Dictionary new]. sounds at: state put: aSound.! ! !FlashButtonMorph methodsFor: 'accessing' stamp: 'di 11/12/2000 15:59'! ownerSprite "Return the sprite owning the receiver. The owning sprite is responsible for executing the actions associated with the button." ^ self orOwnerSuchThat: [:sprite | sprite isFlashMorph and: [sprite isFlashSprite]]! ! !FlashButtonMorph methodsFor: 'accessing' stamp: 'ar 11/20/1998 02:03'! trackAsMenu: aBoolean "Currently unused" aBoolean ifTrue:[self setProperty: #trackAsMenu toValue: true] ifFalse:[self removeProperty: #trackAsMenu].! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/12/1999 15:47'! executeActions: type | rcvr | (events isNil or:[events isEmpty]) ifTrue:[^self]. rcvr _ target. rcvr ifNil:[rcvr _ self ownerSprite]. rcvr isNil ifTrue:[^self]. (events at: type ifAbsent:[^self]) do:[:action| action sentTo: rcvr. ].! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/10/1999 04:16'! executeSounds: type | sound | (sounds isNil or:[sounds isEmpty]) ifTrue:[^self]. sound _ sounds at: type ifAbsent:[^self]. sound isPlaying & false ifFalse:[sound play].! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/16/1998 23:25'! handlesMouseDown: evt ^self visible! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/19/1998 20:32'! handlesMouseOver: evt "Handle mouse events only if I am visible," ^self visible! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/16/1998 23:24'! handlesMouseOverDragging: evt ^false! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/10/1999 04:32'! mouseDown: evt self lookEnable: #(pressLook) disable:#(overLook). self executeSounds: #mouseDown. self executeActions: #mouseDown.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/21/1998 02:30'! mouseEnter: evt self lookEnable: #(overLook) disable:#(pressLook defaultLook). evt hand needsToBeDrawn ifFalse:[Cursor webLink show]. self executeSounds: #mouseEnter. evt anyButtonPressed ifTrue:[self executeActions: #mouseEnterDown] ifFalse:[self executeActions: #mouseEnter].! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/21/1998 02:30'! mouseLeave: evt self lookEnable: #(defaultLook) disable:#(pressLook overLook). evt hand needsToBeDrawn ifFalse:[Cursor normal show]. self executeSounds: #mouseLeave. evt anyButtonPressed ifTrue:[self executeActions: #mouseLeaveDown] ifFalse:[self executeActions: #mouseLeave].! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 10/15/1998 21:08'! mouseMove: evt! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/10/1999 04:33'! mouseUp: evt self lookEnable:#(overLook) disable:#(pressLook). self executeSounds: #mouseUp. self executeActions: #mouseUp.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/12/1999 15:48'! on: eventName send: action "Note: We handle more than the standard Morphic events here" ^self on: eventName sendAll:(Array with: action).! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 11/20/1998 02:09'! on: eventName sendAll: actions "Note: We handle more than the standard Morphic events here" | actionList | events ifNil:[events _ Dictionary new]. self analyzeActionsForBalloonHelp: actions. actionList _ events at: eventName ifAbsent:[#()]. actionList _ actionList, actions. events at: eventName put: actionList.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/11/1999 03:52'! simulateMouseDown "Invoked from a client -- simulate mouse down" self lookEnable: #(pressLook) disable:#(overLook). self executeSounds: #mouseDown. self executeActions: #mouseDown.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/11/1999 03:52'! simulateMouseEnter "Invoked from a client -- simulate mouseEnter" self lookEnable: #(overLook) disable:#(pressLook defaultLook). self executeSounds: #mouseEnter. self executeActions: #mouseEnter.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/11/1999 03:53'! simulateMouseLeave "Invoked from a client -- simulate mouse leave" self lookEnable: #(defaultLook) disable:#(pressLook overLook). self executeSounds: #mouseLeave. self executeActions: #mouseLeave.! ! !FlashButtonMorph methodsFor: 'event handling' stamp: 'ar 2/11/1999 03:53'! simulateMouseUp "Invoked from a client -- simulate mouse up" self lookEnable:#(overLook) disable:#(pressLook). self executeSounds: #mouseUp. self executeActions: #mouseUp.! ! !FlashButtonMorph methodsFor: 'geometry' stamp: 'ar 11/16/1998 21:46'! containsPoint: aPoint | localPt | (self bounds containsPoint: aPoint) ifFalse:[^false]. localPt _ self transform globalPointToLocal: aPoint. submorphs do:[:m| ((m valueOfProperty: #sensitive) ifNil:[false]) ifTrue:[ (m bounds containsPoint: localPt) ifTrue:[^true]. ]. ]. ^false! ! !FlashButtonMorph methodsFor: 'geometry' stamp: 'ar 11/16/1998 21:09'! lookEnable: list1 disable: list2 self changed. submorphs do:[:m| list2 do:[:sym| ((m valueOfProperty: sym) ifNil:[false]) ifTrue:[m visible: false]. ]. list1 do:[:sym| ((m valueOfProperty: sym) ifNil:[false]) ifTrue:[m visible: true]. ]. ]. self computeBounds. self changed.! ! !FlashButtonMorph methodsFor: 'classification' stamp: 'ar 11/16/1998 23:47'! isFlashButton ^true! ! !FlashButtonMorph methodsFor: 'classification' stamp: 'ar 11/19/1998 22:22'! isMouseSensitive "Return true if the receiver is mouse sensitive and must stay unlocked" ^true! ! !FlashButtonMorph methodsFor: 'balloon help' stamp: 'ar 11/20/1998 15:15'! analyzeActionsForBalloonHelp: actionList | helpText | actionList do:[:msg| helpText _ ActionHelpText at: msg selector ifAbsent:[nil]. helpText ifNotNil:[self setBalloonText: helpText]. ]. ! ! !FlashButtonMorph methodsFor: 'printing' stamp: 'ar 11/21/1998 01:36'! printOn: aStream super printOn: aStream. events ifNil:[^self]. aStream nextPut:$[. events keys do:[:k| aStream print: k; space]. aStream nextPut: $].! ! !FlashButtonMorph methodsFor: 'menu' stamp: 'ar 2/23/1999 00:37'! addCustomAction | string code | string _ FillInTheBlank request:'Enter the Smalltalk code to execute:' initialAnswer:'Smalltalk beep.'. string isEmpty ifTrue:[^self]. string _ '[', string,']'. code _ Compiler evaluate: string for: self notifying: nil logged: false. self removeActions. target _ code. self on: #mouseDown send:(Message selector: #value).! ! !FlashButtonMorph methodsFor: 'menu' stamp: 'ar 2/23/1999 00:27'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. aCustomMenu add: 'set custom action' action: #addCustomAction. aCustomMenu add: 'remove all actions' action: #removeActions. ! ! !FlashButtonMorph methodsFor: 'menu' stamp: 'ar 2/23/1999 00:37'! removeActions events _ nil. target _ nil.! ! !FlashButtonMorph methodsFor: 'event processing' stamp: 'ar 9/12/2000 23:05'! handlerForMouseDown: anEvent "Don't give anybody over me a chance" ^self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashButtonMorph class instanceVariableNames: ''! !FlashButtonMorph class methodsFor: 'class initialization' stamp: 'ar 11/20/1998 22:58'! initialize "FlashButtonMorph initialize" ActionHelpText _ Dictionary new. #( (getURL:window: 'Jump to URL') (gotoFrame: 'Continue playing') (gotoLabel: 'Continue playing') (gotoNextFrame 'Continue playing') (gotoPrevFrame 'Continue playing') (actionPlay 'Continue playing') (actionStop 'Stop playing') (stopSounds 'Stop all sounds') (toggleQuality 'Toggle display quality') ) do:[:spec| ActionHelpText at: spec first put: spec last].! ! FlashMorph subclass: #FlashCharacterMorph instanceVariableNames: 'id stepTime frame renderTime vData mData dData cmData rData ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashCharacterMorph methodsFor: 'initialize' stamp: 'ar 9/3/1999 18:03'! initialize super initialize. frame _ 1. self reset.! ! !FlashCharacterMorph methodsFor: 'initialize' stamp: 'ar 9/1/1999 15:25'! loadInitialFrame "Force the transformations taking place in the first frame." super loadInitialFrame. self stepToFrame: 1. (self isSpriteHolder and:[self visible]) ifTrue:[self activateSprites: true].! ! !FlashCharacterMorph methodsFor: 'initialize' stamp: 'ar 9/3/1999 18:02'! reset self removeAllKeyFrameData. self matrix: MatrixTransform2x3 identity atFrame: 0. self visible: false atFrame: 0. self depth: 0 atFrame: 0. self ratio: 0.0 atFrame: 0. self visible: true. ! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 11/13/1998 13:40'! activationKeys "Return the keyframes on which the receiver morph becomes visible" ^self visibleData keys select:[:key| self visibleAtFrame: key]! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 10/3/1998 21:39'! depth ^self depthAtFrame: frame! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 8/14/1998 18:19'! id ^id! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 8/14/1998 18:19'! id: aNumber id _ aNumber! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 11/24/1998 14:32'! isSpriteHolder ^self hasProperty: #spriteHolder! ! !FlashCharacterMorph methodsFor: 'accessing' stamp: 'ar 11/24/1998 14:32'! isSpriteHolder: aBoolean aBoolean ifTrue:[self setProperty: #spriteHolder toValue: true] ifFalse:[self removeProperty: #spriteHolder]! ! !FlashCharacterMorph methodsFor: 'classification' stamp: 'ar 8/14/1998 21:52'! isFlashCharacter ^true! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/24/1998 14:50'! colorTransform: aColorTransform atFrame: frameNumber self colorTransformData at: frameNumber put: aColorTransform! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/24/1998 14:51'! colorTransformAtFrame: frameNumber ^self colorTransformData at: frameNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/24/1998 14:51'! colorTransformData ^cmData "^self keyframeData: #colorMatrixData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 8/14/1998 19:20'! depth: aNumber atFrame: frameNumber self depthData at: frameNumber put: aNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 8/14/1998 19:20'! depthAtFrame: frameNumber ^self depthData at: frameNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:15'! depthData ^dData "^self keyframeData: #depthData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 17:11'! matrix: aMatrixTransform atFrame: frameNumber "self position: aMatrixTransform offset atFrame: frameNumber." self matrixData at: frameNumber put: aMatrixTransform.! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 17:12'! matrixAtFrame: frameNumber ^(self matrixData at: frameNumber) "copy offset: (self positionAtFrame: frameNumber)"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:15'! matrixData ^mData "^self keyframeData: #matrixData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 9/3/1999 15:20'! ratio: aNumber atFrame: frameNumber ^self ratioData at: frameNumber put: aNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 9/3/1999 15:20'! ratioAtFrame: frameNumber ^self ratioData at: frameNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 9/3/1999 15:24'! ratioData ^rData! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 9/3/1999 15:20'! removeAllKeyFrameData "Remove all of the keyframe data associated with this morph" self removeColorMatrixData. self removeDepthData. self removeMatrixData. self removeVisibleData. self removeRatioData.! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:16'! removeColorMatrixData cmData _ FlashKeyframes new. "^self removeKeyframeData: #colorMatrixData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:16'! removeDepthData dData _ FlashKeyframes new. "^self removeKeyframeData: #depthData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:16'! removeMatrixData mData _ FlashKeyframes new. "^self removeKeyframeData: #matrixData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 9/3/1999 15:30'! removeRatioData rData _ FlashKeyframes new.! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:16'! removeVisibleData vData _ FlashKeyframes new. "^self removeKeyframeData: #visibilityData"! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 17:13'! visible: aBool atFrame: frameNumber ^self visibleData at: frameNumber put: aBool! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 8/14/1998 19:23'! visibleAtFrame: frameNumber ^self visibleData at: frameNumber! ! !FlashCharacterMorph methodsFor: 'keyframe data' stamp: 'ar 11/13/1998 15:16'! visibleData ^vData "^self keyframeData: #visibilityData"! ! !FlashCharacterMorph methodsFor: 'stepping' stamp: 'ar 8/14/1998 18:17'! stepTime ^stepTime ifNil:[super stepTime]! ! !FlashCharacterMorph methodsFor: 'stepping' stamp: 'ar 8/14/1998 18:18'! stepTime: aNumber stepTime _ aNumber! ! !FlashCharacterMorph methodsFor: 'stepping' stamp: 'ar 11/24/1998 14:52'! stepToFrame: frameNumber | m wasVisible isVisible noTransform cm | wasVisible _ self visible. self visible: (self visibleAtFrame: frameNumber). isVisible _ self visible. frame _ frameNumber. isVisible ifTrue:[ m _ self matrixAtFrame: frame. cm _ self colorTransformAtFrame: frame. noTransform _ (m = transform) and:[colorTransform = cm]. (noTransform and:[isVisible = wasVisible]) ifTrue:[^self]. "No change" ((noTransform not) and:[wasVisible]) ifTrue:[ "Invalidate with old transform" self changed. ]. self transform: m. self colorTransform: cm. ((noTransform not) and:[isVisible]) ifTrue:[ "Invalidate with new transform" self changed. ]. ((noTransform) and:[isVisible ~~ wasVisible]) ifTrue:[ "Invalidate with new transform" self changed. ]. ] ifFalse:[ wasVisible ifTrue:[self changed]. ]. (isVisible ~~ wasVisible and:[self isSpriteHolder]) ifTrue:[self activateSprites: isVisible].! ! !FlashCharacterMorph methodsFor: 'stepping' stamp: 'ar 8/14/1998 21:03'! stepToNextFrame self stepToFrame: frame + 1.! ! !FlashCharacterMorph methodsFor: 'stepping' stamp: 'ar 11/13/1998 14:02'! wantsSteps ^false "^stepTime notNil"! ! !FlashCharacterMorph methodsFor: 'private' stamp: 'ar 11/24/1998 14:34'! activateSprites: aBool submorphs do:[:m| (m isFlashMorph and:[m isFlashSprite]) ifTrue:[ aBool ifTrue:[m startPlaying] ifFalse:[m stopPlaying]. ]. ].! ! !FlashCharacterMorph methodsFor: 'private' stamp: 'ar 5/19/1999 18:58'! isVisibleBetween: firstFrame and: lastFrame firstFrame to: lastFrame do:[:frameNr| (self visibleAtFrame: frameNr) ifTrue:[^true]]. ^false! ! !FlashCharacterMorph methodsFor: 'private' stamp: 'ar 8/14/1998 20:03'! keyframeData: aSymbol | data | data _ self valueOfProperty: aSymbol. data isNil ifFalse:[^data]. data _ FlashKeyframes new. self setProperty: aSymbol toValue: data. ^data! ! !FlashCharacterMorph methodsFor: 'private' stamp: 'ar 9/20/1998 23:41'! removeKeyframeData: aSymbol self removeProperty: aSymbol.! ! !FlashCharacterMorph methodsFor: 'printing' stamp: 'ar 9/1/1999 15:19'! printOn: aStream super printOn: aStream. aStream nextPutAll:'(renderTime = '; print: renderTime; nextPutAll:'; depth = '; print: self depth; "nextPutAll:' complexity = '; print: self complexity * bounds area // 1000 / 1000.0;" "nextPutAll:' size = '; print: bounds area;" nextPutAll:')'.! ! !FlashCharacterMorph methodsFor: 'drawing' stamp: 'ar 11/17/1998 17:52'! fullDrawOn: canvas renderTime _ Time millisecondsToRun:[super fullDrawOn: canvas].! ! !FlashCharacterMorph methodsFor: 'copying' stamp: 'ar 5/19/1999 19:07'! copyMovieFrom: firstFrame to: lastFrame | copy newFrame | copy _ super copyMovieFrom: firstFrame to: lastFrame. copy reset. copy visible: false atFrame: 0. firstFrame to: lastFrame do:[:i| newFrame _ i - firstFrame + 1. copy visible: (self visibleAtFrame: i) atFrame: newFrame. copy matrix: (self matrixAtFrame: i) atFrame: newFrame. copy depth: (self depthAtFrame: i) atFrame: newFrame. copy colorTransform: (self colorTransformAtFrame: i) atFrame: newFrame. ]. ^copy! ! !FlashCharacterMorph methodsFor: 'menu' stamp: 'ar 6/2/1999 04:17'! addCustomMenuItems: aMenu hand: aHand super addCustomMenuItems: aMenu hand: aHand. aMenu add:'add project target' action: #addProjectTarget. aMenu add:'remove project target' action: #removeProjectTarget.! ! !FlashCharacterMorph methodsFor: 'menu' stamp: 'ar 6/2/1999 04:18'! addProjectTarget | player fill | player _ self flashPlayer. player ifNil:[^self inform:'I must be in a flash player for this']. (submorphs size = 1 and:[submorphs first isFlashShape]) ifFalse:[^self inform:'Cannot use me as a project target']. fill _ submorphs first fillForProjectTarget. fill ifNil:[^self inform:'No suitable fill style found']. player addFillForProjectTarget: fill.! ! !FlashCharacterMorph methodsFor: 'menu' stamp: 'ar 6/2/1999 04:18'! removeProjectTarget | player fill | player _ self flashPlayer. player ifNil:[^self inform:'I must be in a flash player for this']. (submorphs size = 1 and:[submorphs first isFlashShape]) ifFalse:[^self inform:'Cannot use me as a project target']. fill _ submorphs first fillForProjectTarget. fill ifNil:[^self inform:'No suitable fill style found']. player removeFillForProjectTarget: fill.! ! Object subclass: #FlashCodec instanceVariableNames: 'stream ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashCodec methodsFor: 'accessing'! compress: aShape self compressPoints: aShape points. self compressRunArray: aShape leftFills. self compressRunArray: aShape rightFills. self compressRunArray: aShape lineWidths. self compressRunArray: aShape lineFills. self compressFills: aShape fillStyles. ^stream contents! ! !FlashCodec methodsFor: 'accessing'! contents ^stream contents! ! !FlashCodec methodsFor: 'accessing'! decompress | points leftFills rightFills lineWidths lineFills fillStyles | points _ self decompressPoints. leftFills _ self decompressRunArray. rightFills _ self decompressRunArray. lineWidths _ self decompressRunArray. lineFills _ self decompressRunArray. fillStyles _ self decompressFills. ^FlashBoundaryShape points: points leftFills: leftFills rightFills: rightFills fillStyles: fillStyles lineWidths: lineWidths lineFills: lineFills! ! !FlashCodec methodsFor: 'initialize'! initialize stream _ WriteStream on: (String new: 1000).! ! !FlashCodec methodsFor: 'initialize'! on: aStream stream _ aStream! ! !FlashCodec methodsFor: 'compressing points'! compressPoints: points "Compress the points using delta values and RLE compression." | lastPt runLength runValue nextPt deltaPt | points class == ShortPointArray ifTrue:[stream print: points size] ifFalse:[points class == PointArray ifTrue:[stream print: points size negated] ifFalse:[self error:'The point array has the wrong type!!']]. points size = 0 ifTrue:[^self]. lastPt _ points at: 1. "First point has no delta" self printCompressedPoint: lastPt on: stream runLength: 1. runLength _ 0. runValue _ nil. 2 to: points size do:[:i| nextPt _ points at: i. deltaPt _ nextPt - lastPt. runValue = deltaPt ifTrue:[ runLength _ runLength + 1. ]ifFalse:[ self printCompressedPoint: runValue on: stream runLength: runLength. runValue _ deltaPt. runLength _ 1. ]. lastPt _ nextPt]. runLength > 0 ifTrue:[self printCompressedPoint: runValue on: stream runLength: runLength]. stream nextPut:$X."Terminating character" ^stream! ! !FlashCodec methodsFor: 'compressing points'! decompressPoints "Decompress the points using delta values and RLE compression." | pts n index runValue spl runLength c x y | n _ Integer readFrom: stream. n = 0 ifTrue:[^ShortPointArray new]. n < 0 ifTrue:[ n _ 0 - n. pts _ PointArray new: n] ifFalse:[pts _ ShortPointArray new: n]. index _ 0. runValue _ 0@0. "Prefetch special character" spl _ stream next. [index < n] whileTrue:[ "Read runLength/value" spl = $* ifTrue:[ "Run length follows" runLength _ 0. [(c _ stream next) isDigit] whileTrue:[runLength _ (runLength * 10) + c digitValue]. spl _ c. ] ifFalse:[runLength _ 1]. "Check for special zero point" (spl = $Z or:[spl = $A]) ifTrue:[ "Since deltaPt is 0@0 there is no need to update runValue. Just prefetch the next special character" spl = $A ifTrue:[runLength _ 2]. spl _ stream next. ] ifFalse:["Regular point" "Fetch absolute delta x value" x _ 0. [(c _ stream next) isDigit] whileTrue:[x _ (x * 10) + c digitValue]. "Sign correct x" spl = $- ifTrue:[x _ 0 - x] ifFalse:[spl = $+ ifFalse:[self error:'Bad special character']]. spl _ c. "Fetch absolute delta y value" y _ 0. [(c _ stream next) isDigit] whileTrue:[y _ (y * 10) + c digitValue]. "Sign correct y" spl = $- ifTrue:[y _ 0 - y] ifFalse:[spl = $+ ifFalse:[self error:'Bad special character']]. spl _ c. "Compute absolute run value" runValue _ runValue + (x@y). ]. "And store points" 1 to: runLength do:[:i| pts at: (index _ index + 1) put: runValue]. ]. "Last char must be X" spl = $X ifFalse:[self error:'Bad special character']. ^pts! ! !FlashCodec methodsFor: 'compressing points'! printCompressedPoint: aPoint on: aStream runLength: n "Print the given point on the stream using the given run length" n = 0 ifTrue:[^self]. "Can only happen for the first run" "Check if we're storing a zero point" (aPoint x = 0 and:[aPoint y = 0]) ifTrue:[ "Two zero points are specially encoded since they occur if a line segment ends and the next segment starts from its end point, e.g., (p1,p2,p2) (p2,p3,p4) - this is very likely." n = 2 ifTrue:[^aStream nextPut:$A]. n = 1 ifTrue:[^aStream nextPut: $Z]. ^aStream nextPut:$*; print: n; nextPut:$Z]. n > 1 ifTrue:[ "Run length encoding: '*N' repeat the following point n times" aStream nextPut: $*; print: n]. "Point encoding: Two numbers. Number encoding: '+XYZ' or '-XYZ'" self printPoint: aPoint on: aStream! ! !FlashCodec methodsFor: 'compressing points'! printPoint: aPoint on: aStream aPoint x < 0 ifTrue:[aStream print: aPoint x] ifFalse:[aStream nextPut: $+; print: aPoint x]. aPoint y < 0 ifTrue:[aStream print: aPoint y] ifFalse:[aStream nextPut: $+; print: aPoint y].! ! !FlashCodec methodsFor: 'compressing points'! readPointFrom: aStream | sign x y | sign _ aStream next. x _ Integer readFrom: aStream. sign = $- ifTrue:[x _ 0-x]. sign _ aStream next. y _ Integer readFrom: aStream. sign = $- ifTrue:[y _ 0-y]. ^x@y! ! !FlashCodec methodsFor: 'compressing fills'! compressFillStyle: aFillStyle aFillStyle isSolidFill ifTrue:[^self compressSolidFill: aFillStyle]. aFillStyle isGradientFill ifTrue:[^self compressGradientFill: aFillStyle]. aFillStyle isBitmapFill ifTrue:[^self compressBitmapFill: aFillStyle]. self error:'Unknown fill style'! ! !FlashCodec methodsFor: 'compressing fills'! compressFills: anArray stream print: anArray size. anArray do:[:fillStyle| self compressFillStyle: fillStyle]. stream nextPut:$X. "Terminator"! ! !FlashCodec methodsFor: 'compressing fills'! compressGradientFill: aFillStyle "Note: No terminators for simple colors" | ramp key | aFillStyle radial ifTrue:[stream nextPut: $R] " 'R'adial gradient" ifFalse:[stream nextPut: $L]. " 'L' inear gradient" self printPoint: aFillStyle origin on: stream. self printPoint: aFillStyle direction on: stream. self printPoint: aFillStyle normal on: stream. ramp _ aFillStyle colorRamp. stream nextPut: $+; print: ramp size. ramp do:[:assoc| key _ (assoc key * 255) truncated. stream nextPut: (Character value: key). self storeColor: assoc value on: stream]. stream nextPut:$X. "Terminator"! ! !FlashCodec methodsFor: 'compressing fills'! compressSolidFill: aFillStyle "Note: No terminators for simple colors" stream nextPut: $S. " 'S'olid fill" self storeColor: aFillStyle asColor on: stream.! ! !FlashCodec methodsFor: 'compressing fills'! decompressFillStyle | type | type _ stream next. type = $S ifTrue:[^self decompressSolidFill]. type = $R ifTrue:[^self decompressGradientFill: true]. type = $L ifTrue:[^self decompressGradientFill: false]. type = $B ifTrue:[^self decompressBitmapFill]. ^self error:'Unknown fill type'! ! !FlashCodec methodsFor: 'compressing fills'! decompressFills | fills n | n _ Integer readFrom: stream. fills _ Array new: n. 1 to: n do:[:i| fills at: i put: self decompressFillStyle. ]. stream next = $X ifFalse:[^self error:'Compression problem']. ^fills! ! !FlashCodec methodsFor: 'compressing fills' stamp: 'mir 3/2/2000 13:21'! decompressGradientFill: radial "Note: No terminators for simple colors" | ramp fs rampSize rampIndex rampColor | fs _ GradientFillStyle new. fs radial: radial. fs origin: (self readPointFrom: stream). fs direction: (self readPointFrom: stream). fs normal: (self readPointFrom: stream). stream next = $+ ifFalse:[self error:'Negative Array size']. rampSize _ Integer readFrom: stream. ramp _ Array new: rampSize. 1 to: rampSize do:[:i| rampIndex _ stream next asciiValue / 255.0. rampColor _ self readColorFrom: stream. ramp at: i put: (rampIndex -> rampColor)]. fs colorRamp: ramp. fs pixelRamp. "Force computation" stream next = $X ifFalse:[^self error:'Compressio problem']. ^fs! ! !FlashCodec methodsFor: 'compressing fills'! decompressSolidFill | color | color _ self readColorFrom: stream. ^SolidFillStyle color: color! ! !FlashCodec methodsFor: 'compressing fills' stamp: 'ar 7/20/1999 16:05'! readColorFrom: aStream | pv | pv _ stream next asciiValue + (stream next asciiValue bitShift: 8) + (stream next asciiValue bitShift: 16) + (stream next asciiValue bitShift: 24). ^Color colorFromPixelValue: pv depth: 32! ! !FlashCodec methodsFor: 'compressing fills' stamp: 'mir 1/17/2000 15:12'! storeColor: color on: aStream | pv | pv _ color pixelWordForDepth: 32. aStream nextPut: (pv digitAt: 1) asCharacter; nextPut: (pv digitAt: 2) asCharacter; nextPut: (pv digitAt: 3) asCharacter; nextPut: (pv digitAt: 4) asCharacter. ! ! !FlashCodec methodsFor: 'compressing run arrays'! compressRunArray: aShortRunArray stream nextPut:$+; print: aShortRunArray runSize. aShortRunArray lengthsAndValuesDo:[:runLength :runValue| runLength < 0 ifTrue:[self error:'Bad run length']. stream nextPut:$+; print: runLength. runValue < 0 ifTrue:[stream print: runValue] ifFalse:[stream nextPut:$+; print: runValue]. ]. stream nextPut:$X. "Terminator" ^stream! ! !FlashCodec methodsFor: 'compressing run arrays'! decompressRunArray | n array runIndex runLength runValue spl c | stream next = $+ ifFalse:[self error:'Negative array size']. n _ Integer readFrom: stream. array _ ShortRunArray basicNew: n. runIndex _ 0. spl _ stream next. [runIndex < n] whileTrue:[ "Read runLength" runLength _ 0. [(c _ stream next) isDigit] whileTrue:[runLength _ (runLength * 10) + c digitValue]. spl = $+ ifFalse:[self error:'Negative run length']. "Read run value" spl _ c. runValue _ 0. [(c _ stream next) isDigit] whileTrue:[runValue _ (runValue * 10) + c digitValue]. spl = $- ifTrue:[runValue _ 0 - runValue] ifFalse:[spl = $+ ifFalse:[self error:'Compression problem']]. array setRunAt: (runIndex _ runIndex+1) toLength: runLength value: runValue. spl _ c. ]. spl = $X ifFalse:[^self error:'Unexpected special character']. ^array ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashCodec class instanceVariableNames: ''! !FlashCodec class methodsFor: 'decompressing'! decompress: aStream ^(self new on: aStream) decompress! ! !FlashCodec class methodsFor: 'decompressing'! decompressPoints: aStream ^(self new on: aStream) decompressPoints! ! !FlashCodec class methodsFor: 'compressing'! compress: aFlashBoundaryShape ^self new compress: aFlashBoundaryShape! ! !FlashCodec class methodsFor: 'compressing'! compressPoints: points ^(self new initialize compressPoints: points) contents! ! !FlashCodec class methodsFor: 'instance creation'! new ^super new initialize! ! Object subclass: #FlashColorTransform instanceVariableNames: 'rMul rAdd gMul gAdd bMul bAdd aMul aAdd ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashColorTransform methodsFor: 'initialize' stamp: 'ar 11/24/1998 15:01'! initialize rMul _ bMul _ gMul _ aMul _ 1.0. rAdd _ bAdd _ gAdd _ aAdd _ 0.0.! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! aAdd ^aAdd! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! aAdd: aFixed aAdd _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:43'! aMul ^aMul! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! aMul: aFixed aMul _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! bAdd ^bAdd! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! bAdd: aFixed bAdd _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:43'! bMul ^bMul! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! bMul: aFixed bMul _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! gAdd ^gAdd! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! gAdd: aFixed gAdd _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:43'! gMul ^gMul! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! gMul: aFixed gMul _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! rAdd ^rAdd! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! rAdd: aFixed rAdd _ aFixed! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:43'! rMul ^rMul! ! !FlashColorTransform methodsFor: 'accessing' stamp: 'ar 7/2/1998 23:44'! rMul: aFixed rMul _ aFixed! ! !FlashColorTransform methodsFor: 'comparing' stamp: 'ar 8/14/1998 19:39'! = aCT self class == aCT class ifFalse:[^false]. ^rAdd = aCT rAdd and:[rMul = aCT rMul and:[ gAdd = aCT gAdd and:[gMul = aCT gMul and:[ bAdd = aCT bAdd and:[bMul = aCT bMul and:[ aAdd = aCT aAdd and:[aMul = aCT aMul]]]]]]]! ! !FlashColorTransform methodsFor: 'comparing' stamp: 'ar 8/14/1998 19:40'! hash ^rAdd hash + gMul hash + bAdd hash + aMul hash! ! !FlashColorTransform methodsFor: 'composing' stamp: 'ar 11/24/1998 14:54'! composedWithGlobal: aColorTransform ^aColorTransform composedWithLocal: self.! ! !FlashColorTransform methodsFor: 'composing' stamp: 'ar 11/25/1998 21:34'! composedWithLocal: aColorTransform | cm | cm _ self clone. cm rAdd: self rAdd + (aColorTransform rAdd * self rMul). cm rMul: self rMul * aColorTransform rMul. cm gAdd: self gAdd + (aColorTransform gAdd * self gMul). cm gMul: self gMul * aColorTransform gMul. cm bAdd: self bAdd + (aColorTransform bAdd * self bMul). cm bMul: self bMul * aColorTransform bMul. cm aAdd: self aAdd + (aColorTransform aAdd * self aMul). cm aMul: self aMul * aColorTransform aMul. ^cm! ! !FlashColorTransform methodsFor: 'composing' stamp: 'ar 11/24/1998 15:06'! localColorToGlobal: aColor ^Color r: (aColor red * self rMul + self rAdd) g: (aColor green * self gMul + self gAdd) b: (aColor blue * self bMul + self bAdd) alpha: (aColor alpha * self aMul + self aAdd)! ! !FlashColorTransform methodsFor: 'printing' stamp: 'ar 11/24/1998 14:40'! printOn: aStream aStream nextPutAll: self class name; nextPut:$(; cr; nextPutAll:' r * '; print: rMul; nextPutAll:' + '; print: rAdd; cr; nextPutAll:' g * '; print: gMul; nextPutAll:' + '; print: gAdd; cr; nextPutAll:' b * '; print: bMul; nextPutAll:' + '; print: bAdd; cr; nextPutAll:' a * '; print: aMul; nextPutAll:' + '; print: aAdd; nextPut:$).! ! !FlashColorTransform methodsFor: 'testing' stamp: 'ar 9/2/1999 15:01'! isAlphaTransform (aAdd = 0.0 and:[aMul = 1.0]) ifTrue:[^false]. ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashColorTransform class instanceVariableNames: ''! !FlashColorTransform class methodsFor: 'instance creation' stamp: 'ar 7/2/1998 23:42'! new ^super new initialize! ! DamageRecorder subclass: #FlashDamageRecorder instanceVariableNames: 'fullDamageRect ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashDamageRecorder methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 18:41'! fullDamageRect invalidRects isEmpty ifTrue:[^0@0 corner: 0@0]. ^fullDamageRect! ! !FlashDamageRecorder methodsFor: 'as yet unclassified' stamp: 'ar 11/13/1998 15:54'! fullDamageRect: maxBounds invalidRects isEmpty ifTrue:[^0@0 corner: 0@0]. ^fullDamageRect intersect: maxBounds! ! !FlashDamageRecorder methodsFor: 'as yet unclassified' stamp: 'ar 11/13/1998 15:43'! recordInvalidRect: rect totalRepaint ifTrue:[^self]. self updateIsNeeded ifTrue:[ fullDamageRect _ fullDamageRect merge: rect. ] ifFalse:[ fullDamageRect _ rect copy. ]. ^super recordInvalidRect: rect! ! Object subclass: #FlashFileReader instanceVariableNames: 'stream log dataSize nFillBits nLineBits nGlyphBits nAdvanceBits jpegDecoder version ' classVariableNames: 'ActionTable IndexTables StepTable TagTable ' poolDictionaries: '' category: 'Balloon-MMFlash Import'! !FlashFileReader methodsFor: 'initialize' stamp: 'ar 7/4/1998 20:14'! on: aStream aStream binary. stream _ FlashFileStream on: aStream. log _ Transcript. log _ nil.! ! !FlashFileReader methodsFor: 'property access' stamp: 'ar 11/18/1998 21:25'! isStreaming "Subclasses may override this" ^false! ! !FlashFileReader methodsFor: 'reading' stamp: 'ar 11/18/1998 23:41'! processFile "Read and process the entire file" self processHeader ifFalse:[^nil]. self processFileContents.! ! !FlashFileReader methodsFor: 'reading' stamp: 'ar 11/19/1998 00:29'! processFileContents "Process the contents of the flash file. Assume that the header has been read before." | time | time _ Time millisecondsToRun:[ self isStreaming ifTrue:[ "Don't show progress for a streaming connection. Note: Yielding is done someplace else." [self processTagFrom: stream] whileTrue. ] ifFalse:[ 'Reading file' displayProgressAt: Sensor cursorPoint from: 1 to: 100 during:[:theBar| [self processTagFrom: stream] whileTrue:[ theBar value: (stream position * 100 // stream size). stream atEnd ifTrue:[ log ifNotNil:[ log cr; nextPutAll:'Unexpected end of data (no end tag)'. self flushLog]. ^self]]. ]. ]. (stream respondsTo: #close) ifTrue:[stream close]. ]. Transcript cr; print: time / 1000.0; show:' secs to read file'! ! !FlashFileReader methodsFor: 'reading' stamp: 'ar 6/28/1999 16:33'! processHeader "Read header information from the source stream. Return true if successful, false otherwise." | twipsFrameSize frameRate frameCount | self processSignature ifFalse:[^false]. version _ stream nextByte. "Check for the version supported" version > self maximumSupportedVersion ifTrue:[^false]. dataSize _ stream nextLong. "Check for the minimal file size" dataSize < 21 ifTrue:[^false]. twipsFrameSize _ stream nextRect. self recordGlobalBounds: twipsFrameSize. frameRate _ stream nextWord / 256.0. self recordFrameRate: frameRate. frameCount _ stream nextWord. self recordFrameCount: frameCount. log ifNotNil:[ log cr; nextPutAll:'------------- Header information --------------'. log cr; nextPutAll:'File version '; print: version. log cr; nextPutAll:'File size '; print: dataSize. log cr; nextPutAll:'Movie width '; print: twipsFrameSize extent x // 20. log cr; nextPutAll:'Movie height '; print: twipsFrameSize extent y // 20. log cr; nextPutAll:'Frame rate '; print: frameRate. log cr; nextPutAll:'Frame count '; print: frameCount. log cr; cr. self flushLog]. ^true! ! !FlashFileReader methodsFor: 'reading' stamp: 'ar 7/4/1998 20:08'! processSignature "Check the signature of the SWF file" stream nextByte asCharacter = $F ifFalse:[^false]. stream nextByte asCharacter = $W ifFalse:[^false]. stream nextByte asCharacter = $S ifFalse:[^false]. ^true! ! !FlashFileReader methodsFor: 'reading' stamp: 'ar 10/12/1998 23:57'! processTagFrom: aStream "Read and process the next tag from the input stream." | tag data result | tag _ aStream nextTag. log ifNotNil:[ log cr; nextPutAll:'Tag #'; print: tag key. log nextPutAll:' ('; nextPutAll: (TagTable at: tag key + 1); space; print: tag value size; nextPutAll:' bytes)'. self flushLog]. data _ FlashFileStream on: (ReadStream on: tag value). result _ self dispatch: data on: tag key+1 in: TagTable ifNone:[self processUnknown: data]. (log isNil or:[data atEnd]) ifFalse:[ log nextPutAll:'*** '; print: (data size - data position); nextPutAll:' bytes skipped ***'. self flushLog]. ^result! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 7/13/1998 17:53'! processCurveRecordFrom: data | nBits cx cy ax ay | log ifNotNil:[log crtab; nextPutAll:'C: ']. nBits _ (data nextBits: 4) + 2. "Offset by 2" "Read control point change" cx _ data nextSignedBits: nBits. cy _ data nextSignedBits: nBits. log ifNotNil:[log print: cx@cy]. "Read anchor point change" ax _ data nextSignedBits: nBits. ay _ data nextSignedBits: nBits. log ifNotNil:[log nextPutAll:' -- '; print: ax@ay. self flushLog]. self recordCurveSegmentTo: ax@ay with: cx@cy! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 11/13/1998 20:31'! processFillStylesFrom: data | nFills matrix nColors rampIndex rampColor id color fillStyleType ramp | nFills _ data nextByte. nFills = 255 ifTrue:[nFills _ data nextWord]. log ifNotNil:[log crtab; print: nFills; nextPutAll:' New fill styles']. 1 to: nFills do:[:i| log ifNotNil:[log crtab: 2; print: i; nextPut:$:; tab]. fillStyleType _ data nextByte. (fillStyleType = 0) ifTrue:["Solid fill" color _ data nextColor. self recordSolidFill: i color: color. log ifNotNil:[log nextPutAll:'solid color '; print: color]. ]. (fillStyleType anyMask: 16) ifTrue:["Gradient fill" "Read gradient matrix" matrix _ data nextMatrix. "Read color ramp data" nColors _ data nextByte. ramp _ Array new: nColors. log ifNotNil:[log nextPutAll:'Gradient fill with '; print: nColors; nextPutAll:' colors']. 1 to: nColors do:[:j| rampIndex _ data nextByte. rampColor _ data nextColor. ramp at: j put: (rampIndex -> rampColor)]. self recordGradientFill: i matrix: matrix ramp: ramp linear: (fillStyleType = 16)]. (fillStyleType anyMask: 16r40) ifTrue:["Bit fill" "Read bitmap id" id _ data nextWord. "Read bitmap matrix" matrix _ data nextMatrix. log ifNotNil:[log nextPutAll:'Bitmap fill id='; print: id]. self recordBitmapFill: i matrix: matrix id: id clipped: (fillStyleType anyMask: 1)]. self flushLog. ].! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 7/12/1998 23:35'! processFontShapeFrom: data data initBits. nFillBits _ data nextBits: 4. nLineBits _ data nextBits: 4. "Process all records in this shape definition" [self processShapeRecordFrom: data] whileTrue.! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 7/9/1998 20:43'! processLineRecordFrom: data | nBits x y | nBits _ (data nextBits: 4) + 2. "Offset by 2" data nextBitFlag ifTrue:[ "General line" x _ data nextSignedBits: nBits. y _ data nextSignedBits: nBits. self recordLineSegmentBy: x@y. ] ifFalse:[ data nextBitFlag ifTrue:[ "vertical line" y _ data nextSignedBits: nBits. self recordLineSegmentVerticalBy: y] ifFalse:[ "horizontal line" x _ data nextSignedBits: nBits. self recordLineSegmentHorizontalBy: x]. ]. log ifNotNil:[log crtab; nextPutAll:'E: ';print: x; nextPut:$@; print: y. self flushLog].! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 7/4/1998 20:04'! processLineStylesFrom: data | nStyles styles lineWidth lineColor | nStyles _ data nextByte. nStyles = 255 ifTrue:[nStyles _ data nextWord]. log ifNotNil:[log crtab; print: nStyles; nextPutAll:' New line styles']. styles _ Array new: nStyles. 1 to: nStyles do:[:i| lineWidth _ data nextWord. lineColor _ data nextColor. self recordLineStyle: i width: lineWidth color: lineColor. log ifNotNil:[log crtab: 2; print: i; nextPut:$:; tab; print: lineWidth; tab; print: lineColor]]. self flushLog. ^styles! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 11/17/1998 00:35'! processShapeRecordFrom: data | flags pt lineInfo fillInfo0 fillInfo1 | data nextBitFlag ifTrue:["Boundary edge record" data nextBitFlag ifTrue:[self processLineRecordFrom: data] ifFalse:[self processCurveRecordFrom: data]. ^true]. flags _ data nextBits: 5. flags = 0 ifTrue:[^false]. "At end of shape" (flags anyMask: 1) ifTrue:["move to" pt _ data nextPoint. self recordMoveTo: pt. log ifNotNil:[log crtab; nextPutAll:'MoveTo '; print: pt]]. (flags anyMask: 2) ifTrue:["fill info 0" fillInfo0 _ data nextBits: nFillBits. self recordFillStyle0: fillInfo0. log ifNotNil:[log crtab; nextPutAll:'FillInfo0 '; print: fillInfo0]]. (flags anyMask: 4) ifTrue:["fill info 1" fillInfo1 _ data nextBits: nFillBits. self recordFillStyle1: fillInfo1. log ifNotNil:[log crtab; nextPutAll:'FillInfo1 '; print: fillInfo1]]. (flags anyMask: 8) ifTrue:["line info" lineInfo _ data nextBits: nLineBits. self recordLineStyle: lineInfo. log ifNotNil:[log crtab; nextPutAll:'LineInfo '; print: lineInfo]]. (flags anyMask: 16) ifTrue:["new styles" self recordEndSubshape. log ifNotNil:[log crtab; nextPutAll:'New Set of styles ']. self processShapeStylesFrom: data. "And reset info" data initBits. nFillBits _ data nextBits: 4. nLineBits _ data nextBits: 4]. ^true! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 7/4/1998 20:05'! processShapeStylesFrom: data self processFillStylesFrom: data. self processLineStylesFrom: data.! ! !FlashFileReader methodsFor: 'processing shapes' stamp: 'ar 9/3/1999 14:54'! processShapesFrom: data "Process a new shape" | id bounds | "Read shape id and bounding box" id _ data nextWord. bounds _ data nextRect. "Start new shape definition" self recordShapeStart: id bounds: bounds. "Read styles for this shape" self processShapeStylesFrom: data. "Get number of bits for fill and line styles" data initBits. nFillBits _ data nextBits: 4. nLineBits _ data nextBits: 4. "Process all records in this shape definition" [self processShapeRecordFrom: data] whileTrue. "And mark the end of this shape" self recordShapeEnd: id. self recordShapeProperty: id length: data size.! ! !FlashFileReader methodsFor: 'processing glyphs' stamp: 'ar 7/14/1998 23:17'! processGlyphEntries: nGlyphs from: data | index advance | data initBits. 1 to: nGlyphs do:[:i| index _ data nextBits: nGlyphBits. advance _ data nextSignedBits: nAdvanceBits. self recordNextChar: index+1 advanceWidth: advance. log ifNotNil:[ log nextPut:$(;print: index; space; print: advance; nextPut:$). self flushLog]. ].! ! !FlashFileReader methodsFor: 'processing glyphs' stamp: 'ar 11/20/1998 02:47'! processGlyphRecordFrom: data | flags | flags _ data nextByte. flags = 0 ifTrue:[^false]. self flag: #wrongSpec. "From news://forums.macromedia.com/macromedia.open-swf It is an error in the spec. There can be up to 255 characters in run. The high bit does not mean anything. The text record type 0 and type 1 is poorly described. The real format is that all of the info in a 'text record type 1' is always followed by the info in a 'text record type 2'. Note the high bit of 'text record type 1' is reserved and should always be zero. " self processGlyphStateChange: flags from: data. flags _ data nextByte. flags = 0 ifTrue:[^false]. self processGlyphEntries: flags from: data. "Old stuff - which is according to the f**cking spec" "(flags anyMask: 128) ifTrue:[ self processGlyphStateChange: flags from: data. ] ifFalse:[ self processGlyphEntries: flags from: data. ]." ^true! ! !FlashFileReader methodsFor: 'processing glyphs' stamp: 'ar 7/15/1998 19:45'! processGlyphStateChange: flags from: data | hasFont hasColor hasXOffset hasYOffset fontId color xOffset yOffset height | hasFont _ flags anyMask: 8. hasColor _ flags anyMask: 4. hasYOffset _ flags anyMask: 2. hasXOffset _ flags anyMask: 1. hasFont ifTrue:[fontId _ data nextWord]. hasColor ifTrue:[color _ data nextColor]. hasXOffset ifTrue:[xOffset _ data nextWord]. hasYOffset ifTrue:[yOffset _ data nextWord]. hasFont ifTrue:[height _ data nextWord]. log ifNotNil:[ log nextPutAll:'['. hasFont ifTrue:[log nextPutAll:' font='; print: fontId]. hasColor ifTrue:[log nextPutAll:' color='; print: color]. hasXOffset ifTrue:[log nextPutAll:' xOfs=';print: xOffset]. hasYOffset ifTrue:[log nextPutAll:' yOfs=';print: yOffset]. hasFont ifTrue:[log nextPutAll:' height='; print: height]. log nextPutAll:' ]'. self flushLog. ]. self recordTextChange: fontId color: color xOffset: xOffset yOffset: yOffset height: height.! ! !FlashFileReader methodsFor: 'processing glyphs' stamp: 'ar 10/15/1998 03:23'! processGlyphsFrom: data | id bounds matrix | id _ data nextWord. bounds _ data nextRect. matrix _ data nextMatrix. self recordTextStart: id bounds: bounds matrix: matrix. nGlyphBits _ data nextByte. nAdvanceBits _ data nextByte. log ifNotNil:[ log nextPutAll:'(nGlyphBits = '; print: nGlyphBits; nextPutAll:' nAdvanceBits = '; print: nAdvanceBits; nextPutAll:') '. self flushLog]. [self processGlyphRecordFrom: data] whileTrue. self recordTextEnd: id.! ! !FlashFileReader methodsFor: 'processing buttons' stamp: 'ar 6/28/1999 16:32'! processButtonRecords: id from: data cxForm: haveCxForm | flags state characterId layer matrix cxForm | [flags _ data nextByte. flags = 0] whileFalse:[ state _ flags bitAnd: 15. characterId _ data nextWord. layer _ data nextWord. matrix _ data nextMatrix. haveCxForm ifTrue:[cxForm _ data nextColorMatrix: version >= 3]. self recordButton: id character: characterId state: state layer: layer matrix: matrix colorTransform: cxForm].! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 11/24/1998 15:32'! processActionGetURL: data | length position urlString winString | length _ data nextWord. position _ data position. urlString _ data nextString. winString _ data nextString. data position = (position + length) ifFalse:[ self halt. data position: position. ^self processUnknownAction: data]. log ifNotNil:[ log nextPutAll:' url='; print: urlString; nextPutAll:', win='; print: winString]. ^Message selector: #getURL:window: arguments: (Array with: urlString with: winString)! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionGotoFrame: data | length frame | length _ data nextWord. length = 2 ifFalse:["There is something wrong here" self halt. data skip: -2. ^self processUnknownAction: data]. frame _ data nextWord. log ifNotNil:[log nextPutAll:' frame = '; print: frame.]. ^Message selector: #gotoFrame: argument: frame! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 11/24/1998 15:31'! processActionGotoLabel: data | length label | length _ data nextWord. label _ data nextString. log ifNotNil:[log nextPutAll:' label = '; print: label]. ^Message selector: #gotoLabel: argument: label! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionNextFrame: data ^Message selector: #gotoNextFrame! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 11/19/1998 20:39'! processActionPlay: data ^Message selector: #actionPlay! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionPrevFrame: data ^Message selector: #gotoPrevFrame! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 7/15/1998 19:39'! processActionRecordsFrom: data | code actionList action | actionList _ OrderedCollection new. [code _ data nextByte. code = 0] whileFalse:[ code _ code bitAnd: 127. "Mask out the length-follow flag" log ifNotNil:[ log cr; nextPutAll:' Action #'; print: code. log nextPutAll:' ('; nextPutAll: (ActionTable at: code); nextPutAll:')']. action _ self dispatch: data on: code in: ActionTable ifNone:[self processUnknownAction: data]. action ifNotNil:[actionList add: action]. log ifNotNil:[self flushLog]. ]. ^actionList! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 11/17/1998 13:37'! processActionSetTarget: data | length target | length _ data nextWord. target _ data nextString. log ifNotNil:[log nextPutAll:' target = '; print: target]. ^Message selector: #actionTarget: argument: target.! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 11/19/1998 20:39'! processActionStop: data ^Message selector: #actionStop! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionStopSounds: data ^Message selector: #stopSounds! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionToggleQuality: data ^Message selector: #toggleQuality! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 8/17/1998 10:10'! processActionWaitForFrame: data | length frame skip | length _ data nextWord. length = 3 ifFalse:["Something is wrong" self halt. data skip: -2. ^self processUnknownAction: data]. frame _ data nextWord. skip _ data nextByte. log ifNotNil:[ log nextPutAll:'frame = '; print: frame; nextPutAll:', skip = '; print: skip]. ^Message selector: #isFrameLoaded:elseSkip: arguments: (Array with: frame with: skip).! ! !FlashFileReader methodsFor: 'processing actions' stamp: 'ar 7/15/1998 19:37'! processUnknownAction: data | code length | data skip: -1. "For determining the length of the action" code _ data nextByte. (code anyMask: 128) ifTrue:["Two byte length following" length _ data nextWord. data skip: length]. log ifNotNil:[log nextPutAll:'*** skipped ***']. ^nil! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'ar 11/21/1998 00:46'! createSoundBuffersOfSize: numSamples stereo: stereo | channels buffers | channels _ stereo ifTrue:[2] ifFalse:[1]. buffers _ Array new: channels. 1 to: channels do:[:i| buffers at: i put: (WriteStream on: ((SoundBuffer newMonoSampleCount: numSamples)))]. ^buffers! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'jm 3/30/1999 09:08'! createSoundFrom: soundBuffers stereo: stereo samplingRate: samplingRate | snds | snds _ soundBuffers collect: [:buf | (SampledSound samples: buf samplingRate: samplingRate) loudness: 1.0]. stereo ifTrue:[ ^ MixedSound new add: (snds at: 1) pan: 0.0; add: (snds at: 2) pan: 1.0; yourself] ifFalse: [ ^ snds at: 1].! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'jm 3/30/1999 08:55'! decompressSound: aByteArray stereo: stereo samples: numSamples rate: samplingRate | buffers | buffers _ ADPCMCodec new decodeFlash: aByteArray sampleCount: numSamples stereo: stereo. ^ self createSoundFrom: buffers stereo: stereo samplingRate: samplingRate ! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'ar 11/29/1998 14:53'! decompressSound: aByteArray stereo: stereo samples: numSamples rate: samplingRate into: buffers | data nBits signMask indexTable channels valPred index vp idx delta step vpdiff allButSignMask k k0 | data _ FlashFileStream on: (ReadStream on: aByteArray). data initBits. nBits _ (data nextBits: 2) + 2. signMask _ 1 bitShift: nBits - 1. allButSignMask _ signMask bitInvert32. k0 _ 1 bitShift: (nBits - 2). indexTable _ IndexTables at: nBits - 1. channels _ stereo ifTrue:[2] ifFalse:[1]. valPred _ IntegerArray new: channels. index _ IntegerArray new: channels. 1 to: numSamples do:[:nOut| (nOut bitAnd: 16rFFF) = 1 ifTrue:["New block header starts every 4KB" 1 to: channels do:[:i| vp _ data nextSignedBits: 16. valPred at: i put: vp. (buffers at: i) nextPut: vp. "First sample has no delta" index at: i put: (data nextBits: 6). ]. ] ifFalse:[ "Decode next sample" 1 to: channels do:[:i| vp _ valPred at: i. idx _ index at: i. "Get next delta value" delta _ data nextBits: nBits. "Compute difference and new predicted value" "Computes 'vpdiff = (delta+0.5)*step/4" step _ StepTable at: idx + 1. k _ k0. vpdiff _ 0. [ (delta bitAnd: k) = 0 ifFalse:[vpdiff _ vpdiff + step]. step _ step bitShift: -1. k _ k bitShift: -1. k = 0] whileFalse. vpdiff _ vpdiff + step. (delta anyMask: signMask) ifTrue:[vp _ vp - vpdiff] ifFalse:[vp _ vp + vpdiff]. "Compute new index value" idx _ idx + (indexTable at: (delta bitAnd: allButSignMask) + 1). "Clamp index" idx < 0 ifTrue:[idx _ 0]. idx > 88 ifTrue:[idx _ 88]. "Clamp output value" vp < -32768 ifTrue:[vp _ -32768]. vp > 32767 ifTrue:[vp _ 32767]. "Store values back" index at: i put: idx. valPred at: i put: vp. (buffers at: i) nextPut: vp. ] ]. ].! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'ar 8/10/1998 15:37'! processEnvelopeFrom: data | env | env _ FlashSoundEnvelope new. env mark44: data nextULong. env level0: data nextWord. env level1: data nextWord. ^env! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'ar 8/10/1998 16:11'! processSoundInfoFrom: data | flags info nPoints | flags _ data nextByte. info _ FlashSoundInformation new. info syncFlags: (flags bitShift: -4). (flags anyMask: 1) ifTrue:[info inPoint: data nextULong]. (flags anyMask: 2) ifTrue:[info outPoint: data nextULong]. (flags anyMask: 4) ifTrue:[info loopCount: data nextWord]. (flags anyMask: 8) ifTrue:[ nPoints _ data nextByte. info envelopes: ((1 to: nPoints) collect:[:i| self processEnvelopeFrom: data]). ]. ^info! ! !FlashFileReader methodsFor: 'processing sounds' stamp: 'ar 11/20/1998 22:37'! processSoundStreamHeadFrom: data | mixFmt flags stereo bitsPerSample compressed sampleCount | mixFmt _ data nextByte. flags _ data nextByte. stereo _ flags anyMask: 1. self flag: #wrongSpec. bitsPerSample _ (flags anyMask: 2) ifTrue:[16] ifFalse:[8]. compressed _ (flags bitShift: -4) = 1. sampleCount _ data nextWord. self recordSoundStreamHead: mixFmt stereo: stereo bitsPerSample: bitsPerSample sampleCount: sampleCount compressed: compressed. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 2/23/1999 00:10'! processDefineBits: data | id image | id _ data nextWord. image _ jpegDecoder decodeNextImageFrom: data. Preferences compressFlashImages ifTrue:[image _ image asFormOfDepth: 8]. "image display." self recordBitmap: id data: image. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 2/23/1999 00:10'! processDefineBitsJPEG2: data | id image decoder | id _ data nextWord. decoder _ FlashJPEGDecoder new. decoder isStreaming: self isStreaming. decoder decodeJPEGTables: data. image _ decoder decodeNextImageFrom: data. Preferences compressFlashImages ifTrue:[image _ image asFormOfDepth: 8]. self recordBitmap: id data: image. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 2/23/1999 00:10'! processDefineBitsJPEG3: data "TODO: Read zlib compressed alpha." | id image decoder alphaOffset dataOffset | id _ data nextWord. self flag: #wrongSpec. alphaOffset _ data nextWord. dataOffset _ data nextWord. decoder _ FlashJPEGDecoder new. decoder isStreaming: self isStreaming. decoder decodeJPEGTables: data. image _ decoder decodeNextImageFrom: data. Preferences compressFlashImages ifTrue:[image _ image asFormOfDepth: 8]. "Note: We must read the zlib compressed alpha values here." self recordBitmap: id data: image. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:12'! processDefineBitsLossless2: data "TODO: Read zlib compressed data." | id format width height | id _ data nextWord. format _ data nextByte. width _ data nextWord. height _ data nextWord. self recordBitmap: id data: nil. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:12'! processDefineBitsLossless: data "TODO: Read zlib compressed data." | id format width height | id _ data nextWord. format _ data nextByte. width _ data nextWord. height _ data nextWord. self recordBitmap: id data: nil. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 02:41'! processDefineButton2: data | id flags actions condition actionOffset | data hasAlpha: true. id _ data nextWord. self recordDefineButton: id. flags _ data nextByte. self recordButton: id trackAsMenu: flags = 0. self flag: #wrongSpec. actionOffset _ data nextWord. self processButtonRecords: id from: data cxForm: true. [actionOffset = 0] whileFalse:[ actionOffset _ data nextWord. condition _ data nextWord. actions _ self processActionRecordsFrom: data. self recordButton: id actions: actions condition: condition]. data hasAlpha: false. self recordEndButton: id. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/16/1998 20:47'! processDefineButton: data | id actions | id _ data nextWord. self recordDefineButton: id. self processButtonRecords: id from: data cxForm: false. actions _ self processActionRecordsFrom: data. self recordButton: id actions: actions. self recordEndButton: id. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/24/1998 15:32'! processDefineButtonCxform: data ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/21/1998 13:56'! processDefineButtonSound: data | id soundID soundInfo | id _ data nextWord. #(0 mouseEnter mouseDown 3) do:[:state| soundID _ data nextWord. soundID = 0 ifFalse:[ soundInfo _ self processSoundInfoFrom: data. self recordButton: id sound: soundID info: soundInfo state: state]]. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/24/1998 15:31'! processDefineFont2: data ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 10/15/1998 03:18'! processDefineFont: data | fontId firstOffset offsets nShapes | fontId _ data nextWord. firstOffset _ data nextWord. nShapes _ firstOffset // 2. offsets _ Array new: nShapes. offsets at: 1 put: firstOffset. 2 to: nShapes do:[:i| offsets at: i put: data nextWord]. self recordFontBegin: fontId with: nShapes. 1 to: nShapes do:[:i| log ifNotNil:[log cr; nextPutAll:'Glyph '; print: i]. self recordFontShapeStart: fontId with: i. self processFontShapeFrom: data. self recordFontShapeEnd: fontId with: i]. data atEnd ifFalse:[self halt]. self recordFontEnd: fontId. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:34'! processDefineFontInfo: data | id nameLength fontName flags charMap | id _ data nextWord. nameLength _ data nextByte. fontName _ (data nextBytes: nameLength) asString. flags _ data nextByte. charMap _ data upToEnd. self recordFont: id name: fontName charMap: charMap wide: (flags anyMask: 1). ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 9/3/1999 14:45'! processDefineMorphShape: data self processMorphShapeFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 10/13/1998 23:52'! processDefineShape2: data self processShapesFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/15/1998 19:48'! processDefineShape3: data data hasAlpha: true. self processShapesFrom: data. data hasAlpha: false. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 10/13/1998 23:22'! processDefineShape: data self processShapesFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/18/1998 21:29'! processDefineSound: data | flags sampleCount sampleData id stereo bitsPerSample rate compressed sound | id _ data nextWord. flags _ data nextByte. stereo _ (flags anyMask: 1). bitsPerSample _ (flags anyMask: 2) ifTrue:[16] ifFalse:[8]. rate _ #( 5512 11025 22050 44100 ) at: (flags >> 2 bitAnd: 3)+1. compressed _ flags anyMask: 16. sampleCount _ data nextULong. sampleData _ data upToEnd. compressed ifTrue:[ self isStreaming ifFalse:[Cursor wait show]. sound _ self decompressSound: sampleData stereo: stereo samples: sampleCount rate: rate. self isStreaming ifFalse:[Cursor normal show]. ] ifFalse:[ self halt. sound _ nil. ]. self recordSound: id data: sound. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 02:42'! processDefineSprite: data | id frameCount | id _ data nextWord. self flag: #wrongSpec. frameCount _ data nextWord. self recordBeginSprite: id frames: frameCount. [self processTagFrom: data] whileTrue. self recordEndSprite: id. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/15/1998 19:47'! processDefineText2: data data hasAlpha: true. self processGlyphsFrom: data. data hasAlpha: false. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 10/15/1998 23:23'! processDefineText: data self processGlyphsFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/18/1998 22:57'! processDoAction: data | actions | actions _ self processActionRecordsFrom: data. self recordFrameActions: actions. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/4/1998 20:30'! processEnd: data "At end of data" ^false! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/17/1998 13:35'! processFrameLabel: data | label | label _ data nextString. self recordFrameLabel: label. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/24/1998 15:31'! processFreeCharacter: data | id | id _ data nextWord. data atEnd ifFalse:[self halt]. self recordFreeCharacter: id. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/18/1998 21:32'! processJPEGTables: data jpegDecoder _ FlashJPEGDecoder new. jpegDecoder isStreaming: self isStreaming. jpegDecoder decodeJPEGTables: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/24/1998 15:32'! processNameCharacter: data ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 9/3/1999 15:23'! processPlaceObject2: data | id flags depth matrix cxForm ratio name move | flags _ data nextByte. depth _ data nextWord. move _ (flags anyMask: 1). (flags anyMask: 2) ifTrue:[id _ data nextWord]. (flags anyMask: 4) ifTrue:[matrix _ data nextMatrix]. (flags anyMask: 8) ifTrue:[cxForm _ data nextColorMatrix: version >= 3]. self flag: #checkThis. (flags anyMask: 16) ifTrue:["self halt." ratio _ data nextWord / 65536.0]. (flags anyMask: 32) ifTrue:["self halt." name _ data nextString]. (flags anyMask: 64) ifTrue:["self halt:'Clip shape encountered'." ^true]. log ifNotNil:[ log nextPutAll:' (id = ', id printString,' name = ', name printString,' depth = ', depth printString, ' move: ', move printString, ')'. self flushLog]. move ifTrue:[self recordMoveObject: id name: name depth: depth matrix: matrix colorMatrix: cxForm ratio: ratio] ifFalse:[self recordPlaceObject: id name: name depth: depth matrix: matrix colorMatrix: cxForm ratio: ratio]. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 9/3/1999 15:12'! processPlaceObject: data | id depth matrix colorMatrix | id _ data nextWord. depth _ data nextWord. matrix _ data nextMatrix. log ifNotNil:[ log nextPutAll:' (id = ', id printString,' depth = ', depth printString, ')'. self flushLog]. data atEnd ifFalse:[colorMatrix _ data nextColorMatrix]. self recordPlaceObject: id name: nil depth: depth matrix: matrix colorMatrix: colorMatrix ratio: nil. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/4/1998 20:32'! processProtect: data self recordProtection. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 02:43'! processRemoveObject2: data | depth | depth _ data nextWord. log ifNotNil:[ log nextPutAll:' (depth = ', depth printString, ')'. self flushLog]. self recordRemoveObject: nil depth: depth. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/13/1998 00:19'! processRemoveObject: data | id depth | id _ data nextWord. depth _ data nextWord. log ifNotNil:[ log nextPutAll:' (id = ', id printString,' depth = ', depth printString, ')'. self flushLog]. self recordRemoveObject: id depth: depth. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/4/1998 20:33'! processSetBackgroundColor: data | color | color _ data nextColor. self recordBackgroundColor: color. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/4/1998 20:34'! processShowFrame: data "Show the current frame" self recordShowFrame. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:37'! processSoundStreamBlock: data self recordSoundStreamBlock: data upToEnd. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:21'! processSoundStreamHead2: data self processSoundStreamHeadFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 11/20/1998 22:22'! processSoundStreamHead: data self processSoundStreamHeadFrom: data. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 10/15/1998 02:47'! processStartSound: data | id info | id _ data nextWord. info _ self processSoundInfoFrom: data. self recordStartSound: id info: info. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/4/1998 20:34'! processUnknown: data "An unknown tag has been encountered" ^true! ! !FlashFileReader methodsFor: 'private' stamp: 'ar 11/20/1998 22:11'! dispatch: argument on: aKey in: aTable ifNone: exceptionBlock | selector | (aKey < 1 or:[aKey > aTable size]) ifTrue:[^exceptionBlock value]. selector _ aTable at: aKey. ^self perform: selector with: argument! ! !FlashFileReader methodsFor: 'private' stamp: 'ar 11/5/1998 23:42'! flushLog (log == Transcript) ifTrue:[ log endEntry. Sensor leftShiftDown ifTrue:[self halt]. ].! ! !FlashFileReader methodsFor: 'private' stamp: 'mir 11/3/1999 15:25'! maximumSupportedVersion ^4! ! !FlashFileReader methodsFor: 'private' stamp: 'ar 7/12/1998 23:41'! warn: aString Transcript cr; show: aString.! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 19:46'! recordCurveSegmentTo: anchorPoint with: controlPoint! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 11/17/1998 00:36'! recordEndSubshape "A new subshape begins with a full set of line and fill styles"! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 10/14/1998 00:39'! recordFillStyle0: fillIndex! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 10/14/1998 00:39'! recordFillStyle1: fillIndex! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 20:56'! recordLineSegmentBy: deltaPoint! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/8/1998 15:56'! recordLineSegmentHorizontalBy: deltaX ^self recordLineSegmentBy: (deltaX@0)! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/8/1998 15:56'! recordLineSegmentVerticalBy: deltaY ^self recordLineSegmentBy: (0@deltaY)! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 19:48'! recordLineStyle: styleIndex! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 19:47'! recordMoveTo: aPoint! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 19:59'! recordShapeEnd: shapeId! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 1/4/1999 08:44'! recordShapeProperty: id length: length! ! !FlashFileReader methodsFor: 'composing shapes' stamp: 'ar 7/4/1998 19:59'! recordShapeStart: shapeId bounds: shapeBounds! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 11/20/1998 22:35'! recordFont: id name: fontName charMap: charMap wide: isWide "Record the name and character mapping of the font for the given id. If isWide is set then the font is a 16bit Unicode font."! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 18:50'! recordFontBegin: fontId with: nGlyphs! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 18:50'! recordFontEnd: fontId! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:08'! recordFontShapeEnd: fontId with: charId! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:08'! recordFontShapeStart: fontId with: charId! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:09'! recordNextChar: glyphIndex advanceWidth: advance! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:08'! recordTextChange: fontId color: color xOffset: xOffset yOffset: yOffset height: height! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:16'! recordTextEnd: id! ! !FlashFileReader methodsFor: 'defining text' stamp: 'ar 7/13/1998 01:16'! recordTextStart: id bounds: bounds matrix: matrix! ! !FlashFileReader methodsFor: 'defining styles' stamp: 'ar 11/13/1998 20:31'! recordBitmapFill: fillIndex matrix: bmMatrix id: bitmapID clipped: aBoolean! ! !FlashFileReader methodsFor: 'defining styles' stamp: 'ar 7/4/1998 19:52'! recordGradientFill: fillIndex matrix: gradientMatrix ramp: colorRampArray linear: aBoolean! ! !FlashFileReader methodsFor: 'defining styles' stamp: 'ar 7/4/1998 19:55'! recordLineStyle: styleIndex width: lineWidth color: lineColor! ! !FlashFileReader methodsFor: 'defining styles' stamp: 'ar 7/4/1998 19:48'! recordSolidFill: index color: fillColor! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 7/15/1998 20:22'! recordButton: buttonId actions: actionList "Associate an action list with the given button" ^self recordButton: buttonId actions: actionList condition: 8. "OverDownToOverUp"! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 7/15/1998 20:34'! recordButton: buttonId actions: actionList condition: condition "Associate an action list with the given button: buttonId: global ID of the button actions: Collection of MessageSends (e.g., actions) condition: bit mask describing when the actions should be applied General conditions: 1 - IdleToOverUp (Mouse enter up) 2 - OverUpToIdle (Mouse exit up) 4 - OverUpToOverDown (Mouse down) 8 - OverDownToOverUp (Mouse up in) Push button conditions: 16 - OverDownToOutDown (Mouse exit down) 32 - OutDownToOverDown (Mouse enter down) 64 - OutDownToIdle (Mouse up out) Menu button conditions: 128 - IdleToOverDown (Mouse enter down) 256 - OverDownToIdle (Mouse exit down)" ! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 11/24/1998 14:23'! recordButton: buttonId character: characterId state: state layer: layer matrix: matrix colorTransform: cxForm "Define the character to use for a button. buttonId: global ID used for the button characterId: ID of the character defining the shape for the button state: bit mask for when to use the character 1 - default (e.g. no other state applies) 2 - display when the mouse is over the button but not pressed 4 - display when the button is pressed 8 - the area in which the mouse is supposed to be 'over' the button layer: UNKNOWN. matrix: Transformation to apply to the character. (Guess!!)"! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 8/10/1998 15:51'! recordButton: id sound: soundId info: soundInfo state: state! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 7/15/1998 20:06'! recordButton: id trackAsMenu: aBoolean "Track the button with the given ID as a menu (in contrast to a push) button. Push buttons capture the mouse until the button is released. Menu buttons don't. Note: If defined for a button, this method will be called prior to any other #recordButton: methods."! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 11/16/1998 20:36'! recordDefineButton: id "Record the definition of a new button with the given id"! ! !FlashFileReader methodsFor: 'defining buttons' stamp: 'ar 11/16/1998 20:47'! recordEndButton: id "Record the end of a button definition with the given id" ! ! !FlashFileReader methodsFor: 'defining sounds' stamp: 'ar 10/15/1998 02:36'! recordSound: id data: aSampledSound! ! !FlashFileReader methodsFor: 'defining sounds' stamp: 'ar 8/10/1998 15:41'! recordSoundStreamBlock: compressedData! ! !FlashFileReader methodsFor: 'defining sounds' stamp: 'ar 8/10/1998 15:45'! recordSoundStreamHead: mixFmt stereo: stereo bitsPerSample: bitsPerSample sampleCount: sampleCount compressed: compressed! ! !FlashFileReader methodsFor: 'defining sounds' stamp: 'ar 8/10/1998 15:41'! recordStartSound: id info: info! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/4/1998 20:33'! recordBackgroundColor: aColor! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 11/16/1998 16:54'! recordBeginSprite: id frames: frameCount! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/4/1998 22:27'! recordBitmap: bitmapId data: aForm! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 11/16/1998 16:55'! recordEndSprite: id! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/15/1998 19:41'! recordFrameActions: actionList "Record the list of actions executed at the next showFrame"! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 11/18/1998 22:00'! recordFrameCount: maxFrames! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 11/17/1998 13:36'! recordFrameLabel: label "Name the current frame with the given label"! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 8/10/1998 18:23'! recordFrameRate: fps! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/15/1998 20:30'! recordFreeCharacter: id "Free the character with the given id. Not documented."! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/10/1998 15:51'! recordGlobalBounds: bounds! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 9/3/1999 15:10'! recordMorph: id depth: depth ratio: ratio! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 9/1/1999 14:40'! recordMoveObject: objectIndex name: aString depth: depth matrix: matrix colorMatrix: colorMatrix! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 9/1/1999 14:42'! recordPlaceObject: objectIndex name: aString depth: depth matrix: matrix colorMatrix: colorMatrix! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/4/1998 20:32'! recordProtection! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/4/1998 22:34'! recordRemoveObject: id depth: depth! ! !FlashFileReader methodsFor: 'misc' stamp: 'ar 7/4/1998 20:34'! recordShowFrame! ! !FlashFileReader methodsFor: 'processing morphs' stamp: 'mir 11/2/1999 17:05'! processMorphFillStylesFrom: data | nFills nColors rampIndex rampColor id fillStyleType color1 color2 matrix1 matrix2 ramp1 ramp2 | nFills _ data nextByte. nFills = 255 ifTrue:[nFills _ data nextWord]. log ifNotNil:[log crtab; print: nFills; nextPutAll:' New fill styles']. 1 to: nFills do:[:i| log ifNotNil:[log crtab: 2; print: i; nextPut:$:; tab]. fillStyleType _ data nextByte. (fillStyleType = 0) ifTrue:["Solid fill" color1 _ data nextColor: true. color2 _ data nextColor: true. self recordMorphFill: i color1: color1 color2: color2. log ifNotNil:[log nextPutAll:'solid color '; print: color1; nextPutAll:' -- '; print: color2]. ]. (fillStyleType anyMask: 16) ifTrue:["Gradient fill" "Read gradient matrix" matrix1 _ data nextMatrix. matrix2 _ data nextMatrix. "Read color ramp data" nColors _ data nextByte. ramp1 _ Array new: nColors. ramp2 _ Array new: nColors. log ifNotNil:[log nextPutAll:'Gradient fill with '; print: nColors; nextPutAll:' colors']. 1 to: nColors do:[:j| rampIndex _ data nextByte. rampColor _ data nextColor: true. ramp1 at: j put: (rampIndex -> rampColor). rampIndex _ data nextByte. rampColor _ data nextColor: true. ramp2 at: j put: (rampIndex -> rampColor)]. self recordMorphFill: i matrix1: matrix1 ramp1: ramp1 matrix2: matrix2 ramp2: ramp2 linear: (fillStyleType = 16). fillStyleType _ 0]. (fillStyleType anyMask: 16r40) ifTrue:["Bit fill" "Read bitmap id" id _ data nextWord. "Read bitmap matrix" matrix1 _ data nextMatrix. matrix2 _ data nextMatrix. log ifNotNil:[log nextPutAll:'Bitmap fill id='; print: id]. self recordMorphFill: i matrix1: matrix1 matrix2: matrix2 id: id clipped: (fillStyleType anyMask: 1). fillStyleType _ 0]. fillStyleType = 0 ifFalse:[self error:'Unknown fill style: ',fillStyleType printString]. self flushLog. ].! ! !FlashFileReader methodsFor: 'processing morphs' stamp: 'ar 9/3/1999 14:40'! processMorphLineStylesFrom: data | nStyles styles lineWidth1 lineWidth2 lineColor1 lineColor2 | nStyles _ data nextByte. nStyles = 255 ifTrue:[nStyles _ data nextWord]. log ifNotNil:[log crtab; print: nStyles; nextPutAll:' New line styles']. styles _ Array new: nStyles. 1 to: nStyles do:[:i| lineWidth1 _ data nextWord. lineWidth2 _ data nextWord. lineColor1 _ data nextColor: true. lineColor2 _ data nextColor: true. self recordMorphLineStyle: i width1: lineWidth1 width2: lineWidth2 color1: lineColor1 color2: lineColor2. log ifNotNil:[log crtab: 2; print: i; nextPut:$:; tab; print: lineWidth1; tab; print: lineColor1; tab; print: lineWidth2; tab; print: lineColor2; tab]]. self flushLog. ^styles! ! !FlashFileReader methodsFor: 'processing morphs' stamp: 'ar 9/3/1999 19:08'! processMorphShapeFrom: data "Process a new morph shape" | id bounds1 bounds2 edgeOffset | "Read shape id and bounding box" id _ data nextWord. bounds1 _ data nextRect. bounds2 _ data nextRect. edgeOffset _ data nextULong. "edge offset" edgeOffset _ edgeOffset + data position. "Start new shape definition" self recordMorphShapeStart: id srcBounds: bounds1 dstBounds: bounds2. "Read fill styles for this shape" self processMorphFillStylesFrom: data. "Read line styles for this shape" self processMorphLineStylesFrom: data. "Get number of bits for fill and line styles" data initBits. nFillBits _ data nextBits: 4. nLineBits _ data nextBits: 4. "Process all records in this shape definition" [self processShapeRecordFrom: data] whileTrue. self recordMorphBoundary: id. data position: edgeOffset. data initBits. nFillBits _ data nextBits: 4. nLineBits _ data nextBits: 4. [self processShapeRecordFrom: data] whileTrue. "And mark the end of this shape" self recordMorphShapeEnd: id. self recordShapeProperty: id length: data size.! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:43'! recordMorphBoundary: id! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:06'! recordMorphFill: i color1: color1 color2: color2! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:07'! recordMorphFill: id matrix1: matrix1 matrix2: matrix2 id: bmId clipped: aBool! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:07'! recordMorphFill: id matrix1: matrix1 ramp1: ramp1 matrix2: matrix2 ramp2: ramp2 linear: isLinear! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:13'! recordMorphLineStyle: i width1: lineWidth1 width2: lineWidth2 color1: lineColor1 color2: lineColor2! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:18'! recordMorphShapeEnd: id! ! !FlashFileReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 14:06'! recordMorphShapeStart: id srcBounds: bounds1 dstBounds: bounds2! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashFileReader class instanceVariableNames: ''! !FlashFileReader class methodsFor: 'class initialization' stamp: 'ar 10/15/1998 01:18'! initialize "FlashFileReader initialize" self initializeTagTable. self initializeActionTable. self initializeStepTable. self initializeIndexTables.! ! !FlashFileReader class methodsFor: 'class initialization' stamp: 'ar 7/15/1998 18:53'! initializeActionTable "Create and return a new SWF action table" "FlashFileReader initializeActionTable" ActionTable _ Array new: 12. ActionTable atAllPut: #processUnknownAction:. #( (processActionGotoFrame: 1) (processActionGetURL: 3) (processActionNextFrame: 4) (processActionPrevFrame: 5) (processActionPlay: 6) (processActionStop: 7) (processActionToggleQuality: 8) (processActionStopSounds: 9) (processActionWaitForFrame: 10) (processActionSetTarget: 11) (processActionGotoLabel: 12) ) do:[:spec| ActionTable at: spec last put: spec first. ]. ! ! !FlashFileReader class methodsFor: 'class initialization' stamp: 'ar 10/15/1998 01:18'! initializeIndexTables IndexTables _ Array new: 4. IndexTables at: 1 put: #(-1 2). IndexTables at: 2 put: #(-1 -1 2 4). IndexTables at: 3 put: #(-1 -1 -1 -1 2 4 6 8). IndexTables at: 4 put: #(-1 -1 -1 -1 -1 -1 -1 -1 1 2 4 6 8 10 13 16).! ! !FlashFileReader class methodsFor: 'class initialization' stamp: 'ar 10/15/1998 01:15'! initializeStepTable StepTable _ #(7 8 9 10 11 12 13 14 16 17 19 21 23 25 28 31 34 37 41 45 50 55 60 66 73 80 88 97 107 118 130 143 157 173 190 209 230 253 279 307 337 371 408 449 494 544 598 658 724 796 876 963 1060 1166 1282 1411 1552 1707 1878 2066 2272 2499 2749 3024 3327 3660 4026 4428 4871 5358 5894 6484 7132 7845 8630 9493 10442 11487 12635 13899 15289 16818 18500 20350 22385 24623 27086 29794 32767).! ! !FlashFileReader class methodsFor: 'class initialization' stamp: 'ar 11/20/1998 22:10'! initializeTagTable "Create and return a new SWF tag table" "FlashFileReader initializeTagTable" TagTable _ Array new: 50. TagTable atAllPut: #processUnknown:. #( (processEnd: 0) (processShowFrame: 1) (processDefineShape: 2) (processFreeCharacter: 3) (processPlaceObject: 4) (processRemoveObject: 5) (processDefineBits: 6) (processDefineButton: 7) (processJPEGTables: 8) (processSetBackgroundColor: 9) (processDefineFont: 10) (processDefineText: 11) (processDoAction: 12) (processDefineFontInfo: 13) "Event sound tags." (processDefineSound: 14) (processStartSound: 15) (processDefineButtonSound: 17) (processSoundStreamHead: 18) (processSoundStreamBlock: 19) (processDefineBitsLossless: 20) "A bitmap using lossless zlib compression." (processDefineBitsJPEG2: 21) "A bitmap using an internal JPEG compression table" (processDefineShape2: 22) (processDefineButtonCxform: 23) (processProtect: 24) "This file should not be importable for editing." "These are the new tags for Flash 3." (processPlaceObject2: 26) "The new style place w/ alpha color transform and name." (processRemoveObject2: 28) "A more compact remove object that omits the character tag (just depth)." (processDefineShape3: 32) "A shape V3 includes alpha values." (processDefineText2: 33) "A text V2 includes alpha values." (processDefineButton2: 34) "A button V2 includes color transform) alpha and multiple actions" (processDefineBitsJPEG3: 35) "A JPEG bitmap with alpha info." (processDefineBitsLossless2: 36) "A lossless bitmap with alpha info." (processDefineSprite: 39) "Define a sequence of tags that describe the behavior of a sprite." (processNameCharacter: 40) "Name a character definition, character id and a string, (used for buttons) bitmaps, sprites and sounds)." (processFrameLabel: 43) "A string label for the current frame." (processSoundStreamHead2: 45) "For lossless streaming sound, should not have needed this..." (processDefineMorphShape: 46) "A morph shape definition" (processDefineFont2: 48) ) do:[:spec| TagTable at: spec last+1 put: spec first. ].! ! !FlashFileReader class methodsFor: 'instance creation' stamp: 'ar 7/3/1998 19:04'! fileNamed: aString "FlashFileReader fileNamed:'/home/isg/raab/WDI/flash/samples/top.swf'" ^self on: (FileStream readOnlyFileNamed: aString).! ! !FlashFileReader class methodsFor: 'instance creation' stamp: 'ar 7/2/1998 19:53'! on: aStream ^self new on: aStream! ! !FlashFileReader class methodsFor: 'testing' stamp: 'ar 7/2/1998 20:30'! canRead: aStream "Return true if instances of the receiver know how to handle the data from aStream." | ok pos | pos _ aStream position. ok _ aStream next asCharacter = $F and:[ aStream next asCharacter = $W and:[ aStream next asCharacter = $S]]. aStream position: pos. ^ok! ! !FlashFileReader class methodsFor: 'accessing' stamp: 'ar 10/16/1998 00:29'! tagTable ^TagTable! ! Stream subclass: #FlashFileStream instanceVariableNames: 'stream bitBuffer bitPosition hasAlpha ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Import'! !FlashFileStream methodsFor: 'initialize' stamp: 'ar 7/15/1998 20:10'! on: aSourceStream stream _ aSourceStream. bitBuffer _ bitPosition _ 0. hasAlpha _ false. "Turn on if needed"! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/3/1998 17:48'! atEnd ^stream atEnd! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/16/1998 01:44'! close self flushBits. stream close! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/15/1998 19:46'! hasAlpha ^hasAlpha! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/15/1998 19:46'! hasAlpha: aBoolean hasAlpha _ aBoolean! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/1/1998 14:23'! next "Make sure the bit buffer is reset" self initBits. ^stream next! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/13/1998 00:40'! nextByte "Make sure the bit buffer is reset" self initBits. ^stream next! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/15/1998 02:24'! nextByteForBits ^stream next ifNil:[0]! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/16/1998 01:19'! nextByteForBitsPut: aByte ^stream nextPut: aByte! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/16/1998 01:27'! nextBytePut: aByte "Make sure the bit buffer is reset" self flushBits. stream nextPut: aByte! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/3/1998 17:50'! nextBytes: n "Return a ByteArray containing the next n bytes" ^stream next: n! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/1/1998 14:43'! peekFor: anObject ^stream peekFor: anObject! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/3/1998 18:00'! position ^stream position! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 10/1/1998 14:56'! position: aNumber stream position: aNumber.! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/3/1998 18:00'! size ^stream size! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/15/1998 19:01'! skip: nBytes self initBits. stream skip: nBytes! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 7/3/1998 17:48'! stream ^stream! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 8/10/1998 14:18'! upToEnd ^self stream upToEnd.! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:38'! initBits "Initialize the bit buffer for future bit reading operations. Note: We do not fetch the first byte here so we can do multiple #initBits without harming the position of the input stream." bitPosition _ bitBuffer _ 0.! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 21:39'! nextBitFlag ^(self nextBits: 1) = 1! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/4/1998 18:27'! nextBits: n "Return the next n bits" | shift value remaining | n = 0 ifTrue:[^0]. (n between: 1 and: 32) ifFalse:[^self error:'Bad number of bits']. value _ 0. remaining _ n. [true] whileTrue:[ shift _ remaining - bitPosition. value _ value bitOr: (bitBuffer bitShift: shift). shift > 0 ifTrue:["Consumes entire buffer" remaining _ remaining - bitPosition. "And get next byte" bitBuffer _ self nextByteForBits. bitPosition _ 8. ] ifFalse:["Consumes a portion of the buffer" bitPosition _ bitPosition - remaining. "Mask off the consumed bits" bitBuffer _ bitBuffer bitAnd: (255 bitShift: (bitPosition - 8)). ^value]].! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/15/1998 19:44'! nextColor | r g b baseColor | r _ self nextByte / 255.0. g _ self nextByte / 255.0. b _ self nextByte / 255.0. baseColor _ Color r: r g: g b: b. ^hasAlpha ifTrue:[baseColor alpha: self nextByte / 255.0] ifFalse:[baseColor]! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 9/3/1999 14:40'! nextColor: usingAlpha | r g b baseColor | r _ self nextByte / 255.0. g _ self nextByte / 255.0. b _ self nextByte / 255.0. baseColor _ Color r: r g: g b: b. ^usingAlpha ifTrue:[baseColor alpha: self nextByte / 255.0] ifFalse:[baseColor]! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 11/24/1998 15:01'! nextColorMatrix "Read a (possibly compressed) color transformation" | transform nBits flags | transform _ FlashColorTransform new. self initBits. flags _ self nextBits: 2. nBits _ self nextBits: 4. (flags anyMask: 1) ifTrue:["Read multiplication factors" transform rMul: (self nextSignedBits: nBits) / 256.0. transform gMul: (self nextSignedBits: nBits) / 256.0. transform bMul: (self nextSignedBits: nBits) / 256.0. hasAlpha ifTrue:[transform aMul: (self nextSignedBits: nBits) / 256.0]]. (flags anyMask: 2) ifTrue:["Read multiplication factors" transform rAdd: (self nextSignedBits: nBits) / 256.0. transform gAdd: (self nextSignedBits: nBits) / 256.0. transform bAdd: (self nextSignedBits: nBits) / 256.0. hasAlpha ifTrue:[transform aAdd: (self nextSignedBits: nBits) / 256.0]]. ^transform! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 6/28/1999 16:33'! nextColorMatrix: usingAlpha | hadAlpha transform | hadAlpha _ hasAlpha. hasAlpha _ usingAlpha. transform _ self nextColorMatrix. hasAlpha _ hadAlpha. ^transform! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 10/16/1998 00:47'! nextLong | ulong | ulong _ self nextULong. ^ulong > 16r80000000 ifTrue:[ulong - 16r100000000] ifFalse:[ulong]! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 11/20/1998 00:29'! nextMatrix "Read a (possibly compressed) transformation matrix" | transform nBits | transform _ MatrixTransform2x3 identity. self initBits. (self nextBits: 1) = 1 ifTrue:["Read a,d" nBits _ self nextBits: 5. transform a11: (self nextSignedBits: nBits) / 65536.0. transform a22: (self nextSignedBits: nBits) / 65536.0]. (self nextBits: 1) = 1 ifTrue:["Read b,c" nBits _ self nextBits: 5. transform a21: (self nextSignedBits: nBits) / 65536.0. transform a12: (self nextSignedBits: nBits) / 65536.0]. "Read tx, ty" nBits _ self nextBits: 5. "Transcript cr; show:'nBits = ', nBits printString, ' from ', thisContext sender printString." transform a13: (self nextSignedBits: nBits) asFloat. transform a23: (self nextSignedBits: nBits) asFloat. ^transform! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/4/1998 18:42'! nextPoint "Read a (possibly compressed) point" | nBits point | nBits _ self nextBits: 5. point _ (self nextSignedBits: nBits) @ (self nextSignedBits: nBits). ^point! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:41'! nextRect "Read a (possibly compressed) rectangle" | nBits xMin xMax yMin yMax | self initBits. nBits _ self nextBits: 5. xMin _ self nextSignedBits: nBits. xMax _ self nextSignedBits: nBits. yMin _ self nextSignedBits: nBits. yMax _ self nextSignedBits: nBits. ^(xMin@yMin) corner: (xMax@yMax).! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 19:11'! nextSignedBits: n "Return the next n bits as signed integer value" | value bits signBit | n = 0 ifTrue:[^0]. value _ self nextBits: n. "Use a lookup for determining whether or not the value should be sign extended" bits _ #( 1 2 4 8 16 32 64 128 "1 ... 8" 256 512 1024 2048 4096 8192 16384 32768 "9 ... 16" 65536 131072 262144 524288 1048576 2097152 4194304 8388608 "17 ... 24" 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 2147483648 "25 ... 32" 4294967296 "33 bit -- for negation only" ). signBit _ bits at: n. ^(value bitAnd: signBit) = 0 ifTrue:[value] ifFalse:[value - (bits at: n+1)]! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:40'! nextString | out byte | out _ WriteStream on: (String new: 50). [byte _ self nextByte. byte = 0] whileFalse: [out nextPut: (self convertChar2Squeak: byte asCharacter)]. ^out contents! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:44'! nextTag "Read the next tag. Return an association with the key being the tag id and its value the contents of the chunk following." | word tag length | word _ self nextWord. "Extract tag and length from the word" length _ word bitAnd: 16r3F. tag _ word bitShift: -6. "Check if an extra word follows" length = 16r3F ifTrue:[length _ self nextULong]. ^Association key: tag value: (self nextBytes: length).! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:40'! nextULong ^self nextByte + (self nextByte bitShift: 8) + (self nextByte bitShift: 16) + (self nextByte bitShift: 24).! ! !FlashFileStream methodsFor: 'reading data' stamp: 'ar 7/3/1998 17:40'! nextWord ^self nextByte + (self nextByte bitShift: 8)! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:27'! flushBits "Flush the bit buffer for future bit writing operations." bitPosition = 0 ifFalse:[self nextByteForBitsPut: bitBuffer]. bitPosition _ 0. bitBuffer _ 0.! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 00:36'! nextBitFlag: aBoolean ^self nextBits: 1 put: (aBoolean ifTrue:[1] ifFalse:[0])! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:41'! nextBits: n put: aNumber "Write the next n bits" | value remaining shift | value _ aNumber. "Do not round - this is a sanity check" value isInteger ifFalse:[^self error:'Not an integer number']. value < 0 ifTrue:[^self error:'Not a positive number']. n = 0 ifTrue:[^0]. (n between: 1 and: 32) ifFalse:[^self error:'Bad number of bits']. value < (1 bitShift: n) ifFalse:[^self error:'Unable to represent number']. remaining _ n. [true] whileTrue:[ shift _ 8 - bitPosition - remaining. bitBuffer _ bitBuffer + (value bitShift: shift). "Mask out consumed bits" value _ value bitAnd: (1 bitShift: 0-shift) - 1. shift < 0 ifTrue:["Buffer overflow" remaining _ remaining - (8 - bitPosition). "Store next byte" self nextByteForBitsPut: bitBuffer. bitBuffer _ 0. bitPosition _ 0. ] ifFalse:["Store only portion of the buffer" bitPosition _ bitPosition + remaining. ^self ]. ].! ! !FlashFileStream methodsFor: 'writing data' stamp: 'bf 3/16/2000 19:01'! nextColorMatrixPut: cm "Write a (possibly compressed) color transformation" self flushBits. self nextBits: 2 put: 3. "Always write full transform" self nextBits: 4 put: 15. "Always use full accuracy" self nextSignedBits: 15 put: cm rMul. self nextSignedBits: 15 put: cm gMul. self nextSignedBits: 15 put: cm bMul. hasAlpha ifTrue:[self nextSignedBits: 15 put: cm aMul]. self nextSignedBits: 15 put: cm rAdd. self nextSignedBits: 15 put: cm gAdd. self nextSignedBits: 15 put: cm bAdd. hasAlpha ifTrue:[self nextSignedBits: 15 put: cm aAdd].! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 00:38'! nextColorPut: aColor self nextBytePut: (aColor red * 255) rounded. self nextBytePut: (aColor green * 255) rounded. self nextBytePut: (aColor blue * 255) rounded. hasAlpha ifTrue:[self nextBytePut: (aColor alpha * 255) rounded]. ! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 00:46'! nextLongPut: value value < 0 ifTrue:[self nextULongPut: 16r100000000 - value] ifFalse:[self nextULongPut: value]! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 11/2/1998 23:00'! nextMatrixPut: matrix "write a (possibly compressed) transformation matrix" self flushBits. (matrix a11 = 0.0 and:[matrix a22 = 0.0]) ifFalse:[ "Write a/d" self nextBits: 1 put: 1. self nextBits: 5 put: 31. "Always use full accuracy" self nextSignedBits: 31 put: matrix a11 * 65536. self nextSignedBits: 31 put: matrix a22 * 65536. ] ifTrue:[self nextBits: 1 put: 0]. ((matrix a12) = 0.0 and:[(matrix a21) = 0.0]) ifFalse:[ "Write b/c" self nextBits: 1 put: 1. self nextBits: 5 put: 31. "Always use full accuracy" self nextSignedBits: 31 put: matrix a12 * 65536. self nextSignedBits: 31 put: matrix a21 * 65536. ] ifTrue:[self nextBits: 1 put: 0]. "Write tx/ty" self nextBits: 5 put: 31. "Always use full accuracy" self nextSignedBits: 31 put: matrix a13. self nextSignedBits: 31 put: matrix a23. ! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 00:55'! nextPointPut: aPoint "Write a (possibly compressed) point" self nextBits: 5 put: 31. "Always write full accuracy" self nextSignedBits: 31 put: aPoint x. self nextSignedBits: 31 put: aPoint y. ! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:29'! nextRectPut: aRect "Write a (possibly compressed) rectangle" self nextBits: 5 put: 31. "Always use full accuracy" self nextSignedBits: 31 put: aRect origin x. self nextSignedBits: 31 put: aRect corner x. self nextSignedBits: 31 put: aRect origin y. self nextSignedBits: 31 put: aRect corner y.! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 00:59'! nextSignedBits: n put: someValue "Write the next n bits as signed integer value" | value | value _ someValue rounded. "Do rounding here if not done before" value < 0 ifTrue:[self nextBits: n put: 16r100000000 - value] ifFalse:[self nextBits: n put: value]! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:01'! nextStringPut: aString aString do:[:char| self nextBytePut: (self convertCharFromSqueak: char) asInteger]. self nextBytePut: 0.! ! !FlashFileStream methodsFor: 'writing data' stamp: 'di 2/9/1999 15:16'! nextTagPut: tag length: length "Write the next tag." length >= 16r3F ifTrue:[ self nextWordPut: (tag bitShift: 6) + 16r3F. self nextULongPut: length. ] ifFalse:[ self nextWordPut: (tag bitShift: 6) + length. ].! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:04'! nextULongPut: long self nextBytePut: (long bitAnd: 255). self nextBytePut: ((long bitShift: -8) bitAnd: 255). self nextBytePut: ((long bitShift: -16) bitAnd: 255). self nextBytePut: ((long bitShift: -24) bitAnd: 255).! ! !FlashFileStream methodsFor: 'writing data' stamp: 'ar 10/16/1998 01:06'! nextWordPut: value self nextBytePut: (value bitAnd: 255). self nextBytePut: ((value bitShift: -8) bitAnd: 255).! ! !FlashFileStream methodsFor: 'private' stamp: 'ar 7/3/1998 18:17'! convertChar2Squeak: aCharacter "Convert aCharacter from SWF char set (whatever this may be) to Squeaks char set" ^aCharacter! ! !FlashFileStream methodsFor: 'private' stamp: 'ar 10/16/1998 01:01'! convertCharFromSqueak: aCharacter "Convert aCharacter to SWF char set (whatever this may be) " ^aCharacter! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashFileStream class instanceVariableNames: ''! !FlashFileStream class methodsFor: 'instance creation' stamp: 'ar 7/3/1998 17:33'! on: aSourceStream ^self basicNew on: aSourceStream! ! Object subclass: #FlashFileWriter instanceVariableNames: 'stream log dataSize nFillBits nLineBits nGlyphBits nAdvanceBits jpegEncoder ' classVariableNames: 'TagTable ' poolDictionaries: '' category: 'Balloon-MMFlash Import'! !FlashFileWriter methodsFor: 'initialize' stamp: 'ar 10/16/1998 01:23'! close stream close! ! !FlashFileWriter methodsFor: 'initialize' stamp: 'ar 10/16/1998 01:22'! on: aStream aStream binary. stream _ FlashFileStream on: aStream.! ! !FlashFileWriter methodsFor: 'writing' stamp: 'ar 10/16/1998 01:26'! writeHeader: bounds rate: frameRate "Read header information from the source stream. Return true if successful, false otherwise." self halt. self writeSignature. stream nextBytePut: 3. "Always write flash3" dataSize _ stream nextLongPut: 0. "Place holder for data size" stream nextRectPut: bounds. stream nextWordPut: (frameRate * 256) truncated.! ! !FlashFileWriter methodsFor: 'writing' stamp: 'ar 10/16/1998 01:20'! writeSignature stream nextBytePut: $F asInteger. stream nextBytePut: $W asInteger. stream nextBytePut: $S asInteger.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashFileWriter class instanceVariableNames: ''! !FlashFileWriter class methodsFor: 'class initialization' stamp: 'ar 10/16/1998 00:31'! initialize "FlashFileWriter initialize" TagTable _ Dictionary new. FlashFileReader tagTable doWithIndex:[:tag :index| TagTable at: (tag copyWithout: $:) asSymbol put: index ].! ! !FlashFileWriter class methodsFor: 'instance creation' stamp: 'ar 10/16/1998 01:23'! newFileNamed: aString "FlashFileWriter newFileNamed:'f:\wdi\GraphicsEngine\flash\test.swf'" ^self on: (FileStream newFileNamed: aString).! ! !FlashFileWriter class methodsFor: 'instance creation' stamp: 'ar 10/16/1998 01:24'! on: aStream ^self new on: aStream! ! FlashMorph subclass: #FlashGlyphMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashGlyphMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 17:17'! color: aColor super color: aColor. submorphs do:[:m| m color: aColor].! ! !FlashGlyphMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 02:51'! defaultAALevel ^4! ! JPEGReadWriter subclass: #FlashJPEGDecoder instanceVariableNames: 'eoiSeen streaming ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Import'! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'ar 10/1/1998 14:34'! decodeJPEGTables: aStream self setStream: aStream. eoiSeen _ false. self parseFirstMarker. [eoiSeen] whileFalse:[self parseNextMarker].! ! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'ar 11/18/1998 21:33'! decodeNextImageFrom: aStream | image | self setStream: aStream. self isStreaming ifFalse:[Cursor wait show]. image _ self nextImage. self isStreaming ifFalse:[Cursor normal show]. ^image! ! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'ar 11/18/1998 23:25'! nextImageDitheredToDepth: depth "Overwritten to yield every now and then." | form xStep yStep x y | ditherMask _ DitherMasks at: depth ifAbsent: [self error: 'can only dither to display depths']. redResidual _ greenResidual _ blueResidual _ 0. sosSeen _ false. self parseFirstMarker. [sosSeen] whileFalse: [self parseNextMarker]. form _ Form extent: (width @ height) depth: 32. xStep _ mcuWidth * DCTSize. yStep _ mcuHeight * DCTSize. y _ 0. 1 to: mcuRowsInScan do: [:row | "self isStreaming ifTrue:[Processor yield]." x _ 0. 1 to: mcusPerRow do: [:col | self decodeMCU. self idctMCU. self colorConvertMCU. mcuImageBuffer displayOn: form at: (x @ y). x _ x + xStep]. y _ y + yStep]. ^ form! ! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'ar 10/1/1998 14:34'! parseEndOfInput eoiSeen _ true.! ! !FlashJPEGDecoder methodsFor: 'stream access' stamp: 'ar 10/1/1998 14:42'! next ^stream nextByte! ! !FlashJPEGDecoder methodsFor: 'stream access' stamp: 'ar 10/1/1998 14:43'! next: n ^stream nextBytes: n! ! !FlashJPEGDecoder methodsFor: 'accessing' stamp: 'ar 11/18/1998 21:32'! isStreaming ^streaming! ! !FlashJPEGDecoder methodsFor: 'accessing' stamp: 'ar 11/18/1998 21:31'! isStreaming: aBool streaming _ aBool! ! !FlashJPEGDecoder methodsFor: 'accessing' stamp: 'ar 1/15/1999 03:35'! understandsImageFormat "Return false so we don't get confused with ImageReadWriter's mechanism for finding the right class to read a given stream." ^false! ! Object subclass: #FlashKeyframe instanceVariableNames: 'start stop data ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashKeyframe methodsFor: 'initialize' stamp: 'ar 11/12/1998 22:55'! from: startValue to: stopValue data: newData start _ startValue. stop _ stopValue. data _ newData.! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:48'! data ^data! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:48'! data: anObject data _ anObject! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:55'! start ^start! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:55'! start: startValue start _ startValue! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:55'! stop ^stop! ! !FlashKeyframe methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:55'! stop: stopValue stop _ stopValue! ! !FlashKeyframe methodsFor: 'testing' stamp: 'di 11/21/1999 20:26'! includesFrame: aNumber ^aNumber >= start and:[aNumber <= stop]! ! !FlashKeyframe methodsFor: 'printing' stamp: 'ar 11/13/1998 14:33'! printOn: aStream aStream nextPutAll: self class name; nextPut:$(; print: start; nextPut:$-; print: stop; nextPutAll:' -> '; print: data; nextPut:$)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashKeyframe class instanceVariableNames: ''! !FlashKeyframe class methodsFor: 'instance creation' stamp: 'ar 11/12/1998 22:47'! from: startValue to: stopValue ^self new from: startValue to: stopValue! ! !FlashKeyframe class methodsFor: 'instance creation' stamp: 'ar 11/12/1998 22:54'! from: startValue to: stopValue data: newData ^self new from: startValue to: stopValue data: newData! ! Object subclass: #FlashKeyframes instanceVariableNames: 'kfList lastIndex ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashKeyframes methodsFor: 'initialize' stamp: 'ar 8/14/1998 19:32'! initialize kfList _ OrderedCollection new.! ! !FlashKeyframes methodsFor: 'accessing' stamp: 'di 11/21/1999 20:26'! at: frameNumber "Return data from the keyframe list at the given frame number" | lastEntry | kfList isEmpty ifTrue:[^nil]. lastIndex ifNil:[lastIndex _ self searchFor: frameNumber]. lastEntry _ kfList at: lastIndex. (lastEntry includesFrame: frameNumber) ifTrue:[^lastEntry data]. "Do a quick check if the frame is out of range" kfList first stop >= frameNumber ifTrue:[ lastIndex _ 1. ^kfList first data]. kfList last start <= frameNumber ifTrue:[ lastIndex _ kfList size. ^kfList last data]. "Search linearly from lastEntry - most times we'll just be one step away" [lastEntry stop >= frameNumber] whileFalse:[ lastIndex _ lastIndex+1. lastEntry _ kfList at: lastIndex]. [lastEntry start <= frameNumber] whileFalse:[ lastIndex _ lastIndex-1. lastEntry _ kfList at: lastIndex]. ^lastEntry data! ! !FlashKeyframes methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:51'! at: frameNumber put: newData "Add newData to the keyframe list at the given frameNumber" | kf | kfList ifNil:[kfList _ OrderedCollection new]. kfList isEmpty ifFalse:["Check if we can extend the last interval" kf _ kfList last. kf stop < frameNumber ifFalse:[^self replaceData: newData at: frameNumber]. kf data = newData "Extend interval to include frameNumber" ifTrue:[ kf stop: frameNumber. ^newData]. "Extend last interval to just before frameNumer" kf stop: frameNumber - 1]. kfList add: (FlashKeyframe from: frameNumber to: frameNumber data: newData). ^newData! ! !FlashKeyframes methodsFor: 'accessing' stamp: 'ar 11/12/1998 22:51'! keys ^kfList collect:[:kf| kf start].! ! !FlashKeyframes methodsFor: 'accessing' stamp: 'ar 10/14/1998 20:27'! size ^kfList size! ! !FlashKeyframes methodsFor: 'printing' stamp: 'ar 8/14/1998 19:32'! printOn: aStream aStream nextPutAll: self class name; nextPut: $(; cr. kfList do:[:item| aStream print: item; cr]. aStream nextPut:$).! ! !FlashKeyframes methodsFor: 'private' stamp: 'ar 11/12/1998 22:51'! replaceData: newData at: frameNumber (kfList last stop = frameNumber) ifTrue:[^self replaceLastData: newData at: frameNumber]. self halt:'Not implemented yet'! ! !FlashKeyframes methodsFor: 'private' stamp: 'ar 11/18/1998 23:29'! replaceLastData: newData at: frameNumber | kf | lastIndex _ nil. kf _ kfList last. (kf stop = kf start) ifTrue:[kfList removeLast] ifFalse:[kf stop: kf stop-1]. ^self at: frameNumber put: newData! ! !FlashKeyframes methodsFor: 'private' stamp: 'di 11/21/1999 20:26'! searchFor: frameNumber "Return data from the keyframe list at the given frame number" | low high mid kf | low _ kfList at: 1. high _ kfList at: kfList size. "Check if in or before first keyframe interval" frameNumber <= low stop ifTrue:[^1]. "Check if in or after last keyframe interval" frameNumber >= high start ifTrue:[^kfList size]. "Somewhere inbetween 2nd to (n-1)th interval" low _ 2. high _ kfList size - 1. [mid _ high + low // 2. low > high] whileFalse:[ kf _ kfList at: mid. (kf includesFrame: frameNumber) ifTrue:[^mid]. (kf start < frameNumber) ifTrue:[low _ mid + 1] ifFalse:[high _ mid - 1]]. kf _ kfList at: low. (kf includesFrame: frameNumber) ifFalse:[self error:'No keyframe found']. ^low! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashKeyframes class instanceVariableNames: ''! !FlashKeyframes class methodsFor: 'instance creation' stamp: 'ar 8/14/1998 19:32'! new ^super new initialize! ! Object subclass: #FlashLineStyle instanceVariableNames: 'width color ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashLineStyle methodsFor: 'accessing' stamp: 'ar 7/14/1998 21:20'! color ^color! ! !FlashLineStyle methodsFor: 'accessing' stamp: 'ar 7/14/1998 21:20'! color: aColor color _ aColor! ! !FlashLineStyle methodsFor: 'accessing' stamp: 'ar 7/14/1998 21:20'! color: aColor width: aNumber self color: aColor. self width: aNumber.! ! !FlashLineStyle methodsFor: 'accessing' stamp: 'ar 7/14/1998 21:20'! width ^width! ! !FlashLineStyle methodsFor: 'accessing' stamp: 'ar 7/14/1998 21:20'! width: aNumber width _ aNumber! ! !FlashLineStyle methodsFor: 'comparing' stamp: 'ar 8/15/1998 00:59'! = aLineStyle self class = aLineStyle class ifFalse:[^false]. ^self color = aLineStyle color and:[self width = aLineStyle width].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashLineStyle class instanceVariableNames: ''! !FlashLineStyle class methodsFor: 'instance creation' stamp: 'ar 7/14/1998 21:19'! color: aColor width: aNumber ^self new color: aColor width: aNumber! ! MatrixTransformMorph subclass: #FlashMorph instanceVariableNames: 'colorTransform ' classVariableNames: 'FlashSoundVolume ' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashMorph methodsFor: 'initialize' stamp: 'ar 11/16/1998 17:28'! loadInitialFrame self computeBounds.! ! !FlashMorph methodsFor: 'initialize' stamp: 'ar 11/19/1998 22:23'! lockChildren submorphs do:[:m| m isMouseSensitive ifFalse:[m lock]].! ! !FlashMorph methodsFor: 'initialize' stamp: 'ar 11/13/1998 16:10'! reset submorphs do:[:m| m isFlashMorph ifTrue:[m reset]].! ! !FlashMorph methodsFor: 'initialize' stamp: 'ar 8/15/1998 17:21'! unlockChildren submorphs do:[:m| m unlock].! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 21:41'! activationKeys ^#()! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/24/1998 14:27'! colorTransform ^colorTransform! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/24/1998 14:27'! colorTransform: aColorTransform colorTransform _ aColorTransform! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 02:48'! defaultAALevel ^self valueOfProperty: #aaLevel! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 02:51'! defaultAALevel: aNumber aNumber isNil ifTrue:[self removeProperty: #aaLevel] ifFalse:[self setProperty: #aaLevel toValue: aNumber]! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 16:13'! delete | player | player _ self flashPlayer. player ifNotNil:[player noticeRemovalOf: self]. ^super delete! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 21:00'! depth ^(self valueOfProperty: #depth) ifNil:[0]! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 21:00'! depth: d d = 0 ifTrue:[self removeProperty: #depth] ifFalse:[self setProperty: #depth toValue: d]! ! !FlashMorph methodsFor: 'accessing' stamp: 'di 11/12/2000 15:53'! flashPlayer ^ self firstOwnerSuchThat: [:parent | parent isFlashMorph and: [parent isFlashPlayer]]! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 11:40'! id ^-1! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 11/24/1998 14:19'! keepsTransform "Return true if the receiver will keep it's transform while being grabbed by a hand." ^true! ! !FlashMorph methodsFor: 'accessing' stamp: 'ar 1/4/1999 08:48'! originalFileSize ^(self valueOfProperty: #originalFileSize) ifNil:[0]! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 11/16/1998 23:47'! isFlashButton ^false! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 8/14/1998 21:52'! isFlashCharacter ^false! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 8/14/1998 21:12'! isFlashMorph ^true! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 11/15/1998 19:04'! isFlashPlayer ^false! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 6/2/1999 03:15'! isFlashShape ^false! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 11/16/1998 17:03'! isFlashSprite ^false! ! !FlashMorph methodsFor: 'classification' stamp: 'ar 11/19/1998 22:22'! isMouseSensitive "Return true if the receiver is mouse sensitive and must stay unlocked" ^false! ! !FlashMorph methodsFor: 'drawing' stamp: 'ar 11/16/1998 19:04'! debugDraw | vis canvas m | vis _ self visible. self visible: true. canvas _ BalloonCanvas on:Display. m _ MatrixTransform2x3 withScale: 0.05. m offset: (self fullBounds origin // 20) negated. canvas transformBy: m. self fullDrawOn: canvas. self visible: vis.! ! !FlashMorph methodsFor: 'drawing' stamp: 'ar 5/29/1999 09:08'! drawSubmorphsOn: aCanvas | aaLevel | aCanvas asBalloonCanvas preserveStateDuring:[:myCanvas| colorTransform ifNotNil:[myCanvas colorTransformBy: colorTransform]. (aaLevel _ self defaultAALevel) ifNotNil:[myCanvas aaLevel: aaLevel]. super drawSubmorphsOn: myCanvas].! ! !FlashMorph methodsFor: 'menu' stamp: 'ar 6/16/1999 07:13'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addUpdating: #getSmoothingLevel action: #nextSmoothingLevel. aCustomMenu add:'show compressed size' action: #showCompressedSize.! ! !FlashMorph methodsFor: 'menu' stamp: 'ar 6/16/1999 07:16'! getSmoothingLevel "Menu support" | aaLevel | aaLevel _ self defaultAALevel ifNil:[1]. aaLevel = 1 ifTrue:[^'turn on smoothing']. aaLevel = 2 ifTrue:[^'more smoothing']. aaLevel = 4 ifTrue:[^'turn off smoothing']. ! ! !FlashMorph methodsFor: 'menu' stamp: 'ar 6/16/1999 07:17'! nextSmoothingLevel | aaLevel | aaLevel _ self defaultAALevel ifNil:[1]. aaLevel = 1 ifTrue:[self defaultAALevel: 2]. aaLevel = 2 ifTrue:[self defaultAALevel: 4]. aaLevel = 4 ifTrue:[self defaultAALevel: nil]. self changed.! ! !FlashMorph methodsFor: 'menu' stamp: 'ar 1/5/1999 16:18'! showCompressedSize | size string | size _ self originalFileSize. size = 0 ifTrue:[string _ 'Compressed size: not available'] ifFalse:[string _ 'Compressed size: ', size asStringWithCommas, ' bytes']. self world primaryHand attachMorph: (TextMorph new contents: string; beAllFont: ScriptingSystem fontForTiles).! ! !FlashMorph methodsFor: 'events' stamp: 'ar 10/5/2000 19:58'! aboutToBeGrabbedBy: aHand "Usually, FlashMorphs exist in a player. If they're grabbed and moved outside the player they should keep their position." | player | super aboutToBeGrabbedBy: aHand. player _ self flashPlayer. player ifNotNil:[player noticeRemovalOf: self]. self transform: (self transformFrom: self world). "If extracted from player and no default AA level is set use prefs" (player notNil and:[self defaultAALevel == nil]) ifTrue:[ Preferences extractFlashInHighQuality ifTrue:[self defaultAALevel: 2]. Preferences extractFlashInHighestQuality ifTrue:[self defaultAALevel: 4]. ]. ^self "Grab me"! ! !FlashMorph methodsFor: 'events' stamp: 'ar 11/18/1998 14:04'! justDroppedInto: newOwner event: evt | ownerTransform | ownerTransform _ (newOwner transformFrom: newOwner world). ownerTransform isIdentity ifFalse:[ ownerTransform _ ownerTransform asMatrixTransform2x3 inverseTransformation. self transform: (self transform composedWithGlobal: ownerTransform). ]. super justDroppedInto: newOwner event: evt.! ! !FlashMorph methodsFor: 'printing' stamp: 'ar 11/16/1998 11:40'! printOn: aStream super printOn: aStream. aStream nextPut:$[; print: self depth; space. self visible ifTrue:[aStream nextPutAll:'visible'] ifFalse:[aStream nextPutAll:'invisible']. aStream nextPutAll:' id = '; print: self id; nextPut:$]; cr.! ! !FlashMorph methodsFor: 'copying' stamp: 'ar 5/20/1999 15:34'! copyExtension "Copy my extensions dictionary" | ext | extension ifNil:[^self]. ext _ extension copy. ext removeOtherProperties. extension otherProperties ifNotNil:[ extension otherProperties associationsDo:[:assoc| ext setProperty: assoc key toValue: assoc value copy. ]. ]. extension _ ext.! ! !FlashMorph methodsFor: 'copying' stamp: 'ar 5/20/1999 15:30'! copyMovieFrom: firstFrame to: lastFrame | copy | copy _ self copy. copy copyExtension. copy addAllMorphs: (self submorphs collect:[:m| m copyMovieFrom: firstFrame to: lastFrame]). ^copy! ! !FlashMorph methodsFor: 'sound' stamp: 'jm 6/7/1999 08:25'! playFlashSound: aSound "Play the given sound at the volume level for Flash sounds." FlashSoundVolume ifNil: [FlashSoundVolume _ 0.3]. (MixedSound new add: aSound pan: 0.5 volume: FlashSoundVolume) play. ! ! !FlashMorph methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:23'! compress "Compress the receiver for efficient storage on disk" fullBounds _ nil. "Will be computed on the fly" submorphs do:[:m| m compress].! ! !FlashMorph methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:23'! decompress "Decompress the receiver" submorphs do:[:m| m decompress]. self fullBounds. "Force computation"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashMorph class instanceVariableNames: ''! !FlashMorph class methodsFor: 'instance creation' stamp: 'ar 11/15/1998 16:44'! withAll: aCollection ^(self new) addAllMorphs: aCollection; computeBounds; yourself! ! FlashFileReader subclass: #FlashMorphReader instanceVariableNames: 'location fillStyles lineStyles shapes fonts forms sounds buttons lineSequence currentShape player spriteOwners stepTime frameRate frame activeMorphs passiveMorphs activeFont textOffset textHeight textMorph canCompressPoints pointList compressionBounds fillIndex0 fillIndex1 lineStyleIndex leftFillList rightFillList lineStyleList streamingSound morphedFillStyles morphedLineStyles ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Import'! !FlashMorphReader methodsFor: 'initialize' stamp: 'ar 10/15/1998 23:45'! doLog ^false! ! !FlashMorphReader methodsFor: 'initialize' stamp: 'ar 10/14/1998 19:22'! logShapes ^false! ! !FlashMorphReader methodsFor: 'initialize' stamp: 'ar 11/21/1998 00:30'! on: aStream super on: aStream. self doLog ifTrue:[log _ Transcript]. fillStyles _ Dictionary new. lineStyles _ Dictionary new. shapes _ Dictionary new. player _ FlashPlayerMorph new. fonts _ Dictionary new. forms _ Dictionary new. sounds _ Dictionary new. buttons _ Dictionary new. spriteOwners _ IdentityDictionary new. stepTime _ 1000. frame _ 1. activeMorphs _ Dictionary new: 100. passiveMorphs _ Dictionary new: 100. self recordSolidFill: 1 color: Color black. compressionBounds _ (-16r7FFF asPoint) corner: (16r8000) asPoint. currentShape _ WriteStream on: (Array new: 5). pointList _ WriteStream on: (Array new: 100). leftFillList _ WriteStream on: (WordArray new: 100). rightFillList _ WriteStream on: (WordArray new: 100). lineStyleList _ WriteStream on: (WordArray new: 100). fillIndex0 _ fillIndex1 _ lineStyleIndex _ 0. streamingSound _ FlashStreamingSound new.! ! !FlashMorphReader methodsFor: 'reading' stamp: 'ar 2/13/1999 21:25'! processFile "Read and process the entire file" super processFile. player loadInitialFrame. ^player! ! !FlashMorphReader methodsFor: 'reading' stamp: 'ar 11/19/1998 21:54'! processFileAsync "Read and process the entire file" self processHeader ifFalse:[^nil]. player sourceUrl:'dummy'. [self processFileContents] fork. ^player! ! !FlashMorphReader methodsFor: 'reading' stamp: 'ar 11/18/1998 23:44'! processFileAsync: aPlayer "Read and process the entire file" player _ aPlayer. super processFile.! ! !FlashMorphReader methodsFor: 'reading' stamp: 'ar 11/21/1998 00:50'! processFileContents super processFileContents. self flushStreamingSound.! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/16/1998 01:23'! recordFontShapeEnd: fontId with: charId | font shape | self endShape. shape _ FlashGlyphMorph withAll: currentShape contents reversed. shape lockChildren. currentShape resetToStart. font _ fonts at: fontId ifAbsentPut:[Dictionary new]. font at: charId put: shape. self doLog ifTrue:[log _ Transcript].! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/12/1998 21:39'! recordFontShapeStart: fontId with: charId location _ 0@0. self logShapes ifFalse:[log _ nil]. self beginShape. self recordSolidFill: 1 color: Color black.! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/20/1998 01:41'! recordNextChar: glyphIndex advanceWidth: advance | shape transform | (activeFont includesKey: glyphIndex) ifTrue:[ shape _ (activeFont at: glyphIndex) fullCopy reset. "Must include the textMorph's transform here - it might be animated" transform _ ((MatrixTransform2x3 withOffset: textOffset) setScale: (textHeight@textHeight) / 1024.0). transform _ transform composedWithGlobal: textMorph transform. shape transform: transform. shape color: textMorph color. textMorph addMorphBack: shape.]. textOffset _ textOffset + (advance@0).! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/20/1998 01:46'! recordTextChange: fontId color: color xOffset: xOffset yOffset: yOffset height: height fontId ifNotNil:[activeFont _ fonts at: fontId]. height ifNotNil:[textHeight _ height]. xOffset ifNotNil:[textOffset _ xOffset @ textOffset x]. yOffset ifNotNil:[textOffset _ textOffset x @ yOffset]. color ifNotNil:[textMorph color: color].! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/20/1998 00:50'! recordTextEnd: id textMorph submorphs isEmpty ifFalse:[ textMorph allMorphsDo:[:m| m color: textMorph color]. textMorph transform: nil. textMorph id: id. textMorph stepTime: stepTime. textMorph lockChildren. shapes at: id put: textMorph]. self doLog ifTrue:[log _ Transcript].! ! !FlashMorphReader methodsFor: 'defining text' stamp: 'ar 11/20/1998 01:41'! recordTextStart: id bounds: bounds matrix: matrix textOffset _ 0@0. textMorph _ FlashTextMorph new. textMorph privateBounds: bounds. textMorph color: Color black. matrix ifNotNil:[textMorph transform: matrix].! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 8/14/1998 16:19'! recordCurveSegmentTo: anchorPoint with: controlPoint | target midPoint | midPoint _ location + controlPoint. target _ midPoint + anchorPoint. self addLineFrom: location to: target via: midPoint. location _ target.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/17/1998 00:36'! recordEndSubshape "A new subshape begins with a full set of line and fill styles" self endShape. self beginShape.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/12/1998 20:45'! recordFillStyle0: fillIndex fillIndex0 _ fillIndex.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/12/1998 20:45'! recordFillStyle1: fillIndex fillIndex1 _ fillIndex.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/3/1998 16:09'! recordLineSegmentBy: deltaPoint | target | target _ location + deltaPoint. self addLineFrom: location to: target via: location. location _ target.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/12/1998 20:40'! recordLineStyle: styleIndex lineStyleIndex _ styleIndex.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/12/1998 20:44'! recordMoveTo: aPoint location _ aPoint.! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 11/16/1998 01:23'! recordShapeEnd: shapeId | shape | self endShape. shape _ FlashCharacterMorph withAll: (currentShape contents reversed). shape lockChildren. currentShape resetToStart. shape id: shapeId. shape stepTime: stepTime. shapes at: shapeId put: shape. self doLog ifTrue:[log _ Transcript].! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 1/4/1999 08:47'! recordShapeProperty: id length: length (shapes at: id ifAbsent:[^self]) setProperty: #originalFileSize toValue: length! ! !FlashMorphReader methodsFor: 'composing shapes' stamp: 'ar 8/14/1998 23:23'! recordShapeStart: shapeId bounds: bounds location _ 0@0. self logShapes ifFalse:[log _ nil]. self beginShape.! ! !FlashMorphReader methodsFor: 'computing shapes' stamp: 'ar 11/3/1998 18:09'! addLineFrom: start to: end via: via canCompressPoints ifTrue:[ "Check if we can compress the incoming points" (compressionBounds containsPoint: start) ifFalse:[canCompressPoints _ false]. (compressionBounds containsPoint: via) ifFalse:[canCompressPoints _ false]. (compressionBounds containsPoint: end) ifFalse:[canCompressPoints _ false]. ]. pointList nextPut: start. pointList nextPut: via. pointList nextPut: end. leftFillList nextPut: fillIndex0. rightFillList nextPut: fillIndex1. lineStyleList nextPut: lineStyleIndex. ! ! !FlashMorphReader methodsFor: 'computing shapes' stamp: 'ar 11/12/1998 21:43'! beginShape canCompressPoints _ true. fillStyles _ Dictionary new. lineStyles _ Dictionary new. pointList resetToStart. leftFillList resetToStart. rightFillList resetToStart. lineStyleList resetToStart. fillIndex0 _ fillIndex1 _ lineStyleIndex _ 0.! ! !FlashMorphReader methodsFor: 'computing shapes' stamp: 'ar 11/12/1998 21:24'! computeFillLists "Compute the fill index lists" | leftFills rightFills | leftFills_ leftFillList contents as: ShortRunArray. rightFills _ rightFillList contents as: ShortRunArray. ^Array with: leftFills with: rightFills! ! !FlashMorphReader methodsFor: 'computing shapes' stamp: 'ar 11/16/1998 13:02'! computeLineStyleLists "Compute the line style index lists. Each line style will be splitted into two parts, the width and the fill. Then, the fills will be added to the fillStyles and the indexes will be adjusted. Finally, we compute two arrays containing the width of each line and the fill style of each line" | widthList fillList indexMap oldIndex newIndex allFillStyles style | allFillStyles _ Dictionary new. fillStyles associationsDo:[:assoc| allFillStyles at: assoc key put: assoc value]. indexMap _ Dictionary new. lineStyles associationsDo:[:assoc| oldIndex _ assoc key. style _ assoc value. allFillStyles at: allFillStyles size+1 put: (SolidFillStyle color: style color). newIndex _ allFillStyles size. indexMap at: oldIndex put: newIndex. ]. widthList _ OrderedCollection new: lineStyles size. fillList _ OrderedCollection new: lineStyles size. lineStyleList contents do:[:index| index = 0 ifTrue:[ widthList add: 0. fillList add: 0. ] ifFalse:[ style _ lineStyles at: index ifAbsent:[FlashLineStyle color: Color black width: 20]. widthList add: style width. fillList add: (indexMap at: index ifAbsent:[1]). ]. ]. widthList _ widthList as: ShortRunArray. fillList _ fillList as: ShortRunArray. ^Array with: allFillStyles with: fillList with: widthList! ! !FlashMorphReader methodsFor: 'computing shapes' stamp: 'ar 11/15/1998 15:32'! endShape | points shape fillLists lineLists index | canCompressPoints ifTrue:[ points _ ShortPointArray new: pointList size. ] ifFalse:[ points _ PointArray new: pointList size. ]. index _ 1. pointList contents do:[:p| points at: index put: p. index _ index + 1]. fillLists _ self computeFillLists. lineLists _ self computeLineStyleLists. shape _ FlashBoundaryShape points: points leftFills: fillLists first rightFills: fillLists last fillStyles: lineLists first lineWidths: lineLists last lineFills: (lineLists at: 2). shape remapFills. currentShape nextPut:(FlashShapeMorph shape: shape).! ! !FlashMorphReader methodsFor: 'defining styles' stamp: 'ar 12/5/1998 22:22'! recordBitmapFill: index matrix: bmMatrix id: bitmapID clipped: aBoolean | fillStyle form | form _ forms at: bitmapID ifAbsent:[^nil]. fillStyle _ BitmapFillStyle form: form. fillStyle origin: (bmMatrix localPointToGlobal: 0@0). fillStyle direction: (bmMatrix localPointToGlobal: form extent x @ 0) - fillStyle origin. fillStyle normal: (bmMatrix localPointToGlobal: 0 @ form extent y) - fillStyle origin. fillStyle tileFlag: aBoolean not. fillStyles at: index put: fillStyle.! ! !FlashMorphReader methodsFor: 'defining styles' stamp: 'ar 11/18/1998 21:36'! recordGradientFill: fillIndex matrix: gradientMatrix ramp: colorRampArray linear: aBoolean | fillStyle ramp origin direction normal | ramp _ colorRampArray collect:[:assoc| (assoc key / 255.0) -> assoc value]. origin _ gradientMatrix localPointToGlobal: (aBoolean ifFalse:[0@0] ifTrue:[-16384@0]). direction _ (gradientMatrix localPointToGlobal: (16384@0)) - origin. normal _ (gradientMatrix localPointToGlobal: (0@16384)) - origin. fillStyle _ GradientFillStyle ramp: ramp. fillStyle origin: origin. fillStyle direction: direction. fillStyle normal: normal. fillStyle radial: aBoolean not. fillStyle pixelRamp. "Force creation beforehand" fillStyles at: fillIndex put: fillStyle.! ! !FlashMorphReader methodsFor: 'defining styles' stamp: 'ar 8/15/1998 00:58'! recordLineStyle: styleIndex width: lineWidth color: lineColor lineStyles at: styleIndex put: (FlashLineStyle color: lineColor width: lineWidth).! ! !FlashMorphReader methodsFor: 'defining styles' stamp: 'ar 11/11/1998 22:39'! recordSolidFill: index color: fillColor fillStyles at: index put: (SolidFillStyle color: fillColor)! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 10/15/1998 20:44'! recordBackgroundColor: aColor player color: aColor! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/19/1998 20:30'! recordBeginSprite: id frames: frameCount | sprite | sprite _ FlashSpriteMorph new. sprite maxFrames: frameCount. sprite stepTime: stepTime. spriteOwners at: sprite put: ( Array with: player with: frame with: activeMorphs with: passiveMorphs). player _ sprite. frame _ 1. activeMorphs _ Dictionary new: 100. passiveMorphs _ Dictionary new: 100. ! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/12/1998 21:50'! recordBitmap: id data: aForm aForm ifNil:[^self]. "Record the current form" forms at: id put: aForm. "Define a new character" ! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/24/1998 14:35'! recordEndSprite: id | shape sprite | sprite _ player. player _ (spriteOwners at: sprite) at: 1. frame _ (spriteOwners at: sprite) at: 2. activeMorphs _ (spriteOwners at: sprite) at: 3. passiveMorphs _ (spriteOwners at: sprite) at: 4. spriteOwners removeKey: sprite. sprite loadInitialFrame. shape _ FlashCharacterMorph withAll: (Array with: sprite). shape id: id. shape isSpriteHolder: true. shape stepTime: stepTime. shapes at: id put: shape. shape lockChildren. ! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/16/1998 22:53'! recordFrameActions: actionList player addActions: actionList atFrame: frame.! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/18/1998 22:00'! recordFrameCount: maxFrames player maxFrames: maxFrames! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/17/1998 13:36'! recordFrameLabel: label "Name the current frame with the given label" player addLabel: label atFrame: frame.! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/21/1998 00:32'! recordFrameRate: fps frameRate _ fps. fps > 0.0 ifTrue:[stepTime _ (1000.0 / fps) rounded]. player stepTime: stepTime.! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/18/1998 20:42'! recordGlobalBounds: bounds player localBounds: bounds.! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 9/3/1999 15:10'! recordMorph: id depth: depth ratio: ratio! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 9/3/1999 15:50'! recordMoveObject: objectIndex name: aString depth: depth matrix: matrix colorMatrix: colorMatrix ratio: ratio | index oldObj mat | index _ nil. activeMorphs do:[:list| list do:[:morph| ((morph visibleAtFrame: frame-1) and:[ (morph depthAtFrame: frame-1) = depth]) ifTrue:[index _ morph id]]]. oldObj _ self recordRemoveObject: index depth: depth. oldObj isNil ifTrue:[^self]. objectIndex isNil ifFalse:[index _ objectIndex]. matrix isNil ifTrue:[mat _ oldObj matrixAtFrame: frame] ifFalse:[mat _ matrix]. self recordPlaceObject: index name: aString depth: depth matrix: mat colorMatrix: colorMatrix ratio: ratio.! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 9/3/1999 15:50'! recordPlaceObject: objectIndex name: aString depth: depth matrix: matrix colorMatrix: colorTransform ratio: ratio | cached active doLoad | cached _ passiveMorphs at: objectIndex ifAbsent:[#()]. cached size >= 1 ifTrue:["Got an old morph. Re-use it" doLoad _ false. active _ cached first. passiveMorphs at: objectIndex put: (cached copyWithout: active)] ifFalse:["Need a new morph" doLoad _ true. active _ self newMorphFromShape: objectIndex. active isNil ifTrue:[^self]. active reset. active visible: false atFrame: frame - 1]. active isNil ifTrue:[^self]. active visible: true atFrame: frame. active depth: depth atFrame: frame. active matrix: matrix atFrame: frame. active colorTransform: colorTransform atFrame: frame. doLoad ifTrue:[ active loadInitialFrame. player addMorph: active]. cached _ (activeMorphs at: objectIndex ifAbsent:[#()]) copyWith: active. activeMorphs at: objectIndex put: cached. aString ifNotNil:[active setNameTo: aString]. ratio ifNotNil:[active ratio: ratio atFrame: frame].! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/21/1998 01:57'! recordRemoveObject: id depth: depth id ifNotNil:["Faster if id is given" (activeMorphs at: id ifAbsent:[#()]) do:[:morph| ((morph visibleAtFrame: frame-1) and:[ (morph depthAtFrame: frame-1) = depth]) ifTrue:[^self removeActiveMorph: morph]]]. activeMorphs do:[:list| list do:[:morph| ((morph visibleAtFrame: frame-1) and:[ (morph depthAtFrame: frame-1) = depth]) ifTrue:[^self removeActiveMorph: morph]]]. Transcript cr; nextPutAll:'Shape (id = '; print: id; nextPutAll:' depth = '; print: depth; nextPutAll:') not removed in frame '; print: frame; endEntry. ^nil! ! !FlashMorphReader methodsFor: 'misc' stamp: 'ar 11/19/1998 20:30'! recordShowFrame player loadedFrames: frame. frame _ frame + 1.! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 11/20/1998 01:04'! createSound: id info: info | theSound loops | theSound _ sounds at: id ifAbsent:[^nil]. loops _ info loopCount. loops <= 1 ifTrue:[^theSound]. ^RepeatingSound repeat: theSound count: loops! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 11/16/1998 19:39'! myActiveMorphs | out | out _ WriteStream on: (Array new: 10). activeMorphs do:[:array| out nextPutAll: array]. ^out contents! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 11/16/1998 19:26'! myFlush Transcript endEntry. Sensor leftShiftDown ifTrue:[self halt].! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 9/3/1999 18:39'! newMorphFromShape: objectIndex "Return a new character morph from the given object index. If the character morph at objectIndex is already used, then create and return a full copy of it" | prototype | prototype _ self oldMorphFromShape: objectIndex. prototype isNil ifTrue:[^nil]. ^(prototype owner notNil) ifTrue:[prototype fullCopy] ifFalse:[prototype].! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 11/16/1998 20:24'! oldMorphFromShape: objectIndex "Return an existing character morph from the given object index." | prototype | prototype _ shapes at: objectIndex ifAbsent:[nil]. "prototype ifNil:[prototype _ buttons at: objectIndex ifAbsent:[nil]]." prototype ifNil:[Transcript cr; nextPutAll:'No shape for '; print: objectIndex; nextPutAll:' in frame '; print: frame; endEntry]. ^prototype! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 8/15/1998 15:27'! placeGlyph: aMorph at: position aMorph privateFullMoveBy: position.! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 11/16/1998 21:55'! removeActiveMorph: aMorph | newActive newPassive | aMorph visible: false atFrame: frame. newActive _ (activeMorphs at: aMorph id) copyWithout: aMorph. newPassive _ (passiveMorphs at: aMorph id ifAbsent:[#()]) copyWith: aMorph. activeMorphs at: aMorph id put: newActive. passiveMorphs at: aMorph id put: newPassive. ^aMorph! ! !FlashMorphReader methodsFor: 'private' stamp: 'ar 8/15/1998 15:28'! resizeGlyph: aMorph to: extent aMorph extent: 1440@1440. aMorph extent: extent.! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/20/1998 01:59'! recordButton: buttonId actions: actionList condition: condition | button | button _ buttons at: buttonId ifAbsent:[^self halt]. (condition anyMask: 1) ifTrue:[ button on: #mouseEnter sendAll: actionList. ]. (condition anyMask: 2) ifTrue:[ button on: #mouseLeave sendAll: actionList. ]. (condition anyMask: 4) ifTrue:[ button on: #mouseDown sendAll: actionList. ]. (condition anyMask: 8) ifTrue:[ button on: #mouseUp sendAll: actionList. ]. (condition anyMask: 16) ifTrue:[ button on: #mouseLeaveDown sendAll: actionList. ]. (condition anyMask: 32) ifTrue:[ button on: #mouseEnterDown sendAll: actionList. ]. (condition anyMask: 64) ifTrue:[ button on: #mouseUpOut sendAll: actionList. ]. (condition anyMask: 128) ifTrue:[ button on: #mouseEnterDown sendAll: actionList. ]. (condition anyMask: 256) ifTrue:[ button on: #mouseLeaveDown sendAll: actionList. ]. ! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/24/1998 14:24'! recordButton: buttonId character: characterId state: state layer: layer matrix: matrix colorTransform: cxForm | button children shape | button _ buttons at: buttonId ifAbsent:[^self halt]. button id: buttonId. shape _ self oldMorphFromShape: characterId. shape isNil ifTrue:[^nil]. children _ shape submorphs collect:[:m| m fullCopy]. shape _ FlashMorph withAll: children. shape lockChildren. shape depth: layer. shape transform: matrix. shape colorTransform: cxForm. (state anyMask: 1) ifTrue:[ button defaultLook: shape. ]. (state anyMask: 2) ifTrue:[ button overLook: shape. ]. (state anyMask: 4) ifTrue:[ button pressLook: shape. ]. (state anyMask: 8) ifTrue:[ button sensitiveLook: shape. ]. button lockChildren.! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/21/1998 02:20'! recordButton: id sound: soundId info: soundInfo state: state "Give the button a sound" | button theSound | button _ buttons at: id ifAbsent:[^self halt]. theSound _ self createSound: soundId info: soundInfo. theSound ifNil:[^self]. button addSound: theSound forState: state.! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/16/1998 20:51'! recordButton: id trackAsMenu: aBoolean | button | button _ buttons at: id ifAbsent:[^self halt]. button trackAsMenu: aBoolean. ! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/16/1998 20:36'! recordDefineButton: id "Record the definition of a new button with the given id" | button | button _ buttons at: id put: FlashButtonMorph new. button id: id. shapes at: id put: button.! ! !FlashMorphReader methodsFor: 'defining buttons' stamp: 'ar 11/16/1998 21:11'! recordEndButton: id "Record the end of a button definition with the given id" ! ! !FlashMorphReader methodsFor: 'testing' stamp: 'ar 11/18/1998 21:37'! isStreaming ^player isStreaming! ! !FlashMorphReader methodsFor: 'defining sounds' stamp: 'jm 3/30/1999 09:43'! flushStreamingSound | bufs sound | streamingSound buffers ifNil: [^ self]. streamingSound buffers first position = 0 ifFalse: [ bufs _ streamingSound buffers collect: [:b | b contents]. sound _ self createSoundFrom: bufs stereo: streamingSound stereo samplingRate: streamingSound samplingRate. player addSound: sound at: streamingSound firstFrame]. streamingSound firstFrame: frame. streamingSound frameNumber: frame. streamingSound buffers do: [:s | s reset]. ! ! !FlashMorphReader methodsFor: 'defining sounds' stamp: 'ar 11/20/1998 22:38'! recordSound: id data: aSampledSound aSampledSound ifNotNil:[sounds at: id put: aSampledSound]! ! !FlashMorphReader methodsFor: 'defining sounds' stamp: 'jm 3/30/1999 09:14'! recordSoundStreamBlock: data | newBufs | streamingSound frameNumber + 1 = frame ifFalse: [self flushStreamingSound]. newBufs _ ADPCMCodec new decodeFlash: data sampleCount: streamingSound sampleCount stereo: streamingSound stereo. streamingSound buffers with: newBufs do: [:streamBuf :newBuf | streamBuf nextPutAll: newBuf]. streamingSound frameNumber: frame. ! ! !FlashMorphReader methodsFor: 'defining sounds' stamp: 'ar 11/21/1998 00:53'! recordSoundStreamHead: mixFmt stereo: stereo bitsPerSample: bitsPerSample sampleCount: sampleCount compressed: compressed streamingSound buffers isNil ifFalse:[self flushStreamingSound]. streamingSound mixFmt: mixFmt. streamingSound stereo: stereo. streamingSound bitsPerSample: bitsPerSample. streamingSound sampleCount: sampleCount. streamingSound compressed: compressed. streamingSound samplingRate: (frameRate * sampleCount) truncated. streamingSound buffers: (self createSoundBuffersOfSize: sampleCount stereo: stereo). streamingSound firstFrame: frame. streamingSound frameNumber: frame. ! ! !FlashMorphReader methodsFor: 'defining sounds' stamp: 'ar 11/20/1998 22:38'! recordStartSound: id info: info | theSound | theSound _ self createSound: id info: info. theSound ifNotNil:[player addSound: theSound at: frame].! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:48'! recordMorphBoundary: id self recordShapeEnd: id. morphedLineStyles keysAndValuesDo:[:idx :val| lineStyles at: idx put: val]. morphedFillStyles keysAndValuesDo:[:idx :val| fillStyles at: idx put: val]. location _ 0@0. self beginShape.! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:43'! recordMorphFill: id color1: color1 color2: color2 self recordSolidFill: id color: color2. morphedFillStyles at: id put: (fillStyles at: id). self recordSolidFill: id color: color1.! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:43'! recordMorphFill: id matrix1: matrix1 matrix2: matrix2 id: bmId clipped: aBool self recordBitmapFill: id matrix: matrix2 id: bmId clipped: aBool. morphedFillStyles at: id put: (fillStyles at: id). self recordBitmapFill: id matrix: matrix1 id: bmId clipped: aBool.! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:44'! recordMorphFill: id matrix1: matrix1 ramp1: ramp1 matrix2: matrix2 ramp2: ramp2 linear: isLinear self recordGradientFill: id matrix: matrix2 ramp: ramp2 linear: isLinear. morphedFillStyles at: id put: (fillStyles at: id). self recordGradientFill: id matrix: matrix1 ramp: ramp1 linear: isLinear. ! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:45'! recordMorphLineStyle: id width1: lineWidth1 width2: lineWidth2 color1: lineColor1 color2: lineColor2 self recordLineStyle: id width: lineWidth2 color: lineColor2. morphedLineStyles at: id put: (lineStyles at: id). self recordLineStyle: id width: lineWidth1 color: lineColor1.! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:46'! recordMorphShapeEnd: id | startShape endShape morphShape | startShape _ shapes at: id. self recordShapeEnd: id. endShape _ shapes at: id. morphShape _ FlashMorphingMorph from: startShape to: endShape. morphShape id: id. morphShape stepTime: stepTime. shapes at: id put: morphShape. morphedLineStyles _ morphedFillStyles _ nil.! ! !FlashMorphReader methodsFor: 'composing morphs' stamp: 'ar 9/3/1999 18:42'! recordMorphShapeStart: shapeId srcBounds: bounds1 dstBounds: bounds2 morphedFillStyles _ Dictionary new. morphedLineStyles _ Dictionary new. location _ 0@0. self logShapes ifFalse:[log _ nil]. self beginShape.! ! FlashCharacterMorph subclass: #FlashMorphingMorph instanceVariableNames: 'srcShapes dstShapes morphShapes ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashMorphingMorph methodsFor: 'initialize' stamp: 'ar 9/3/1999 16:58'! extractShapesFrom: aMorph | shapes | shapes _ WriteStream on: Array new. aMorph allMorphsDo:[:m| (m isFlashMorph and:[m isFlashShape]) ifTrue:[shapes nextPut: m shape]. ]. ^shapes contents. ! ! !FlashMorphingMorph methodsFor: 'initialize' stamp: 'ar 9/3/1999 18:35'! from: srcMorph to: dstMorph | shape | "Note: Add srcMorph and dstMorph to the receiver so the damned bounds will be correct." self addMorphBack: srcMorph. self addMorphBack: dstMorph. self computeBounds. srcShapes _ self extractShapesFrom: srcMorph. dstShapes _ self extractShapesFrom: dstMorph. srcShapes size = dstShapes size ifFalse:[^self error:'Shape size mismatch']. 1 to: srcShapes size do:[:i| (srcShapes at: i) numSegments = (dstShapes at: i) numSegments ifFalse:[^self error:'Edge size mismatch']]. morphShapes _ WriteStream on: Array new. srcShapes do:[:s| shape _ FlashBoundaryShape points: s points copy leftFills: s leftFills rightFills: s rightFills fillStyles: s fillStyles lineWidths: s lineWidths lineFills: s lineFills. morphShapes nextPut: shape. self addMorphFront: (FlashShapeMorph shape: shape)]. morphShapes _ morphShapes contents. srcMorph visible: false. dstMorph visible: false.! ! !FlashMorphingMorph methodsFor: 'stepping' stamp: 'ar 9/3/1999 18:50'! morphTo: ratio | srcShape dstShape morphShape | 1 to: morphShapes size do:[:i| srcShape _ srcShapes at: i. dstShape _ dstShapes at: i. morphShape _ morphShapes at: i. morphShape morphFrom: srcShape to: dstShape at: ratio]. ! ! !FlashMorphingMorph methodsFor: 'stepping' stamp: 'ar 9/3/1999 18:38'! stepToFrame: frameNumber | ratio | super stepToFrame: frameNumber. self visible ifTrue:[ ratio _ self ratioAtFrame: frame. self morphTo: ratio. self changed].! ! !FlashMorphingMorph methodsFor: 'copying' stamp: 'ar 9/3/1999 18:39'! updateReferencesUsing: aDictionary | srcMorph dstMorph | super updateReferencesUsing: aDictionary. srcMorph _ (submorphs at: submorphs size-1). dstMorph _ (submorphs at: submorphs size). self removeAllMorphs. self from: srcMorph to: dstMorph. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashMorphingMorph class instanceVariableNames: ''! !FlashMorphingMorph class methodsFor: 'instance creation' stamp: 'ar 9/3/1999 16:53'! from: srcMorph to: dstMorph ^self new from: srcMorph to: dstMorph! ! Model subclass: #FlashPlayerModel instanceVariableNames: 'player ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 14:47'! initialExtent ^player bounds extent! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:02'! isStreaming ^player isStreaming! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:33'! loadedFrames ^player loadedFrames! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:02'! maxFrames ^player maxFrames! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 14:45'! player: flashPlayer player _ flashPlayer! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:42'! progressValue ^player progressValue! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:45'! startPlaying player startPlaying! ! !FlashPlayerModel methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:43'! stopPlaying player stopPlaying! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashPlayerModel class instanceVariableNames: ''! !FlashPlayerModel class methodsFor: 'instance creation' stamp: 'ar 11/17/1998 14:45'! player: flashPlayer ^self new player: flashPlayer! ! FlashSpriteMorph subclass: #FlashPlayerMorph instanceVariableNames: 'activationKeys activeMorphs localBounds sourceUrl progressValue ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/19/1998 22:53'! downloadState | doc | doc _ sourceUrl retrieveContents. (FlashMorphReader on: doc contentStream binary) processFileAsync: self. self startPlaying.! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/19/1998 23:09'! downloadStateIn: aScamper | doc | doc _ sourceUrl retrieveContents. (FlashMorphReader on: doc contentStream binary) processFileAsync: self. "Wait until the first frame is there" [loadedFrames = 0] whileTrue:[(Delay forMilliseconds: 100) wait]. aScamper invalidateLayout. self startPlaying.! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 9/1/1999 14:39'! initialize super initialize. color _ Color white. self loopFrames: true. localBounds _ bounds. activationKeys _ #(). activeMorphs _ SortedCollection new: 50. activeMorphs sortBlock:[:m1 :m2| m1 depth > m2 depth]. progressValue _ ValueHolder new. progressValue contents: 0.0. self defaultAALevel: 2. self deferred: true.! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/19/1998 20:31'! loadInitialFrame "Note: Must only be sent to a player if not in streaming mode" self isStreaming ifTrue:[^self]. super loadInitialFrame. activationKeys _ self collectActivationKeys: maxFrames. activeMorphs _ SortedCollection new: 50. activeMorphs sortBlock:[:m1 :m2| m1 depth > m2 depth]. activeMorphs addAll: activationKeys first.! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 12/30/1998 14:47'! makeControls | b r loopSwitch | b _ SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r _ AlignmentMorph newRow. r hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. r addMorphBack: (b fullCopy label: 'Rewind'; actionSelector: #rewind). r addMorphBack: (b fullCopy label: 'Play'; actionSelector: #startPlaying). r addMorphBack: (b fullCopy label: 'Pause'; actionSelector: #stopPlaying). r addMorphBack: (b fullCopy label: 'Next'; actionSelector: #stepForward). r addMorphBack: (b fullCopy label: 'Prev'; actionSelector: #stepBackward). loopSwitch _ SimpleSwitchMorph new borderWidth: 2; label: 'Loop'; actionSelector: #loopFrames:; target: self; setSwitchState: self loopFrames. r addMorphBack: loopSwitch. loopSwitch _ SimpleSwitchMorph new borderWidth: 2; label: 'Defer'; actionSelector: #toggleDeferred; target: self; setSwitchState: self deferred. r addMorphBack: loopSwitch. r addMorphBack: (b fullCopy label: 'Fastest'; actionSelector: #drawFastest). r addMorphBack: (b fullCopy label: 'Medium'; actionSelector: #drawMedium). r addMorphBack: (b fullCopy label: 'Nicest'; actionSelector: #drawNicest). r addMorphBack: (b fullCopy label: '+10'; actionSelector: #jump10). b target: self. ^ self world activeHand attachMorph: r! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/18/1998 21:40'! open Smalltalk isMorphic ifTrue:[self openInWorld] ifFalse:[self openInMVC]! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/17/1998 15:39'! openInMVC | window extent | self localBounds: localBounds. extent _ bounds extent. window _ FlashPlayerWindow labelled:'Flash Player'. window model: (FlashPlayerModel player: self). window addMorph: self frame:(0@0 corner: 1@1). window openInMVCExtent: extent! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/17/1998 15:24'! openInWorld | window extent | self localBounds: localBounds. extent _ bounds extent. window _ FlashPlayerWindow labelled:'Flash Player'. window model: (FlashPlayerModel player: self). window addMorph: self frame:(0@0 corner: 1@1). window openInWorldExtent: extent! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'ar 11/18/1998 19:21'! sourceUrl: urlString sourceUrl _ urlString! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 2/10/1999 03:37'! activeMorphs ^activeMorphs! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/20/1998 02:27'! addMorph: aMorph aMorph isFlashMorph ifFalse:[^super addMorph: aMorph]. aMorph isMouseSensitive ifTrue:[self addMorphFront: aMorph] ifFalse:[self addMorphBack: aMorph].! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 20:52'! borderWidth "Ignored here - only for keeping the window happy" ^0! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 20:52'! borderWidth: bw "Ignored here - only for keeping the window happy" ! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 12/30/1998 11:13'! deferred ^self hasProperty:#deferred! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 12/30/1998 11:14'! deferred: aBoolean aBoolean ifTrue:[self setProperty: #deferred toValue: true] ifFalse:[self removeProperty: #deferred]! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 21:46'! isStreaming "Return true if we're in streaming mode" ^sourceUrl notNil! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'RAA 6/2/2000 10:53'! loadedFrames: aNumber self isStreaming ifTrue: [activationKeys _ self collectActivationKeys: aNumber. aNumber = 1 ifTrue: [activeMorphs addAll: activationKeys first. self changed]. progressValue contents: aNumber asFloat / maxFrames. "Give others a chance" Smalltalk isMorphic ifTrue: [Display doOneCycleMorphic] ifFalse: [Processor yield]]. loadedFrames _ aNumber! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 00:56'! localBounds ^localBounds ifNil:[localBounds _ self transform globalBoundsToLocal: self bounds]! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 20:43'! localBounds: newBounds localBounds _ newBounds. bounds _ (self position extent: newBounds extent // 20). transform _ MatrixTransform2x3 transformFromLocal: newBounds toGlobal: bounds! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:17'! loopFrames ^(self valueOfProperty: #loopFrames) ifNil:[false]! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 10/15/1998 02:51'! loopFrames: aBoolean self setProperty: #loopFrames toValue: aBoolean! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 22:33'! progressValue ^progressValue! ! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 22:33'! progressValue: aValueHolder progressValue _ aValueHolder! ! !FlashPlayerMorph methodsFor: 'drawing' stamp: 'ar 5/25/2000 17:58'! debugDraw self fullDrawOn: (Display getCanvas)! ! !FlashPlayerMorph methodsFor: 'drawing' stamp: 'ar 2/12/2000 18:34'! drawOn: aCanvas "Draw the background of the player" | box bgImage | box _ self bounds. bgImage _ self valueOfProperty: #transitionBackground ifAbsent:[nil]. bgImage ifNil:[aCanvas fillRectangle: box color: color] ifNotNil:[aCanvas drawImage: bgImage at: box origin].! ! !FlashPlayerMorph methodsFor: 'drawing' stamp: 'ar 5/29/1999 09:49'! drawSubmorphsOn: aCanvas | myCanvas | aCanvas clipBy: self bounds during:[:tempCanvas| myCanvas _ tempCanvas asBalloonCanvas. myCanvas aaLevel: (self defaultAALevel ifNil:[1]). myCanvas deferred: self deferred. myCanvas transformBy: self transform during:[:childCanvas| activeMorphs reverseDo:[:m| childCanvas fullDrawMorph: m]]. myCanvas flush]. ! ! !FlashPlayerMorph methodsFor: 'drawing' stamp: 'ar 5/19/1999 17:57'! imageFormOfSize: extentPoint forFrame: frameNr "Create an image of the given size for the given frame number" | thumbTransform form canvas morphsToDraw | thumbTransform _ MatrixTransform2x3 transformFromLocal: localBounds toGlobal: (0@0 extent: extentPoint). form _ Form extent: extentPoint depth: 8. form fillColor: self color. canvas _ BalloonCanvas on: form. canvas transformBy: thumbTransform. canvas aaLevel: (self defaultAALevel ifNil:[1]). canvas deferred: true. morphsToDraw _ (submorphs select:[:m| m stepToFrame: frameNr. m visible]) sortBy:[:m1 :m2| m1 depth > m2 depth]. morphsToDraw reverseDo:[:m| m fullDrawOn: canvas]. submorphs do:[:m| m stepToFrame: frameNumber]. canvas flush. ^form! ! !FlashPlayerMorph methodsFor: 'drawing' stamp: 'ar 11/12/2000 18:43'! invalidRect: rect from: aMorph damageRecorder isNil ifTrue:[ super invalidRect: rect from: aMorph ] ifFalse:[ damageRecorder recordInvalidRect: rect. ].! ! !FlashPlayerMorph methodsFor: 'geometry' stamp: 'ar 2/3/2000 17:23'! boundsChangedFrom: oldBounds to: newBounds | newWidth newLeft | newWidth _ localBounds width * newBounds height // localBounds height. newLeft _ newBounds left + (newBounds width - newWidth // 2). transform _ MatrixTransform2x3 transformFromLocal: localBounds toGlobal: (newLeft @ newBounds top extent: newWidth @ newBounds height).! ! !FlashPlayerMorph methodsFor: 'geometry' stamp: 'ar 11/16/1998 01:06'! computeBounds "Do nothing."! ! !FlashPlayerMorph methodsFor: 'geometry' stamp: 'ar 6/2/1999 02:41'! containsPoint: aPoint ^self bounds containsPoint: aPoint! ! !FlashPlayerMorph methodsFor: 'geometry' stamp: 'ar 11/15/1998 16:07'! fullBounds "The player clips its children" ^bounds! ! !FlashPlayerMorph methodsFor: 'geometry' stamp: 'ar 11/19/1998 20:48'! fullContainsPoint: pt "The player clips its children" (bounds containsPoint: pt) ifFalse:[^false]. ^super fullContainsPoint: pt! ! !FlashPlayerMorph methodsFor: 'classification' stamp: 'ar 11/15/1998 19:05'! isFlashPlayer ^true! ! !FlashPlayerMorph methodsFor: 'stepping' stamp: 'ar 11/6/1998 23:56'! jump10 1 to: 10 do:[:i| self stepForward].! ! !FlashPlayerMorph methodsFor: 'stepping' stamp: 'ar 8/14/1998 21:54'! rewind self frameNumber: 1.! ! !FlashPlayerMorph methodsFor: 'stepping' stamp: 'ar 2/3/2000 17:20'! stepToFrame: frame | fullRect postDamage | frame = frameNumber ifTrue:[^self]. frame > loadedFrames ifTrue:[^self]. postDamage _ damageRecorder isNil. postDamage ifTrue:[damageRecorder _ FlashDamageRecorder new]. frame = (frameNumber+1) ifTrue:[ self stepToFrameForward: frame. ] ifFalse:[ activeMorphs _ activeMorphs select:[:any| false]. submorphs do:[:m| (m isFlashMorph and:[m isFlashCharacter]) ifTrue:[ m stepToFrame: frame. m visible ifTrue:[activeMorphs add: m]. ]]. ]. frameNumber _ frame. playing ifTrue:[ self playSoundsAt: frame. self executeActionsAt: frame. ]. (postDamage and:[owner notNil]) ifTrue:[ damageRecorder updateIsNeeded ifTrue:[ fullRect _ damageRecorder fullDamageRect: self localBounds. fullRect _ (self transform localBoundsToGlobal: fullRect). owner invalidRect: (fullRect insetBy: -1). ]. ]. postDamage ifTrue:[damageRecorder _ nil].! ! !FlashPlayerMorph methodsFor: 'stepping' stamp: 'ar 11/17/1998 18:33'! stepToFrameForward: frame | activeRemoved resortNeeded morph | frameNumber+1 to: frame do:[:f| activeRemoved _ false. resortNeeded _ false. 1 to: activeMorphs size do:[:i| morph _ activeMorphs at: i. morph stepToFrame: f. morph visible ifFalse:[activeRemoved _ true]. (i > 1 and:[(activeMorphs at: i-1) depth < morph depth]) ifTrue:[resortNeeded _ true]. ]. activeRemoved ifTrue:[ activeMorphs _ activeMorphs select:[:m| m visible]. resortNeeded _ false. ]. resortNeeded ifTrue:[activeMorphs reSort]. (activationKeys at: f) do:[:m| m stepToFrame: f. m visible ifTrue:[activeMorphs add: m]. ]. ].! ! !FlashPlayerMorph methodsFor: 'stepping' stamp: 'ar 5/19/1999 17:23'! stepToFrameSilently: frame "Like stepToFrame but without executing any actions or starting sounds. Note: This method is not intended for fast replay." | fullRect postDamage | frame = frameNumber ifTrue:[^self]. frame > loadedFrames ifTrue:[^self]. postDamage _ damageRecorder isNil. postDamage ifTrue:[damageRecorder _ FlashDamageRecorder new]. activeMorphs _ activeMorphs select:[:any| false]. submorphs do:[:m| (m isFlashMorph and:[m isFlashCharacter]) ifTrue:[ m stepToFrame: frame. m visible ifTrue:[activeMorphs add: m]. ]. ]. frameNumber _ frame. (postDamage and:[owner notNil]) ifTrue:[ damageRecorder updateIsNeeded ifTrue:[ fullRect _ damageRecorder fullDamageRect: self localBounds. fullRect _ (self transform localBoundsToGlobal: fullRect). owner invalidRect: (fullRect insetBy: -1). ]. ]. postDamage ifTrue:[damageRecorder _ nil].! ! !FlashPlayerMorph methodsFor: 'private' stamp: 'ar 11/18/1998 23:21'! collectActivationKeys: frame "Note: Must only be called after a frame has been completed" | vis lastKey | vis _ Array new: frame. vis atAllPut: #(). lastKey _ activationKeys size. vis replaceFrom: 1 to: lastKey with: activationKeys startingAt: 1. submorphs do:[:m| (m isFlashMorph and:[m isFlashCharacter]) ifTrue:[ m activationKeys do:[:key| key > lastKey ifTrue:[ vis at: key put: ((vis at: key) copyWith: m) ]. ]. ]. ]. ^vis! ! !FlashPlayerMorph methodsFor: 'private' stamp: 'ar 11/16/1998 02:58'! noticeRemovalOf: aFlashMorph "The flash morph is removed from the player. Remove it's activation keys so that we don't have any problems." | morphs | aFlashMorph activationKeys do:[:key| morphs _ activationKeys at: key. activationKeys at: key put: (morphs copyWithout: aFlashMorph). ]. "And remove it from the activeMorphs" activeMorphs remove: aFlashMorph ifAbsent:[]! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 6/16/1999 07:25'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'open sorter' action: #openSorter. aCustomMenu add: 'make controls' action: #makeControls. aCustomMenu addLine.! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 12/30/1998 14:44'! drawFastest self defaultAALevel: 1. self changed.! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 12/30/1998 14:44'! drawMedium self defaultAALevel: 2. self changed.! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 12/30/1998 14:44'! drawNicest self defaultAALevel: 4. self changed.! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 5/19/1999 16:28'! openSorter (FlashSorterMorph new on: self) openInWindow! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'ar 12/30/1998 11:33'! toggleDeferred self deferred: self deferred not. self changed.! ! !FlashPlayerMorph methodsFor: 'copying' stamp: 'ar 5/20/1999 12:16'! copyMovieFrom: firstFrame to: lastFrame "Create a copy of the receiver containing the given frames" | player delta actionList newMorphs | delta _ firstFrame - 1. player _ FlashPlayerMorph new. player bounds: self bounds. player localBounds: self localBounds. player maxFrames: lastFrame - firstFrame + 1. player loadedFrames: player maxFrames. player stepTime: stepTime. player color: self color. "Copy the sounds, actions and labels" sounds associationsDo:[:sndAssoc| (sndAssoc key between: firstFrame and: lastFrame) ifTrue:[ sndAssoc value do:[:snd| player addSound: snd at: sndAssoc key - delta]]]. actions associationsDo:[:action| actionList _ action value collect:[:a| a selector == #gotoFrame: ifTrue:[Message selector: a selector argument: (a argument - delta)] ifFalse:[a]]. (action key between: firstFrame and: lastFrame) ifTrue:[player addActions: actionList atFrame: action key - delta]]. labels associationsDo:[:label| (label value between: firstFrame and: lastFrame) ifTrue:[player addLabel: label key atFrame: label value - delta]]. "Finally, copy the morphs" newMorphs _ submorphs select:[:m| m isVisibleBetween: firstFrame and: lastFrame] thenCollect:[:m| m copyMovieFrom: firstFrame to: lastFrame]. player addAllMorphs: newMorphs. player loadInitialFrame. player stepToFrame: 1. ^player! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:43'! addFillForProjectTarget: aFillStyle | fillStyles | fillStyles _ self valueOfProperty: #projectTargetFills ifAbsent:[IdentityDictionary new]. (fillStyles includesKey: aFillStyle) ifTrue:[^self]. fillStyles at: aFillStyle put: aFillStyle form. self setProperty: #projectTargetFills toValue: fillStyles. CurrentProjectRefactoring updateProjectFillsIn: self. self changed.! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'ar 6/2/1999 05:59'! beFullScreenTransition: aBoolean "Make the receiver a full-screen transition if requested" self setProperty: #fullScreenTransition toValue: true.! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:07'! beTransitionFrom: srcProjectName ^self beTransitionFrom: srcProjectName to: CurrentProjectRefactoring currentProjectName! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:39'! beTransitionFrom: srcProjectName to: dstProjectName "Make the receiver the animation between the two projects" | srcProject dstProject | srcProject _ CurrentProjectRefactoring projectWithNameOrCurrent: srcProjectName. dstProject _ CurrentProjectRefactoring projectWithNameOrCurrent: dstProjectName. (dstProject projectParameters at: #flashTransition ifAbsentPut:[IdentityDictionary new]) at: srcProject put: self. ! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:07'! beTransitionTo: dstProjectName ^self beTransitionFrom: CurrentProjectRefactoring currentProjectName to: dstProjectName! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/6/2000 17:00'! playProjectTransitionFrom: oldProject to: newProject entering: aBoolean "Play the transition from the old to the new project." Smalltalk isMorphic ifFalse: [^ self]. "Not in MVC" self stopPlaying. owner ifNotNil:[ self stopStepping. owner privateRemoveMorph: self. owner _ nil]. aBoolean ifTrue:[ self updateProjectFillsFrom: newProject. ] ifFalse:[ self updateProjectFillsFrom: oldProject. self setProperty: #transitionBackground toValue: newProject imageForm. ]. self frameNumber: 1. self loopFrames: false. (self valueOfProperty: #fullScreenTransition ifAbsent:[false]) ifTrue:[self bounds: self world bounds]. self comeToFront. self startStepping. self startPlaying. [playing] whileTrue: [Display doOneCycleNowMorphic]. self stopPlaying. self stopStepping. owner privateRemoveMorph: self. owner _ nil. self removeProperty: #transitionBackground. Display deferUpdates: true. Display bestGuessOfCurrentWorld fullDrawOn: (Display getCanvas). Display deferUpdates: false.! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:43'! removeFillForProjectTarget: aFillStyle | fillStyles | fillStyles _ self valueOfProperty: #projectTargetFills ifAbsent:[^self]. aFillStyle form: (fillStyles at: aFillStyle ifAbsent:[^self]). fillStyles removeKey: aFillStyle. CurrentProjectRefactoring updateProjectFillsIn: self. self changed.! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'ar 6/2/1999 05:00'! updateProjectFillsFrom: aProject "Update all the project target fills from the given project" | fillStyles projImage | fillStyles _ self valueOfProperty: #projectTargetFills ifAbsent:[^self]. fillStyles isEmpty ifTrue:[^self]. projImage _ aProject imageFormOfSize: Display extent depth: 8. fillStyles keysDo:[:fs| fs form: projImage]. "Note: We must issue a full GC here for cleaning up the old bitmaps" Smalltalk garbageCollect.! ! !FlashPlayerMorph methodsFor: 'disk i/o'! compress super compress. activeMorphs _ activeMorphs asOrderedCollection! ! !FlashPlayerMorph methodsFor: 'disk i/o'! decompress super decompress. activeMorphs _ activeMorphs asSortedCollection: [:a :b | a depth > b depth]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashPlayerMorph class instanceVariableNames: ''! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:07'! transitionFrom: srcProjectName ^self transitionFrom: srcProjectName to: CurrentProjectRefactoring currentProjectName! ! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:39'! transitionFrom: srcProjectName to: dstProjectName "Return the transition between the two projects" | srcProject dstProject | srcProject _ CurrentProjectRefactoring projectWithNameOrCurrent: srcProjectName. dstProject _ CurrentProjectRefactoring projectWithNameOrCurrent: dstProjectName. ^dstProject projectParameters at: #flashTransition ifPresent:[:dict| dict at: srcProject ifAbsent:[nil]]. ! ! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'RAA 6/3/2000 09:07'! transitionTo: dstProjectName ^self transitionFrom: CurrentProjectRefactoring currentProjectName to: dstProjectName! ! SystemWindow subclass: #FlashPlayerWindow instanceVariableNames: 'startButton stopButton progress ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 01:31'! addMorph: aMorph frame: relFrame "Do not change the color" | cc | cc _ aMorph color. super addMorph: aMorph frame: relFrame. aMorph color: cc.! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:34'! addProgressIndicator progress _ FlashProgressMorph new. progress borderWidth: 1. progress color: Color transparent. progress progressColor: Color gray. progress extent: (100 @ (startButton extent y - 6)). self addMorph: progress. ! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:42'! addProgressIndicator: aValueHolder progress _ FlashProgressMorph new. progress borderWidth: 1. progress color: Color transparent. progress progressColor: Color gray. progress value: aValueHolder. progress extent: (100 @ (startButton extent y - 6)). self addMorph: progress. ! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'sw 9/29/1999 10:14'! adjustBookControls | inner | startButton ifNil: [^ self]. startButton align: startButton topLeft with: (inner _ self innerBounds) topLeft + (35@-4). progress ifNotNil: [progress align: progress topLeft with: (startButton right @ inner top) + (10@0)]. stopButton align: stopButton topRight with: inner topRight - (16@4)! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/18/1998 22:40'! collapseOrExpand super collapseOrExpand. isCollapsed ifTrue:[ startButton delete. stopButton delete. progress ifNotNil:[progress delete]. ] ifFalse:[ self addMorph: startButton. self addMorph: stopButton. progress ifNotNil:[self addMorph: progress]. self adjustBookControls. ].! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 15:09'! extent: newExtent super extent: (newExtent max: 100 @ 50). self adjustBookControls! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'sw 12/6/1999 18:05'! initialize | aFont | super initialize. aFont _ Preferences standardButtonFont. self addMorph: (startButton _ SimpleButtonMorph new borderWidth: 0; label: 'play' font: aFont; color: Color transparent; actionSelector: #startPlaying; target: self). startButton setBalloonText: 'continue playing'. self addMorph: (stopButton _ SimpleButtonMorph new borderWidth: 0; label: 'stop' font: aFont; color: Color transparent; actionSelector: #stopPlaying; target: self). stopButton setBalloonText: 'stop playing'. startButton submorphs first color: Color blue. stopButton submorphs first color: Color red. self adjustBookControls! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 21:10'! model: aFlashPlayerModel aFlashPlayerModel isStreaming ifTrue:[self addProgressIndicator: aFlashPlayerModel progressValue]. ^super model: aFlashPlayerModel! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 15:39'! openInMVCExtent: extent Smalltalk isMorphic ifTrue:[^self openInWorldExtent: extent]. super openInMVCExtent: (extent + borderWidth + (0@self labelHeight))! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/17/1998 15:39'! openInWorldExtent: extent Smalltalk isMorphic ifFalse:[^self openInMVCExtent: extent]. super openInWorldExtent: (extent + borderWidth + (0@self labelHeight))! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:46'! startPlaying model startPlaying! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:43'! stopPlaying model stopPlaying! ! BorderedMorph subclass: #FlashProgressMorph instanceVariableNames: 'value progressColor lastValue ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashProgressMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 22:14'! progressColor ^progressColor! ! !FlashProgressMorph methodsFor: 'accessing' stamp: 'sma 3/3/2000 18:52'! progressColor: aColor progressColor = aColor ifFalse: [progressColor _ aColor. self changed]! ! !FlashProgressMorph methodsFor: 'accessing' stamp: 'ar 11/18/1998 22:09'! value ^value! ! !FlashProgressMorph methodsFor: 'accessing' stamp: 'sma 3/3/2000 18:53'! value: aModel value ifNotNil: [value removeDependent: self]. value _ aModel. value ifNotNil: [value addDependent: self]! ! !FlashProgressMorph methodsFor: 'drawing' stamp: 'sma 3/3/2000 18:54'! drawOn: aCanvas | width inner | super drawOn: aCanvas. inner _ self innerBounds. width _ (inner width * lastValue) truncated min: inner width. aCanvas fillRectangle: (inner origin extent: width @ inner height) color: progressColor.! ! !FlashProgressMorph methodsFor: 'initialize' stamp: 'sma 3/3/2000 18:55'! initialize super initialize. progressColor _ Color green. self value: (ValueHolder new contents: 0.0). lastValue _ 0.0! ! !FlashProgressMorph methodsFor: 'updating' stamp: 'sma 3/3/2000 18:51'! update: aSymbol aSymbol == #contents ifTrue: [lastValue _ value contents. self changed]! ! !FlashProgressMorph methodsFor: 'menu' stamp: 'sma 3/3/2000 19:20'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addList: #( ('progress color...' changeProgressColor:) ('progress value...' changeProgressValue:))! ! !FlashProgressMorph methodsFor: 'menu' stamp: 'ar 10/5/2000 18:51'! changeProgressColor: evt | aHand | aHand _ evt ifNotNil: [evt hand] ifNil: [self primaryHand]. self changeColorTarget: self selector: #progressColor: originalColor: self progressColor hand: aHand.! ! !FlashProgressMorph methodsFor: 'menu' stamp: 'sma 3/3/2000 19:27'! changeProgressValue: evt | answer | answer _ FillInTheBlank request: 'Enter new value (0 - 1.0)' initialAnswer: self value contents asString. answer isEmptyOrNil ifTrue: [^ self]. self value contents: answer asNumber! ! FlashMorph subclass: #FlashShapeMorph instanceVariableNames: 'shape ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 17:19'! color: aColor | fillStyle | color _ aColor. fillStyle _ SolidFillStyle color: aColor. shape _ shape copyAndCollectFills:[:fill| fillStyle]! ! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 6/2/1999 04:38'! fillForProjectTarget "Find a fill style that is suitable for a project target." shape fillStyles do:[:fs| fs isBitmapFill ifTrue:[^fs]]. ^nil! ! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 11:39'! id ^-1! ! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 15:31'! shape ^shape! ! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 16:43'! shape: newShape shape _ newShape. self computeBounds.! ! !FlashShapeMorph methodsFor: 'drawing' stamp: 'ar 12/30/1998 10:47'! drawOn: aCanvas "Display the receiver." | aaLevel | shape ifNil:[^aCanvas frameRectangle: self bounds color: Color black.]. aCanvas asBalloonCanvas preserveStateDuring:[:balloonCanvas| balloonCanvas transformBy: self transform. aaLevel _ self defaultAALevel. aaLevel ifNotNil:[balloonCanvas aaLevel: aaLevel]. balloonCanvas drawCompressedShape: shape. ].! ! !FlashShapeMorph methodsFor: 'geometry' stamp: 'ar 11/18/1998 13:59'! computeBounds bounds _ self transform localBoundsToGlobal: (shape bounds). fullBounds _ nil.! ! !FlashShapeMorph methodsFor: 'classification' stamp: 'ar 6/2/1999 03:15'! isFlashShape ^true! ! !FlashShapeMorph methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:37'! compress super compress. shape compress.! ! !FlashShapeMorph methodsFor: 'disk i/o' stamp: 'ar 6/30/1999 12:38'! decompress shape decompress. super decompress.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashShapeMorph class instanceVariableNames: ''! !FlashShapeMorph class methodsFor: 'instance creation' stamp: 'ar 11/15/1998 15:32'! shape: aCompressedFlashGeometry ^self new shape: aCompressedFlashGeometry! ! TransformMorph subclass: #FlashSorterMorph instanceVariableNames: 'player ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashSorterMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:27'! addThumbnails: extentPoint | m morphList handler | handler _ nil. 'Preparing thumbnails' displayProgressAt: Sensor cursorPoint from: 1 to: player maxFrames during:[:bar| morphList _ Array new: player maxFrames. 1 to: player maxFrames do:[:i| bar value: i. m _ FlashThumbnailMorph new. m extent: extentPoint. m player: player. m frameNumber: i. handler isNil ifTrue:[ m on: #mouseDown send: #mouseDown:onItem: to: self. m on: #mouseStillDown send: #mouseStillDown:onItem: to: self. m on: #mouseUp send: #mouseUp:onItem: to: self. handler _ m eventHandler. ] ifFalse:[m eventHandler: handler]. morphList at: i put: m]. self addAllMorphs: morphList. self doLayout. ].! ! !FlashSorterMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:06'! initialize super initialize. self color: Color transparent.! ! !FlashSorterMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 20:40'! makeControls | b r | b _ SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r _ AlignmentMorph newRow. r hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 2. r addMorphBack: (b fullCopy label: 'Make movie'; actionSelector: #makeMovie). ^r! ! !FlashSorterMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:10'! on: aFlashPlayerMorph | w h | player _ aFlashPlayerMorph. w _ player bounds width. h _ player bounds height. w > h ifTrue:[ h _ h * 50 // w. w _ 50. ] ifFalse:[ w _ w * 50 // h. h _ 50. ]. self addThumbnails: w@h.! ! !FlashSorterMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:54'! openInWindow | window wrapper | window _ SystemWindow new. wrapper _ self makeControls. window addMorph: wrapper frame: (0@0 extent: 1@0.1). wrapper _ ScrollPane new. wrapper scroller: self. window addMorph: wrapper frame: (0 @ 0.1 extent: 1 @ 1). self bounds: owner bounds. self doLayout. window openInWorld.! ! !FlashSorterMorph methodsFor: 'layout' stamp: 'ar 5/19/1999 16:47'! doLayout "Do the layout of the child morphs" | x y maxHeight w | w _ self bounds width. x _ 0. y _ 0. maxHeight _ 0. submorphs do:[:m| x + m bounds width > w ifTrue:[ "Wrap the guy on the next line" x _ 0. y _ y + maxHeight. maxHeight _ 0]. m position: x@y. x _ x + m bounds width. maxHeight _ maxHeight max: m bounds height]. ! ! !FlashSorterMorph methodsFor: 'layout' stamp: 'ar 5/19/1999 16:25'! extent: extentPoint super extent: extentPoint. self doLayout.! ! !FlashSorterMorph methodsFor: 'interaction' stamp: 'ar 5/19/1999 17:35'! mouseDown: event onItem: aMorph submorphs do:[:m| m == aMorph ifFalse:[m isSelected: false]]. aMorph isSelected: true.! ! !FlashSorterMorph methodsFor: 'interaction' stamp: 'ar 10/5/2000 18:42'! mouseStillDown: evt onItem: aMorph | pt index m yOffset | submorphs do:[:mm| mm == aMorph ifFalse:[mm isSelected: false]]. pt _ evt cursorPoint. yOffset _ self offset y. index _ aMorph frameNumber. "What a fake hack@!!" pt y - yOffset < 0 ifTrue:[ owner scrollBy: 0@owner scrollDeltaHeight]. pt y - yOffset > self extent y ifTrue:[ owner scrollBy: 0@owner scrollDeltaHeight negated]. (aMorph bounds containsPoint: pt) ifTrue:[^self]. (pt y > aMorph bottom or:[pt x > aMorph right]) ifTrue:[ "Select all morphs forward." index+1 to: submorphs size do:[:i| m _ submorphs at: i. m isSelected: aMorph isSelected. (m bounds containsPoint: pt) ifTrue:[^self]. "Done" ]. ^self]. "Select morphs backwards" index-1 to: 1 by: -1 do:[:i| m _ submorphs at: i. m isSelected: aMorph isSelected. (m bounds containsPoint: pt) ifTrue:[^self]. ].! ! !FlashSorterMorph methodsFor: 'interaction' stamp: 'ar 5/19/1999 17:30'! mouseUp: evt onItem: aMorph | pt | pt _ evt cursorPoint. (aMorph bounds containsPoint: pt) ifTrue:[ player stepToFrameSilently: aMorph frameNumber. ^self].! ! !FlashSorterMorph methodsFor: 'editing' stamp: 'ar 5/19/1999 19:38'! makeMovie "Take all the currently selected frames and make a new movie out of it" | firstSelection lastSelection | firstSelection _ submorphs size + 1. lastSelection _ 0. submorphs doWithIndex:[:m :index| m isSelected ifTrue:[ firstSelection _ firstSelection min: index. lastSelection _ lastSelection max: index. ]. ]. firstSelection > lastSelection ifTrue:[^self inform:'You have to select the frames first']. (player copyMovieFrom: firstSelection to: lastSelection) open! ! Object subclass: #FlashSoundEnvelope instanceVariableNames: 'mark44 level0 level1 ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashSoundEnvelope methodsFor: 'initialize' stamp: 'ar 8/10/1998 15:35'! initialize mark44 _ level0 _ level1 _ 0.! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! level0 ^level0! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! level0: anInteger level0 _ anInteger! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! level1 ^level1! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! level1: anInteger level1 _ anInteger! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! mark44 ^mark44! ! !FlashSoundEnvelope methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:36'! mark44: anInteger mark44 _ anInteger! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashSoundEnvelope class instanceVariableNames: ''! !FlashSoundEnvelope class methodsFor: 'instance creation' stamp: 'ar 8/10/1998 15:35'! new ^super new initialize! ! Object subclass: #FlashSoundInformation instanceVariableNames: 'syncFlags inPoint outPoint loopCount envelopes ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashSoundInformation methodsFor: 'initialize' stamp: 'ar 8/10/1998 15:30'! initialize syncFlags _ 0. inPoint _ -1. outPoint _ -1. loopCount _ 0. envelopes _ #().! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:32'! envelopes ^envelopes! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:32'! envelopes: aCollection envelopes _ aCollection! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! inPoint ^inPoint! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! inPoint: anInteger inPoint _ anInteger! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! loopCount ^loopCount! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:32'! loopCount: anInteger loopCount _ anInteger! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! outPoint ^outPoint! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! outPoint: anInteger outPoint _ anInteger! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:30'! syncFlags ^syncFlags! ! !FlashSoundInformation methodsFor: 'accessing' stamp: 'ar 8/10/1998 15:31'! syncFlags: anInteger syncFlags _ anInteger! ! !FlashSoundInformation methodsFor: 'testing' stamp: 'ar 8/10/1998 15:33'! syncNoMultiple "Don't start the sound if already playing." ^syncFlags anyMask: 1! ! !FlashSoundInformation methodsFor: 'testing' stamp: 'ar 8/10/1998 15:34'! syncStopSound "Stop the sound." ^syncFlags anyMask: 2! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashSoundInformation class instanceVariableNames: ''! !FlashSoundInformation class methodsFor: 'instance creation' stamp: 'ar 8/10/1998 15:29'! new ^super new initialize! ! FlashMorph subclass: #FlashSpriteMorph instanceVariableNames: 'playing maxFrames loadedFrames frameNumber stepTime damageRecorder sounds actions labels lastStepTime useTimeSync ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashSpriteMorph methodsFor: 'initialize' stamp: 'ar 7/11/2000 11:10'! initialize super initialize. playing _ false. loadedFrames _ 0. maxFrames _ 1. frameNumber _ 1. sounds _ Dictionary new. actions _ Dictionary new. labels _ Dictionary new. stepTime _ 1. useTimeSync _ true.! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 16:48'! addActions: actionList atFrame: frame actions ifNil:[actions _ Dictionary new]. actions at: frame put: actionList.! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 16:48'! addLabel: labelString atFrame: frame labels ifNil:[labels _ Dictionary new]. labels at: labelString put: frame.! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/16/1998 16:49'! addSound: aSound at: frameNr | oldSound newSound | oldSound _ sounds at: frameNr ifAbsent:[nil]. oldSound isNil ifTrue:[newSound _ Array with: aSound] ifFalse:[newSound _ oldSound copyWith: newSound]. sounds at: frameNr put: newSound.! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:09'! frameNumber ^frameNumber! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:09'! frameNumber: frame ^self stepToFrame: frame! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:02'! loadedFrames ^loadedFrames! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:02'! loadedFrames: n loadedFrames _ n.! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 9/1/1999 15:27'! loopFrames ^true! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:16'! loopFrames: aBool! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:02'! maxFrames ^maxFrames! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 11/19/1998 22:02'! maxFrames: n maxFrames _ n! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 7/11/2000 11:08'! useTimeSync ^useTimeSync ifNil:[true]! ! !FlashSpriteMorph methodsFor: 'accessing' stamp: 'ar 7/11/2000 11:08'! useTimeSync: aBool useTimeSync _ aBool! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 2/3/2000 17:46'! startPlaying "Start playing from the current frame" playing ifTrue:[^self]. loadedFrames = 0 ifTrue:[^nil]. frameNumber >= maxFrames ifTrue:[self frameNumber: 1]. playing _ true. self playSoundsAt: frameNumber. self executeActionsAt: frameNumber. lastStepTime _ Time millisecondClockValue.! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 7/11/2000 11:08'! step | nowStepTime maxSteps | playing ifFalse:[^self]. self useTimeSync ifTrue:[ maxSteps _ 5. nowStepTime _ Time millisecondClockValue. [(lastStepTime + stepTime <= nowStepTime) and:[playing and:[maxSteps >= 0]]] whileTrue:[ self stepForward. lastStepTime _ lastStepTime + stepTime. maxSteps _ maxSteps - 1. ]. ] ifFalse:[self stepForward]. damageRecorder _ nil. "Insurance"! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 11/19/1998 22:09'! stepBackward frameNumber > 1 ifTrue:[self frameNumber: frameNumber - 1] ifFalse:[self frameNumber: loadedFrames].! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 9/1/1999 16:15'! stepForward frameNumber < maxFrames ifTrue:[^self frameNumber: frameNumber + 1]. self loopFrames ifTrue:[self frameNumber: 1] ifFalse:[self stopPlaying].! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 7/11/2000 11:08'! stepTime "If we're syncing with time step at double speed." ^self useTimeSync ifTrue:[stepTime // 2] ifFalse:[stepTime]! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 11/16/1998 17:07'! stepTime: time stepTime _ time.! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 2/3/2000 17:21'! stepToFrame: frame "Step to the given frame" | fullRect postDamage lastVisible resortNeeded | frame = frameNumber ifTrue:[^self]. frame > loadedFrames ifTrue:[^self]. postDamage _ damageRecorder isNil. postDamage ifTrue:[damageRecorder _ FlashDamageRecorder new]. lastVisible _ nil. resortNeeded _ false. submorphs do:[:m| (m isFlashMorph and:[m isFlashCharacter]) ifTrue:[ m stepToFrame: frame. m visible ifTrue:[ (lastVisible notNil and:[lastVisible depth < m depth]) ifTrue:[resortNeeded _ true]. lastVisible _ m. (bounds containsRect: m bounds) ifFalse:[bounds _ bounds merge: m bounds]. ]. ]. ]. resortNeeded ifTrue:[submorphs _ submorphs sortBy:[:m1 :m2| m1 depth > m2 depth]]. frameNumber _ frame. playing ifTrue:[ self playSoundsAt: frame. self executeActionsAt: frame. ]. (postDamage and:[owner notNil]) ifTrue:[ damageRecorder updateIsNeeded ifTrue:[ "fullRect _ damageRecorder fullDamageRect. fullRect _ (self transform localBoundsToGlobal: fullRect)." fullRect _ bounds. owner invalidRect: (fullRect insetBy: -1). ]. ]. postDamage ifTrue:[ damageRecorder _ nil].! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 11/19/1998 22:10'! stopPlaying "Stop playing at the current frame." playing _ false.! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 11/16/1998 16:27'! wantsSteps ^true! ! !FlashSpriteMorph methodsFor: 'classification' stamp: 'ar 11/16/1998 17:03'! isFlashSprite ^true! ! !FlashSpriteMorph methodsFor: 'classification' stamp: 'ar 11/19/1998 22:22'! isMouseSensitive "Return true - my children may be sensitive" ^true! ! !FlashSpriteMorph methodsFor: 'change reporting' stamp: 'ar 11/12/2000 18:43'! invalidRect: rect from: aMorph damageRecorder isNil ifTrue:[ super invalidRect: rect from: aMorph ] ifFalse:[ damageRecorder recordInvalidRect: rect. ].! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:10'! actionPlay self startPlaying. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:10'! actionStop "Stop playing at the current frame." self stopPlaying. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 9/1/1999 15:50'! actionTarget: target "Set the context (e.g., the receiver) of the following actions." | rcvr lastSlash nextSlash loc | target = '' ifTrue:[^self]. target first = $/ ifTrue:[rcvr _ self flashPlayer ifNil:[self]. lastSlash _ 1.] "absoute path" ifFalse:[rcvr _ self. lastSlash _ 0]. "relative path" [lastSlash > target size] whileFalse:[ nextSlash _ target findString:'/' startingAt: lastSlash+1. nextSlash = 0 ifTrue:[nextSlash _ target size + 1]. loc _ target copyFrom: lastSlash+1 to: nextSlash-1. (loc size = 2 and:[loc = '..']) ifTrue:[ [rcvr _ rcvr owner. rcvr isFlashSprite] whileFalse. ] ifFalse:[ rcvr _ rcvr submorphs detect:[:m| m knownName = loc] ifNone:[rcvr owner]. rcvr _ rcvr submorphs detect:[:m| m isFlashSprite]. ]. lastSlash _ nextSlash]. ^rcvr! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 9/1/1999 16:15'! executeActionsAt: frame | rcvr actionList index msg result | actionList _ actions at: frame ifAbsent:[^self]. index _ 1. rcvr _ self. [index <= actionList size] whileTrue:[ msg _ actionList at: index. "Transcript cr; print: msg selector; space; print: msg arguments; endEntry." msg selector == #actionTarget: ifTrue:[ rcvr _ msg sentTo: self] ifFalse:[ result _ msg sentTo: rcvr. result ifNotNil:[index _ index + result]]. index _ index + 1].! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/20/1998 02:36'! getURL: urlString window: windowString "Load the given url in display it in the window specified by windowString. Ignored for now." | browser | browser _ self getWebBrowser. browser ifNotNil:[ browser jumpToUrl: urlString. ^nil]. "(self confirm: ('open a browser to view\',urlString,' ?') withCRs) ifTrue: [ browser _ Scamper new. browser jumpToUrl: urlString. browser openAsMorph ]." ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 2/3/2000 17:21'! gotoFrame: frame "Jump to the given frame" self stopPlaying. self frameNumber: frame+1. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! gotoLabel: labelString "Go to the frame with the associated label string." labels ifNil:[^nil]. self frameNumber: (labels at: labelString ifAbsent:[^nil]). ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! gotoNextFrame "Go to the next frame" self frameNumber: self frameNumber+1. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! gotoPrevFrame "Go to the previous frame" self frameNumber: self frameNumber-1. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! isFrameLoaded: frame elseSkip: nActions "Skip nActions if the given frame is not loaded yet." ^loadedFrames >= frameNumber ifTrue:[nil] ifFalse:[nActions].! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 2/3/2000 17:20'! playSoundsAt: frame (sounds at: frame ifAbsent:[#()]) do: [:sound | sound ifNotNil:[self playFlashSound: sound]].! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! stopSounds "Stop all sounds" SoundPlayer shutDown. ^nil! ! !FlashSpriteMorph methodsFor: 'actions' stamp: 'ar 11/19/1998 22:11'! toggleQuality "Toggle the display quality. Ignored for now - we're aiming at adaptive quality settings." ^nil! ! !FlashSpriteMorph methodsFor: 'private' stamp: 'di 11/13/2000 00:51'! getWebBrowser "Return a web browser if we're running in one" self withAllOwnersDo: [:morph | morph isWebBrowser ifTrue: [^ morph]. (morph hasProperty: #webBrowserView) ifTrue: [^ morph model]]. ^ nil! ! !FlashSpriteMorph methodsFor: 'copying' stamp: 'ar 5/19/1999 19:11'! copyMovieFrom: firstFrame to: lastFrame "Note: This is different if sent to a sprite since a sprite contains a *full* animation and is therefore always completely." ^super copyMovieFrom: 1 to: maxFrames.! ! !FlashSpriteMorph methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:45'! convertToCurrentVersion: varDict refStream: smartRefStrm lastStepTime ifNil: [lastStepTime _ 0]. useTimeSync ifNil: [useTimeSync _ true]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! Object subclass: #FlashStreamingSound instanceVariableNames: 'mixFmt stereo samplingRate bitsPerSample sampleCount compressed firstFrame frameNumber buffers ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Support'! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:40'! bitsPerSample ^bitsPerSample! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:41'! bitsPerSample: aNumber bitsPerSample _ aNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:45'! buffers ^buffers! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:45'! buffers: anArray buffers _ anArray! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:41'! compressed ^compressed! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:41'! compressed: aBool compressed _ aBool! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:34'! firstFrame ^firstFrame! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:34'! firstFrame: frame firstFrame _ frame.! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:45'! frameNumber ^frameNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:45'! frameNumber: aNumber frameNumber _ aNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:40'! mixFmt ^mixFmt! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:40'! mixFmt: aNumber mixFmt _ aNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:41'! sampleCount ^sampleCount! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:41'! sampleCount: aNumber sampleCount _ aNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:33'! samplingRate ^samplingRate! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/21/1998 00:33'! samplingRate: aNumber samplingRate _ aNumber! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:40'! stereo ^stereo! ! !FlashStreamingSound methodsFor: 'accessing' stamp: 'ar 11/20/1998 22:40'! stereo: aBool stereo _ aBool! ! FlashCharacterMorph subclass: #FlashTextMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! BorderedMorph subclass: #FlashThumbnailMorph instanceVariableNames: 'player frameNumber image selected ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-MMFlash Morphs'! !FlashThumbnailMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:29'! initialize super initialize. selected _ false.! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:01'! frameNumber ^frameNumber! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:02'! frameNumber: aNumber frameNumber = aNumber ifFalse:[ frameNumber _ aNumber. image _ nil. ].! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:01'! image ^image! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:01'! image: aForm image _ aForm. self changed.! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 17:14'! isSelected ^selected == true! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 17:29'! isSelected: aBoolean selected == aBoolean ifTrue:[^self]. selected _ aBoolean. self borderColor: (self isSelected ifTrue:[Color red] ifFalse:[Color black]).! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:00'! player ^player! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:01'! player: aFlashPlayerMorph player _ aFlashPlayerMorph.! ! !FlashThumbnailMorph methodsFor: 'drawing' stamp: 'ar 2/12/2000 18:05'! drawOn: aCanvas (player == nil or:[frameNumber == nil]) ifTrue:[^super drawOn: aCanvas]. false ifTrue:[super drawOn: aCanvas. ^aCanvas text: frameNumber printString bounds: self innerBounds font: nil color: Color red]. image ifNil:[ Cursor wait showWhile:[ image _ player imageFormOfSize: (self extent - (self borderWidth * 2)) forFrame: frameNumber. frameNumber printString displayOn: image]]. aCanvas frameRectangle: self bounds width: self borderWidth color: self borderColor. aCanvas paintImage: image at: self topLeft + self borderWidth. ! ! EllipseMorph subclass: #FlasherMorph instanceVariableNames: 'onColor ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !FlasherMorph methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:16'! canHaveFillStyles ^false! ! !FlasherMorph methodsFor: 'as yet unclassified'! color: aColor super color: aColor. onColor _ aColor.! ! !FlasherMorph methodsFor: 'as yet unclassified'! fullPrintOn: aStream color _ onColor. super fullPrintOn: aStream.! ! !FlasherMorph methodsFor: 'as yet unclassified'! initialize super initialize. self color: Color red. self extent: 25@25. self borderWidth: 2. ! ! !FlasherMorph methodsFor: 'as yet unclassified' stamp: 'tk 7/4/2000 12:08'! step super step. color = onColor ifTrue: [super color: (onColor alphaMixed: 0.5 with: Color black)] ifFalse: [super color: onColor]. ! ! !FlasherMorph methodsFor: 'as yet unclassified'! stepTime "Answer the desired time between steps in milliseconds." ^ 500! ! NullEncoder subclass: #FlattenEncoder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Postscript Filters'! !FlattenEncoder commentStamp: '' prior: 0! The simplest possible encoding: leave the objects as is. ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 21:51'! cr ^self print:String cr. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 21:50'! writeArrayedCollection:anArrayedCollection ^self writeCollectionContents:anArrayedCollection. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 01:03'! writeCollection:aCollection ^self writeCollectionContents:aCollection. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/4/1901 08:26'! writeCollectionContents:aCollection ^self writeCollectionContents:aCollection separator:self elementSeparator iterationMessage:#do:. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/4/1901 08:26'! writeCollectionContents:aCollection separator:separator ^self writeCollectionContents:aCollection separator:separator iterationMessage:#do:.! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/4/1901 08:25'! writeCollectionContents:aCollection separator:separator iterationMessage:op | first | first _ true. aCollection perform:op with: [ :each | first ifFalse:[ self writeObject:separator ]. self write:each. first_false.]. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/4/1901 08:30'! writeDictionary:aCollection ^self writeDictionaryContents:aCollection separator:nil. ! ! !FlattenEncoder methodsFor: 'writing' stamp: 'MPW 1/4/1901 08:29'! writeDictionaryContents:aCollection separator:separator ^self writeCollectionContents:aCollection separator:separator iterationMessage:#associationsDo:.! ! !FlattenEncoder methodsFor: 'accessing' stamp: 'MPW 1/1/1901 01:32'! elementSeparator ^target elementSeparator.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlattenEncoder class instanceVariableNames: ''! !FlattenEncoder class methodsFor: 'configuring' stamp: 'MPW 1/1/1901 00:08'! filterSelector ^#flattenOnStream: ! ! SketchMorph subclass: #FlexMorph instanceVariableNames: 'originalMorph borderWidth borderColor ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 21:40'! addCustomMenuItems: aCustomMenu hand: aHandMorph "super addCustomMenuItems: aCustomMenu hand: aHandMorph." aCustomMenu addLine. aCustomMenu add: 'update from original' action: #updateFromOriginal. aCustomMenu addList: #(('border color...' changeBorderColor:) ('border width...' changeBorderWidth:)). aCustomMenu addLine. ! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 21:43'! borderColor: aColor borderColor _ aColor. self updateFromOriginal! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 21:43'! borderWidth: width borderWidth _ width asPoint. self updateFromOriginal! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 18:52'! changeBorderColor: evt | aHand | aHand _ evt ifNotNil: [evt hand] ifNil: [self primaryHand]. self changeColorTarget: self selector: #borderColor: originalColor: self borderColor hand: aHand.! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 8/30/2000 21:39'! changeBorderWidth: evt | handle origin aHand | aHand _ evt ifNil: [self primaryHand] ifNotNil: [evt hand]. origin _ aHand position. handle _ HandleMorph new forEachPointDo: [:newPoint | handle removeAllMorphs. handle addMorph: (LineMorph from: origin to: newPoint color: Color black width: 1). self borderWidth: (newPoint - origin) r asInteger // 5]. aHand attachMorph: handle. handle startStepping! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:54'! drawOn: aCanvas originalForm _ nil. "Aggressively uncache the originalForm" ^ super drawOn: aCanvas! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:35'! extent: newExtent self loadOriginalForm. "make sure it's not nil" ^ super extent: newExtent! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:35'! form self loadOriginalForm. "make sure it's not nil" ^ super form! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:35'! generateRotatedForm self loadOriginalForm. "make sure it's not nil" ^ super generateRotatedForm! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 21:37'! initialize super initialize. borderWidth _ 2@2. borderColor _ Color black.! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:35'! layoutChanged self loadOriginalForm. "make sure it's not nil" ^ super layoutChanged! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:34'! loadOriginalForm originalForm ifNil: [self updateFromOriginal]. ! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'tk 2/25/1999 10:36'! originalMorph ^ originalMorph! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 20:19'! originalMorph: aMorph originalMorph _ aMorph. scalePoint _ 0.25@0.25. self updateFromOriginal.! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 19:44'! releaseCachedState "Clear cache of rotated, scaled Form." originalForm _ Form extent: 10@10. "So super hibernate won't have to work hard but won't crash either." super releaseCachedState. rotatedForm _ nil. originalForm _ nil.! ! !FlexMorph methodsFor: 'as yet unclassified' stamp: 'di 1/11/1999 21:46'! updateFromOriginal | intermediateForm | intermediateForm _ originalMorph imageForm offset: 0@0. intermediateForm border: intermediateForm boundingBox widthRectangle: (borderWidth corner: borderWidth+1) rule: Form over fillColor: borderColor. self form: intermediateForm. originalMorph fullReleaseCachedState! ! PasteUpMorph subclass: #Flipper instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Kernel'! Array variableSubclass: #FlippyArray2 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-TestPlugins'! !FlippyArray2 commentStamp: '' prior: 0! Demonstrator class using FlippyArrayPlugin2! !FlippyArray2 methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 08:22'! reverse ^FlippyArrayPlugin2 doPrimitive: 'primReverse' withArguments: {}! ! !FlippyArray2 methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 08:24'! reverseFrom: x to: y ^FlippyArrayPlugin2 doPrimitive: 'primReverseFrom:to:' withArguments: {x. y}! ! TestInterpreterPlugin variableSubclass: #FlippyArrayPlugin2 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FlippyArrayPlugin2 commentStamp: '' prior: 0! "TestInterpreterPlugin Example: primitives for reversing an array" FlippyArrayPlugin2 translate Smalltalk garbageCollect! !FlippyArrayPlugin2 methodsFor: 'as yet unclassified' stamp: 'bf 9/24/1999 14:39'! primReverse | rcvr t a b | rcvr _ self primitive: 'primReverse' parameters: #() receiver: #Array. a _ 0. b _ rcvr size - 1. [a < b] whileTrue: [t _ rcvr at: a. rcvr at: a put: (rcvr at: b). rcvr at: b put: t. a _ a + 1. b _ b - 1]. ^rcvr asOop: Array! ! !FlippyArrayPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 9/20/1999 09:52'! primReverseFrom: fromInteger to: toInteger | rcvr t a b | rcvr _ self primitive: 'primReverseFromto' parameters: #(SmallInteger SmallInteger) receiver: #Array. a _ fromInteger - 1. b _ toInteger - 1. [a < b] whileTrue: [t _ rcvr at: a. rcvr at: a put: (rcvr at: b). rcvr at: b put: t. a _ a + 1. b _ b - 1]. ^rcvr asOop: Array! ! Number variableWordSubclass: #Float instanceVariableNames: '' classVariableNames: 'E Epsilon Halfpi Infinity Ln10 Ln2 MaxVal MaxValLn MinValLogBase2 NaN NegativeInfinity NegativeZero Pi RadiansPerDegree Sqrt2 Twopi ' poolDictionaries: '' category: 'Kernel-Numbers'! !Float commentStamp: '' prior: 0! My instances represent IEEE-754 floating-point double-precision numbers. They have about 16 digits of accuracy and their range is between plus and minus 10^307. Some valid examples are: 8.0 13.3 0.3 2.5e6 1.27e-30 1.27e-31 -12.987654e12 Mainly: no embedded blanks, little e for tens power, and a digit on both sides of the decimal point. It is actually possible to specify a radix for Squeak Float constants. This is great for teaching about numbers, but may be confusing to the average reader: 3r20.2 --> 6.66666666666667 8r20.2 --> 16.25 If you don't have access to the definition of IEEE-754, you can figure out what is going on by printing various simple values in Float hex. It may help you to know that the basic format is... sign 1 bit exponent 11 bits with bias of 1023 (16r3FF) to produce an exponent in the range -1023 .. +1024 - 16r000: significand = 0: Float zero significand ~= 0: Denormalized number (exp = -1024, no hidden '1' bit) - 16r7FF: significand = 0: Infinity significand ~= 0: Not A Number (NaN) representation mantissa 53 bits, but only 52 are stored (20 in the first word, 32 in the second). This is because a normalized mantissa, by definition, has a 1 to the right of its floating point, and IEEE-754 omits this redundant bit to gain an extra bit of precision instead. People talk about the mantissa without its leading one as the FRACTION, and with its leading 1 as the SIGNFICAND. The single-precision format is... sign 1 bit exponent 8 bits, with bias of 127, to represent -126 to +127 - 0x0 and 0xFF reserved for Float zero (mantissa is ignored) - 16r7F reserved for Float underflow/overflow (mantissa is ignored) mantissa 24 bits, but only 23 are stored This format is used in FloatArray (qv), and much can be learned from the conversion routines, Float asIEEE32BitWord, and Float class fromIEEE32Bit:. Thanks to Rich Harmon for asking many questions and to Tim Olson, Bruce Cohen, Rick Zaccone and others for the answers that I have collected here.! ]style[(680 9 1189 21 6 26 149)f1,f1LFloat hex;,f1,f1LFloat asIEEE32BitWord;,f1,f1LFloat class fromIEEE32Bit:;,f1! !Float methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:54'! * aNumber "Primitive. Answer the result of multiplying the receiver by aNumber. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." ^ aNumber adaptToFloat: self andSend: #*! ! !Float methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:22'! + aNumber "Primitive. Answer the sum of the receiver and aNumber. Essential. Fail if the argument is not a Float. See Object documentation whatIsAPrimitive." ^ aNumber adaptToFloat: self andSend: #+! ! !Float methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:55'! - aNumber "Primitive. Answer the difference between the receiver and aNumber. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." ^ aNumber adaptToFloat: self andSend: #-! ! !Float methodsFor: 'arithmetic' stamp: 'tfei 4/12/1999 12:45'! / aNumber "Primitive. Answer the result of dividing receiver by aNumber. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." aNumber = 0 ifTrue: [^(ZeroDivide dividend: self) signal]. ^ aNumber adaptToFloat: self andSend: #/! ! !Float methodsFor: 'arithmetic'! abs "This is faster than using Number abs." self < 0.0 ifTrue: [^ 0.0 - self] ifFalse: [^ self]! ! !Float methodsFor: 'arithmetic'! negated "Answer a Number that is the negation of the receiver." ^0.0 - self! ! !Float methodsFor: 'arithmetic'! reciprocal ^ 1.0 / self! ! !Float methodsFor: 'mathematical functions'! arcCos "Answer the angle in radians." ^ Halfpi - self arcSin! ! !Float methodsFor: 'mathematical functions' stamp: 'jsp 2/25/1999 11:15'! arcSin "Answer the angle in radians." ((self < -1.0) or: [self > 1.0]) ifTrue: [self error: 'Value out of range']. ((self = -1.0) or: [self = 1.0]) ifTrue: [^ Halfpi * self] ifFalse: [^ (self / (1.0 - (self * self)) sqrt) arcTan]! ! !Float methodsFor: 'mathematical functions'! arcTan "Answer the angle in radians. Optional. See Object documentation whatIsAPrimitive." | theta eps step sinTheta cosTheta | "Newton-Raphson" self < 0.0 ifTrue: [ ^ 0.0 - (0.0 - self) arcTan ]. "first guess" theta _ (self * Halfpi) / (self + 1.0). "iterate" eps _ Halfpi * Epsilon. step _ theta. [(step * step) > eps] whileTrue: [ sinTheta _ theta sin. cosTheta _ theta cos. step _ (sinTheta * cosTheta) - (self * cosTheta * cosTheta). theta _ theta - step]. ^ theta! ! !Float methodsFor: 'mathematical functions' stamp: 'jsp 3/30/1999 12:38'! arcTan: denominator "Answer the angle in radians. Optional. See Object documentation whatIsAPrimitive." | result | (self = 0.0) ifTrue: [ (denominator > 0.0) ifTrue: [ result _ 0 ] ifFalse: [ result _ Pi ] ] ifFalse: [(denominator = 0.0) ifTrue: [ (self > 0.0) ifTrue: [ result _ Halfpi ] ifFalse: [ result _ Halfpi negated ] ] ifFalse: [ (denominator > 0) ifTrue: [ result _ (self / denominator) arcTan ] ifFalse: [ result _ ((self / denominator) arcTan) + Pi ] ]. ]. ^ result.! ! !Float methodsFor: 'mathematical functions'! cos "Answer the cosine of the receiver taken as an angle in radians." ^ (self + Halfpi) sin! ! !Float methodsFor: 'mathematical functions'! degreeCos "Answer the cosine of the receiver taken as an angle in degrees." ^ self degreesToRadians cos! ! !Float methodsFor: 'mathematical functions'! degreeSin "Answer the sine of the receiver taken as an angle in degrees." ^ self degreesToRadians sin! ! !Float methodsFor: 'mathematical functions'! exp "Answer E raised to the receiver power. Optional. See Object documentation whatIsAPrimitive." | base fract correction delta div | "Taylor series" "check the special cases" self < 0.0 ifTrue: [^ (self negated exp) reciprocal]. self = 0.0 ifTrue: [^ 1]. self abs > MaxValLn ifTrue: [self error: 'exp overflow']. "get first approximation by raising e to integer power" base _ E raisedToInteger: (self truncated). "now compute the correction with a short Taylor series" "fract will be 0..1, so correction will be 1..E" "in the worst case, convergance time is logarithmic with 1/Epsilon" fract _ self fractionPart. fract = 0.0 ifTrue: [ ^ base ]. "no correction required" correction _ 1.0 + fract. delta _ fract * fract / 2.0. div _ 2.0. [delta > Epsilon] whileTrue: [ correction _ correction + delta. div _ div + 1.0. delta _ delta * fract / div]. correction _ correction + delta. ^ base * correction! ! !Float methodsFor: 'mathematical functions' stamp: 'jm 3/27/98 06:28'! floorLog: radix "Answer the floor of the log base radix of the receiver." ^ (self log: radix) floor ! ! !Float methodsFor: 'mathematical functions'! ln "Answer the natural logarithm of the receiver. Optional. See Object documentation whatIsAPrimitive." | expt n mant x div pow delta sum eps | "Taylor series" self <= 0.0 ifTrue: [self error: 'ln is only defined for x > 0.0']. "get a rough estimate from binary exponent" expt _ self exponent. n _ Ln2 * expt. mant _ self timesTwoPower: 0 - expt. "compute fine correction from mantinssa in Taylor series" "mant is in the range [0..2]" "we unroll the loop to avoid use of abs" x _ mant - 1.0. div _ 1.0. pow _ delta _ sum _ x. x _ x negated. "x <= 0" eps _ Epsilon * (n abs + 1.0). [delta > eps] whileTrue: [ "pass one: delta is positive" div _ div + 1.0. pow _ pow * x. delta _ pow / div. sum _ sum + delta. "pass two: delta is negative" div _ div + 1.0. pow _ pow * x. delta _ pow / div. sum _ sum + delta]. ^ n + sum "2.718284 ln 1.0"! ! !Float methodsFor: 'mathematical functions'! log "Answer the base 10 logarithm of the receiver." ^ self ln / Ln10! ! !Float methodsFor: 'mathematical functions' stamp: 'RJ 3/15/1999 19:35'! raisedTo: aNumber "Answer the receiver raised to aNumber." aNumber isInteger ifTrue: ["Do the special case of integer power" ^ self raisedToInteger: aNumber]. self < 0.0 ifTrue: [ self error: self printString, ' raised to a non-integer power' ]. 0.0 = aNumber ifTrue: [^ 1.0]. "special case for exponent = 0.0" (self= 0.0) | (aNumber = 1.0) ifTrue: [^ self]. "special case for self = 1.0" ^ (self ln * aNumber asFloat) exp "otherwise use logarithms" ! ! !Float methodsFor: 'mathematical functions' stamp: 'tao 4/19/98 23:22'! reciprocalFloorLog: radix "Quick computation of (self log: radix) floor, when self < 1.0. Avoids infinite recursion problems with denormalized numbers" | adjust scale n | adjust _ 0. scale _ 1.0. [(n _ radix / (self * scale)) isInfinite] whileTrue: [scale _ scale * radix. adjust _ adjust + 1]. ^ ((n floorLog: radix) + adjust) negated! ! !Float methodsFor: 'mathematical functions' stamp: 'tao 10/15/97 14:23'! reciprocalLogBase2 "optimized for self = 10, for use in conversion for printing" ^ self = 10.0 ifTrue: [Ln2 / Ln10] ifFalse: [Ln2 / self ln]! ! !Float methodsFor: 'mathematical functions' stamp: 'laza 12/21/1999 12:15'! safeArcCos "Answer the angle in radians." (self between: -1.0 and: 1.0) ifTrue: [^ self arcCos] ifFalse: [^ self sign arcCos]! ! !Float methodsFor: 'mathematical functions'! sin "Answer the sine of the receiver taken as an angle in radians. Optional. See Object documentation whatIsAPrimitive." | sum delta self2 i | "Taylor series" "normalize to the range [0..Pi/2]" self < 0.0 ifTrue: [^ (0.0 - ((0.0 - self) sin))]. self > Twopi ifTrue: [^ (self \\ Twopi) sin]. self > Pi ifTrue: [^ (0.0 - (self - Pi) sin)]. self > Halfpi ifTrue: [^ (Pi - self) sin]. "unroll loop to avoid use of abs" sum _ delta _ self. self2 _ 0.0 - (self * self). i _ 2.0. [delta > Epsilon] whileTrue: [ "once" delta _ (delta * self2) / (i * (i + 1.0)). i _ i + 2.0. sum _ sum + delta. "twice" delta _ (delta * self2) / (i * (i + 1.0)). i _ i + 2.0. sum _ sum + delta]. ^ sum! ! !Float methodsFor: 'mathematical functions'! sqrt "Answer the square root of the receiver. Optional. See Object documentation whatIsAPrimitive." | exp guess eps delta | "Newton-Raphson" self <= 0.0 ifTrue: [ self = 0.0 ifTrue: [^ 0.0] ifFalse: [^ self error: 'sqrt is invalid for x < 0']]. "first guess is half the exponent" exp _ self exponent // 2. guess _ self timesTwoPower: (0 - exp). "get eps value" eps _ guess * Epsilon. eps _ eps * eps. delta _ (self - (guess * guess)) / (guess * 2.0). [(delta * delta) > eps] whileTrue: [ guess _ guess + delta. delta _ (self - (guess * guess)) / (guess * 2.0)]. ^ guess! ! !Float methodsFor: 'mathematical functions'! tan "Answer the tangent of the receiver taken as an angle in radians." ^ self sin / self cos! ! !Float methodsFor: 'mathematical functions'! timesTwoPower: anInteger "Primitive. Answer with the receiver multiplied by 2.0 raised to the power of the argument. Optional. See Object documentation whatIsAPrimitive." anInteger < -29 ifTrue: [^ self * (2.0 raisedToInteger: anInteger)]. anInteger < 0 ifTrue: [^ self / (1 bitShift: (0 - anInteger)) asFloat]. anInteger < 30 ifTrue: [^ self * (1 bitShift: anInteger) asFloat]. ^ self * (2.0 raisedToInteger: anInteger)! ! !Float methodsFor: 'comparing' stamp: 'di 11/6/1998 13:55'! < aNumber "Primitive. Compare the receiver with the argument and return true if the receiver is less than the argument. Otherwise return false. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." ^ aNumber adaptToFloat: self andSend: # ^ aNumber adaptToFloat: self andSend: #<=! ! !Float methodsFor: 'comparing' stamp: 'di 11/6/1998 13:56'! = aNumber "Primitive. Compare the receiver with the argument and return true if the receiver is equal to the argument. Otherwise return false. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." aNumber isNumber ifFalse: [^ false]. ^ aNumber adaptToFloat: self andSend: #=! ! !Float methodsFor: 'comparing' stamp: 'di 11/6/1998 13:57'! > aNumber "Primitive. Compare the receiver with the argument and return true if the receiver is greater than the argument. Otherwise return false. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." ^ aNumber adaptToFloat: self andSend: #>! ! !Float methodsFor: 'comparing' stamp: 'di 11/6/1998 13:57'! >= aNumber "Primitive. Compare the receiver with the argument and return true if the receiver is greater than or equal to the argument. Otherwise return false. Fail if the argument is not a Float. Optional. See Object documentation whatIsAPrimitive. " ^ aNumber adaptToFloat: self andSend: #>! ! !Float methodsFor: 'comparing' stamp: 'tk 11/27/1999 21:47'! closeTo: num "are these two numbers close?" | fuzz ans | num isNumber ifFalse: [ [ans _ self = num] ifError: [:aString :aReceiver | ^ false]. ^ ans]. self = 0.0 ifTrue: [^ num abs < 0.0001]. num = 0.0 ifTrue: [^ self abs < 0.0001]. self isNaN == num isNaN ifFalse: [^ false]. self isInfinite == num isInfinite ifFalse: [^ false]. fuzz := (self abs max: num abs) * 0.0001. ^ (self - num) abs <= fuzz! ! !Float methodsFor: 'comparing' stamp: 'jm 4/28/1998 01:04'! hash "Hash is reimplemented because = is implemented. Both words of the float are used; 8 bits are removed from each end to clear most of the exponent regardless of the byte ordering. (The bitAnd:'s ensure that the intermediate results do not become a large integer.) Slower than the original version in the ratios 12:5 to 2:1 depending on values. (DNS, 11 May, 1997)" ^ (((self basicAt: 1) bitAnd: 16r00FFFF00) + ((self basicAt: 2) bitAnd: 16r00FFFF00)) bitShift: -8 ! ! !Float methodsFor: 'comparing'! ~= aNumber "Primitive. Compare the receiver with the argument and return true if the receiver is not equal to the argument. Otherwise return false. Fail if the argument is not a Float. Optional. See Object documentation whatIsAPrimitive." ^super ~= aNumber! ! !Float methodsFor: 'testing' stamp: 'bf 8/20/1999 12:56'! hasContentsInExplorer ^false! ! !Float methodsFor: 'testing'! isFloat ^ true! ! !Float methodsFor: 'testing' stamp: 'jm 4/30/1998 13:50'! isInfinite "Return true if the receiver is positive or negative infinity." ^ self = Infinity or: [self = NegativeInfinity] ! ! !Float methodsFor: 'testing'! isLiteral ^true! ! !Float methodsFor: 'testing' stamp: 'tao 10/10/97 16:39'! isNaN "simple, byte-order independent test for Not-a-Number" ^ self ~= self! ! !Float methodsFor: 'testing' stamp: 'ar 6/9/2000 18:56'! isPowerOfTwo "Return true if the receiver is an integral power of two. Floats never return true here." ^false! ! !Float methodsFor: 'testing'! isZero ^self = 0.0! ! !Float methodsFor: 'testing' stamp: 'jm 4/28/1998 01:10'! sign "Answer 1 if the receiver is greater than 0, -1 if less than 0, else 0. Handle IEEE-754 negative-zero by reporting a sign of -1" self > 0 ifTrue: [^ 1]. (self < 0 or: [((self at: 1) bitShift: -31) = 1]) ifTrue: [^ -1]. ^ 0! ! !Float methodsFor: 'truncation and round off'! exponent "Primitive. Consider the receiver to be represented as a power of two multiplied by a mantissa (between one and two). Answer with the SmallInteger to whose power two is raised. Optional. See Object documentation whatIsAPrimitive." | positive | self >= 1.0 ifTrue: [^self floorLog: 2]. self > 0.0 ifTrue: [positive _ (1.0 / self) exponent. self = (1.0 / (1.0 timesTwoPower: positive)) ifTrue: [^positive negated] ifFalse: [^positive negated - 1]]. self = 0.0 ifTrue: [^-1]. ^self negated exponent! ! !Float methodsFor: 'truncation and round off'! fractionPart "Primitive. Answer a Float whose value is the difference between the receiver and the receiver's asInteger value. Optional. See Object documentation whatIsAPrimitive." ^self - self truncated asFloat! ! !Float methodsFor: 'truncation and round off'! integerPart "Answer a Float whose value is the receiver's truncated value." ^self - self fractionPart! ! !Float methodsFor: 'truncation and round off' stamp: 'tk 12/30/2000 20:04'! reduce "If self is close to an integer, return that integer" (self closeTo: self rounded) ifTrue: [^ self rounded]! ! !Float methodsFor: 'truncation and round off'! rounded "Answer the integer nearest the receiver." self >= 0.0 ifTrue: [^(self + 0.5) truncated] ifFalse: [^(self - 0.5) truncated]! ! !Float methodsFor: 'truncation and round off' stamp: 'tao 4/19/98 13:14'! significand ^ self timesTwoPower: (self exponent negated)! ! !Float methodsFor: 'truncation and round off' stamp: 'tao 4/19/98 14:27'! significandAsInteger | exp sig | exp _ self exponent. sig _ (((self at: 1) bitAnd: 16r000FFFFF) bitShift: 32) bitOr: (self at: 2). exp > -1023 ifTrue: [sig _ sig bitOr: (1 bitShift: 52)]. ^ sig.! ! !Float methodsFor: 'truncation and round off' stamp: 'di 7/1/1998 23:01'! truncated "Answer with a SmallInteger equal to the value of the receiver without its fractional part. The primitive fails if the truncated value cannot be represented as a SmallInteger. In that case, the code below will compute a LargeInteger truncated value. Essential. See Object documentation whatIsAPrimitive. " (self isInfinite or: [self isNaN]) ifTrue: [self error: 'Cannot truncate this number']. self abs < 2.0e16 ifTrue: ["Fastest way when it may not be an integer" ^ (self quo: 1073741823.0) * 1073741823 + (self rem: 1073741823.0) truncated] ifFalse: [^ self asTrueFraction. "Extract all bits of the mantissa and shift if necess"]! ! !Float methodsFor: 'converting' stamp: 'di 11/6/1998 13:38'! adaptToFraction: rcvr andSend: selector "If I am involved in arithmetic with a Fraction, convert it to a Float." ^ rcvr asFloat perform: selector with: self! ! !Float methodsFor: 'converting' stamp: 'di 11/6/1998 13:07'! adaptToInteger: rcvr andSend: selector "If I am involved in arithmetic with an Integer, convert it to a Float." ^ rcvr asFloat perform: selector with: self! ! !Float methodsFor: 'converting'! asApproximateFraction "Answer a Fraction approximating the receiver. This conversion uses the continued fraction method to approximate a floating point number." | num1 denom1 num2 denom2 int frac newD temp | num1 _ self asInteger. "The first of two alternating numerators" denom1 _ 1. "The first of two alternating denominators" num2 _ 1. "The second numerator" denom2 _ 0. "The second denominator--will update" int _ num1. "The integer part of self" frac _ self fractionPart. "The fractional part of self" [frac = 0] whileFalse: ["repeat while the fractional part is not zero" newD _ 1.0 / frac. "Take reciprocal of the fractional part" int _ newD asInteger. "get the integer part of this" frac _ newD fractionPart. "and save the fractional part for next time" temp _ num2. "Get old numerator and save it" num2 _ num1. "Set second numerator to first" num1 _ num1 * int + temp. "Update first numerator" temp _ denom2. "Get old denominator and save it" denom2 _ denom1. "Set second denominator to first" denom1 _ int * denom1 + temp. "Update first denominator" 10000000000.0 < denom1 ifTrue: ["Is ratio past float precision? If so, pick which of the two ratios to use" num2 = 0.0 ifTrue: ["Is second denominator 0?" ^ Fraction numerator: num1 denominator: denom1]. ^ Fraction numerator: num2 denominator: denom2]]. "If fractional part is zero, return the first ratio" denom1 = 1 ifTrue: ["Am I really an Integer?" ^ num1 "Yes, return Integer result"] ifFalse: ["Otherwise return Fraction result" ^ Fraction numerator: num1 denominator: denom1]! ! !Float methodsFor: 'converting'! asFloat "Answer the receiver itself." ^self! ! !Float methodsFor: 'converting' stamp: 'sma 5/3/2000 21:46'! asFraction ^ self asTrueFraction ! ! !Float methodsFor: 'converting' stamp: 'di 2/8/1999 12:51'! asIEEE32BitWord "Convert the receiver into a 32 bit Integer value representing the same number in IEEE 32 bit format. Used for conversion in FloatArrays only." | word1 word2 sign mantissa exponent destWord | self = 0.0 ifTrue:[^0]. word1 _ self basicAt: 1. word2 _ self basicAt: 2. mantissa _ (word2 bitShift: -29) + ((word1 bitAnd: 16rFFFFF) bitShift: 3). exponent _ ((word1 bitShift: -20) bitAnd: 16r7FF) - 1023 + 127. exponent < 0 ifTrue:[^0]. "Underflow" exponent > 254 ifTrue:["Overflow" exponent _ 255. mantissa _ 0]. sign _ word1 bitAnd: 16r80000000. destWord _ (sign bitOr: (exponent bitShift: 23)) bitOr: mantissa. ^ destWord! ! !Float methodsFor: 'converting' stamp: 'di 7/1/1998 22:20'! asTrueFraction " Answer a fraction that EXACTLY represents self, a double precision IEEE floating point number. Floats are stored in the same form on all platforms. (Does not handle gradual underflow or NANs.) By David N. Smith with significant performance improvements by Luciano Esteban Notarfrancesco. (Version of 11April97)" | shifty sign expPart exp fraction fractionPart result zeroBitsCount | self isInfinite ifTrue: [self error: 'Cannot represent infinity as a fraction']. self isNaN ifTrue: [self error: 'Cannot represent Not-a-Number as a fraction']. " Extract the bits of an IEEE double float " shifty := ((self basicAt: 1) bitShift: 32) + (self basicAt: 2). " Extract the sign and the biased exponent " sign := (shifty bitShift: -63) = 0 ifTrue: [1] ifFalse: [-1]. expPart := (shifty bitShift: -52) bitAnd: 16r7FF. " Extract fractional part; answer 0 if this is a true 0.0 value " fractionPart := shifty bitAnd: 16r000FFFFFFFFFFFFF. ( expPart=0 and: [ fractionPart=0 ] ) ifTrue: [ ^ 0 ]. " Replace omitted leading 1 in fraction " fraction := fractionPart bitOr: 16r0010000000000000. "Unbias exponent: 16r3FF is bias; 52 is fraction width" exp := 16r3FF + 52 - expPart. " Form the result. When exp>52, the exponent is adjusted by the number of trailing zero bits in the fraction to minimize the (huge) time otherwise spent in #gcd:. " exp negative ifTrue: [ result := sign * fraction bitShift: exp negated ] ifFalse: [ zeroBitsCount _ fraction lowBit - 1. exp := exp - zeroBitsCount. exp <= 0 ifTrue: [ zeroBitsCount := zeroBitsCount + exp. "exp := 0." " Not needed; exp not refernced again " result := sign * fraction bitShift: zeroBitsCount negated ] ifFalse: [ result := Fraction numerator: (sign * fraction bitShift: zeroBitsCount negated) denominator: (1 bitShift: exp) ] ]. "Low cost validation omitted after extensive testing" "(result asFloat = self) ifFalse: [self error: 'asTrueFraction validation failed']." ^ result ! ! !Float methodsFor: 'converting'! degreesToRadians "Answer the receiver in radians. Assumes the receiver is in degrees." ^self * RadiansPerDegree! ! !Float methodsFor: 'converting' stamp: 'tao 10/10/97 16:38'! isInf "simple, byte-order independent test for +/- Infinity" ^ self = (self * 1.5 + 1.0)! ! !Float methodsFor: 'converting'! radiansToDegrees "Answer the receiver in degrees. Assumes the receiver is in radians." ^self / RadiansPerDegree! ! !Float methodsFor: 'copying'! deepCopy ^self copy! ! !Float methodsFor: 'copying'! shallowCopy ^self + 0.0! ! !Float methodsFor: 'copying' stamp: 'tk 8/19/1998 16:08'! veryDeepCopyWith: deepCopier "Return self. Do not record me." ^ self clone! ! !Float methodsFor: 'printing' stamp: 'MPW 1/1/1901 01:59'! absByteEncode: aStream base: base "Print my value on a stream in the given base. Assumes that my value is strictly positive; negative numbers, zero, and NaNs have already been handled elsewhere. Based upon the algorithm outlined in: Robert G. Burger and R. Kent Dybvig Printing Floating Point Numbers Quickly and Accurately ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation June 1996. This version performs all calculations with Floats instead of LargeIntegers, and loses about 3 lsbs of accuracy compared to an exact conversion." | significantBits fBase exp baseExpEstimate r s mPlus mMinus scale d tc1 tc2 fixedFormat decPointCount | self isInfinite ifTrue: [aStream print: 'Infinity'. ^ self]. significantBits _ 50. "approximately 3 lsb's of accuracy loss during conversion" fBase _ base asFloat. exp _ self exponent. baseExpEstimate _ (exp * fBase reciprocalLogBase2 - 1.0e-10) ceiling. exp >= 0 ifTrue: [r _ self. s _ 1.0. mPlus _ 1.0 timesTwoPower: exp - significantBits. mMinus _ self significand ~= 1.0 ifTrue: [mPlus] ifFalse: [mPlus / 2.0]] ifFalse: [r _ self timesTwoPower: significantBits. s _ 1.0 timesTwoPower: significantBits. mMinus _ 1.0 timesTwoPower: (exp max: -1024). mPlus _ (exp = MinValLogBase2) | (self significand ~= 1.0) ifTrue: [mMinus] ifFalse: [mMinus * 2.0]]. baseExpEstimate >= 0 ifTrue: [s _ s * (fBase raisedToInteger: baseExpEstimate). exp = 1023 ifTrue: "scale down to prevent overflow to Infinity during conversion" [r _ r / fBase. s _ s / fBase. mPlus _ mPlus / fBase. mMinus _ mMinus / fBase]] ifFalse: [exp < -1023 ifTrue: "scale up to prevent denorm reciprocals overflowing to Infinity" [d _ (53 * fBase reciprocalLogBase2 - 1.0e-10) ceiling. scale _ fBase raisedToInteger: d. r _ r * scale. mPlus _ mPlus * scale. mMinus _ mMinus * scale. scale _ fBase raisedToInteger: (baseExpEstimate + d) negated] ifFalse: [scale _ fBase raisedToInteger: baseExpEstimate negated]. s _ s / scale]. (r + mPlus >= s) ifTrue: [baseExpEstimate _ baseExpEstimate + 1] ifFalse: [s _ s / fBase]. (fixedFormat _ baseExpEstimate between: -3 and: 6) ifTrue: [decPointCount _ baseExpEstimate. baseExpEstimate <= 0 ifTrue: [aStream print: ('0.000000' truncateTo: 2 - baseExpEstimate)]] ifFalse: [decPointCount _ 1]. [d _ (r / s) truncated. r _ r - (d * s). (tc1 _ r <= mMinus) | (tc2 _ r + mPlus >= s)] whileFalse: [aStream print: (Character digitValue: d). r _ r * fBase. mPlus _ mPlus * fBase. mMinus _ mMinus * fBase. decPointCount _ decPointCount - 1. decPointCount = 0 ifTrue: [aStream print: $.]]. tc2 ifTrue: [tc1 not | (tc1 & (r*2.0 >= s)) ifTrue: [d _ d + 1]]. aStream print: (Character digitValue: d). decPointCount > 0 ifTrue: [decPointCount - 1 to: 1 by: -1 do: [:i | aStream print: $0]. aStream print: '.0']. fixedFormat ifFalse: [aStream print: $e. aStream print: (baseExpEstimate - 1) printString]! ! !Float methodsFor: 'printing' stamp: 'tao 4/19/98 23:21'! absPrintExactlyOn: aStream base: base "Print my value on a stream in the given base. Assumes that my value is strictly positive; negative numbers, zero, and NaNs have already been handled elsewhere. Based upon the algorithm outlined in: Robert G. Burger and R. Kent Dybvig Printing Floating Point Numbers Quickly and Accurately ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation June 1996. This version guarantees that the printed representation exactly represents my value by using exact integer arithmetic." | fBase significand exp baseExpEstimate be be1 r s mPlus mMinus scale roundingIncludesLimits d tc1 tc2 fixedFormat decPointCount | self isInfinite ifTrue: [aStream nextPutAll: 'Infinity'. ^ self]. fBase _ base asFloat. significand _ self significandAsInteger. roundingIncludesLimits _ significand even. exp _ (self exponent - 52) max: MinValLogBase2. baseExpEstimate _ (self exponent * fBase reciprocalLogBase2 - 1.0e-10) ceiling. exp >= 0 ifTrue: [be _ 1 << exp. significand ~= 16r10000000000000 ifTrue: [r _ significand * be * 2. s _ 2. mPlus _ be. mMinus _ be] ifFalse: [be1 _ be * 2. r _ significand * be1 * 2. s _ 4. mPlus _ be1. mMinus _ be]] ifFalse: [(exp = MinValLogBase2) | (significand ~= 16r10000000000000) ifTrue: [r _ significand * 2. s _ (1 << (exp negated)) * 2. mPlus _ 1. mMinus _ 1] ifFalse: [r _ significand * 4. s _ (1 << (exp negated + 1)) * 2. mPlus _ 2. mMinus _ 1]]. baseExpEstimate >= 0 ifTrue: [s _ s * (base raisedToInteger: baseExpEstimate)] ifFalse: [scale _ base raisedToInteger: baseExpEstimate negated. r _ r * scale. mPlus _ mPlus * scale. mMinus _ mMinus * scale]. (r + mPlus > s) | (roundingIncludesLimits & (r + mPlus = s)) ifTrue: [baseExpEstimate _ baseExpEstimate + 1] ifFalse: [r _ r * base. mPlus _ mPlus * base. mMinus _ mMinus * base]. (fixedFormat _ baseExpEstimate between: -3 and: 6) ifTrue: [decPointCount _ baseExpEstimate. baseExpEstimate <= 0 ifTrue: [aStream nextPutAll: ('0.000000' truncateTo: 2 - baseExpEstimate)]] ifFalse: [decPointCount _ 1]. [d _ r // s. r _ r \\ s. (tc1 _ (r < mMinus) | (roundingIncludesLimits & (r = mMinus))) | (tc2 _ (r + mPlus > s) | (roundingIncludesLimits & (r + mPlus = s)))] whileFalse: [aStream nextPut: (Character digitValue: d). r _ r * base. mPlus _ mPlus * base. mMinus _ mMinus * base. decPointCount _ decPointCount - 1. decPointCount = 0 ifTrue: [aStream nextPut: $.]]. tc2 ifTrue: [tc1 not | (tc1 & (r*2 >= s)) ifTrue: [d _ d + 1]]. aStream nextPut: (Character digitValue: d). decPointCount > 0 ifTrue: [decPointCount - 1 to: 1 by: -1 do: [:i | aStream nextPut: $0]. aStream nextPutAll: '.0']. fixedFormat ifFalse: [aStream nextPut: $e. aStream nextPutAll: (baseExpEstimate - 1) printString]! ! !Float methodsFor: 'printing' stamp: 'tao 4/22/98 11:58'! absPrintOn: aStream base: base "Print my value on a stream in the given base. Assumes that my value is strictly positive; negative numbers, zero, and NaNs have already been handled elsewhere. Based upon the algorithm outlined in: Robert G. Burger and R. Kent Dybvig Printing Floating Point Numbers Quickly and Accurately ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation June 1996. This version performs all calculations with Floats instead of LargeIntegers, and loses about 3 lsbs of accuracy compared to an exact conversion." | significantBits fBase exp baseExpEstimate r s mPlus mMinus scale d tc1 tc2 fixedFormat decPointCount | self isInfinite ifTrue: [aStream nextPutAll: 'Infinity'. ^ self]. significantBits _ 50. "approximately 3 lsb's of accuracy loss during conversion" fBase _ base asFloat. exp _ self exponent. baseExpEstimate _ (exp * fBase reciprocalLogBase2 - 1.0e-10) ceiling. exp >= 0 ifTrue: [r _ self. s _ 1.0. mPlus _ 1.0 timesTwoPower: exp - significantBits. mMinus _ self significand ~= 1.0 ifTrue: [mPlus] ifFalse: [mPlus / 2.0]] ifFalse: [r _ self timesTwoPower: significantBits. s _ 1.0 timesTwoPower: significantBits. mMinus _ 1.0 timesTwoPower: (exp max: -1024). mPlus _ (exp = MinValLogBase2) | (self significand ~= 1.0) ifTrue: [mMinus] ifFalse: [mMinus * 2.0]]. baseExpEstimate >= 0 ifTrue: [s _ s * (fBase raisedToInteger: baseExpEstimate). exp = 1023 ifTrue: "scale down to prevent overflow to Infinity during conversion" [r _ r / fBase. s _ s / fBase. mPlus _ mPlus / fBase. mMinus _ mMinus / fBase]] ifFalse: [exp < -1023 ifTrue: "scale up to prevent denorm reciprocals overflowing to Infinity" [d _ (53 * fBase reciprocalLogBase2 - 1.0e-10) ceiling. scale _ fBase raisedToInteger: d. r _ r * scale. mPlus _ mPlus * scale. mMinus _ mMinus * scale. scale _ fBase raisedToInteger: (baseExpEstimate + d) negated] ifFalse: [scale _ fBase raisedToInteger: baseExpEstimate negated]. s _ s / scale]. (r + mPlus >= s) ifTrue: [baseExpEstimate _ baseExpEstimate + 1] ifFalse: [s _ s / fBase]. (fixedFormat _ baseExpEstimate between: -3 and: 6) ifTrue: [decPointCount _ baseExpEstimate. baseExpEstimate <= 0 ifTrue: [aStream nextPutAll: ('0.000000' truncateTo: 2 - baseExpEstimate)]] ifFalse: [decPointCount _ 1]. [d _ (r / s) truncated. r _ r - (d * s). (tc1 _ r <= mMinus) | (tc2 _ r + mPlus >= s)] whileFalse: [aStream nextPut: (Character digitValue: d). r _ r * fBase. mPlus _ mPlus * fBase. mMinus _ mMinus * fBase. decPointCount _ decPointCount - 1. decPointCount = 0 ifTrue: [aStream nextPut: $.]]. tc2 ifTrue: [tc1 not | (tc1 & (r*2.0 >= s)) ifTrue: [d _ d + 1]]. aStream nextPut: (Character digitValue: d). decPointCount > 0 ifTrue: [decPointCount - 1 to: 1 by: -1 do: [:i | aStream nextPut: $0]. aStream nextPutAll: '.0']. fixedFormat ifFalse: [aStream nextPut: $e. aStream nextPutAll: (baseExpEstimate - 1) printString]! ! !Float methodsFor: 'printing' stamp: 'MPW 1/1/1901 00:02'! byteEncode: aStream base: base "Handle sign, zero, and NaNs; all other values passed to absPrintOn:base:" self isNaN ifTrue: [aStream print: 'NaN'. ^ self]. "check for NaN before sign" self > 0.0 ifTrue: [self absByteEncode: aStream base: base] ifFalse: [self sign = -1 ifTrue: [aStream print: '-']. self = 0.0 ifTrue: [aStream print: '0.0'. ^ self] ifFalse: [aStream writeNumber:self negated base: base]]! ! !Float methodsFor: 'printing'! hex "If ya really want to know..." | word nibble | ^ String streamContents: [:strm | 1 to: 2 do: [:i | word _ self at: i. 1 to: 8 do: [:s | nibble _ (word bitShift: -8+s*4) bitAnd: 16rF. strm nextPut: ('0123456789ABCDEF' at: nibble+1)]]] " (-2.0 to: 2.0) collect: [:f | f hex] "! ! !Float methodsFor: 'printing' stamp: 'tao 4/19/98 23:31'! printOn: aStream base: base "Handle sign, zero, and NaNs; all other values passed to absPrintOn:base:" self isNaN ifTrue: [aStream nextPutAll: 'NaN'. ^ self]. "check for NaN before sign" self > 0.0 ifTrue: [self absPrintOn: aStream base: base] ifFalse: [self sign = -1 ifTrue: [aStream nextPutAll: '-']. self = 0.0 ifTrue: [aStream nextPutAll: '0.0'. ^ self] ifFalse: [self negated absPrintOn: aStream base: base]]! ! !Float methodsFor: 'private' stamp: 'ls 10/10/1999 11:55'! absPrintOn: aStream base: base digitCount: digitCount "Print me in the given base, using digitCount significant figures." | fuzz x exp q fBase scale logScale xi | self isInf ifTrue: [^ aStream nextPutAll: 'Inf']. fBase _ base asFloat. "x is myself normalized to [1.0, fBase), exp is my exponent" exp _ self < 1.0 ifTrue: [self reciprocalFloorLog: fBase] ifFalse: [self floorLog: fBase]. scale _ 1.0. logScale _ 0. [(x _ fBase raisedTo: (exp + logScale)) = 0] whileTrue: [scale _ scale * fBase. logScale _ logScale + 1]. x _ self * scale / x. fuzz _ fBase raisedTo: 1 - digitCount. "round the last digit to be printed" x _ 0.5 * fuzz + x. x >= fBase ifTrue: ["check if rounding has unnormalized x" x _ x / fBase. exp _ exp + 1]. (exp < 6 and: [exp > -4]) ifTrue: ["decimal notation" q _ 0. exp < 0 ifTrue: [1 to: 1 - exp do: [:i | aStream nextPut: ('0.0000' at: i)]]] ifFalse: ["scientific notation" q _ exp. exp _ 0]. [x >= fuzz] whileTrue: ["use fuzz to track significance" xi _ x asInteger. aStream nextPut: (Character digitValue: xi). x _ x - xi asFloat * fBase. fuzz _ fuzz * fBase. exp _ exp - 1. exp = -1 ifTrue: [aStream nextPut: $.]]. [exp >= -1] whileTrue: [aStream nextPut: $0. exp _ exp - 1. exp = -1 ifTrue: [aStream nextPut: $.]]. q ~= 0 ifTrue: [aStream nextPut: $e. q printOn: aStream]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Float class instanceVariableNames: ''! !Float class methodsFor: 'class initialization' stamp: 'jm 4/30/1998 13:48'! initialize "Float initialize" "Constants from Computer Approximations, pp. 182-183: Pi = 3.14159265358979323846264338327950288 Pi/2 = 1.57079632679489661923132169163975144 Pi*2 = 6.28318530717958647692528676655900576 Pi/180 = 0.01745329251994329576923690768488612 2.0 ln = 0.69314718055994530941723212145817657 2.0 sqrt = 1.41421356237309504880168872420969808" Pi _ 3.14159265358979323846264338327950288. Halfpi _ Pi / 2.0. Twopi _ Pi * 2.0. RadiansPerDegree _ Pi / 180.0. Ln2 _ 0.69314718055994530941723212145817657. Ln10 _ 10.0 ln. Sqrt2 _ 1.41421356237309504880168872420969808. E _ 2.718281828459045235360287471353. Epsilon _ 0.000000000001. "Defines precision of mathematical functions" MaxVal _ 1.7976931348623159e308. MaxValLn _ 709.782712893384. MinValLogBase2 _ -1074. Infinity _ MaxVal * MaxVal. NegativeInfinity _ 0.0 - Infinity. NaN _ Infinity - Infinity. NegativeZero _ 1.0 / Infinity negated. ! ! !Float class methodsFor: 'instance creation' stamp: 'di 2/8/1999 12:58'! fromIEEE32Bit: word "Convert the given 32 bit word (which is supposed to be a positive 32bit value) from a 32bit IEEE floating point representation into an actual Squeak float object (being 64bit wide). Should only be used for conversion in FloatArrays or likewise objects." | sign mantissa exponent newFloat | word negative ifTrue: [^ self error:'Cannot deal with negative numbers']. word = 0 ifTrue:[^ 0.0]. mantissa _ word bitAnd: 16r7FFFFF. exponent _ ((word bitShift: -23) bitAnd: 16rFF) - 127. sign _ word bitAnd: 16r80000000. exponent = 128 ifTrue:["Either NAN or INF" mantissa = 0 ifFalse:[^ Float nan]. sign = 0 ifTrue:[^ Float infinity] ifFalse:[^ Float infinity negated]]. "Create new float" newFloat _ self new: 2. newFloat basicAt: 1 put: ((sign bitOr: (1023 + exponent bitShift: 20)) bitOr: (mantissa bitShift: -3)). newFloat basicAt: 2 put: ((mantissa bitAnd: 7) bitShift: 29). ^newFloat! ! !Float class methodsFor: 'instance creation'! readFrom: aStream "Answer a new Float as described on the stream, aStream." ^(super readFrom: aStream) asFloat! ! !Float class methodsFor: 'constants'! e "Answer the constant, E." ^E! ! !Float class methodsFor: 'constants' stamp: 'sw 10/8/1999 22:59'! halfPi ^ Halfpi! ! !Float class methodsFor: 'constants' stamp: 'tao 4/23/98 11:37'! infinity "Answer the value used to represent an infinite magnitude" ^ Infinity! ! !Float class methodsFor: 'constants' stamp: 'tao 4/23/98 11:38'! nan "Answer the canonical value used to represent Not-A-Number" ^ NaN! ! !Float class methodsFor: 'constants' stamp: 'tao 4/23/98 12:05'! negativeZero ^ NegativeZero! ! !Float class methodsFor: 'constants'! pi "Answer the constant, Pi." ^Pi! ! !Float class methodsFor: 'plugin generation' stamp: 'bf 3/16/2000 19:06'! ccg: cg emitLoadFor: aString from: anInteger on: aStream cg emitLoad: aString asFloatValueFrom: anInteger on: aStream! ! !Float class methodsFor: 'plugin generation' stamp: 'acg 10/5/1999 06:05'! ccg: cg generateCoerceToOopFrom: aNode on: aStream cg generateCoerceToFloatObjectFrom: aNode on: aStream! ! !Float class methodsFor: 'plugin generation' stamp: 'acg 10/5/1999 06:10'! ccg: cg generateCoerceToValueFrom: aNode on: aStream cg generateCoerceToFloatValueFrom: aNode on: aStream! ! !Float class methodsFor: 'plugin generation' stamp: 'acg 9/18/1999 17:08'! ccg: cg prolog: aBlock expr: aString index: anInteger ^cg ccgLoad: aBlock expr: aString asFloatValueFrom: anInteger! ! !Float class methodsFor: 'plugin generation' stamp: 'acg 9/20/1999 11:22'! ccgCanConvertFrom: anObject ^anObject class == self! ! !Float class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:09'! ccgDeclareCForVar: aSymbolOrString ^'double ', aSymbolOrString! ! ArrayedCollection variableWordSubclass: #FloatArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !FloatArray commentStamp: '' prior: 0! FloatArrays store 32bit IEEE floating point numbers.! !FloatArray methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! at: index ^Float fromIEEE32Bit: (self basicAt: index)! ! !FloatArray methodsFor: 'accessing' stamp: 'ar 2/2/2001 15:47'! at: index put: value value isFloat ifTrue:[self basicAt: index put: value asIEEE32BitWord] ifFalse:[self at: index put: value asFloat]. ^value! ! !FloatArray methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:19'! defaultElement "Return the default element of the receiver" ^0.0! ! !FloatArray methodsFor: 'accessing' stamp: 'laza 3/24/2000 13:08'! length "Return the length of the receiver" ^self squaredLength sqrt! ! !FloatArray methodsFor: 'accessing' stamp: 'laza 3/24/2000 13:08'! squaredLength "Return the squared length of the receiver" ^self dot: self! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:33'! * anObject ^self clone *= anObject! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:49'! *= anObject ^anObject isNumber ifTrue:[self primMulScalar: anObject asFloat] ifFalse:[self primMulArray: anObject]! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:33'! + anObject ^self clone += anObject! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:48'! += anObject ^anObject isNumber ifTrue:[self primAddScalar: anObject asFloat] ifFalse:[self primAddArray: anObject]! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:33'! - anObject ^self clone -= anObject! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:49'! -= anObject ^anObject isNumber ifTrue:[self primSubScalar: anObject asFloat] ifFalse:[self primSubArray: anObject]! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 9/14/1998 22:34'! / anObject ^self clone /= anObject! ! !FloatArray methodsFor: 'arithmetic' stamp: 'ar 10/7/1998 19:58'! /= anObject ^anObject isNumber ifTrue:[self primDivScalar: anObject asFloat] ifFalse:[self primDivArray: anObject]! ! !FloatArray methodsFor: 'arithmetic' stamp: 'laza 3/24/2000 13:07'! dot: aFloatVector "Primitive. Return the dot product of the receiver and the argument. Fail if the argument is not of the same size as the receiver." | result | "" self size = aFloatVector size ifFalse:[^self error:'Must be equal size']. result _ 0.0. 1 to: self size do:[:i| result _ result + ((self at: i) * (aFloatVector at: i)). ]. ^result! ! !FloatArray methodsFor: 'comparing' stamp: 'ar 2/2/2001 15:47'! = aFloatArray | length | aFloatArray class = self class ifFalse: [^ false]. length _ self size. length = aFloatArray size ifFalse: [^ false]. 1 to: self size do: [:i | (self at: i) = (aFloatArray at: i) ifFalse: [^ false]]. ^ true! ! !FloatArray methodsFor: 'comparing' stamp: 'ar 8/31/2000 21:43'! hash | result | "WARNING: THE PRIMITIVE SEEMS TO BE BROKEN" "" result _ 0. 1 to: self size do:[:i| result _ result + (self basicAt: i) ]. ^result bitAnd: 16r1FFFFFFF! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primAddArray: floatArray 1 to: self size do:[:i| self at: i put: (self at: i) + (floatArray at: i)].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primAddScalar: scalarValue 1 to: self size do:[:i| self at: i put: (self at: i) + scalarValue].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primDivArray: floatArray 1 to: self size do:[:i| self at: i put: (self at: i) / (floatArray at: i)].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primDivScalar: scalarValue 1 to: self size do:[:i| self at: i put: (self at: i) / scalarValue].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primMulArray: floatArray 1 to: self size do:[:i| self at: i put: (self at: i) * (floatArray at: i)].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primMulScalar: scalarValue 1 to: self size do:[:i| self at: i put: (self at: i) * scalarValue].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primSubArray: floatArray 1 to: self size do:[:i| self at: i put: (self at: i) - (floatArray at: i)].! ! !FloatArray methodsFor: 'primitives-plugin' stamp: 'ar 2/2/2001 15:47'! primSubScalar: scalarValue 1 to: self size do:[:i| self at: i put: (self at: i) - scalarValue].! ! !FloatArray methodsFor: 'converting' stamp: 'ar 9/14/1998 23:46'! asFloatArray ^self! ! !FloatArray methodsFor: 'private' stamp: 'ar 10/9/1998 11:27'! replaceFrom: start to: stop with: replacement startingAt: repStart "Primitive. This destructively replaces elements from start to stop in the receiver starting at index, repStart, in the collection, replacement. Answer the receiver. Range checks are performed in the primitive only. Optional. See Object documentation whatIsAPrimitive." super replaceFrom: start to: stop with: replacement startingAt: repStart! ! !FloatArray methodsFor: 'user interface' stamp: 'ar 2/13/1999 21:33'! inspect "Open a OrderedCollectionInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." OrderedCollectionInspector openOn: self withEvalPane: true! ! !FloatArray methodsFor: 'user interface' stamp: 'ar 2/13/1999 21:33'! inspectWithLabel: aLabel "Open a OrderedCollectionInspector on the receiver. Use basicInspect to get a normal (less useful) type of inspector." OrderedCollectionInspector openOn: self withEvalPane: true withLabel: aLabel! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FloatArray class instanceVariableNames: ''! !FloatArray class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:17'! ccg: cg emitLoadFor: aString from: anInteger on: aStream cg emitLoad: aString asFloatPtrFrom: anInteger on: aStream! ! !FloatArray class methodsFor: 'plugin generation' stamp: 'acg 9/18/1999 17:07'! ccg: cg prolog: aBlock expr: aString index: anInteger ^cg ccgLoad: aBlock expr: aString asWBFloatPtrFrom: anInteger! ! !FloatArray class methodsFor: 'plugin generation' stamp: 'acg 9/17/1999 01:14'! ccgDeclareCForVar: aSymbolOrString ^'float *', aSymbolOrString! ! InterpreterPlugin subclass: #FloatArrayPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FloatArrayPlugin commentStamp: '' prior: 0! FloatArrayPlugin defines the basic access messages for FloatArray (e.g., floatAt: and floatAt:put:).! !FloatArrayPlugin methodsFor: 'access primitives' stamp: 'ar 5/11/2000 23:14'! primitiveAt | index rcvr floatValue floatPtr | self export: true. self var: #floatValue declareC:'double floatValue'. self var: #floatPtr declareC:'float *floatPtr'. index _ interpreterProxy stackIntegerValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy success: (index > 0 and:[index <= (interpreterProxy slotSizeOf: rcvr)]). interpreterProxy failed ifTrue:[^nil]. floatPtr _ interpreterProxy firstIndexableField: rcvr. floatValue _ (floatPtr at: index-1) asFloat. interpreterProxy pop: 2. interpreterProxy pushFloat: floatValue.! ! !FloatArrayPlugin methodsFor: 'access primitives' stamp: 'ar 5/11/2000 23:16'! primitiveAtPut | value floatValue index rcvr floatPtr | self export: true. self var: #floatValue declareC: 'double floatValue'. self var: #floatPtr declareC:'float *floatPtr'. value _ interpreterProxy stackValue: 0. (interpreterProxy isIntegerObject: value) ifTrue:[floatValue _ (interpreterProxy integerValueOf: value) asFloat] ifFalse:[floatValue _ interpreterProxy floatValueOf: value]. index _ interpreterProxy stackIntegerValue: 1. rcvr _ interpreterProxy stackObjectValue: 2. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy success: (index > 0 and:[index <= (interpreterProxy slotSizeOf: rcvr)]). interpreterProxy failed ifTrue:[^nil]. floatPtr _ interpreterProxy firstIndexableField: rcvr. floatPtr at: index-1 put: (self cCoerce: floatValue to:'float'). interpreterProxy failed ifFalse:[interpreterProxy pop: 3 thenPush: value].! ! !FloatArrayPlugin methodsFor: 'access primitives' stamp: 'ar 5/11/2000 23:17'! primitiveEqual | rcvr arg rcvrPtr argPtr length | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. interpreterProxy pop: 2. length _ interpreterProxy stSizeOf: arg. length = (interpreterProxy stSizeOf: rcvr) ifFalse:[^interpreterProxy pushBool: false]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. 0 to: length-1 do:[:i| (rcvrPtr at: i) = (argPtr at: i) ifFalse:[^interpreterProxy pushBool: false]. ]. ^interpreterProxy pushBool: true! ! !FloatArrayPlugin methodsFor: 'access primitives' stamp: 'ar 5/11/2000 23:16'! primitiveHash | rcvr rcvrPtr length result | self export: true. self var: #rcvrPtr declareC:'int *rcvrPtr'. rcvr _ interpreterProxy stackObjectValue: 0. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: rcvr. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'int *'. result _ 0. 0 to: length-1 do:[:i| result _ result + (rcvrPtr at: i). rcvrPtr _ rcvrPtr + 4. ]. interpreterProxy pop: 1. ^interpreterProxy pushInteger: (result bitAnd: 16r1FFFFFFF)! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:17'! primitiveAddFloatArray "Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver." | rcvr arg rcvrPtr argPtr length | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: arg. interpreterProxy success: (length = (interpreterProxy stSizeOf: rcvr)). interpreterProxy failed ifTrue:[^nil]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) + (argPtr at: i). ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:15'! primitiveAddScalar "Primitive. Add the argument, a scalar value to the receiver, a FloatArray" | rcvr rcvrPtr value length | self export: true. self var: #value declareC:'double value'. self var: #rcvrPtr declareC:'float *rcvrPtr'. value _ interpreterProxy stackFloatValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: rcvr. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) + value. ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:15'! primitiveDivFloatArray "Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver." | rcvr arg rcvrPtr argPtr length | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: arg. interpreterProxy success: (length = (interpreterProxy stSizeOf: rcvr)). interpreterProxy failed ifTrue:[^nil]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. "Check if any of the argument's values is zero" 0 to: length-1 do:[:i| (interpreterProxy longAt: (argPtr + i)) = 0 ifTrue:[^interpreterProxy primitiveFail]]. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) / (argPtr at: i). ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:16'! primitiveDivScalar "Primitive. Add the argument, a scalar value to the receiver, a FloatArray" | rcvr rcvrPtr value inverse length | self export: true. self var: #value declareC:'double value'. self var: #inverse declareC:'double inverse'. self var: #rcvrPtr declareC:'float *rcvrPtr'. value _ interpreterProxy stackFloatValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. value = 0.0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: rcvr. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. inverse _ 1.0 / value. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) * inverse. ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:18'! primitiveDotProduct "Primitive. Compute the dot product of the receiver and the argument. The dot product is defined as the sum of the products of the individual elements." | rcvr arg rcvrPtr argPtr length result | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. self var: #result declareC:'double result'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: arg. interpreterProxy success: (length = (interpreterProxy stSizeOf: rcvr)). interpreterProxy failed ifTrue:[^nil]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. result _ 0.0. 0 to: length-1 do:[:i| result _ result + ((rcvrPtr at: i) * (argPtr at: i)). ]. interpreterProxy pop: 2. "Pop args + rcvr" interpreterProxy pushFloat: result. "Return result"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:15'! primitiveMulFloatArray "Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver." | rcvr arg rcvrPtr argPtr length | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: arg. interpreterProxy success: (length = (interpreterProxy stSizeOf: rcvr)). interpreterProxy failed ifTrue:[^nil]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) * (argPtr at: i). ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:17'! primitiveMulScalar "Primitive. Add the argument, a scalar value to the receiver, a FloatArray" | rcvr rcvrPtr value length | self export: true. self var: #value declareC:'double value'. self var: #rcvrPtr declareC:'float *rcvrPtr'. value _ interpreterProxy stackFloatValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: rcvr. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) * value. ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:17'! primitiveSubFloatArray "Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver." | rcvr arg rcvrPtr argPtr length | self export: true. self var: #rcvrPtr declareC:'float *rcvrPtr'. self var: #argPtr declareC:'float *argPtr'. arg _ interpreterProxy stackObjectValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: arg). interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: arg. interpreterProxy success: (length = (interpreterProxy stSizeOf: rcvr)). interpreterProxy failed ifTrue:[^nil]. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. argPtr _ self cCoerce: (interpreterProxy firstIndexableField: arg) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) - (argPtr at: i). ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! !FloatArrayPlugin methodsFor: 'arithmetic primitives' stamp: 'ar 5/11/2000 23:17'! primitiveSubScalar "Primitive. Add the argument, a scalar value to the receiver, a FloatArray" | rcvr rcvrPtr value length | self export: true. self var: #value declareC:'double value'. self var: #rcvrPtr declareC:'float *rcvrPtr'. value _ interpreterProxy stackFloatValue: 0. rcvr _ interpreterProxy stackObjectValue: 1. interpreterProxy failed ifTrue:[^nil]. interpreterProxy success: (interpreterProxy isWords: rcvr). interpreterProxy failed ifTrue:[^nil]. length _ interpreterProxy stSizeOf: rcvr. rcvrPtr _ self cCoerce: (interpreterProxy firstIndexableField: rcvr) to: 'float *'. 0 to: length-1 do:[:i| rcvrPtr at: i put: (rcvrPtr at: i) - value. ]. interpreterProxy pop: 1. "Leave rcvr on stack"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FloatArrayPlugin class instanceVariableNames: ''! !FloatArrayPlugin class methodsFor: 'translation to C' stamp: 'ar 9/15/1998 00:30'! declareCVarsIn: cg "Nothing to declare..."! ! AlignmentMorph subclass: #FloatingBookControlsMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Navigators'! !FloatingBookControlsMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:40'! initialize super initialize. borderWidth _ 1. borderColor _ Color black. self layoutInset: 0. self hResizing: #shrinkWrap. self vResizing: #shrinkWrap.! ! !FloatingBookControlsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/23/2000 12:47'! morphicLayerNumber "helpful for insuring some morphs always appear in front of or behind others. smaller numbers are in front" ^23 "page controls are behind menus and balloons, but in front of most other stuff"! ! !FloatingBookControlsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/23/2000 12:48'! step owner == self world ifFalse: [^ self]. owner addMorphInLayer: self. ! ! !FloatingBookControlsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/23/2000 12:47'! stepTime ^1000! ! !FloatingBookControlsMorph methodsFor: 'as yet unclassified' stamp: 'RAA 8/23/2000 12:47'! wantsSteps ^true! ! Object subclass: #FontCache instanceVariableNames: 'fonts ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Remote'! !FontCache commentStamp: '' prior: 0! Used by MREncoder and MRDecoder. It associates an integer index with a number of fonts. Fonts can be searched by index, and the index can be found for a font that isn't present. If a font is added to the cache, sometimes the cache will discard another font to make room.! !FontCache methodsFor: 'initialization' stamp: 'ls 3/27/2000 17:23'! initialize: cacheSize fonts := Array new: cacheSize.! ! !FontCache methodsFor: 'lookups' stamp: 'ls 3/27/2000 17:28'! fontAt: index "return the font associated with the given index" ^fonts at: index! ! !FontCache methodsFor: 'lookups' stamp: 'ls 3/27/2000 17:25'! includesFont: aFont "decide whether the given font is included in the collection" ^fonts identityIncludes: aFont ! ! !FontCache methodsFor: 'lookups' stamp: 'ls 3/27/2000 17:28'! indexForNewFont: aFont "add aFont to the cache. Return its index. The receiver will sometimes choose an index that is already used; that means that aFont is replacing the other font" | index | index := fonts size atRandom. "random is simpler to manage than anything else" fonts at: index put: aFont. ^index! ! !FontCache methodsFor: 'lookups' stamp: 'ls 3/27/2000 17:25'! indexOf: aFont "return the index for a given font" ^fonts identityIndexOf: aFont! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FontCache class instanceVariableNames: ''! !FontCache class methodsFor: 'instance creation' stamp: 'ls 3/27/2000 17:33'! new: size ^super new initialize: size! ! Object subclass: #FontSet instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Text'! !FontSet commentStamp: '' prior: 0! FontSet provides a mechanism for storing a set of fonts as a class that can be conveniently filedOut, filedIn, and installed as a TextStyle. The most common use is... Find a font you like. Use BitFont to convert a bunch of sizes to data files named, eg, LovelyNN.BF Use FontSet convertFontsNamed: 'Lovely' to produce a FontSet named Lovely. FileOut that FontSet for later use. Use Lovely installAsTextStyle to make all sizes available in a TextStyle named #Lovely in the TextConstants dictionary. Use ctrl-k in any text pane to select the new Lovely style for that paragraph. Then use cmd-1 through 5 or cmd-k to set the point-size for any selection. ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FontSet class instanceVariableNames: ''! !FontSet class methodsFor: 'as yet unclassified' stamp: 'di 9/15/97 12:01'! convertFontsNamed: familyName "FontSet convertFontsNamed: 'Palatino' " ^ self convertFontsNamed: familyName inDirectoryNamed: ''! ! !FontSet class methodsFor: 'private' stamp: 'mdr 8/31/2000 19:18'! compileFont: strikeFont | tempName literalString header | tempName _ 'FontTemp.sf2'. strikeFont writeAsStrike2named: tempName. literalString _ (FileStream readOnlyFileNamed: tempName) contentsOfEntireFile printString. header _ 'sizeNNN ^ self fontNamed: ''NNN'' fromLiteral: ' copyReplaceAll: 'NNN' with: strikeFont name. self class compile: header , literalString classified: 'fonts' notifying: nil. FileDirectory default deleteFileNamed: tempName! ]style[(13 10 4 30 4 8 3 14 3 10 22 8 3 13 4 10 20 8 37 6 3 50 17 5 7 10 8 4 18 6 3 13 15 7 14 3 3 13 26 8)f1b,f1cblack;b,f1,f1cblack;,f1,f1cblack;,f1,f1cblue;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblue;,f1,f1cblue;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;,f1,f1cblue;,f1,f1cblack;,f1,f1cblack;,f1,f1cblack;! ! !FontSet class methodsFor: 'private' stamp: 'sma 12/29/1999 12:58'! fontCategory ^ 'Graphics-Fonts' asSymbol! ! !FontSet class methodsFor: 'private' stamp: 'RAA 6/20/2000 13:29'! fontName self flag: #bob. "temporary hack until I figure out what's happening here" (self name beginsWith: superclass name) ifFalse: [^self name]. ^ (self name copyFrom: superclass name size + 1 to: self name size) asSymbol! ! !FontSet class methodsFor: 'private' stamp: 'sma 12/29/1999 12:58'! fontSetClass: aString | className fontSet | className _ (self name , (aString select: [:c | c isAlphaNumeric]) capitalized) asSymbol. fontSet _ Smalltalk at: className ifAbsentPut: [self subclass: className instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: self fontCategory]. (fontSet inheritsFrom: self) ifFalse: [^ self error: 'The name ' , className , ' is already in use']. ^ fontSet! ! !FontSet class methodsFor: 'converting' stamp: 'sma 12/29/1999 21:18'! convertFontsNamed: familyName inDirectoryNamed: dirName "FontSet convertFontsNamed: 'Tekton' inDirectoryNamed: 'Tekton Fonts' " "This utility is for use after you have used BitFont to produce data files for the fonts you wish to use. It will read the BitFont files and build a fontset class from them. If one already exists, the sizes that can be found will be overwritten." "For this utility to work as is, the BitFont data files must be named 'familyNN.BF', and must reside in the directory named by dirName (use '' for the current directory)." | allFontNames fontSet dir | "Check first for matching file names and usable FontSet class name." dir _ dirName isEmpty ifTrue: [FileDirectory default] ifFalse: [FileDirectory default directoryNamed: dirName]. allFontNames _ dir fileNamesMatching: familyName , '##.BF'. allFontNames isEmpty ifTrue: [^ self error: 'No files found like ' , familyName , 'NN.BF']. fontSet _ self fontSetClass: familyName. allFontNames do: [:each | Transcript cr; show: each. fontSet compileFont: (StrikeFont new readFromBitFont: (dir fullNameFor: each))]! ! !FontSet class methodsFor: 'converting' stamp: 'sma 12/29/1999 12:27'! convertTextStyleNamed: aString | style fontSet | (style _ TextStyle named: aString) ifNil: [^ self error: 'unknown text style ' , aString]. fontSet _ self fontSetClass: aString. style fontArray do: [:each | fontSet compileFont: each]! ! !FontSet class methodsFor: 'filein/out' stamp: 'sma 12/29/1999 11:49'! fileOut "FileOut and then change the properties of the file so that it won't be treated as text by, eg, email attachment facilities" super fileOut. (FileStream oldFileNamed: self name , '.st') setFileTypeToObject; close! ! !FontSet class methodsFor: 'installing' stamp: 'sma 12/30/1999 15:01'! fontNamed: fontName fromLiteral: aString "This method allows a font set to be captured as sourcecode in a subclass. The string literals will presumably be created by printing, eg, (FileStream readOnlyFileNamed: 'Palatino24.sf2') contentsOfEntireFile, and then pasting into a browser after a heading like, eg, sizeNewYork10 ^ self fontNamed: 'NewYork10' fromLiteral: '--unreadable binary data--' See the method installAsTextStyle to see how this can be used." ^ StrikeFont new name: fontName; readFromStrike2Stream: (ReadStream on: aString asByteArray)! ! !FontSet class methodsFor: 'installing' stamp: 'sma 12/30/1999 15:07'! installAsDefault "FontSetNewYork installAsDefault" (SelectionMenu confirm: 'Do you want to install ''' , self fontName , ''' as default font?') ifFalse: [^ self]. self installAsTextStyle. "TextConstants at: #OldDefaultTextStyle put: TextStyle default." TextConstants at: #DefaultTextStyle put: (TextStyle named: self fontName). ListParagraph initialize. PopUpMenu initialize. StandardSystemView initialize. "SelectionMenu notify: 'The old text style has been saved as ''OldDefaultTextStyle''.'"! ! !FontSet class methodsFor: 'installing' stamp: 'sma 12/30/1999 15:05'! installAsTextStyle "FontSetNewYork installAsTextStyle" | selectors | (TextConstants includesKey: self fontName) ifTrue: [(self confirm: self fontName , ' is already defined in TextConstants. Do you want to replace that definition?') ifFalse: [^ self]]. selectors _ (self class selectors select: [:s | s beginsWith: 'size']) asSortedCollection. TextConstants at: self fontName put: (TextStyle fontArray: (selectors collect: [:each | self perform: each]))! ! !FontSet class methodsFor: 'installing' stamp: 'RAA 6/17/2000 10:16'! size: pointSize fromLiteral: aString "This method allows a font set to be captured as sourcecode in a subclass. The string literals will presumably be created by printing, eg, (FileStream readOnlyFileNamed: 'Palatino24.sf2') contentsOfEntireFile, and then pasting into a browser after a heading like, eg, size24 ^ self size: 24 fromLiteral: '--unreadable binary data--' See the method installAsTextStyle to see how this can be used." "This method is old and for backward compatibility only. please use fontNamed:fromLiteral: instead." self flag: #bob. "used in Alan's projects" ^ StrikeFont new name: self fontName , (pointSize < 10 ifTrue: ['0' , pointSize printString] ifFalse: [pointSize printString]); readFromStrike2Stream: ((RWBinaryOrTextStream with: aString) reset; binary)! ! !FontSet class methodsFor: 'compiling' stamp: 'sma 12/29/1999 11:48'! acceptsLoggingOfCompilation "Dont log sources for my subclasses, so as not to waste time and space storing printString versions of the string literals." ^ self == FontSet! ! Object subclass: #Foo2 instanceVariableNames: 'myInteger ' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-TestPlugins'! !Foo2 commentStamp: '' prior: 0! Demonstrator class using FooPlugin2! !Foo2 methodsFor: 'as yet unclassified' stamp: 'acg 9/19/1999 20:31'! myInteger: anInteger ^myInteger _ anInteger! ! !Foo2 methodsFor: 'as yet unclassified' stamp: 'acg 9/19/1999 20:55'! primFooIntegerIdentity: x ^"FooPlugin2 doPrimitive: 'primFooIntegerIdentity:' withArguments: {x}" 'Whoops!!'! ! !Foo2 methodsFor: 'as yet unclassified' stamp: 'acg 9/19/1999 20:55'! primFooIntegerSeventeen ^"FooPlugin2 doPrimitive: 'primFooIntegerSeventeen' withArguments: {}" 'Whoops!!'! ! !Foo2 methodsFor: 'as yet unclassified' stamp: 'acg 9/19/1999 20:55'! primFooIntegerSumAnd: x with: y ^"FooPlugin2 doPrimitive: 'primFooIntegerSumAnd:with:' withArguments: {x . y}" 'Whoops!!'! ! !Foo2 methodsFor: 'as yet unclassified' stamp: 'acg 9/19/1999 20:56'! primFooIntegerSumWith: x ^"FooPlugin2 doPrimitive: 'primFooIntegerSumWith:' withArguments: {x}" 'Whoops!!'! ! TestInterpreterPlugin subclass: #FooPlugin2 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'VMConstruction-Plugins'! !FooPlugin2 commentStamp: '' prior: 0! "TestInterpreter Example: Squeak Swiki named primitive documentation primitives" FooPlugin2 translate! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 09:59'! primFooIntegerIdentity: x self primitive: 'primFooIntegerIdentity' parameters: #(SmallInteger) receiver: #Oop. ^ x asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 10:00'! primFooIntegerSeventeen self primitive: 'primFooIntegerSeventeen' parameters: #() receiver: #Oop. ^ 17 asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 10:00'! primFooIntegerSumAnd: x with: y self primitive: 'primFooIntegerSumAnd' parameters: #(SmallInteger SmallInteger) receiver: #Oop. ^ (x + y) asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 10:00'! primFooIntegerSumWith: x |rcvr myInteger| rcvr _ self primitive: 'primFooIntegerSumWith' parameters: #(SmallInteger) receiver: #Foo2. myInteger _ (rcvr asIf: Foo2 var: 'myInteger') asValue: SmallInteger. ^ (x + myInteger) asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 11:28'! primNGFooIntegerIdentity: x self primitive: 'primNGFooIntegerIdentity' parameters: #(SmallInteger) receiver: #Oop. self suppressFailureGuards: true. ^ x asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 11:28'! primNGFooIntegerSeventeen self primitive: 'primNGFooIntegerSeventeen' parameters: #() receiver: #Oop. self suppressFailureGuards: true. ^ 17 asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 11:28'! primNGFooIntegerSumAnd: x with: y self primitive: 'primNGFooIntegerSumAnd' parameters: #(SmallInteger SmallInteger) receiver: #Oop. self suppressFailureGuards: true. ^ (x + y) asOop: SmallInteger! ! !FooPlugin2 methodsFor: 'as yet unclassified' stamp: 'acg 12/18/1999 11:28'! primNGFooIntegerSumWith: x |rcvr myInteger| rcvr _ self primitive: 'primNGFooIntegerSumWith' parameters: #(SmallInteger) receiver: #Foo2. self suppressFailureGuards: true. myInteger _ (rcvr asIf: Foo2 var: 'myInteger') asValue: SmallInteger. ^ (x + myInteger) asOop: SmallInteger! ! DisplayMedium subclass: #Form instanceVariableNames: 'bits width height depth offset ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !Form commentStamp: '' prior: 0! A rectangular array of pixels, used for holding images. All pictures, including character images are Forms. The depth of a Form is how many bits are used to specify the color at each pixel. The actual bits are held in a Bitmap, whose internal structure is different at each depth. Class Color allows you to deal with colors without knowing how they are actually encoded inside a Bitmap. The supported depths (in bits) are 1, 2, 4, 8, 16, and 32. The number of actual colors at these depths are: 2, 4, 16, 256, 32768, and 16 million. Forms are combined using BitBlt. See the comment in class BitBlt. Forms that are have both transparent and opaque areas are MaskedForms. Forms that repeat many times to fill a large destination are InfiniteForms. colorAt: x@y Returns the abstract color at this location displayAt: x@y shows this form on the screen displayOn: aMedium at: x@y shows this form in a Window, a Form, or other DisplayMedium fillColor: aColor Set all the pixels to the color. edit launch an editor to change the bits of this form. pixelValueAt: x@y The encoded color. Depends on the depth. ! !Form methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 16:00'! allocateForm: extentPoint "Allocate a new form which is similar to the receiver and can be used for accelerated blts" ^Form extent: extentPoint depth: self depth! ! !Form methodsFor: 'initialize-release' stamp: 'ar 5/26/2000 00:46'! finish "If there are any pending operations on the receiver complete them. Do not return before all modifications have taken effect."! ! !Form methodsFor: 'initialize-release' stamp: 'ar 5/26/2000 00:45'! flush "If there are any pending operations on the receiver start doing them. In time, they will show up on the receiver but not necessarily immediately after this method returns."! ! !Form methodsFor: 'initialize-release'! fromDisplay: aRectangle "Create a virtual bit map from a user specified rectangular area on the display screen. Reallocates bitmap only if aRectangle ~= the receiver's extent." (width = aRectangle width and: [height = aRectangle height]) ifFalse: [self setExtent: aRectangle extent depth: depth]. self copyBits: (aRectangle origin extent: self extent) from: Display at: 0 @ 0 clippingBox: self boundingBox rule: Form over fillColor: nil! ! !Form methodsFor: 'initialize-release' stamp: 'ar 5/28/2000 18:45'! shutDown "The system is going down. Try to preserve some space" self hibernate! ! !Form methodsFor: 'accessing'! bits "Answer the receiver's Bitmap containing its bits." ^ bits! ! !Form methodsFor: 'accessing'! bits: aBitmap "Reset the Bitmap containing the receiver's bits." bits _ aBitmap! ! !Form methodsFor: 'accessing' stamp: 'tk 3/9/97'! center "Note that offset is ignored here. Are we really going to embrace offset? " ^ (width @ height) // 2! ! !Form methodsFor: 'accessing' stamp: 'ar 5/28/2000 12:03'! defaultCanvasClass "Return the default canvas used for drawing onto the receiver" ^Display defaultCanvasClass! ! !Form methodsFor: 'accessing'! depth ^ depth! ! !Form methodsFor: 'accessing'! depth: bitsPerPixel (bitsPerPixel > 32 or: [(bitsPerPixel bitAnd: bitsPerPixel-1) ~= 0]) ifTrue: [self halt: 'bitsPerPixel must be 1, 2, 4, 8, 16 or 32']. depth _ bitsPerPixel! ! !Form methodsFor: 'accessing' stamp: 'ar 5/27/2000 16:56'! displayScreen "Return the display screen the receiver is allocated on. Forms in general are Squeak internal and not allocated on any particular display." ^nil! ! !Form methodsFor: 'accessing'! extent ^ width @ height! ! !Form methodsFor: 'accessing'! form "Answer the receiver's form. For vanilla Forms, this degenerates to self. Makes several methods that operate on both Forms and MaskedForms much more straightforward. 6/1/96 sw" ^ self! ! !Form methodsFor: 'accessing' stamp: 'ar 5/28/2000 12:03'! getCanvas "Return a Canvas that can be used to draw onto the receiver" ^self defaultCanvasClass on: self! ! !Form methodsFor: 'accessing' stamp: 'ar 5/28/2000 00:48'! hasBeenModified "Return true if something *might* have been drawn into the receiver" ^(bits == nil or:[bits class == ByteArray]) not "Read the above as: If the receiver has forgotten its contents (bits == nil) or is still hibernated it can't be modified."! ! !Form methodsFor: 'accessing' stamp: 'ar 5/28/2000 00:48'! hasBeenModified: aBool "Change the receiver to reflect the modification state" aBool ifTrue:[^self unhibernate]. self shouldPreserveContents ifTrue:[self hibernate] ifFalse:[bits _ nil]! ! !Form methodsFor: 'accessing'! height ^ height! ! !Form methodsFor: 'accessing' stamp: 'ar 2/16/2000 22:00'! offset ^offset ifNil:[0@0]! ! !Form methodsFor: 'accessing'! offset: aPoint offset _ aPoint! ! !Form methodsFor: 'accessing'! size "Should no longer be used -- use bitsSize instead. length of variable part of instance." ^ super size! ! !Form methodsFor: 'accessing'! width ^ width! ! !Form methodsFor: 'copying' stamp: 'jm 11/12/97 19:28'! as8BitColorForm "Simple conversion of zero pixels to transparent. Force it to 8 bits." | f map | f _ ColorForm extent: self extent depth: 8. self displayOn: f at: self offset negated. map _ Color indexedColors copy. map at: 1 put: Color transparent. f colors: map. f offset: self offset. ^ f ! ! !Form methodsFor: 'copying' stamp: 'ar 5/28/2000 12:07'! asFormOfDepth: d | newForm | d = depth ifTrue:[^self]. newForm _ Form extent: self extent depth: d. (BitBlt current toForm: newForm) colorMap: (self colormapIfNeededForDepth: d); copy: (self boundingBox) from: 0@0 in: self fillColor: nil rule: Form over. ^newForm! ! !Form methodsFor: 'copying' stamp: 'ar 6/9/2000 18:59'! contentsOfArea: aRect "Return a new form which derives from the portion of the original form delineated by aRect." ^self contentsOfArea: aRect into: (self class extent: aRect extent depth: depth).! ! !Form methodsFor: 'copying' stamp: 'ar 6/9/2000 19:00'! contentsOfArea: aRect into: newForm "Return a new form which derives from the portion of the original form delineated by aRect." ^ newForm copyBits: aRect from: self at: 0@0 clippingBox: newForm boundingBox rule: Form over fillColor: nil! ! !Form methodsFor: 'copying'! copy: aRect "Return a new form which derives from the portion of the original form delineated by aRect." | newForm | newForm _ self class extent: aRect extent depth: depth. ^ newForm copyBits: aRect from: self at: 0@0 clippingBox: newForm boundingBox rule: Form over fillColor: nil! ! !Form methodsFor: 'copying' stamp: 'ar 5/28/2000 12:08'! copy: destRectangle from: sourcePt in: sourceForm rule: rule "Make up a BitBlt table and copy the bits." (BitBlt current toForm: self) copy: destRectangle from: sourcePt in: sourceForm fillColor: nil rule: rule! ! !Form methodsFor: 'copying'! copy: sourceRectangle from: sourceForm to: destPt rule: rule ^ self copy: (destPt extent: sourceRectangle extent) from: sourceRectangle topLeft in: sourceForm rule: rule! ! !Form methodsFor: 'copying' stamp: 'jm 2/27/98 09:35'! deepCopy ^ self shallowCopy bits: bits copy; offset: offset copy ! ! !Form methodsFor: 'copying' stamp: 'tk 8/19/1998 16:11'! veryDeepCopyWith: deepCopier "Return self. I am immutable in the Morphic world. Do not record me." ^ self! ! !Form methodsFor: 'displaying' stamp: 'di 7/1/97 14:06'! colormapIfNeededForDepth: destDepth "Return a colormap for displaying the receiver at the given depth, or nil if no colormap is needed." depth = destDepth ifTrue: [^ nil]. "not needed if depths are the same" ^ Color colorMapIfNeededFrom: depth to: destDepth ! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! copyBits: sourceForm at: destOrigin translucent: factor "Make up a BitBlt table and copy the bits with the given colorMap." (BitBlt current destForm: self sourceForm: sourceForm halftoneForm: nil combinationRule: 30 destOrigin: destOrigin sourceOrigin: 0@0 extent: sourceForm extent clipRect: self boundingBox) copyBitsTranslucent: ((0 max: (factor*255.0) asInteger) min: 255) " | f f2 f3 | f _ Form fromUser. f2 _ Form fromDisplay: (0@0 extent: f extent). f3 _ f2 deepCopy. 0.0 to: 1.0 by: 1.0/32 do: [:t | f3 _ f2 deepCopy. f3 copyBits: f at: 0@0 translucent: t. f3 displayAt: 0@0. (Delay forMilliseconds: 100) wait]. "! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! copyBits: sourceRect from: sourceForm at: destOrigin clippingBox: clipRect rule: rule fillColor: aForm "Make up a BitBlt table and copy the bits." (BitBlt current destForm: self sourceForm: sourceForm fillColor: aForm combinationRule: rule destOrigin: destOrigin sourceOrigin: sourceRect origin extent: sourceRect extent clipRect: clipRect) copyBits! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! copyBits: sourceRect from: sourceForm at: destOrigin clippingBox: clipRect rule: rule fillColor: aForm map: map "Make up a BitBlt table and copy the bits. Use a colorMap." ((BitBlt current destForm: self sourceForm: sourceForm fillColor: aForm combinationRule: rule destOrigin: destOrigin sourceOrigin: sourceRect origin extent: sourceRect extent clipRect: clipRect) colorMap: map) copyBits! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! copyBits: sourceRect from: sourceForm at: destOrigin colorMap: map "Make up a BitBlt table and copy the bits with the given colorMap." ((BitBlt current destForm: self sourceForm: sourceForm halftoneForm: nil combinationRule: Form over destOrigin: destOrigin sourceOrigin: sourceRect origin extent: sourceRect extent clipRect: self boundingBox) colorMap: map) copyBits! ! !Form methodsFor: 'displaying' stamp: 'RAA 11/21/2000 18:28'! displayInterpolatedIn: aRectangle on: aForm "Display the receiver on aForm, using interpolation if necessary. Form fromUser displayInterpolatedOn: Display. Note: When scaling we attempt to use bilinear interpolation based on the 3D engine. If the engine is not there then we use WarpBlt. " | engine adjustedR | self extent = aRectangle extent ifTrue:[^self displayOn: aForm at: aRectangle origin]. Smalltalk at: #B3DRenderEngine ifPresent:[:engineClass| engine _ (engineClass defaultForPlatformOn: aForm)]. engine ifNil:[ "We've got no bilinear interpolation. Use WarpBlt instead" (WarpBlt current toForm: aForm) sourceForm: self destRect: aRectangle; combinationRule: 3; cellSize: 2; warpBits. ^self ]. "Otherwise use the 3D engine for our purposes" "there seems to be a slight bug in B3D which the following adjusts for" adjustedR _ (aRectangle withRight: aRectangle right + 1) translateBy: 0@1. engine viewport: adjustedR. engine material: (B3DMaterial new emission: Color white). engine texture: self. engine render: (B3DIndexedQuadMesh new plainTextureRect). engine finish.! ! !Form methodsFor: 'displaying' stamp: 'ar 6/9/2000 19:13'! displayInterpolatedOn: aForm "Display the receiver on aForm, using interpolation if necessary. Form fromUser displayInterpolatedOn: Display. Note: When scaling we attempt to use bilinear interpolation based on the 3D engine. If the engine is not there then we use WarpBlt. " | engine | self extent = aForm extent ifTrue:[^self displayOn: aForm]. Smalltalk at: #B3DRenderEngine ifPresent:[:engineClass| engine _ (engineClass defaultForPlatformOn: aForm)]. engine ifNil:[ "We've got no bilinear interpolation. Use WarpBlt instead" (WarpBlt current toForm: aForm) sourceForm: self destRect: aForm boundingBox; combinationRule: 3; cellSize: 2; warpBits. ^self ]. "Otherwise use the 3D engine for our purposes" engine viewport: aForm boundingBox. engine material: (B3DMaterial new emission: Color white). engine texture: self. engine render: (B3DIndexedQuadMesh new plainTextureRect). engine finish.! ! !Form methodsFor: 'displaying'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: rule fillColor: aForm aDisplayMedium copyBits: self boundingBox from: self at: aDisplayPoint + self offset clippingBox: clipRectangle rule: rule fillColor: aForm map: (self colormapIfNeededForDepth: aDisplayMedium depth). ! ! !Form methodsFor: 'displaying'! displayOn: aDisplayMedium transformation: displayTransformation clippingBox: clipRectangle align: alignmentPoint with: relativePoint rule: ruleInteger fillColor: aForm "Graphically, it means nothing to scale a Form by floating point values. Because scales and other display parameters are kept in floating point to minimize round off errors, we are forced in this routine to round off to the nearest integer." | absolutePoint scale magnifiedForm | absolutePoint _ displayTransformation applyTo: relativePoint. absolutePoint _ absolutePoint x asInteger @ absolutePoint y asInteger. displayTransformation noScale ifTrue: [magnifiedForm _ self] ifFalse: [scale _ displayTransformation scale. scale _ scale x @ scale y. (1@1 = scale) ifTrue: [scale _ nil. magnifiedForm _ self] ifFalse: [magnifiedForm _ self magnify: self boundingBox by: scale]]. magnifiedForm displayOn: aDisplayMedium at: absolutePoint - alignmentPoint clippingBox: clipRectangle rule: ruleInteger fillColor: aForm! ! !Form methodsFor: 'displaying'! displayOnPort: port at: location port copyForm: self to: location rule: Form over! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! drawLine: sourceForm from: beginPoint to: endPoint clippingBox: clipRect rule: anInteger fillColor: aForm "Refer to the comment in DisplayMedium|drawLine:from:to:clippingBox:rule:mask:." | dotSetter | "set up an instance of BitBlt for display" dotSetter _ BitBlt current destForm: self sourceForm: sourceForm fillColor: aForm combinationRule: anInteger destOrigin: beginPoint sourceOrigin: 0 @ 0 extent: sourceForm extent clipRect: clipRect. dotSetter drawFrom: beginPoint to: endPoint! ! !Form methodsFor: 'displaying' stamp: 'ar 5/28/2000 12:08'! paintBits: sourceForm at: destOrigin translucent: factor "Make up a BitBlt table and copy the bits with the given colorMap." (BitBlt current destForm: self sourceForm: sourceForm halftoneForm: nil combinationRule: 31 destOrigin: destOrigin sourceOrigin: 0@0 extent: sourceForm extent clipRect: self boundingBox) copyBitsTranslucent: ((0 max: (factor*255.0) asInteger) min: 255) " | f f2 f3 | f _ Form fromUser. f replaceColor: f peripheralColor withColor: Color transparent. f2 _ Form fromDisplay: (0@0 extent: f extent). f3 _ f2 deepCopy. 0.0 to: 1.0 by: 1.0/32 do: [:t | f3 _ f2 deepCopy. f3 paintBits: f at: 0@0 translucent: t. f3 displayAt: 0@0. (Delay forMilliseconds: 100) wait]. "! ! !Form methodsFor: 'displaying' stamp: 'di 9/12/2000 10:04'! setAsBackground "Set this form as a background image." | world newColor | Smalltalk isMorphic ifTrue: [world _ self currentWorld. newColor _ InfiniteForm with: self. self rememberCommand: (Command new cmdWording: 'set background to a picture'; undoTarget: world selector: #color: argument: world color; redoTarget: world selector: #color: argument: newColor). world color: newColor] ifFalse: [ScheduledControllers screenController model form: self. Display restoreAfter: []]! ! !Form methodsFor: 'display box access'! boundingBox ^ Rectangle origin: 0 @ 0 corner: width @ height! ! !Form methodsFor: 'display box access'! computeBoundingBox ^ Rectangle origin: 0 @ 0 corner: width @ height! ! !Form methodsFor: 'filling' stamp: 'di 2/19/1999 07:07'! anyShapeFill "Fill the interior of the outermost outlined region in the receiver, a 1-bit deep form. Typically the resulting form is used with fillShape:fillColor: to paint a solid color. See also convexShapeFill:" | shape | "Draw a seed line around the edge and fill inward from the outside." shape _ self findShapeAroundSeedBlock: [:f | f borderWidth: 1]. "Reverse so that this becomes solid in the middle" shape _ shape reverse. "Finally erase any bits from the original so the fill is only elsewhere" shape copy: shape boundingBox from: self to: 0@0 rule: Form erase. ^ shape! ! !Form methodsFor: 'filling'! bitPatternForDepth: suspectedDepth "Only called when a Form is being used as a fillColor. Use a Pattern or InfiniteForm instead for this purpose. Interpret me as an array of (32/depth) Color pixelValues. BitBlt aligns the first element of this array with the top scanline of the destinationForm, the second with the second, and so on, cycling through the color array as necessary. 6/18/96 tk" ^ self! ! !Form methodsFor: 'filling' stamp: 'tk 6/20/96'! colorAt: aPoint "Return the color in the pixel at the given point. " ^ Color colorFromPixelValue: (self pixelValueAt: aPoint) depth: depth ! ! !Form methodsFor: 'filling' stamp: 'tk 6/20/96'! colorAt: aPoint put: aColor "Store a Color into the pixel at coordinate aPoint. " self pixelValueAt: aPoint put: (aColor pixelValueForDepth: depth). "[Sensor anyButtonPressed] whileFalse: [Display colorAt: Sensor cursorPoint put: Color red]" ! ! !Form methodsFor: 'filling' stamp: 'di 9/11/1998 16:25'! convexShapeFill: aMask "Fill the interior of the outtermost outlined region in the receiver. The outlined region must not be concave by more than 90 degrees. Typically aMask is Color black, to produce a solid fill. then the resulting form is used with fillShape: to paint a solid color. See also anyShapeFill" | destForm tempForm | destForm _ Form extent: self extent. destForm fillBlack. tempForm _ Form extent: self extent. (0@0) fourNeighbors do: [:dir | "Smear self in all 4 directions, and AND the result" self displayOn: tempForm at: (0@0) - self offset. tempForm smear: dir distance: (dir dotProduct: tempForm extent) abs. tempForm displayOn: destForm at: 0@0 clippingBox: destForm boundingBox rule: Form and fillColor: nil]. destForm displayOn: self at: 0@0 clippingBox: self boundingBox rule: Form over fillColor: aMask! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:08'! fill: aRectangle rule: anInteger fillColor: aForm "Replace a rectangular area of the receiver with the pattern described by aForm according to the rule anInteger." (BitBlt current toForm: self) copy: aRectangle from: 0@0 in: nil fillColor: aForm rule: anInteger! ! !Form methodsFor: 'filling'! fillFromXColorBlock: colorBlock "Horizontal Gradient Fill. Supply relative x in [0.0 ... 1.0] to colorBlock, and paint each pixel with the color that comes back" | xRel | 0 to: width-1 do: [:x | xRel _ x asFloat / (width-1) asFloat. self fill: (x@0 extent: 1@height) fillColor: (colorBlock value: xRel)] " ((Form extent: 100@100 depth: Display depth) fillFromXColorBlock: [:x | Color r: x g: 0.0 b: 0.5]) display "! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:08'! fillFromXYColorBlock: colorBlock "General Gradient Fill. Supply relative x and y in [0.0 ... 1.0] to colorBlock, and paint each pixel with the color that comes back" | poker yRel xRel | poker _ BitBlt current bitPokerToForm: self. 0 to: height-1 do: [:y | yRel _ y asFloat / (height-1) asFloat. 0 to: width-1 do: [:x | xRel _ x asFloat / (width-1) asFloat. poker pixelAt: x@y put: ((colorBlock value: xRel value: yRel) pixelWordForDepth: depth)]] " | d | ((Form extent: 100@20 depth: Display depth) fillFromXYColorBlock: [:x :y | d _ 1.0 - (x - 0.5) abs - (y - 0.5) abs. Color r: d g: 0 b: 1.0-d]) display "! ! !Form methodsFor: 'filling'! fillFromYColorBlock: colorBlock "Vertical Gradient Fill. Supply relative y in [0.0 ... 1.0] to colorBlock, and paint each pixel with the color that comes back" | yRel | 0 to: height-1 do: [:y | yRel _ y asFloat / (height-1) asFloat. self fill: (0@y extent: width@1) fillColor: (colorBlock value: yRel)] " ((Form extent: 100@100 depth: Display depth) fillFromYColorBlock: [:y | Color r: y g: 0.0 b: 0.5]) display "! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:08'! findShapeAroundSeedBlock: seedBlock "Build a shape that is black in any region marked by seedBlock. SeedBlock will be supplied a form, in which to blacken various pixels as 'seeds'. Then the seeds are smeared until there is no change in the smear when it fills the region, ie, when smearing hits a black border and thus goes no further." | smearForm previousSmear all count smearPort | depth > 1 ifTrue: [self halt]. "Only meaningful for B/W forms." all _ self boundingBox. smearForm _ Form extent: self extent. smearPort _ BitBlt current toForm: smearForm. seedBlock value: smearForm. "Blacken seeds to be smeared" smearPort copyForm: self to: 0@0 rule: Form erase. "Clear any in black" previousSmear _ smearForm deepCopy. count _ 1. [count = 10 and: "check for no change every 10 smears" [count _ 1. previousSmear copy: all from: 0@0 in: smearForm rule: Form reverse. previousSmear isAllWhite]] whileFalse: [smearPort copyForm: smearForm to: 1@0 rule: Form under. smearPort copyForm: smearForm to: -1@0 rule: Form under. "After horiz smear, trim around the region border" smearPort copyForm: self to: 0@0 rule: Form erase. smearPort copyForm: smearForm to: 0@1 rule: Form under. smearPort copyForm: smearForm to: 0@-1 rule: Form under. "After vert smear, trim around the region border" smearPort copyForm: self to: 0@0 rule: Form erase. count _ count+1. count = 9 ifTrue: "Save penultimate smear for comparison" [previousSmear copy: all from: 0@0 in: smearForm rule: Form over]]. "Now paint the filled region in me with aHalftone" ^ smearForm! ! !Form methodsFor: 'filling' stamp: 'ar 12/19/2000 01:17'! floodFill2: aColor at: interiorPoint "Fill the shape (4-connected) at interiorPoint. The algorithm is based on Paul Heckbert's 'A Seed Fill Algorithm', Graphic Gems I, Academic Press, 1990. NOTE: This is a less optimized variant for flood filling which is precisely along the lines of Heckbert's algorithm. For almost all cases #floodFill:at: will be faster (see the comment there) but this method is left in both as reference and as a fallback if such a strange case is encountered in reality." | peeker poker stack old new x y top x1 x2 dy left goRight | peeker _ BitBlt current bitPeekerFromForm: self. poker _ BitBlt current bitPokerToForm: self. stack _ OrderedCollection new: 50. "read old pixel value" old _ peeker pixelAt: interiorPoint. "compute new value" new _ aColor pixelValueForDepth: self depth. old = new ifTrue:[^self]. "no point, is there?!!" x _ interiorPoint x. y _ interiorPoint y. (y >= 0 and:[y < height]) ifTrue:[ stack addLast: {y. x. x. 1}. "y, left, right, dy" stack addLast: {y+1. x. x. -1}]. [stack isEmpty] whileFalse:[ top _ stack removeLast. y _ top at: 1. x1 _ top at: 2. x2 _ top at: 3. dy _ top at: 4. y _ y + dy. "Segment of scanline (y-dy) for x1 <= x <= x2 was previously filled. Now explore adjacent pixels in scanline y." x _ x1. [x >= 0 and:[(peeker pixelAt: x@y) = old]] whileTrue:[ poker pixelAt: x@y put: new. x _ x - 1]. goRight _ x < x1. left _ x+1. (left < x1 and:[y-dy >= 0 and:[y-dy < height]]) ifTrue:[stack addLast: {y. left. x1-1. 0-dy}]. goRight ifTrue:[x _ x1 + 1]. [ goRight ifTrue:[ [x < width and:[(peeker pixelAt: x@y) = old]] whileTrue:[ poker pixelAt: x@y put: new. x _ x + 1]. (y+dy >= 0 and:[y+dy < height]) ifTrue:[stack addLast: {y. left. x-1. dy}]. (x > (x2+1) and:[y-dy >= 0 and:[y-dy >= 0]]) ifTrue:[stack addLast: {y. x2+1. x-1. 0-dy}]]. [(x _ x + 1) <= x2 and:[(peeker pixelAt: x@y) ~= old]] whileTrue. left _ x. goRight _ true. x <= x2] whileTrue. ]. ! ! !Form methodsFor: 'filling' stamp: 'ar 12/19/2000 16:37'! floodFill: aColor at: interiorPoint "Fill the shape (4-connected) at interiorPoint. The algorithm is based on Paul Heckbert's 'A Seed Fill Algorithm', Graphic Gems I, Academic Press, 1990. NOTE: This variant has been heavily optimized to prevent the overhead of repeated calls to BitBlt. Usually this is a really big winner but the runtime now depends a bit on the complexity of the shape to be filled. For extremely complex shapes (say, a Hilbert curve) with very few pixels to fill it can be slower than #floodFill2:at: since it needs to repeatedly read the source bits. However, in all practical cases I found this variant to be 15-20 times faster than anything else." | peeker poker stack old new x y top x1 x2 dy left goRight span spanBits w box debug | debug _ false. "set it to true to see the filling process" box _ interiorPoint extent: 1@1. span _ Form extent: width@1 depth: 32. span unhibernate. spanBits _ span bits. peeker _ BitBlt current toForm: span. peeker sourceForm: self; combinationRule: 3; width: width; height: 1. poker _ BitBlt current toForm: self. poker fillColor: aColor; combinationRule: 3; width: width; height: 1. stack _ OrderedCollection new: 50. "read old pixel value" peeker sourceOrigin: interiorPoint; destOrigin: interiorPoint x @ 0; width: 1; copyBits. old _ spanBits at: interiorPoint x + 1. "compute new value (accuracy is important since the algorithm will fail if old = new)" new _ self privateFloodFillValue: aColor. old = new ifTrue:[^box]. x _ interiorPoint x. y _ interiorPoint y. (y >= 0 and:[y < height]) ifTrue:[ stack addLast: {y. x. x. 1}. "y, left, right, dy" stack addLast: {y+1. x. x. -1}]. [stack isEmpty] whileFalse:[ debug ifTrue:[self displayOn: Display]. top _ stack removeLast. y _ top at: 1. x1 _ top at: 2. x2 _ top at: 3. dy _ top at: 4. y _ y + dy. debug ifTrue:[ (Line from: (x1-1)@y to: (x2+1)@y withForm: (Form extent: 1@1 depth: 8) fillWhite) displayOn: Display]. "Segment of scanline (y-dy) for x1 <= x <= x2 was previously filled. Now explore adjacent pixels in scanline y." peeker sourceOrigin: 0@y; destOrigin: 0@0; width: width; copyBits. "Note: above is necessary since we don't know where we'll end up filling" x _ x1. w _ 0. [x >= 0 and:[(spanBits at: x+1) = old]] whileTrue:[ w _ w + 1. x _ x - 1]. w > 0 ifTrue:[ "overwrite pixels" poker destOrigin: x+1@y; width: w; copyBits. box _ box quickMerge: ((x+1@y) extent: w@1)]. goRight _ x < x1. left _ x+1. (left < x1 and:[y-dy >= 0 and:[y-dy < height]]) ifTrue:[stack addLast: {y. left. x1-1. 0-dy}]. goRight ifTrue:[x _ x1 + 1]. [ goRight ifTrue:[ w _ 0. [x < width and:[(spanBits at: x+1) = old]] whileTrue:[ w _ w + 1. x _ x + 1]. w > 0 ifTrue:[ "overwrite pixels" poker destOrigin: (x-w)@y; width: w; copyBits. box _ box quickMerge: ((x-w@y) extent: w@1)]. (y+dy >= 0 and:[y+dy < height]) ifTrue:[stack addLast: {y. left. x-1. dy}]. (x > (x2+1) and:[y-dy >= 0 and:[y-dy >= 0]]) ifTrue:[stack addLast: {y. x2+1. x-1. 0-dy}]]. [(x _ x + 1) <= x2 and:[(spanBits at: x+1) ~= old]] whileTrue. left _ x. goRight _ true. x <= x2] whileTrue. ]. ^box! ! !Form methodsFor: 'filling' stamp: 'di 3/2/98 12:42'! isAllWhite "Answer whether all bits in the receiver are white (=0)." self unhibernate. 1 to: bits size do: [:i | (bits at: i) = 0 ifFalse: [^ false]]. ^ true! ! !Form methodsFor: 'filling'! isTransparentAt: aPoint "Return true if the receiver is transparent at the given point." depth = 1 ifTrue: [^ false]. "no transparency at depth 1" ^ (self pixelValueAt: aPoint) = (Color transparent pixelValueForDepth: depth) ! ! !Form methodsFor: 'filling'! makeBWForm: foregroundColor "Map this form into a B/W form with 1's in the foreground regions." | bwForm map | bwForm _ Form extent: self extent. map _ self newColorMap. "All non-foreground go to 0's" map at: (foregroundColor indexInMap: map) put: 1. bwForm copyBits: self boundingBox from: self at: 0@0 colorMap: map. ^ bwForm! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:08'! pixelValueAt: aPoint "Return the raw pixel value at the given point. This pixel value depends on the receiver's depth. Typical clients use colorAt: to get a Color. " ^ (BitBlt current bitPeekerFromForm: self) pixelAt: aPoint ! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:08'! pixelValueAt: aPoint put: pixelValue "Store the given raw pixel value at the given point. Typical clients use colorAt:put: to store a color. " (BitBlt current bitPokerToForm: self) pixelAt: aPoint put: pixelValue. ! ! !Form methodsFor: 'filling' stamp: 'ar 5/28/2000 12:09'! shapeFill: aColor interiorPoint: interiorPoint "Identify the shape (region of identical color) at interiorPoint, and then fill that shape with the new color, aColor : modified di's original method such that it returns the bwForm, for potential use by the caller" | bwForm interiorPixVal map ppd color ind | depth = 1 ifTrue: [^ self shapeFill: aColor seedBlock: [:form | form pixelValueAt: interiorPoint put: 1]]. "First map this form into a B/W form with 0's in the interior region." interiorPixVal _ self pixelValueAt: interiorPoint. bwForm _ Form extent: self extent. map _ Bitmap new: (1 bitShift: (depth min: 12)). "Not calling newColorMap. All non-foreground go to 0. Length is 2 to 4096." ppd _ depth. "256 long color map in depth 8 is not one of the following cases" 3 to: 5 do: [:bitsPerColor | (2 raisedTo: bitsPerColor*3) = map size ifTrue: [ppd _ bitsPerColor*3]]. "ready for longer maps than 512" ppd <= 8 ifTrue: [map at: interiorPixVal+1 put: 1] ifFalse: [interiorPixVal = 0 ifFalse: [color _ Color colorFromPixelValue: interiorPixVal depth: depth. ind _ color pixelValueForDepth: ppd. map at: ind+1 put: 1] ifTrue: [map at: 1 put: 1]]. bwForm copyBits: self boundingBox from: self at: 0@0 colorMap: map. "bwForm _ self makeBWForm: interiorColor." "not work for two whites" bwForm reverse. "Make interior region be 0's" "Now fill the interior region and return that shape" bwForm _ bwForm findShapeAroundSeedBlock: [:form | form pixelValueAt: interiorPoint put: 1]. "Finally use that shape as a mask to flood the region with color" ((BitBlt current destForm: self sourceForm: bwForm fillColor: nil combinationRule: Form erase1bitShape "Cut a hole in the picture with my mask" destOrigin: bwForm offset sourceOrigin: 0@0 extent: self extent clipRect: self boundingBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF)) copyBits. self fillShape: bwForm fillColor: aColor. ^ bwForm! ! !Form methodsFor: 'filling'! shapeFill: aColor seedBlock: seedBlock depth > 1 ifTrue: [self error: 'This call only meaningful for B/W forms']. (self findShapeAroundSeedBlock: seedBlock) displayOn: self at: 0@0 clippingBox: self boundingBox rule: Form under fillColor: aColor ! ! !Form methodsFor: 'bordering' stamp: 'ar 5/28/2000 12:07'! border: rect width: borderWidth rule: rule fillColor: fillColor "Paint a border whose rectangular area is defined by rect. The width of the border of each side is borderWidth. Uses fillColor for drawing the border." | blt | blt _ (BitBlt current toForm: self) combinationRule: rule; fillColor: fillColor. blt sourceOrigin: 0@0. blt destOrigin: rect origin. blt width: rect width; height: borderWidth; copyBits. blt destY: rect corner y - borderWidth; copyBits. blt destY: rect origin y + borderWidth. blt height: rect height - borderWidth - borderWidth; width: borderWidth; copyBits. blt destX: rect corner x - borderWidth; copyBits! ! !Form methodsFor: 'bordering' stamp: 'ar 5/28/2000 12:07'! borderFormOfWidth: borderWidth sharpCorners: sharpen "Smear this form around and then subtract the original to produce an outline. If sharpen is true, then cause right angles to be outlined by right angles (takes an additional diagonal smears ANDed with both horizontal and vertical smears)." | smearForm bigForm smearPort all cornerForm cornerPort nbrs | depth > 1 ifTrue: [self halt]. "Only meaningful for B/W forms." bigForm _ self deepCopy. all _ bigForm boundingBox. smearForm _ Form extent: self extent. smearPort _ BitBlt current toForm: smearForm. sharpen ifTrue: [cornerForm _ Form extent: self extent. cornerPort _ BitBlt current toForm: cornerForm]. nbrs _ (0@0) fourNeighbors. 1 to: borderWidth do: [:i | "Iterate to get several layers of 'skin'" nbrs do: [:d | "Smear the self in 4 directions to grow each layer of skin" smearPort copyForm: bigForm to: d rule: Form under]. sharpen ifTrue: ["Special treatment to smear sharp corners" nbrs with: ((2 to: 5) collect: [:i2 | nbrs atWrap: i2]) do: [:d1 :d2 | "Copy corner points diagonally" cornerPort copyForm: bigForm to: d1+d2 rule: Form over. "But only preserve if there were dots on either side" cornerPort copyForm: bigForm to: d1+d1+d2 rule: Form and. cornerPort copyForm: bigForm to: d1+d2+d2 rule: Form and. smearPort copyForm: cornerForm to: 0@0 rule: Form under]. ]. bigForm copy: all from: 0@0 in: smearForm rule: Form over. ]. "Now erase the original shape to obtain the outline" bigForm copy: all from: 0@0 in: self rule: Form erase. ^ bigForm! ! !Form methodsFor: 'bordering'! borderWidth: anInteger "Set the width of the border for the receiver to be anInteger and paint it using black as the border color." self border: self boundingBox width: anInteger fillColor: Color black! ! !Form methodsFor: 'bordering'! borderWidth: anInteger color: aMask "Set the width of the border for the receiver to be anInteger and paint it using aMask as the border color." self border: self boundingBox width: anInteger fillColor: aMask! ! !Form methodsFor: 'bordering'! borderWidth: anInteger fillColor: aMask "Set the width of the border for the receiver to be anInteger and paint it using aMask as the border color." self border: self boundingBox width: anInteger fillColor: aMask! ! !Form methodsFor: 'bordering'! shapeBorder: aColor width: borderWidth interiorPoint: interiorPoint sharpCorners: sharpen internal: internal "Identify the shape (region of identical color) at interiorPoint, and then add an outline of width=borderWidth and color=aColor. If sharpen is true, then cause right angles to be outlined by right angles. If internal is true, then produce a border that lies within the identified shape. Thus one can put an internal border around the whole background, thus effecting a normal border around every other foreground image." | shapeForm borderForm interiorColor | "First identify the shape in question as a B/W form" interiorColor _ self colorAt: interiorPoint. shapeForm _ (self makeBWForm: interiorColor) reverse findShapeAroundSeedBlock: [:form | form pixelValueAt: interiorPoint put: 1]. "Reverse the image to grow the outline inward" internal ifTrue: [shapeForm reverse]. "Now find the border fo that shape" borderForm _ shapeForm borderFormOfWidth: borderWidth sharpCorners: sharpen. "Finally use that shape as a mask to paint the border with color" self fillShape: borderForm fillColor: aColor! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/28/2000 12:12'! flipBy: direction centerAt: aPoint "Return a copy of the receiver flipped either #vertical or #horizontal." | newForm quad | newForm _ self class extent: self extent depth: depth. quad _ self boundingBox innerCorners. quad _ (direction = #vertical ifTrue: [#(2 1 4 3)] ifFalse: [#(4 3 2 1)]) collect: [:i | quad at: i]. (WarpBlt current toForm: newForm) sourceForm: self; colorMap: (self colormapIfNeededForDepth: depth); combinationRule: 3; copyQuad: quad toRect: newForm boundingBox. newForm offset: (self offset flipBy: direction centerAt: aPoint). ^ newForm " [Sensor anyButtonPressed] whileFalse: [((Form fromDisplay: (Sensor cursorPoint extent: 130@66)) flipBy: #vertical centerAt: 0@0) display] " "Consistency test... | f f2 p | [Sensor anyButtonPressed] whileFalse: [f _ Form fromDisplay: ((p _ Sensor cursorPoint) extent: 31@41). Display fillBlack: (p extent: 31@41). f2 _ f flipBy: #vertical centerAt: 0@0. (f2 flipBy: #vertical centerAt: 0@0) displayAt: p] " ! ! !Form methodsFor: 'scaling, rotation'! magnify: aRectangle by: scale "Answer a Form created as a scaling of the receiver. Scale may be a Float, and may be greater or less than 1.0." ^ self magnify: aRectangle by: scale smoothing: 1 "Dynamic test... [Sensor anyButtonPressed] whileFalse: [(Display magnify: (Sensor cursorPoint extent: 31@41) by: 5@3) display] " "Scaling test... | f cp | f _ Form fromDisplay: (Rectangle originFromUser: 100@100). Display restoreAfter: [Sensor waitNoButton. [Sensor anyButtonPressed] whileFalse: [cp _ Sensor cursorPoint. (f magnify: f boundingBox by: (cp x asFloat@cp y asFloat)/f extent) display]] " "Consistency test... | f f2 p | [Sensor anyButtonPressed] whileFalse: [f _ Form fromDisplay: ((p _ Sensor cursorPoint) extent: 31@41). Display fillBlack: (p extent: 31@41). f2 _ f magnify: f boundingBox by: 5@3. (f2 shrink: f2 boundingBox by: 5@3) displayAt: p] " ! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/28/2000 12:12'! magnify: aRectangle by: scale smoothing: cellSize "Answer a Form created as a scaling of the receiver. Scale may be a Float, and may be greater or less than 1.0." | newForm | newForm _ self blankCopyOf: aRectangle scaledBy: scale. (WarpBlt current toForm: newForm) sourceForm: self; colorMap: (self colormapIfNeededForDepth: depth); cellSize: cellSize; "installs a new colormap if cellSize > 1" combinationRule: 3; copyQuad: aRectangle innerCorners toRect: newForm boundingBox. ^ newForm "Dynamic test... [Sensor anyButtonPressed] whileFalse: [(Display magnify: (Sensor cursorPoint extent: 131@81) by: 0.5 smoothing: 2) display] " "Scaling test... | f cp | f _ Form fromDisplay: (Rectangle originFromUser: 100@100). Display restoreAfter: [Sensor waitNoButton. [Sensor anyButtonPressed] whileFalse: [cp _ Sensor cursorPoint. (f magnify: f boundingBox by: (cp x asFloat@cp y asFloat)/f extent smoothing: 2) display]] "! ! !Form methodsFor: 'scaling, rotation' stamp: 'di 8/17/1998 22:17'! magnifyBy: scale "Answer a Form created as a scaling of the receiver. Scale may be a Float, and may be greater or less than 1.0." ^ self magnify: self boundingBox by: scale smoothing: (scale < 1 ifTrue: [2] ifFalse: [1])! ! !Form methodsFor: 'scaling, rotation'! rotateBy: deg "Rotate the receiver by the indicated number of degrees." "rot is the destination form, bit enough for any angle." ^ self rotateBy: deg smoothing: 1 " | a f | f _ Form fromDisplay: (0@0 extent: 200@200). a _ 0. [Sensor anyButtonPressed] whileFalse: [((Form fromDisplay: (Sensor cursorPoint extent: 130@66)) rotateBy: (a _ a+5)) display]. f display "! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/28/2000 12:12'! rotateBy: direction centerAt: aPoint "Return a rotated copy of the receiver. direction = #none, #right, #left, or #pi" | newForm quad rot | direction == #none ifTrue: [^ self]. newForm _ self class extent: (direction = #pi ifTrue: [width@height] ifFalse: [height@width]) depth: depth. quad _ self boundingBox innerCorners. rot _ #(right pi left) indexOf: direction. (WarpBlt current toForm: newForm) sourceForm: self; colorMap: (self colormapIfNeededForDepth: depth); combinationRule: 3; copyQuad: ((1+rot to: 4+rot) collect: [:i | quad atWrap: i]) toRect: newForm boundingBox. newForm offset: (self offset rotateBy: direction centerAt: aPoint). ^ newForm " [Sensor anyButtonPressed] whileFalse: [((Form fromDisplay: (Sensor cursorPoint extent: 130@66)) rotateBy: #left centerAt: 0@0) display] " "Consistency test... | f f2 p | [Sensor anyButtonPressed] whileFalse: [f _ Form fromDisplay: ((p _ Sensor cursorPoint) extent: 31@41). Display fillBlack: (p extent: 31@41). f2 _ f rotateBy: #left centerAt: 0@0. (f2 rotateBy: #right centerAt: 0@0) displayAt: p] " ! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/28/2000 12:12'! rotateBy: deg magnify: scale smoothing: cellSize "Rotate the receiver by the indicated number of degrees and magnify. " "rot is the destination form, big enough for any angle." | side rot warp r1 pts p bigSide | side _ 1 + ((width*width) + (height*height)) asFloat sqrt asInteger. bigSide _ (side * scale) rounded. rot _ Form extent: bigSide@bigSide depth: self depth. warp _ (WarpBlt current toForm: rot) sourceForm: self; colorMap: (self colormapIfNeededForDepth: depth); cellSize: cellSize; "installs a new colormap if cellSize > 1" combinationRule: Form paint. r1 _ (0@0 extent: side@side) align: (side@side)//2 with: self boundingBox center. "Rotate the corners of the source rectangle." pts _ r1 innerCorners collect: [:pt | p _ pt - r1 center. (r1 center x asFloat + (p x asFloat*deg degreeCos) + (p y asFloat*deg degreeSin)) @ (r1 center y asFloat - (p x asFloat*deg degreeSin) + (p y asFloat*deg degreeCos))]. warp copyQuad: pts toRect: rot boundingBox. ^ rot " | a f | f _ Form fromDisplay: (0@0 extent: 200@200). a _ 0. [Sensor anyButtonPressed] whileFalse: [((Form fromDisplay: (Sensor cursorPoint extent: 130@66)) rotateBy: (a _ a+5) magnify: 0.75 smoothing: 2) display]. f display "! ! !Form methodsFor: 'scaling, rotation' stamp: 'sw 8/9/96'! rotateBy: deg rotationCenter: aPoint "Rotate the receiver by the indicated number of degrees. This variant gets a rotation center, but in fact ignores the thing -- awaiting someone's doing the right thing. Note that rotationCenter should now be easy to include in the offset of the resulting form -- see rotateBy: angle about: center. Could be even faster by sharing the sin, cos inside rotateBy:. This should really be reversed so that this becomes the workhorse, and rotateBy: calls this with rotationCenter: self boundingBox center. And while we're at it, why not include scaling? " ^ self rotateBy: deg smoothing: 1! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/28/2000 12:12'! rotateBy: deg smoothing: cellSize "Rotate the receiver by the indicated number of degrees." "rot is the destination form, bit enough for any angle." | side rot warp r1 pts p center | side _ 1 + ((width*width) + (height*height)) asFloat sqrt asInteger. rot _ Form extent: side@side depth: self depth. center _ rot extent // 2. "Now compute the sin and cos constants for the rotation angle." warp _ (WarpBlt current toForm: rot) sourceForm: self; colorMap: (self colormapIfNeededForDepth: depth); cellSize: cellSize; "installs a new colormap if cellSize > 1" combinationRule: Form over. r1 _ rot boundingBox align: center with: self boundingBox center. pts _ r1 innerCorners collect: [:pt | p _ pt - r1 center. (r1 center x asFloat + (p x asFloat*deg degreeCos) + (p y asFloat*deg degreeSin)) @ (r1 center y asFloat - (p x asFloat*deg degreeSin) + (p y asFloat*deg degreeCos))]. warp copyQuad: pts toRect: rot boundingBox. ^ rot " | a f | f _ Form fromDisplay: (0@0 extent: 200@200). a _ 0. [Sensor anyButtonPressed] whileFalse: [((Form fromDisplay: (Sensor cursorPoint extent: 130@66)) rotateBy: (a _ a+5) smoothing: 2) display]. f display "! ! !Form methodsFor: 'scaling, rotation' stamp: 'RAA 7/13/2000 12:09'! scaledToSize: newExtent | scale | newExtent = self extent ifTrue: [^self]. scale _ newExtent x / self width min: newExtent y / self height. ^self magnify: self boundingBox by: scale smoothing: 2. ! ! !Form methodsFor: 'scaling, rotation'! shrink: aRectangle by: scale | scalePt | scalePt _ scale asPoint. ^ self magnify: aRectangle by: (1.0 / scalePt x asFloat) @ (1.0 / scalePt y asFloat)! ! !Form methodsFor: 'editing' stamp: 'bf 10/11/1999 15:38'! bitEdit "Create and schedule a view located in an area designated by the user that contains a view of the receiver magnified by 8@8 that can be modified using the Bit Editor. It also contains a view of the original form." Smalltalk isMorphic ifFalse: [BitEditor openOnForm: self] ifTrue: [self currentHand attachMorph: (FatBitsPaint new editForm: self; magnification: 8; brushColor: Color black; penSize: 1; yourself)]. "Note that using direct messages to BitEditor, fixed locations and scales can be created. That is, also try: BitEditor openOnForm: self at: BitEditor openOnForm: self at: scale: "! ! !Form methodsFor: 'editing'! bitEditAt: magnifiedFormLocation scale: scaleFactor "Create and schedule a view whose top left corner is magnifiedLocation and that contains a view of the receiver magnified by scaleFactor that can be modified using the Bit Editor. It also contains a view of the original form." BitEditor openOnForm: self at: magnifiedFormLocation scale: scaleFactor ! ! !Form methodsFor: 'editing'! edit "Start up an instance of the FormEditor on this form. Typically the form is not visible on the screen. The editor menu is located at the bottom of the form editing frame. The form is displayed centered in the frame. YellowButtonMenu accept is used to modify the form to reflect the changes made on the screen version; cancel restores the original form to the screen. Note that the changes are clipped to the original size of the form." FormEditor openOnForm: self! ! !Form methodsFor: 'editing' stamp: 'RAA 9/28/1999 09:11'! morphEdit ^ FatBitsPaint new openWith: self! ! !Form methodsFor: 'image manipulation'! cgForPixelValue: pv orNot: not "Return the center of gravity for all pixels of value pv. Note: If orNot is true, then produce the center of gravity for all pixels that are DIFFERENT from the supplied (background) value" | pixCount weighted xAndY | xAndY _ (Array with: (self xTallyPixelValue: pv orNot: not) with: (self yTallyPixelValue: pv orNot: not)) collect: [:profile | "For both x and y profiles..." pixCount _ 0. weighted _ 0. profile doWithIndex: [:t :i | pixCount _ pixCount + t. weighted _ weighted + (t*i)]. pixCount = 0 "Produce average of nPixels weighted by coordinate" ifTrue: [0.0] ifFalse: [weighted asFloat / pixCount asFloat - 1.0]]. ^ xAndY first @ xAndY last " | f cg | [Sensor anyButtonPressed] whileFalse: [f _ Form fromDisplay: (Sensor cursorPoint extent: 50@50). cg _ f cgForPixelValue: (Color black pixelValueForDepth: f depth) orNot: false. f displayAt: 0@0. Display fill: (cg extent: 2@2) fillColor: Color red]. ScheduledControllers restore "! ! !Form methodsFor: 'image manipulation' stamp: 'bf 10/12/1999 18:07'! dominantColor | tally max maxi | depth > 16 ifTrue: [^(self asFormOfDepth: 16) dominantColor]. tally _ self tallyPixelValues. max _ maxi _ 0. tally withIndexDo: [:n :i | n > max ifTrue: [max _ n. maxi _ i]]. ^ Color colorFromPixelValue: maxi - 1 depth: depth! ! !Form methodsFor: 'image manipulation'! innerPixelRectFor: pv orNot: not "Return a rectangle describing the smallest part of me that includes all pixels of value pv. Note: If orNot is true, then produce a copy that includes all pixels that are DIFFERENT from the supplied (background) value" | xTally yTally | xTally _ self xTallyPixelValue: pv orNot: not. yTally _ self yTallyPixelValue: pv orNot: not. ^ ((xTally findFirst: [:t | t>0]) - 1) @ ((yTally findFirst: [:t | t>0]) - 1) corner: (xTally findLast: [:t | t>0])@(yTally findLast: [:t | t>0])! ! !Form methodsFor: 'image manipulation' stamp: 'ar 7/23/1999 17:04'! orderedDither32To16 "Do an ordered dithering for converting from 32 to 16 bit depth." | ditherMatrix ii out inBits outBits index pv dmv r di dmi dmo g b pvOut outIndex | self depth = 32 ifFalse:[^self error:'Must be 32bit for this']. ditherMatrix _ #( 0 8 2 10 12 4 14 6 3 11 1 9 15 7 13 5). ii _ (0 to: 31) collect:[:i| i]. out _ Form extent: self extent depth: 16. inBits _ self bits. outBits _ out bits. index _ outIndex _ 0. pvOut _ 0. 0 to: self height-1 do:[:y| 0 to: self width-1 do:[:x| pv _ inBits at: (index _ index + 1). dmv _ ditherMatrix at: (y bitAnd: 3) * 4 + (x bitAnd: 3) + 1. r _ pv bitAnd: 255. di _ r * 496 bitShift: -8. dmi _ di bitAnd: 15. dmo _ di bitShift: -4. r _ dmv < dmi ifTrue:[ii at: 2+dmo] ifFalse:[ii at: 1+dmo]. g _ (pv bitShift: -8) bitAnd: 255. di _ g * 496 bitShift: -8. dmi _ di bitAnd: 15. dmo _ di bitShift: -4. g _ dmv < dmi ifTrue:[ii at: 2+dmo] ifFalse:[ii at: 1+dmo]. b _ (pv bitShift: -16) bitAnd: 255. di _ b * 496 bitShift: -8. dmi _ di bitAnd: 15. dmo _ di bitShift: -4. b _ dmv < dmi ifTrue:[ii at: 2+dmo] ifFalse:[ii at: 1+dmo]. pvOut _ (pvOut bitShift: 16) + (b bitShift: 10) + (g bitShift: 5) + r. (x bitAnd: 1) = 1 ifTrue:[ outBits at: (outIndex _ outIndex+1) put: pvOut. pvOut _ 0]. ]. (self width bitAnd: 1) = 1 ifTrue:[ outBits at: (outIndex _ outIndex+1) put: (pvOut bitShift: -16). pvOut _ 0]. ]. ^out! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:08'! pixelCompare: aRect with: otherForm at: otherLoc "Compare the selected bits of this form (those within aRect) against those in a similar rectangle of otherFrom. Return the sum of the absolute value of the differences of the color values of every pixel. Obviously, this is most useful for rgb (16- or 32-bit) pixels but, in the case of 8-bits or less, this will return the sum of the differing bits of the corresponding pixel values (somewhat less useful)" | pixPerWord temp | pixPerWord _ 32//depth. (aRect left\\pixPerWord = 0 and: [aRect right\\pixPerWord = 0]) ifTrue: ["If word-aligned, use on-the-fly difference" ^ (BitBlt current toForm: self) copy: aRect from: otherLoc in: otherForm fillColor: nil rule: 32]. "Otherwise, combine in a word-sized form and then compute difference" temp _ self copy: aRect. temp copy: aRect from: otherLoc in: otherForm rule: 21. ^ (BitBlt current toForm: temp) copy: aRect from: otherLoc in: nil fillColor: (Bitmap with: 0) rule: 32 " Dumb example prints zero only when you move over the original rectangle... | f diff | f _ Form fromUser. [Sensor anyButtonPressed] whileFalse: [diff _ f pixelCompare: f boundingBox with: Display at: Sensor cursorPoint. diff printString , ' ' displayAt: 0@0] "! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:08'! primCountBits "Count the non-zero pixels of this form." depth > 8 ifTrue: [^(self asFormOfDepth: 8) primCountBits]. ^ (BitBlt current toForm: self) fillColor: (Bitmap with: 0); destRect: (0@0 extent: width@height); combinationRule: 32; copyBits! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! rectangleEnclosingPixelsNotOfColor: aColor "Answer the smallest rectangle enclosing all the pixels of me that are different from the given color. Useful for extracting a foreground graphic from its background." | cm slice copyBlt countBlt top bottom newH left right | "map the specified color to 1 and all others to 0" cm _ Bitmap new: (1 bitShift: (depth min: 15)). cm primFill: 1. cm at: (aColor indexInMap: cm) put: 0. cm _ ColorMap colors: cm. "build a 1-pixel high horizontal slice and BitBlts for counting pixels of interest" slice _ Form extent: width@1 depth: 1. copyBlt _ (BitBlt current toForm: slice) sourceForm: self; combinationRule: Form over; destX: 0 destY: 0 width: width height: 1; colorMap: cm. countBlt _ (BitBlt current toForm: slice) fillColor: (Bitmap with: 0); destRect: (0@0 extent: slice extent); combinationRule: 32. "scan in from top and bottom" top _ (0 to: height) detect: [:y | copyBlt sourceOrigin: 0@y; copyBits. countBlt copyBits > 0] ifNone: [^ 0@0 extent: 0@0]. bottom _ (height - 1 to: top by: -1) detect: [:y | copyBlt sourceOrigin: 0@y; copyBits. countBlt copyBits > 0]. "build a 1-pixel wide vertical slice and BitBlts for counting pixels of interest" newH _ bottom - top + 1. slice _ Form extent: 1@newH depth: 1. copyBlt _ (BitBlt current toForm: slice) sourceForm: self; combinationRule: Form over; destX: 0 destY: 0 width: 1 height: newH; colorMap: cm. countBlt _ (BitBlt current toForm: slice) fillColor: (Bitmap with: 0); destRect: (0@0 extent: slice extent); combinationRule: 32. "scan in from left and right" left _ (0 to: width) detect: [:x | copyBlt sourceOrigin: x@top; copyBits. countBlt copyBits > 0]. right _ (width - 1 to: left by: -1) detect: [:x | copyBlt sourceOrigin: x@top; copyBits. countBlt copyBits > 0]. ^ left@top corner: (right + 1)@(bottom + 1) ! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! replaceColor: oldColor withColor: newColor "Replace one color with another everywhere is this form" | cm newInd target ff | depth = 32 ifTrue: [cm _ (Color cachedColormapFrom: 16 to: 32) copy] ifFalse: [cm _ Bitmap new: (1 bitShift: (depth min: 15)). 1 to: cm size do: [:i | cm at: i put: i - 1]]. newInd _ newColor pixelValueForDepth: depth. cm at: (oldColor pixelValueForDepth: (depth min: 16))+1 put: newInd. target _ newColor isTransparent ifTrue: [ff _ Form extent: self extent depth: depth. ff fillWithColor: newColor. ff] ifFalse: [self]. (BitBlt current toForm: target) sourceForm: self; sourceOrigin: 0@0; combinationRule: Form paint; destX: 0 destY: 0 width: width height: height; colorMap: cm; copyBits. newColor = Color transparent ifTrue: [target displayOn: self].! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! smear: dir distance: dist "Smear any black pixels in this form in the direction dir in Log N steps" | skew bb | bb _ BitBlt current destForm: self sourceForm: self fillColor: nil combinationRule: Form under destOrigin: 0@0 sourceOrigin: 0@0 extent: self extent clipRect: self boundingBox. skew _ 1. [skew < dist] whileTrue: [bb destOrigin: dir*skew; copyBits. skew _ skew+skew]! ! !Form methodsFor: 'image manipulation' stamp: 'jm 6/18/1999 18:41'! tallyPixelValues "Answer a Bitmap whose elements contain the number of pixels in this Form with the pixel value corresponding to their index. Note that the pixels of multiple Forms can be tallied together using tallyPixelValuesInRect:into:." ^ self tallyPixelValuesInRect: self boundingBox into: (Bitmap new: (1 bitShift: (self depth min: 15))) " Move a little rectangle around the screen and print its tallies... | r tallies nonZero | Cursor blank showWhile: [ [Sensor anyButtonPressed] whileFalse: [r _ Sensor cursorPoint extent: 10@10. Display border: (r expandBy: 2) width: 2 rule: Form reverse fillColor: nil. tallies _ (Display copy: r) tallyPixelValues. nonZero _ (1 to: tallies size) select: [:i | (tallies at: i) > 0] thenCollect: [:i | (tallies at: i) -> (i-1)]. nonZero printString , ' ' displayAt: 0@0. Display border: (r expandBy: 2) width: 2 rule: Form reverse fillColor: nil]] " ! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! tallyPixelValuesInRect: destRect into: valueTable "Tally the selected pixels of this Form into valueTable, a Bitmap of depth 2^depth similar to a color map. Answer valueTable." (BitBlt current toForm: self) sourceForm: self; "src must be given for color map ops" sourceOrigin: 0@0; tallyMap: valueTable; combinationRule: 33; destRect: destRect; copyBits. ^ valueTable " Move a little rectangle around the screen and print its tallies... | r tallies nonZero | Cursor blank showWhile: [ [Sensor anyButtonPressed] whileFalse: [r _ Sensor cursorPoint extent: 10@10. Display border: (r expandBy: 2) width: 2 rule: Form reverse fillColor: nil. tallies _ (Display copy: r) tallyPixelValues. nonZero _ (1 to: tallies size) select: [:i | (tallies at: i) > 0] thenCollect: [:i | (tallies at: i) -> (i-1)]. nonZero printString , ' ' displayAt: 0@0. Display border: (r expandBy: 2) width: 2 rule: Form reverse fillColor: nil]] "! ! !Form methodsFor: 'image manipulation' stamp: 'jm 6/30/1999 15:36'! trimBordersOfColor: aColor "Answer a copy of this Form with each edge trimmed in to the first pixel that is not of the given color. (That is, border strips of the given color are removed)." | r | r _ self rectangleEnclosingPixelsNotOfColor: aColor. ^ self copy: r ! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! xTallyPixelValue: pv orNot: not "Return an array of the number of pixels with value pv by x-value. Note that if not is true, then this will tally those different from pv." | cm slice countBlt copyBlt | cm _ self newColorMap. "Map all colors but pv to zero" not ifTrue: [cm atAllPut: 1]. "... or all but pv to one" cm at: pv+1 put: 1 - (cm at: pv+1). slice _ Form extent: 1@height. copyBlt _ (BitBlt current destForm: slice sourceForm: self halftoneForm: nil combinationRule: Form over destOrigin: 0@0 sourceOrigin: 0@0 extent: 1 @ slice height clipRect: slice boundingBox) colorMap: cm. countBlt _ (BitBlt current toForm: slice) fillColor: (Bitmap with: 0); destRect: (0@0 extent: slice extent); combinationRule: 32. ^ (0 to: width-1) collect: [:x | copyBlt sourceOrigin: x@0; copyBits. countBlt copyBits]! ! !Form methodsFor: 'image manipulation' stamp: 'ar 5/28/2000 12:09'! yTallyPixelValue: pv orNot: not "Return an array of the number of pixels with value pv by y-value. Note that if not is true, then this will tally those different from pv." | cm slice copyBlt countBlt | cm _ self newColorMap. "Map all colors but pv to zero" not ifTrue: [cm atAllPut: 1]. "... or all but pv to one" cm at: pv+1 put: 1 - (cm at: pv+1). slice _ Form extent: width@1. copyBlt _ (BitBlt current destForm: slice sourceForm: self halftoneForm: nil combinationRule: Form over destOrigin: 0@0 sourceOrigin: 0@0 extent: slice width @ 1 clipRect: slice boundingBox) colorMap: cm. countBlt _ (BitBlt current toForm: slice) fillColor: (Bitmap with: 0); destRect: (0@0 extent: slice extent); combinationRule: 32. ^ (0 to: height-1) collect: [:y | copyBlt sourceOrigin: 0@y; copyBits. countBlt copyBits]! ! !Form methodsFor: 'fileIn/Out' stamp: 'RAA 8/13/2000 15:32'! encodeForRemoteCanvas | header binaryForm | "encode into a bitstream for use with RemoteCanvas. The format does not require invoking the Compiler" header := String streamContents: [ :str | str "nextPutAll: 'F|';" nextPutAll: self depth printString; nextPut: $,; nextPutAll: self width printString; nextPut: $,; nextPutAll: self height printString; nextPut: $|. ]. binaryForm := ByteArray streamContents: [ :str | self unhibernate. bits writeOn: str. ]. ^header, binaryForm asString ! ! !Form methodsFor: 'fileIn/Out' stamp: 'di 8/5/1998 11:37'! hibernate "Replace my bitmap with a compactly encoded representation (a ByteArray). It is vital that BitBlt and any other access to the bitmap (such as writing to a file) not be used when in this state. Since BitBlt will fail if the bitmap size is wrong (not = bitsSize), we do not allow replacement by a byteArray of the same (or larger) size." "NOTE: This method copies code from Bitmap compressToByteArray so that it can nil out the old bits during the copy, thus avoiding 2x need for extra storage." | compactBits lastByte | (bits isMemberOf: Bitmap) ifFalse: [^ self "already hibernated or weird state"]. compactBits _ ByteArray new: (bits size*4) + 7 + (bits size//1984*3). lastByte _ bits compress: bits toByteArray: compactBits. lastByte < (bits size*4) ifTrue: [bits _ nil. "Let GC reclaim the old bits before the copy if necessary" bits _ compactBits copyFrom: 1 to: lastByte]! ! !Form methodsFor: 'fileIn/Out' stamp: 'di 3/15/1999 14:50'! printOn: aStream aStream nextPutAll: self class name; nextPut: $(; print: width; nextPut: $x; print: height; nextPut: $x; print: depth; nextPut: $). ! ! !Form methodsFor: 'fileIn/Out' stamp: 'jm 3/27/98 14:25'! readFrom: aBinaryStream "Reads the receiver from the given binary stream with the format: depth, extent, offset, bits." | offsetX offsetY | depth _ aBinaryStream next. (depth isPowerOfTwo and: [depth between: 1 and: 32]) ifFalse: [self error: 'invalid depth; bad Form file?']. width _ aBinaryStream nextWord. height _ aBinaryStream nextWord. offsetX _ aBinaryStream nextWord. offsetY _ aBinaryStream nextWord. offsetX > 32767 ifTrue: [offsetX _ offsetX - 65536]. offsetY > 32767 ifTrue: [offsetY _ offsetY - 65536]. bits _ Bitmap newFromStream: aBinaryStream. bits size = self bitsSize ifFalse: [self error: 'wrong bitmap size; bad Form file?']. ^ self ! ! !Form methodsFor: 'fileIn/Out' stamp: 'jm 3/27/98 16:54'! readFromOldFormat: aBinaryStream "Read a Form in the original ST-80 format." | w h offsetX offsetY newForm theBits pos | self error: 'this method must be updated to read into 32-bit word bitmaps'. w _ aBinaryStream nextWord. h _ aBinaryStream nextWord. offsetX _ aBinaryStream nextWord. offsetY _ aBinaryStream nextWord. offsetX > 32767 ifTrue: [offsetX _ offsetX - 65536]. offsetY > 32767 ifTrue: [offsetY _ offsetY - 65536]. newForm _ Form extent: w @ h offset: offsetX @ offsetY. theBits _ newForm bits. pos _ 0. 1 to: w + 15 // 16 do: [:j | 1 to: h do: [:i | theBits at: (pos _ pos+1) put: aBinaryStream nextWord]]. newForm bits: theBits. ^ newForm ! ! !Form methodsFor: 'fileIn/Out' stamp: 'RAA 9/19/2000 18:37'! store15To24HexBitsOn:aStream | buf i | "write data for 16-bit form, optimized for encoders writing directly to files to do one single file write rather than 12. I'm not sure I understand the significance of the shifting pattern, but I think I faithfully translated it from the original" buf _ String new: 12. bits do: [:word | i _ 0. "upper pixel" buf at: (i _ i + 1) put: ((word bitShift: -27) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -32) bitAnd: 8) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -22) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -27) bitAnd: 8) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -17) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -22) bitAnd: 8) asHexDigit. "lower pixel" buf at: (i _ i + 1) put: ((word bitShift: -11) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -16) bitAnd: 8) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -6) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -11) bitAnd: 8) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -1) bitAnd: 15) asHexDigit. buf at: (i _ i + 1) put: ((word bitShift: -6) bitAnd: 8) asHexDigit. aStream print: buf. "#( 31 26 21 15 10 5 ) do:[:startBit | ]" ].! ! !Form methodsFor: 'fileIn/Out'! store32To24HexBitsOn:aStream ^self storeBits:20 to:0 on:aStream.! ! !Form methodsFor: 'fileIn/Out'! storeBits:startBit to:stopBit on:aStream bits storeBits:startBit to:stopBit on:aStream.! ! !Form methodsFor: 'fileIn/Out'! storeBitsOn:aStream base:anInteger bits do: [:word | anInteger = 10 ifTrue: [aStream space] ifFalse: [aStream crtab: 2]. word printOn: aStream base: anInteger]. ! ! !Form methodsFor: 'fileIn/Out'! storeHexBitsOn:aStream ^self storeBits:28 to:0 on:aStream.! ! !Form methodsFor: 'fileIn/Out'! storeOn: aStream self storeOn: aStream base: 10! ! !Form methodsFor: 'fileIn/Out'! storeOn: aStream base: anInteger "Store the receiver out as an expression that can be evaluated to recreate a Form with the same contents as the original." self unhibernate. aStream nextPut: $(. aStream nextPutAll: self species name. aStream crtab: 1. aStream nextPutAll: 'extent: '. self extent printOn: aStream. aStream crtab: 1. aStream nextPutAll: 'depth: '. self depth printOn: aStream. aStream crtab: 1. aStream nextPutAll: 'fromArray: #('. self storeBitsOn:aStream base:anInteger. aStream nextPut: $). aStream crtab: 1. aStream nextPutAll: 'offset: '. self offset printOn: aStream. aStream nextPut: $). ! ! !Form methodsFor: 'fileIn/Out' stamp: 'ar 5/28/2000 00:52'! unhibernate "If my bitmap has been compressed into a ByteArray, then expand it now, and return true." bits == nil ifTrue:[bits _ Bitmap new: self bitsSize. ^true]. (bits isMemberOf: ByteArray) ifTrue: [bits _ Bitmap decompressFromByteArray: bits. ^ true]. ^ false! ! !Form methodsFor: 'fileIn/Out' stamp: 'di 12/6/1999 10:14'! writeBMPfileNamed: fName "Display writeBMPfileNamed: 'display'" | fileName bhSize biSize biClrUsed f biSizeImage bfOffBits rowBytes rgb data colorValues | self unhibernate. (#(1 4 8 32) includes: depth) ifFalse: [self halt "depth must be one of these"]. ((fileName _ fName) asUppercase endsWith: '.BMP') ifFalse: [fileName _ fName , '.BMP']. bhSize _ 14. "# bytes in file header" biSize _ 40. "info header size in bytes" biClrUsed _ depth = 32 ifTrue: [0] ifFalse:[1 << depth]. "No. color table entries" bfOffBits _ biSize + bhSize + (4*biClrUsed). rowBytes _ ((depth min: 24) * width + 31 // 32) * 4. biSizeImage _ height * rowBytes. f _ (FileStream newFileNamed: fileName) binary. "Write the file header" f position: 0. f nextLittleEndianNumber: 2 put: 19778. "bfType = BM" f nextLittleEndianNumber: 4 put: bfOffBits + biSizeImage. "Entire file size in bytes" f nextLittleEndianNumber: 4 put: 0. "bfReserved" f nextLittleEndianNumber: 4 put: bfOffBits. "Offset of bitmap data from start of hdr (and file)" "Write the bitmap info header" f position: bhSize. f nextLittleEndianNumber: 4 put: biSize. "info header size in bytes" f nextLittleEndianNumber: 4 put: width. "biWidth" f nextLittleEndianNumber: 4 put: height. "biHeight" f nextLittleEndianNumber: 2 put: 1. "biPlanes" f nextLittleEndianNumber: 2 put: (depth min: 24). "biBitCount" f nextLittleEndianNumber: 4 put: 0. "biCompression" f nextLittleEndianNumber: 4 put: biSizeImage. "size of image section in bytes" f nextLittleEndianNumber: 4 put: 2800. "biXPelsPerMeter" f nextLittleEndianNumber: 4 put: 2800. "biYPelsPerMeter" f nextLittleEndianNumber: 4 put: biClrUsed. f nextLittleEndianNumber: 4 put: 0. "biClrImportant" biClrUsed > 0 ifTrue: [ "write color map; this works for ColorForms, too" colorValues _ self colormapIfNeededForDepth: 32. 1 to: biClrUsed do: [:i | rgb _ colorValues at: i. 0 to: 24 by: 8 do: [:j | f nextPut: (rgb >> j bitAnd: 16rFF)]]]. 'Writing image data' displayProgressAt: Sensor cursorPoint from: 1 to: height during: [:bar | 1 to: height do: [:i | bar value: i. data _ (self copy: (0@(height-i) extent: width@1)) bits. depth = 32 ifTrue: [1 to: data size do: [:j | f nextLittleEndianNumber: 3 put: (data at: j)]. 1 to: (data size*3)+3//4*4-(data size*3) do: [:j | f nextPut: 0 "pad to 32-bits"]] ifFalse: [1 to: data size do: [:j | f nextNumber: 4 put: (data at: j)]]]]. f position = (bfOffBits + biSizeImage) ifFalse: [self halt]. f close. ! ! !Form methodsFor: 'fileIn/Out' stamp: 'di 3/2/98 12:43'! writeOn: file "Write the receiver on the file in the format depth, extent, offset, bits." self unhibernate. file nextPut: depth. file nextWordPut: width. file nextWordPut: height. file nextWordPut: ((self offset x) >=0 ifTrue: [self offset x] ifFalse: [self offset x + 65536]). file nextWordPut: ((self offset y) >=0 ifTrue: [self offset y] ifFalse: [self offset y + 65536]). bits writeOn: file! ! !Form methodsFor: 'fileIn/Out' stamp: 'di 7/6/1998 23:00'! writeOnMovie: file "Write just my bits on the file." self unhibernate. bits writeUncompressedOn: file! ! !Form methodsFor: 'fileIn/Out' stamp: 'tk 2/19/1999 07:30'! writeUncompressedOn: file "Write the receiver on the file in the format depth, extent, offset, bits. Warning: Caller must put header info on file!! Use writeUncompressedOnFileNamed: instead." self unhibernate. file binary. file nextPut: depth. file nextWordPut: width. file nextWordPut: height. file nextWordPut: ((self offset x) >=0 ifTrue: [self offset x] ifFalse: [self offset x + 65536]). file nextWordPut: ((self offset y) >=0 ifTrue: [self offset y] ifFalse: [self offset y + 65536]). bits writeUncompressedOn: file! ! !Form methodsFor: 'private' stamp: 'tk 3/13/2000 15:21'! hackBits: bitThing "This method provides an initialization so that BitBlt may be used, eg, to copy ByteArrays and other non-pointer objects efficiently. The resulting form looks 4 wide, 8 deep, and bitThing-size-in-words high." width _ 4. depth _ 8. bitThing class isBits ifFalse: [self error: 'bitThing must be a non-pointer object']. bitThing class isBytes ifTrue: [height _ bitThing basicSize // 4] ifFalse: [height _ bitThing basicSize]. bits _ bitThing! ! !Form methodsFor: 'private'! initFromArray: array "Fill the bitmap from array. If the array is shorter, then cycle around in its contents until the bitmap is filled." | ax aSize array32 i j word16 | ax _ 0. aSize _ array size. aSize > bits size ifTrue: ["backward compatibility with old 16-bit bitmaps and their forms" array32 _ Array new: height * (width + 31 // 32). i _ j _ 0. 1 to: height do: [:y | 1 to: width+15//16 do: [:x16 | word16 _ array at: (i _ i + 1). x16 odd ifTrue: [array32 at: (j _ j+1) put: (word16 bitShift: 16)] ifFalse: [array32 at: j put: ((array32 at: j) bitOr: word16)]]]. ^ self initFromArray: array32]. 1 to: bits size do: [:index | (ax _ ax + 1) > aSize ifTrue: [ax _ 1]. bits at: index put: (array at: ax)]! ! !Form methodsFor: 'private' stamp: 'ar 12/19/2000 16:23'! privateFloodFillValue: aColor "Private. Compute the pixel value in the receiver's depth but take into account implicit color conversions by BitBlt." | f1 f2 bb | f1 _ Form extent: 1@1 depth: depth. f2 _ Form extent: 1@1 depth: 32. bb _ BitBlt toForm: f1. bb fillColor: aColor; destRect: (0@0 corner: 1@1); combinationRule: 3; copyBits. bb _ BitBlt toForm: f2. bb sourceForm: f1; sourceOrigin: 0@0; destRect: (0@0 corner: 1@1); combinationRule: 3; copyBits. ^f2 pixelValueAt: 0@0.! ! !Form methodsFor: 'private' stamp: '6/9/97 16:10 di'! setExtent: extent depth: bitsPerPixel "Create a virtual bit map with the given extent and bitsPerPixel." width _ extent x asInteger. width < 0 ifTrue: [width _ 0]. height _ extent y asInteger. height < 0 ifTrue: [height _ 0]. depth _ bitsPerPixel. bits _ Bitmap new: self bitsSize! ! !Form methodsFor: 'private' stamp: 'ar 5/28/2000 15:49'! setExtent: extent depth: bitsPerPixel bits: bitmap "Create a virtual bit map with the given extent and bitsPerPixel." width _ extent x asInteger. width < 0 ifTrue: [width _ 0]. height _ extent y asInteger. height < 0 ifTrue: [height _ 0]. depth _ bitsPerPixel. (bits isNil or:[self bitsSize = bitmap size]) ifFalse:[^self error:'Bad dimensions']. bits _ bitmap! ! !Form methodsFor: 'other' stamp: 'RAA 8/14/2000 10:13'! asCursorForm ^ self as: StaticForm! ! !Form methodsFor: 'other' stamp: 'ar 5/28/2000 12:07'! asGrayScale "Assume the receiver is a grayscale image. Return a grayscale ColorForm computed by extracting the brightness levels of one color component. This technique allows a 32-bit Form to be converted to an 8-bit ColorForm to save space while retaining a full 255 levels of gray. (The usual colormapping technique quantizes to 8, 16, or 32 levels, which loses information.)" | f32 srcForm result map bb grays | depth = 32 ifFalse: [ f32 _ Form extent: width@height depth: 32. self displayOn: f32. ^ f32 asGrayScale]. self unhibernate. srcForm _ Form extent: (width * 4)@height depth: 8. srcForm bits: bits. result _ ColorForm extent: width@height depth: 8. map _ Bitmap new: 256. 2 to: 256 do: [:i | map at: i put: i - 1]. map at: 1 put: 1. "map zero pixel values to near-black" bb _ (BitBlt current toForm: result) sourceForm: srcForm; combinationRule: Form over; colorMap: map. 0 to: width - 1 do: [:dstX | bb sourceRect: (((dstX * 4) + 2)@0 extent: 1@height); destOrigin: dstX@0; copyBits]. "final BitBlt to zero-out pixels that were truely transparent in the original" map _ Bitmap new: 512. map at: 1 put: 16rFF. (BitBlt current toForm: result) sourceForm: self; sourceRect: self boundingBox; destOrigin: 0@0; combinationRule: Form erase; colorMap: map; copyBits. grays _ (0 to: 255) collect: [:brightness | Color gray: brightness asFloat / 255.0]. grays at: 1 put: Color transparent. result colors: grays. ^ result ! ! !Form methodsFor: 'other'! bitsSize | pixPerWord | depth == nil ifTrue: [depth _ 1]. pixPerWord _ 32 // depth. ^ width + pixPerWord - 1 // pixPerWord * height! ! !Form methodsFor: 'other' stamp: 'jm 4/5/1999 19:20'! colorReduced "Return a color-reduced ColorForm version of the receiver, if possible, or the receiver itself if not." | tally tallyDepth colorCount newForm cm oldPixelValues newFormColors nextColorIndex c | tally _ self tallyPixelValues asArray. tallyDepth _ (tally size log: 2) asInteger. colorCount _ 0. tally do: [:n | n > 0 ifTrue: [colorCount _ colorCount + 1]]. (tally at: 1) = 0 ifTrue: [colorCount _ colorCount + 1]. "include transparent" colorCount > 256 ifTrue: [^ self]. "cannot reduce" newForm _ self formForColorCount: colorCount. "build an array of just the colors used, and a color map to translate old pixel values to their indices into this color array" cm _ Bitmap new: tally size. oldPixelValues _ self colormapIfNeededForDepth: 32. newFormColors _ Array new: colorCount. newFormColors at: 1 put: Color transparent. nextColorIndex _ 2. 2 to: cm size do: [:i | (tally at: i) > 0 ifTrue: [ oldPixelValues = nil ifTrue: [c _ Color colorFromPixelValue: i - 1 depth: tallyDepth] ifFalse: [c _ Color colorFromPixelValue: (oldPixelValues at: i) depth: 32]. newFormColors at: nextColorIndex put: c. cm at: i put: nextColorIndex - 1. "pixel values are zero-based indices" nextColorIndex _ nextColorIndex + 1]]. "copy pixels into new ColorForm, mapping to new pixel values" newForm copyBits: self boundingBox from: self at: 0@0 clippingBox: self boundingBox rule: Form over fillColor: nil map: cm. newForm colors: newFormColors. newForm offset: offset. ^ newForm ! ! !Form methodsFor: 'other' stamp: 'jm 12/5/97 19:48'! colorsUsed "Return a list of the Colors this form uses." | tallies tallyDepth usedColors | tallies _ self tallyPixelValues. tallyDepth _ (tallies size log: 2) asInteger. usedColors _ OrderedCollection new. tallies doWithIndex: [:count :i | count > 0 ifTrue: [ usedColors add: (Color colorFromPixelValue: i - 1 depth: tallyDepth)]]. ^ usedColors asArray ! ! !Form methodsFor: 'other' stamp: 'jm 9/27/97 21:02'! formForColorCount: colorCount "Return a ColorForm of sufficient depth to represent the given number of colors. The maximum number of colors is 256." colorCount > 256 ifTrue: [^ self error: 'too many colors']. colorCount > 16 ifTrue: [^ ColorForm extent: self extent depth: 8]. colorCount > 4 ifTrue: [^ ColorForm extent: self extent depth: 4]. colorCount > 2 ifTrue: [^ ColorForm extent: self extent depth: 2]. ^ ColorForm extent: self extent depth: 1 ! ! !Form methodsFor: 'other' stamp: 'ar 5/28/2000 12:08'! mapColor: oldColor to: newColor "Make all pixels of the given color in this Form to the given new color." "Warnings: This method modifies the receiver. It may lose some color accuracy on 32-bit Forms, since the transformation uses a color map with only 15-bit resolution." | map | map _ (Color cachedColormapFrom: depth to: depth) copy. map at: (oldColor indexInMap: map) put: (newColor pixelWordForDepth: depth). (BitBlt current toForm: self) sourceForm: self; sourceOrigin: 0@0; combinationRule: Form over; destX: 0 destY: 0 width: width height: height; colorMap: map; copyBits. ! ! !Form methodsFor: 'other' stamp: 'ar 5/28/2000 12:08'! mapColors: oldColorBitsCollection to: newColorBits "Make all pixels of the given color in this Form to the given new color." "Warnings: This method modifies the receiver. It may lose some color accuracy on 32-bit Forms, since the transformation uses a color map with only 15-bit resolution." | map | depth < 16 ifTrue: [map _ (Color cachedColormapFrom: depth to: depth) copy] ifFalse: [ "use maximum resolution color map" "source is 16-bit or 32-bit RGB; use colormap with 5 bits per color component" map _ Color computeRGBColormapFor: depth bitsPerColor: 5]. oldColorBitsCollection do:[ :oldColor | map at: oldColor put: newColorBits]. (BitBlt current toForm: self) sourceForm: self; sourceOrigin: 0@0; combinationRule: Form over; destX: 0 destY: 0 width: width height: height; colorMap: map; copyBits. ! ! !Form methodsFor: 'other' stamp: 'jm 12/1/97 19:58'! newColorMap "Return an uninitialized color map array appropriate to this Form's depth." ^ Bitmap new: (1 bitShift: (depth min: 15)) ! ! !Form methodsFor: 'other' stamp: 'jm 1/6/98 10:37'! primPrintHScale: hScale vScale: vScale landscape: aBoolean "On platforms that support it, this primitive prints the receiver, assumed to be a Form, to the default printer." "(Form extent: 10@10) primPrintHScale: 1.0 vScale: 1.0 landscape: true" self primitiveFailed ! ! !Form methodsFor: 'transitions' stamp: 'jm 5/21/1998 23:46'! fadeImage: otherImage at: topLeft indexAndMaskDo: indexAndMaskBlock "This fade uses halftones as a blending hack. Zeros in the halftone produce the original image (self), and ones in the halftone produce the 'otherImage'. IndexAndMaskBlock gets evaluated prior to each cycle, and the resulting boolean determines whether to continue cycling." | index imageRect maskForm resultForm | imageRect _ otherImage boundingBox. resultForm _ self copy: (topLeft extent: imageRect extent). maskForm _ Form extent: 32@32. index _ 0. [indexAndMaskBlock value: (index _ index+1) value: maskForm] whileTrue: [maskForm reverse. resultForm copyBits: imageRect from: resultForm at: 0@0 clippingBox: imageRect rule: Form over fillColor: maskForm. maskForm reverse. resultForm copyBits: imageRect from: otherImage at: 0@0 clippingBox: imageRect rule: Form under fillColor: maskForm. self copyBits: imageRect from: resultForm at: topLeft clippingBox: self boundingBox rule: Form over fillColor: nil. Display forceDisplayUpdate]! ! !Form methodsFor: 'transitions'! fadeImageCoarse: otherImage at: topLeft "Display fadeImageCoarse: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" | pix j | ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | i=1 ifTrue: [pix _ (1 bitShift: depth) - 1. 1 to: 8//depth-1 do: [:q | pix _ pix bitOr: (pix bitShift: depth*4)]]. i <= 16 ifTrue: [j _ i-1//4+1. (0 to: 28 by: 4) do: [:k | mask bits at: j+k put: ((mask bits at: j+k) bitOr: (pix bitShift: i-1\\4*depth))]. "mask display." true] ifFalse: [false]]! ! !Form methodsFor: 'transitions'! fadeImageFine: otherImage at: topLeft "Display fadeImageFine: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" | pix j ii | ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | i=1 ifTrue: [pix _ (1 bitShift: depth) - 1. 1 to: 8//depth-1 do: [:q | pix _ pix bitOr: (pix bitShift: depth*4)]]. i <= 16 ifTrue: [ii _ #(0 10 2 8 7 13 5 15 1 11 3 9 6 12 4 14) at: i. j _ ii//4+1. (0 to: 28 by: 4) do: [:k | mask bits at: j+k put: ((mask bits at: j+k) bitOr: (pix bitShift: ii\\4*depth))]. true] ifFalse: [false]]! ! !Form methodsFor: 'transitions'! fadeImageHor: otherImage at: topLeft "Display fadeImageHor: (Form fromDisplay: (10@10 extent: 300@300)) reverse at: 10@10" ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | mask fill: (0@(mask height//2-i) extent: mask width@(i*2)) fillColor: Color black. (i*2) <= mask width]! ! !Form methodsFor: 'transitions'! fadeImageHorFine: otherImage at: topLeft "Display fadeImageHorFine: (Form fromDisplay: (10@10 extent: 300@300)) reverse at: 10@10" ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | mask fill: (0@(i-1) extent: mask width@1) fillColor: Color black. mask fill: (0@(i-1+16) extent: mask width@1) fillColor: Color black. (i*2) <= mask width]! ! !Form methodsFor: 'transitions'! fadeImageSquares: otherImage at: topLeft "Display fadeImageSquares: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | mask fill: ((16-i) asPoint extent: (i*2) asPoint) fillColor: Color black. i <= 16]! ! !Form methodsFor: 'transitions'! fadeImageVert: otherImage at: topLeft "Display fadeImageVert: (Form fromDisplay: (10@10 extent: 300@300)) reverse at: 10@10" ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | mask fill: ((mask width//2//depth-i*depth)@0 extent: i*2*depth@mask height) fillColor: Color black. i <= (mask width//depth)]! ! !Form methodsFor: 'transitions' stamp: 'jm 6/1/1998 10:55'! pageImage: otherImage at: topLeft corner: corner "Produce a page-turning illusion that gradually reveals otherImage located at topLeft in this form. Corner specifies which corner, as 1=topLeft, 2=topRight, 3=bottomRight, 4=bottomLeft." | bb maskForm resultForm delta maskLoc maskRect stepSize cornerSel smallRect | stepSize _ 10. bb _ otherImage boundingBox. resultForm _ self copy: (topLeft extent: bb extent). maskForm _ Form extent: ((otherImage width min: otherImage height) + stepSize) asPoint. "maskLoc _ starting loc rel to topLeft" otherImage width > otherImage height ifTrue: ["wide image; motion is horizontal." (corner between: 2 and: 3) not ifTrue: ["motion is to the right" delta _ 1@0. maskLoc _ bb topLeft - (corner = 1 ifTrue: [maskForm width@0] ifFalse: [maskForm width@stepSize])] ifFalse: ["motion is to the left" delta _ -1@0. maskLoc _ bb topRight - (corner = 2 ifTrue: [0@0] ifFalse: [0@stepSize])]] ifFalse: ["tall image; motion is vertical." corner <= 2 ifTrue: ["motion is downward" delta _ 0@1. maskLoc _ bb topLeft - (corner = 1 ifTrue: [0@maskForm height] ifFalse: [stepSize@maskForm height])] ifFalse: ["motion is upward" delta _ 0@-1. maskLoc _ bb bottomLeft - (corner = 3 ifTrue: [stepSize@0] ifFalse: [0@0])]]. "Build a solid triangle in the mask form" (Pen newOnForm: maskForm) in: [:p | corner even "Draw 45-degree line" ifTrue: [p place: 0@0; turn: 135; go: maskForm width*3//2] ifFalse: [p place: 0@(maskForm height-1); turn: 45; go: maskForm width*3//2]]. maskForm smear: delta negated distance: maskForm width. "Copy the mask to full resolution for speed. Make it be the reversed so that it can be used for ORing in the page-corner color" maskForm _ (Form extent: maskForm extent depth: otherImage depth) copyBits: maskForm boundingBox from: maskForm at: 0@0 colorMap: (Bitmap with: 16rFFFFFFFF with: 0). "Now move the triangle maskForm across the resultForm selecting the triangular part of otherImage to display, and across the resultForm, selecting the part of the original image to erase." cornerSel _ #(topLeft topRight bottomRight bottomLeft) at: corner. 1 to: (otherImage width + otherImage height // stepSize)+1 do: [:i | "Determine the affected square" maskRect _ (maskLoc extent: maskForm extent) intersect: bb. ((maskLoc x*delta x) + (maskLoc y*delta y)) < 0 ifTrue: [smallRect _ 0@0 extent: (maskRect width min: maskRect height) asPoint. maskRect _ smallRect align: (smallRect perform: cornerSel) with: (maskRect perform: cornerSel)]. "AND otherForm with triangle mask, and OR into result" resultForm copyBits: bb from: otherImage at: 0@0 clippingBox: maskRect rule: Form over fillColor: nil. resultForm copyBits: maskForm boundingBox from: maskForm at: maskLoc clippingBox: maskRect rule: Form erase fillColor: nil. resultForm copyBits: maskForm boundingBox from: maskForm at: maskLoc clippingBox: maskRect rule: Form under fillColor: Color lightBrown. "Now update Display in a single BLT." self copyBits: maskRect from: resultForm at: topLeft + maskRect topLeft clippingBox: self boundingBox rule: Form over fillColor: nil. Display forceDisplayUpdate. maskLoc _ maskLoc + (delta*stepSize)] " 1 to: 4 do: [:corner | Display pageImage: (Form fromDisplay: (10@10 extent: 200@300)) reverse at: 10@10 corner: corner] " ! ! !Form methodsFor: 'transitions' stamp: 'ar 5/28/2000 12:12'! pageWarp: otherImage at: topLeft forward: forward "Produce a page-turning illusion that gradually reveals otherImage located at topLeft in this form. forward == true means turn pages toward you, else away. [ignored for now]" | pageRect oldPage nSteps buffer p leafRect sourceQuad warp oldBottom d | pageRect _ otherImage boundingBox. oldPage _ self copy: (pageRect translateBy: topLeft). (forward ifTrue: [oldPage] ifFalse: [otherImage]) border: pageRect widthRectangle: (Rectangle left: 0 right: 2 top: 1 bottom: 1) rule: Form over fillColor: Color black. oldBottom _ self copy: ((pageRect bottomLeft + topLeft) extent: (pageRect width@(pageRect height//4))). nSteps _ 8. buffer _ Form extent: otherImage extent + (0@(pageRect height//4)) depth: self depth. d _ pageRect topLeft + (0@(pageRect height//4)) - pageRect topRight. 1 to: nSteps-1 do: [:i | forward ifTrue: [buffer copy: pageRect from: otherImage to: 0@0 rule: Form over. p _ pageRect topRight + (d * i // nSteps)] ifFalse: [buffer copy: pageRect from: oldPage to: 0@0 rule: Form over. p _ pageRect topRight + (d * (nSteps-i) // nSteps)]. buffer copy: oldBottom boundingBox from: oldBottom to: pageRect bottomLeft rule: Form over. leafRect _ pageRect topLeft corner: p x @ (pageRect bottom + p y). sourceQuad _ Array with: pageRect topLeft with: pageRect bottomLeft + (0@p y) with: pageRect bottomRight with: pageRect topRight - (0@p y). warp _ (WarpBlt current toForm: buffer) clipRect: leafRect; sourceForm: (forward ifTrue: [oldPage] ifFalse: [otherImage]); combinationRule: Form paint. warp copyQuad: sourceQuad toRect: leafRect. self copy: buffer boundingBox from: buffer to: topLeft rule: Form over. Display forceDisplayUpdate]. buffer copy: pageRect from: otherImage to: 0@0 rule: Form over. buffer copy: oldBottom boundingBox from: oldBottom to: pageRect bottomLeft rule: Form over. self copy: buffer boundingBox from: buffer to: topLeft rule: Form over. Display forceDisplayUpdate. " 1 to: 4 do: [:corner | Display pageWarp: (Form fromDisplay: (10@10 extent: 200@300)) reverse at: 10@10 forward: false] " ! ! !Form methodsFor: 'transitions' stamp: 'jm 5/21/1998 23:46'! slideImage: otherImage at: topLeft delta: delta "Display slideImage: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40 delta: 3@-4" | bb nSteps clipRect | bb _ otherImage boundingBox. clipRect _ topLeft extent: otherImage extent. nSteps _ 1. delta x = 0 ifFalse: [nSteps _ nSteps max: (bb width//delta x abs) + 1]. delta y = 0 ifFalse: [nSteps _ nSteps max: (bb height//delta y abs) + 1]. 1 to: nSteps do: [:i | self copyBits: bb from: otherImage at: delta*(i-nSteps) + topLeft clippingBox: clipRect rule: Form paint fillColor: nil. Display forceDisplayUpdate]! ! !Form methodsFor: 'transitions' stamp: 'jm 6/18/1998 12:57'! wipeImage: otherImage at: topLeft clippingBox: clipBox rectForIndex: rectForIndexBlock | i clipRect t rectOrList waitTime | i _ 0. clipRect _ topLeft extent: otherImage extent. clipBox ifNotNil: [clipRect _ clipRect intersect: clipBox]. [rectOrList _ rectForIndexBlock value: (i _ i + 1). rectOrList == nil] whileFalse: [ t _ Time millisecondClockValue. rectOrList asOrderedCollection do: [:r | self copyBits: r from: otherImage at: topLeft + r topLeft clippingBox: clipRect rule: Form over fillColor: nil]. Display forceDisplayUpdate. waitTime _ 3 - (Time millisecondClockValue - t). waitTime > 0 ifTrue: ["(Delay forMilliseconds: waitTime) wait"]]. ! ! !Form methodsFor: 'transitions' stamp: 'jm 10/16/97 15:21'! wipeImage: otherImage at: topLeft delta: delta "Display wipeImage: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40 delta: 0@-2" self wipeImage: otherImage at: topLeft delta: delta clippingBox: nil. ! ! !Form methodsFor: 'transitions' stamp: 'jm 10/16/97 15:17'! wipeImage: otherImage at: topLeft delta: delta clippingBox: clipBox | wipeRect bb nSteps | bb _ otherImage boundingBox. wipeRect _ delta x = 0 ifTrue: [delta y = 0 ifTrue: [nSteps _ 1. bb "allow 0@0"] ifFalse: [ nSteps _ bb height//delta y abs + 1. "Vertical movement" delta y > 0 ifTrue: [bb topLeft extent: bb width@delta y] ifFalse: [bb bottomLeft+delta extent: bb width@delta y negated]]] ifFalse: [nSteps _ bb width//delta x abs + 1. "Horizontal movement" delta x > 0 ifTrue: [bb topLeft extent: delta x@bb height] ifFalse: [bb topRight+delta extent: delta x negated@bb height]]. ^ self wipeImage: otherImage at: topLeft clippingBox: clipBox rectForIndex: [:i | i <= nSteps ifTrue: [wipeRect translateBy: (delta* (i-1))] ifFalse: [nil]]! ! !Form methodsFor: 'transitions' stamp: 'di 1/28/1999 09:20'! zoomIn: goingIn orOutTo: otherImage at: topLeft vanishingPoint: vp "Display zoomInTo: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40. Display zoomOutTo: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40." | nSteps j bigR lilR minTime startTime lead | nSteps _ 16. minTime _ 500. "milliseconds" startTime _ Time millisecondClockValue. ^ self wipeImage: otherImage at: topLeft clippingBox: nil rectForIndex: [:i | "i runs from 1 to nsteps" i > nSteps ifTrue: [nil "indicates all done"] ifFalse: ["If we are going too fast, delay for a bit" lead _ startTime + (i-1*minTime//nSteps) - Time millisecondClockValue. lead > 10 ifTrue: [(Delay forMilliseconds: lead) wait]. "Return an array with the difference rectangles for this step." j _ goingIn ifTrue: [i] ifFalse: [nSteps+1-i]. bigR _ vp - (vp*(j)//nSteps) corner: vp + (otherImage extent-vp*(j)//nSteps). lilR _ vp - (vp*(j-1)//nSteps) corner: vp + (otherImage extent-vp*(j-1)//nSteps). bigR areasOutside: lilR]]! ! !Form methodsFor: 'transitions' stamp: 'di 3/2/98 09:14'! zoomInTo: otherImage at: topLeft "Display zoomInTo: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" ^ self zoomIn: true orOutTo: otherImage at: topLeft vanishingPoint: otherImage extent//2+topLeft! ! !Form methodsFor: 'transitions' stamp: 'di 3/2/98 09:15'! zoomOutTo: otherImage at: topLeft "Display zoomOutTo: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" ^ self zoomIn: false orOutTo: otherImage at: topLeft vanishingPoint: otherImage extent//2+topLeft! ! !Form methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 09:01'! addDeltasFrom: previousForm (BitBlt destForm: self sourceForm: previousForm fillColor: nil combinationRule: Form reverse destOrigin: 0@0 sourceOrigin: 0@0 extent: self extent clipRect: self boundingBox) copyBits. ^self! ! !Form methodsFor: 'as yet unclassified' stamp: 'di 7/8/1998 12:18'! baldMountainWorkspace " -- Reset player, do this, then play -- | file ff | Time millisecondsToRun: [ file _ FileStream oldFileNamed: 'Bald10fpsAll338.forms'. file binary. file next = 2 ifFalse: [self halt]. ff _ Form extent: 320@240 depth: 16. 1 to: 338 by: 2 do: [:i | file position: i-1*153613+1. ff readFrom: file. ff display. [i*124>AA msecsSinceStart] whileTrue: [World doOneCycle]. Sensor yellowButtonPressed ifTrue: [^ file close]]. file close] -- Async version -- | file ff byteCount nFrames bytesRead di | file _ AsyncFile new open: 'Bald10fpsAll338.forms' forWrite: false. ff _ Form extent: 320@240 depth: 16. byteCount _ ff bits size * 4. nFrames _ 338. file primReadStart: file fileHandle fPosition: 1+13 count: byteCount. 1 to: nFrames by: (di_1) do: [:i | file waitForCompletion. bytesRead _ file primReadResult: file fileHandle intoBuffer: ff bits at: 1 count: byteCount//4. Sensor anyButtonPressed ifTrue: [^ file close]. (i+di) <= nFrames ifTrue: [file primReadStart: file fileHandle fPosition: (i-1+di)*(byteCount+13)+1+13 count: byteCount]. ff display. [i*124>AA msecsSinceStart] whileTrue: [World doOneCycle]. ]. file close. waitTime | ps zps f32 f16 | 1 to: 338 by: 1 do: [:i | ps _ i printString. zps _ ('00000' copyFrom: 1 to: 5 - ps size) , ps. f32 _ Form fromFile: (FileStream oldFileNamed: 'Macintosh HD:Shipping.Receiving:Bald10fps bmps:Bald' , zps , '.BMP'). f32 displayAt: 0@0. f16 _ Form extent: f32 extent depth: 16. f32 displayOn: f16 at: 0@0. f16 displayAt: 330@0. Transcript cr; show: i printString. f16 writeUncompressedOnFileNamed: 'Macintosh HD:Shipping.Receiving:Bald10fps forms:Bald' , zps , '.form'. Sensor anyButtonPressed ifTrue: [^ nil]] | ps zps file | file _ FileStream newFileNamed: 'Macintosh HD:Shipping.Receiving:Bald10fpsAll338.forms'. file binary. file nextPut: 2. 1 to: 338 by: 1 do: [:i | ps _ i printString. zps _ ('00000' copyFrom: 1 to: 5 - ps size) , ps. (Form fromFileNamed: 'Macintosh HD:Shipping.Receiving:Bald10fps forms:Bald' , zps , '.form') display; writeUncompressedOn: file. Sensor anyButtonPressed ifTrue: [^ nil]]. file close -- convert .forms file to .movie format -- | out ps zps ff | out _ FileStream newFileNamed: 'Bald2minAt10fps.movie'. out binary. ff _ Form extent: 320@240 depth: 16. #(22 320 240 16 338) , (6 to: 32) do: [:i | out nextInt32Put: i]. 1 to: 1203 by: 1 do: [:i | i printString displayAt: 400@0. ps _ i printString. zps _ ('00000' copyFrom: 1 to: 5 - ps size) , ps. (Form fromFileNamed: 'BackStreet HD:Bald Mt Disk:Bald2 10fps Proc:Bald10.' , zps) displayOn: ff. ff display; writeOnMovie: out]. out close. | file ff | Time millisecondsToRun: [ file _ FileStream oldFileNamed: 'Bald10fpsAll338.forms'. file binary. file next = 2 ifFalse: [self halt]. ff _ Form extent: 320@240 depth: 16. 1 to: 338 by: 1 do: [:i | ff readFrom: file. ff display. Sensor anyButtonPressed ifTrue: [^ file close]]. file close] | file ff byteCount nFrames filePosition bytesRead waitTime t | Array with: (Time millisecondsToRun: [ file _ AsyncFile new open: 'Bald10fpsAll338.forms' forWrite: false. ff _ Form extent: 320@240 depth: 16. waitTime _ 0. byteCount _ ff bits size * 4. filePosition _ 1. nFrames _ 338. file primReadStart: file fileHandle fPosition: filePosition+13 count: byteCount. 1 to: nFrames by: 1 do: [:i | t _ Time millisecondClockValue. file waitForCompletion. waitTime _ waitTime + (Time millisecondClockValue - t). bytesRead _ file primReadResult: file fileHandle intoBuffer: ff bits at: 1 count: byteCount//4. filePosition _ filePosition+13 + bytesRead. Sensor anyButtonPressed ifTrue: [^ file close]. i < nFrames ifTrue: [file primReadStart: file fileHandle fPosition: filePosition+13 count: byteCount]. ff display]. file close]) with: waitTime (18400 9798 ) | ff | ff _ Form fromFileNamed: 'Macintosh HD:Shipping.Receiving:Bald10fps forms:Bald00338.form'. Time millisecondsToRun: [1 to: 100 do: [:i | ff display]] 100000//1359 73 Try out on-the-fly pixel doubling [dummied for timing]... | file f1 f2 f2a pixMap bb1 bb2 | Time millisecondsToRun: [ file _ FileStream oldFileNamed: 'Bald10fpsAll338.forms'. file binary. file next = 2 ifFalse: [self halt]. f1 _ Form extent: 320@240 depth: 16. f2 _ Form extent: 640@480 depth: 16. f2a _ Form extent: 320@480 depth: 32. f2a bits: f2 bits. pixMap _ Bitmap new: 32768. 1 to: 32768 do: [:i | pixMap at: i put: (i bitOr: (i bitShift: 16))]. bb1 _ BitBlt destForm: f2a sourceForm: f1 halftoneForm: nil combinationRule: 3 destOrigin: 0@0 sourceOrigin: 0@0 extent: 320@1 clipRect: f2a boundingBox. bb1 colorMap: pixMap. bb2 _ BitBlt destForm: f2 sourceForm: f2 halftoneForm: nil combinationRule: 3 destOrigin: 0@0 sourceOrigin: 0@0 extent: 640@1 clipRect: f2 boundingBox. 1 to: 338 by: 1 do: [:i | f1 readFrom: file. bb1 destOrigin: 0@0; sourceOrigin: 0@0. bb1 destOrigin: 0@0; sourceOrigin: 0@0. 0 to: 239 do: [:j | bb1 sourceY: j; destY: j*2; copyBits. bb2 sourceY: j*2; destY: j*2+1; copyBits]. f2 display. Sensor anyButtonPressed ifTrue: [^ nil]]. file close ] 104512 53247 39812 338000.0/ 53247 6.34777546152835 6 6 "! ! !Form methodsFor: 'as yet unclassified' stamp: 'RAA 7/29/2000 09:01'! deltaFrom: previousForm | newForm | newForm _ previousForm deepCopy. (BitBlt destForm: newForm sourceForm: self fillColor: nil combinationRule: Form reverse destOrigin: 0@0 sourceOrigin: 0@0 extent: self extent clipRect: self boundingBox) copyBits. ^newForm! ! !Form methodsFor: 'as yet unclassified' stamp: 'RAA 8/1/2000 06:15'! deltaFrom: smallerForm at: offsetInMe | newForm | newForm _ smallerForm deepCopy. (BitBlt destForm: newForm sourceForm: self fillColor: nil combinationRule: Form reverse destOrigin: 0@0 sourceOrigin: offsetInMe extent: smallerForm extent clipRect: newForm boundingBox) copyBits. ^newForm! ! !Form methodsFor: 'postscript generation'! bitsPerComponent ^depth <= 8 ifTrue:[depth] ifFalse:[8]. ! ! !Form methodsFor: 'postscript generation' stamp: 'mpw 11/14/1999 22:22'! bytesPerRow ^ self numComponents * self paddedWidth * self bitsPerComponent / 8.! ! !Form methodsFor: 'postscript generation'! decodeArray ^depth <= 8 ifTrue:['[1 0]'] ifFalse:['[0 1 0 1 0 1 ]']. ! ! !Form methodsFor: 'postscript generation' stamp: 'sma 6/14/2000 14:19'! encodePostscriptOn: aStream self unhibernate. ^ self printPostscript: aStream operator: (self depth = 1 ifTrue: ['imagemask'] ifFalse: ['image'])! ! !Form methodsFor: 'postscript generation'! numComponents ^depth <= 8 ifTrue:[1] ifFalse:[3]. ! ! !Form methodsFor: 'postscript generation'! paddedWidth ^ (self width + (self rowPadding-1)// self rowPadding) * self rowPadding.! ! !Form methodsFor: 'postscript generation' stamp: 'di 8/12/2000 10:43'! printPostscript: aStream operator: operator aStream preserveStateDuring: [:inner | inner rectclip: (0 @ 0 extent: (width) @ (height)). self setColorspaceOn: inner. inner print:'[ '; cr; print: '/ImageType 1'; cr; print: '/ImageMatrix [1 0 0 1 0 0]'; cr; print: '/MultipleDataSources false'; cr; print: '/DataSource level1 { { currentfile '; write: self bytesPerRow;print: ' string readhexstring pop }} bind { currentfile /ASCIIHexDecode filter } ifelse'; cr; print: '/Width '; write:self paddedWidth; cr; print: '/Height '; write:self height; cr; print: '/Decode '; print:self decodeArray; cr; print: '/BitsPerComponent '; write: self bitsPerComponent; cr; print: 'makeDict '; print: operator; cr. self depth <= 8 ifTrue: [self storeHexBitsOn: inner]. self depth = 16 ifTrue: [self store15To24HexBitsOn: inner]. self depth = 32 ifTrue: [self store32To24HexBitsOn: inner]. inner print: $>; cr. inner cr]. aStream cr.! ! !Form methodsFor: 'postscript generation' stamp: 'mpw 11/15/1999 08:34'! rowPadding ^ 32 // self depth! ! !Form methodsFor: 'postscript generation'! setColorspaceOn:aStream self numComponents = 1 ifTrue:[aStream print:'/DeviceGray setcolorspace 0 setgray'; cr.] ifFalse:[aStream print:'/DeviceRGB setcolorspace'; cr.].! ! !Form methodsFor: 'converting' stamp: 'ar 11/7/1999 20:29'! asMorph ^ImageMorph new image: self! ! !Form methodsFor: 'converting' stamp: 'ar 5/28/2000 12:07'! asTexture | newForm | newForm _ B3DTexture extent: self extent depth: 32. (BitBlt current toForm: newForm) colorMap: (self colormapIfNeededForDepth: 32); copy: (self boundingBox) from: 0@0 in: self fillColor: nil rule: Form over. newForm interpolate: false. newForm wrap: false. newForm envMode: 0. ^newForm! ! !Form methodsFor: 'Demo' stamp: 'RAA 9/28/1999 11:20'! blankCopyOf: aRectangle scaledBy: scale ^ self class extent: (aRectangle extent * scale) truncated depth: depth! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 18:17'! balancedPatternFor: aColor "Return the pixel word for representing the given color on the receiver" self isExternalForm ifTrue:[^self bitPatternFor: aColor] ifFalse:[^aColor balancedPatternForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 6/8/2000 20:37'! bitPatternFor: aColor "Return the pixel word for representing the given color on the receiver" aColor isColor ifFalse:[^aColor bitPatternForDepth: self depth]. self isExternalForm ifTrue:[^Bitmap with: (self pixelWordFor: aColor)] ifFalse:[^aColor bitPatternForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 20:12'! colormapFromARGB "Return a ColorMap mapping from canonical ARGB space into the receiver. Note: This version is optimized for Squeak forms." | map nBits | self isExternalForm ifTrue:[^ColorMap mappingFromARGB: self rgbaBitMasks]. self depth <= 8 ifTrue:[ map _ Color colorMapIfNeededFrom: 32 to: self depth. map size = 512 ifTrue:[nBits _ 3]. map size = 4096 ifTrue:[nBits _ 4]. map size = 32768 ifTrue:[nBits _ 5]. ^ColorMap shifts: (Array with: 3 * nBits - 24 with: 2 * nBits - 16 with: 1 * nBits - 8 with: 0) masks: (Array with: (1 << nBits) - 1 << (24 - nBits) with: (1 << nBits) - 1 << (16 - nBits) with: (1 << nBits) - 1 << (8 - nBits) with: 0) colors: map]. self depth = 16 ifTrue:[ ^ColorMap shifts: #(-9 -6 -3 0) masks: #(16rF80000 16rF800 16rF8 0)]. self depth = 32 ifTrue:[ ^ColorMap shifts: #(0 0 0 0) masks: #(16rFF0000 16rFF00 16rFF 16rFF000000)]. self error:'Bad depth'! ! !Form methodsFor: 'color mapping' stamp: 'ar 6/8/2000 20:36'! colormapIfNeededFor: destForm "Return a ColorMap mapping from the receiver to destForm." "Note: This is very magical for now - we really need to switch to palettes here but as long as this isn't done we need something that works." | map nBits myBits | (self isExternalForm or:[destForm isExternalForm]) ifTrue:[ ^self colormapFromARGB mappingTo: destForm colormapFromARGB]. self depth = destForm depth ifTrue:[^nil]. "no conversion" self depth <= 8 ifTrue:["Always map indexed" ^ColorMap shifts: nil masks: nil colors: (Color colorMapIfNeededFrom: self depth to: destForm depth)]. (self depth = 16 and:[destForm depth = 32]) ifTrue:["Expand bits" ^ColorMap shifts: #( 9 6 3 0) masks: #(16r7C00 16r3E0 16r1F 0)]. (self depth = 32 and:[destForm depth = 16]) ifTrue:["Contract bits" ^ColorMap shifts: #(-9 -6 -3 0) masks: #(16rF80000 16rF800 16rF8 0)]. "destForm is indexed, I am non-indexed" map _ Color colorMapIfNeededFrom: self depth to: destForm depth. map size = 512 ifTrue:[nBits _ 3]. map size = 4096 ifTrue:[nBits _ 4]. map size = 32768 ifTrue:[nBits _ 5]. myBits _ depth == 16 ifTrue:[5] ifFalse:[8]. ^ColorMap shifts: { (3 * nBits) - (3 * myBits). (2 * nBits) - (2 * myBits). (1 * nBits) - (1 * myBits). 0} masks: { (1 bitShift: nBits) - 1 bitShift: (3 * myBits - nBits). (1 bitShift: nBits) - 1 bitShift: (2 * myBits - nBits). (1 bitShift: nBits) - 1 bitShift: (1 * myBits - nBits). 0} colors: map.! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 20:14'! colormapToARGB "Return a ColorMap mapping from the receiver into canonical ARGB space." self isExternalForm ifTrue:[^self colormapFromARGB inverseMap]. self depth <= 8 ifTrue:[ ^ColorMap shifts: #(0 0 0 0) masks: #(16rFF0000 16rFF00 16rFF 16rFF000000) colors: (Color colorMapIfNeededFrom: self depth to: 32)]. self depth = 16 ifTrue:[ ^ColorMap shifts: #( 9 6 3 0) masks: #(16r7C00 16r3E0 16r1F 0)]. self depth = 32 ifTrue:[ ^ColorMap shifts: #(0 0 0 0) masks: #(16rFF0000 16rFF00 16rFF 16rFF000000)]. self error:'Bad depth'! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 18:18'! pixelValueFor: aColor "Return the pixel word for representing the given color on the receiver" self isExternalForm ifTrue:[^self colormapFromARGB mapPixel: (aColor pixelValueForDepth: 32)] ifFalse:[^aColor pixelValueForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 18:18'! pixelWordFor: aColor "Return the pixel word for representing the given color on the receiver" | basicPattern | self isExternalForm ifFalse:[^aColor pixelWordForDepth: self depth]. basicPattern _ self pixelValueFor: aColor. self depth = 32 ifTrue:[^basicPattern] ifFalse:[^aColor pixelWordFor: self depth filledWith: basicPattern]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/27/2000 20:14'! rgbaBitMasks "Return the masks for specifying the R,G,B, and A components in the receiver" self depth <= 8 ifTrue:[^#(16rFF0000 16rFF00 16rFF 16rFF000000)]. self depth = 16 ifTrue:[^#(16r7C00 16r3E0 16r1F 16r0)]. self depth = 32 ifTrue:[^#(16rFF0000 16rFF00 16rFF 16rFF000000)]. self error:'Bad depth for form'! ! !Form methodsFor: 'testing' stamp: 'RAA 1/19/2001 15:04'! appearsToBeSameCostumeAs: anotherForm (anotherForm isKindOf: self class) ifFalse: [^false]. anotherForm depth = self depth ifFalse: [^false]. ^anotherForm bits = bits ! ! !Form methodsFor: 'testing' stamp: 'ar 5/28/2000 14:58'! isBltAccelerated: ruleInteger for: sourceForm "Return true if the receiver can perform accelerated blts operations by itself" ^false! ! !Form methodsFor: 'testing' stamp: 'ar 5/28/2000 15:04'! isDisplayScreen ^false! ! !Form methodsFor: 'testing' stamp: 'ar 5/27/2000 16:54'! isExternalForm ^false! ! !Form methodsFor: 'testing' stamp: 'ar 5/28/2000 14:58'! isFillAccelerated: ruleInteger for: aColor "Return true if the receiver can perform accelerated fill operations by itself" ^false! ! !Form methodsFor: 'testing' stamp: 'RAA 8/14/2000 10:00'! isStatic ^false! ! !Form methodsFor: 'testing' stamp: 'ar 5/28/2000 14:58'! shouldPreserveContents "Return true if the receiver should preserve it's contents when flagged to be clean. Most forms can not be trivially restored by some drawing operation but some may." ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Form class instanceVariableNames: ''! !Form class methodsFor: 'instance creation' stamp: 'ar 5/28/2000 12:07'! dotOfSize: diameter "Create a form which contains a round black dot." | radius form bb rect centerX centerY centerYBias centerXBias radiusSquared xOverY maxy dx | radius _ diameter//2. form _ self extent: diameter@diameter offset: (0@0) - (radius@radius). bb _ (BitBlt current toForm: form) sourceX: 0; sourceY: 0; combinationRule: Form over; fillColor: Color black. rect _ form boundingBox. centerX _ rect center x. centerY _ rect center y. centerYBias _ rect height odd ifTrue: [0] ifFalse: [1]. centerXBias _ rect width odd ifTrue: [0] ifFalse: [1]. radiusSquared _ (rect height asFloat / 2.0) squared - 0.01. xOverY _ rect width asFloat / rect height asFloat. maxy _ rect height - 1 // 2. "First do the inner fill, and collect x values" 0 to: maxy do: [:dy | dx _ ((radiusSquared - (dy * dy) asFloat) sqrt * xOverY) truncated. bb destX: centerX - centerXBias - dx destY: centerY - centerYBias - dy width: dx + dx + centerXBias + 1 height: 1; copyBits. bb destY: centerY + dy; copyBits]. ^ form " Time millisecondsToRun: [1 to: 20 do: [:i | (Form dotOfSize: i) displayAt: (i*20)@(i*20)]] "! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:27'! extent: extentPoint "Answer an instance of me with a blank bitmap of depth 1." ^ self extent: extentPoint depth: 1 ! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:36'! extent: extentPoint depth: bitsPerPixel "Answer an instance of me with blank bitmap of the given dimensions and depth." ^ self basicNew setExtent: extentPoint depth: bitsPerPixel ! ! !Form class methodsFor: 'instance creation' stamp: 'ar 10/9/1998 23:44'! extent: extentPoint depth: bitsPerPixel bits: aBitmap "Answer an instance of me with blank bitmap of the given dimensions and depth." ^ self basicNew setExtent: extentPoint depth: bitsPerPixel bits: aBitmap! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:35'! extent: extentPoint depth: bitsPerPixel fromArray: anArray offset: offsetPoint "Answer an instance of me with a pixmap of the given depth initialized from anArray." ^ (self extent: extentPoint depth: bitsPerPixel) offset: offsetPoint; initFromArray: anArray ! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:33'! extent: extentPoint fromArray: anArray offset: offsetPoint "Answer an instance of me of depth 1 with bitmap initialized from anArray." ^ (self extent: extentPoint depth: 1) offset: offsetPoint; initFromArray: anArray ! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:28'! extent: extentPoint fromStipple: fourNibbles "Answer an instance of me with bitmap initialized from a repeating 4x4 bit stipple encoded in a 16-bit constant." | nibble | ^ (self extent: extentPoint depth: 1) initFromArray: ((1 to: 4) collect: [:i | nibble _ (fourNibbles bitShift: -4*(4-i)) bitAnd: 16rF. 16r11111111 * nibble]) "fill 32 bits with each 4-bit nibble" ! ! !Form class methodsFor: 'instance creation' stamp: 'jm 3/27/98 16:26'! extent: extentPoint offset: offsetPoint "Answer an instance of me with a blank bitmap of depth 1." ^ (self extent: extentPoint depth: 1) offset: offsetPoint ! ! !Form class methodsFor: 'instance creation' stamp: 'tk 2/19/1999 07:20'! fromBinaryStream: aBinaryStream "Read a Form or ColorForm from given file, using the first byte of the file to guess its format. Currently handles: GIF, uncompressed BMP, and both old and new DisplayObject writeOn: formats, JPEG, and PCX. Return nil if the file could not be read or was of an unrecognized format." | firstByte | aBinaryStream binary. firstByte _ aBinaryStream next. firstByte = 1 ifTrue: [ "old Squeakform format" ^ self new readFromOldFormat: aBinaryStream]. firstByte = 2 ifTrue: [ "new Squeak form format" ^ self new readFrom: aBinaryStream]. firstByte = $B asciiValue ifTrue: [ "BMP format" aBinaryStream skip: - 1. ^ self fromBMPFile: aBinaryStream]. "Try for JPG, GIF, or PCX..." "Note: The following call closes the stream." ^ Smalltalk imageReaderClass formFromStream: aBinaryStream ! ! !Form class methodsFor: 'instance creation'! fromDisplay: aRectangle "Answer an instance of me with bitmap initialized from the area of the display screen defined by aRectangle." ^ (self extent: aRectangle extent depth: Display depth) fromDisplay: aRectangle! ! !Form class methodsFor: 'instance creation'! fromDisplay: aRectangle using: oldForm "Like fromDisplay: only if oldForm is the right size, copy into it and answer it instead." ((oldForm ~~ nil) and: [oldForm extent = aRectangle extent]) ifTrue: [oldForm fromDisplay: aRectangle. ^ oldForm] ifFalse: [^ self fromDisplay: aRectangle]! ! !Form class methodsFor: 'instance creation' stamp: 'jm 1/11/1999 10:42'! fromFileNamed: fileName "Read a Form or ColorForm from the given file." | file form | file _ (FileStream readOnlyFileNamed: fileName) binary. form _ self fromBinaryStream: file. file close. ^ form ! ! !Form class methodsFor: 'instance creation'! fromUser "Answer an instance of me with bitmap initialized from the area of the display screen designated by the user. The grid for selecting an area is 1@1." ^self fromUser: 1 @ 1! ! !Form class methodsFor: 'instance creation'! fromUser: aPoint "Answer an instance of me with bitmap initialized from the area of the display screen designated by the user. The grid for selecting an area is aPoint." ^ self fromDisplay: (Rectangle fromUser: aPoint)! ! !Form class methodsFor: 'instance creation' stamp: 'jm 12/5/97 19:32'! fromUserWithExtent: anExtent "Answer an instance of me with bitmap initialized from the area of the display screen whose origin is designated by the user and whose size is anExtent" ^ self fromDisplay: (Rectangle originFromUser: anExtent) "(Form fromUserWithExtent: 50@50) displayAt: 10@10"! ! !Form class methodsFor: 'mode constants'! and "Answer the integer denoting the logical 'and' combination rule." ^1! ! !Form class methodsFor: 'mode constants'! blend "Answer the integer denoting BitBlt's alpha blend combination rule." ^24! ! !Form class methodsFor: 'mode constants' stamp: 'di 12/31/1998 14:02'! blendAlpha "Answer the integer denoting BitBlt's blend-with-constant-alpha rule." ^ 30! ! !Form class methodsFor: 'mode constants'! erase "Answer the integer denoting mode erase." ^4! ! !Form class methodsFor: 'mode constants'! erase1bitShape "Answer the integer denoting mode erase." ^ 26! ! !Form class methodsFor: 'mode constants'! oldErase1bitShape "Answer the integer denoting mode erase." ^ 17! ! !Form class methodsFor: 'mode constants'! oldPaint "Answer the integer denoting the 'paint' combination rule." ^16! ! !Form class methodsFor: 'mode constants'! over "Answer the integer denoting mode over." ^3! ! !Form class methodsFor: 'mode constants'! paint "Answer the integer denoting the 'paint' combination rule." ^25! ! !Form class methodsFor: 'mode constants' stamp: 'di 12/31/1998 14:02'! paintAlpha "Answer the integer denoting BitBlt's paint-with-constant-alpha rule." ^ 31! ! !Form class methodsFor: 'mode constants'! reverse "Answer the integer denoting mode reverse." ^6! ! !Form class methodsFor: 'mode constants'! under "Answer the integer denoting mode under." ^7! ! !Form class methodsFor: 'examples'! exampleBorder "Form exampleBorder" "This example demonstrates the border finding algorithm. Start by having the user sketch on the screen (end with option-click) and then select a rectangular area of the screen which includes all of the area to be filled. Finally, (with crosshair cursor), the user points at the interior of the region to be outlined, and the region begins with that place as its seed." | f r interiorPoint | Form exampleSketch. "sketch a little area with an enclosed region" r _ Rectangle fromUser. f _ Form fromDisplay: r. Cursor crossHair showWhile: [interiorPoint _ Sensor waitButton - r origin]. Cursor execute showWhile: [f shapeBorder: Color blue width: 2 interiorPoint: interiorPoint sharpCorners: false internal: false]. f displayOn: Display at: r origin ! ! !Form class methodsFor: 'examples'! exampleEdits "In Form category editing are messages edit and bitEdit that make it possible to create editors on instances of Form. This is the general form editor: | f | f _ Form fromUser. f edit. This is the general bit editor: | f | f _ Form fromUser. f bitEdit."! ! !Form class methodsFor: 'examples'! exampleMagnify | f m | f _ Form fromUser. m _ f magnify: f boundingBox by: 5 @ 5. m displayOn: Display at: Sensor waitButton "Form exampleMagnify."! ! !Form class methodsFor: 'examples'! exampleShrink | f s | f _ Form fromUser. s _ f shrink: f boundingBox by: 2 @ 5. s displayOn: Display at: Sensor waitButton "Form exampleShrink."! ! !Form class methodsFor: 'examples'! exampleSketch "This is a simple drawing algorithm to get a sketch on the display screen. Draws whenever mouse button down. Ends with option-click." | aPen color | aPen _ Pen new. color _ 0. [Sensor yellowButtonPressed] whileFalse: [aPen place: Sensor cursorPoint; color: (color _ color + 1). [Sensor redButtonPressed] whileTrue: [aPen goto: Sensor cursorPoint]]. Sensor waitNoButton. "Form exampleSketch"! ! !Form class methodsFor: 'examples'! exampleSpaceFill "Form exampleSpaceFill" "This example demonstrates the area filling algorithm. Starts by having the user sketch on the screen (ended by option-click) and then select a rectangular area of the screen which includes all of the area to be filled. Finally, (with crosshair cursor), the user points at the interior of some region to be filled, and the filling begins with that place as its seed." | f r interiorPoint | Form exampleSketch. "sketch a little area with an enclosed region" r _ Rectangle fromUser. f _ Form fromDisplay: r. Cursor crossHair showWhile: [interiorPoint _ Sensor waitButton - r origin]. Cursor execute showWhile: [f shapeFill: Color gray interiorPoint: interiorPoint]. f displayOn: Display at: r origin ! ! !Form class methodsFor: 'examples'! makeStar "See the similar example in OpaqueForm" | sampleForm pen | sampleForm _ Form extent: 50@50. "Make a form" pen _ Pen newOnForm: sampleForm. pen place: 24@50; turn: 18. "Draw a 5-pointed star on it." 1 to: 5 do: [:i | pen go: 19; turn: 72; go: 19; turn: -144]. ^ sampleForm " Form makeStar follow: [Sensor cursorPoint] while: [Sensor noButtonPressed] "! ! !Form class methodsFor: 'examples' stamp: 'tk 7/4/2000 12:08'! toothpaste: diam "Display restoreAfter: [Form toothpaste: 30]" "Draws wormlike lines by laying down images of spheres. See Ken Knowlton, Computer Graphics, vol. 15 no. 4 p352. Draw with mouse button down; terminate by option-click." | facade ball filter point queue port color q colors colr colr2 | colors _ Display depth = 1 ifTrue: [Array with: Color black] ifFalse: [Color red wheel: 12]. facade _ Form extent: diam@diam offset: (diam//-2) asPoint. (Form dotOfSize: diam) displayOn: facade at: (diam//2) asPoint clippingBox: facade boundingBox rule: Form under fillColor: Color white. #(1 2 3) do: [:x | "simulate facade by circles of gray" (Form dotOfSize: x*diam//5) displayOn: facade at: (diam*2//5) asPoint clippingBox: facade boundingBox rule: Form under fillColor: (Color perform: (#(black gray lightGray) at: x)). "facade displayAt: 50*x@50"]. ball _ Form dotOfSize: diam. color _ 8. [ true ] whileTrue: [port _ BitBlt current toForm: Display. "Expand 1-bit forms to any pixel depth" port colorMap: (Bitmap with: 0 with: 16rFFFFFFFF). queue _ OrderedCollection new: 32. 16 timesRepeat: [queue addLast: -20@-20]. Sensor waitButton. Sensor yellowButtonPressed ifTrue: [^ self]. filter _ Sensor cursorPoint. colr _ colors atWrap: (color _ color + 5). "choose increment relatively prime to colors size" colr2 _ colr alphaMixed: 0.3 with: Color white. [Sensor redButtonPressed or: [queue size > 0]] whileTrue: [filter _ filter * 4 + Sensor cursorPoint // 5. point _ Sensor redButtonPressed ifTrue: [filter] ifFalse: [-20@-20]. port copyForm: ball to: point rule: Form paint fillColor: colr. (q _ queue removeFirst) == nil ifTrue: [^ self]. "exit" Display depth = 1 ifTrue: [port copyForm: facade to: q rule: Form erase] ifFalse: [port copyForm: facade to: q rule: Form paint fillColor: colr2]. Sensor redButtonPressed ifTrue: [queue addLast: point]]]. ! ! !Form class methodsFor: 'examples'! xorHack: size "Display restoreAfter: [Form xorHack: 256]" "Draw a smiley face or stick figure, and end with option-click. Thereafter image gets 'processed' as long as you have button down. If you stop at just the right time, you'll see you figure upside down, and at the end of a full cycle, you'll see it perfectly restored. Dude -- this works in color too!!" | rect form i bb | rect _ 5@5 extent: size@size. Display fillWhite: rect; border: (rect expandBy: 2) width: 2. Display border: (rect topRight - (0@2) extent: rect extent*2 + 4) width: 2. Form exampleSketch. form _ Form fromDisplay: rect. bb _ form boundingBox. i _ 0. [Sensor yellowButtonPressed] whileFalse: [[Sensor redButtonPressed] whileTrue: [i _ i + 1. (Array with: 0@1 with: 0@-1 with: 1@0 with: -1@0) do: [:d | form copyBits: bb from: form at: d clippingBox: bb rule: Form reverse fillColor: nil]. form displayAt: rect topLeft. i+2\\size < 4 ifTrue: [(Delay forMilliseconds: 300) wait]]. (form magnify: form boundingBox by: 2@2) displayAt: rect topRight + (2@0). Sensor waitButton].! ! !Form class methodsFor: 'shut down' stamp: 'ar 5/28/2000 23:35'! shutDown "Form shutDown" "Compress all instances in the system. Will decompress on demand..." Form allInstancesDo: [:f | f hibernate]. ColorForm allInstancesDo: [:f | f hibernate].! ! !Form class methodsFor: 'BMP file reading' stamp: 'jm 4/18/98 19:11'! bmp24BitPixelDataFrom: aBinaryStream width: w height: h "Read 24-bit pixel data from the given a BMP stream." | form rowBytes line blackPixelValue pixelLine pixIndex rgb | form _ Form extent: w@h depth: 32. rowBytes _ (((24 * w) + 31) // 32) * 4. line _ Form extent: w@1 depth: 32. blackPixelValue _ Color black pixelValueForDepth: 32. 1 to: h do: [:i | pixelLine _ aBinaryStream next: rowBytes. pixIndex _ 1. 1 to: w do: [:j | rgb _ (pixelLine at: pixIndex) + ((pixelLine at: pixIndex + 1) bitShift: 8) + ((pixelLine at: pixIndex + 2) bitShift: 16). "BMP's don't support transparency, so map zero pixels to black" rgb = 0 ifTrue: [rgb _ blackPixelValue]. line bits at: j put: rgb. pixIndex _ pixIndex + 3]. form copy: line boundingBox from: line to: 0@(h - i) rule: Form over]. ^ form ! ! !Form class methodsFor: 'BMP file reading' stamp: 'di 2/3/1999 07:44'! bmpColorsFrom: aBinaryStream count: colorCount depth: depth "Read colorCount BMP color map entries from the given binary stream. Answer an array of Colors." | maxLevel colors b g r | colorCount = 0 ifTrue: [ "this BMP file does not have a color map" "default monochrome color map" depth = 1 ifTrue: [^ Array with: Color white with: Color black]. "default gray-scale color map" maxLevel _ (2 raisedTo: depth) - 1. ^ (0 to: maxLevel) collect: [:level | Color gray: (level asFloat / maxLevel)]]. colors _ Array new: colorCount. 1 to: colorCount do: [:i | b _ aBinaryStream next. g _ aBinaryStream next. r _ aBinaryStream next. aBinaryStream skip: 1. colors at: i put: (Color r: r g: g b: b range: 255)]. ^ colors ! ! !Form class methodsFor: 'BMP file reading' stamp: 'jm 4/18/98 20:03'! bmpPixelDataFrom: aBinaryStream width: w height: h depth: d "Read uncompressed pixel data of depth d from the given BMP stream, where d is 1, 4, or 8." | form bytesPerRow pixelData pixelLine startIndex | form _ ColorForm extent: w@h depth: d. "color map filled in by caller" bytesPerRow _ (((d* w) + 31) // 32) * 4. pixelData _ ByteArray new: bytesPerRow * h. h to: 1 by: -1 do: [:y | pixelLine _ aBinaryStream next: bytesPerRow. startIndex _ ((y - 1) * bytesPerRow) + 1. pixelData replaceFrom: startIndex to: startIndex + bytesPerRow - 1 with: pixelLine startingAt: 1]. form bits copyFromByteArray: pixelData. ^ form ! ! !Form class methodsFor: 'BMP file reading' stamp: 'jm 1/12/1999 17:37'! fromBMPFile: aBinaryStream "Read a BMP format image from the given binary stream." "Form fromBMPFile: (HTTPSocket httpGet: 'http://anHTTPServer/squeak/squeakers.bmp' accept: 'image/bmp')" | fType fSize reserved pixDataStart hdrSize w h planes d compressed colorCount colors colorForm | (aBinaryStream isMemberOf: String) ifTrue: [^ nil]. "a network error message" aBinaryStream binary. fType _ aBinaryStream nextLittleEndianNumber: 2. fSize _ aBinaryStream nextLittleEndianNumber: 4. reserved _ aBinaryStream nextLittleEndianNumber: 4. pixDataStart _ aBinaryStream nextLittleEndianNumber: 4. hdrSize _ aBinaryStream nextLittleEndianNumber: 4. w _ aBinaryStream nextLittleEndianNumber: 4. h _ aBinaryStream nextLittleEndianNumber: 4. planes _ aBinaryStream nextLittleEndianNumber: 2. d _ aBinaryStream nextLittleEndianNumber: 2. compressed _ aBinaryStream nextLittleEndianNumber: 4. aBinaryStream nextLittleEndianNumber: 4. "biSizeImage" aBinaryStream nextLittleEndianNumber: 4. "biXPelsPerMeter" aBinaryStream nextLittleEndianNumber: 4. "biYPelsPerMeter" colorCount _ aBinaryStream nextLittleEndianNumber: 4. aBinaryStream nextLittleEndianNumber: 4. "biClrImportant" ((fType = 19778) & (reserved = 0) & (planes = 1) & (hdrSize = 40) & (fSize <= aBinaryStream size)) ifFalse: [self error: 'Bad BMP file header']. compressed = 0 ifFalse: [self error: 'Can only read uncompressed BMP files']. d = 24 ifTrue: [ aBinaryStream position: pixDataStart. ^ self bmp24BitPixelDataFrom: aBinaryStream width: w height: h]. "read the color map" "Note: some programs (e.g. Photoshop 4.0) apparently do not set colorCount; assume that any data between the end of the header and the start of the pixel data is the color map" colorCount _ (pixDataStart - 54) // 4. colors _ self bmpColorsFrom: aBinaryStream count: colorCount depth: d. "read the pixel data" aBinaryStream position: pixDataStart. colorForm _ self bmpPixelDataFrom: aBinaryStream width: w height: h depth: d. colorForm colors: colors. ^ colorForm ! ! !Form class methodsFor: 'BMP file reading' stamp: 'mdr 8/31/2000 19:01'! fromBMPFileNamed: fileName "Form fromBMPFileNamed: 'FulS.bmp'" | fileStream result | fileStream _ (FileStream readOnlyFileNamed: fileName) binary. result _ self fromBMPFile: fileStream. fileStream close. ^ result ! ! Object subclass: #FormButtonCache instanceVariableNames: 'offset form value initialState ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Editors'! !FormButtonCache commentStamp: '' prior: 0! My instances are used to save information needed to construct the switch in a menu for a FormEditor. A collection of my instances is stored as a class variable of FormMenuView.! !FormButtonCache methodsFor: 'accessing'! form "Answer the receiver's form, the image of the button on the screen." ^form! ! !FormButtonCache methodsFor: 'accessing'! form: aForm "Set the receiver's form to be the argument." form _ aForm! ! !FormButtonCache methodsFor: 'accessing'! initialState "Answer the receiver's initial state, on or off." ^initialState! ! !FormButtonCache methodsFor: 'accessing'! initialState: aBoolean "Set the receiver's initial state, on or off, to be the argument." initialState _ aBoolean! ! !FormButtonCache methodsFor: 'accessing'! offset "Answer the receiver's offset, its relative position for displaying the button." ^offset! ! !FormButtonCache methodsFor: 'accessing'! offset: anInteger "Set the receiver's offset." offset _ anInteger! ! !FormButtonCache methodsFor: 'accessing'! value "Answer the receiver's value, the keyboard key that selects the button." ^value! ! !FormButtonCache methodsFor: 'accessing'! value: aCharacter "Set the receiver's key character." value _ aCharacter! ! Canvas subclass: #FormCanvas instanceVariableNames: 'origin clipRect form port shadowColor ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !FormCanvas commentStamp: '' prior: 0! Note that when shadowDrawing is true, shadowStipple may be either a color, for a solid shadow of the given color, or it may be a stipple used to simulate gray shading when the display cannot support alpha blending.! !FormCanvas methodsFor: 'initialize-release' stamp: 'ar 5/27/2000 21:51'! finish "If there are any pending operations on the receiver complete them. Do not return before all modifications have taken effect." form finish! ! !FormCanvas methodsFor: 'initialize-release' stamp: 'ar 2/17/2000 00:21'! reset origin _ 0@0. "origin of the top-left corner of this cavas" clipRect _ (0@0 corner: 10000@10000). "default clipping rectangle" self shadowColor: nil.! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 5/28/2000 17:11'! allocateForm: extentPoint "Allocate a new form which is similar to the receiver" ^form allocateForm: extentPoint! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 14:06'! clipRect "Return the currently active clipping rectangle" ^ clipRect translateBy: origin negated! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 5/28/2000 14:42'! contentsOfArea: aRectangle into: aForm self flush. port contentsOfArea: ((aRectangle origin + origin) negated extent: aRectangle extent) into: aForm. ^aForm! ! !FormCanvas methodsFor: 'accessing'! depth ^ form depth ! ! !FormCanvas methodsFor: 'accessing'! extent ^ form extent! ! !FormCanvas methodsFor: 'accessing'! form ^ form! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 14:10'! origin "Return the current origin for drawing operations" ^ origin! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 2/17/2000 00:26'! shadowColor ^shadowColor! ! !FormCanvas methodsFor: 'accessing' stamp: 'ar 2/17/2000 00:26'! shadowColor: aColor shadowColor _ aColor! ! !FormCanvas methodsFor: 'testing' stamp: 'ar 2/17/2000 00:24'! isShadowDrawing ^ self shadowColor notNil! ! !FormCanvas methodsFor: 'testing' stamp: 'ar 6/22/1999 14:08'! isVisible: aRectangle "Optimization" (aRectangle right + origin x) < clipRect left ifTrue: [^ false]. (aRectangle left + origin x) > clipRect right ifTrue: [^ false]. (aRectangle bottom + origin y) < clipRect top ifTrue: [^ false]. (aRectangle top + origin y) > clipRect bottom ifTrue: [^ false]. ^ true ! ! !FormCanvas methodsFor: 'copying' stamp: 'jm 8/2/97 14:00'! copy "Make a copy the receiver on the same underlying Form but with its own grafPort." ^ self clone resetGrafPort ! ! !FormCanvas methodsFor: 'copying' stamp: 'ar 6/17/1999 02:51'! copyClipRect: aRectangle ^ self copyOrigin: origin clipRect: (aRectangle translateBy: origin) ! ! !FormCanvas methodsFor: 'copying' stamp: 'ar 6/17/1999 02:52'! copyOffset: aPoint ^ self copyOrigin: origin + aPoint clipRect: clipRect! ! !FormCanvas methodsFor: 'copying' stamp: 'ar 6/17/1999 02:52'! copyOffset: aPoint clipRect: sourceClip "Make a copy of me offset by aPoint, and further clipped by sourceClip, a rectangle in the un-offset coordinates" ^ self copyOrigin: aPoint + origin clipRect: ((sourceClip translateBy: origin) intersect: clipRect)! ! !FormCanvas methodsFor: 'copying' stamp: 'ar 6/17/1999 02:52'! copyOrigin: aPoint clipRect: aRectangle "Return a copy of this canvas with the given origin. The clipping rectangle of this canvas is the intersection of the given rectangle and the receiver's current clipping rectangle. This allows the clipping rectangles of nested clipping morphs to be composed." ^ self copy setOrigin: aPoint clipRect: (clipRect intersect: aRectangle)! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 5/14/2000 15:50'! fillColor: c "Note: This always fills, even if the color is transparent." self setClearColor: c. port fillRect: form boundingBox offset: origin.! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 2/17/2000 00:12'! line: pt1 to: pt2 brushForm: brush | offset | offset _ origin. self setPaintColor: Color black. port sourceForm: brush; fillColor: nil; sourceRect: brush boundingBox; colorMap: (brush colormapIfNeededForDepth: self depth); drawFrom: (pt1 + offset) to: (pt2 + offset)! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 2/16/2000 22:07'! line: pt1 to: pt2 width: w color: c | offset | offset _ origin - (w // 2) asPoint. self setFillColor: c. port width: w; height: w; drawFrom: (pt1 + offset) to: (pt2 + offset)! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 2/17/2000 00:23'! paragraph: para bounds: bounds color: c | scanner | self setPaintColor: c. scanner _ port displayScannerFor: para foreground: (self shadowColor ifNil:[c]) background: Color transparent ignoreColorChanges: self shadowColor notNil. para displayOn: self using: scanner at: (bounds topLeft + origin). ! ! !FormCanvas methodsFor: 'drawing'! point: pt color: c form colorAt: (pt + origin) put: c.! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 5/18/2000 18:35'! text: s bounds: boundsRect font: fontOrNil color: c | scanner | scanner _ DisplayScanner quickPrintOn: form box: ((boundsRect translateBy: origin) intersect: clipRect) truncated font: fontOrNil color: (self shadowColor ifNil:[c]). scanner drawString: s at: boundsRect topLeft + origin! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 03:02'! clipBy: aRectangle during: aBlock "Set a clipping rectangle active only during the execution of aBlock. Note: In the future we may want to have more general clip shapes - not just rectangles" ^aBlock value: (self copyClipRect: aRectangle)! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'RAA 8/15/2000 10:53'! transform2By: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize ^super transform2By: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize ! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'ar 5/25/2000 18:04'! transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize "Note: This method has been originally copied from TransformationMorph." | innerRect patchRect sourceQuad warp start subCanvas | (aDisplayTransform isPureTranslation) ifTrue:[ ^aBlock value: (self copyOffset: aDisplayTransform offset negated truncated clipRect: aClipRect) ]. "Prepare an appropriate warp from patch to innerRect" innerRect _ aClipRect. patchRect _ aDisplayTransform globalBoundsToLocal: innerRect. sourceQuad _ (aDisplayTransform sourceQuadFor: innerRect) collect: [:p | p - patchRect topLeft]. warp _ self warpFrom: sourceQuad toRect: innerRect. warp cellSize: cellSize. "Render the submorphs visible in the clipping rectangle, as patchForm" start _ (self depth = 1 and: [self isShadowDrawing not]) "If this is true B&W, then we need a first pass for erasure." ifTrue: [1] ifFalse: [2]. start to: 2 do: [:i | "If i=1 we first make a shadow and erase it for opaque whites in B&W" subCanvas _ self class extent: patchRect extent depth: self depth. i=1 ifTrue: [subCanvas shadowColor: Color black. warp combinationRule: Form erase] ifFalse: [self isShadowDrawing ifTrue: [subCanvas shadowColor: self shadowColor]. warp combinationRule: Form paint]. subCanvas translateBy: patchRect topLeft negated during:[:offsetCanvas| aBlock value: offsetCanvas]. warp sourceForm: subCanvas form; warpBits. warp sourceForm: nil. subCanvas _ nil "release space for next loop"] ! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 03:03'! translateBy: delta clippingTo: aRectangle during: aBlock "Set a translation and clipping rectangle only during the execution of aBlock." ^aBlock value: (self copyOffset: delta clipRect: aRectangle)! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 03:02'! translateBy: delta during: aBlock "Set a translation only during the execution of aBlock." ^aBlock value: (self copyOffset: delta)! ! !FormCanvas methodsFor: 'drawing-support' stamp: 'ar 6/17/1999 02:55'! translateTo: newOrigin clippingTo: aRectangle during: aBlock "Set a new origin and clipping rectangle only during the execution of aBlock." aBlock value: (self copyOrigin: newOrigin clipRect: aRectangle)! ! !FormCanvas methodsFor: 'other' stamp: 'ar 11/11/1998 22:57'! asBalloonCanvas ^(BalloonCanvas on: form) setOrigin: origin clipRect: clipRect! ! !FormCanvas methodsFor: 'other'! flushDisplay Display deferUpdates: false; forceDisplayUpdate.! ! !FormCanvas methodsFor: 'other'! forceToScreen:rect ^Display forceToScreen:rect. ! ! !FormCanvas methodsFor: 'other' stamp: 'ar 5/28/2000 17:07'! printOn: aStream super printOn: aStream. aStream nextPutAll:' on: '; print: form.! ! !FormCanvas methodsFor: 'other'! showAt: pt ^ form displayAt: pt! ! !FormCanvas methodsFor: 'other' stamp: 'ar 5/28/2000 12:09'! showAt: pt invalidRects: updateRects | blt | blt _ (BitBlt current toForm: Display) sourceForm: form; combinationRule: Form over. updateRects do: [:rect | blt sourceRect: rect; destOrigin: rect topLeft + pt; copyBits]! ! !FormCanvas methodsFor: 'other' stamp: 'ar 5/28/2000 12:12'! warpFrom: sourceQuad toRect: destRect ^ (WarpBlt current toForm: port destForm) combinationRule: Form paint; sourceQuad: sourceQuad destRect: (destRect translateBy: origin); clipRect: clipRect! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 14:52'! portClass "Return the class used as port" ^Display defaultBitBltClass asGrafPort! ! !FormCanvas methodsFor: 'private' stamp: 'RAA 12/17/2000 13:24'! privateClipRect ^clipRect! ! !FormCanvas methodsFor: 'private' stamp: 'RAA 12/17/2000 13:25'! privatePort ^port! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/25/2000 17:25'! resetGrafPort "Private!! Create a new grafPort for a new copy." port _ self portClass toForm: form. port clipRect: clipRect. ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 14:43'! setClearColor: aColor "Install a new clear color - e.g., a color is used for clearing the background" | clearColor | port isFXBlt ifTrue:[port sourceMap: nil; destMap: nil; colorMap: nil; sourceKey: nil]. clearColor _ aColor ifNil:[Color transparent]. clearColor isColor ifFalse:[ (clearColor isKindOf: InfiniteForm) ifFalse:[^self error:'Cannot install color']. ^port fillPattern: clearColor; combinationRule: Form over]. "Okay, so clearColor really *is* a color" port sourceForm: nil. port combinationRule: Form over. port fillPattern: clearColor. self depth = 8 ifTrue:[ "Use a stipple pattern" port fillColor: (clearColor balancedPatternForDepth: 8)]. ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 14:45'! setFillColor: aColor "Install a new color used for filling." | screen patternWord fillColor | port isFXBlt ifTrue:[port sourceMap: nil; destMap: nil; colorMap: nil; sourceKey: nil]. fillColor _ self shadowColor ifNil:[aColor]. fillColor ifNil:[fillColor _ Color transparent]. fillColor isColor ifFalse:[ (fillColor isKindOf: InfiniteForm) ifFalse:[^self error:'Cannot install color']. ^port fillPattern: fillColor; combinationRule: Form over]. "Okay, so fillColor really *is* a color" port sourceForm: nil. fillColor isTranslucent ifFalse:[ port combinationRule: Form over. port fillPattern: fillColor. self depth = 8 ifTrue:[ "In 8 bit depth it's usually a good idea to use a stipple pattern" port fillColor: (fillColor balancedPatternForDepth: 8)]. ^self]. "fillColor is some translucent color" (port isFXBlt and:[self depth >= 8]) ifTrue:[ "FXBlt setup for full alpha mapped transfer" port fillColor: (fillColor bitPatternForDepth: 32). port destMap: form colormapToARGB. port colorMap: form colormapFromARGB. ^port combinationRule: Form blend]. self depth > 8 ifTrue:[ "BitBlt setup for alpha masked transfer" port fillPattern: fillColor. self depth = 16 ifTrue:[port alphaBits: fillColor privateAlpha; combinationRule: 30] ifFalse:[port combinationRule: Form blend]. ^self]. "Can't represent actual transparency -- use stipple pattern" screen _ Color translucentMaskFor: fillColor alpha depth: self depth. patternWord _ fillColor pixelWordForDepth: self depth. port fillPattern: (screen collect: [:maskWord | maskWord bitAnd: patternWord]). port combinationRule: Form paint. ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/25/2000 17:25'! setForm: aForm self reset. form _ aForm. port _ self portClass toForm: form. ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 6/22/1999 14:06'! setOrigin: aPoint clipRect: aRectangle origin _ aPoint. clipRect _ aRectangle. port clipRect: aRectangle. ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 14:49'! setPaintColor: aColor "Install a new color used for filling." | paintColor screen patternWord | port isFXBlt ifTrue:[port sourceMap: nil; destMap: nil; colorMap: nil; sourceKey: nil]. paintColor _ self shadowColor ifNil:[aColor]. paintColor ifNil:[paintColor _ Color transparent]. paintColor isColor ifFalse:[ (paintColor isKindOf: InfiniteForm) ifFalse:[^self error:'Cannot install color']. ^port fillPattern: paintColor; combinationRule: Form paint]. "Okay, so paintColor really *is* a color" port sourceForm: nil. (paintColor isTranslucent) ifFalse:[ port fillPattern: paintColor. port combinationRule: Form paint. self depth = 8 ifTrue:[ port fillColor: (paintColor balancedPatternForDepth: 8)]. ^self]. "paintColor is translucent color" (port isFXBlt and:[self depth >= 8]) ifTrue:[ "FXBlt setup for alpha mapped transfer" port fillPattern: paintColor. port fillColor: (paintColor bitPatternForDepth: 32). port destMap: form colormapToARGB. port colorMap: form colormapFromARGB. port combinationRule: Form blend. ^self]. self depth > 8 ifTrue:[ "BitBlt setup for alpha mapped transfer" port fillPattern: paintColor. self depth = 16 ifTrue:[port alphaBits: paintColor privateAlpha; combinationRule: 31] ifFalse:[port combinationRule: Form blend]. ^self]. "Can't represent actual transparency -- use stipple pattern" screen _ Color translucentMaskFor: paintColor alpha depth: self depth. patternWord _ paintColor pixelWordForDepth: self depth. port fillPattern: (screen collect: [:maskWord | maskWord bitAnd: patternWord]). port combinationRule: Form paint ! ! !FormCanvas methodsFor: 'private' stamp: 'ar 5/28/2000 14:50'! setStencilColor: aColor form: sourceForm "Install a new color used for stenciling through FXBlt. Stenciling in general is done mapping all colors of source form to the stencil color and installing the appropriate source key. However, due to possible transparency we may have to install the color map as source map so that sourceForm gets mapped to a 32bit ARGB pixel value before the color combination is done. If we don't need translucency we can just use the regular color map (faster!!)" | stencilColor screen patternWord | port isFXBlt ifFalse:[^self]. "Not appropriate for BitBlt" port sourceMap: nil; destMap: nil; colorMap: nil; sourceKey: nil. stencilColor _ self shadowColor ifNil:[aColor]. stencilColor isColor ifFalse:[^self]. "No way" (stencilColor isTranslucent) ifFalse:[ "If the paint color is not translucent we can use a simpler transformation going through a single color map." port sourceKey: 0. "The transparent source key" port fillPattern: stencilColor. port colorMap: (ColorMap colors: port fillColor). port fillColor: nil. ^port combinationRule: Form over]. (self depth >= 8) ifTrue:[ "For transparent stenciling, things are more complicated. We need to install the transparent stencil color as source map so that all colors are mapped to the stencil color and afterwards blended with the destination." port sourceKey: 0. "The transparent source key" port fillPattern: stencilColor. port destMap: form colormapToARGB. port colorMap: form colormapFromARGB. port sourceMap: (ColorMap colors: (stencilColor bitPatternForDepth: 32)). port fillColor: nil. port combinationRule: Form blend. ^self]. "Translucent stenciling in < 8bit depth requires three parts, a color map, a fill pattern and the appropriate combination rule." port colorMap: (ColorMap colors: (Color maskingMap: form depth)). screen _ Color translucentMaskFor: stencilColor alpha depth: self depth. patternWord _ stencilColor pixelWordForDepth: self depth. port fillPattern: (screen collect: [:maskWord | maskWord bitAnd: patternWord]). port combinationRule: Form paint! ! !FormCanvas methodsFor: 'converting' stamp: 'ar 2/17/2000 00:17'! asShadowDrawingCanvas "Note: This is sort of an optimization here since since the logic is all there" ^self copy shadowColor: (Color black alpha: 0.5)! ! !FormCanvas methodsFor: 'converting' stamp: 'ar 2/17/2000 00:16'! asShadowDrawingCanvas: aColor "Note: This is sort of an optimization here since since the logic is all there" ^self copy shadowColor: aColor! ! !FormCanvas methodsFor: 'drawing-rectangles' stamp: 'RAA 7/28/2000 07:39'! balloonFillRectangle: aRectangle fillStyle: aFillStyle self asBalloonCanvas fillRectangle: aRectangle fillStyle: aFillStyle.! ! !FormCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 10/26/2000 19:26'! fillRectangle: aRectangle fillStyle: aFillStyle "Fill the given rectangle." | pattern | self shadowColor ifNotNil: [^self fillRectangle: aRectangle color: aFillStyle asColor]. (aFillStyle isKindOf: InfiniteForm) ifTrue: [ ^self infiniteFillRectangle: aRectangle fillStyle: aFillStyle ]. (aFillStyle isSolidFill) ifTrue:[^self fillRectangle: aRectangle color: aFillStyle asColor]. "We have a very special case for filling with infinite forms" (aFillStyle isBitmapFill and:[aFillStyle origin = (0@0)]) ifTrue:[ pattern _ aFillStyle form. (aFillStyle direction = (pattern width @ 0) and:[aFillStyle normal = (0@pattern height)]) ifTrue:[ "Can use an InfiniteForm" ^self fillRectangle: aRectangle color: (InfiniteForm with: pattern)]. ]. "Use a BalloonCanvas instead" self balloonFillRectangle: aRectangle fillStyle: aFillStyle.! ! !FormCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 5/14/2000 15:50'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: borderColor | rect | rect _ r translateBy: origin. "draw the border of the rectangle" borderColor isTransparent ifFalse:[ self setFillColor: borderColor. (r area > 10000 or: [fillColor isTranslucent]) ifTrue: [ port frameRect: rect borderWidth: borderWidth. ] ifFalse: ["for small rectangles, it's faster to fill the entire outer rectangle than to compute and fill the border rects" port fillRect: rect offset: origin]]. "fill the inside" fillColor isTransparent ifFalse: [self setFillColor: fillColor. port fillRect: (rect insetBy: borderWidth) offset: origin].! ! !FormCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 2/16/2000 22:07'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth topLeftColor: topLeftColor bottomRightColor: bottomRightColor | w h rect | "First use quick code for top and left borders and fill" self frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: topLeftColor. "Now use slow code for bevelled bottom and right borders" bottomRightColor isTransparent ifFalse: [ borderWidth isNumber ifTrue: [w _ h _ borderWidth] ifFalse: [w _ borderWidth x. h _ borderWidth y]. rect _ r translateBy: origin. self setFillColor: bottomRightColor. port frameRectRight: rect width: w; frameRectBottom: rect height: h]. ! ! !FormCanvas methodsFor: 'drawing-rectangles' stamp: 'RAA 9/20/2000 17:16'! infiniteFillRectangle: aRectangle fillStyle: aFillStyle ^aFillStyle displayOnPort: (port clippedBy: (aRectangle translateBy: origin)) at: aRectangle origin + origin ! ! !FormCanvas methodsFor: 'drawing-ovals' stamp: 'RAA 11/6/2000 15:21'! balloonFillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc self asBalloonCanvas fillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc! ! !FormCanvas methodsFor: 'drawing-ovals' stamp: 'ar 1/30/2001 23:10'! fillOval: r color: fillColor borderWidth: borderWidth borderColor: borderColor | rect | "draw the border of the oval" rect _ (r translateBy: origin) truncated. borderColor isTransparent ifFalse:[ self setFillColor: borderColor. (r area > 10000 or: [fillColor isTranslucent]) ifTrue: [port frameOval: rect borderWidth: borderWidth] ifFalse: [port fillOval: rect]]. "faster this way" "fill the inside" fillColor isTransparent ifFalse: [self setFillColor: fillColor. port fillOval: (rect insetBy: borderWidth)]. ! ! !FormCanvas methodsFor: 'drawing-ovals' stamp: 'RAA 11/6/2000 16:42'! fillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc "Fill the given oval." self flag: #bob. "this and its siblings could be moved up to Canvas with the right #balloonFillOval:..." self shadowColor ifNotNil: [^self fillOval: aRectangle color: aFillStyle asColor borderWidth: bw borderColor: bc]. (aFillStyle isBitmapFill and:[aFillStyle isKindOf: InfiniteForm]) ifTrue:[ self flag: #fixThis. ^self fillOval: aRectangle color: aFillStyle borderWidth: bw borderColor: bc]. (aFillStyle isSolidFill) ifTrue:[ ^self fillOval: aRectangle color: aFillStyle asColor borderWidth: bw borderColor: bc]. "Use a BalloonCanvas instead" self balloonFillOval: aRectangle fillStyle: aFillStyle borderWidth: bw borderColor: bc! ! !FormCanvas methodsFor: 'drawing-polygons' stamp: 'ar 6/18/1999 08:57'! drawPolygon: vertices color: aColor borderWidth: bw borderColor: bc "Generalize for the BalloonCanvas" ^self drawPolygon: vertices fillStyle: aColor borderWidth: bw borderColor: bc! ! !FormCanvas methodsFor: 'drawing-polygons' stamp: 'ar 12/6/2000 14:59'! drawPolygon: vertices fillStyle: aFillStyle borderWidth: bw borderColor: bc "Use a BalloonCanvas" self asBalloonCanvas drawPolygon: vertices asArray fillStyle: (self shadowColor ifNil:[aFillStyle]) borderWidth: bw borderColor: bc! ! !FormCanvas methodsFor: 'drawing-images' stamp: 'ar 5/28/2000 23:47'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Draw the portion of the given Form defined by sourceRect at the given point using the given BitBlt combination rule." port isFXBlt ifTrue:[port sourceKey: nil; sourceMap: nil; destMap: nil; colorMap: (aForm colormapIfNeededFor: form); fillPattern: nil] ifFalse:[port colorMap: (aForm colormapIfNeededForDepth: form depth); fillColor: nil]. port image: aForm at: aPoint + origin sourceRect: sourceRect rule: rule.! ! !FormCanvas methodsFor: 'drawing-images' stamp: 'ar 5/28/2000 14:40'! stencil: stencilForm at: aPoint sourceRect: sourceRect color: aColor "Flood this canvas with aColor wherever stencilForm has non-zero pixels" port isFXBlt "FXBlt has a very different setup" ifTrue:[self setStencilColor: aColor form: stencilForm] ifFalse:[self setPaintColor: aColor. port colorMap: (Color maskingMap: stencilForm depth)]. port stencil: stencilForm at: aPoint + origin sourceRect: sourceRect.! ! !FormCanvas methodsFor: 'drawing-general' stamp: 'ar 10/26/2000 19:41'! roundCornersOf: aMorph during: aBlock aMorph wantsRoundedCorners ifFalse:[^aBlock value]. (self seesNothingOutside: (CornerRounder rectWithinCornersOf: aMorph bounds)) ifTrue: ["Don't bother with corner logic if the region is inside them" ^ aBlock value]. CornerRounder roundCornersOf: aMorph on: self displayBlock: aBlock borderWidth: aMorph borderWidth corners: aMorph roundedCorners! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormCanvas class instanceVariableNames: ''! !FormCanvas class methodsFor: 'instance creation'! extent: aPoint ^ self extent: aPoint depth: Display depth ! ! !FormCanvas class methodsFor: 'instance creation'! extent: extent depth: depth ^ self new setForm: (Form extent: extent depth: depth)! ! !FormCanvas class methodsFor: 'instance creation' stamp: 'jm 8/2/97 13:54'! on: aForm ^ self new setForm: aForm ! ! !FormCanvas class methodsFor: 'testing' stamp: 'jm 6/2/1998 07:46'! test1 "FormCanvas test1" | canvas | canvas _ FormCanvas extent: 200@200. canvas fillColor: (Color black). canvas line: 10@10 to: 50@30 width: 1 color: (Color red). canvas frameRectangle: ((20@20) corner: (120@120)) width: 4 color: (Color green). canvas point: 100@100 color: (Color black). canvas text: 'Hello, World!!' at: 40@40 font: nil color: (Color cyan). canvas fillRectangle: ((10@80) corner: (31@121)) color: (Color magenta). canvas fillOval: ((10@80) corner: (31@121)) color: (Color cyan). canvas frameOval: ((40@80) corner: (61@121)) color: (Color blue). canvas frameOval: ((70@80) corner: (91@121)) width: 3 color: (Color red alpha: 0.2). canvas fillRectangle: ((130@30) corner: (170@80)) color: (Color lightYellow). canvas showAt: 0@0. ! ! !FormCanvas class methodsFor: 'testing' stamp: 'ar 6/17/1999 02:39'! test2 "FormCanvas test2" | baseCanvas p | baseCanvas _ FormCanvas extent: 200@200. p _ Sensor cursorPoint. [Sensor anyButtonPressed] whileFalse: [ baseCanvas translateBy: (Sensor cursorPoint - p) during:[:canvas| canvas fillColor: Color white. canvas line: 10@10 to: 50@30 width: 1 color: Color red. canvas frameRectangle: ((20@20) corner: (120@120)) width: 4 color: Color green. canvas point: 100@100 color: Color black. canvas text: 'Hello, World!!' at: 40@40 font: nil color: Color cyan. canvas fillRectangle: ((10@80) corner: (31@121)) color: Color magenta. canvas fillOval: ((10@80) corner: (31@121)) color: Color cyan. canvas frameOval: ((40@80) corner: (61@121)) color: Color blue. canvas frameOval: ((70@80) corner: (91@121)) width: 3 color: Color red. canvas fillRectangle: ((130@30) corner: (170@80)) color: Color lightYellow. canvas showAt: 0@0]]. ! ! !FormCanvas class methodsFor: 'testing' stamp: 'ar 2/17/2000 00:17'! test3 "FormCanvas test3" | baseCanvas | baseCanvas _ FormCanvas extent: 200@200. baseCanvas fillColor: Color white. baseCanvas translateBy: 10@10 during:[:canvas| canvas shadowColor: (Color black alpha: 0.5). canvas line: 10@10 to: 50@30 width: 1 color: Color red. canvas frameRectangle: ((20@20) corner: (120@120)) width: 4 color: Color green. canvas point: 100@100 color: Color black. canvas text: 'Hello, World!!' at: 40@40 font: nil color: Color cyan. canvas fillRectangle: ((10@80) corner: (31@121)) color: Color magenta. canvas fillOval: ((10@80) corner: (31@121)) color: Color cyan. canvas frameOval: ((40@80) corner: (61@121)) color: Color blue. canvas frameOval: ((70@80) corner: (91@121)) width: 3 color: Color red. canvas fillRectangle: ((130@30) corner: (170@80)) color: Color lightYellow. canvas showAt: 0@0. ].! ! MouseMenuController subclass: #FormEditor instanceVariableNames: 'form tool grid togglegrid mode previousTool color unNormalizedColor xgridOn ygridOn ' classVariableNames: 'BitEditKey BlackKey BlockKey ChangeGridsKey CurveKey DarkGrayKey EraseKey FlashCursor GrayKey InKey LightGrayKey LineKey OutKey OverKey RepeatCopyKey ReverseKey SelectKey SingleCopyKey TogglexGridKey ToggleyGridKey UnderKey WhiteKey YellowButtonMenu YellowButtonMessages YgridKey ' poolDictionaries: '' category: 'ST80-Editors'! !FormEditor commentStamp: '' prior: 0! I represent the basic editor for creating and modifying Forms. This is intended to be an easy to use general-purpose picture (bitMap) editor. I am a kind of MouseMenuController that creates a yellow button menu for accepting and canceling edits. My instances give up control if the cursor is outside the FormView or if a key on the keyboard is pressed.! !FormEditor methodsFor: 'initialize-release' stamp: 'sma 3/11/2000 15:07'! initialize super initialize. self setVariables! ! !FormEditor methodsFor: 'initialize-release'! release "Break the cycle between the Controller and its view. It is usually not necessary to send release provided the Controller's view has been properly released independently." super release. form _ nil! ! !FormEditor methodsFor: 'basic control sequence' stamp: 'sma 4/22/2000 12:56'! controlInitialize Cursor crossHair show. self normalizeColor: unNormalizedColor. sensor waitNoButton! ! !FormEditor methodsFor: 'basic control sequence'! controlTerminate "Resets the cursor to be the normal Smalltalk cursor." Cursor normal show. view updateDisplay! ! !FormEditor methodsFor: 'control defaults'! controlActivity super controlActivity. self dragForm! ! !FormEditor methodsFor: 'control defaults' stamp: 'sma 3/11/2000 15:07'! isControlActive ^ super isControlActive and: [sensor keyboardPressed not]! ! !FormEditor methodsFor: 'editing tools'! block "Allow the user to fill a rectangle with the gray tone and mode currently selected." | rectangle | rectangle _ Rectangle fromUser: grid. rectangle isNil ifFalse: [Display fill: (rectangle intersect: view insetDisplayBox) rule: mode fillColor: color]! ! !FormEditor methodsFor: 'editing tools' stamp: 'jm 3/27/98 15:01'! changeGridding "Allow the user to change the values of the horizontal and/or vertical grid modules. Does not change the primary tool." | response gridInteger gridX gridY | gridX _ togglegrid x. gridY _ togglegrid y. response _ FillInTheBlank request: 'Current horizontal gridding is: ', gridX printString, '. Type new horizontal gridding.'. response isEmpty ifFalse: [gridInteger _ Integer readFromString: response. gridX _ ((gridInteger max: 1) min: Display extent x)]. response _ FillInTheBlank request: 'Current vertical gridding is: ', gridY printString, '. Type new vertical gridding.'. response isEmpty ifFalse: [gridInteger _ Integer readFromString: response. gridY _ ((gridInteger max: 1) min: Display extent y)]. xgridOn ifTrue: [grid _ gridX @ grid y]. ygridOn ifTrue: [grid _ grid x @ gridY]. togglegrid _ gridX @ gridY. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! changeTool: aCharacter "Change the value of the instance variable tool to be the tool corresponding to aCharacter. Typically sent from a Switch in a FormMenuView." previousTool _ tool. tool _ self selectTool: aCharacter. (#(singleCopy repeatCopy line curve block) includes: tool) ifFalse: [self perform: tool]! ! !FormEditor methodsFor: 'editing tools'! colorBlack "Set the mask (color) to black. Leaves the tool set in its previous state." self setColor: Color black! ! !FormEditor methodsFor: 'editing tools'! colorDarkGray "Set the mask (color) to dark gray. Leaves the tool set in its previous state." self setColor: Color darkGray! ! !FormEditor methodsFor: 'editing tools'! colorGray "Set the color to gray. Leaves the tool set in its previous state." self setColor: Color gray. ! ! !FormEditor methodsFor: 'editing tools'! colorLightGray "Set the mask (color) to light gray. Leaves the tool set in its previous state." self setColor: Color lightGray! ! !FormEditor methodsFor: 'editing tools'! colorWhite "Set the color to white. Leaves the tool set in its previous state." self setColor: Color white! ! !FormEditor methodsFor: 'editing tools' stamp: '6/9/97 10:15 di'! curve "Conic-section specified by three points designated by: first point--press red button second point--release red button third point--click red button. The resultant curve on the display is displayed according to the current form and mode." | firstPoint secondPoint thirdPoint curve | "sensor noButtonPressed ifTrue: [^self]." firstPoint _ self cursorPoint. form displayOn: Display at: firstPoint clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) fillColor: color. secondPoint _ self trackFormUntil: [sensor noButtonPressed]. form displayOn: Display at: secondPoint clippingBox: view insetDisplayBox rule: Form reverse fillColor: color. thirdPoint _ self trackFormUntil: [sensor redButtonPressed].. form displayOn: Display at: thirdPoint clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) fillColor: color. form displayOn: Display at: secondPoint clippingBox: view insetDisplayBox rule: Form reverse fillColor: color. curve _ CurveFitter new. curve firstPoint: firstPoint. curve secondPoint: secondPoint. curve thirdPoint: thirdPoint. curve form: form. curve displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) fillColor: color. sensor waitNoButton! ! !FormEditor methodsFor: 'editing tools'! eraseMode "Set the mode for the tools that copy the form onto the display to erase. Leaves the tool set in its previous state." mode _ 4. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools' stamp: 'jm 3/27/98 14:59'! fileInForm "Ask the user for a file name and then recalls the Form in that file as the current source Form (form). Does not change the tool." | fileName | fileName _ FillInTheBlank request: 'File name?' initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. form _ Form fromFileNamed: fileName. tool _ previousTool. ! ! !FormEditor methodsFor: 'editing tools' stamp: 'jm 3/27/98 14:54'! fileOutForm "Ask the user for a file name and save the current source form under that name. Does not change the tool." | fileName | fileName _ FillInTheBlank request: 'File name?' initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. Cursor normal showWhile: [form writeOnFileNamed: fileName]. tool _ previousTool. ! ! !FormEditor methodsFor: 'editing tools'! line "Line is specified by two points from the mouse: first point--press red button; second point--release red button. The resultant line is displayed according to the current form and mode." | firstPoint endPoint | firstPoint _ self cursorPoint. endPoint _ self rubberBandFrom: firstPoint until: [sensor noButtonPressed]. (Line from: firstPoint to: endPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) fillColor: color! ! !FormEditor methodsFor: 'editing tools'! magnify "Allow for bit editing of an area of the Form. The user designates a rectangular area that is scaled by 5 to allow individual screens dots to be modified. Red button is used to set a bit to black, and yellow button is used to set a bit to white. Editing continues until the user depresses any key on the keyboard." | smallRect smallForm scaleFactor tempRect | scaleFactor _ 8@8. smallRect _ (Rectangle fromUser: grid) intersect: view insetDisplayBox. smallRect isNil ifTrue: [^self]. smallForm _ Form fromDisplay: smallRect. "Do this computation here in order to be able to save the existing display screen." tempRect _ BitEditor locateMagnifiedView: smallForm scale: scaleFactor. BitEditor openScreenViewOnForm: smallForm at: smallRect topLeft magnifiedAt: tempRect topLeft scale: scaleFactor. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools' stamp: 'jm 6/30/1999 15:46'! newSourceForm "Allow the user to define a new source form for the FormEditor. Copying the source form onto the display is the primary graphical operation. Resets the tool to be repeatCopy." | dForm interiorPoint interiorColor | dForm _ Form fromUser: grid. "sourceForm must be only 1 bit deep" interiorPoint _ dForm extent // 2. interiorColor _ dForm colorAt: interiorPoint. form _ (dForm makeBWForm: interiorColor) reverse findShapeAroundSeedBlock: [:f | f pixelValueAt: interiorPoint put: 1]. form _ form trimBordersOfColor: Color white. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! overMode "Set the mode for the tools that copy the form onto the display to over. Leaves the tool set in its previous state." mode _ Form over. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools' stamp: 'ar 5/28/2000 12:09'! repeatCopy "As long as the red button is pressed, copy the source form onto the display screen." [sensor redButtonPressed] whileTrue: [(BitBlt current destForm: Display sourceForm: form halftoneForm: color combinationRule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) destOrigin: self cursorPoint sourceOrigin: 0@0 extent: form extent clipRect: view insetDisplayBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF); copyBits]! ! !FormEditor methodsFor: 'editing tools'! reverseMode "Set the mode for the tools that copy the form onto the display to reverse. Leaves the tool set in its previous state." mode _ Form reverse. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! setColor: aColor "Set the mask (color) to aColor. Hacked to invoke color chooser if not B/W screen. Leaves the tool set in its previous state." self normalizeColor: (Display depth > 1 ifTrue: [Color fromUser] ifFalse: [aColor]). tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! singleCopy "If the red button is clicked, copy the source form onto the display screen." form displayOn: Display at: self cursorPoint clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [Form paint] ifFalse: [mode]) fillColor: color. sensor waitNoButton! ! !FormEditor methodsFor: 'editing tools'! togglexGridding "Turn x (horizontal) gridding off, if it is on, and turns it on, if it is off. Does not change the primary tool." xgridOn ifTrue: [grid _ 1 @ grid y. xgridOn _ false] ifFalse: [grid _ togglegrid x @ grid y. xgridOn _ true]. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! toggleyGridding "Turn y (vertical) gridding off, if it is on, and turns it on, if it is off. Does not change the primary tool." ygridOn ifTrue: [grid _ grid x @ 1. ygridOn _ false] ifFalse: [grid _ grid x @ togglegrid y. ygridOn _ true]. tool _ previousTool! ! !FormEditor methodsFor: 'editing tools'! underMode "Set the mode for the tools that copy the form onto the display to under. Leaves the tool set in its previous state." mode _ Form under. tool _ previousTool! ! !FormEditor methodsFor: 'menu messages'! accept "The edited information should now be accepted by the view." view updateDisplay. view accept! ! !FormEditor methodsFor: 'menu messages'! cancel "The edited information should be forgotten by the view." view cancel! ! !FormEditor methodsFor: 'menu messages'! edit model edit! ! !FormEditor methodsFor: 'menu messages' stamp: 'jm 3/27/98 14:52'! fileOut | fileName | fileName _ FillInTheBlank request: 'File name?' initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. Cursor normal showWhile: [model writeOnFileNamed: fileName]. ! ! !FormEditor methodsFor: 'menu messages'! redButtonActivity "Refer to the comment in MouseMenuController|redButtonActivity." self perform: tool! ! !FormEditor methodsFor: 'cursor'! cursorPoint "Answer the mouse coordinate data gridded according to the receiver's grid." ^sensor cursorPoint grid: grid! ! !FormEditor methodsFor: 'private'! dragForm tool = #block ifTrue: [Cursor origin show. [sensor anyButtonPressed or: [sensor keyboardPressed or: [self viewHasCursor not]]] whileFalse: []. ^self cursorPoint] ifFalse: [^self trackFormUntil: [sensor anyButtonPressed or: [sensor keyboardPressed or: [self viewHasCursor not]]]]! ! !FormEditor methodsFor: 'private' stamp: 'jm 12/4/97 10:22'! normalizeColor: aColor color _ aColor. ! ! !FormEditor methodsFor: 'private'! rubberBandFrom: startPoint until: aBlock | endPoint previousEndPoint | previousEndPoint _ startPoint. [aBlock value] whileFalse: [(endPoint _ self cursorPoint) = previousEndPoint ifFalse: [(Line from: startPoint to: previousEndPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: Color black. (Line from: startPoint to: endPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: Color black. previousEndPoint _ endPoint]]. (Line from: startPoint to: previousEndPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: Color black. ^endPoint! ! !FormEditor methodsFor: 'private'! selectTool: aCharacter "A new tool has been selected. It is denoted by aCharacter. Set the tool. This code is written out in long hand (i.e., rather than dispatching on a table of options) so that it is obvious what is happening." aCharacter = SingleCopyKey ifTrue: [^#singleCopy]. aCharacter = RepeatCopyKey ifTrue: [^#repeatCopy]. aCharacter = LineKey ifTrue: [^#line]. aCharacter = CurveKey ifTrue: [^#curve]. aCharacter = BlockKey ifTrue: [^#block]. aCharacter = SelectKey ifTrue: [^#newSourceForm]. aCharacter = OverKey ifTrue: [^#overMode]. aCharacter = UnderKey ifTrue: [^#underMode]. aCharacter = ReverseKey ifTrue: [^#reverseMode]. aCharacter = EraseKey ifTrue: [^#eraseMode]. aCharacter = ChangeGridsKey ifTrue: [^#changeGridding]. aCharacter = TogglexGridKey ifTrue: [^#togglexGridding]. aCharacter = ToggleyGridKey ifTrue: [^#toggleyGridding]. aCharacter = BitEditKey ifTrue: [^#magnify]. aCharacter = WhiteKey ifTrue: [^#colorWhite]. aCharacter = LightGrayKey ifTrue: [^#colorLightGray]. aCharacter = GrayKey ifTrue: [^#colorGray]. aCharacter = DarkGrayKey ifTrue: [^#colorDarkGray]. aCharacter = BlackKey ifTrue: [^#colorBlack]. aCharacter = OutKey ifTrue: [^#fileOutForm]. aCharacter = InKey ifTrue: [^#fileInForm]! ! !FormEditor methodsFor: 'private'! setVariables tool _ #repeatCopy. previousTool _ tool. grid _ 1 @ 1. togglegrid _ 8 @ 8. xgridOn _ false. ygridOn _ false. mode _ Form over. form _ Form extent: 8 @ 8. form fillBlack. unNormalizedColor _ color _ Color black. ! ! !FormEditor methodsFor: 'private'! trackFormUntil: aBlock | previousPoint cursorPoint | previousPoint _ self cursorPoint. form displayOn: Display at: previousPoint rule: Form reverse. [aBlock value] whileFalse: [cursorPoint _ self cursorPoint. (FlashCursor or: [cursorPoint ~= previousPoint]) ifTrue: [form displayOn: Display at: previousPoint rule: Form reverse. form displayOn: Display at: cursorPoint rule: Form reverse. previousPoint _ cursorPoint]]. form displayOn: Display at: previousPoint rule: Form reverse. ^previousPoint! ! !FormEditor methodsFor: 'pluggable menus' stamp: 'sma 3/11/2000 15:08'! getPluggableYellowButtonMenu: shiftKeyState ^ YellowButtonMenu! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormEditor class instanceVariableNames: ''! !FormEditor class methodsFor: 'class initialization'! flashCursor: aBoolean FlashCursor _ aBoolean "FormEditor flashCursor: true"! ! !FormEditor class methodsFor: 'class initialization' stamp: 'sma 3/11/2000 15:06'! initialize FlashCursor _ false. self setKeyboardMap. YellowButtonMenu _ SelectionMenu labels: 'accept cancel edit file out' lines: #(2) selections: #(accept cancel edit fileOut). "FormEditor initialize"! ! !FormEditor class methodsFor: 'instance creation'! openFullScreenForm "Create and schedule an instance of me on the form whose extent is the extent of the display screen." | topView | topView _ self createFullScreenForm. topView controller openDisplayAt: (topView viewport extent//2) "FormEditor openFullScreenForm."! ! !FormEditor class methodsFor: 'instance creation'! openOnForm: aForm "Create and schedule an instance of me on the form aForm." | topView | topView _ self createOnForm: aForm. topView controller open ! ! !FormEditor class methodsFor: 'examples'! formFromDisplay "Create an instance of me on a new form designated by the user at a location designated by the user." Form fromUser edit "FormEditor formFromDisplay"! ! !FormEditor class methodsFor: 'examples'! fullScreen "Create an instance of me on a new form that fills the full size of the display screen." FormEditor openFullScreenForm "FormEditor fullScreen"! ! !FormEditor class methodsFor: 'examples'! newForm "Create an instance of me on a new form at a location designated by the user. " (Form extent: 400 @ 200 depth: Display depth) edit "FormEditor newForm"! ! !FormEditor class methodsFor: 'private' stamp: 'di 1/16/98 15:46'! createFullScreenForm "Create a StandardSystemView for a FormEditor on the form whole screen." | formView formEditor menuView topView extent aForm | aForm _ Form extent: (Display extent x @ (Display extent y - 112)) depth: Display depth. formView _ FormHolderView new model: aForm. formView borderWidthLeft: 0 right: 0 top: 0 bottom: 1. formEditor _ formView controller. menuView _ FormMenuView new makeFormEditorMenu model: formEditor. formEditor model: menuView controller. topView _ StandardSystemView new. topView backgroundColor: #veryLightGray. topView model: aForm. topView addSubView: formView. topView addSubView: menuView align: menuView viewport topCenter with: formView viewport bottomCenter + (0@16). topView window: (formView viewport merge: (menuView viewport expandBy: (16 @ 0 corner: 16@16))). topView label: 'Form Editor'. extent _ topView viewport extent. topView minimumSize: extent. topView maximumSize: extent. ^topView ! ! !FormEditor class methodsFor: 'private' stamp: 'di 9/12/1998 12:28'! createOnForm: aForm "Create a StandardSystemView for a FormEditor on the form aForm." | formView formEditor menuView aView topView extent topViewBorder | topViewBorder _ 2. formView _ FormHolderView new model: aForm. formEditor _ formView controller. menuView _ FormMenuView new makeFormEditorMenu model: formEditor. formEditor model: aForm. aView _ View new. aView model: aForm. aView addSubView: formView. aView addSubView: menuView align: menuView viewport topCenter with: formView viewport bottomCenter + (0@16). aView window: ((formView viewport merge: (menuView viewport expandBy: (16 @ 0 corner: 16@16))) expandBy: (0@topViewBorder corner: 0@0)). topView _ StandardSystemView new. topView backgroundColor: #veryLightGray. topView addSubView: aView. topView label: 'Form Editor'. topView borderWidth: topViewBorder. extent _ topView viewport extent. topView minimumSize: extent. topView maximumSize: extent. ^topView! ! !FormEditor class methodsFor: 'private'! setKeyboardMap "Keyboard Mapping." SelectKey_$a. SingleCopyKey_$s. "tools" RepeatCopyKey_$d. LineKey_$f. CurveKey_$g. BlockKey_$h. OverKey_$j. "modes" UnderKey_$k. ReverseKey_$l. EraseKey_$;. InKey_$'. "file In" BitEditKey_$z. WhiteKey_$x. "colors" LightGrayKey_$c. GrayKey_$v. DarkGrayKey_$b. BlackKey_$n. TogglexGridKey_$m. "gridding" ToggleyGridKey_$,. ChangeGridsKey_$.. OutKey_$/ "file Out"! ! StandardSystemView subclass: #FormEditorView instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Editors'! !FormEditorView methodsFor: 'as yet unclassified'! cacheBitsAsTwoTone ^ false! ! FormView subclass: #FormHolderView instanceVariableNames: 'displayedForm ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Views'! !FormHolderView commentStamp: '' prior: 0! I represent a view of a Form. Editing takes place by modifying a working version of the Form. The message accept is used to copy the working version into the Form; the message cancel copies the Form into the working version.! !FormHolderView methodsFor: 'initialize-release'! release super release. displayedForm release. displayedForm _ nil! ! !FormHolderView methodsFor: 'model access'! changeValueAt: location put: anInteger "Refer to the comment in FormView|changeValueAt:put:." displayedForm pixelValueAt: location put: anInteger. displayedForm changed: self! ! !FormHolderView methodsFor: 'model access'! model: aForm super model: aForm. displayedForm _ aForm deepCopy! ! !FormHolderView methodsFor: 'model access'! workingForm "Answer the form that is currently being displayed--the working version in which edits are carried out." ^displayedForm! ! !FormHolderView methodsFor: 'displaying'! displayView "Display the Form associated with this View according to the rule and fillColor specifed by this class." | oldOffset | oldOffset _ displayedForm offset. displayedForm offset: 0@0. displayedForm displayOn: Display transformation: self displayTransformation clippingBox: self insetDisplayBox rule: self rule fillColor: self fillColor. displayedForm offset: oldOffset! ! !FormHolderView methodsFor: 'displaying'! updateDisplay "The working version is redefined by copying the bits displayed in the receiver's display area." displayedForm fromDisplay: self displayBox. displayedForm changed: self! ! !FormHolderView methodsFor: 'menu messages'! accept "Refer to the comment in FormView|accept." model copyBits: displayedForm boundingBox from: displayedForm at: 0 @ 0 clippingBox: model boundingBox rule: Form over fillColor: nil. model changed: self! ! !FormHolderView methodsFor: 'menu messages'! cancel "Refer to the comment in FormView|cancel." displayedForm become: model deepCopy. displayedForm changed: self. self display! ! Object subclass: #FormInput instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-HTML Forms'! !FormInput commentStamp: '' prior: 0! an input instance for a form. A form takes its input from a collection of FormInputs; each FormInput has a name and can retrieve a textual value. WHen a form is submitted, these name-value associations are gathered together and passed to an HTTP server.! !FormInput methodsFor: 'testing' stamp: 'ls 8/11/1998 20:43'! isRadioButtonSetInput ^false! ! !FormInput methodsFor: 'input handling' stamp: 'bolot 11/3/1999 20:39'! active "whether this input is currently providing an input" ^self name isNil not! ! !FormInput methodsFor: 'input handling' stamp: 'ls 8/5/1998 06:20'! name "name associated with this input" ^self subclassResponsibility! ! !FormInput methodsFor: 'input handling' stamp: 'ls 8/5/1998 06:20'! reset "reset to a default value" ! ! !FormInput methodsFor: 'input handling' stamp: 'ls 8/5/1998 06:20'! value "value associated with this input" ^self subclassResponsibility! ! Model subclass: #FormInputSet instanceVariableNames: 'inputs browser form ' classVariableNames: '' poolDictionaries: '' category: 'Network-HTML Forms'! !FormInputSet commentStamp: '' prior: 0! Holds the inputs for an instance of an HTML Form. It has a link to the browser it will be displayed in, and it has a link to each of the input PluggableTextMorph's that it input will read from. inputs - maps HtmlInput's into the text morphs which will input their value.! !FormInputSet methodsFor: 'private-initialization' stamp: 'ls 8/5/1998 03:57'! form: f browser: b inputs _ OrderedCollection new. form _ f. browser _ b.! ! !FormInputSet methodsFor: 'adding inputs' stamp: 'ls 8/5/1998 03:57'! addInput: anInput inputs add: anInput! ! !FormInputSet methodsFor: 'adding inputs' stamp: 'ls 8/11/1998 03:30'! inputs "return a list of the list of inputs" ^inputs! ! !FormInputSet methodsFor: 'action' stamp: 'ls 8/5/1998 03:58'! reset "reset all inputs to their default value" inputs do: [ :input | input reset ]! ! !FormInputSet methodsFor: 'action' stamp: 'bolot 11/3/1999 03:09'! submit "collect inputs and instruct the browser to do a submission" | inputValues | inputValues _ Dictionary new. inputs do: [ :input | input active ifTrue: [ (inputValues includesKey: input name) ifFalse: [ inputValues at: input name put: (OrderedCollection new: 1) ]. (inputValues at: input name) add: input value ] ]. browser submitFormWithInputs: inputValues url: form url method: form method encoding: form encoding. ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormInputSet class instanceVariableNames: ''! !FormInputSet class methodsFor: 'instance creation' stamp: 'ls 7/16/1998 22:01'! forForm: form andBrowser: browser "create a FormData for the given form and browser" ^super new form: form browser: browser! ! FormView subclass: #FormInspectView instanceVariableNames: 'offset ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Views'! !FormInspectView methodsFor: 'as yet unclassified'! defaultControllerClass "Refer to the comment in View|defaultControllerClass." ^ NoController! ! !FormInspectView methodsFor: 'as yet unclassified' stamp: 'di 9/23/1998 10:55'! displayView "Display the form as a value in an inspector. 8/11/96 sw" "Defeated form scaling for HS FormInspector. 8/20/96 di" | scale | Display fill: self insetDisplayBox fillColor: Color white. model selectionIndex == 0 ifTrue: [^ self]. scale _ self insetDisplayBox extent / model selection extent. scale _ (scale x min: scale y) min: 1. model selection displayOn: Display transformation: (WindowingTransformation scale: scale asPoint translation: self insetDisplayBox topLeft - model selection offset) clippingBox: self insetDisplayBox rule: self rule fillColor: self fillColor! ! Controller subclass: #FormMenuController instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Editors'! !FormMenuController commentStamp: '' prior: 0! I represent a Controller that takes control if a key on the keyboard is depressed or if the cursor is within my rectangular area.! !FormMenuController methodsFor: 'control defaults'! controlActivity "Pass control to a subView corresponding to a pressed keyboard key or to a mouse button pressed, if any." sensor keyboardPressed ifTrue: [self processMenuKey] ifFalse: [self controlToNextLevel]! ! !FormMenuController methodsFor: 'control defaults'! isControlActive "Answer false if the blue mouse button is pressed and the cursor is outside of the inset display box of the Controller's view; answer true, otherwise." ^sensor keyboardPressed | (view containsPoint: sensor cursorPoint) & sensor blueButtonPressed not! ! !FormMenuController methodsFor: 'control defaults'! isControlWanted "Answer true if the cursor is inside the inset display box (see View|insetDisplayBox) of the receiver's view, and answer false, otherwise. It is sent by Controller|controlNextLevel in order to determine whether or not control should be passed to this receiver from the Controller of the superView of this receiver's view." ^sensor keyboardPressed | self viewHasCursor! ! !FormMenuController methodsFor: 'control defaults' stamp: 'jm 4/7/98 20:59'! processMenuKey "The user typed a key on the keyboard. Perform the action of the button whose shortcut is that key, if any." | aView | aView _ view subViewContainingCharacter: sensor keyboard. aView ~~ nil ifTrue: [aView performAction]. ! ! View subclass: #FormMenuView instanceVariableNames: '' classVariableNames: 'BorderForm FormButtons SpecialBorderForm ' poolDictionaries: '' category: 'ST80-Editors'! !FormMenuView commentStamp: '' prior: 0! I represent a View whose subViews are Switches (and Buttons and OneOnSwitches) whose actions set the mode, color, and tool for editing a Form on the screen. The default controller of my instances is FormMenuController.! !FormMenuView methodsFor: 'initialize-release'! makeFormEditorMenu | button buttonCache form aSwitchView aSwitchController| "Now get those forms into the subviews" self makeButton: 1. "form source" self makeConnections: (2 to: 6). "tools" self makeConnections: (7 to: 10). "modes" self makeButton: 11. "filing in" self makeButton: 12. "bit editing" self makeColorConnections: (13 to: 17). "colors" self makeGridSwitch: 18. "toggle x" self makeGridSwitch: 19. "toggle y" self makeButton: 20. "setting grid" self makeButton: 21 "filing out"! ! !FormMenuView methodsFor: 'subView access' stamp: 'jm 4/2/98 17:29'! subViewContainingCharacter: aCharacter "Answer the receiver's subView that corresponds to the key, aCharacter. Answer nil if no subView is selected by aCharacter." self subViews reverseDo: [:aSubView | (aSubView shortcutCharacter = aCharacter) ifTrue: [^aSubView]]. ^nil ! ! !FormMenuView methodsFor: 'controller access'! defaultControllerClass "Refer to the comment in View|defaultControllerClass." ^FormMenuController! ! !FormMenuView methodsFor: 'private' stamp: 'jm 4/7/98 20:17'! makeButton: index | buttonCache button | buttonCache _ FormButtons at: index. button _ Button newOff. button onAction: [model changeTool: buttonCache value]. self makeViews: buttonCache for: button. ! ! !FormMenuView methodsFor: 'private' stamp: 'jrm 6/1/1998 21:57'! makeColorConnections: indexInterval | connector buttonCache button aSwitchView | connector _ Object new. "a dummy model for connecting dependents" indexInterval do: [:index | buttonCache _ FormButtons at: index. buttonCache initialState = #true ifTrue: [button _ OneOnSwitch newOn] ifFalse: [button _ OneOnSwitch newOff]. button onAction: [model changeTool: buttonCache value]. button connection: connector. aSwitchView _ self makeViews: buttonCache for: button. aSwitchView borderWidthLeft: 1 right: 0 top: 1 bottom: 1; action: #turnOn]. aSwitchView borderWidth: 1. ! ! !FormMenuView methodsFor: 'private' stamp: 'jrm 6/1/1998 21:56'! makeConnections: indexInterval | connector buttonCache button aSwitchView | connector _ Object new. "a dummy model for connecting dependents." indexInterval do: [:index | buttonCache _ FormButtons at: index. buttonCache initialState = #true ifTrue: [button _ OneOnSwitch newOn] ifFalse: [button _ OneOnSwitch newOff]. button onAction: [model changeTool: buttonCache value]. button connection: connector. aSwitchView _ self makeViews: buttonCache for: button. aSwitchView borderWidthLeft: 1 right: 0 top: 1 bottom: 1; action: #turnOn]. aSwitchView borderWidth: 1. ! ! !FormMenuView methodsFor: 'private' stamp: 'jm 4/7/98 20:30'! makeGridSwitch: index | buttonCache button | buttonCache _ FormButtons at: index. buttonCache initialState = #true ifTrue: [button _ Switch newOn] ifFalse: [button _ Switch newOff]. button onAction: [model changeTool: buttonCache value]. button offAction: [model changeTool: buttonCache value]. self makeViews: buttonCache for: button. ! ! !FormMenuView methodsFor: 'private' stamp: 'jm 4/7/98 20:30'! makeSwitch: index | buttonCache button | buttonCache _ FormButtons at: index. buttonCache initialState = #true ifTrue: [button _ Switch newOn] ifFalse: [button _ Switch newOff]. button onAction: [model changeTool: buttonCache value]. self makeViews: buttonCache for: button. ! ! !FormMenuView methodsFor: 'private' stamp: 'jm 4/7/98 20:24'! makeViews: cache for: aSwitch | form aSwitchView | form _ cache form. aSwitchView _ PluggableButtonView on: aSwitch getState: #isOn action: #switch. aSwitchView label: form; shortcutCharacter: cache value; window: (0@0 extent: form extent); translateBy: cache offset; borderWidth: 1. self addSubView: aSwitchView. ^ aSwitchView ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormMenuView class instanceVariableNames: ''! !FormMenuView class methodsFor: 'class initialization' stamp: 'jm 3/27/98 14:54'! fileOut "Save the FormEditor button icons." "FormMenuView fileOut" | names | names _ #('select.form' 'singlecopy.form' 'repeatcopy.form' 'line.form' 'curve.form' 'block.form' 'over.form' 'under.form' 'reverse.form' 'erase.form' 'in.form' 'magnify.form' 'white.form' 'lightgray.form' 'gray.form' 'darkgray.form' 'black.form' 'xgrid.form' 'ygrid.form' 'togglegrids.form' 'out.form'). 1 to: FormButtons size do: [:i | (FormButtons at: i) form writeOnFileNamed: (names at: i)]. SpecialBorderForm writeOnFileNamed: 'specialborderform.form'. BorderForm writeOnFileNamed: 'borderform.form'. ! ! !FormMenuView class methodsFor: 'class initialization' stamp: 'jm 3/27/98 13:07'! initialize "The icons for the menu are typically stored on files. In order to avoid reading them every time, they are stored in a collection in a class variable, along with their offset, tool value, and initial visual state (on or off)." "FormMenuView initialize" | offsets keys states names button | offsets _ OrderedCollection new: 21. #(0 64 96 128 160 192 256 288 320 352 420) do: [:i | offsets addLast: i@0]. "First row" #(0 64 96 128 160 192 256 304 352 420) do: [:i | offsets addLast: i@48]. "Second row" offsets _ offsets asArray. keys _ #($a $s $d $f $g $h $j $k $l $; $' $z $x $c $v $b $n $m $, $. $/ ). "Keyboard" states _ #( false false true false false false true false false false false false false false false false true false false false false). "Initial button states" names _ #('select.form' 'singlecopy.form' 'repeatcopy.form' 'line.form' 'curve.form' 'block.form' 'over.form' 'under.form' 'reverse.form' 'erase.form' 'in.form' 'magnify.form' 'white.form' 'lightgray.form' 'gray.form' 'darkgray.form' 'black.form' 'xgrid.form' 'ygrid.form' 'togglegrids.form' 'out.form'). "Files of button images" FormButtons _ OrderedCollection new. 1 to: 21 do: [:index | button _ FormButtonCache new. button form: (Form fromFileNamed: (names at: index)). button offset: (offsets at: index). button value: (keys at: index). button initialState: (states at: index). FormButtons addLast: button]. SpecialBorderForm _ Form fromFileNamed: 'specialborderform.form'. BorderForm _ Form fromFileNamed: 'borderform.form'. ! ! StrikeFont subclass: #FormSetFont instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Text'! !FormSetFont commentStamp: '' prior: 0! FormSetFonts are designed to capture individual images as character forms for imbedding in normal text. While most often used to insert an isolated glyph in some text, the code is actually desinged to support an entire user-defined font. The TextAttribute subclass TextFontReference is specifically designed for such in-line insertion of exceptional fonts in normal text.! !FormSetFont methodsFor: 'as yet unclassified' stamp: 'ar 5/23/2000 12:49'! fromFormArray: formArray asciiStart: asciiStart ascent: ascentVal | height width x badChar | type _ 2. name _ 'aFormFont'. minAscii _ asciiStart. maxAscii _ minAscii + formArray size - 1. ascent _ ascentVal. subscript _ superscript _ emphasis _ 0. height _ width _ 0. maxWidth _ 0. formArray do: [:f | width _ width + f width. maxWidth _ maxWidth max: f width. height _ height max: f height + f offset y]. badChar _ (Form extent: 7@height) borderWidth: 1. width _ width + badChar width. descent _ height - ascent. pointSize _ height. glyphs _ Form extent: width @ height depth: formArray first depth. xTable _ Array new: maxAscii + 3 withAll: 0. x _ 0. formArray doWithIndex: [:f :i | f displayOn: glyphs at: x@0. xTable at: minAscii + i+1 put: (x _ x + f width)]. badChar displayOn: glyphs at: x@0. xTable at: maxAscii + 3 put: x + badChar width. characterToGlyphMap _ nil.! ! !FormSetFont methodsFor: 'as yet unclassified'! reset "Ignored by FormSetFonts"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormSetFont class instanceVariableNames: ''! !FormSetFont class methodsFor: 'examples' stamp: 'ar 1/15/2001 18:38'! copy: charForm toClipBoardAs: char ascent: ascent Clipboard clipboardText: (Text string: char asString attribute: (TextFontReference toFont: (FormSetFont new fromFormArray: (Array with: charForm) asciiStart: char asciiValue ascent: ascent))) " The S in the Squeak welcome window was installed by doing the following in a workspace (where the value of, eg, charForm will persist through BitEdit... f _ TextStyle default fontAt: 4. oldS _ f characterFormAt: $S. charForm _ Form extent: oldS extent depth: 8. oldS displayOn: charForm. charForm bitEdit. ...Play around with the BitEditor, then accept and close... FormSetFont copy: charForm toClipBoardAs: $S ascent: f ascent. ...Then do a paste into the Welcome window "! ! !FormSetFont class methodsFor: 'examples'! example "FormSetFont example" "Lets the user select a (small) area of the screen to represent the character A, then copies 'A' to the clipboard with that as the letter form. Thereafter, a paste operation will imbed that character in any text." | charForm | charForm _ Form fromUser. self copy: charForm toClipBoardAs: $A ascent: charForm height! ! View subclass: #FormView instanceVariableNames: 'rule mask ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Views'! !FormView commentStamp: '' prior: 0! I represent a view of a Form.! !FormView methodsFor: 'accessing'! fillColor "Answer an instance of class Form that is the mask used when displaying the receiver's model (a Form) on the display screen (see BitBlt for the meaning of this mask)." ^ mask! ! !FormView methodsFor: 'accessing'! fillColor: aForm "Set the display mask for displaying the receiver's model to be the argument, aForm." mask _ aForm! ! !FormView methodsFor: 'accessing'! mask "Answer an instance of class Form that is the mask used when displaying the receiver's model (a Form) on the display screen (see BitBlt for the meaning of this mask)." ^ mask! ! !FormView methodsFor: 'accessing'! rule "Answer a number from 0 to 15 that indicates which of the sixteen display rules (logical function of two boolean values) is to be used when copying the receiver's model (a Form) onto the display screen." rule == nil ifTrue: [^self defaultRule] ifFalse: [^rule]! ! !FormView methodsFor: 'accessing'! rule: anInteger "Set the display rule for the receiver to be the argument, anInteger." rule _ anInteger! ! !FormView methodsFor: 'controller access'! defaultControllerClass "Refer to the comment in View|defaultControllerClass." ^ FormEditor! ! !FormView methodsFor: 'model access'! changeValueAt: location put: anInteger "The receiver's model is a form which has an array of bits. Change the bit at index, location, to be anInteger (either 1 or 0). Inform all objects that depend on the model that it has changed." model pixelValueAt: location put: anInteger. model changed: self! ! !FormView methodsFor: 'window access'! defaultWindow "Refer to the comment in View|defaultWindow." ^(Rectangle origin: 0 @ 0 extent: model extent) expandBy: borderWidth! ! !FormView methodsFor: 'window access'! windowBox "For comaptibility with Control manager (see senders)" ^ self insetDisplayBox! ! !FormView methodsFor: 'displaying'! displayOn: aPort model displayOnPort: aPort at: self displayBox origin! ! !FormView methodsFor: 'displaying' stamp: 'hmm 7/21/97 20:45'! displayView "Refer to the comment in View|displayView." | oldOffset | super displayView. insideColor == nil ifFalse: [Display fill: self insetDisplayBox fillColor: insideColor]. oldOffset _ model offset. model offset: "borderWidth origin" 0@0. model displayOn: Display transformation: self displayTransformation clippingBox: self insetDisplayBox rule: self rule fillColor: self fillColor. model offset: oldOffset! ! !FormView methodsFor: 'displaying'! uncacheBits "Placed vacuously here so that when ControlManager>>restore calls uncacheBits for a project with no windows, we don't hang. 1/24/96 sw"! ! !FormView methodsFor: 'displaying'! updateDisplay "overridden by subclass"! ! !FormView methodsFor: 'updating'! update: aFormView "Refer to the comment in View|update:." self == aFormView ifFalse: [self display]! ! !FormView methodsFor: 'menu messages'! accept "The receiver's model is set to the working version, the one in which edits are carried out." ^self! ! !FormView methodsFor: 'menu messages'! cancel "Set the working form to be a copy of the model." ^self! ! !FormView methodsFor: 'private'! defaultRule "The default display rule is 3=over or storing." ^Form over! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormView class instanceVariableNames: ''! !FormView class methodsFor: 'examples'! exampleOne "Frame a Form (specified by the user) with a border of 2 bits in width and display it offset 60 x 40 from the cornor of the display screen. " | f view | f _ Form fromUser. view _ self new model: f. view translateBy: 60 @ 40. view borderWidth: 2. view display. view release "FormView exampleOne"! ! !FormView class methodsFor: 'examples'! exampleTwo "Frame a Form (specified by the user) that is scaled by 2. The border is 2 bits in width. Displays at location 60, 40." | f view | f _ Form fromUser. view _ self new model: f. view scaleBy: 2.0. view translateBy: 60 @ 40. view borderWidth: 2. view display. view release "FormView exampleTwo"! ! !FormView class methodsFor: 'examples' stamp: 'di 9/12/1998 10:17'! open: aForm named: aString "FormView open: ((Form extent: 100@100) borderWidth: 1) named: 'Squeak' " "Open a window whose model is aForm and whose label is aString." | topView aView | topView _ StandardSystemView new. topView model: aForm. topView label: aString. topView minimumSize: 80@80. aView _ FormView new. aView model: aForm. aView window: (aForm boundingBox expandBy: 2). aView borderWidthLeft: 2 right: 2 top: 2 bottom: 2. topView addSubView: aView. topView controller open! ! Number subclass: #Fraction instanceVariableNames: 'numerator denominator ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Numbers'! !Fraction commentStamp: '' prior: 0! I represent some rational number as a fraction. All public arithmetic operations answer reduced fractions.! !Fraction methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:58'! * aNumber "Answer the result of multiplying the receiver by aNumber." | d1 d2 | aNumber isFraction ifTrue: [d1 _ numerator gcd: aNumber denominator. d2 _ denominator gcd: aNumber numerator. (d2 = denominator and: [d1 = aNumber denominator]) ifTrue: [^ numerator // d1 * (aNumber numerator // d2)]. ^ Fraction numerator: numerator // d1 * (aNumber numerator // d2) denominator: denominator // d2 * (aNumber denominator // d1)]. ^ aNumber adaptToFraction: self andSend: #*! ! !Fraction methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:58'! + aNumber "Answer the sum of the receiver and aNumber." | n d d1 d2 | aNumber isFraction ifTrue: [d _ denominator gcd: aNumber denominator. n _ numerator * (d1 _ aNumber denominator // d) + (aNumber numerator * (d2 _ denominator // d)). d1 _ d1 * d2. n _ n // (d2 _ n gcd: d). (d _ d1 * (d // d2)) = 1 ifTrue: [^ n]. ^ Fraction numerator: n denominator: d]. ^ aNumber adaptToFraction: self andSend: #+! ! !Fraction methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:58'! - aNumber "Answer the difference between the receiver and aNumber." aNumber isFraction ifTrue: [^ self + aNumber negated]. ^ aNumber adaptToFraction: self andSend: #-! ! !Fraction methodsFor: 'arithmetic' stamp: 'di 11/6/1998 13:58'! / aNumber "Answer the result of dividing the receiver by aNumber." aNumber isFraction ifTrue: [^self * aNumber reciprocal]. ^ aNumber adaptToFraction: self andSend: #/! ! !Fraction methodsFor: 'arithmetic'! negated "Refer to the comment in Number|negated." ^ Fraction numerator: numerator negated denominator: denominator! ! !Fraction methodsFor: 'arithmetic'! reciprocal "Refer to the comment in Number|reciprocal." numerator = 0 ifTrue: [self error: '0 has no reciprocal']. numerator = 1 ifTrue: [^denominator]. numerator = -1 ifTrue: [^denominator negated]. ^Fraction numerator: denominator denominator: numerator! ! !Fraction methodsFor: 'comparing' stamp: 'di 11/6/1998 13:58'! < aNumber aNumber isFraction ifTrue: [^ numerator * aNumber denominator < (aNumber numerator * denominator)]. ^ aNumber adaptToFraction: self andSend: # 500 or: [mSecs < 0 "clock wrap-around"]) ifTrue: [mSecsPerFrame _ mSecs // framesSinceLastDisplay. framesPerSec _ (framesSinceLastDisplay * 1000) // mSecs. newContents _ mSecsPerFrame printString, ' mSecs (', framesPerSec printString, ' frame', (framesPerSec == 1 ifTrue: [''] ifFalse: ['s']), '/sec)'. self contents: newContents. lastDisplayTime _ now. framesSinceLastDisplay _ 0]! ! !FrameRateMorph methodsFor: 'as yet unclassified' stamp: 'jm 2/23/98 18:41'! stepTime "Answer the desired time between steps in milliseconds." ^ 0 ! ! AlignmentMorph subclass: #FreeCell instanceVariableNames: 'board cardsRemainingDisplay elapsedTimeDisplay gameNumberDisplay lastGameLost state autoMoveRecursionCount myFillStyle ' classVariableNames: 'Statistics ' poolDictionaries: '' category: 'Morphic-Games'! !FreeCell methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:17'! initialize super initialize. Statistics newSession. autoMoveRecursionCount _ 0. self listDirection: #topToBottom. self wrapCentering: #center; cellPositioning: #topCenter. self vResizing: #shrinkWrap. self hResizing: #shrinkWrap. self color: self colorNearTop; borderWidth: 2; addMorph: self makeControls; addMorph: self board; newGame. ! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/16/1999 17:17'! makeCardsRemainingDisplay cardsRemainingDisplay _ LedMorph new digits: 2; extent: (2*10@15). ^self wrapPanel: cardsRemainingDisplay label: 'Cards Left: '! ! !FreeCell methodsFor: 'initialization' stamp: 'ar 11/9/2000 21:17'! makeControlBar ^AlignmentMorph newRow color: self colorNearBottom; borderColor: #inset; borderWidth: 2; layoutInset: 0; hResizing: #spaceFill; vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter; yourself.! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/24/1999 14:38'! makeControls ^self makeControlBar addMorph: AlignmentMorph newVariableTransparentSpacer; addMorph: self makeHelpButton; addMorph: self makeQuitButton; addMorph: self makeStatisticsButton; addMorph: self makeGameNumberDisplay; addMorph: self makePickGameButton; addMorph: self makeSameGameButton; addMorph: self makeNewGameButton; addMorph: self makeElapsedTimeDisplay; addMorph: self makeCardsRemainingDisplay; yourself.! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/23/1999 21:27'! makeElapsedTimeDisplay elapsedTimeDisplay _ LedTimerMorph new digits: 3; extent: (3*10@15). ^self wrapPanel: elapsedTimeDisplay label: 'Elapsed Time: '! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/23/1999 22:45'! makeGameNumberDisplay gameNumberDisplay _ LedMorph new digits: 5; extent: (5*10@15). ^self wrapPanel: gameNumberDisplay label: 'Game #: '! ! !FreeCell methodsFor: 'initialization' stamp: 'di 10/19/1999 01:12'! makeHelpButton ^self buildButton: SimpleButtonMorph new target: self label: 'Help' selector: #help! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/16/1999 15:05'! makeNewGameButton ^self buildButton: SimpleButtonMorph new target: self label: 'New game' selector: #newGame! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/23/1999 22:46'! makePickGameButton ^self buildButton: SimpleButtonMorph new target: self label: 'Pick game' selector: #pickGame! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/23/1999 21:18'! makeQuitButton ^self buildButton: SimpleButtonMorph new target: self label: 'Quit' selector: #quit ! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/17/1999 17:44'! makeSameGameButton ^self buildButton: SimpleButtonMorph new target: self label: 'Same game' selector: #sameGame! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/24/1999 14:38'! makeStatisticsButton ^self buildButton: SimpleButtonMorph new target: self label: 'Statistics' selector: #statistics! ! !FreeCell methodsFor: 'initialization' stamp: 'djp 10/31/1999 18:48'! openInWindowLabeled: aString inWorld: aWorld ^(super openInWindowLabeled: aString inWorld: aWorld) model: self; yourself! ! !FreeCell methodsFor: 'accessing' stamp: 'djp 10/31/1999 19:28'! board board ifNil: [board _ FreeCellBoard new target: self; actionSelector: #boardAction:]. ^board! ! !FreeCell methodsFor: 'accessing' stamp: 'djp 10/24/1999 21:36'! currentGame ^self board cardDeck seed! ! !FreeCell methodsFor: 'accessing' stamp: 'djp 10/25/1999 21:56'! helpText ^'The objective of FreeCell is to move all of the cards to the four "home cells" in the upper right corner. Each home cell will hold one suit and must be filled sequentially starting with the Ace. There are four "free cells" in the upper left corner that can each hold one card. Cards can be moved from the bottom of a stack to a free cell or to another stack. When moving a card to another stack, it must have a value that is one less than the exposed card and of a different color.'! ! !FreeCell methodsFor: 'actions' stamp: 'djp 10/31/1999 21:17'! autoMovingHome elapsedTimeDisplay pause. autoMoveRecursionCount _ autoMoveRecursionCount + 1.! ! !FreeCell methodsFor: 'actions' stamp: 'djp 10/31/1999 21:35'! boardAction: actionSymbol actionSymbol = #cardMovedHome ifTrue: [^self cardMovedHome]. actionSymbol = #autoMovingHome ifTrue: [^self autoMovingHome].! ! !FreeCell methodsFor: 'actions' stamp: 'di 3/5/2000 15:30'! cardMovedHome cardsRemainingDisplay value: (cardsRemainingDisplay value - 1). autoMoveRecursionCount _ autoMoveRecursionCount - 1 max: 0. cardsRemainingDisplay value = 0 ifTrue: [self gameWon] ifFalse: [autoMoveRecursionCount = 0 ifTrue: [elapsedTimeDisplay continue]].! ! !FreeCell methodsFor: 'actions' stamp: 'di 3/5/2000 16:20'! gameLost state _ #lost. elapsedTimeDisplay stop. cardsRemainingDisplay highlighted: true; flash: true. Statistics gameLost: self currentGame! ! !FreeCell methodsFor: 'actions' stamp: 'di 3/5/2000 16:20'! gameWon state _ #won. elapsedTimeDisplay stop; highlighted: true; flash: true. Statistics gameWon: self currentGame! ! !FreeCell methodsFor: 'actions' stamp: 'di 10/19/1999 01:12'! help | window helpMorph | window _ SystemWindow labelled: 'FreeCell Help'. window model: self. helpMorph _ (PluggableTextMorph new editString: self helpText) lock. window addMorph: helpMorph frame: (0@0 extent: 1@1). window openInWorld. ! ! !FreeCell methodsFor: 'actions' stamp: 'di 12/12/2000 13:08'! inAutoMove "Return true if an automove sequence is in progress" ^ autoMoveRecursionCount > 0! ! !FreeCell methodsFor: 'actions' stamp: 'th 12/15/1999 15:20'! modelSleep "When fixing #contains: calls beware of reinventing #includes:" (#(newGame sameGame pickGame won lost ) includes: state) ifTrue: [elapsedTimeDisplay pause]! ! !FreeCell methodsFor: 'actions' stamp: 'th 12/15/1999 15:22'! modelWakeUp "Maybe less performant but more readable" (#(won lost) includes: state) ifFalse: [elapsedTimeDisplay resume]! ! !FreeCell methodsFor: 'actions' stamp: 'di 1/16/2000 10:35'! newGame Collection initialize. self newGameNumber: nil. state _ #newGame! ! !FreeCell methodsFor: 'actions' stamp: 'di 3/5/2000 16:21'! newGameNumber: aSeedOrNil cardsRemainingDisplay value ~~ 0 ifTrue: [self gameLost]. cardsRemainingDisplay flash: false; highlighted: false; value: 52. elapsedTimeDisplay flash: false; highlighted: false. "board handles nil case" self board pickGame: aSeedOrNil. elapsedTimeDisplay reset; start. gameNumberDisplay value: self currentGame! ! !FreeCell methodsFor: 'actions' stamp: 'th 12/15/1999 15:05'! pickGame | seed | seed _ self promptForSeed. seed isNil ifTrue: [^ self]. self newGameNumber: seed. state _ #pickGame! ! !FreeCell methodsFor: 'actions' stamp: 'th 12/15/1999 14:56'! promptForSeed | s i | [s _ FillInTheBlank request: 'Pick a game number between 1 and 32000'. "Let the user cancel." s isEmpty ifTrue:[^nil]. [i _ s asNumber asInteger] on: Error do: [i _ 0]. i between: 1 and: 32000] whileFalse. ^ i! ! !FreeCell methodsFor: 'actions' stamp: 'di 3/5/2000 15:35'! quit cardsRemainingDisplay value ~~ 0 ifTrue: [self gameLost]. self owner == self world ifTrue: [self delete] ifFalse: [self owner delete]. Statistics close! ! !FreeCell methodsFor: 'actions' stamp: 'th 12/15/1999 15:03'! sameGame self newGameNumber: self currentGame. state _ #sameGame. ! ! !FreeCell methodsFor: 'actions' stamp: 'djp 10/24/1999 15:07'! statistics Statistics display! ! !FreeCell methodsFor: 'private' stamp: 'ar 11/9/2000 21:16'! buildButton: aButton target: aTarget label: aLabel selector: aSelector "wrap a button or switch in an alignmentMorph to provide some space around the button" | a | aButton target: aTarget; label: aLabel; actionSelector: aSelector; borderColor: #raised; borderWidth: 2; color: Color gray. a _ AlignmentMorph newColumn wrapCentering: #center; cellPositioning: #topCenter; hResizing: #shrinkWrap; vResizing: #shrinkWrap; color: Color transparent; layoutInset: 1. a addMorph: aButton. ^ a ! ! !FreeCell methodsFor: 'private' stamp: 'ar 11/9/2000 21:17'! wrapPanel: anLedPanel label: aLabel "wrap an LED panel in an alignmentMorph with a label to its left" | a | a _ AlignmentMorph newRow wrapCentering: #center; cellPositioning: #leftCenter; hResizing: #shrinkWrap; vResizing: #shrinkWrap; borderWidth: 0; layoutInset: 5; color: Color transparent. a addMorph: anLedPanel. a addMorph: (StringMorph contents: aLabel). ^ a ! ! !FreeCell methodsFor: 'visual properties' stamp: 'RAA 3/3/2000 23:28'! colorNearBottom ^Color r: 0.0 g: 0.455 b: 0.18! ! !FreeCell methodsFor: 'visual properties' stamp: 'RAA 3/4/2000 10:26'! colorNearTop ^ (Color r: 0.304 g: 0.833 b: 0.075)! ! !FreeCell methodsFor: 'visual properties' stamp: 'RAA 3/3/2000 23:29'! defaultBackgroundColor ^Color r: 0.365 g: 1.0 b: 0.09! ! !FreeCell methodsFor: 'as yet unclassified' stamp: 'RAA 3/4/2000 17:01'! fillStyle myFillStyle ifNil: [ myFillStyle _ GradientFillStyle ramp: { 0.0 -> self colorNearTop. 1.0 -> self colorNearBottom }. ]. ^myFillStyle origin: self position; direction: (self width // 2)@self height ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FreeCell class instanceVariableNames: ''! !FreeCell class methodsFor: 'class initialization' stamp: 'djp 10/24/1999 14:50'! initialize Statistics _ FreeCellStatistics new.! ! AlignmentMorph subclass: #FreeCellBoard instanceVariableNames: 'cardDeck lastCardDeck freeCells homeCells stacks target actionSelector ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !FreeCellBoard methodsFor: 'initialization' stamp: 'ar 11/9/2000 20:54'! initialize super initialize. self listDirection: #topToBottom. self hResizing: #shrinkWrap. self vResizing: #rigid. self height: 500. borderWidth _ 0. color _ Color green. self layout. ! ! !FreeCellBoard methodsFor: 'initialization' stamp: 'di 11/29/2000 18:50'! pickGame: aSeedOrNil | sorted msg | cardDeck _ PlayingCardDeck newDeck. aSeedOrNil == 1 ifTrue: ["Special case of game 1 does a time profile playing the entire (trivial) game." sorted _ cardDeck submorphs asSortedCollection: [ :a :b | a cardNumber >= b cardNumber]. cardDeck removeAllMorphs; addAllMorphs: sorted. self resetBoard. self world doOneCycle. Utilities informUser: 'Game #1 is a special case for performance analysis' during: [msg _ self world firstSubmorph. msg align: msg topRight with: owner bottomRight. MessageTally spyOn: [sorted last owner doubleClickOnCard: sorted last]]] ifFalse: [aSeedOrNil ifNotNil:[cardDeck seed: aSeedOrNil]. cardDeck shuffle. self resetBoard]. ! ! !FreeCellBoard methodsFor: 'initialization' stamp: 'di 12/12/2000 15:02'! resetBoard self purgeAllCommands. self resetFreeCells; resetHomeCells; resetStacks; changed.! ! !FreeCellBoard methodsFor: 'initialization' stamp: 'djp 10/16/1999 20:06'! resetFreeCells freeCells do: [:deck | deck removeAllCards]! ! !FreeCellBoard methodsFor: 'initialization' stamp: 'djp 10/16/1999 20:06'! resetHomeCells homeCells do: [:deck | deck removeAllCards]! ! !FreeCellBoard methodsFor: 'initialization' stamp: 'djp 10/16/1999 20:06'! resetStacks | card stackStream stack | stacks do: [:deck | deck removeAllCards]. stackStream _ ReadStream on: stacks. [card _ cardDeck deal. card notNil] whileTrue: [ stack _ stackStream next ifNil: [stackStream reset; next]. stack addCard: card]. ! ! !FreeCellBoard methodsFor: 'layout' stamp: 'ar 11/20/2000 19:08'! cardCell ^PlayingCardDeck new layout: #pile; listDirection: #topToBottom; enableDragNDrop; color: Color transparent; borderColor: (Color gray alpha: 0.5); borderWidth: 2; layoutBounds: (0@0 extent: PlayingCardMorph width @ PlayingCardMorph height); yourself! ! !FreeCellBoard methodsFor: 'layout' stamp: 'RAA 3/3/2000 23:33'! cellsRow | row | row := (AlignmentMorph newRow) vResizing: #shrinkWrap; hResizing: #shrinkWrap; color: Color transparent; addAllMorphs: self freeCells; addMorphBack: self cellsRowSpacer; addAllMorphs: self homeCells; yourself. ^row! ! !FreeCellBoard methodsFor: 'layout' stamp: 'djp 10/17/1999 18:25'! cellsRowSpacer | column | column := (AlignmentMorph newColumn) vResizing: #rigid; hResizing: #rigid; color: Color transparent; extent: PlayingCardMorph cardSize; yourself. ^column! ! !FreeCellBoard methodsFor: 'layout' stamp: 'th 12/15/1999 16:14'! freeCell | freeCell | freeCell _ self cardCell. freeCell stackingPolicy: #single; emptyDropPolicy: #any; target: self; cardDroppedSelector: #cardMoved; acceptCardSelector: #acceptSingleCard:on:. ^ freeCell! ! !FreeCellBoard methodsFor: 'layout' stamp: 'djp 10/11/1999 16:41'! freeCells ^freeCells ifNil: [freeCells := (1 to: 4) collect: [:i | self freeCell]]! ! !FreeCellBoard methodsFor: 'layout' stamp: 'th 12/15/1999 16:12'! homeCell | homeCell | homeCell _ self cardCell. homeCell stackingPolicy: #straight; stackingOrder: #ascending; emptyDropPolicy: #inOrder; target: self; cardDroppedSelector: #cardMovedHome; cardDraggedSelector: #dragCard:fromHome:; acceptCardSelector: #acceptSingleCard:on:. ^ homeCell! ! !FreeCellBoard methodsFor: 'layout' stamp: 'djp 10/11/1999 16:41'! homeCells ^homeCells ifNil: [homeCells := (1 to: 4) collect: [:i | self homeCell]]! ! !FreeCellBoard methodsFor: 'layout' stamp: 'djp 10/11/1999 18:27'! layout self addMorphBack: self cellsRow; addMorphBack: self stacksRow. ! ! !FreeCellBoard methodsFor: 'layout' stamp: 'ar 11/9/2000 20:55'! stack ^ PlayingCardDeck new color: Color transparent; layout: #stagger; listDirection: #topToBottom; enableDragNDrop; stackingPolicy: #altStraight; stackingOrder: #descending; emptyDropPolicy: #any; target: self; cardDroppedSelector: #cardMoved; cardDraggedSelector: #dragCard:fromStack:; acceptCardSelector: #acceptCard:onStack:; cardDoubleClickSelector: #doubleClickInStack:OnCard:! ! !FreeCellBoard methodsFor: 'layout' stamp: 'djp 10/11/1999 16:41'! stacks ^stacks ifNil: [stacks:= (1 to: 8) collect: [:i | self stack]]! ! !FreeCellBoard methodsFor: 'layout' stamp: 'ar 11/20/2000 18:58'! stacksRow | row | row := (AlignmentMorph newRow) vResizing: #spaceFill; hResizing: #spaceFill; wrapCentering: #topLeft; cellPositioning: #topLeft; color: Color transparent; yourself. self stacks do: [:stack | row addMorphBack: AlignmentMorph newVariableTransparentSpacer; addMorphBack: stack]. row addMorphBack: AlignmentMorph newVariableTransparentSpacer. ^row! ! !FreeCellBoard methodsFor: 'private' stamp: 'RAA 3/4/2000 10:48'! autoMoveCardsHome | first | first _ false. (self stacks, self freeCells) do: [:deck | self homeCells do: [ :homeCell | deck hasCards ifTrue: [ (homeCell repelCard: deck topCard) ifFalse: [ (self isPlayableCardInHomeCells: deck topCard) ifTrue: [ first ifFalse: [ " trigger autoMoving event on first move." first _ true. self performActionSelector: #autoMovingHome ]. self visiblyMove: deck topCard to: homeCell. ] ] ] ] ]. ! ! !FreeCellBoard methodsFor: 'private' stamp: 'RAA 3/4/2000 17:11'! isPlayableCardInHomeCells: aPlayingCard | unplayedOther topsThisColor topsOtherColor unplayedSame | " are all cards that could be played on this card if it stayed on the stack present in the home cells?" aPlayingCard cardNumber <= 2 ifTrue: [^true]. "special case for Aces and 2's" topsThisColor _ OrderedCollection new. topsOtherColor _ OrderedCollection new. self homeCells do: [ :deck | deck hasCards ifTrue: [ (aPlayingCard suitColor == deck topCard suitColor ifTrue: [topsThisColor] ifFalse: [topsOtherColor]) add: deck topCard cardNumber. ] ]. unplayedOther _ topsOtherColor size < 2 ifTrue: [1] ifFalse: [topsOtherColor min + 1]. unplayedSame _ topsThisColor size < 2 ifTrue: [1] ifFalse: [topsThisColor min + 1]. unplayedOther > (aPlayingCard cardNumber - 1) ifTrue: [^true]. unplayedOther < (aPlayingCard cardNumber - 1) ifTrue: [^false]. ^unplayedSame >= (unplayedOther - 1) ! ! !FreeCellBoard methodsFor: 'private' stamp: 'djp 10/24/1999 00:50'! maxDraggableStackSize: dropIntoEmptyStack "Note: dropIntoEmptyStack, means one less empty stack to work with. This needs to be reevaluated at time of drop." "Not super smart - doesn't use stacks that are buildable though not empty" | nFree nEmptyStacks | nFree _ (freeCells select: [:d | d hasCards not]) size. nEmptyStacks _ (stacks select: [:d | d hasCards not]) size. dropIntoEmptyStack ifTrue: [nEmptyStacks _ nEmptyStacks - 1]. ^ (1 + nFree) * (2 raisedTo: nEmptyStacks)! ! !FreeCellBoard methodsFor: 'private' stamp: 'djp 10/31/1999 20:15'! performActionSelector: actionSymbol (target ~~ nil and: [actionSelector ~~ nil]) ifTrue: [ target perform: actionSelector with: actionSymbol].! ! !FreeCellBoard methodsFor: 'private' stamp: 'di 12/12/2000 13:09'! visiblyMove: aCard to: aCell | p1 p2 nSteps | self inAutoMove ifFalse: [self captureStateBeforeGrab]. owner owner addMorphFront: aCard. p1 _ aCard position. p2 _ aCell position. nSteps _ 10. 1 to: nSteps-1 do: "Note final step happens with actual drop" [:i | aCard position: ((p2*i) + (p1*(nSteps-i))) // nSteps. self world displayWorld]. aCell acceptDroppingMorph: aCard event: nil! ! !FreeCellBoard methodsFor: 'actions' stamp: 'th 12/9/1999 19:10'! acceptCard: aCard onStack: aDeck " assumes that number of cards was check at drag time, need to reduce count if dropping into an empty stack" aCard hasSubmorphs ifTrue: [ aDeck ifEmpty: [ (aCard submorphCount+1) > (self maxDraggableStackSize: true) ifTrue: [^false]]] ifFalse: [^ nil]. ^nil. ! ! !FreeCellBoard methodsFor: 'actions' stamp: 'th 12/15/1999 16:17'! acceptSingleCard: aCard on: aDeck "Home cells and free cells don't accept multiple cards on a home cell, defer to deck for other cases" aCard hasSubmorphs ifTrue: [^ false] ifFalse: [^ nil]! ! !FreeCellBoard methodsFor: 'actions' stamp: 'th 12/15/1999 16:15'! cardMoved "Free cells and stacks do nothing special here - yet - th 12/15/1999 16:15 " self autoMoveCardsHome! ! !FreeCellBoard methodsFor: 'actions' stamp: 'djp 10/31/1999 22:02'! cardMovedHome self autoMoveCardsHome. self performActionSelector: #cardMovedHome.! ! !FreeCellBoard methodsFor: 'actions' stamp: 'RAA 3/4/2000 17:07'! doubleClickInStack: aDeck OnCard: aCard "if there is an empty free cell, move the card there. otherwise try for an empty stack" aCard == aDeck topCard ifFalse: [^self]. freeCells do: [:freeCell | freeCell ifEmpty: [ self visiblyMove: aCard to: freeCell. ^ aCard ] ]. stacks do: [ :each | each ifEmpty: [ self visiblyMove: aCard to: each. ^ aCard ] ]. ! ! !FreeCellBoard methodsFor: 'actions' stamp: 'djp 10/24/1999 03:08'! dragCard: aCard fromHome: aCardDeck ^nil "don't allow any cards to be dragged from a home cell"! ! !FreeCellBoard methodsFor: 'actions' stamp: 'djp 10/24/1999 00:46'! dragCard: aCard fromStack: aCardDeck | i cards | cards _ aCardDeck cards. i _ cards indexOf: aCard ifAbsent: [^ nil]. i > (self maxDraggableStackSize: false) ifTrue: [^ nil]. [i > 1] whileTrue: [(aCardDeck inStackingOrder: (cards at: i-1) onTopOf: (cards at: i)) ifFalse: [^ nil]. i _ i-1]. ^ aCard! ! !FreeCellBoard methodsFor: 'actions' stamp: 'di 12/12/2000 13:08'! inAutoMove "Return true if an automove sequence is in progress" ^ owner inAutoMove! ! !FreeCellBoard methodsFor: 'accessing' stamp: 'djp 10/16/1999 16:21'! actionSelector: aSymbolOrString (nil = aSymbolOrString or: ['nil' = aSymbolOrString or: [aSymbolOrString isEmpty]]) ifTrue: [^ actionSelector _ nil]. actionSelector _ aSymbolOrString asSymbol. ! ! !FreeCellBoard methodsFor: 'accessing' stamp: 'djp 10/11/1999 15:51'! cardDeck ^cardDeck! ! !FreeCellBoard methodsFor: 'accessing' stamp: 'djp 10/16/1999 15:33'! target: anObject target _ anObject! ! !FreeCellBoard methodsFor: 'as yet unclassified' stamp: 'RAA 3/4/2000 17:02'! drawOn: aCanvas "we don't have anything to draw, but we need a color so the inset border of one of our submorphs will work" ! ! !FreeCellBoard methodsFor: 'undo' stamp: 'di 12/12/2000 11:54'! captureStateBeforeGrab self removeProperty: #stateBeforeGrab. self setProperty: #stateBeforeGrab toValue: self capturedState ! ! !FreeCellBoard methodsFor: 'undo' stamp: 'di 12/12/2000 11:50'! capturedState self valueOfProperty: #stateBeforeGrab ifPresentDo: [:st | ^ st]. ^ { freeCells collect: [:deck | deck submorphs]. homeCells collect: [:deck | deck submorphs]. stacks collect: [:deck | deck submorphs] } ! ! !FreeCellBoard methodsFor: 'undo' stamp: 'di 12/12/2000 12:14'! rememberUndoableAction: aBlock named: caption self inAutoMove ifTrue: [^ aBlock value]. ^ super rememberUndoableAction: aBlock named: caption! ! !FreeCellBoard methodsFor: 'undo' stamp: 'di 12/12/2000 08:12'! undoFromCapturedState: st freeCells with: st first do: [:deck :morphs | deck removeAllMorphs; addAllMorphs: morphs]. homeCells with: st second do: [:deck :morphs | deck removeAllMorphs; addAllMorphs: morphs]. stacks with: st third do: [:deck :morphs | deck removeAllMorphs; addAllMorphs: morphs]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FreeCellBoard class instanceVariableNames: ''! !FreeCellBoard class methodsFor: 'as yet unclassified' stamp: 'di 1/16/2000 10:39'! includeInNewMorphMenu ^false! ! Object subclass: #FreeCellStatistics instanceVariableNames: 'sessionWins sessionLosses totalWins totalLosses streakWins streakLosses winsWithReplay lossesWithReplay lastGameWon lastGameLost currentCount currentType window statsMorph ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Games'! !FreeCellStatistics methodsFor: 'initialization' stamp: 'djp 10/24/1999 17:26'! initialize self reset.! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'th 12/20/1999 20:37'! print: aNumber type: type on: aStream "I moved the code from #printWins:on: and #printLosses:on: here because it is basically the same. I hope this increases the maintainability. - th 12/20/1999 20:37" aStream print: aNumber. type = #wins ifTrue: [aNumber = 1 ifTrue: [aStream nextPutAll: ' win'] ifFalse: [aStream nextPutAll: ' wins']]. type = #losses ifTrue: [aNumber = 1 ifTrue: [aStream nextPutAll: ' loss'] ifFalse: [aStream nextPutAll: ' losses']]! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'di 3/5/2000 15:22'! printOn: aStream self printSessionOn: aStream. aStream cr. self printTotalOn: aStream. aStream cr. self printReplaysOn: aStream. aStream cr. self printStreaksOn: aStream.! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'di 3/5/2000 15:38'! printReplaysOn: aStream | total | aStream nextPutAll: 'With replays: '; tab. self print: winsWithReplay type: #wins on: aStream. aStream nextPutAll: ', '. self print: lossesWithReplay type: #losses on: aStream. total _ winsWithReplay + lossesWithReplay. total ~~ 0 ifTrue: [aStream nextPutAll: ', '; print: (winsWithReplay / total * 100) asInteger; nextPut: $%]! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'th 12/20/1999 19:50'! printSessionOn: aStream | total | aStream nextPutAll: 'This session: ' , String tab. self print: sessionWins type: #wins on: aStream. aStream nextPutAll: ', '. self print: sessionLosses type: #losses on: aStream. total _ sessionWins + sessionLosses. total ~~ 0 ifTrue: [aStream nextPutAll: ', '; print: (sessionWins / total * 100) asInteger; nextPut: $%]! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'th 12/20/1999 19:53'! printStreaksOn: aStream aStream nextPutAll: 'Streaks: '; tab; tab. self print: streakWins type: #wins on: aStream. aStream nextPutAll: ', '. self print: streakLosses type: #losses on: aStream. aStream cr; tab; tab; tab; tab; nextPutAll: 'Current: '. self print: currentCount type: currentType on: aStream! ! !FreeCellStatistics methodsFor: 'printing' stamp: 'th 12/20/1999 19:48'! printTotalOn: aStream | total | aStream nextPutAll: 'Total: '; tab; tab; tab. self print: totalWins type: #wins on: aStream. aStream nextPutAll: ', '. self print: totalLosses type: #losses on: aStream. total _ totalWins + totalLosses. total ~~ 0 ifTrue: [aStream nextPutAll: ', '; print: (totalWins / total * 100) asInteger; nextPut: $%]! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'djp 10/24/1999 19:04'! changed window ifNotNil: [ statsMorph ifNotNil: [statsMorph contents: self statsText]]! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'di 3/5/2000 14:30'! gameLost: gameNumber "Don't count multiple losses of the same game" gameNumber = lastGameLost ifTrue: [^ self]. lastGameLost _ gameNumber. sessionLosses _ sessionLosses + 1. totalLosses _ totalLosses + 1. lossesWithReplay _ lossesWithReplay + 1. currentType = #losses ifTrue: [currentCount _ currentCount + 1] ifFalse: [currentCount _ 1. currentType _ #losses]. self updateStreak. self changed! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'di 3/5/2000 16:48'! gameWon: gameNumber sessionWins _ sessionWins + 1. totalWins _ totalWins + 1. gameNumber = lastGameWon ifFalse: [gameNumber = lastGameLost ifTrue: ["Finally won a game by replaying" lossesWithReplay _ lossesWithReplay - 1]. winsWithReplay _ winsWithReplay + 1]. lastGameWon _ gameNumber. currentType = #wins ifTrue: [currentCount _ currentCount + 1] ifFalse: [currentCount _ 1. currentType _ #wins]. self updateStreak. self changed! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'djp 10/24/1999 19:21'! newSession sessionWins _ 0. sessionLosses _ 0. currentCount _ 0. currentType _ nil. self changed.! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'djp 10/24/1999 17:15'! ok window delete. window _ nil.! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'di 3/5/2000 14:30'! reset sessionWins _ 0. sessionLosses _ 0. totalWins _ 0. totalLosses _ 0. streakWins _ 0. streakLosses _ 0. winsWithReplay _ 0. lossesWithReplay _ 0. lastGameWon _ 0. lastGameLost _ 0. currentCount _ 0. currentType _ nil. self changed. ! ! !FreeCellStatistics methodsFor: 'actions' stamp: 'th 12/20/1999 20:42'! updateStreak "I moved the code from #printWins:on: and #printLosses:on: here because it is basically the same. I hope this increases the maintainability. th 12/20/1999 20:41" currentType = #losses ifTrue: [streakLosses _ streakLosses max: currentCount]. currentType = #wins ifTrue: [streakWins _ streakWins max: currentCount]! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'ar 11/9/2000 21:17'! buildButton: aButton target: aTarget label: aLabel selector: aSelector "wrap a button or switch in an alignmentMorph to provide some space around the button" | a | aButton target: aTarget; label: aLabel; actionSelector: aSelector; borderColor: #raised; borderWidth: 2; color: Color gray. a _ AlignmentMorph newColumn wrapCentering: #center; cellPositioning: #topCenter; hResizing: #spaceFill; vResizing: #shrinkWrap; color: Color transparent; layoutInset: 1. a addMorph: aButton. ^ a ! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 19:20'! close window ifNotNil: [ window delete. window _ nil].! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 16:42'! color ^Color green darker! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'ar 11/9/2000 21:18'! display | panel | (window notNil and: [window owner notNil]) ifTrue: [window activate. ^nil]. panel _ AlignmentMorph newColumn. panel wrapCentering: #center; cellPositioning: #topCenter; hResizing: #rigid; vResizing: #rigid; extent: 250@150; color: self color; addMorphBack: self makeStatistics; addMorphBack: self makeControls. window _ panel openInWindowLabeled: 'FreeCell Statistics'.! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'ar 11/9/2000 21:18'! makeControls | row | row _ AlignmentMorph newRow. row wrapCentering: #center; cellPositioning: #leftCenter; hResizing: #spaceFill; vResizing: #shrinkWrap; color: self color; borderWidth: 2; borderColor: #inset; addMorphBack: self makeOkButton; addMorphBack: self makeResetButton. ^row.! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 16:34'! makeOkButton ^self buildButton: SimpleButtonMorph new target: self label: 'OK' selector: #ok! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 17:07'! makeResetButton ^self buildButton: SimpleButtonMorph new target: self label: 'Reset' selector: #reset! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'ar 11/9/2000 21:26'! makeStatistics | row | row _ AlignmentMorph newRow. row wrapCentering: #center; cellPositioning: #leftCenter; hResizing: #spaceFill; vResizing: #spaceFill; color: self color; borderWidth: 2; borderColor: #inset; addMorphBack: (AlignmentMorph newColumn wrapCentering: #center; cellPositioning: #topCenter; color: self color; addMorph: (statsMorph _ TextMorph new contents: self statsText)). ^row.! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 19:04'! statsText ^ String cr,self printString,String cr! ! !FreeCellStatistics methodsFor: 'user interface' stamp: 'djp 10/24/1999 15:56'! stringMorphFromPrintOn: aSelector ^StringMorph new contents: (String streamContents: [:s | self perform: aSelector with: s]); yourself.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FreeCellStatistics class instanceVariableNames: ''! !FreeCellStatistics class methodsFor: 'instance creation' stamp: 'di 1/16/2000 10:39'! includeInNewMorphMenu ^false! ! !FreeCellStatistics class methodsFor: 'instance creation' stamp: 'djp 10/24/1999 17:03'! new ^super new initialize! ! Object subclass: #FreeTranslation instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-TelNet WordNet'! !FreeTranslation commentStamp: '' prior: 0! Squeak interface to the translation server at www.freetranslation.com. Invoke it in any Squeak text pane by choosing 'translate it' from the shift-menu. Languages are set by the 'choose language; menu item of the shift menu. Or by changing (Preferences valueOfFlag: #languageTranslateFrom) and (Preferences valueOfFlag: #languageTranslateTo). See class method openScamperOn:. FreeTranslation openScamperOn: 'Why don''t you ever write anymore?' ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FreeTranslation class instanceVariableNames: ''! !FreeTranslation class methodsFor: 'as yet unclassified' stamp: 'tk 7/19/2000 08:19'! extract: aMimeDoc | pageSource str | "Extract the translated text from the web page" (aMimeDoc content beginsWith: 'error') ifTrue: [^ aMimeDoc content]. pageSource _ aMimeDoc content isoToSqueak. "brute force way to pull out the result" str _ ReadStream on: pageSource. str match: 'Translation Results by Transparent Language'. str match: '

'. ^ str upToAll: '

'! ! !FreeTranslation class methodsFor: 'as yet unclassified' stamp: 'tk 7/12/2000 13:54'! openScamperOn: currentSelection "Submit the string to the translation server at www.freetranslation.com. Ask it to translate from (Preferences valueOfFlag: #languageTranslateFrom) to (Preferences valueOfFlag: #languageTranslateTo). Display the results in a Scamper window, reusing the previous one if possible." | inputs scamperWindow from to | currentSelection size >= 10000 ifTrue: [^ self inform: 'Text selection is too long.']. (Preferences valueOfFlag: #languageTranslateFrom) == false ifTrue: [ Preferences setPreference: #languageTranslateFrom toValue: 'English']. (Preferences valueOfFlag: #languageTranslateTo) == false ifTrue: [ Preferences setPreference: #languageTranslateTo toValue: 'German']. (from _ Preferences valueOfFlag: #languageTranslateFrom) = (to _ Preferences valueOfFlag: #languageTranslateTo) ifTrue: [ ^ self inform: 'You asked to translate from ', from, ' to ', to, '.\' withCRs, 'Use "choose language" to set these.']. inputs _ Dictionary new. inputs at: 'SrcText' put: (Array with: currentSelection). inputs at: 'Sequence' put: #('core'). inputs at: 'Mode' put: #('html'). inputs at: 'template' put: #('TextResult2.htm'). inputs at: 'Language' put: (Array with: from, '/', to). scamperWindow _ Scamper newOrExistingOn: 'http://ets.freetranslation.com'. scamperWindow model submitFormWithInputs: inputs url: 'http://ets.freetranslation.com:5081' asUrl method: 'post'. scamperWindow activate. ! ! !FreeTranslation class methodsFor: 'as yet unclassified' stamp: 'tk 7/15/2000 07:33'! translate: aString from: fromLang to: toLang | inputs | "Submit the string to the translation server at www.freetranslation.com. Return the entire web page that freetranslation sends back." aString size >= 10000 ifTrue: [^ self inform: 'Text selection is too long.']. inputs _ Dictionary new. inputs at: 'SrcText' put: (Array with: aString). inputs at: 'Sequence' put: #('core'). inputs at: 'Mode' put: #('html'). inputs at: 'template' put: #('TextResult2.htm'). inputs at: 'Language' put: (Array with: fromLang, '/', toLang). ^ 'http://ets.freetranslation.com:5081' asUrl postFormArgs: inputs. ! ! !FreeTranslation class methodsFor: 'as yet unclassified' stamp: 'tk 1/3/2001 08:58'! translatePanel: buttonPlayer fromTo: normalDirection | ow fromTM toTM fromLang toLang tt doc answer width | "Gather up all the info I need from the morphs in the button's owner and do the translation. Insert the results in a TextMorph. Use www.freeTranslation.com Refresh the banner ad. TextMorph with 'from' in the title is starting text. PopUpChoiceMorph with 'from' in the title is the starting language. TextMorph with 'from' in the title is place to put the answer. PopUpChoiceMorph with 'from' in the title is the target language. If normalDirection is false, translate the other direction." ow _ buttonPlayer costume ownerThatIsA: PasteUpMorph. ow allMorphs do: [:mm | (mm isKindOf: TextMorph) ifTrue: [ (mm knownName asString includesSubString: 'from') ifTrue: [ fromTM _ mm]. (mm knownName asString includesSubString: 'to') ifTrue: [ toTM _ mm]]. (mm isKindOf: PopUpChoiceMorph) ifTrue: [ (mm knownName asString includesSubString: 'from') ifTrue: [ fromLang _ mm contents asString]. (mm owner knownName asString includesSubString: 'from') ifTrue: [ fromLang _ mm contents asString]. (mm knownName asString includesSubString: 'to') ifTrue: [ toLang _ mm contents asString]. (mm owner knownName asString includesSubString: 'to') ifTrue: [ toLang _ mm contents asString]]]. normalDirection ifFalse: ["switch" tt _ fromTM. fromTM _ toTM. toTM _ tt. tt _ fromLang. fromLang _ toLang. toLang _ tt]. Cursor wait showWhile: [ doc _ self translate: fromTM contents asString from: fromLang to: toLang. answer _ self extract: doc]. "pull out the translated text" width _ toTM width. toTM contents: answer wrappedTo: width. toTM changed.! ! HierarchicalUrl subclass: #FtpUrl instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !FtpUrl commentStamp: '' prior: 0! a reference to a file which may be downloaded by anonymous ftp! !FtpUrl methodsFor: 'downloading' stamp: 'ls 8/12/1998 01:24'! retrieveContents | server contents pathString listing | "currently assumes directories end in /, and things that don't end in / are files. Also, doesn't handle errors real well...." server _ ServerDirectory new. server server: self authority. server directory: '/'. server user: 'anonymous'. server password: 'SqueakUser'. pathString _ self pathString. pathString _ pathString copyFrom: 2 to: pathString size. "remove the leading /" self path last size = 0 ifFalse: [ "a file" contents _ (server getFileNamed: pathString). (contents respondsTo: #contents) ifTrue: [ "the file exists--return it" ^MIMEDocument contentType: (MIMEDocument guessTypeFromName: self path last) content: contents contents ] ifFalse: [ "some error" ^nil ]. ]. "a directory?" server directory: self pathString. listing _ String streamContents: [ :stream | stream nextPutAll: '', self pathString, ''; cr. stream nextPutAll: '

Listing for ', self pathString, '

'; cr. stream nextPutAll: '
    '; cr. server entries do: [ :entry | stream nextPutAll: '
  • '; nextPutAll: ''; nextPutAll: entry name; nextPutAll: ''; cr ] ]. ^MIMEDocument contentType: 'text/html' content: listing! ! !FtpUrl methodsFor: 'access' stamp: 'ls 7/24/1998 00:18'! pathString self path isEmpty ifTrue: [ ^'/' copy ]. ^String streamContents: [ :s | self path do: [ :p | s nextPut: $/. s nextPutAll: p ] ]! ! Vocabulary subclass: #FullVocabulary instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Protocols'! !FullVocabulary commentStamp: '' prior: 0! The vocabulary that it all-encompassing. Its categories consist of the union of all categories of a class and all its superclasses. The methods in each category consist of those with selectors that are associated with that category.! !FullVocabulary methodsFor: 'initialization' stamp: 'sw 12/5/2000 11:02'! initialize "Initialize the receiver (automatically called when instances are created via 'new') Vocabulary initialize " super initialize. vocabularyName _ #Full. self documentation: '"Full" is all-encompassing vocabulary that embraces all methods understood by an object'. self rigAFewCategories! ! !FullVocabulary methodsFor: 'initialization' stamp: 'sw 1/26/2001 23:03'! rigAFewCategories "Rig a few catgories, mate. 'Vocabulary fullVocabulary rigAFewCategories'" | aMethodCategory | #( (accessing 'Generally holds methods to read and write instance variables') (initialization 'messages typically sent when an object is created, to set up its initial state')) do: [:pair | aMethodCategory _ ElementCategory new categoryName: pair first. aMethodCategory documentation: pair second. self addCategory: aMethodCategory]! ! !FullVocabulary methodsFor: 'category list' stamp: 'sw 12/13/2000 17:34'! categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass "Answer the category list for the given object, considering only code implemented in mostGeneric and lower (or higher, depending on which way you're facing" | classToUse | classToUse _ anObject ifNil: [aClass] ifNotNil: [anObject class]. ^ mostGenericClass == classToUse ifTrue: [mostGenericClass organization categories] ifFalse: [classToUse allMethodCategoriesIntegratedThrough: mostGenericClass]! ! !FullVocabulary methodsFor: 'method list' stamp: 'sw 12/12/2000 12:22'! allMethodsInCategory: categoryName forInstance: anObject ofClass: aClass "Answer a list of all methods which are in the given category, on behalf of anObject" | classToUse | classToUse _ aClass ifNil: [anObject class]. ^ classToUse allMethodsInCategory: categoryName! ! !FullVocabulary methodsFor: 'queries' stamp: 'sw 12/13/2000 17:44'! categoriesContaining: aSelector forClass: aTargetClass "Answer a list of category names (all symbols) of categories that contain the given selector for the target object. Initially, this just returns one." | classDefiningSelector catName | classDefiningSelector _ aTargetClass classThatUnderstands: aSelector. classDefiningSelector ifNil: [^ OrderedCollection new]. catName _ classDefiningSelector whichCategoryIncludesSelector: aSelector. ^ OrderedCollection with: catName ! ! !FullVocabulary methodsFor: 'queries' stamp: 'sw 1/30/2001 12:47'! categoryDefiningSelector: aSelector forClass: targetClass "Answer which category defines the selector for the given class. Note reimplementor" | aClass | ^ (aClass _ targetClass classThatUnderstands: aSelector) ifNotNil: [aClass whichCategoryIncludesSelector: aSelector]! ! !FullVocabulary methodsFor: 'queries' stamp: 'sw 12/12/2000 06:06'! encompassesAPriori: aClass "Answer whether an object, by its very nature, is one that the receiver embraces" ^ true! ! !FullVocabulary methodsFor: 'queries' stamp: 'sw 12/1/2000 21:57'! includesSelector: aSelector "Answer whether the given selector is known to the vocabulary" ^ true! ! !FullVocabulary methodsFor: 'queries' stamp: 'sw 12/14/2000 06:00'! includesSelector: aSelector forInstance: anInstance ofClass: aTargetClass limitClass: mostGenericClass "Answer whether the vocabulary includes the given selector for the given class, only considering method implementations in mostGenericClass and lower" | classToUse aClass | classToUse _ self classToUseFromInstance: anInstance ofClass: aTargetClass. ^ (aClass _ classToUse whichClassIncludesSelector: aSelector) ifNil: [false] ifNotNil: [aClass includesBehavior: mostGenericClass]! ! TextComponent subclass: #FunctionComponent instanceVariableNames: 'inputSelectors functionSelector outputSelector outputValue ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Components'! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/4/1998 08:57'! accept "Inform the model of text to be accepted, and return true if OK." | textToAccept oldSelector | oldSelector _ functionSelector. textToAccept _ textMorph asText. textToAccept = self getText ifTrue: [^ self]. "No body to compile yet" functionSelector _ model class compile: self headerString , textToAccept asString classified: 'functions' notifying: nil. self setText: textToAccept. self hasUnacceptedEdits: false. oldSelector ifNotNil: [functionSelector = oldSelector ifFalse: [model class removeSelector: oldSelector]]. self fire! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/4/1998 08:12'! addAddHandMenuItemsForHalo: aMenu hand: aHandMorph super addAddHandMenuItemsForHalo: aMenu hand: aHandMorph. aMenu add: 'add pin' target: self selector: #addPin. ! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/4/1998 08:56'! addPin | i prev sideLength wasNew | wasNew _ self getText = textMorph asText. i _ pinSpecs size. prev _ pinSpecs last. sideLength _ prev pinLoc asInteger odd ifTrue: [self height] ifFalse: [self width]. pinSpecs _ pinSpecs copyWith: (PinSpec new pinName: ('abcdefghi' copyFrom: i to: i) direction: #input localReadSelector: nil localWriteSelector: nil modelReadSelector: nil modelWriteSelector: nil defaultValue: nil pinLoc: prev pinLoc + (8/sideLength) asFloat \\ 4). self initFromPinSpecs. self addPinFromSpec: pinSpecs last. wasNew ifTrue: [self setText: self getText]. self accept ! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/6/1998 11:27'! fire | arguments newValue | outputSelector ifNil: [^ outputValue _ nil]. functionSelector ifNil: [^ outputValue _ nil]. arguments _ inputSelectors collect: [:s | s ifNil: [nil] ifNotNil: [model perform: s]]. newValue _ (arguments findFirst: [:a | a==nil]) = 0 ifTrue: [model perform: functionSelector withArguments: arguments] ifFalse: [nil]. newValue = outputValue ifFalse: [model perform: outputSelector with: newValue. outputValue _ newValue]! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 22:12'! getText | ps | ^ ('"type a function of' , (String streamContents: [:s | 2 to: pinSpecs size do: [:i | ps _ pinSpecs at: i. (i>2 and: [i = pinSpecs size]) ifTrue: [s nextPutAll: ' and']. s nextPutAll: ' ', ps pinName]]) , '"') asText! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 22:04'! headerString | ps | ^ String streamContents: [:s | s nextPutAll: self knownName. 2 to: pinSpecs size do: [:i | ps _ pinSpecs at: i. s nextPutAll: ps pinName , ': '; nextPutAll: ps pinName , ' ']. s cr; tab; nextPutAll: '^ ']! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 23:34'! initFromPinSpecs outputSelector _ pinSpecs first modelWriteSelector. inputSelectors _ (pinSpecs copyFrom: 2 to: pinSpecs size) collect: [:ps | ps modelReadSelector]! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 16:14'! initPinSpecs pinSpecs _ Array with: (PinSpec new pinName: 'output' direction: #output localReadSelector: nil localWriteSelector: nil modelReadSelector: nil modelWriteSelector: nil defaultValue: nil pinLoc: 3.5) with: (PinSpec new pinName: 'a' direction: #input localReadSelector: nil localWriteSelector: nil modelReadSelector: nil modelWriteSelector: nil defaultValue: nil pinLoc: 1.5) ! ! !FunctionComponent methodsFor: 'as yet unclassified' stamp: 'di 5/3/1998 23:25'! update: aSymbol inputSelectors do: [:s | aSymbol = s ifTrue: [^ self fire]].! ! ImageReadWriter subclass: #GIFReadWriter instanceVariableNames: 'width height bitsPerPixel depth colorPalette rowByteSize xpos ypos pass interlace codeSize clearCode eoiCode freeCode maxCode prefixTable suffixTable remainBitCount bufByte bufStream transparentIndex mapOf32 ' classVariableNames: 'Extension ImageSeparator Terminator ' poolDictionaries: '' category: 'Graphics-Files'! !GIFReadWriter commentStamp: '' prior: 0! Copyright (c) Kazuki Yasumatsu, 1995. All rights reserved. Used with permission. Modified for use in Squeak.! !GIFReadWriter methodsFor: 'accessing' stamp: 'jm 11/12/97 18:00'! nextImage "Read in the next GIF image from the stream. Read it all into memory first for speed." | f | stream class == ReadWriteStream ifFalse: [ (stream respondsTo: #binary) ifTrue: [stream binary]. self on: (ReadWriteStream with: (stream contentsOfEntireFile))]. self readHeader. f _ self readBody. self close. f == nil ifTrue: [^ self error: 'corrupt GIF file']. transparentIndex ifNotNil: [ transparentIndex + 1 > colorPalette size ifTrue: [ colorPalette _ colorPalette forceTo: transparentIndex + 1 paddingWith: Color white]. colorPalette at: transparentIndex + 1 put: Color transparent]. f colors: colorPalette. ^ f ! ! !GIFReadWriter methodsFor: 'accessing' stamp: 'ar 9/28/2000 15:17'! nextPutImage: aForm | f newF | aForm unhibernate. f _ aForm colorReduced. "minimize depth" f depth > 8 ifTrue: [ "Not enough color space; do it the hard way." f _ f asFormOfDepth: 8]. f depth < 8 ifTrue: [ "writeBitData: expects depth of 8" newF _ f class extent: f extent depth: 8. (f isKindOf: ColorForm) ifTrue: [ newF copyBits: f boundingBox from: f at: 0@0 clippingBox: f boundingBox rule: Form over fillColor: nil map: nil. newF colors: f colors] ifFalse: [f displayOn: newF]. f _ newF]. (f isKindOf: ColorForm) ifTrue: [ (f colorsUsed includes: Color transparent) ifTrue: [ transparentIndex _ (f colors indexOf: Color transparent) - 1]] ifFalse: [transparentIndex _ nil]. width _ f width. height _ f height. bitsPerPixel _ f depth. colorPalette _ f colormapIfNeededForDepth: 32. interlace _ false. self writeHeader. self writeBitData: f bits. self close. ! ! !GIFReadWriter methodsFor: 'accessing' stamp: '6/18/97 13:18 '! setStream: aStream "Feed it in from an existing source" stream _ aStream! ! !GIFReadWriter methodsFor: 'accessing' stamp: 'di 9/15/1998 09:53'! understandsImageFormat ^('abc' collect: [:x | stream next asCharacter]) = 'GIF'! ! !GIFReadWriter methodsFor: 'private-encoding'! flushCode self flushBits! ! !GIFReadWriter methodsFor: 'private-encoding' stamp: 'tk 9/14/97 16:25'! readPixelFrom: bits "Since bits is a Bitmap with 32 bit values, watch out for the padding at the end of each row. But, GIF format already wants padding to 32 bit boundary!! OK as is. tk 9/14/97" | pixel | ypos >= height ifTrue: [^nil]. pixel _ bits byteAt: (ypos * rowByteSize + xpos + 1). self updatePixelPosition. ^pixel! ! !GIFReadWriter methodsFor: 'private-encoding'! writeBitData: bits "using modified Lempel-Ziv Welch algorithm." | maxBits maxMaxCode tSize initCodeSize ent tShift fCode pixel index disp nomatch | pass _ 0. xpos _ 0. ypos _ 0. rowByteSize _ width * 8 + 31 // 32 * 4. remainBitCount _ 0. bufByte _ 0. bufStream _ WriteStream on: (ByteArray new: 256). maxBits _ 12. maxMaxCode _ 1 bitShift: maxBits. tSize _ 5003. prefixTable _ Array new: tSize. suffixTable _ Array new: tSize. initCodeSize _ bitsPerPixel <= 1 ifTrue: [2] ifFalse: [bitsPerPixel]. self nextPut: initCodeSize. self setParameters: initCodeSize. tShift _ 0. fCode _ tSize. [fCode < 65536] whileTrue: [tShift _ tShift + 1. fCode _ fCode * 2]. tShift _ 8 - tShift. 1 to: tSize do: [:i | suffixTable at: i put: -1]. self writeCodeAndCheckCodeSize: clearCode. ent _ self readPixelFrom: bits. [(pixel _ self readPixelFrom: bits) == nil] whileFalse: [ fCode _ (pixel bitShift: maxBits) + ent. index _ ((pixel bitShift: tShift) bitXor: ent) + 1. (suffixTable at: index) = fCode ifTrue: [ent _ prefixTable at: index] ifFalse: [nomatch _ true. (suffixTable at: index) >= 0 ifTrue: [disp _ tSize - index + 1. index = 1 ifTrue: [disp _ 1]. "probe" [(index _ index - disp) < 1 ifTrue: [index _ index + tSize]. (suffixTable at: index) = fCode ifTrue: [ent _ prefixTable at: index. nomatch _ false. "continue whileFalse:"]. nomatch and: [(suffixTable at: index) > 0]] whileTrue: ["probe"]]. "nomatch" nomatch ifTrue: [self writeCodeAndCheckCodeSize: ent. ent _ pixel. freeCode < maxMaxCode ifTrue: [prefixTable at: index put: freeCode. suffixTable at: index put: fCode. freeCode _ freeCode + 1] ifFalse: [self writeCodeAndCheckCodeSize: clearCode. 1 to: tSize do: [:i | suffixTable at: i put: -1]. self setParameters: initCodeSize]]]]. prefixTable _ suffixTable _ nil. self writeCodeAndCheckCodeSize: ent. self writeCodeAndCheckCodeSize: eoiCode. self flushCode. self nextPut: 0. "zero-length packet" self nextPut: Terminator. ! ! !GIFReadWriter methodsFor: 'private-encoding'! writeCode: aCode self nextBitsPut: aCode! ! !GIFReadWriter methodsFor: 'private-encoding'! writeCodeAndCheckCodeSize: aCode self writeCode: aCode. self checkCodeSize! ! !GIFReadWriter methodsFor: 'private-encoding' stamp: 'jm 4/19/98 00:50'! writeHeader | byte | self nextPutAll: 'GIF87a' asByteArray. self writeWord: width. "Screen Width" self writeWord: height. "Screen Height" byte _ 16r80. "has color map" byte _ byte bitOr: ((bitsPerPixel - 1) bitShift: 5). "color resolution" byte _ byte bitOr: bitsPerPixel - 1. "bits per pixel" self nextPut: byte. self nextPut: 0. "background color." self nextPut: 0. "reserved" colorPalette do: [:pixelValue | self nextPut: ((pixelValue bitShift: -16) bitAnd: 255); nextPut: ((pixelValue bitShift: -8) bitAnd: 255); nextPut: (pixelValue bitAnd: 255)]. transparentIndex ifNotNil: [ "write graphics control block to record transparent color index" self nextPut: Extension; nextPutAll: (#(16rF9 4 1 0 0) as: ByteArray); nextPut: transparentIndex; nextPut: 0]. self nextPut: ImageSeparator. self writeWord: 0. "Image Left" self writeWord: 0. "Image Top" self writeWord: width. "Image Width" self writeWord: height. "Image Height" byte _ interlace ifTrue: [16r40] ifFalse: [0]. self nextPut: byte. ! ! !GIFReadWriter methodsFor: 'private-encoding'! writeWord: aWord self nextPut: (aWord bitAnd: 255). self nextPut: ((aWord bitShift: -8) bitAnd: 255). ^aWord! ! !GIFReadWriter methodsFor: 'private-decoding' stamp: 'bf 6/3/2000 00:14'! readBitData "using modified Lempel-Ziv Welch algorithm." | outCodes outCount bitMask initCodeSize code curCode oldCode inCode finChar i bytes f c | self readWord. "skip Image Left" self readWord. "skip Image Top" width _ self readWord. height _ self readWord. interlace _ (self next bitAnd: 16r40) ~= 0. "I ignore the possible existence of a local color map." pass _ 0. xpos _ 0. ypos _ 0. rowByteSize _ ((width + 3) // 4) * 4. remainBitCount _ 0. bufByte _ 0. bufStream _ ReadStream on: ByteArray new. outCodes _ ByteArray new: 1025. outCount _ 0. bitMask _ (1 bitShift: bitsPerPixel) - 1. prefixTable _ Array new: 4096. suffixTable _ Array new: 4096. initCodeSize _ self next. self setParameters: initCodeSize. bitsPerPixel > 8 ifTrue: [^self error: 'never heard of a GIF that deep']. bytes _ ByteArray new: rowByteSize * height. [(code _ self readCode) = eoiCode] whileFalse: [code = clearCode ifTrue: [self setParameters: initCodeSize. curCode _ oldCode _ code _ self readCode. finChar _ curCode bitAnd: bitMask. "Horrible hack to avoid running off the end of the bitmap. Seems to cure problem reading some gifs!!? tk 6/24/97 20:16" xpos = 0 ifTrue: [ ypos < height ifTrue: [ bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar]] ifFalse: [bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar]. self updatePixelPosition] ifFalse: [curCode _ inCode _ code. curCode >= freeCode ifTrue: [curCode _ oldCode. outCodes at: (outCount _ outCount + 1) put: finChar]. [curCode > bitMask] whileTrue: [outCount > 1024 ifTrue: [^self error: 'corrupt GIF file (OutCount)']. outCodes at: (outCount _ outCount + 1) put: (suffixTable at: curCode + 1). curCode _ prefixTable at: curCode + 1]. finChar _ curCode bitAnd: bitMask. outCodes at: (outCount _ outCount + 1) put: finChar. i _ outCount. [i > 0] whileTrue: ["self writePixel: (outCodes at: i) to: bits" bytes at: (ypos * rowByteSize) + xpos + 1 put: (outCodes at: i). self updatePixelPosition. i _ i - 1]. outCount _ 0. prefixTable at: freeCode + 1 put: oldCode. suffixTable at: freeCode + 1 put: finChar. oldCode _ inCode. freeCode _ freeCode + 1. self checkCodeSize]]. prefixTable _ suffixTable _ nil. f _ ColorForm extent: width@height depth: 8. f bits copyFromByteArray: bytes. "Squeak can handle depths 1, 2, 4, and 8" bitsPerPixel > 4 ifTrue: [^ f]. "reduce depth to save space" c _ ColorForm extent: width@height depth: (bitsPerPixel = 3 ifTrue: [4] ifFalse: [bitsPerPixel]). f displayOn: c. ^ c ! ! !GIFReadWriter methodsFor: 'private-decoding' stamp: 'tk 8/7/96'! readBody "Read the GIF blocks. Modified to return a form. " | form extype block blocksize | form _ nil. [stream atEnd] whileFalse: [ block _ self next. block = Terminator ifTrue: [^ form]. block = ImageSeparator ifTrue: [ form isNil ifTrue: [form _ self readBitData] ifFalse: [self skipBitData]. ] ifFalse: [ block = Extension ifFalse: [^ form "^ self error: 'Unknown block type'"]. "Extension block" extype _ self next. "extension type" extype = 16rF9 ifTrue: [ "graphics control" self next = 4 ifFalse: [^ form "^ self error: 'corrupt GIF file'"]. self next; next; next. transparentIndex _ self next. self next = 0 ifFalse: [^ form "^ self error: 'corrupt GIF file'"]. ] ifFalse: [ "Skip blocks" [(blocksize _ self next) > 0] whileTrue: [self next: blocksize]]]]. ! ! !GIFReadWriter methodsFor: 'private-decoding'! readCode ^self nextBits! ! !GIFReadWriter methodsFor: 'private-decoding'! readHeader | is89 byte hasColorMap array r g b | (self hasMagicNumber: 'GIF87a' asByteArray) ifTrue: [is89 _ false] ifFalse: [(self hasMagicNumber: 'GIF89a' asByteArray) ifTrue: [is89 _ true] ifFalse: [^ self error: 'This does not appear to be a GIF file']]. self readWord. "skip Screen Width" self readWord. "skip Screen Height" byte _ self next. hasColorMap _ (byte bitAnd: 16r80) ~= 0. bitsPerPixel _ (byte bitAnd: 7) + 1. byte _ self next. "skip background color." self next ~= 0 ifTrue: [is89 ifFalse: [^self error: 'corrupt GIF file (screen descriptor)']]. hasColorMap ifTrue: [array _ Array new: (1 bitShift: bitsPerPixel). 1 to: array size do: [:i | r _ self next. g _ self next. b _ self next. array at: i put: (Color r: r g: g b: b range: 255) "depth 32"]. colorPalette _ array] ifFalse: ["Transcript cr; show: 'GIF file does not have a color map.'." colorPalette _ nil "Palette monochromeDefault"].! ! !GIFReadWriter methodsFor: 'private-decoding'! readWord ^self next + (self next bitShift: 8)! ! !GIFReadWriter methodsFor: 'private-decoding'! skipBitData | misc blocksize | self readWord. "skip Image Left" self readWord. "skip Image Top" self readWord. "width" self readWord. "height" misc _ self next. (misc bitAnd: 16r80) = 0 ifFalse: [ "skip colormap" 1 to: (1 bitShift: (misc bitAnd: 7) + 1) do: [:i | self next; next; next]]. self next. "minimum code size" [(blocksize _ self next) > 0] whileTrue: [self next: blocksize]! ! !GIFReadWriter methodsFor: 'private-bits access'! flushBits remainBitCount = 0 ifFalse: [self nextBytePut: bufByte. remainBitCount _ 0]. self flushBuffer! ! !GIFReadWriter methodsFor: 'private-bits access'! nextBits | integer readBitCount shiftCount byte | integer _ 0. remainBitCount = 0 ifTrue: [readBitCount _ 8. shiftCount _ 0] ifFalse: [readBitCount _ remainBitCount. shiftCount _ remainBitCount - 8]. [readBitCount < codeSize] whileTrue: [byte _ self nextByte. byte == nil ifTrue: [^eoiCode]. integer _ integer + (byte bitShift: shiftCount). shiftCount _ shiftCount + 8. readBitCount _ readBitCount + 8]. (remainBitCount _ readBitCount - codeSize) = 0 ifTrue: [byte _ self nextByte] ifFalse: [byte _ self peekByte]. byte == nil ifTrue: [^eoiCode]. ^(integer + (byte bitShift: shiftCount)) bitAnd: maxCode! ! !GIFReadWriter methodsFor: 'private-bits access'! nextBitsPut: anInteger | integer writeBitCount shiftCount | shiftCount _ 0. remainBitCount = 0 ifTrue: [writeBitCount _ 8. integer _ anInteger] ifFalse: [writeBitCount _ remainBitCount. integer _ bufByte + (anInteger bitShift: 8 - remainBitCount)]. [writeBitCount < codeSize] whileTrue: [self nextBytePut: ((integer bitShift: shiftCount) bitAnd: 255). shiftCount _ shiftCount - 8. writeBitCount _ writeBitCount + 8]. (remainBitCount _ writeBitCount - codeSize) = 0 ifTrue: [self nextBytePut: (integer bitShift: shiftCount)] ifFalse: [bufByte _ integer bitShift: shiftCount]. ^anInteger! ! !GIFReadWriter methodsFor: 'private-packing'! fillBuffer | packSize | packSize _ self next. bufStream _ ReadStream on: (self next: packSize)! ! !GIFReadWriter methodsFor: 'private-packing'! flushBuffer bufStream isEmpty ifTrue: [^self]. self nextPut: bufStream size. self nextPutAll: bufStream contents. bufStream _ WriteStream on: (ByteArray new: 256)! ! !GIFReadWriter methodsFor: 'private-packing'! nextByte bufStream atEnd ifTrue: [self atEnd ifTrue: [^nil]. self fillBuffer]. ^bufStream next! ! !GIFReadWriter methodsFor: 'private-packing'! nextBytePut: aByte bufStream nextPut: aByte. bufStream size >= 254 ifTrue: [self flushBuffer]! ! !GIFReadWriter methodsFor: 'private-packing'! peekByte bufStream atEnd ifTrue: [self atEnd ifTrue: [^nil]. self fillBuffer]. ^bufStream peek! ! !GIFReadWriter methodsFor: 'private'! checkCodeSize (freeCode > maxCode and: [codeSize < 12]) ifTrue: [codeSize _ codeSize + 1. maxCode _ (1 bitShift: codeSize) - 1]! ! !GIFReadWriter methodsFor: 'private'! setParameters: initCodeSize clearCode _ 1 bitShift: initCodeSize. eoiCode _ clearCode + 1. freeCode _ clearCode + 2. codeSize _ initCodeSize + 1. maxCode _ (1 bitShift: codeSize) - 1! ! !GIFReadWriter methodsFor: 'private'! updatePixelPosition (xpos _ xpos + 1) >= width ifFalse: [^self]. xpos _ 0. interlace ifFalse: [ypos _ ypos + 1. ^self]. pass = 0 ifTrue: [(ypos _ ypos + 8) >= height ifTrue: [pass _ pass + 1. ypos _ 4]. ^self]. pass = 1 ifTrue: [(ypos _ ypos + 8) >= height ifTrue: [pass _ pass + 1. ypos _ 2]. ^self]. pass = 2 ifTrue: [(ypos _ ypos + 4) >= height ifTrue: [pass _ pass + 1. ypos _ 1]. ^self]. pass = 3 ifTrue: [ypos _ ypos + 2. ^self]. ^self error: 'can''t happen'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GIFReadWriter class instanceVariableNames: ''! !GIFReadWriter class methodsFor: 'class initialization'! initialize "GIFReadWriter initialize" ImageSeparator _ $, asInteger. Extension _ $!! asInteger. Terminator _ $; asInteger. ! ! SoundCodec subclass: #GSMCodec instanceVariableNames: 'encodeState decodeState ' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !GSMCodec methodsFor: 'subclass responsibilities' stamp: 'jm 2/4/1999 11:36'! bytesPerEncodedFrame ^ 33 ! ! !GSMCodec methodsFor: 'subclass responsibilities' stamp: 'jm 2/4/1999 11:44'! decodeFrames: frameCount from: srcByteArray at: srcIndex into: dstSoundBuffer at: dstIndex | p | p _ self primDecode: decodeState frames: frameCount from: srcByteArray at: srcIndex into: dstSoundBuffer at: dstIndex. ^ Array with: p x with: p y ! ! !GSMCodec methodsFor: 'subclass responsibilities' stamp: 'jm 2/4/1999 11:44'! encodeFrames: frameCount from: srcSoundBuffer at: srcIndex into: dstByteArray at: dstIndex | p | p _ self primEncode: encodeState frames: frameCount from: srcSoundBuffer at: srcIndex into: dstByteArray at: dstIndex. ^ Array with: p x with: p y ! ! !GSMCodec methodsFor: 'subclass responsibilities' stamp: 'jm 2/4/1999 11:32'! reset "Reset my encoding/decoding state to prepare to encode or decode a new sound stream." encodeState _ self primNewState. decodeState _ self primNewState. ! ! !GSMCodec methodsFor: 'subclass responsibilities' stamp: 'jm 2/4/1999 11:36'! samplesPerFrame ^ 160 ! ! !GSMCodec methodsFor: 'primitives' stamp: 'jm 2/4/1999 11:33'! primDecode: state frames: frameCount from: srcSoundBuffer at: srcIndex into: dstByteArray at: dstIndex self primitiveFailed. ! ! !GSMCodec methodsFor: 'primitives' stamp: 'jm 2/4/1999 11:33'! primEncode: state frames: frameCount from: srcSoundBuffer at: srcIndex into: dstByteArray at: dstIndex self primitiveFailed. ! ! !GSMCodec methodsFor: 'primitives' stamp: 'jm 2/4/1999 11:35'! primNewState self error: 'The SoundCodecPrims plugin is not available'. ! ! FastInflateStream subclass: #GZipReadStream instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'GZipConstants ' category: 'System-Compression'! !GZipReadStream methodsFor: 'initialize' stamp: 'ar 12/27/1999 15:37'! on: aCollection from: firstIndex to: lastIndex "Check the header of the GZIP stream." | method magic flags length | super on: aCollection from: firstIndex to: lastIndex. magic _ self nextBits: 16. (magic = GZipMagic) ifFalse:[^self error:'Not a GZipped stream']. method _ self nextBits: 8. (method = GZipDeflated) ifFalse:[^self error:'Bad compression method']. flags _ self nextBits: 8. (flags anyMask: GZipEncryptFlag) ifTrue:[^self error:'Cannot decompress encrypted stream']. (flags anyMask: GZipReservedFlags) ifTrue:[^self error:'Cannot decompress stream with unknown flags']. "Ignore stamp, extra flags, OS type" self nextBits: 16; nextBits: 16. "stamp" self nextBits: 8. "extra flags" self nextBits: 8. "OS type" (flags anyMask: GZipContinueFlag) "Number of multi-part archive - ignored" ifTrue:[self nextBits: 16]. (flags anyMask: GZipExtraField) "Extra fields - ignored" ifTrue:[ length _ self nextBits: 16. 1 to: length do:[:i| self nextBits: 8]]. (flags anyMask: GZipNameFlag) "Original file name - ignored" ifTrue:[[(self nextBits: 8) = 0] whileFalse]. (flags anyMask: GZipCommentFlag) "Comment - ignored" ifTrue:[[(self nextBits: 8) = 0] whileFalse]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GZipReadStream class instanceVariableNames: ''! !GZipReadStream class methodsFor: 'class initialization' stamp: 'ar 12/27/1999 15:37'! initialize "GZipReadStream initialize" #( (GZipMagic 16r8B1F) "GZIP magic number" (GZipDeflated 8) "Compression method" (GZipAsciiFlag 16r01) "Contents is ASCII" (GZipContinueFlag 16r02) "Part of a multi-part archive" (GZipExtraField 16r04) "Archive has extra fields" (GZipNameFlag 16r08) "Archive has original file name" (GZipCommentFlag 16r10) "Archive has comment" (GZipEncryptFlag 16r20) "Archive is encrypted" (GZipReservedFlags 16rC0)"Reserved" ) do:[:spec| GZipConstants declare: spec first from: Undeclared. GZipConstants at: spec first put: spec last. ].! ! Object subclass: #GZipSurrogateStream instanceVariableNames: 'gZipStream zippedFileStream bufferStream positionThusFar ' classVariableNames: '' poolDictionaries: '' category: 'System-Compression'! !GZipSurrogateStream commentStamp: '' prior: 0! A pseudo stream that allows SmartRefStream to write directly to a gzipped file. There are some peculiarities of the project exporting process that require: 1. We ignore #close since the file is closed and may be reopened to continue writing. We implement #reallyClose for when we know that all writing is over. 2. We use a BitBlt to write WordArrayForSegment objects. Bit of a hack, but there it is. | fileStream wa | wa _ WordArrayForSegment new: 30000. 1 to: wa size do: [ :i | wa at: i put: i]. fileStream _ GZipSurrogateStream newFileNamed: 'xxx3.gz' inDirectory: FileDirectory default. fileStream nextPutAll: 'this is a test'. fileStream nextPutAll: wa. fileStream reallyClose. ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! ascii self bufferStream ascii! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:42'! binary self bufferStream binary! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:43'! bufferStream ^bufferStream ifNil: [bufferStream _ RWBinaryOrTextStream on: (ByteArray new: 5000)]. ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 09:26'! close "we don't want to until user is really done" ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:26'! closed ^false! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 10:59'! command: aString "Overridden by HtmlFileStream to append commands directly without translation. 4/5/96 tk" "We ignore any HTML commands. Do nothing"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! cr self bufferStream cr! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 19:18'! fileOutClass: extraClass andObject: theObject "Write a file that has both the source code for the named class and an object as bits. Any instance-specific object will get its class written automatically." | class srefStream | self timeStamp. extraClass ifNotNil: [ class _ extraClass. "A specific class the user wants written" class sharedPools size > 0 ifTrue: [ class shouldFileOutPools ifTrue: [class fileOutSharedPoolsOn: self] ]. class fileOutOn: self moveSource: false toFile: 0 ]. "Append the object's raw data" srefStream _ SmartRefStream on: self. srefStream nextPut: theObject. "and all subobjects" srefStream close. "also closes me - well it thinks it does, anyway" ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 19:16'! flushBuffer | data | bufferStream ifNil: [^self]. data _ bufferStream contents asByteArray. gZipStream nextPutAll: data. positionThusFar _ positionThusFar + data size. bufferStream _ nil. ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/22/2000 18:36'! header "ignore"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 19:12'! newFileNamed: fName inDirectory: aDirectory positionThusFar _ 0. zippedFileStream _ aDirectory newFileNamed: fName. zippedFileStream binary; setFileTypeToObject. "Type and Creator not to be text, so can be enclosed in an email" gZipStream _ GZipWriteStream on: zippedFileStream. ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 09:23'! next ^self bufferStream next! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:42'! nextChunkPut: aString self bufferStream nextChunkPut: aString! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:42'! nextInt32Put: int32 ^self bufferStream nextInt32Put: int32 ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! nextNumber: n put: v ^self bufferStream nextNumber: n put: v ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! nextPut: aByte ^self bufferStream nextPut: aByte ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:24'! nextPutAll: aString ^aString writeOnGZIPByteStream: self ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! nextPutAllBytes: aString ^self bufferStream nextPutAll: aString ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 11:36'! nextPutAllWordArray: aWordArray | ba hackwa hackba blt rowsAtATime sourceOrigin rowsRemaining | self flag: #bob. "do we need to be concerned by bytesPerElement??" ba _ nil. rowsAtATime _ 2000. "or 8000 bytes" hackwa _ Form new hackBits: aWordArray. sourceOrigin _ 0@0. [(rowsRemaining _ hackwa height - sourceOrigin y) > 0] whileTrue: [ rowsAtATime _ rowsAtATime min: rowsRemaining. (ba isNil or: [ba size ~= (rowsAtATime * 4)]) ifTrue: [ ba _ ByteArray new: rowsAtATime * 4. hackba _ Form new hackBits: ba. blt _ (BitBlt toForm: hackba) sourceForm: hackwa. ]. blt combinationRule: Form over; sourceOrigin: sourceOrigin; destX: 0 destY: 0 width: 4 height: rowsAtATime; copyBits. self bufferStream nextPutAll: ba. self flushBuffer. sourceOrigin _ sourceOrigin x @ (sourceOrigin y + rowsAtATime). ]. ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/22/2000 18:42'! nextStringPut: s "Append the string, s, to the receiver. Only used by DataStream. Max size of 64*256*256*256." | length | (length _ s size) < 192 ifTrue: [self nextPut: length] ifFalse: [self nextPut: (length digitAt: 4)+192. self nextPut: (length digitAt: 3). self nextPut: (length digitAt: 2). self nextPut: (length digitAt: 1)]. self nextPutAll: s. ^s! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 7/11/2000 11:22'! originalContents ^'' "used only to determine if we are byte-structured"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:26'! padToEndWith: aChar "We don't have pages, so we are at the end, and don't need to pad."! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 19:12'! position ^self bufferStream position + positionThusFar! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 19:13'! reallyClose self flushBuffer. gZipStream close. ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:30'! reopen "ignore"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/22/2000 18:36'! setFileTypeToObject "ignore"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:30'! setToEnd "ignore"! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 09:21'! skip: aNumber ^self bufferStream skip: aNumber ! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 08:41'! timeStamp "Append the current time to the receiver as a String." self bufferStream nextChunkPut: "double string quotes and !!s" (String streamContents: [:s | Smalltalk timeStamp: s]) printString. self bufferStream cr! ! !GZipSurrogateStream methodsFor: 'as yet unclassified' stamp: 'RAA 6/22/2000 18:37'! trailer "ignore"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GZipSurrogateStream class instanceVariableNames: ''! !GZipSurrogateStream class methodsFor: 'as yet unclassified' stamp: 'RAA 6/23/2000 11:50'! newFileNamed: fName inDirectory: aDirectory ^self new newFileNamed: fName inDirectory: aDirectory! ! ZipWriteStream subclass: #GZipWriteStream instanceVariableNames: 'crc crcPosition bytesWritten ' classVariableNames: 'CrcTable ' poolDictionaries: 'GZipConstants ' category: 'System-Compression'! !GZipWriteStream methodsFor: 'initialize-release' stamp: 'ar 12/29/1999 20:07'! on: aCollection crc _ 16rFFFFFFFF. crcPosition _ 1. bytesWritten _ 0. super on: aCollection. self writeHeader. ! ! !GZipWriteStream methodsFor: 'initialize-release' stamp: 'ar 1/2/2000 16:36'! release "Write crc and the number of bytes encoded" super release. self updateCrc. crc _ crc bitXor: 16rFFFFFFFF. encoder flushBits. 0 to: 3 do:[:i| encoder nextBytePut: (crc >> (i*8) bitAnd: 255)]. 0 to: 3 do:[:i| encoder nextBytePut: (bytesWritten >> (i*8) bitAnd: 255)].! ! !GZipWriteStream methodsFor: 'initialize-release' stamp: 'ar 12/30/1999 11:41'! writeHeader "Write the GZip header" encoder nextBits: 16 put: GZipMagic. encoder nextBits: 8 put: GZipDeflated. encoder nextBits: 8 put: 0. "No flags" encoder nextBits: 32 put: 0. "no time stamp" encoder nextBits: 8 put: 0. "No extra flags" encoder nextBits: 8 put: 0. "No OS type" ! ! !GZipWriteStream methodsFor: 'private' stamp: 'ar 12/27/1999 17:12'! moveContentsToFront "Need to update crc here" self updateCrc. super moveContentsToFront. crcPosition _ position + 1.! ! !GZipWriteStream methodsFor: 'private' stamp: 'ar 12/29/1999 20:09'! updateCrc crcPosition <= position ifTrue:[ bytesWritten _ bytesWritten + position - crcPosition + 1. crc _ self updateCrc: crc from: crcPosition to: position in: collection. crcPosition _ position + 1].! ! !GZipWriteStream methodsFor: 'private' stamp: 'ar 2/2/2001 15:47'! updateCrc: oldCrc from: start to: stop in: aCollection | newCrc | newCrc _ oldCrc. start to: stop do:[:i| newCrc _ (CrcTable at: ((newCrc bitXor: (aCollection byteAt: i)) bitAnd: 255) + 1) bitXor: (newCrc bitShift: -8). ]. ^newCrc! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GZipWriteStream class instanceVariableNames: ''! !GZipWriteStream class methodsFor: 'class initialization' stamp: 'ar 12/30/1999 14:35'! crcTable ^CrcTable! ! !GZipWriteStream class methodsFor: 'class initialization' stamp: 'ar 12/27/1999 16:55'! initialize "GZipWriteStream initialize" CrcTable _ #(16r00000000 16r77073096 16rEE0E612C 16r990951BA 16r076DC419 16r706AF48F 16rE963A535 16r9E6495A3 16r0EDB8832 16r79DCB8A4 16rE0D5E91E 16r97D2D988 16r09B64C2B 16r7EB17CBD 16rE7B82D07 16r90BF1D91 16r1DB71064 16r6AB020F2 16rF3B97148 16r84BE41DE 16r1ADAD47D 16r6DDDE4EB 16rF4D4B551 16r83D385C7 16r136C9856 16r646BA8C0 16rFD62F97A 16r8A65C9EC 16r14015C4F 16r63066CD9 16rFA0F3D63 16r8D080DF5 16r3B6E20C8 16r4C69105E 16rD56041E4 16rA2677172 16r3C03E4D1 16r4B04D447 16rD20D85FD 16rA50AB56B 16r35B5A8FA 16r42B2986C 16rDBBBC9D6 16rACBCF940 16r32D86CE3 16r45DF5C75 16rDCD60DCF 16rABD13D59 16r26D930AC 16r51DE003A 16rC8D75180 16rBFD06116 16r21B4F4B5 16r56B3C423 16rCFBA9599 16rB8BDA50F 16r2802B89E 16r5F058808 16rC60CD9B2 16rB10BE924 16r2F6F7C87 16r58684C11 16rC1611DAB 16rB6662D3D 16r76DC4190 16r01DB7106 16r98D220BC 16rEFD5102A 16r71B18589 16r06B6B51F 16r9FBFE4A5 16rE8B8D433 16r7807C9A2 16r0F00F934 16r9609A88E 16rE10E9818 16r7F6A0DBB 16r086D3D2D 16r91646C97 16rE6635C01 16r6B6B51F4 16r1C6C6162 16r856530D8 16rF262004E 16r6C0695ED 16r1B01A57B 16r8208F4C1 16rF50FC457 16r65B0D9C6 16r12B7E950 16r8BBEB8EA 16rFCB9887C 16r62DD1DDF 16r15DA2D49 16r8CD37CF3 16rFBD44C65 16r4DB26158 16r3AB551CE 16rA3BC0074 16rD4BB30E2 16r4ADFA541 16r3DD895D7 16rA4D1C46D 16rD3D6F4FB 16r4369E96A 16r346ED9FC 16rAD678846 16rDA60B8D0 16r44042D73 16r33031DE5 16rAA0A4C5F 16rDD0D7CC9 16r5005713C 16r270241AA 16rBE0B1010 16rC90C2086 16r5768B525 16r206F85B3 16rB966D409 16rCE61E49F 16r5EDEF90E 16r29D9C998 16rB0D09822 16rC7D7A8B4 16r59B33D17 16r2EB40D81 16rB7BD5C3B 16rC0BA6CAD 16rEDB88320 16r9ABFB3B6 16r03B6E20C 16r74B1D29A 16rEAD54739 16r9DD277AF 16r04DB2615 16r73DC1683 16rE3630B12 16r94643B84 16r0D6D6A3E 16r7A6A5AA8 16rE40ECF0B 16r9309FF9D 16r0A00AE27 16r7D079EB1 16rF00F9344 16r8708A3D2 16r1E01F268 16r6906C2FE 16rF762575D 16r806567CB 16r196C3671 16r6E6B06E7 16rFED41B76 16r89D32BE0 16r10DA7A5A 16r67DD4ACC 16rF9B9DF6F 16r8EBEEFF9 16r17B7BE43 16r60B08ED5 16rD6D6A3E8 16rA1D1937E 16r38D8C2C4 16r4FDFF252 16rD1BB67F1 16rA6BC5767 16r3FB506DD 16r48B2364B 16rD80D2BDA 16rAF0A1B4C 16r36034AF6 16r41047A60 16rDF60EFC3 16rA867DF55 16r316E8EEF 16r4669BE79 16rCB61B38C 16rBC66831A 16r256FD2A0 16r5268E236 16rCC0C7795 16rBB0B4703 16r220216B9 16r5505262F 16rC5BA3BBE 16rB2BD0B28 16r2BB45A92 16r5CB36A04 16rC2D7FFA7 16rB5D0CF31 16r2CD99E8B 16r5BDEAE1D 16r9B64C2B0 16rEC63F226 16r756AA39C 16r026D930A 16r9C0906A9 16rEB0E363F 16r72076785 16r05005713 16r95BF4A82 16rE2B87A14 16r7BB12BAE 16r0CB61B38 16r92D28E9B 16rE5D5BE0D 16r7CDCEFB7 16r0BDBDF21 16r86D3D2D4 16rF1D4E242 16r68DDB3F8 16r1FDA836E 16r81BE16CD 16rF6B9265B 16r6FB077E1 16r18B74777 16r88085AE6 16rFF0F6A70 16r66063BCA 16r11010B5C 16r8F659EFF 16rF862AE69 16r616BFFD3 16r166CCF45 16rA00AE278 16rD70DD2EE 16r4E048354 16r3903B3C2 16rA7672661 16rD06016F7 16r4969474D 16r3E6E77DB 16rAED16A4A 16rD9D65ADC 16r40DF0B66 16r37D83BF0 16rA9BCAE53 16rDEBB9EC5 16r47B2CF7F 16r30B5FFE9 16rBDBDF21C 16rCABAC28A 16r53B39330 16r24B4A3A6 16rBAD03605 16rCDD70693 16r54DE5729 16r23D967BF 16rB3667A2E 16rC4614AB8 16r5D681B02 16r2A6F2B94 16rB40BBE37 16rC30C8EA1 16r5A05DF1B 16r2D02EF8D ).! ! GesturalEvent subclass: #GazeGesturalEvent instanceVariableNames: 'point ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !GazeGesturalEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 03:17'! point ^ point! ! !GazeGesturalEvent methodsFor: 'accessing' stamp: 'len 8/28/1999 03:17'! point: aPoint point _ aPoint! ! !GazeGesturalEvent methodsFor: 'playing' stamp: 'len 9/6/1999 00:42'! actOn: aHeadMorph aHeadMorph face lookAt: self point! ! !GazeGesturalEvent methodsFor: 'printing' stamp: 'len 9/6/1999 00:42'! printOn: aStream aStream nextPutAll: 'look at '; print: self point! ! BookMorph subclass: #GeeBookMorph instanceVariableNames: 'geeMail ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeeBookMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 11:48'! geeMail: aGeeMail geeMail _ aGeeMail.! ! !GeeBookMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 12:13'! geePageRectangles | pageBounds allPageRects | pageBounds _ geeMail topLeft extent: geeMail width @ (geeMail height min: Display height - 50). allPageRects _ OrderedCollection new. [pageBounds top <= geeMail bottom] whileTrue: [ allPageRects add: pageBounds. pageBounds _ pageBounds translateBy: 0 @ pageBounds height. ]. ^allPageRects ! ! !GeeBookMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 12:15'! initialize newPagePrototype _ GeeBookPageMorph new extent: Display extent // 3. super initialize. color _ (Color r: 0.909 g: 0.819 b: 0.09).! ! !GeeBookMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 12:44'! rebuildPages pages _ self geePageRectangles collect: [ :each | GeeBookPageMorph new disableDragNDrop; geeMail: geeMail geeMailRectangle: each. ]. currentPage delete. currentPage _ nil. pages isEmpty ifTrue: [^ self insertPage]. self goToPage: 1. ! ! PasteUpMorph subclass: #GeeBookPageMorph instanceVariableNames: 'geeMail geeMailRectangle ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeeBookPageMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 12:40'! fullDrawOn: aCanvas aCanvas translateTo: self topLeft + aCanvas origin - geeMailRectangle origin clippingTo: (bounds translateBy: aCanvas origin) during: [ :c | geeMail disablePageBreaksWhile: [geeMail fullDrawOn: c]. ]. ! ! !GeeBookPageMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 11:57'! geeMail: aGeeMail geeMailRectangle: aRectangle geeMail _ aGeeMail. geeMailRectangle _ aRectangle. self extent: aRectangle extent.! ! !GeeBookPageMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 13:25'! handlesMouseDown: evt "| localPt | localPt _ (self transformFrom: self world) globalPointToLocal: evt cursorPoint. submorphs do: [ :each | (each fullBounds containsPoint: localPt) ifTrue: [^false]]." ^ true ! ! !GeeBookPageMorph methodsFor: 'as yet unclassified' stamp: 'RAA 10/3/2000 13:51'! mouseDown: evt "Handle a mouse down event." "{evt. self recipientForMouseDown: evt. self} explore." ! ! AlansTextPlusMorph subclass: #GeeMailMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeeMailMorph commentStamp: '' prior: 0! Well, what can I say? GeeMail is a scrolling playfield with a text morph (typically on the left) and room on the right for other morphs to be placed. The morphs on the right can be linked to text selections on the left so that they remain positioned beside the pertinent text as the text is reflowed. Probably the best thing is and example and Alan will be making some available soon.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GeeMailMorph class instanceVariableNames: ''! !GeeMailMorph class methodsFor: 'as yet unclassified' stamp: 'RAA 9/10/2000 12:52'! includeInNewMorphMenu ^ true! ! Object subclass: #GeePrinter instanceVariableNames: 'pasteUp printSpecs ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:33'! allPages | pageNumber allPages maxPages | maxPages _ 9999. pageNumber _ 0. allPages _ self pageRectangles collect: [ :rect | pageNumber _ pageNumber + 1. (self as: GeePrinterPage) pageNumber: pageNumber bounds: rect ]. allPages size > maxPages ifTrue: [allPages _ allPages first: maxPages]. allPages do: [ :each | each totalPages: allPages size]. ^allPages ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 09:46'! bounds | w | w _ pasteUp width. ^0@0 extent: w@(w * self hOverW) rounded.! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 12:51'! doPages | dialog | (dialog _ GeePrinterDialogMorph new) printSpecs: self printSpecs printBlock: [ :preview :specs | preview ifTrue: [self doPrintPreview] ifFalse: [self doPrintToPrinter] ]; fullBounds; position: Display extent - dialog extent // 2; openInWorld ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:41'! doPrintPreview | pageDisplay sz newPage subBounds pic align | sz _ (85 @ 110) * 3. self printSpecs landscapeFlag ifTrue: [ sz _ sz transposed ]. pageDisplay _ BookMorph new color: Color paleYellow; borderWidth: 1. self allPages withIndexDo: [ :each :index | pic _ ImageMorph new image: (each pageThumbnailOfSize: sz). align _ AlignmentMorph newColumn addMorph: pic; borderWidth: 1; layoutInset: 0; borderColor: Color blue. newPage _ pageDisplay insertPageLabel: 'Page ',index printString morphs: {align}. subBounds _ newPage boundingBoxOfSubmorphs. newPage extent: subBounds corner - newPage topLeft + ((subBounds left - newPage left)@0). ]. pageDisplay goToPage: 1; deletePageBasic; position: Display extent - pageDisplay extent // 2; openInWorld. ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 10/12/2000 09:29'! doPrintToPrinter "fileName _ ('gee.',Time millisecondClockValue printString,'.eps') asFileName." DSCPostscriptCanvasToDisk morphAsPostscript: self rotated: self printSpecs landscapeFlag ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/22/2000 13:58'! drawOn: aCanvas pasteUp drawOn: aCanvas ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 17:40'! fullBounds ^self bounds! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/22/2000 14:28'! fullDrawOn: aCanvas pasteUp fullDrawOn: aCanvas ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:33'! fullDrawPostscriptOn: aCanvas aCanvas drawPages: self allPages. ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 09:46'! hOverW ^self printSpecs landscapeFlag ifTrue: [ 8.5 / 11.0 ] ifFalse: [ 11.0 / 8.5 ]. ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 09:47'! pageRectangles | pageBounds allPageRects | pageBounds _ self bounds. allPageRects _ OrderedCollection new. [pageBounds top <= pasteUp bottom] whileTrue: [ allPageRects add: pageBounds. pageBounds _ pageBounds translateBy: 0 @ pageBounds height. ]. ^allPageRects ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 2/1/2001 17:41'! pagesHandledAutomatically ^true! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 17:35'! pasteUp: x pasteUp _ x.! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 09:33'! printSpecs ^printSpecs ifNil: [printSpecs _ PrintSpecifications defaultSpecs]. ! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 09:28'! printSpecs: aPrintSpecification printSpecs _ aPrintSpecification! ! !GeePrinter methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 17:40'! wantsRoundedCorners ^false! ! AlignmentMorphBob1 subclass: #GeePrinterDialogMorph instanceVariableNames: 'printSpecs printBlock ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:50'! buttonColor ^color darker! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:48'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString; color: aColor; actionSelector: aSymbol; setBalloonText: helpString. col _ (self inAColumn: {f}) hResizing: #shrinkWrap. ^col! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:52'! cancelButton ^self buttonNamed: 'Cancel' action: #doCancel color: self buttonColor help: 'Cancel this printing operation.'! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:24'! doCancel self delete! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:42'! doPreview self delete. printBlock value: true value: printSpecs.! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:42'! doPrint self delete. printBlock value: false value: printSpecs.! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/19/2000 17:44'! getChoice: aSymbol aSymbol == #landscapeFlag ifTrue: [^printSpecs landscapeFlag]. aSymbol == #drawAsBitmapFlag ifTrue: [^printSpecs drawAsBitmapFlag]. ! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 20:41'! initialize super initialize. self vResizing: #shrinkWrap. self hResizing: #shrinkWrap. color _ Color paleYellow. borderWidth _ 8. borderColor _ color darker. self layoutInset: 4. self useRoundedCorners. printSpecs ifNil: [printSpecs _ PrintSpecifications defaultSpecs]. self rebuild. ! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:51'! previewButton ^self buttonNamed: 'Preview' action: #doPreview color: self buttonColor help: 'Show a preview of the pages that will be printed on the screen.'! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 10:49'! printButton ^self buttonNamed: 'Print' action: #doPrint color: self buttonColor help: 'Print me (a PostScript file will be created)'! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 11:26'! printSpecs: aPrintSpecification printBlock: aTwoArgBlock printSpecs _ aPrintSpecification. printBlock _ aTwoArgBlock.! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/19/2000 17:43'! rebuild self removeAllMorphs. self addARow: { (StringMorph contents: 'PostScript Printing Options') lock. }. self addARow: { self simpleToggleButtonFor: self attribute: #landscapeFlag help: 'Print in landscape mode'. (StringMorph contents: ' Landscape') lock. }. self addARow: { self simpleToggleButtonFor: self attribute: #drawAsBitmapFlag help: 'Print as a bitmap'. (StringMorph contents: ' Bitmap') lock. }. self addARow: { self printButton. self previewButton. self cancelButton. }.! ! !GeePrinterDialogMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/19/2000 17:44'! toggleChoice: aSymbol aSymbol == #landscapeFlag ifTrue: [ printSpecs landscapeFlag: printSpecs landscapeFlag not ]. aSymbol == #drawAsBitmapFlag ifTrue: [ printSpecs drawAsBitmapFlag: printSpecs drawAsBitmapFlag not ]. ! ! GeePrinter subclass: #GeePrinterPage instanceVariableNames: 'pageNumber bounds totalPages ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-GeeMail'! !GeePrinterPage methodsFor: 'as yet unclassified' stamp: 'RAA 9/19/2000 17:41'! fullDrawPostscriptOn: aCanvas | s | s _ TextMorph new beAllFont: (TextStyle default fontOfSize: 30); contentsAsIs: ' Drawing page ',pageNumber printString,' of ',totalPages printString,' '. s layoutChanged; fullBounds. s _ AlignmentMorph newRow addMorph: s; color: Color yellow. s position: Display center - (s width // 2 @ 0). World addMorphFront: s. World displayWorld. printSpecs drawAsBitmapFlag ifTrue: [ aCanvas paintImage: self pageAsForm at: 0@0 ] ifFalse: [ aCanvas translateTo: bounds origin negated clippingTo: (0@0 extent: bounds extent) during: [ :c | pasteUp fullDrawForPrintingOn: c ]. ]. s delete. ! ! !GeePrinterPage methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 16:00'! pageAsForm | f canvas | f _ Form extent: bounds extent depth: 16. canvas _ f getCanvas. canvas fillColor: pasteUp color. canvas translateTo: bounds origin negated clippingTo: f boundingBox during: [ :c | pasteUp fullDrawForPrintingOn: c ]. ^f ! ! !GeePrinterPage methodsFor: 'as yet unclassified' stamp: 'RAA 9/16/2000 17:53'! pageNumber: anInteger bounds: aRect pageNumber _ anInteger. bounds _ aRect.! ! !GeePrinterPage methodsFor: 'as yet unclassified' stamp: 'RAA 9/18/2000 16:00'! pageThumbnailOfSize: aPoint ^self pageAsForm scaledToSize: aPoint ! ! !GeePrinterPage methodsFor: 'as yet unclassified' stamp: 'RAA 9/17/2000 16:51'! totalPages: x totalPages _ x! ! Url subclass: #GenericUrl instanceVariableNames: 'schemeName locator ' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !GenericUrl commentStamp: '' prior: 0! a URL type that can't be broken down in any systematic way. For example, mailto: and telnet: URLs. The part after the scheme name is stored available via the #locator message.! !GenericUrl methodsFor: 'parsing' stamp: 'ls 8/4/1998 01:28'! privateInitializeFromText: aString schemeName _ Url schemeNameForString: aString. schemeName ifNil: [ self error: 'opaque URL with no scheme--shouldn''t happen!!'. ]. locator _ aString copyFrom: (schemeName size+2) to: aString size.! ! !GenericUrl methodsFor: 'parsing' stamp: 'ls 8/4/1998 01:28'! privateInitializeFromText: aString relativeTo: aUrl schemeName _ aUrl schemeName. locator _ aString.! ! !GenericUrl methodsFor: 'access' stamp: 'ls 6/20/1998 19:46'! locator ^locator! ! !GenericUrl methodsFor: 'access' stamp: 'ls 6/20/1998 19:46'! schemeName ^schemeName! ! !GenericUrl methodsFor: 'private' stamp: 'ls 6/20/1998 19:46'! schemeName: schemeName0 locator: locator0 schemeName _ schemeName0. locator _ locator0.! ! !GenericUrl methodsFor: 'printing' stamp: 'ls 8/4/1998 02:41'! toText | s | s _ WriteStream on: String new. s nextPutAll: self schemeName. s nextPut: $:. s nextPutAll: self locator. self fragment ifNotNil: [ s nextPut: $#. s nextPutAll: self fragment ]. ^s contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GenericUrl class instanceVariableNames: ''! !GenericUrl class methodsFor: 'parsing' stamp: 'ls 7/26/1998 21:24'! absoluteFromText: aString | schemeName locator | schemeName _ Url schemeNameForString: aString. schemeName ifNil: [ ^self schemeName: 'xnoscheme' locator: aString ]. locator _ aString copyFrom: (schemeName size + 2) to: aString size. ^self schemeName: schemeName locator: locator! ! !GenericUrl class methodsFor: 'instance creation' stamp: 'ls 6/20/1998 19:46'! schemeName: schemeName locator: locator ^self new schemeName: schemeName locator: locator! ! VoiceEvent subclass: #GesturalEvent instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !GesturalEvent methodsFor: 'accessing' stamp: 'len 8/29/1999 02:18'! voice "Answer the default voice for the reciever." ^ Voice voices detect: [ :one | one class == GesturalVoice] ifNone: [super voice]! ! !GesturalEvent methodsFor: 'playing' stamp: 'len 9/6/1999 00:40'! actOn: aHeadMorph self subclassResponsibility! ! !GesturalEvent methodsFor: 'playing' stamp: 'len 8/28/1999 03:53'! playOn: aVoice at: time aVoice playGesturalEvent: self at: time! ! !GesturalEvent methodsFor: 'testing' stamp: 'len 8/29/1999 21:18'! isGestural ^ true! ! Voice subclass: #GesturalVoice instanceVariableNames: 'head ' classVariableNames: '' poolDictionaries: '' category: 'Speech-Gestures'! !GesturalVoice commentStamp: '' prior: 0! My instances are speaking voices with a head that acts in response to gestural events.! !GesturalVoice methodsFor: 'accessing' stamp: 'len 8/28/1999 23:07'! face ^ self head face! ! !GesturalVoice methodsFor: 'accessing' stamp: 'len 8/28/1999 23:06'! head ^ head! ! !GesturalVoice methodsFor: 'accessing' stamp: 'len 9/28/1999 02:16'! head: aHeadMorph head notNil ifTrue: [aHeadMorph position: head position. head delete]. head _ aHeadMorph! ! !GesturalVoice methodsFor: 'accessing' stamp: 'len 8/28/1999 23:06'! lips ^ self face lips! ! !GesturalVoice methodsFor: 'accessing' stamp: 'len 9/28/1999 02:16'! newHead | m | m _ HeadMorph new. self head: m. m openInWorld. ^ m! ! !GesturalVoice methodsFor: 'playing' stamp: 'len 9/7/1999 01:41'! playGesturalEvent: event at: time self head playEvent: event at: time! ! !GesturalVoice methodsFor: 'playing' stamp: 'len 9/6/1999 00:46'! playPhoneticEvent: event at: time (TalkGesturalEvent new phoneme: event phoneme) playOn: self at: time! ! RectangleMorph subclass: #GradientFillMorph instanceVariableNames: 'fillColor2 gradientDirection colorArray colorDepth ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Widgets'! !GradientFillMorph commentStamp: '' prior: 0! Class GradientFillMorph is obsolete. For getting gradient fills use a BorderedMorph with an appropriate fill style, e.g., | morph fs | morph _ BorderedMorph new. fs _ GradientFillStyle ramp: {0.0 -> Color red. 1.0 -> Color green}. fs origin: morph bounds center. fs direction: (morph bounds width // 2) @ 0. fs radial: true. morph fillStyle: fs. World primaryHand attachMorph: morph. Here's the old (obsolete) comment: GradientFills cache an array of bitpatterns for the colors across their rectangle. It costs a bit of space, but makes display fast enough to eschew the use of a bitmap. The array must be recomputed whenever the colors, dimensions or display depth change.! !GradientFillMorph methodsFor: 'initialize' stamp: 'di 11/13/97 15:20'! changed super changed. self releaseCachedState! ! !GradientFillMorph methodsFor: 'initialize' stamp: 'di 1/9/98 22:17'! initialize super initialize. borderWidth _ 0. fillColor2 _ Color black. gradientDirection _ #vertical! ! !GradientFillMorph methodsFor: 'drawing' stamp: 'ar 5/16/2000 20:49'! drawOn: aCanvas "Note that this could run about 4 times faster if we got hold of the canvas's port and just sent it copyBits with new coords and color" | style | super drawOn: aCanvas. (color isKindOf: Color) ifFalse: [^ self]. "An InfiniteForm, for example" (color = Color transparent) ifTrue: [^ self]. "Skip the gradient attempts, which will drop into debugger" color = fillColor2 ifTrue: [^ self]. "same color; no gradient" "Check if we can use the cached gradient fill" ((self valueOfProperty: #cachedGradientColor1) = color and:[(self valueOfProperty: #cachedGradientColor2) = fillColor2]) ifTrue:[style _ self valueOfProperty: #cachedGradientFill]. style ifNil:[ style _ GradientFillStyle ramp: {0.0 -> color. 1.0 -> fillColor2}. self setProperty: #cachedGradientColor1 toValue: color. self setProperty: #cachedGradientColor2 toValue: fillColor2. self setProperty: #cachedGradientFill toValue: style. ]. style origin: self position. style direction: (gradientDirection == #vertical ifTrue:[0@self height] ifFalse:[self width@0]). aCanvas fillRectangle: self innerBounds fillStyle: style.! ! !GradientFillMorph methodsFor: 'menu' stamp: 'jm 11/5/97 12:41'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'gradient color' action: #setGradientColor:. gradientDirection == #vertical ifTrue: [aCustomMenu add: 'horizontal pan' action: #beHorizontal] ifFalse: [aCustomMenu add: 'vertical pan' action: #beVertical]. ! ! !GradientFillMorph methodsFor: 'menu' stamp: 'di 11/2/97 14:35'! beHorizontal gradientDirection _ #horizontal. self changed! ! !GradientFillMorph methodsFor: 'menu' stamp: 'di 11/2/97 14:35'! beVertical gradientDirection _ #vertical. self changed! ! !GradientFillMorph methodsFor: 'menu' stamp: 'sw 4/2/98 00:12'! gradientDirection ^ gradientDirection! ! !GradientFillMorph methodsFor: 'as yet unclassified' stamp: 'jm 11/5/97 12:40'! gradientFillColor: aColor fillColor2 _ aColor. self changed. ! ! !GradientFillMorph methodsFor: 'as yet unclassified' stamp: 'di 1/3/1999 12:24'! hasTranslucentColor "Answer true if this any of this morph is translucent but not transparent." (color isColor and: [color isTranslucentColor]) ifTrue: [^ true]. (fillColor2 isColor and: [fillColor2 isTranslucentColor]) ifTrue: [^ true]. ^ false ! ! !GradientFillMorph methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 18:52'! setGradientColor: evt self changeColorTarget: self selector: #gradientFillColor: originalColor: fillColor2 hand: evt hand! ! OrientedFillStyle subclass: #GradientFillStyle instanceVariableNames: 'colorRamp pixelRamp radial isTranslucent ' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Fills'! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 11/7/1998 22:10'! colorRamp ^colorRamp! ! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 9/2/1999 14:30'! colorRamp: anArray colorRamp _ anArray. pixelRamp _ nil. isTranslucent _ nil.! ! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 9/2/1999 15:06'! pixelRamp ^pixelRamp ifNil:[pixelRamp _ self computePixelRampOfSize: 512].! ! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 11/9/1998 14:06'! pixelRamp: aBitmap pixelRamp _ aBitmap! ! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 11/9/1998 14:06'! radial ^radial! ! !GradientFillStyle methodsFor: 'accessing' stamp: 'ar 11/7/1998 22:11'! radial: aBoolean radial _ aBoolean! ! !GradientFillStyle methodsFor: 'testing' stamp: 'ar 11/7/1998 22:12'! isGradientFill ^true! ! !GradientFillStyle methodsFor: 'testing' stamp: 'ar 11/7/1998 22:13'! isRadialFill ^radial == true! ! !GradientFillStyle methodsFor: 'testing' stamp: 'ar 11/7/1998 22:12'! isSolidFill ^false! ! !GradientFillStyle methodsFor: 'testing' stamp: 'ar 9/2/1999 14:29'! isTranslucent ^isTranslucent ifNil:[isTranslucent _ self checkTranslucency]! ! !GradientFillStyle methodsFor: 'converting' stamp: 'ar 11/9/1998 14:07'! asColor ^colorRamp first value! ! !GradientFillStyle methodsFor: 'private' stamp: 'di 11/21/1999 20:18'! checkTranslucency ^colorRamp anySatisfy: [:any| any value isTranslucent]! ! !GradientFillStyle methodsFor: 'private' stamp: 'ar 7/16/2000 18:32'! computePixelRampOfSize: length "Compute the pixel ramp in the receiver" | bits lastColor lastIndex nextIndex nextColor distance theta lastValue ramp lastWord nextWord step | ramp _ colorRamp asSortedCollection:[:a1 :a2| a1 key < a2 key]. bits _ Bitmap new: length. lastColor _ ramp first value. lastWord _ lastColor pixelWordForDepth: 32. lastIndex _ 0. ramp do:[:assoc| nextIndex _ (assoc key * length) rounded. nextColor _ assoc value. nextWord _ nextColor pixelWordForDepth: 32. distance _ (nextIndex - lastIndex). distance = 0 ifTrue:[distance _ 1]. step _ 1.0 / distance asFloat. theta _ 0.0. lastIndex+1 to: nextIndex do:[:i| theta _ theta + step. "The following is an open-coded version of: color _ nextColor alphaMixed: theta with: lastColor. bits at: i put: (color scaledPixelValue32). " bits at: i put: (self scaledAlphaMix: theta of: lastWord with: nextWord). ]. lastIndex _ nextIndex. lastColor _ nextColor. lastWord _ nextWord. ]. lastValue _ lastColor scaledPixelValue32. lastIndex+1 to: length do:[:i| bits at: i put: lastValue]. ^bits! ! !GradientFillStyle methodsFor: 'private' stamp: 'ar 11/9/1998 16:56'! display | f ramp | ramp _ self pixelRamp. f _ Form extent: ramp size @ 1 depth: 32 bits: ramp. 1 to: 100 do:[:i| f displayAt: 1@i]. [Sensor anyButtonPressed] whileFalse. [Sensor anyButtonPressed] whileTrue.! ! !GradientFillStyle methodsFor: 'private' stamp: 'ar 7/11/2000 16:47'! scaledAlphaMix: theta of: lastWord with: nextWord "Open-coded version of alpha mixing two 32bit pixel words and returning the scaled pixel value." | word0 word1 a0 a1 alpha v0 v1 vv value | word0 _ lastWord. word1 _ nextWord. "note: extract alpha first so we'll be in SmallInteger range afterwards" a0 _ word0 bitShift: -24. a1 _ word1 bitShift: -24. alpha _ a0 + (a1 - a0 * theta) truncated. "Now make word0 and word1 SmallIntegers" word0 _ word0 bitAnd: 16rFFFFFF. word1 _ word1 bitAnd: 16rFFFFFF. "Compute first component value" v0 _ (word0 bitAnd: 255). v1 _ (word1 bitAnd: 255). vv _ (v0 + (v1 - v0 * theta) truncated) * alpha // 255. value _ vv. "Compute second component value" v0 _ ((word0 bitShift: -8) bitAnd: 255). v1 _ ((word1 bitShift: -8) bitAnd: 255). vv _ (v0 + (v1 - v0 * theta) truncated) * alpha // 255. value _ value bitOr: (vv bitShift: 8). "Compute third component value" v0 _ ((word0 bitShift: -16) bitAnd: 255). v1 _ ((word1 bitShift: -16) bitAnd: 255). vv _ (v0 + (v1 - v0 * theta) truncated) * alpha // 255. value _ value bitOr: (vv bitShift: 16). "Return result" ^value bitOr: (alpha bitShift: 24)! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 11:42'! addFillStyleMenuItems: aMenu hand: aHand from: aMorph "Add the items for changing the current fill style of the receiver" self isRadialFill ifTrue:[ aMenu add: 'linear gradient' target: self selector: #beLinearGradientIn: argument: aMorph. ] ifFalse:[ aMenu add: 'radial gradient' target: self selector: #beRadialGradientIn: argument: aMorph. ]. aMenu addLine. aMenu add: 'change first color' target: self selector: #changeFirstColorIn:event: argument: aMorph. aMenu add: 'change second color' target: self selector: #changeSecondColorIn:event: argument: aMorph. aMenu addLine. super addFillStyleMenuItems: aMenu hand: aHand from: aMorph.! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/18/1999 09:49'! addNewColorIn: aMorph event: evt ^self inform:'not yet implemented'! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/18/1999 07:25'! beLinearGradientIn: aMorph self radial: false. aMorph changed.! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/18/1999 07:25'! beRadialGradientIn: aMorph self radial: true. aMorph changed.! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'sw 9/8/2000 18:13'! changeColorSelector: aSymbol hand: aHand morph: aMorph originalColor: originalColor "Change either the firstColor or the lastColor (depending on aSymbol). Put up a color picker to hande it. We always use a modal picker so that the user can adjust both colors concurrently." ColorPickerMorph new initializeModal: false; sourceHand: aHand; target: self; selector: aSymbol; argument: aMorph; originalColor: originalColor; putUpFor: aMorph near: aMorph fullBoundsInWorld! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'di 9/3/1999 11:34'! changeFirstColorIn: aMorph event: evt ^self changeColorSelector: #firstColor:forMorph:hand: hand: evt hand morph: aMorph originalColor: colorRamp first value! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'di 9/3/1999 11:34'! changeSecondColorIn: aMorph event: evt ^self changeColorSelector: #lastColor:forMorph:hand: hand: evt hand morph: aMorph originalColor: colorRamp last value! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 11:39'! firstColor: aColor forMorph: aMorph hand: aHand colorRamp first value: aColor. pixelRamp _ nil. aMorph changed.! ! !GradientFillStyle methodsFor: 'Morphic menu' stamp: 'ar 6/25/1999 11:40'! lastColor: aColor forMorph: aMorph hand: aHand colorRamp last value: aColor. pixelRamp _ nil. aMorph changed.! ! !GradientFillStyle methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 08:37'! encodeForRemoteCanvas ^(DataStream streamedRepresentationOf: self) asString ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GradientFillStyle class instanceVariableNames: ''! !GradientFillStyle class methodsFor: 'instance creation' stamp: 'ar 11/9/1998 14:05'! ramp: colorRamp ^self new colorRamp: colorRamp! ! !GradientFillStyle class methodsFor: 'instance creation' stamp: 'ar 11/10/1998 19:13'! sample "GradientFill sample" ^(self ramp: { 0.0 -> Color red. 0.5 -> Color green. 1.0 -> Color blue}) origin: 300 @ 300; direction: 400@0; normal: 0@400; radial: true; yourself! ! BitBlt subclass: #GrafPort instanceVariableNames: 'alpha fillPattern ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !GrafPort methodsFor: 'accessing' stamp: 'ar 2/17/2000 01:07'! alphaBits: a alpha _ a! ! !GrafPort methodsFor: 'accessing' stamp: 'ar 5/28/2000 14:41'! contentsOfArea: aRectangle into: aForm destForm displayOn: aForm at: aRectangle origin clippingBox: (0@0 extent: aRectangle extent). ^aForm! ! !GrafPort methodsFor: 'accessing' stamp: 'ar 5/18/2000 18:34'! displayScannerFor: para foreground: foreColor background: backColor ignoreColorChanges: shadowMode ^ (DisplayScanner new text: para text textStyle: para textStyle foreground: foreColor background: backColor fillBlt: self ignoreColorChanges: shadowMode) setPort: self clone ! ! !GrafPort methodsFor: 'accessing' stamp: 'ar 2/17/2000 01:09'! fillPattern: anObject fillPattern _ anObject. self fillColor: anObject.! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/16/2000 22:32'! fillOval: rect | centerX centerY nextY yBias xBias outer nextOuterX | rect area <= 0 ifTrue: [^ self]. height _ 1. yBias _ rect height odd ifTrue: [0] ifFalse: [-1]. xBias _ rect width odd ifTrue: [1] ifFalse: [0]. centerX _ rect center x. centerY _ rect center y. outer _ EllipseMidpointTracer new on: rect. nextY _ rect height // 2. [nextY > 0] whileTrue:[ nextOuterX _ outer stepInY. width _ (nextOuterX bitShift: 1) + xBias. destX _ centerX - nextOuterX. destY _ centerY - nextY. self copyBits. destY _ centerY + nextY + yBias. self copyBits. nextY _ nextY - 1. ]. destY _ centerY. height _ 1 + yBias. width _ rect width. destX _ rect left. self copyBits. ! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 5/17/2000 21:20'! fillRect: rect offset: aPoint "The offset is really just for stupid InfiniteForms." | fc | fillPattern class == InfiniteForm ifTrue:[ fc _ halftoneForm. self fillColor: nil. fillPattern displayOnPort: ((self clippedBy: rect) colorMap: nil) at: aPoint. halftoneForm _ fc. ^self]. destX _ rect left. destY _ rect top. sourceX _ 0. sourceY _ 0. width _ rect width. height _ rect height. self copyBits.! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/16/2000 22:26'! frameOval: rect borderWidth: borderWidth | centerX centerY nextY yBias xBias wp outer inner nextOuterX nextInnerX fillAlpha | rect area <= 0 ifTrue: [^ self]. height _ 1. wp _ borderWidth asPoint. yBias _ rect height odd ifTrue: [0] ifFalse: [-1]. xBias _ rect width odd ifTrue: [1] ifFalse: [0]. centerX _ rect center x. centerY _ rect center y. outer _ EllipseMidpointTracer new on: rect. inner _ EllipseMidpointTracer new on: (rect insetBy: wp). nextY _ rect height // 2. 1 to: (wp y min: nextY) do:[:i| nextOuterX _ outer stepInY. width _ (nextOuterX bitShift: 1) + xBias. destX _ centerX - nextOuterX. destY _ centerY - nextY. self copyBits. destY _ centerY + nextY + yBias. self copyBits. nextY _ nextY - 1. ]. [nextY > 0] whileTrue:[ nextOuterX _ outer stepInY. nextInnerX _ inner stepInY. destX _ centerX - nextOuterX. destY _ centerY - nextY. width _ nextOuterX - nextInnerX. self copyBits. destX _ centerX + nextInnerX + xBias. self copyBits. destX _ centerX - nextOuterX. destY _ centerY + nextY + yBias. self copyBits. destX _ centerX + nextInnerX + xBias. self copyBits. nextY _ nextY - 1. ]. destY _ centerY. height _ 1 + yBias. width _ wp x. destX _ rect left. self copyBits. destX _ rect right - wp x. self copyBits. ! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 14:44'! frameRect: rect borderWidth: borderWidth sourceX _ 0. sourceY _ 0. (rect areasOutside: (rect insetBy: borderWidth)) do: [:edgeStrip | self destRect: edgeStrip; copyBits]. ! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 01:08'! frameRectBottom: rect height: h destX _ rect left + 1. destY _ rect bottom - 1. width _ rect width - 2. height _ 1. 1 to: h do: [:i | self copyBits. destX _ destX + 1. destY _ destY - 1. width _ width - 2]. ! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 01:08'! frameRectRight: rect width: w width _ 1. height _ rect height - 1. destX _ rect right - 1. destY _ rect top + 1. 1 to: w do: [:i | self copyBits. destX _ destX - 1. destY _ destY + 1. height _ height - 2]. ! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 01:08'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Draw the portion of the given Form defined by sourceRect at the given point using the given BitBlt combination rule." sourceForm _ aForm. combinationRule _ rule. self sourceRect: sourceRect. self destOrigin: aPoint. self copyBits! ! !GrafPort methodsFor: 'drawing support' stamp: 'ar 2/17/2000 00:31'! stencil: stencilForm at: aPoint sourceRect: aRect "Paint using aColor wherever stencilForm has non-zero pixels" self sourceForm: stencilForm; destOrigin: aPoint; sourceRect: aRect. self copyBits! ! !GrafPort methodsFor: 'copying' stamp: 'ar 2/17/2000 01:07'! clippedBy: aRectangle ^ self copy clipRect: (self clipRect intersect: aRectangle)! ! !GrafPort methodsFor: 'copying' stamp: 'ar 2/17/2000 01:07'! copyBits "Override copybits to do translucency if desired" (combinationRule >= 30 and: [combinationRule <= 31]) ifTrue: [alpha == nil ifTrue: [self copyBitsTranslucent: 255] ifFalse: [self copyBitsTranslucent: alpha]] ifFalse: [super copyBits]! ! RectangleMorph subclass: #GraphMorph instanceVariableNames: 'data dataColor cursor cursorColor cursorColorAtZeroCrossings startIndex minVal maxVal cachedForm hasChanged ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Widgets'! !GraphMorph commentStamp: '' prior: 0! I display a graph of numbers, normalized so the full range of values just fits my height. I support a movable cursor that can be dragged with the mouse. Implementation notes: Some operations on me may be done at sound sampling rates (e.g. 11-44 thousand times/second). To allow such high bandwidth application, certain operations that change my appearance do not immediately report a damage rectangle. Instead, a flag is set indicating that my display needs to refreshed and a step method reports the damage rectangle if that flag is set. Also, I cache a bitmap of my graph to allow the cursor to be moved without redrawing the graph. ! !GraphMorph methodsFor: 'initialization' stamp: 'jm 6/17/1999 20:09'! initialize super initialize. self color: (Color r: 0.8 g: 0.8 b: 0.6). self extent: 365@80. self borderWidth: 2. dataColor _ Color darkGray. cursor _ 1.0. "may be fractional" cursorColor _ Color red. cursorColorAtZeroCrossings _ Color red. startIndex _ 1. hasChanged _ false. self data: ((0 to: 360 - 1) collect: [:x | (100.0 * (x degreesToRadians sin)) asInteger]). ! ! !GraphMorph methodsFor: 'accessing'! color: aColor super color: aColor. self flushCachedForm. ! ! !GraphMorph methodsFor: 'accessing'! cursor ^ cursor ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 6/17/1999 21:41'! cursor: aNumber | truncP | cursor ~= aNumber ifTrue: [ cursor _ aNumber. truncP _ aNumber truncated. truncP > data size ifTrue: [cursor _ data size]. truncP < 0 ifTrue: [cursor _ 1]. self keepIndexInView: truncP. hasChanged _ true]. ! ! !GraphMorph methodsFor: 'accessing'! cursorAtEnd ^ cursor truncated >= data size ! ! !GraphMorph methodsFor: 'accessing'! cursorColor ^ cursorColor ! ! !GraphMorph methodsFor: 'accessing'! cursorColor: aColor cursorColor _ aColor. self flushCachedForm. ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 7/8/1998 20:32'! cursorColorAtZeroCrossing ^ cursorColorAtZeroCrossings ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 7/8/1998 20:32'! cursorColorAtZeroCrossings: aColor cursorColorAtZeroCrossings _ aColor. self flushCachedForm. ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 6/17/1999 21:43'! cursorWrapped: aNumber | sz | cursor ~= aNumber ifTrue: [ cursor _ aNumber. sz _ data size. sz = 0 ifTrue: [cursor _ 1] ifFalse: [ ((cursor >= (sz + 1)) or: [cursor < 0]) ifTrue: [ cursor _ cursor - ((cursor // sz) * sz)]. cursor < 1 ifTrue: [cursor _ sz + cursor]]. "assert: 1 <= cursor < data size + 1" hasChanged _ true]. ! ! !GraphMorph methodsFor: 'accessing'! data ^ data ! ! !GraphMorph methodsFor: 'accessing'! data: aCollection data _ aCollection. maxVal _ minVal _ 0. data do: [:x | x < minVal ifTrue: [minVal _ x]. x > maxVal ifTrue: [maxVal _ x]]. self flushCachedForm. ! ! !GraphMorph methodsFor: 'accessing'! dataColor ^ dataColor ! ! !GraphMorph methodsFor: 'accessing'! dataColor: aColor dataColor _ aColor. self flushCachedForm. ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 6/16/1999 13:49'! interpolatedValueAtCursor | sz prev frac next | data isEmpty ifTrue: [^ 0]. sz _ data size. cursor < 0 ifTrue: [^ data at: 1]. "just to be safe, though cursor shouldn't be negative" prev _ cursor truncated. frac _ cursor - prev. prev < 1 ifTrue: [prev _ sz]. prev > sz ifTrue: [prev _ 1]. "assert: 1 <= prev <= sz" frac = 0 ifTrue: [^ data at: prev]. "no interpolation needed" "interpolate" next _ prev = sz ifTrue: [1] ifFalse: [prev + 1]. ^ ((1.0 - frac) * (data at: prev)) + (frac * (data at: next)) ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 4/21/1999 11:24'! lastValue data size = 0 ifTrue: [^ 0]. ^ data last ! ! !GraphMorph methodsFor: 'accessing' stamp: 'jm 4/21/1999 11:25'! lastValue: aNumber self appendValue: aNumber. ! ! !GraphMorph methodsFor: 'accessing'! startIndex ^ startIndex ! ! !GraphMorph methodsFor: 'accessing'! startIndex: aNumber startIndex ~= aNumber ifTrue: [ startIndex _ aNumber asInteger. self flushCachedForm]. ! ! !GraphMorph methodsFor: 'accessing'! valueAtCursor data isEmpty ifTrue: [^ 0]. ^ data at: ((cursor truncated max: 1) min: data size) ! ! !GraphMorph methodsFor: 'accessing'! valueAtCursor: aPointOrNumber data isEmpty ifTrue: [^ 0]. data at: ((cursor truncated max: 1) min: data size) put: (self asNumber: aPointOrNumber). self flushCachedForm. ! ! !GraphMorph methodsFor: 'drawing' stamp: 'ar 5/25/2000 18:04'! drawOn: aCanvas | c | cachedForm = nil ifTrue: [ c _ Display defaultCanvasClass extent: bounds extent. c translateBy: bounds origin negated during:[:tempCanvas| self drawDataOn: tempCanvas]. cachedForm _ c form]. aCanvas cache: bounds using: cachedForm during:[:cachingCanvas| self drawDataOn: cachingCanvas]. self drawCursorOn: aCanvas. ! ! !GraphMorph methodsFor: 'change reporting'! layoutChanged super layoutChanged. cachedForm _ nil. ! ! !GraphMorph methodsFor: 'events' stamp: 'jm 10/18/97 11:32'! handlesMouseDown: evt evt shiftPressed ifTrue: [^ super handlesMouseDown: evt] ifFalse: [^ true]. ! ! !GraphMorph methodsFor: 'events' stamp: 'jm 10/18/97 11:52'! mouseMove: evt | x w | x _ evt cursorPoint x - (bounds left + borderWidth). w _ self width - (2 * borderWidth). self changed. x < 0 ifTrue: [ cursor _ startIndex + (3 * x). cursor _ (cursor max: 1) min: data size. ^ self startIndex: cursor]. x > w ifTrue: [ cursor _ startIndex + w + (3 * (x - w)). cursor _ (cursor max: 1) min: data size. ^ self startIndex: cursor - w]. cursor _ ((startIndex + x) max: 1) min: data size. ! ! !GraphMorph methodsFor: 'stepping' stamp: 'jm 6/17/1999 21:32'! step "Make a deferred damage rectangle if I've changed. This allows applications to call methods that invalidate my display at high-bandwidth without paying the cost of doing the damage reporting on ever call; they can merely set hasChanged to true." super step. hasChanged == nil ifTrue: [hasChanged _ false]. hasChanged ifTrue: [ self changed. hasChanged _ false]. ! ! !GraphMorph methodsFor: 'menu' stamp: 'jm 6/16/1999 13:08'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'open wave editor' action: #openWaveEditor. aCustomMenu add: 'read file' action: #readDataFromFile. ! ! !GraphMorph methodsFor: 'menu' stamp: 'di 10/11/1999 08:38'! openWaveEditor | scaleFactor scaledData editor | self data: data. "make sure maxVal and minVal are current" scaleFactor _ 32767 // ((minVal abs max: maxVal abs) max: 1). scaledData _ SoundBuffer newMonoSampleCount: data size. 1 to: data size do: [:i | scaledData at: i put: (scaleFactor * (data at: i)) truncated]. editor _ WaveEditor new data: scaledData; samplingRate: 11025; perceivedFrequency: 220.0. editor openInWorld. ! ! !GraphMorph methodsFor: 'menu' stamp: 'jm 6/16/1999 11:24'! readDataFromFile | fileName | fileName _ FillInTheBlank request: 'File name?' initialAnswer: ''. fileName isEmpty ifTrue: [^ self]. (StandardFileStream isAFileNamed: fileName) ifFalse: [ ^ self inform: 'Sorry, I cannot find that file']. self data: (SampledSound fromAIFFfileNamed: fileName) samples. ! ! !GraphMorph methodsFor: 'commands'! appendValue: aPointOrNumber | newVal | (data isKindOf: OrderedCollection) ifFalse: [data _ data asOrderedCollection]. newVal _ self asNumber: aPointOrNumber. data addLast: newVal. newVal < minVal ifTrue: [minVal _ newVal]. newVal > maxVal ifTrue: [maxVal _ newVal]. self cursor: data size. self flushCachedForm. ! ! !GraphMorph methodsFor: 'commands' stamp: 'jm 7/30/1998 12:17'! centerCursor "Scroll so that the cursor is as close as possible to the center of my window." | w | w _ self width - (2 * borderWidth). self startIndex: ((cursor - (w // 2)) max: 1). ! ! !GraphMorph methodsFor: 'commands'! clear self startIndex: 1. self cursor: 1. self data: OrderedCollection new. ! ! !GraphMorph methodsFor: 'commands' stamp: 'jm 4/22/1999 14:49'! loadSineWave self loadSoundData: FMSound sineTable. ! ! !GraphMorph methodsFor: 'commands' stamp: 'jm 4/22/1999 14:17'! loadSound: aSound self loadSoundData: aSound samples. ! ! !GraphMorph methodsFor: 'commands' stamp: 'jm 4/22/1999 14:23'! loadSoundData: aCollection | scale absV newData | scale _ 0. aCollection do: [:v | (absV _ v abs) > scale ifTrue: [scale _ absV]]. scale _ 100.0 / scale. newData _ OrderedCollection new: aCollection size. 1 to: aCollection size do: [:i | newData addLast: (scale * (aCollection at: i))]. self data: newData. self startIndex: 1. self cursor: 1. ! ! !GraphMorph methodsFor: 'commands' stamp: 'jm 6/16/1999 11:29'! playOnce | scale absV scaledData | data isEmpty ifTrue: [^ self]. "nothing to play" scale _ 1. data do: [:v | (absV _ v abs) > scale ifTrue: [scale _ absV]]. scale _ 32767.0 / scale. scaledData _ SoundBuffer newMonoSampleCount: data size. 1 to: data size do: [:i | scaledData at: i put: (scale * (data at: i)) truncated]. (SampledSound samples: scaledData samplingRate: 11025) play. ! ! !GraphMorph methodsFor: 'commands'! reverse data _ data reversed. self flushCachedForm. ! ! !GraphMorph methodsFor: 'private' stamp: 'jm 6/17/1999 21:36'! drawCursorOn: aCanvas | ptr x r c | ptr _ (cursor asInteger max: 1) min: data size. c _ cursorColor. ((ptr > 1) and: [ptr < data size]) ifTrue: [ (data at: ptr) sign ~= (data at: ptr + 1) sign ifTrue: [c _ cursorColorAtZeroCrossings]]. r _ self innerBounds. x _ r left + ptr - startIndex. ((x >= r left) and: [x <= r right]) ifTrue: [ aCanvas fillRectangle: (x@r top corner: x + 1@r bottom) color: c]. ! ! !GraphMorph methodsFor: 'private'! drawDataOn: aCanvas | yScale baseLine x start end value left top bottom right | super drawOn: aCanvas. data isEmpty ifTrue: [^ self]. maxVal = minVal ifTrue: [ yScale _ 1. ] ifFalse: [ yScale _ (bounds height - (2 * borderWidth)) asFloat / (maxVal - minVal)]. baseLine _ bounds bottom - borderWidth + (minVal * yScale) truncated. left _ top _ 0. right _ 10. bottom _ 0. x _ bounds left + borderWidth. start _ (startIndex asInteger max: 1) min: data size. end _ (start + bounds width) min: data size. start to: end do: [:i | left _ x truncated. right _ x + 1. right > (bounds right - borderWidth) ifTrue: [^ self]. value _ (data at: i) asFloat. value >= 0.0 ifTrue: [ top _ baseLine - (yScale * value) truncated. bottom _ baseLine. ] ifFalse: [ top _ baseLine. bottom _ baseLine - (yScale * value) truncated]. aCanvas fillRectangle: (left@top corner: right@bottom) color: dataColor. x _ x + 1]. ! ! !GraphMorph methodsFor: 'private' stamp: 'jm 6/17/1999 20:10'! flushCachedForm cachedForm _ nil. hasChanged _ true. ! ! !GraphMorph methodsFor: 'private' stamp: 'jm 4/21/1999 11:30'! keepIndexInView: index | w newStart | w _ bounds width - (2 * borderWidth). index < startIndex ifTrue: [ newStart _ index - w + 1. ^ self startIndex: (newStart max: 1)]. index > (startIndex + w) ifTrue: [ ^ self startIndex: (index min: data size)]. ! ! !GraphMorph methodsFor: 'object fileIn' stamp: 'RAA 12/20/2000 17:46'! convertToCurrentVersion: varDict refStream: smartRefStrm hasChanged ifNil: [hasChanged _ false]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GraphMorph class instanceVariableNames: ''! !GraphMorph class methodsFor: 'scripting' stamp: 'sw 9/17/2000 12:34'! additionsToViewerCategories "Answer a list of ( ) pairs that characterize the phrases this kind of morph wishes to add to various Viewer categories." ^ #( (basic ( (slot cursor 'The current cursor location, wrapped back to the beginning if appropriate' number readWrite player getCursor player setCursorWrapped:) (slot sampleAtCursor 'The sample value at the current cursor location' number readWrite player getSampleAtCursor player setSampleAtCursor:))) (sampling ( (slot cursor 'The current cursor location, wrapped back to the beginning if appropriate' number readWrite player getCursor player setCursorWrapped:) (slot sampleAtCursor 'The sample value at the current cursor location' number readWrite player getSampleAtCursor player setSampleAtCursor:) (slot lastValue 'The last value obtained' number readWrite player getLastValue player setLastValue:) (command clear 'Clear the graph of current contents') (command loadSineWave 'Load a sine wave as the current graph') (command loadSound: 'Load the specified sound into the current graph' sound) (command reverse 'Reverse the graph') (command play 'Play the current graph as a sound'))))! ! OrderedCollection variableSubclass: #GraphicSymbol instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-Symbols'! !GraphicSymbol commentStamp: '' prior: 0! I represent a structured picture built from primitive display objects and other instances of me.! !GraphicSymbol methodsFor: 'displaying'! displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm "Display the receiver on the Display where aTransformation is provided as an argument, rule is anInteger and mask is aForm. No translation. Information to be displayed must be confined to the area that intersects with clipRect." self do: [:element | element displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm]! ! !GraphicSymbol methodsFor: 'displaying'! displayTransformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm "Display the receiver where aTransformation is provided as an argument, rule is anInteger and mask is aForm. No translation. Information to be displayed must be confined to the area that intersects with clipRect." self displayOn: Display transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm! ! !GraphicSymbol methodsFor: 'accessing' stamp: 'jrm 2/6/2000 11:01'! computeBoundingBox "Compute a boundingBox that encloses all of the Paths in this symbol" ^Rectangle merging: (self collect: [:each | each computeBoundingBox]) ! ! Object subclass: #GraphicSymbolInstance instanceVariableNames: 'transformation graphicSymbol ' classVariableNames: '' poolDictionaries: '' category: 'ST80-Symbols'! !GraphicSymbolInstance commentStamp: '' prior: 0! I represent a display transformation of a GraphicSymbol. Multiple copies of a GraphicSymbol can be displayed at different positions and scales on the screen by making appropriate, multiple, instances of me.! !GraphicSymbolInstance methodsFor: 'accessing'! graphicSymbol "Answer the graphic symbol that the receiver displays." ^graphicSymbol! ! !GraphicSymbolInstance methodsFor: 'accessing'! graphicSymbol: aGraphicSymbol "Set the argument, aGraphicSymbol, to be the graphic symbol that the receiver displays." graphicSymbol _ aGraphicSymbol! ! !GraphicSymbolInstance methodsFor: 'transforming'! transformation "Answer the receiver's display transformation." ^transformation! ! !GraphicSymbolInstance methodsFor: 'transforming'! transformation: aWindowingTransformation "Set the argument, aWindowingTransformation, to be the receiver's display transformation." transformation _ aWindowingTransformation! ! !GraphicSymbolInstance methodsFor: 'displaying' stamp: 'jrm 2/13/2000 10:02'! displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm "Display the graphic symbol on the Display according to the arguments of this message." graphicSymbol displayOn: aDisplayMedium transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm! ! !GraphicSymbolInstance methodsFor: 'displaying'! displayTransformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm "Display the graphic symbol according to the arguments of this message." self displayOn: Display transformation: aTransformation clippingBox: clipRect rule: anInteger fillColor: aForm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GraphicSymbolInstance class instanceVariableNames: ''! !GraphicSymbolInstance class methodsFor: 'examples'! example "Simply evaluate the method and two GraphicSymbolInstances, each displaying a transformation of the same graphic symbol, will be presented on the screen. Clears the screen to white." | gate instance1 instance2 trans1 trans2 line arc f| Display fillWhite. "clear the Screen." f _ Form extent: 2@2. f fillBlack. gate_ GraphicSymbol new. "make a logic gate out of lines and arcs." line_Line new. line beginPoint: -20@-20. line endPoint: 0@-20. line form: f. gate add: line. line_Line new. line beginPoint: -20@20. line endPoint: 0@20. line form: f. gate add: line. line_Line new. line beginPoint: 0@-40. line endPoint: 0@40. line form: f. gate add: line. arc _ Arc new. arc center: 0@0 radius: 40 quadrant: 1. arc form: f. gate add: arc. arc _ Arc new. arc center: 0@0 radius: 40 quadrant: 4. arc form: f. gate add: arc. "one instance at 1/2 scale." trans1_WindowingTransformation identity. trans1_ trans1 scaleBy: 0.5@0.5. trans1_ trans1 translateBy: 100@100. "the other instance at 2 times scale" trans2_WindowingTransformation identity. trans2_ trans2 scaleBy: 2.0@2.0. trans2_ trans2 translateBy: 200@200. instance1 _ GraphicSymbolInstance new. instance1 transformation: trans1. instance1 graphicSymbol: gate. instance2 _ GraphicSymbolInstance new. instance2 transformation: trans2. instance2 graphicSymbol: gate. "display both instances of the logic gate" instance1 displayOn: Display transformation: WindowingTransformation identity clippingBox: Display boundingBox rule: Form under fillColor: nil. instance2 displayOn: Display transformation: WindowingTransformation identity clippingBox: Display boundingBox rule: Form under fillColor: nil "GraphicSymbolInstance example"! ! GraphicalMenu subclass: #GraphicalDictionaryMenu instanceVariableNames: 'baseDictionary entryNames ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Menus'! !GraphicalDictionaryMenu commentStamp: '' prior: 0! A morph that allows you to view, rename, and remove elements from a dictionary whose keys are strings and whose values are forms.! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/24/1998 11:59'! baseDictionary: aDictionary baseDictionary _ aDictionary. entryNames _ aDictionary keys asSortedArray. formChoices _ entryNames collect: [:n | aDictionary at: n]. currentIndex _ 1! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 10/27/1999 21:42'! findEntry | toFind searchIndex | toFind _ FillInTheBlank request: 'Type name or fragment: ' initialAnswer: 'Controls'. toFind isEmptyOrNil ifTrue: [^ self]. searchIndex _ currentIndex + 1. toFind _ '*', toFind asLowercase, '*'. [toFind match: (entryNames at: searchIndex) asString] whileFalse: [searchIndex _ (searchIndex \\ entryNames size) + 1. searchIndex == currentIndex ifTrue: [^ self inform: 'not found']]. currentIndex _ searchIndex. self updateThumbnail! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 5/26/2000 07:01'! handMeOne self currentHand attachMorph: (SketchMorph new form: (formChoices at: currentIndex))! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:18'! initializeFor: aTarget fromDictionary: aDictionary | imageWrapper anIndex aButton controlsWrapper asm | self listDirection: #topToBottom. self addMorphBack: (controlsWrapper _ AlignmentMorph newRow). self baseDictionary: aDictionary. target _ aTarget. coexistWithOriginal _ true. color _ Color white. borderColor _ Color blue darker. borderWidth _ 1. self hResizing: #shrinkWrap; vResizing: #shrinkWrap. controlsWrapper borderWidth: 0; layoutInset: 0; hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. controlsWrapper wrapCentering: #topLeft; color: Color white; vResizing: #spaceFill. controlsWrapper addTransparentSpacerOfSize: (18@0). controlsWrapper addMorphBack: (IconicButton new borderWidth: 0; labelGraphic: (ScriptingSystem formAtKey: 'Menu'); color: Color transparent; actWhen: #buttonDown; actionSelector: #showMenu; target: self; setBalloonText: 'menu'). controlsWrapper addTransparentSpacerOfSize: (14@0). aButton _ SimpleButtonMorph new target: self; borderColor: Color black. controlsWrapper addMorphBack: (aButton fullCopy label: 'Prev'; actionSelector: #downArrowHit; actWhen: #whilePressed; setBalloonText: 'show previous picture'; yourself). controlsWrapper addTransparentSpacerOfSize: (15@0). controlsWrapper addMorphBack: (aButton fullCopy label: 'Next'; actionSelector: #upArrowHit; actWhen: #whilePressed; setBalloonText: 'show next pictutre'). self addMorphBack: controlsWrapper. self addTransparentSpacerOfSize: (0 @ 12). self addMorphBack: (asm _ UpdatingStringMorph new contents: ' '; target: self; putSelector: #renameGraphicTo:; getSelector: #truncatedNameOfGraphic; useStringFormat). asm setBalloonText: 'The name of the current graphic'. self addTransparentSpacerOfSize: (0 @ 12). self addMorphBack: (AlignmentMorph newRow height: 4; borderWidth: 0; color: Color black). imageWrapper _ Morph new color: Color transparent; extent: 190 @ 82. imageWrapper addMorphBack: (formDisplayMorph _ ImageMorph new extent: 100 @ 100). self addMorphBack: imageWrapper. target ifNotNil: [(anIndex _ formChoices indexOf: target form ifAbsent: [nil]) ifNotNil: [currentIndex _ anIndex]]. self updateThumbnail! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/24/1998 11:25'! nameOfGraphic ^ entryNames at: currentIndex! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/24/1998 12:15'! removeEntry baseDictionary removeKey: (entryNames at: currentIndex). self baseDictionary: baseDictionary. self updateThumbnail! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sma 6/18/2000 10:36'! renameEntry | reply curr | reply _ FillInTheBlank request: 'New key? ' initialAnswer: (curr _ entryNames at: currentIndex) centerAt: self center. (reply isEmptyOrNil or: [reply = curr]) ifTrue: [^ self beep]. (baseDictionary includesKey: reply) ifTrue: [^ self inform: 'sorry that conflicts with the name of another entry in this dictionary']. baseDictionary at: reply put: (baseDictionary at: curr). baseDictionary removeKey: curr. self baseDictionary: baseDictionary. self updateThumbnail! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 4/5/1999 12:34'! renameGraphicTo: newName | curr | curr _ entryNames at: currentIndex. (newName isEmptyOrNil or: [newName = curr]) ifTrue: [^ self beep]. (baseDictionary includesKey: newName) ifTrue: [^ self inform: 'sorry that conflicts with the name of another entry in this dictionary']. baseDictionary at: newName put: (baseDictionary at: curr). baseDictionary removeKey: curr. self baseDictionary: baseDictionary. currentIndex _ entryNames indexOf: newName. self updateThumbnail! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'di 2/17/2000 20:29'! repaintEntry "Let the user enter into painting mode to repaint the item and save it back." | aWorld bnds sketchEditor aPaintBox formToEdit | (aWorld _ self world) assureNotPaintingElse: [^ self]. aWorld stopRunningAll; abandonAllHalos. aWorld displayWorld. formToEdit _ formChoices at: currentIndex. bnds _ (submorphs second boundsInWorld origin extent: formToEdit extent) intersect: aWorld bounds. bnds _ (aWorld paintingBoundsAround: bnds center) merge: bnds. sketchEditor _ SketchEditorMorph new. aWorld addMorphFront: sketchEditor. sketchEditor initializeFor: ((SketchMorph withForm: formToEdit) position: submorphs second positionInWorld) inBounds: bnds pasteUpMorph: aWorld paintBoxPosition: bnds topRight. sketchEditor afterNewPicDo: [:aForm :aRect | formChoices at: currentIndex put: aForm. baseDictionary at: (entryNames at: currentIndex) put: aForm. self updateThumbnail. (aPaintBox _ aWorld paintBoxOrNil) ifNotNil: [aPaintBox delete]] ifNoBits: [(aPaintBox _ aWorld paintBoxOrNil) ifNotNil: [aPaintBox delete]]. ! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'ar 10/5/2000 19:31'! showMenu | aMenu | aMenu _ MenuMorph new defaultTarget: self. aMenu addList: #( ('remove' removeEntry) ('rename' renameEntry) ('repaint' repaintEntry) - ('find...' findEntry) - ('hand me one' handMeOne)). aMenu popUpInWorld: self currentWorld. ! ! !GraphicalDictionaryMenu methodsFor: 'as yet unclassified' stamp: 'sw 5/28/2000 23:19'! truncatedNameOfGraphic ^ self nameOfGraphic truncateTo: 30! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! GraphicalDictionaryMenu class instanceVariableNames: ''! !GraphicalDictionaryMenu class methodsFor: 'example' stamp: 'sw 12/24/1998 12:11'! example "GraphicalDictionaryMenu example" | aDict | aDict _ Dictionary new. #('ColorTilesOff' 'ColorTilesOn' 'Controls') do: [:aString | aDict at: aString put: (ScriptingSystem formAtKey: aString)]. aDict inspectFormsWithLabel: 'Testing One Two Three'! ! AlignmentMorph subclass: #GraphicalMenu instanceVariableNames: 'target selector argument currentIndex formChoices formDisplayMorph coexistWithOriginal ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Menus'! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:54'! argument ^argument! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:54'! argument: anObject argument _ anObject! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/4/1998 23:49'! cancel coexistWithOriginal ifTrue: [self delete] ifFalse: [owner replaceSubmorph: self topRendererOrSelf by: target]! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/1/1998 17:56'! downArrowHit currentIndex _ currentIndex - 1. (currentIndex < 1) ifTrue: [currentIndex _ formChoices size]. self updateThumbnail ! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:53'! initialize super initialize. selector _ #newForm:.! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 11/9/2000 21:18'! initializeFor: aTarget withForms: formList coexist: aBoolean " World primaryHand attachMorph: (GraphicalMenu new initializeFor: nil withForms: Form allInstances coexist: true) " | buttons b anIndex buttonCage imageWrapper | target _ aTarget. coexistWithOriginal _ aBoolean. color _ Color white. borderColor _ Color blue darker. borderWidth _ 1. formChoices _ formList. currentIndex _ 1. self hResizing: #shrinkWrap; vResizing: #shrinkWrap. b _ SimpleButtonMorph new target: self; borderColor: Color black. buttons _ AlignmentMorph newRow. buttons borderWidth: 0; layoutInset: 0. buttons hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. buttons wrapCentering: #topLeft. buttonCage _ AlignmentMorph newColumn. buttonCage hResizing: #shrinkWrap; vResizing: #spaceFill. buttonCage addTransparentSpacerOfSize: (0 @ 10). buttons addMorphBack: (b fullCopy label: 'Prev'; actionSelector: #downArrowHit; actWhen: #whilePressed). buttons addTransparentSpacerOfSize: (9@0). buttons addMorphBack: (b fullCopy label: 'Next'; actionSelector: #upArrowHit; actWhen: #whilePressed). buttons addTransparentSpacerOfSize: (5@0). buttons submorphs last color: Color white. buttonCage addMorphBack: buttons. buttonCage addTransparentSpacerOfSize: (0 @ 12). buttons _ AlignmentMorph newRow. buttons addMorphBack: (b fullCopy label: 'OK'; actionSelector: #okay). buttons addTransparentSpacerOfSize: (5@0). buttons addMorphBack: (b fullCopy label: 'Cancel'; actionSelector: #cancel). buttonCage addMorphBack: buttons. buttonCage addTransparentSpacerOfSize: (0 @ 10). self addMorphFront: buttonCage. imageWrapper _ Morph new color: Color transparent; extent: 102 @ 82. imageWrapper addMorphBack: (formDisplayMorph _ ImageMorph new extent: 100 @ 100). self addMorphBack: imageWrapper. target ifNotNil: [(anIndex _ formList indexOf: target form ifAbsent: [nil]) ifNotNil: [currentIndex _ anIndex]]. self updateThumbnail! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:54'! okay | nArgs | target ifNotNil:[ nArgs _ selector numArgs. nArgs = 1 ifTrue:[target perform: selector with: (formChoices at: currentIndex)]. nArgs = 2 ifTrue:[target perform: selector with: (formChoices at: currentIndex) with: argument]]. coexistWithOriginal ifTrue: [self delete] ifFalse: [owner replaceSubmorph: self topRendererOrSelf by: target]! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:54'! selector ^selector! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'ar 6/25/1999 11:54'! selector: aSymbol selector _ aSymbol! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/1/1998 17:54'! upArrowHit currentIndex _ currentIndex + 1. (currentIndex > formChoices size) ifTrue: [currentIndex _ 1]. self updateThumbnail ! ! !GraphicalMenu methodsFor: 'as yet unclassified' stamp: 'sw 12/2/1998 22:05'! updateThumbnail | f scaleY scaleX maxWidth stdHeight | maxWidth _ 100. stdHeight _ 80. f _ formChoices at: currentIndex. scaleY _ stdHeight / f height. "keep height invariant" scaleY _ scaleY min: 1. scaleX _ ((f width * scaleY) <= maxWidth) ifTrue: [scaleY] ifFalse: [maxWidth / f width]. formDisplayMorph image: (f magnify: f boundingBox by: (scaleX @ scaleY) smoothing: 2). formDisplayMorph layoutChanged! ! Object subclass: #HTMLformatter instanceVariableNames: 'formattingBlock specialCharacter ' classVariableNames: '' poolDictionaries: '' category: 'Network-Pluggable Web Server'! !HTMLformatter commentStamp: '' prior: 0! HTMLformatter class (instances don't know anything) knows alot about HTML formatting: Creating forms, pages, different widgets, etc. It also knows how to process Smalltalk embedded within an HTML document. ! !HTMLformatter methodsFor: 'formatting' stamp: 'ls 4/18/98 16:46'! format: requestObject "format text with requestObject as the argument for the code blocks" ^String streamContents: [ :stream | formattingBlock value: requestObject value: stream ]! ! !HTMLformatter methodsFor: 'private-initialization' stamp: 'ls 4/18/98 13:23'! formattingBlock: aBlock formattingBlock _ aBlock! ! !HTMLformatter methodsFor: 'private-initialization' stamp: 'mjg 8/28/1998 20:56'! initialize specialCharacter = $*.! ! !HTMLformatter methodsFor: 'translating' stamp: 'mjg 8/28/1998 21:03'! rangesOfAngleBrackets: sourceStrm "Return an OrderedCollection of intervals of position within angle brackets < and >. Caller wants to avoid putting
    in there." | list char intervals start | list _ OrderedCollection new: 10. [sourceStrm atEnd] whileFalse: [ (char _ sourceStrm next) == $< ifTrue: [list add: sourceStrm position]. "a start" char == $> ifTrue: [list add: sourceStrm position negated]]. "an end" sourceStrm reset. intervals _ OrderedCollection new: 10. start _ nil. list do: [:each | (each > 0) & (start == nil) ifTrue: [start _ each]. (each < 0) & (start ~~ nil) ifTrue: [ intervals add: (start to: each negated). start _ nil]]. ^ intervals " HTMLformatter rangesOfAngleBrackets: (ReadStream on: '1234 <456 567> ') "! ! !HTMLformatter methodsFor: 'translating' stamp: 'tk 9/24/1998 08:08'! swikify: aStringOrStream linkhandler: aBlock | sourceStream aLine targetStream start end forbidden ignore | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream := aStringOrStream] ifFalse: [sourceStream := ReadStream on: aStringOrStream]. forbidden _ self rangesOfAngleBrackets: sourceStream. targetStream := WriteStream on: String new. [sourceStream atEnd] whileFalse: [aLine := sourceStream upTo: (Character cr). " Now, look for links " start _ 1. [(start _ aLine indexOfSubCollection: (specialCharacter asString) startingAt: start ifAbsent: [0]) ~= 0 and: [start < aLine size]] whileTrue: [(aLine at: start+1) = specialCharacter ifTrue: [aLine _ aLine copyReplaceFrom: start to: start+1 with: specialCharacter asString. start_start + 1. ] ifFalse: [ (end _ aLine indexOfSubCollection: (specialCharacter asString) startingAt: (start+1) ifAbsent: [0]) ~= 0 ifTrue: [aLine _ aLine copyReplaceFrom: start to: end with: (aBlock value: (aLine copyFrom: start+1 to: end-1))] ifFalse: [start _ start + 1]]]. "If it's at least 4 dashes, make it a horizontal rule" (aLine indexOfSubCollection: '----' startingAt: 1) = 1 ifTrue: [targetStream nextPutAll: '
    '] ifFalse: [targetStream nextPutAll: aLine]. "Should there be a
    after this line?" (ignore _ sourceStream peek = $<) ifTrue: [ "If just before a tag, ignore the newline" targetStream nextPut: $ ]. "but do put in a separator" forbidden do: [:interval | (interval includes: sourceStream position) ifTrue: [ignore _ true]]. ignore ifFalse: [ (sourceStream peek) = (Character cr) ifTrue: [sourceStream next. targetStream nextPutAll: '

    '; cr.] ifFalse: [targetStream nextPutAll: '
    '; cr.]]]. ^targetStream contents. ! ! !HTMLformatter methodsFor: 'accessing' stamp: 'mjg 8/28/1998 21:00'! specialCharacter ^specialCharacter! ! !HTMLformatter methodsFor: 'accessing' stamp: 'mjg 8/28/1998 21:01'! specialCharacter: someCharacter specialCharacter _ someCharacter! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! HTMLformatter class instanceVariableNames: ''! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 16:00'! endForm ^ '

    ' ! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:58'! endPage | stream | stream _ WriteStream on: ''. stream nextPutAll: '';cr; nextPutAll: '';cr. ^stream contents! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:59'! startForm: action | stream | stream _ WriteStream on: ''. stream nextPutAll: '
    '; cr. ^ stream contents! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:57'! startPage: title | stream | stream _ WriteStream on: ''. stream nextPutAll: ''; cr; nextPutAll: '';cr; nextPutAll: '';cr; nextPutAll: ''; nextPutAll: title; nextPutAll: '';cr; nextPutAll: ''; cr. ^ stream contents! ! !HTMLformatter class methodsFor: 'translating' stamp: 'ls 4/18/98 16:23'! evalEmbedded: stringOrStream with: request | formatter | formatter _ self forEvaluatingEmbedded: stringOrStream. ^formatter format: request! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 1/29/98 21:40'! evalEmbedded: string with: request unlessContains: dangerSet dangerSet do: [:each | (string includesSubstring: each caseSensitive: false) ifTrue: [^'Unsafe code!!']]. ^self evalEmbedded: string with: request ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 11/25/97 11:50'! fixEndings: aStringOrStream | sourceStream targetStream aLine | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream := aStringOrStream] ifFalse: [sourceStream := ReadStream on: aStringOrStream]. targetStream := ReadWriteStream on: String new. [sourceStream atEnd] whileFalse: [aLine := sourceStream upTo: (Character linefeed). targetStream nextPutAll: aLine. targetStream nextPut: Character cr.]. ^targetStream ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 6/3/98 10:29'! fixForIE: text | ch targetStream sourceStream | targetStream := WriteStream on: String new. sourceStream := ReadStream on: text. [sourceStream atEnd] whileFalse: [ch := sourceStream next. ch = $> ifTrue: [targetStream nextPutAll: '>'] ifFalse: [ch = $< ifTrue: [targetStream nextPutAll: '<'] ifFalse: [targetStream nextPut: ch]].]. ^targetStream contents ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'tk 7/15/1998 12:02'! forEvaluatingEmbedded: stringOrStream "stringOrStream is text with expressions intermingled. This creates a HTLMLformatter instance which will substitute the expressions with the value of the argument (named request), and which leaves all other text in stringOrStream alone" | blockStream sourceStream doingEval ch | blockStream _ WriteStream on: String new. blockStream nextPutAll: '[ :request :output | output nextPutAll: '''. (stringOrStream isKindOf: Stream) ifTrue: [sourceStream := stringOrStream] ifFalse: [sourceStream := ReadStream on: stringOrStream]. doingEval _ false. [sourceStream atEnd] whileFalse: [ ch := sourceStream next. (doingEval not and: [ ch = $< and: [ sourceStream peek = $? ]]) ifTrue: [ "beginning of an expression" blockStream nextPutAll: '''. output nextPutAll: ['. sourceStream next. "Skip the ?" doingEval _ true] ifFalse: [ (doingEval and: [ ch = $? and: [ sourceStream peek = $> ]]) ifTrue: [ "end of a expression" blockStream nextPutAll: '] value asString. output nextPutAll: '''. sourceStream next. "Skip the >" doingEval _ false.] ifFalse: [ "normal char" blockStream nextPut: ch. (doingEval not and: [ ch = $' ]) ifTrue: [ "double $' marks" blockStream nextPut: $' ] ] ] ]. "end the block" doingEval ifTrue: [ blockStream nextPutAll: '] value asString' ] ifFalse: [ blockStream nextPutAll: '''' ]. blockStream nextPutAll: ']'. ^HTMLformatter new formattingBlock: (Compiler evaluate: blockStream contents)! ! !HTMLformatter class methodsFor: 'translating' stamp: 'ls 4/18/98 16:38'! oldEvalEmbedded: stringOrStream with: request | sourceStream targetStream evalStream currentStream evalValue peekValue ch | (stringOrStream isKindOf: Stream) ifTrue: [sourceStream := stringOrStream] ifFalse: [sourceStream := ReadStream on: stringOrStream]. targetStream := WriteStream on: String new. currentStream := targetStream. [sourceStream atEnd] whileFalse: [ch := sourceStream next. ch = $< ifTrue: [ peekValue := sourceStream peek. (peekValue = $?) ifTrue: [evalStream := WriteStream on: String new. currentStream := evalStream. sourceStream next. "Eat the ?" ch := sourceStream next.]]. ((currentStream = evalStream) and: [ch = $?]) ifTrue: [ peekValue := sourceStream peek. (peekValue = $>) ifTrue: [sourceStream next. "Eat the >" currentStream := targetStream. evalValue := (Compiler new evaluate: (evalStream contents) in: thisContext to: self notifying: nil ifFail: [^nil]). (evalValue isKindOf: String) ifFalse: [evalValue := evalValue printString]. currentStream nextPutAll: evalValue.]] ifFalse: [currentStream nextPut: ch].]. ^targetStream contents ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'tk 1/14/98 10:30'! rangesOfAngleBrackets: sourceStrm "Return an OrderedCollection of intervals of position within angle brackets < and >. Caller wants to avoid putting
    in there." | list char intervals start | list _ OrderedCollection new: 10. [sourceStrm atEnd] whileFalse: [ (char _ sourceStrm next) == $< ifTrue: [list add: sourceStrm position]. "a start" char == $> ifTrue: [list add: sourceStrm position negated]]. "an end" sourceStrm reset. intervals _ OrderedCollection new: 10. start _ nil. list do: [:each | (each > 0) & (start == nil) ifTrue: [start _ each]. (each < 0) & (start ~~ nil) ifTrue: [ intervals add: (start to: each negated). start _ nil]]. ^ intervals " HTMLformatter rangesOfAngleBrackets: (ReadStream on: '1234 <456 567> ') "! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 11/10/97 12:38'! simpleProcess: aStringOrStream | sourceStream targetStream ch | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream := aStringOrStream] ifFalse: [sourceStream := ReadStream on: aStringOrStream]. targetStream := WriteStream on: String new. [sourceStream atEnd] whileFalse: [ch := sourceStream next. (ch = Character linefeed) ifTrue: [(sourceStream peek) = (Character linefeed) ifTrue: [sourceStream next. targetStream nextPutAll: '

    '] ifFalse: [targetStream nextPutAll: '
    ']]. targetStream nextPut: ch]. ^targetStream contents. ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 9/3/1998 16:58'! swikify: aStringOrStream linkhandler: aBlock | formatter | formatter _ self new. formatter specialCharacter: $*. ^formatter swikify: aStringOrStream linkhandler: aBlock! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 12/8/97 11:37'! textToGIF: oneLineString | form filename | form _ (Form extent: 400@20 depth: Display depth) fillWhite. oneLineString displayOn: form at: 2@0. "form display." filename _ 'f',(SmallInteger maxVal atRandom) printString,'.gif'. GIFReadWriter putForm: form onFileNamed: filename. ^(FileStream fileNamed: filename) contentsOfEntireFile ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:48'! checkbox: buttonname value: b ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/24/97 16:56'! formFooter "Write the standard footer for a form." self reply: '


    ' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/24/97 16:58'! formHeader: title For: aReference "Write the standard header for a page and form for editing anObject." self title: title; reply: '
    ' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:54'! graphic: f ^self graphic: f standIn: 'Picture' alignment: 'right'! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:53'! graphic: f standIn: s alignment: a ^ '' , s , ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:55'! hiddenName: n value: v ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 16:02'! linkTo: url label: label ^'',label,''.! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:50'! select: n values: values selection: selection size: size ^ self select: n values: values selections: (Array with: selection) size: size multiple: false! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:49'! select: buttonname values: values selections: selections size: size multiple: multiple | stream | stream _ WriteStream on: ''. stream nextPutAll: ''. ^ stream contents! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:53'! submit: label ^ self submit: 'submit' label: label! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:52'! submit: buttonName label: v ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:51'! text: fieldName ^ self text: fieldName value: '' length: 80. ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:51'! text: fieldName value: v length: l ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:30'! textArea: fieldName ^ (self textAreaStart: fieldName rows: 15 cols: 70), self textAreaEnd! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:30'! textArea: fieldName value: value ^ (self textAreaStart: fieldName rows: 15 cols: 70), value, self textAreaEnd! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:28'! textAreaEnd ^ '' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'pm 6/6/1998 21:39'! textAreaStart: fieldName rows: rows cols: cols ^ '