'From Squeak3.9alpha of 4 July 2005 [latest update: #7054] on 7 September 2006 at 11:17:37 pm'! 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 algorithm 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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." | bitCount | frameSizeMask = 0 ifTrue: [^ bitsPerSample]. "Following assumes mono:" bitCount := 16 + 6 + ((self samplesPerFrame - 1) * bitsPerSample). ^ (bitCount + 7) // 8 ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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 12/14/2001 11:21'! reset self resetForMono. ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'stephaneducasse 2/4/2006 20:40'! resetForMono "Reset my encoding and decoding state for mono." predicted := 0. index := 0. ! ! !ADPCMCodec methodsFor: 'codec stuff' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'jm 3/28/1999 08:59'! encode: aSoundBuffer bitsPerSample: bits ^ self encodeLeft: aSoundBuffer right: nil bitsPerSample: bits frameSize: 0 forFlash: false ! ! !ADPCMCodec methodsFor: 'private' 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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 roundUpTo: self bytesPerEncodedFrame). 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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 actually 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'private' stamp: 'ar 4/23/2001 15:12'! 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: 'instance creation' stamp: 'jm 11/15/2001 16:02'! newBitsPerSample: bitsPerSample ^ super new initializeForBitsPerSample: bitsPerSample 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:)) ! ! Object subclass: #AIFFFileReader instanceVariableNames: 'in fileType channelCount frameCount bitsPerSample samplingRate channelData channelDataOffset 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: '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 10/20/2001 15:07'! channelDataOffset ^ channelDataOffset ! ! !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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: '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' stamp: 'stephaneducasse 2/4/2006 20:40'! readFromFile: fileName mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag "Read the AIFF file of the given name. See comment in readFromStream:mergeIfStereo:skipDataChunk:." "AIFFFileReader new readFromFile: 'test.aiff' mergeIfStereo: false skipDataChunk: true" | f | f := (FileStream readOnlyFileNamed: fileName) binary. self readFromStream: f mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag. f close. ! ! !AIFFFileReader methodsFor: 'reading' stamp: 'stephaneducasse 2/4/2006 20:40'! readFromStream: aBinaryStream mergeIfStereo: mergeFlag skipDataChunk: skipDataFlag "Read an AIFF file from the given binary stream. 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." mergeIfStereo := mergeFlag. skipDataChunk := skipDataFlag. isLooped := false. gain := 1.0. self readFrom: aBinaryStream. ! ! !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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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 | 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']. channelDataOffset := in position. "record stream position for start of data" skipDataChunk ifTrue: [in skip: (chunkSize - 8). ^ self]. "if skipDataChunk, skip sample data" (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: 'stephaneducasse 2/4/2006 20:40'! 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]]. ! ! Exception subclass: #Abort instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! !Abort methodsFor: 'as yet unclassified' stamp: 'ajh 3/24/2003 00:55'! defaultAction "No one has handled this error, but now give them a chance to decide how to debug it. If none handle this either then open debugger (see UnhandedError-defaultAction)" UnhandledError signalForException: self! ! Object subclass: #AbstractEvent instanceVariableNames: 'item itemKind environment' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 6/30/2003 08:22'! item "Return the item that triggered the event (typically the name of a class, a category, a protocol, a method)." ^item! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:43'! itemCategory ^self environmentAt: self class categoryKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:43'! itemClass ^self environmentAt: self class classKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 7/14/2003 12:10'! itemExpression ^self environmentAt: self class expressionKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 6/30/2003 08:22'! itemKind "Return the kind of the item of the event (#category, #class, #protocol, #method, ...)" ^itemKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:44'! itemMethod ^self environmentAt: self class methodKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:44'! itemProtocol ^self environmentAt: self class protocolKind! ! !AbstractEvent methodsFor: 'accessing' stamp: 'NS 1/27/2004 10:38'! itemRequestor ^self environmentAt: #requestor! ! !AbstractEvent methodsFor: 'accessing' stamp: 'NS 1/27/2004 10:38'! itemSelector ^self environmentAt: #selector! ! !AbstractEvent methodsFor: 'printing' stamp: 'NS 1/19/2004 17:52'! printOn: aStream self printEventKindOn: aStream. aStream nextPutAll: ' Event for item: '; print: self item; nextPutAll: ' of kind: '; print: self itemKind! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 6/30/2003 08:34'! isAdded ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'NS 1/19/2004 18:41'! isCategoryKnown ^self itemCategory notNil! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 7/10/2003 15:01'! isCommented ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 7/14/2003 10:15'! isDoIt ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'NS 1/19/2004 15:09'! isModified ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'NS 1/21/2004 09:40'! isProtocolKnown ^self itemCategory notNil! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 7/1/2003 19:53'! isRecategorized ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 6/30/2003 08:34'! isRemoved ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'rw 7/1/2003 11:35'! isRenamed ^false! ! !AbstractEvent methodsFor: 'testing' stamp: 'NS 1/27/2004 12:44'! isReorganized ^ false! ! !AbstractEvent methodsFor: 'triggering' stamp: 'rw 7/14/2003 17:06'! trigger: anEventManager "Trigger the event manager." anEventManager triggerEvent: self eventSelector with: self.! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:10'! changeKind ^self class changeKind! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:43'! environmentAt: anItemKind (self itemKind = anItemKind) ifTrue: [^self item]. ^environment at: anItemKind ifAbsent: [nil]! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:20'! eventSelector ^self class eventSelectorBlock value: itemKind value: self changeKind! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:36'! item: anItem kind: anItemKind item := anItem. itemKind := anItemKind. environment := Dictionary new! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:37'! itemCategory: aCategory environment at: self class categoryKind put: aCategory! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:36'! itemClass: aClass environment at: self class classKind put: aClass! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/14/2003 12:11'! itemExpression: anExpression environment at: self class expressionKind put: anExpression! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:38'! itemMethod: aMethod environment at: self class methodKind put: aMethod! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'rw 7/10/2003 12:38'! itemProtocol: aProtocol environment at: self class protocolKind put: aProtocol! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'NS 1/27/2004 10:38'! itemRequestor: requestor environment at: #requestor put: requestor! ! !AbstractEvent methodsFor: 'private-accessing' stamp: 'NS 1/27/2004 10:39'! itemSelector: aSymbol environment at: #selector put: aSymbol! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AbstractEvent class instanceVariableNames: ''! !AbstractEvent class methodsFor: 'accessing' stamp: 'NS 1/16/2004 14:08'! allChangeKinds "AbstractEvent allChangeKinds" ^AbstractEvent allSubclasses collect: [:cl | cl changeKind]! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'bvs 7/20/2004 12:12'! allItemKinds "self allItemKinds" ^(AbstractEvent class organization listAtCategoryNamed: #'item kinds') collect: [:sel | self perform: sel]! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:08'! changeKind "Return a symbol, with a : as last character, identifying the change kind." self subclassResponsibility! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:18'! eventSelectorBlock ^[:itemKind :changeKind | itemKind, changeKind, 'Event:']! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:19'! itemChangeCombinations ^self supportedKinds collect: [:itemKind | self eventSelectorBlock value: itemKind value: self changeKind]! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:04'! supportedKinds "All the kinds of items that this event can take. By default this is all the kinds in the system. But subclasses can override this to limit the choices. For example, the SuperChangedEvent only works with classes, and not with methods, instance variables, ..." ^self allItemKinds! ! !AbstractEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 11:39'! systemEvents "Return all the possible events in the system. Make a cross product of the items and the change types." "self systemEvents" ^self allSubclasses inject: OrderedCollection new into: [:allEvents :eventClass | allEvents addAll: eventClass itemChangeCombinations; yourself]! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'ab 2/10/2005 16:32'! classCategory: aName ^ self item: aName kind: AbstractEvent categoryKind.! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/19/2004 18:42'! class: aClass ^ self item: aClass kind: AbstractEvent classKind.! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/19/2004 18:42'! class: aClass category: cat | instance | instance := self class: aClass. instance itemCategory: cat. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'rw 7/9/2003 11:19'! item: anItem kind: anItemKind ^self basicNew item: anItem kind: anItemKind! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/16/2004 14:19'! method: aMethod class: aClass | instance | instance := self item: aMethod kind: self methodKind. instance itemClass: aClass. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/16/2004 14:20'! method: aMethod protocol: prot class: aClass | instance | instance := self method: aMethod class: aClass. instance itemProtocol: prot. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/27/2004 10:48'! method: aMethod selector: aSymbol class: aClass | instance | instance := self item: aMethod kind: self methodKind. instance itemSelector: aSymbol. instance itemClass: aClass. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/27/2004 10:49'! method: aMethod selector: aSymbol class: aClass requestor: requestor | instance | instance := self method: aMethod selector: aSymbol class: aClass. instance itemRequestor: requestor. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/27/2004 10:49'! method: aMethod selector: aSymbol protocol: prot class: aClass | instance | instance := self method: aMethod selector: aSymbol class: aClass. instance itemProtocol: prot. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'NS 1/27/2004 10:50'! method: aMethod selector: aSymbol protocol: prot class: aClass requestor: requestor | instance | instance := self method: aMethod selector: aSymbol protocol: prot class: aClass. instance itemRequestor: requestor. ^instance! ! !AbstractEvent class methodsFor: 'instance creation' stamp: 'rw 6/30/2003 09:20'! new "Override new to trigger an error, since we want to use specialized methods to create basic and higher-level events." ^self error: 'Instances can only be created using specialized instance creation methods.'! ! !AbstractEvent class methodsFor: 'item kinds' stamp: 'rw 7/9/2003 11:12'! categoryKind ^#category! ! !AbstractEvent class methodsFor: 'item kinds' stamp: 'rw 7/9/2003 11:12'! classKind ^#class! ! !AbstractEvent class methodsFor: 'item kinds' stamp: 'rw 7/14/2003 11:41'! expressionKind ^#expression! ! !AbstractEvent class methodsFor: 'item kinds' stamp: 'rw 7/9/2003 11:12'! methodKind ^#method! ! !AbstractEvent class methodsFor: 'item kinds' stamp: 'rw 7/10/2003 12:36'! protocolKind ^#protocol! ! !AbstractEvent class methodsFor: 'temporary' stamp: 'rw 7/11/2003 10:23'! comment1 "Smalltalk organization removeElement: #ClassForTestingSystemChanges3 Smalltalk garbageCollect Smalltalk organizati classify:under: SystemChangeNotifier uniqueInstance releaseAll SystemChangeNotifier uniqueInstance noMoreNotificationsFor: aDependent. aDependent := SystemChangeNotifierTest new. SystemChangeNotifier uniqueInstance notifyOfAllSystemChanges: aDependent using: #event: SystemChangeNotifier uniqueInstance classAdded: #Foo inCategory: #FooCat | eventSource dependentObject | eventSource := EventManager new. dependentObject := Object new. register - dependentObject becomes dependent: eventSource when: #anEvent send: #error to: dependentObject. unregister dependentObject: eventSource removeDependent: dependentObject. [eventSource triggerEvent: #anEvent] on: Error do: [:exc | self halt: 'Should not be!!']."! ! !AbstractEvent class methodsFor: 'temporary' stamp: 'rw 7/11/2003 10:24'! comment2 "HTTPSocket useProxyServerNamed: 'proxy.telenet.be' port: 8080 TestRunner open -------------------- We propose two orthogonal groups to categorize each event: (1) the 'change type': added, removed, modified, renamed + the composite 'changed' (see below for an explanation) (2) the 'item type': class, method, instance variable, pool variable, protocol, category + the composite 'any' (see below for an explanation). The list of supported events is the cross product of these two lists (see below for an explicit enumeration of the events). Depending on the change type, certain information related to the change is always present (for adding, the new things that was added, for removals, what was removed, for renaming, the old and the new name, etc.). Depending on the item type, information regarding the item is present (for a method, which class it belongs to). Certain events 'overlap', for example, a method rename triggers a class change. To capture this I impose a hierarchy on the 'item types' (just put some numbers to clearly show the idea. They don't need numbers, really. Items at a certain categories are included by items one category number higher): level 1 category level 2 class level 3 instance variable, pool variable, protocol, method. Changes propagate according to this tree: any 'added', 'removed' or 'renamed' change type in level X triggers a 'changed' change type in level X - 1. A 'modified' change type does not trigger anything special. For example, a method additions triggers a class modification. This does not trigger a category modification. Note that we added 'composite events': wildcards for the 'change type' ('any' - any system additions) and for the 'item type' ('Changed' - all changes related to classes), and one for 'any change systemwide' (systemChanged). This result is this list of Events: classAdded classRemoved classModified classRenamed (?) classChanged (composite) methodAdded methodRemoved methodModified methodRenamed (?) methodChanged (composite) instanceVariableAdded instanceVariableRemoved instanceVariableModified instanceVariableRenamed (?) instanceVariableChanged (composite) protocolAdded protocolRemoved protocolModified protocolRenamed (?) protocolChanged (composite) poolVariableAdded poolVariableRemoved poolVariableModified poolVariableRenamed (?) poolChanged (composite) categoryAdded categoryRemoved categoryModified categeryRenamed (?) categoryChanged (composite) anyAdded (composite) anyRemoved (composite) anyModified (composite) anyRenamed (composite) anyChanged (composite) To check: can we pass somehow the 'source' of the change (a browser, a file-in, something else) ? Maybe by checking the context, but should not be too expensive either... I found this useful in some of my tools, but it might be too advanced to have in general. Tools that need this can always write code to check it for them. But is not always simple... Utilities (for the recent methods) and ChangeSet are the two main clients at this moment. Important: make it very explicit that the event is send synchronously (or asynchronously, would we take that route). category class comment protocol method OR category Smalltalk class comment protocol method ?? Smalltalk category \ / class / | \ comment | protocol | / method "! ! !AbstractEvent class methodsFor: 'temporary' stamp: 'rw 7/11/2003 15:43'! comment3 "Things to consider for trapping: ClassOrganizer>>#changeFromCategorySpecs: Problem: I want to trap this to send the appropriate bunch of ReCategorization events, but ClassOrganizer instances do not know where they belong to (what class, or what system); it just uses symbols. So I cannot trigger the change, because not enough information is available. This is a conceptual problem: the organization is stand-alone implementation-wise, while conceptually it belongs to a class. The clean solution could be to reroute this message to a class, but this does not work for all of the senders (that would work from the browserm but not for the file-in). Browser>>#categorizeAllUncategorizedMethods Problem: should be trapped to send a ReCategorization event. However, this is model code that should not be in the Browser. Clean solution is to move it out of there to the model, and then trap it there (or reroute it to one of the trapped places). Note: Debugger>>#contents:notifying: recompiles methods when needed, so I trapped it to get updates. However, I need to find a way to write a unit test for this. Haven't gotten around yet for doing this though... "! ! !AbstractEvent class methodsFor: 'temporary' stamp: 'ar 9/27/2005 20:05'! saveChangeNotificationAsSARFileWithNumber: aNumber "Use the SARBuilder package to output the SystemChangeNotification stuff as a SAR file. Put this statement here so that I don't forget it when moving between images :-)" "self saveChangeNotificationAsSARFileWithNumber: 6" | filename changesText readmeText dumper | filename := 'SystemchangeNotification'. dumper := self class environment at: #SARChangeSetDumper ifAbsent: [ ^self ]. changesText := ' 0.6 Version for Squeak 3.7 (no longer for 3.6!!!!) Changed one hook method to make this version work in Squeak3.7. Download version 5 from http://www.iam.unibe.ch/~wuyts/SystemchangeNotification5.sar if you are working with Squeak 3.6. 0.5 Updated the safeguard mechanism so that clients with halts and errors do not stop all notifications. Added and updated new tests for this. If this interests you have a look at the class WeakActionSequenceTrappingErrors. 0.4 Ported to Squeak 3.6. 0.3 Added the hooks for instance variables (addition, removal and renaming). Refactored the tests. 0.2 Added hooks and tests for method removal and method recategorization. 0.1 First release'. readmeText := 'Implements (part of) the system change notification mechanism. Clients that want to receive notifications about system changes should look at the category #public of the class SystemChangeNotifier, and the unit tests. VERY IMPORTANT: This version is for Squeak 3.7 only. It will not work in Squeak version 3.6. Download and install the last version that worked in Squeak 3.6 (version 5) from the following URL: http://www.iam.unibe.ch/~wuyts/SystemchangeNotification5.sar'. (dumper on: Project current changeSet including: (ChangeSet allChangeSetNames select: [:ea | 'SystemChangeHooks' match: ea])) changesText: changesText; readmeText: readmeText; fileOutAsZipNamed: filename , aNumber printString , '.sar'! ! Object subclass: #AbstractFont instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Fonts'! !AbstractFont commentStamp: '' prior: 0! AbstractFont defines the generic interface that all fonts need to implement.! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:19'! ascent self subclassResponsibility. ! ! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:18'! ascentOf: aCharacter ^ self ascent. ! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 4/2/2004 11:06'! baseKern ^0! ! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:18'! basicAscentOf: aCharacter ^ self ascent. ! ! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:19'! basicDescentOf: aCharacter ^ self descent. ! ! !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: 'nk 3/15/2004 18:57'! derivativeFonts ^#()! ! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:20'! descent self subclassResponsibility. ! ! !AbstractFont methodsFor: 'accessing' stamp: 'yo 1/7/2005 11:20'! descentOf: aCharacter ^ self descent. ! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:15'! familyName "Answer the name to be used as a key in the TextConstants dictionary." ^self subclassResponsibility! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:14'! height "Answer the height of the receiver, total of maximum extents of characters above and below the baseline." ^self subclassResponsibility! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 5/26/2003 09:45'! isRegular ^false! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:14'! lineGrid "Answer the relative space between lines" ^self subclassResponsibility! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 4/2/2004 11:33'! pixelSize "Make sure that we don't return a Fraction" ^ TextStyle pointsToPixels: self pointSize! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 4/1/2004 10:48'! pointSize self subclassResponsibility.! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 7/11/2004 21:15'! textStyle ^ TextStyle actualTextStyles detect: [:aStyle | aStyle fontArray includes: self] ifNone: [ TextStyle fontArray: { self } ]! ! !AbstractFont methodsFor: 'accessing' stamp: 'nk 3/22/2004 15:15'! textStyleName "Answer the name to be used as a key in the TextConstants dictionary." ^self familyName! ! !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: 'caching' stamp: 'nk 3/15/2004 18:47'! releaseCachedState ! ! !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: 'yo 1/7/2005 11:36'! displayString: aString on: aDisplayContext from: startIndex to: stopIndex at: aPoint kern: kernDelta baselineY: baselineY "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! ! !AbstractFont methodsFor: 'measuring' stamp: 'tak 1/11/2005 17:20'! approxWidthOfText: aText "Return the width of aText -- quickly, and a little bit dirty. Used by lists morphs containing Text objects to get a quick, fairly accurate measure of the width of a list item." | w | (aText isNil or: [aText size == 0 ]) ifTrue:[^0]. w _ self widthOfString: aText asString. "If the text has no emphasis, just return the string size. If it is empasized, just approximate the width by adding about 20% to the width" (((aText runLengthFor: 1) == aText size) and: [(aText emphasisAt: 1) == 0 ]) ifTrue:[^w] ifFalse:[ ^w * 6 // 5 ]. ! ! !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 12/31/2001 14:25'! widthOfString: aString aString ifNil:[^0]. ^self widthOfString: aString from: 1 to: aString size. " TextStyle default defaultFont widthOfString: 'zort' 21 "! ! !AbstractFont methodsFor: 'measuring' stamp: 'ar 12/31/2001 00:54'! widthOfString: aString from: startIndex to: stopIndex "Measure the length of the given string between start and stop index" | character resultX | resultX _ 0. startIndex to: stopIndex do:[:i | character _ aString at: i. resultX _ resultX + (self widthOf: character)]. ^resultX! ! !AbstractFont methodsFor: 'measuring' stamp: 'sps 3/23/2004 15:50'! widthOfStringOrText: aStringOrText aStringOrText ifNil:[^0]. ^aStringOrText isText ifTrue:[self approxWidthOfText: aStringOrText ] ifFalse:[self widthOfString: aStringOrText ] ! ! !AbstractFont methodsFor: 'notifications' stamp: 'nk 4/2/2004 11:25'! pixelsPerInchChanged "The definition of TextStyle class>>pixelsPerInch has changed. Do whatever is necessary."! ! !AbstractFont methodsFor: 'testing' stamp: 'nk 6/25/2003 12:54'! isTTCFont ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AbstractFont class instanceVariableNames: ''! !AbstractFont class methodsFor: 'as yet unclassified' stamp: 'nk 9/1/2004 11:41'! emphasisStringFor: emphasisCode "Answer a translated string that represents the attributes given in emphasisCode." | emphases bit | emphasisCode = 0 ifTrue: [ ^'Normal' translated ]. emphases := (IdentityDictionary new) at: 1 put: 'Bold' translated; at: 2 put: 'Italic' translated; at: 4 put: 'Underlined' translated; at: 8 put: 'Narrow' translated; at: 16 put: 'StruckOut' translated; yourself. bit := 1. ^String streamContents: [ :s | [ bit < 32 ] whileTrue: [ | code | code := emphasisCode bitAnd: bit. code isZero ifFalse: [ s nextPutAll: (emphases at: code); space ]. bit := bit bitShift: 1 ]. s position isZero ifFalse: [ s skip: -1 ]. ]! ! Model subclass: #AbstractHierarchicalList instanceVariableNames: 'currentSelection myBrowser' classVariableNames: '' poolDictionaries: '' category: 'Morphic-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: #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: 'running' stamp: 'tk 10/24/2001 06:40'! startUp "A backstop for subclasses. Note that this is not a class message (most startUps are class messages)." ! ! !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: 'mdr 4/10/2001 10:50'! 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 class instanceVariableNames: ''! !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'! deactivate "Unregister this launcher with the auto start class" self autoStarter removeLauncher: self! ! !AbstractLauncher class methodsFor: 'private' stamp: 'mir 8/4/1999 13:57'! autoStarter ^AutoStart! ! !AbstractLauncher class methodsFor: 'private' stamp: 'sd 9/30/2003 13:55'! extractParameters ^ SmalltalkImage current extractParameters! ! RectangleMorph subclass: #AbstractMediaEventMorph instanceVariableNames: 'startTimeInScore endTimeInScore' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !AbstractMediaEventMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color paleYellow! ! !AbstractMediaEventMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 21:38'! initialize "initialize the state of the receiver" super initialize. "" self layoutPolicy: TableLayout new; listDirection: #leftToRight; wrapCentering: #topLeft; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 2; rubberBandCells: true! ! !AbstractMediaEventMorph methodsFor: '*sound-piano rolls' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: #AbstractObjectsAsMethod instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-ObjectsAsMethods'! !AbstractObjectsAsMethod methodsFor: 'as yet unclassified' stamp: 'md 3/1/2006 14:25'! flushCache! ! !AbstractObjectsAsMethod methodsFor: 'as yet unclassified' stamp: 'md 3/1/2006 14:23'! methodClass: aMethodClass! ! !AbstractObjectsAsMethod methodsFor: 'as yet unclassified' stamp: 'md 3/1/2006 14:23'! selector: aSymbol! ! Morph subclass: #AbstractResizerMorph instanceVariableNames: 'dotColor handleColor lastMouse' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !AbstractResizerMorph commentStamp: 'jmv 1/29/2006 17:15' prior: 0! I am the superclass of a hierarchy of morph specialized in allowing the user to resize or rearrange windows and panes.! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:36'! dotColor ^ dotColor ifNil: [self setDefaultColors. dotColor]! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:35'! handleColor ^ handleColor ifNil: [self setDefaultColors. handleColor]! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:37'! handlesMouseDown: anEvent ^ true! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:37'! handlesMouseOver: anEvent ^ true ! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'md 2/24/2006 23:01'! initialize super initialize. self color: Color transparent! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:40'! isCursorOverHandle ^ true! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:42'! mouseDown: anEvent lastMouse _ anEvent cursorPoint! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'md 2/24/2006 23:01'! mouseEnter: anEvent self isCursorOverHandle ifTrue: [self setInverseColors. self changed. anEvent hand showTemporaryCursor: self resizeCursor]! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:37'! mouseLeave: anEvent anEvent hand showTemporaryCursor: nil. self setDefaultColors. self changed! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/5/2005 21:37'! resizeCursor self subclassResponsibility! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/29/2005 13:25'! setDefaultColors handleColor _ Color lightGray lighter lighter. dotColor _ Color gray lighter! ! !AbstractResizerMorph methodsFor: 'as yet unclassified' stamp: 'jrp 7/30/2005 21:30'! setInverseColors handleColor _ Color lightGray. dotColor _ Color white! ! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'accessing' stamp: 'jm 12/16/2001 22:34'! isStereo "Answer true if this sound has distinct left and right channels. (Every sound plays into a stereo sample buffer, but most sounds, which produce exactly the same samples on both channels, are not stereo.)" ^ false ! ! !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: 'conversion' stamp: 'jm 12/16/2001 13:26'! asSampledSound "Answer a SampledSound containing my samples. If the receiver is some kind of sampled sound, the resulting SampledSound will have the same original sampling rate as the receiver." ^ SampledSound samples: self samples samplingRate: self originalSamplingRate ! ! !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: 'stephaneducasse 2/4/2006 20:40'! 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: 'envelopes' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! removeAllEnvelopes "Remove all envelopes from my envelopes list." envelopes := #(). ! ! !AbstractSound methodsFor: 'envelopes' stamp: 'stephaneducasse 2/4/2006 20:40'! removeEnvelope: anEnvelope "Remove the given envelope from my envelopes list." envelopes := envelopes copyWithout: anEnvelope. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeAIFFOnFileNamed: fileName "Store this sound as a AIFF file of the given name." | f | f := (FileStream fileNamed: fileName) binary. self storeAIFFSamplesOn: f. f close. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeAIFFSamplesOn: aBinaryStream "Store this sound as a 16-bit AIFF file at the current SoundPlayer sampling rate. Store both channels if self isStereo is true; otherwise, store the left channel only as a mono sound." | samplesToStore channelCount dataByteCount | samplesToStore := (self duration * self samplingRate) ceiling. channelCount := self isStereo ifTrue: [2] ifFalse: [1]. dataByteCount := samplesToStore * channelCount * 2. "write AIFF file header:" aBinaryStream nextPutAll: 'FORM' asByteArray. aBinaryStream nextInt32Put: ((7 * 4) + 18) + dataByteCount. aBinaryStream nextPutAll: 'AIFF' asByteArray. aBinaryStream nextPutAll: 'COMM' asByteArray. aBinaryStream nextInt32Put: 18. aBinaryStream nextNumber: 2 put: channelCount. aBinaryStream nextInt32Put: samplesToStore. aBinaryStream nextNumber: 2 put: 16. "bits/sample" self storeExtendedFloat: self samplingRate on: aBinaryStream. aBinaryStream nextPutAll: 'SSND' asByteArray. aBinaryStream nextInt32Put: dataByteCount + 8. aBinaryStream nextInt32Put: 0. aBinaryStream nextInt32Put: 0. "write data:" self storeSampleCount: samplesToStore bigEndian: true on: aBinaryStream. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! storeSampleCount: samplesToStore bigEndian: bigEndianFlag on: aBinaryStream "Store my samples on the given stream at the current SoundPlayer sampling rate. If bigFlag is true, then each 16-bit sample is stored most-significant byte first (AIFF files), otherwise it is stored least-significant byte first (WAV files). If self isStereo is true, both channels are stored, creating a stereo file. Otherwise, only the left channel is stored, creating a mono file." | bufSize stereoBuffer reverseBytes remaining out | self reset. bufSize := (2 * self samplingRate rounded) min: samplesToStore. "two second buffer" stereoBuffer := SoundBuffer newStereoSampleCount: bufSize. reverseBytes := bigEndianFlag ~= (SmalltalkImage current isBigEndian). 'Storing audio...' displayProgressAt: Sensor cursorPoint from: 0 to: samplesToStore during: [:bar | remaining := samplesToStore. [remaining > 0] whileTrue: [ bar value: samplesToStore - remaining. stereoBuffer primFill: 0. "clear the buffer" self playSampleCount: (bufSize min: remaining) into: stereoBuffer startingAt: 1. self isStereo ifTrue: [out := stereoBuffer] ifFalse: [out := stereoBuffer extractLeftChannel]. reverseBytes ifTrue: [out reverseEndianness]. (aBinaryStream isKindOf: StandardFileStream) ifTrue: [ "optimization for files: write sound buffer directly to file" aBinaryStream next: (out size // 2) putAll: out startingAt: 1] "size in words" ifFalse: [ "for non-file streams:" 1 to: out monoSampleCount do: [:i | aBinaryStream int16: (out at: i)]]. remaining := remaining - bufSize]]. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeSunAudioOnFileNamed: fileName "Store this sound as an uncompressed Sun audio file of the given name." | f | f := (FileStream fileNamed: fileName) binary. self storeSunAudioSamplesOn: f. f close. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeSunAudioSamplesOn: aBinaryStream "Store this sound as a 16-bit Sun audio file at the current SoundPlayer sampling rate. Store both channels if self isStereo is true; otherwise, store the left channel only as a mono sound." | samplesToStore channelCount dataByteCount | samplesToStore := (self duration * self samplingRate) ceiling. channelCount := self isStereo ifTrue: [2] ifFalse: [1]. dataByteCount := samplesToStore * channelCount * 2. "write Sun audio file header" channelCount := self isStereo ifTrue: [2] ifFalse: [1]. aBinaryStream nextPutAll: '.snd' asByteArray. aBinaryStream uint32: 24. "header size in bytes" aBinaryStream uint32: dataByteCount. aBinaryStream uint32: 3. "format: 16-bit linear" aBinaryStream uint32: self samplingRate truncated. aBinaryStream uint32: channelCount. "write data:" self storeSampleCount: samplesToStore bigEndian: true on: aBinaryStream. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeWAVOnFileNamed: fileName "Store this sound as a 16-bit Windows WAV file of the given name." | f | f := (FileStream fileNamed: fileName) binary. self storeWAVSamplesOn: f. f close. ! ! !AbstractSound methodsFor: 'file i/o' stamp: 'stephaneducasse 2/4/2006 20:40'! storeWAVSamplesOn: aBinaryStream "Store this sound as a 16-bit Windows WAV file at the current SoundPlayer sampling rate. Store both channels if self isStereo is true; otherwise, store the left channel only as a mono sound." | samplesToStore channelCount dataByteCount samplesPerSec bytesPerSec | samplesToStore := (self duration * self samplingRate) ceiling. channelCount := self isStereo ifTrue: [2] ifFalse: [1]. dataByteCount := samplesToStore * channelCount * 2. samplesPerSec := self samplingRate rounded. bytesPerSec := samplesPerSec * channelCount * 2. "file header" aBinaryStream nextPutAll: 'RIFF' asByteArray; nextLittleEndianNumber: 4 put: dataByteCount + 36; "total length of all chunks" nextPutAll: 'WAVE' asByteArray. "format chunk" aBinaryStream nextPutAll: 'fmt ' asByteArray; nextLittleEndianNumber: 4 put: 16; "length of this chunk" nextLittleEndianNumber: 2 put: 1; "format tag" nextLittleEndianNumber: 2 put: channelCount; nextLittleEndianNumber: 4 put: samplesPerSec; nextLittleEndianNumber: 4 put: bytesPerSec; nextLittleEndianNumber: 2 put: 4; "alignment" nextLittleEndianNumber: 2 put: 16. "bits per sample" "data chunk" aBinaryStream nextPutAll: 'data' asByteArray; nextLittleEndianNumber: 4 put: dataByteCount. "length of this chunk" self storeSampleCount: samplesToStore bigEndian: false on: aBinaryStream. ! ! !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: 'stephaneducasse 2/4/2006 20:40'! initialize envelopes := #(). mSecsSinceStart := 0. samplesUntilNextControl := 0. scaledVol := (1.0 * ScaleFactor) rounded. scaledVolIncr := 0. scaledVolLimit := scaledVol. ! ! !AbstractSound methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'playing' stamp: 'stephaneducasse 2/4/2006 20:40'! 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' stamp: 'gk 2/24/2004 22:23'! play "Play this sound to the sound output 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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 12/16/2001 13:22'! samples "Answer a monophonic sample buffer containing my samples. The left and write channels are merged." "Warning: This may require a lot of memory!!" ^ (self computeSamplesForSeconds: self duration) mergeStereo ! ! !AbstractSound methodsFor: 'playing' stamp: 'jm 12/16/2001 13:24'! viewSamples "Open a WaveEditor on my samples." WaveEditor openOn: self samples. ! ! !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/16/2001 13:14'! originalSamplingRate "For sampled sounds, answer the sampling rate used to record the stored samples. For other sounds, this is the same as the playback sampling rate." ^ SoundPlayer samplingRate ! ! !AbstractSound methodsFor: 'sampling rates' stamp: 'jm 12/17/97 18:00'! samplingRate "Answer the sampling rate in samples per second." ^ SoundPlayer samplingRate ! ! !AbstractSound methodsFor: 'sound generation' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'volume' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! 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 class instanceVariableNames: ''! !AbstractSound class methodsFor: 'class initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'examples' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: '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: 'stephaneducasse 2/4/2006 20:41'! 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: '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:) ). ! ! !AbstractSound class methodsFor: 'sound library' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! fileInSoundLibrary "Prompt the user for a file name and the file in the sound library with that name." "AbstractSound fileInSoundLibrary" | fileName | fileName := UIManager default 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'rbb 3/1/2005 10:21'! 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 := UIManager default 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'utilities' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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 ! ! Object subclass: #AbstractSoundSystem instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !AbstractSoundSystem commentStamp: 'gk 2/24/2004 08:34' prior: 0! This is the abstract base class for a sound system. A sound system offers a small protocol for playing sounds and making beeps and works like a facade towards the rest of Squeak. A sound system is registered in the application registry SoundService and can be accessed by "SoundService default" like for example: SoundService default playSoundNamed: 'croak' The idea is that as much sound playing as possible should go through this facade. This way we decouple the sound system from the rest of Squeak and make it pluggable. It also is a perfect spot to check for the Preference class>>soundsEnabled. Two basic subclasses exist at the time of this writing, the BaseSoundSystem which represents the standard Squeak sound system, and the DummySoundSystem which is a dummy implementation that can be used when there is no sound card available, or when the base sound system isn't in the image, or when you simply don't want to use the available sound card.! !AbstractSoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:52'! randomBitsFromSoundInput: bitCount self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:52'! sampledSoundChoices self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:53'! shutDown "Default is to do nothing."! ! !AbstractSoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:56'! soundNamed: soundName self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'playing' stamp: 'gk 2/24/2004 23:27'! beep "Make a primitive beep." self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:47'! playSampledSound: samples rate: rate self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:50'! playSoundNamed: soundName self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:51'! playSoundNamed: soundName ifAbsentReadFrom: aifFileName self subclassResponsibility! ! !AbstractSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:51'! playSoundNamedOrBeep: soundName self subclassResponsibility! ! PluggableTextMorph subclass: #AcceptableCleanTextMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Pluggable Widgets'! !AcceptableCleanTextMorph methodsFor: 'menu commands' stamp: 'dgd 2/21/2003 22:50'! accept "Overridden to allow accept of clean text" | textToAccept ok | textToAccept := textMorph asText. ok := setTextSelector isNil 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: 'LegalCharMap' poolDictionaries: '' category: 'Files-Directories'! !AcornFileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! checkName: aFileName fixErrors: fixing "Check if the file name contains any invalid characters" | fName hasBadChars correctedName newChar| fName := super checkName: aFileName fixErrors: fixing. correctedName := String streamContents:[:s| fName do:[:c| (newChar := LegalCharMap at: c asciiValue +1) ifNotNil:[s nextPut: newChar]]]. hasBadChars := fName ~= correctedName. (hasBadChars and:[fixing not]) ifTrue:[^self error:'Invalid file name']. hasBadChars ifFalse:[^ fName]. ^ correctedName! ! !AcornFileDirectory methodsFor: 'file name utilities' stamp: 'tpr 11/5/2004 13:08'! fullPathFor: path "if the arg is an empty string, just return my path name converted via the language stuff. If the arg seems to be a rooted path, return it raw, assuming it is already ok. Otherwise cons up a path" path isEmpty ifTrue:[^pathName asSqueakPathName]. ((path includes: $$ ) or:[path includes: $:]) ifTrue:[^path]. ^pathName asSqueakPathName, self slash, path! ! !AcornFileDirectory methodsFor: 'path access' stamp: 'tpr 11/30/2003 21:42'! pathParts "Return the path from the root of the file system to this directory as an array of directory names. This version tries to cope with the RISC OS' strange filename formatting; filesystem::discname/$/path/to/file where the $ needs to be considered part of the filingsystem-discname atom." | pathList | pathList := super pathParts. (pathList indexOf: '$') = 2 ifTrue: ["if the second atom is root ($) then stick $ on the first atom and drop the second. Yuck" ^ Array streamContents: [:a | a nextPut: (pathList at: 1), '/$'. 3 to: pathList size do: [:i | a nextPut: (pathList at: i)]]]. ^ pathList! ! !AcornFileDirectory methodsFor: 'testing' stamp: 'tpr 4/28/2004 21:54'! directoryExists: filenameOrPath "if the path is a root,we have to treat it carefully" (filenameOrPath endsWith: '$') ifTrue:[^(FileDirectory on: filenameOrPath) exists]. ^(self directoryNamed: filenameOrPath ) exists! ! !AcornFileDirectory methodsFor: 'private' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 extraPath | entries := super directoryContentsFor: fullPath. fullPath isNullPath ifTrue: [ "For Acorn we also make sure that at least the parent of the current dir is added - sometimes this is in a filing system that has not been (or cannot be) polled for disc root names" extraPath := self class default containingDirectory. "Only add the extra path if we haven't already got the root of the current dir in the list" entries detect: [:ent | extraPath fullName beginsWith: ent name] ifNone: [entries := entries copyWith: (DirectoryEntry name: extraPath fullName creationTime: 0 modificationTime: 0 isDirectory: true fileSize: 0)]]. ^ entries ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AcornFileDirectory class instanceVariableNames: ''! !AcornFileDirectory class methodsFor: 'class initialization' stamp: 'stephaneducasse 2/4/2006 20:32'! initialize "Set up the legal chars map for filenames. May need extending for unicode etc. Basic rule is that any char legal for use in filenames will have a non-nil entry in this array; except for space, this is the same character. Space is transcoded to a char 160 to be a 'hard space' " "AcornFileDirectory initialize" | aVal | LegalCharMap := Array new: 256. Character alphabet do:[:c| LegalCharMap at: c asciiValue +1 put: c. LegalCharMap at: (aVal := c asUppercase) asciiValue +1 put: aVal]. '`!!()-_=+[{]};~,./1234567890' do:[:c| LegalCharMap at: c asciiValue + 1 put: c]. LegalCharMap at: Character space asciiValue +1 put: (Character value:160 "hardspace"). LegalCharMap at: 161 put: (Character value:160 "hardspace")."secondary mapping to keep it in strings"! ! !AcornFileDirectory class methodsFor: 'platform specific' stamp: 'md 10/26/2003 13:16'! 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" ^ SmalltalkImage current platformName = 'RiscOS' or: [self primPathNameDelimiter = $.]! ! !AcornFileDirectory class methodsFor: 'platform specific' stamp: 'tpr 8/1/2003 16:38'! isCaseSensitive "Risc OS ignores the case of file names" ^ false! ! !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" ^ $/ ! ! !AcornFileDirectory class methodsFor: '*network-uri' stamp: 'tpr 5/4/2005 17:22'! privateFullPathForURI: aURI "derive the full filepath from aURI" | first path | path _ String streamContents: [ :s | first := false. aURI pathComponents do: [ :p | first ifTrue: [ s nextPut: self pathNameDelimiter ]. first _ true. s nextPutAll: p ] ]. ^path unescapePercents ! ! Array variableSubclass: #ActionSequence instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Object Events'! !ActionSequence commentStamp: 'tlk 5/7/2006 20:02' prior: 0! An ActionSequence is an array that lists the object's dependant objects.! !ActionSequence methodsFor: 'converting' stamp: 'reThink 2/18/2001 15:12'! asActionSequence ^self! ! !ActionSequence methodsFor: 'converting' stamp: 'rw 7/20/2003 16:03'! asActionSequenceTrappingErrors ^WeakActionSequenceTrappingErrors withAll: self! ! !ActionSequence methodsFor: 'converting' stamp: 'reThink 2/18/2001 15:28'! asMinimalRepresentation self size = 0 ifTrue: [^nil]. self size = 1 ifTrue: [^self first]. ^self! ! !ActionSequence methodsFor: 'evaluating' stamp: 'reThink 2/18/2001 17:51'! value "Answer the result of evaluating the elements of the receiver." | answer | self do: [:each | answer := each value]. ^answer! ! !ActionSequence methodsFor: 'evaluating' stamp: 'reThink 2/18/2001 17:52'! valueWithArguments: anArray | answer | self do: [:each | answer := each valueWithArguments: anArray]. ^answer! ! !ActionSequence methodsFor: 'printing' stamp: 'SqR 07/28/2001 18:25'! printOn: aStream self size < 2 ifTrue: [^super printOn: aStream]. aStream nextPutAll: '#('. self do: [:each | each printOn: aStream] separatedBy: [aStream cr]. aStream nextPut: $)! ! Object subclass: #ActorState instanceVariableNames: 'owningPlayer penDown penSize penColor fractionalPosition instantiatedUserScriptsDictionary penArrowheads trailStyle' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'filter streaming' 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']. ! ! !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: '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: 'pen' stamp: 'nk 6/12/2004 16:36'! choosePenColor: evt owningPlayer costume changeColorTarget: owningPlayer costume selector: #penColor: originalColor: owningPlayer getPenColor 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: 'tk 10/4/2001 16:42'! getPenArrowheads ^ penArrowheads == true! ! !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: 'tk 10/4/2001 16:43'! setPenArrowheads: aBoolean penArrowheads _ aBoolean! ! !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: 'pen' stamp: 'sw 4/16/2003 12:26'! trailStyle "Answer the receiver's trailStyle. For backward compatibility, if the old penArrowheads slot is in found to be set, use it as a guide for initialization" ^ trailStyle ifNil: [trailStyle _ penArrowheads == true ifTrue: [#arrows] ifFalse: [#lines]]! ! !ActorState methodsFor: 'pen' stamp: 'sw 3/11/2003 11:28'! trailStyle: aSymbol "Set the trail style to the given symbol" trailStyle _ aSymbol! ! !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: 'printing' 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: 'script instantiations' stamp: 'sw 4/9/98 22:35'! instantiatedUserScriptsDictionary instantiatedUserScriptsDictionary ifNil: [instantiatedUserScriptsDictionary _ IdentityDictionary new]. ^ instantiatedUserScriptsDictionary! ! AbstractEvent subclass: #AddedEvent instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'! !AddedEvent methodsFor: 'printing' stamp: 'rw 6/30/2003 09:31'! printEventKindOn: aStream aStream nextPutAll: 'Added'! ! !AddedEvent methodsFor: 'testing' stamp: 'rw 6/30/2003 08:35'! isAdded ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AddedEvent class instanceVariableNames: ''! !AddedEvent class methodsFor: 'accessing' stamp: 'rw 7/19/2003 09:52'! changeKind ^#Added! ! !AddedEvent class methodsFor: 'accessing' stamp: 'NS 1/20/2004 12:22'! supportedKinds "All the kinds of items that this event can take." ^ Array with: self classKind with: self methodKind with: self categoryKind with: self protocolKind! ! EllipseMorph subclass: #AlertMorph instanceVariableNames: 'onColor offColor myObjSock socketOwner' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Audio Chat'! !AlertMorph methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! color: aColor super color: aColor. onColor := aColor.! ! !AlertMorph methodsFor: 'accessing' stamp: 'mir 8/31/2004 15:47'! onColor ^onColor ifNil: [onColor := Color green]! ! !AlertMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! socketOwner: aChatGUI socketOwner := aChatGUI.! ! !AlertMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:11'! defaultBorderWidth "answer the default border width for the receiver" ^ 2! ! !AlertMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:11'! defaultColor "answer the default color/fill style for the receiver" ^ Color red! ! !AlertMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:11'! initialize "initialize the state of the receiver" super initialize. "" self extent: 25 @ 25. ! ! !AlertMorph methodsFor: 'stepping and presenter' stamp: 'sd 11/20/2005 21:25'! step super step. offColor ifNil: [offColor := self 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: 'testing' stamp: 'TBP 3/5/2000 13:47'! stepTime "Answer the desired time between steps in milliseconds." ^ 500! ! !AlertMorph methodsFor: 'visual properties' stamp: 'TBP 3/5/2000 13:47'! canHaveFillStyles ^false! ! RectangleMorph subclass: #AlignmentMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !AlignmentMorph commentStamp: 'kfr 10/27/2003 10:25' prior: 0! Used for layout. Since all morphs now support layoutPolicy the main use of this class is no longer needed. Kept around for compability. Supports a few methods not found elsewhere that can be convenient, eg. newRow ! !AlignmentMorph methodsFor: 'classification' stamp: 'di 5/7/1998 01:20'! isAlignmentMorph ^ true ! ! !AlignmentMorph methodsFor: 'event handling' stamp: 'sw 5/6/1998 15:58'! wantsKeyboardFocusFor: aSubmorph aSubmorph wouldAcceptKeyboardFocus ifTrue: [^ true]. ^ super wantsKeyboardFocusFor: aSubmorph! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 0! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color r: 0.8 g: 1.0 b: 0.8! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:19'! initialize "initialize the state of the receiver" super initialize. "" self layoutPolicy: TableLayout new; listDirection: #leftToRight; wrapCentering: #topLeft; hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 2; rubberBandCells: true! ! !AlignmentMorph methodsFor: 'initialization' stamp: 'ar 11/9/2000 20:34'! openInWindowLabeled: aString inWorld: aWorld self layoutInset: 0. ^super openInWindowLabeled: aString inWorld: aWorld.! ! !AlignmentMorph methodsFor: 'object fileIn' stamp: 'gm 2/22/2003 13:12'! 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 isMorph ifTrue: [(owner isAlignmentMorph) 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 isSystemWindow 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: 'objects from disk' stamp: 'tk 11/26/2004 05:51'! convertToCurrentVersion: varDict refStream: smartRefStrm | newish | newish _ super convertToCurrentVersion: varDict refStream: smartRefStrm. "major change - much of AlignmentMorph is now implemented more generally in Morph" varDict at: 'hResizing' ifPresent: [ :x | ^ newish convertOldAlignmentsNov2000: varDict using: smartRefStrm]. ^ newish ! ! !AlignmentMorph methodsFor: 'visual properties' stamp: 'sw 11/5/2001 15:11'! canHaveFillStyles "Return true if the receiver can have general fill styles; not just colors. This method is for gradually converting old morphs." ^ self class == AlignmentMorph "no subclasses"! ! !AlignmentMorph methodsFor: '*eToys-e-toy support' stamp: 'panda 4/25/2000 15:44'! configureForKids self disableDragNDrop. super configureForKids ! ! !AlignmentMorph methodsFor: '*eToys-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: '*MorphicExtras-initialization' stamp: 'dgd 2/14/2003 22:02'! basicInitialize "Do basic generic initialization of the instance variables" super basicInitialize. "" self layoutPolicy: TableLayout new; listDirection: #leftToRight; wrapCentering: #topLeft; hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 2; rubberBandCells: true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AlignmentMorph class instanceVariableNames: ''! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'dgd 9/20/2003 19:05'! columnPrototype "Answer a prototypical column" | sampleMorphs aColumn | sampleMorphs _ #(red yellow green) collect: [:aColor | Morph new extent: 130 @ 38; color: (Color perform: aColor); setNameTo: aColor asString; yourself]. aColumn _ self inAColumn: sampleMorphs. aColumn setNameTo: 'Column'. aColumn color: Color veryVeryLightGray. aColumn cellInset: 4; layoutInset: 6. aColumn enableDragNDrop. aColumn setBalloonText: 'Things dropped into here will automatically be organized into a column. Once you have added your own items here, you will want to remove the sample colored rectangles that this started with, and you will want to change this balloon help message to one of your own!!' translated. ^ aColumn! ! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'sw 11/2/2001 04:45'! inAColumn: aCollectionOfMorphs "Answer a columnar AlignmentMorph holding the given collection" | col | col _ self newColumn color: Color transparent; vResizing: #shrinkWrap; hResizing: #shrinkWrap; layoutInset: 1; borderColor: Color black; borderWidth: 1; wrapCentering: #center; cellPositioning: #topCenter. aCollectionOfMorphs do: [:each | col addMorphBack: each]. ^ col! ! !AlignmentMorph class methodsFor: 'instance creation' stamp: 'sw 11/5/2001 15:11'! inARow: aCollectionOfMorphs "Answer a row-oriented AlignmentMorph holding the given collection" | aRow | aRow _ self newRow color: Color transparent; vResizing: #shrinkWrap; hResizing: #shrinkWrap; layoutInset: 1; borderColor: Color black; borderWidth: 1; wrapCentering: #center; cellPositioning: #topCenter. aCollectionOfMorphs do: [ :each | aRow addMorphBack: each]. ^ aRow! ! !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 class methodsFor: 'instance creation' stamp: 'dgd 9/20/2003 19:05'! rowPrototype "Answer a prototypical row" | sampleMorphs aRow | sampleMorphs _ (1 to: (2 + 3 atRandom)) collect: [:integer | EllipseMorph new extent: ((60 + (20 atRandom)) @ (80 + ((20 atRandom)))); color: Color random; setNameTo: ('egg', integer asString); yourself]. aRow _ self inARow: sampleMorphs. aRow setNameTo: 'Row'. aRow enableDragNDrop. aRow cellInset: 6. aRow layoutInset: 8. aRow setBalloonText: 'Things dropped into here will automatically be organized into a row. Once you have added your own items here, you will want to remove the sample colored eggs that this started with, and you will want to change this balloon help message to one of your own!!' translated. aRow color: Color veryVeryLightGray. ^ aRow "AlignmentMorph rowPrototype openInHand"! ! !AlignmentMorph class methodsFor: 'scripting' stamp: 'sw 11/16/2004 00:44'! defaultNameStemForInstances "The code just below, now commented out, resulted in every instance of every sublcass of AlignmentMorph being given a default name of the form 'Alignment1', rather than the desired 'MoviePlayer1', 'ScriptEditor2', etc." "^ 'Alignment'" ^ super defaultNameStemForInstances! ! !AlignmentMorph class methodsFor: '*eToys-scripting' stamp: 'sw 11/16/2001 10:01'! additionsToViewerCategories "Answer viewer additions for the 'layout' category" ^#(( layout ( (slot cellInset 'The cell inset' Number readWrite Player getCellInset Player setCellInset:) (slot layoutInset 'The layout inset' Number readWrite Player getLayoutInset Player setLayoutInset:) (slot listCentering 'The list centering' ListCentering readWrite Player getListCentering Player setListCentering:) (slot hResizing 'Horizontal resizing' Resizing readWrite Player getHResizing Player setHResizing:) (slot vResizing 'Vertical resizing' Resizing readWrite Player getVResizing Player setVResizing:) (slot listDirection 'List direction' ListDirection readWrite Player getListDirection Player setListDirection:) (slot wrapDirection 'Wrap direction' ListDirection readWrite Player getWrapDirection Player setWrapDirection:) ))) ! ! !AlignmentMorph class methodsFor: '*MorphicExtras-parts bin' stamp: 'sw 11/16/2001 09:16'! supplementaryPartsDescriptions "Extra items for parts bins" ^ {DescriptionForPartsBin formalName: 'Column' categoryList: #('Presentation') documentation: 'An object that presents the things within it in a column' globalReceiverSymbol: #AlignmentMorph nativitySelector: #columnPrototype. DescriptionForPartsBin formalName: 'Row' categoryList: #('Presentation') documentation: 'An object that presents the things within it in a row' globalReceiverSymbol: #AlignmentMorph nativitySelector: #rowPrototype}! ! AlignmentMorph subclass: #AlignmentMorphBob1 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-AdditionalSupport'! !AlignmentMorphBob1 commentStamp: '' prior: 0! A quick and easy to space things vertically in absolute or proportional amounts.! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'RAA 3/10/2001 13:54'! acceptDroppingMorph: aMorph event: evt | handlerForDrops | handlerForDrops _ self valueOfProperty: #handlerForDrops ifAbsent: [ ^super acceptDroppingMorph: aMorph event: evt ]. (handlerForDrops acceptDroppingMorph: aMorph event: evt in: self) ifFalse: [ aMorph rejectDropMorphEvent: evt. "send it back where it came from" ].! ! !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: 'dgd 11/7/2004 19:52'! addARowCentered: aCollectionOfMorphs cellInset: cellInsetInteger ^(self addARow: aCollectionOfMorphs) hResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter; cellInset: cellInsetInteger! ! !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: 'dgd 11/3/2004 19:28'! fancyText: aString font: aFont color: aColor | answer tm col | col := ColorTheme current dialog3DTitles ifTrue: [aColor] ifFalse: [aColor negated]. tm := TextMorph new. tm beAllFont: aFont; color: col; contents: aString. answer := self inAColumn: {tm}. ColorTheme current dialog3DTitles ifTrue: ["" tm addDropShadow. tm shadowPoint: 5 @ 5 + tm bounds center]. tm lock. ^ answer! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'ar 12/30/2001 19:14'! fullDrawOn: aCanvas | mask | (aCanvas isVisible: self fullBounds) ifFalse:[^self]. super fullDrawOn: aCanvas. mask _ self valueOfProperty: #disabledMaskColor ifAbsent: [^self]. aCanvas fillRectangle: bounds color: mask. ! ! !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: 'dgd 11/2/2004 21:54'! inARightColumn: aCollectionOfMorphs | col | col := AlignmentMorph newColumn color: Color transparent; vResizing: #shrinkWrap; layoutInset: 1; wrapCentering: #bottomRight; cellPositioning: #topCenter. aCollectionOfMorphs do: [:each | col addMorphBack: each]. ^ col! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'dgd 4/4/2006 16:16'! inARow: aCollectionOfMorphs | row | row := AlignmentMorph newRow color: Color transparent; vResizing: #shrinkWrap; layoutInset: 2; wrapCentering: #center; cellPositioning: #leftCenter. aCollectionOfMorphs do: [:each | row addMorphBack: each]. ^ row! ! !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 ! ! !AlignmentMorphBob1 methodsFor: 'as yet unclassified' stamp: 'RAA 3/14/2001 08:36'! wantsDroppedMorph: aMorph event: evt | handlerForDrops | handlerForDrops _ self valueOfProperty: #handlerForDrops ifAbsent: [ ^super wantsDroppedMorph: aMorph event: evt ]. ^handlerForDrops wantsDroppedMorph: aMorph event: evt in: self! ! !AlignmentMorphBob1 methodsFor: 'initialization' stamp: 'dgd 4/4/2006 16:17'! initialize super initialize. self listDirection: #topToBottom. self layoutInset: 0. self hResizing: #rigid. "... this is very unlikely..." self vResizing: #rigid! ! AlignmentMorph subclass: #AllPlayersTool instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting'! !AllPlayersTool commentStamp: '' prior: 0! A tool that lets you see find, view, and obtain tiles for all the active players in the project.! !AllPlayersTool methodsFor: 'initialization' stamp: 'sw 12/8/2004 11:12'! addHeaderRow "Add the header morph at the top of the tool" | aRow title aButton | aRow _ AlignmentMorph newRow. aRow listCentering: #justified; color: Color transparent. aButton _ self tanOButton. aButton actionSelector: #delete. aRow addMorphFront: aButton. aRow addMorphBack: (title _ StringMorph contents: 'Gallery of Players' translated). title setBalloonText: 'Double-click here to refresh the contents' translated. title on: #doubleClick send: #reinvigorate to: self. aRow addMorphBack: self helpButton. self addMorphFront: aRow. ! ! !AllPlayersTool methodsFor: 'initialization' stamp: 'sw 7/28/2004 20:48'! 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 scope of the containing pasteup's presenter" | placeHolder | self color: Color brown muchLighter muchLighter; wrapCentering: #center; cellPositioning: #topCenter; vResizing: #shrinkWrap; hResizing: #shrinkWrap. self useRoundedCorners. self borderStyle: BorderStyle complexAltInset; borderWidth: 4; borderColor: (Color r: 0.452 g: 0.839 b: 1.0). "Color fromUser" self addHeaderRow. placeHolder _ Morph new beTransparent. placeHolder extent: 200@1. self addMorphBack: placeHolder. ActiveWorld presenter reinvigoratePlayersTool: self ! ! !AllPlayersTool methodsFor: 'initialization' stamp: 'sw 7/28/2004 18:08'! initializeToStandAlone "Initialize the receiver" super initializeToStandAlone. self layoutPolicy: TableLayout new; listDirection: #topToBottom; hResizing: #spaceFill; extent: 1@1; vResizing: #spaceFill; rubberBandCells: true; yourself. self initializeFor: self currentWorld presenter! ! !AllPlayersTool methodsFor: 'menus' stamp: 'sw 7/28/2004 18:32'! addCustomMenuItems: aMenu hand: aHand "Add further items to the menu" aMenu add: 'reinvigorate' target: self action: #reinvigorate. Preferences eToyFriendly ifFalse: [aMenu add: 'inspect' target: self action: #inspect]! ! !AllPlayersTool methodsFor: 'menus' stamp: 'sw 7/28/2004 22:58'! presentHelp "Sent when a Help button is hit; provide the user with some form of help for the tool at hand" | aString aTextMorph | aString _ 'About the Gallery of Players Click on an object''s picture to reveal its location. Click on the turquoise eye to open the object''s viewer. Click on an object''s name to obtain a tile representing the object. Double-click on the title ("Gallery of Players") to refresh the tool; this may allow you to see newly-added or newly-scripted objects.'. aTextMorph _ TextMorph new contents: aString translated. aTextMorph useRoundedCorners; borderWidth: 3; borderColor: Color gray; margins: 3@3. aTextMorph backgroundColor: Color blue muchLighter. aTextMorph beAllFont: (StrikeFont familyName: #ComicBold size: 18); centered; lock. AlignmentMorph new beTransparent hResizing: #shrinkWrap; vResizing: #shrinkWrap; addMorphBack: aTextMorph; openInHand! ! !AllPlayersTool methodsFor: 'reinvigoration' stamp: 'sw 7/19/2004 16:37'! invigorateButton "Answer a button that triggers reinvigoration" | aButton | aButton _ IconicButton new target: self; borderWidth: 0; labelGraphic: (ScriptingSystem formAtKey: #Refresh); color: Color transparent; actWhen: #buttonUp; actionSelector: #reinvigorate; yourself. aButton setBalloonText: 'Click here to refresh the list of players'. ^ aButton ! ! !AllPlayersTool methodsFor: 'reinvigoration' stamp: 'sw 7/19/2004 16:17'! menuButton "Answer a button that brings up a menu. Useful when adding new features, but at present is between uses" | aButton | aButton _ IconicButton new target: self; borderWidth: 0; labelGraphic: (ScriptingSystem formAtKey: #TinyMenu); color: Color transparent; actWhen: #buttonDown; actionSelector: #offerMenu; yourself. aButton setBalloonText: 'click here to get a menu with further options'. ^ aButton ! ! !AllPlayersTool methodsFor: 'reinvigoration' stamp: 'sw 7/28/2004 18:08'! reinvigorate "Referesh the contents of the receiver" (submorphs copyFrom: 3 to: submorphs size) do: [:m | m delete]. ActiveWorld doOneCycleNow. self playSoundNamed: 'scritch'. (Delay forMilliseconds: 700) wait. ActiveWorld presenter reinvigoratePlayersTool: self. self playSoundNamed: 'scratch'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AllPlayersTool class instanceVariableNames: ''! !AllPlayersTool class methodsFor: 'instance-creation defaults' stamp: 'sw 7/19/2004 10:38'! defaultNameStemForInstances "Answer the default name stem for new instances of this class" ^ 'Players'! ! !AllPlayersTool class methodsFor: 'parts bin' stamp: 'sw 7/19/2004 10:37'! descriptionForPartsBin "Answer a description for use in parts bins" ^ self partName: 'Players' categories: #('Scripting') documentation: 'A tool showing all the players in your project'! ! AlignmentMorph subclass: #AllScriptsTool instanceVariableNames: 'showingOnlyActiveScripts showingAllInstances showingOnlyTopControls' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting'! !AllScriptsTool commentStamp: '' prior: 0! A tool for controlling and viewing all scripts in a project. The tool has an open and a closed form. In the closed form, stop-step-go buttons are available, plus a control for opening the tool up. In the open form, it has a second row of controls that govern which scripts should be shown, followed by the individual script items.! !AllScriptsTool methodsFor: 'initialization' stamp: 'dgd 9/19/2003 14:34'! addSecondLineOfControls "Add the second line of controls" | aRow outerButton aButton worldToUse | aRow _ AlignmentMorph newRow listCentering: #center; color: Color transparent. 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 addTransparentSpacerOfSize: (4@0). outerButton addMorphBack: (StringMorph contents: 'tickers only' translated) lock. outerButton setBalloonText: 'If checked, then only scripts that are paused or ticking will be shown' translated. aRow addMorphBack: outerButton. aRow addTransparentSpacerOfSize: 20@0. aRow addMorphBack: self helpButton. aRow addTransparentSpacerOfSize: 20@0. 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 addTransparentSpacerOfSize: (4@0). outerButton addMorphBack: (StringMorph contents: 'all instances' translated) lock. outerButton setBalloonText: 'If checked, then entries for all instances will be shown, but if not checked, scripts for only one representative of each different kind of object will be shown. Consult the help available by clicking on the purple ? for more information.' translated. aRow addMorphBack: outerButton. self addMorphBack: aRow. worldToUse _ self isInWorld ifTrue: [self world] ifFalse: [ActiveWorld]. worldToUse presenter reinvigorateAllScriptsTool: self. self layoutChanged.! ! !AllScriptsTool methodsFor: 'initialization' stamp: 'dgd 8/31/2003 19:43'! dismissButton "Answer a button whose action would be to dismiss the receiver " | aButton | aButton := super dismissButton. aButton setBalloonText: 'Click here to remove this tool from the screen; you can get another one any time you want from the Widgets flap' translated. ^ aButton! ! !AllScriptsTool methodsFor: 'initialization' stamp: 'sw 12/8/2004 11:26'! initializeFor: ignored "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 scope of the containing pasteup's presenter" | aRow aButton | showingOnlyActiveScripts _ true. showingAllInstances _ true. showingOnlyTopControls _ true. self color: Color brown muchLighter muchLighter; wrapCentering: #center; cellPositioning: #topCenter; vResizing: #shrinkWrap; hResizing: #shrinkWrap. self useRoundedCorners. self borderWidth: 4; borderColor: Color brown darker. aRow _ AlignmentMorph newRow. aRow listCentering: #justified; color: Color transparent. aButton _ self tanOButton. aButton actionSelector: #delete. aRow addMorphFront: aButton. aRow addMorphBack: ScriptingSystem scriptControlButtons. aRow addMorphBack: self openUpButton. self addMorphFront: aRow. ! ! !AllScriptsTool methodsFor: 'initialization' stamp: 'dgd 9/19/2003 14:35'! presentHelp "Sent when a Help button is hit; provide the user with some form of help for the tool at hand" | aString | aString _ 'This tool allows you to see all the scripts for all the objects in this project. Sometimes you are only interested in those scripts that are ticking, or that are *ready* to tick when you hit the GO button (which are said to be "paused.") Check "tickers only" if you only want to see such scripts -- i.e., scripts that are either paused or ticking. If "tickers only" is *not* checked, then all scripts will be shown, whatever their status. The other checkbox, labeled "all instances", only comes into play if you have created "multiple sibling instances" (good grief) of the same object, which share the same scripts; if you have such things, it is often convenient to see the scripts of just *one* such sibling, because it will take up less space and require less mindshare -- and note that you can control a script for an object *and* all its siblings from the menu of that one that you see, via menu items such as "propagate status to siblings". If "all instances" is checked, scripts for all sibling instances will be shown, whereas if "all instances" is *not* checked, only one of each group of siblings will be selected to have its scripts shown. But how do you get "multiple sibling instances" of the same object? There are several ways: (1) Use the "make a sibling instance" or the "make multiple siblings..." menu item in the halo menu of a scripted object (2) Use the "copy" tile in a script. (3) Request "give me a copy now" from the menu associated with the "copy" item in a Viewer If you have on your screen multiple sibling instances of the same object, then you may or may want to see them all in the All Scripts tool, and that is what the "all instances" checkbox governs. Set "all instances" if you want a separate entry for each instance, as opposed to a single representative of that kind of object. Note that if you obtain a copy of an object by using the green halo handle, it will *not* be a sibling instance of the original. It will in many ways seem to be, because it will start out its life having the same scripts as the original. But it will then lead an independent life, so that changes to scripts of the original will not be reflected in it, and vice-versa. This is an important distinction, and an unavoidable one because people sometimes want the deep sharing of sibling instances and sometimes they clearly do not. But the truly understandable description of these concepts and distinctions certainly lies *ahead* of us!!'. (StringHolder new contents: aString translated) openLabel: 'About the All Scripts tool' translated! ! !AllScriptsTool methodsFor: 'parts bin' stamp: 'dgd 2/22/2003 19:37'! initializeToStandAlone super initializeToStandAlone. self layoutPolicy: TableLayout new; listDirection: #topToBottom; hResizing: #spaceFill; extent: 1 @ 1; vResizing: #spaceFill; rubberBandCells: true. self initializeFor: self currentWorld presenter! ! !AllScriptsTool methodsFor: 'stepping and presenter' stamp: 'sw 11/14/2001 00:31'! step "If the list of scripts to show has changed, refresh my contents" self showingOnlyTopControls ifFalse: [self presenter reinvigorateAllScriptsTool: self].! ! !AllScriptsTool methodsFor: 'testing' stamp: 'sw 1/31/2001 23:12'! stepTime "Answer the interval between steps -- in this case a leisurely 4 seconds" ^ 4000! ! !AllScriptsTool methodsFor: 'testing' stamp: 'sw 1/31/2001 23:12'! wantsSteps "Answer whether the receiver wishes to receive the #step message" ^ true! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 12/8/2004 11:28'! openUpButton "Answer a button whose action would be to open up the receiver or snap it back closed" | aButton aForm | aButton _ IconicButton new borderWidth: 0. aForm _ ScriptingSystem formAtKey: #PowderBlueOpener. aForm ifNil: [aForm _ Form extent: 13@22 depth: 16 fromArray: #( 0 0 12017 787558129 0 0 0 0 12017 787561309 995965789 787558129 0 0 0 787561309 995965789 995965789 995965789 787546112 0 12017 995965789 995965789 995965789 995965789 995962609 0 12017 995965789 995965789 995965789 995965789 995962609 0 787561309 995965789 995965789 995965789 995965789 995965789 787546112 787561309 995965789 995965789 995965789 995965789 995965789 787546112 787561309 995965789 995950593 80733 995965789 995965789 787546112 787561309 995965789 65537 65537 80733 995965789 787546112 787561309 995950593 80733 995950593 80733 995965789 787546112 787561309 995950593 80733 995950593 80733 995965789 787546112 787561309 995950593 65537 65537 80733 995965789 787546112 787561309 995965789 65537 65537 995965789 995965789 787546112 787561309 995965789 995965789 995965789 995965789 995965789 787546112 787561309 995965789 995965789 995965789 995965789 995965789 787546112 787561309 995965789 995965789 995965789 995965789 995965789 787546112 787561309 995965789 995965789 995965789 995965789 995965789 787546112 12017 995965789 995965789 995965789 995965789 995962609 0 12017 995965789 995965789 995965789 995965789 995962609 0 0 787561309 995965789 995965789 995965789 787546112 0 0 12017 787561309 995965789 787558129 0 0 0 0 12017 787558129 0 0 0) offset: 0@0. ScriptingSystem saveForm: aForm atKey: #PowderBlueOpener]. aButton labelGraphic: aForm. aButton target: self; color: Color transparent; actionSelector: #toggleWhetherShowingOnlyTopControls; setBalloonText: 'open or close the lower portion that shows individual scripts' translated. ^ aButton! ! !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 11/13/2001 19:43'! showingOnlyTopControls "Answer whether the receiver is currently showing only the top controls" ^ showingOnlyTopControls ifNil: [showingOnlyTopControls _ true]! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 11/14/2001 00:32'! toggleWhetherShowingAllInstances "Toggle whether the receiver is showing all instances or only one exemplar per uniclass" showingAllInstances _ showingAllInstances not. self presenter reinvigorateAllScriptsTool: self! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 11/14/2001 00:32'! toggleWhetherShowingOnlyActiveScripts "Toggle whether the receiver is showing only active scripts" showingOnlyActiveScripts _ showingOnlyActiveScripts not. self presenter reinvigorateAllScriptsTool: self! ! !AllScriptsTool methodsFor: 'toggles' stamp: 'sw 11/14/2001 00:32'! toggleWhetherShowingOnlyTopControls "Toggle whether the receiver is showing only the stop/step/go line or the full whammy" | aCenter | showingOnlyTopControls _ self showingOnlyTopControls not. aCenter _ self center x. self showingOnlyTopControls ifTrue: [self removeAllButFirstSubmorph] ifFalse: [self addSecondLineOfControls. self presenter reinvigorateAllScriptsTool: self]. WorldState addDeferredUIMessage: [self center: (aCenter @ self center y)] ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AllScriptsTool class instanceVariableNames: ''! !AllScriptsTool class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 10:28'! initialize self registerInFlapsRegistry. ! ! !AllScriptsTool class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 10:30'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you see and control all the running scripts in your project') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you control all the running scripts in your world') forFlapNamed: 'Scripting'. cl registerQuad: #(AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you see and control all the running scripts in your project') forFlapNamed: 'Widgets']! ! !AllScriptsTool class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 12:30'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !AllScriptsTool class methodsFor: 'instance creation' stamp: 'sw 6/12/2001 11:52'! allScriptsToolForActiveWorld "Launch an AllScriptsTool to view scripts of the active world" | aTool | aTool _ self newColumn. aTool initializeFor: ActiveWorld presenter. ^ aTool! ! !AllScriptsTool class methodsFor: 'instance creation' 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 ! ! !AllScriptsTool class methodsFor: 'parts bin' stamp: 'sw 11/13/2001 18:31'! descriptionForPartsBin "Answer a description for use in parts bins" ^ self partName: 'All Scripts' categories: #('Scripting') documentation: 'A tool allowing you to monitor and change the status of all scripts in your project'! ! !AllScriptsTool class methodsFor: 'printing' stamp: 'sw 11/13/2001 19:44'! defaultNameStemForInstances "Answer the default name stem for new instances of this class" ^ 'All Scripts'! ! ColorMappingCanvas subclass: #AlphaBlendingCanvas instanceVariableNames: 'alpha' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !AlphaBlendingCanvas methodsFor: 'accessing' stamp: 'ar 8/8/2001 14:24'! alpha ^alpha! ! !AlphaBlendingCanvas methodsFor: 'accessing' stamp: 'ar 8/8/2001 14:24'! alpha: newAlpha alpha _ newAlpha.! ! !AlphaBlendingCanvas methodsFor: 'initialization' stamp: 'ar 8/8/2001 14:18'! on: aCanvas myCanvas _ aCanvas. alpha _ 1.0.! ! !AlphaBlendingCanvas methodsFor: 'private' stamp: 'bf 10/28/2003 15:46'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Draw the given form. For the 'paint' combination rule use stenciling otherwise simply fill the source rectangle." rule = Form paint ifTrue:[ ^myCanvas image: aForm at: aPoint sourceRect: sourceRect rule: Form paintAlpha alpha: alpha. ]. rule = Form over ifTrue:[ ^myCanvas image: aForm at: aPoint sourceRect: sourceRect rule: Form blendAlpha alpha: alpha. ].! ! !AlphaBlendingCanvas methodsFor: 'private' stamp: 'ar 8/8/2001 14:23'! mapColor: aColor aColor isColor ifFalse:[^aColor]. "Should not happen but who knows..." aColor isTransparent ifTrue:[^aColor]. aColor isOpaque ifTrue:[^aColor alpha: alpha]. ^aColor alpha: (aColor alpha * alpha)! ! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:40'! target: t selector: s arguments: a target := t. selector := s. arguments := a. ! ! GIFReadWriter subclass: #AnimatedGIFReadWriter instanceVariableNames: 'forms delays comments' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Files'! !AnimatedGIFReadWriter methodsFor: 'accessing' stamp: 'bf 2/25/2005 11:11'! allImages | body colorTable | stream class == ReadWriteStream ifFalse: [ stream binary. self on: (ReadWriteStream with: (stream contentsOfEntireFile))]. localColorTable _ nil. forms _ OrderedCollection new. delays _ OrderedCollection new. comments _ OrderedCollection new. self readHeader. [(body _ self readBody) == nil] whileFalse: [colorTable _ localColorTable ifNil: [colorPalette]. transparentIndex ifNotNil: [transparentIndex + 1 > colorTable size ifTrue: [colorTable _ colorTable forceTo: transparentIndex + 1 paddingWith: Color white]. colorTable at: transparentIndex + 1 put: Color transparent]. body colors: colorTable. forms add: body. delays add: delay]. ^ forms! ! !AnimatedGIFReadWriter methodsFor: 'accessing' stamp: 'mir 11/19/2003 14:16'! delays ^ delays! ! !AnimatedGIFReadWriter methodsFor: 'accessing' stamp: 'mir 11/19/2003 14:16'! forms ^ forms! ! !AnimatedGIFReadWriter methodsFor: 'private' stamp: 'mir 11/19/2003 12:25'! comment: aString comments add: aString! ! !AnimatedGIFReadWriter methodsFor: 'private-decoding' stamp: 'mir 11/19/2003 12:21'! readBitData | form | form := super readBitData. form offset: offset. ^form! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AnimatedGIFReadWriter class instanceVariableNames: ''! !AnimatedGIFReadWriter class methodsFor: 'image reading/writing' stamp: 'mir 11/18/2003 17:00'! formsFromFileNamed: fileName | stream | stream _ FileStream readOnlyFileNamed: fileName. ^ self formsFromStream: stream! ! !AnimatedGIFReadWriter class methodsFor: 'image reading/writing' stamp: 'mir 11/18/2003 17:00'! formsFromStream: stream | reader | reader _ self new on: stream reset. Cursor read showWhile: [reader allImages. reader close]. ^reader! ! !AnimatedGIFReadWriter class methodsFor: 'image reading/writing' stamp: 'nk 6/12/2004 13:12'! typicalFileExtensions "Answer a collection of file extensions (lowercase) which files that I can read might commonly have" ^#('gif')! ! !AnimatedGIFReadWriter class methodsFor: 'image reading/writing' stamp: 'asm 12/11/2003 21:29'! wantsToHandleGIFs ^true! ! ImageMorph subclass: #AnimatedImageMorph instanceVariableNames: 'images delays stepTime nextTime imageIndex' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-AdditionalMorphs'! !AnimatedImageMorph commentStamp: '' prior: 0! I am an ImageMorph that can hold more than one image. Each image has its own delay time.! !AnimatedImageMorph methodsFor: 'stepping and presenter' stamp: 'bf 2/25/2005 11:06'! step | f d | images isEmpty ifTrue: [^ self]. nextTime > Time millisecondClockValue ifTrue: [^self]. imageIndex _ imageIndex \\ images size + 1. f _ images at: imageIndex. f displayOn: self image at: 0@0 rule: Form paint. self invalidRect: (self position + f offset extent: f extent). d _ (delays at: imageIndex) ifNil: [0]. nextTime := Time millisecondClockValue + d ! ! !AnimatedImageMorph methodsFor: 'stepping and presenter' stamp: 'mir 11/19/2003 13:40'! stepTime ^stepTime ifNil: [super stepTime]! ! !AnimatedImageMorph methodsFor: 'stepping and presenter' stamp: 'mir 11/19/2003 13:40'! stepTime: anInteger stepTime _ anInteger! ! !AnimatedImageMorph methodsFor: 'stepping and presenter' stamp: 'asm 12/15/2003 19:44'! wantsSteps ^(images size > 1) ! ! !AnimatedImageMorph methodsFor: 'private' stamp: 'nk 2/15/2004 15:20'! fromGIFFileNamed: fileName self fromReader: (AnimatedGIFReadWriter formsFromFileNamed: fileName)! ! !AnimatedImageMorph methodsFor: 'private' stamp: 'bf 2/25/2005 11:18'! fromReader: reader images _ reader forms. delays _ reader delays. imageIndex _ 0. self image: (Form extent: images first extent depth: 32). self step! ! !AnimatedImageMorph methodsFor: 'private' stamp: 'nk 2/15/2004 15:20'! fromStream: aStream self fromReader: (AnimatedGIFReadWriter formsFromStream: aStream)! ! !AnimatedImageMorph methodsFor: 'private' stamp: 'mir 11/19/2003 13:42'! images ^images! ! !AnimatedImageMorph methodsFor: 'private' stamp: 'bf 2/25/2005 11:09'! initialize nextTime := Time millisecondClockValue. imageIndex := 1. stepTime := 10. super initialize! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AnimatedImageMorph class instanceVariableNames: ''! !AnimatedImageMorph class methodsFor: 'class initialization' stamp: 'asm 12/11/2003 21:05'! initialize "register the receiver in the global registries" self environment at: #FileList ifPresent: [:cl | cl registerFileReader: self]! ! !AnimatedImageMorph class methodsFor: 'class initialization' stamp: 'asm 12/11/2003 21:01'! unload "Unload the receiver from global registries" self environment at: #FileList ifPresent: [:cl | cl unregisterFileReader: self]! ! !AnimatedImageMorph class methodsFor: 'fileIn/Out' stamp: 'nk 6/12/2004 13:11'! fileReaderServicesForFile: fullName suffix: suffix ^((AnimatedGIFReadWriter typicalFileExtensions asSet add: '*'; add: 'form'; yourself) includes: suffix) ifTrue: [ self services ] ifFalse: [#()] ! ! !AnimatedImageMorph class methodsFor: 'fileIn/Out' stamp: 'dgd 4/3/2006 13:36'! serviceOpenGIFInWindow "Answer a service for opening a gif graphic in a window" ^ (SimpleServiceEntry provider: self label: 'open the graphic as a morph' selector: #openGIFInWindow: description: 'open a GIF graphic file as a morph' buttonLabel: 'open') argumentGetter: [:fileList | fileList readOnlyStream]! ! !AnimatedImageMorph class methodsFor: 'fileIn/Out' stamp: 'dgd 4/3/2006 13:36'! services ^ Array with: self serviceOpenGIFInWindow "with: Form serviceImageImports" with: Form serviceImageAsBackground! ! !AnimatedImageMorph class methodsFor: 'instance creation' stamp: 'nk 2/15/2004 15:23'! fromGIFFileNamed: fileName | reader | reader _ AnimatedGIFReadWriter formsFromFileNamed: fileName. ^reader forms size = 1 ifTrue: [ ImageMorph new image: reader forms first ] ifFalse: [ self new fromReader: reader ]! ! !AnimatedImageMorph class methodsFor: 'instance creation' stamp: 'nk 2/15/2004 15:27'! fromStream: aStream | reader | reader _ AnimatedGIFReadWriter formsFromStream: aStream. ^reader forms size = 1 ifTrue: [ ImageMorph new image: reader forms first ] ifFalse: [ self new fromReader: reader ]! ! !AnimatedImageMorph class methodsFor: 'instance creation' stamp: 'nk 2/15/2004 16:57'! openGIFInWindow: aStream ^(self fromStream: aStream binary) openInWorld! ! StarSqueakTurtle subclass: #AntColonyTurtle instanceVariableNames: 'isCarryingFood pheromoneDropSize' classVariableNames: '' poolDictionaries: '' category: 'StarSqueak-Worlds'! !AntColonyTurtle methodsFor: 'demons' stamp: 'sd 11/20/2005 21:26'! dropFoodInNest (isCarryingFood and: [(self get: 'isNest') > 0]) ifTrue: [ self color: Color black. isCarryingFood := false. "turn around and go forward to try to pick up pheromone trail" self turnRight: 180. self forward: 3]. ! ! !AntColonyTurtle methodsFor: 'demons' stamp: 'sd 11/20/2005 21:26'! pickUpFood | newFood | (isCarryingFood not and: [(self get: 'food') > 0]) ifTrue: [ newFood := (self get: 'food') - 1. self set: 'food' to: newFood. newFood = 0 ifTrue: [self patchColor: world backgroundColor]. isCarryingFood := true. pheromoneDropSize := 800. self color: Color red. "drop a blob of pheromone on the side of the food farthest from nest" self turnTowardsStrongest: 'nestScent'. self turnRight: 180. self forward: 4. self increment: 'pheromone' by: 5000]. ! ! !AntColonyTurtle methodsFor: 'demons' stamp: 'sd 11/20/2005 21:26'! returnToNest isCarryingFood ifTrue: [ "decrease size of pheromone drops to create a gradient back to food" pheromoneDropSize > 0 ifTrue: [ self increment: 'pheromone' by: pheromoneDropSize. pheromoneDropSize := pheromoneDropSize - 20]. self turnTowardsStrongest: 'nestScent'. self forward: 1]. ! ! !AntColonyTurtle methodsFor: 'demons' stamp: 'jm 2/7/2001 08:12'! searchForFood "If you smell pheromone, go towards the strongest smell. Otherwise, wander aimlessly." isCarryingFood ifFalse: [ ((self get: 'pheromone') > 1) ifTrue: [self turnTowardsStrongest: 'pheromone'] ifFalse: [ self turnRight: (self random: 40). self turnLeft: (self random: 40)]. self forward: 1]. ! ! !AntColonyTurtle methodsFor: 'variables' stamp: 'jm 2/26/2000 10:24'! isCarryingFood ^ isCarryingFood ! ! !AntColonyTurtle methodsFor: 'variables' stamp: 'sd 11/20/2005 21:26'! isCarryingFood: aBoolean isCarryingFood := aBoolean. ! ! !AntColonyTurtle methodsFor: 'variables' stamp: 'jm 2/26/2000 10:24'! pheromoneDropSize ^ pheromoneDropSize ! ! !AntColonyTurtle methodsFor: 'variables' stamp: 'sd 11/20/2005 21:26'! pheromoneDropSize: aNumber pheromoneDropSize := aNumber. ! ! Object subclass: #AppRegistry instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Applications'! !AppRegistry commentStamp: 'ads 4/2/2003 15:30' prior: 0! AppRegistry is a simple little class, not much more than a wrapper around a collection. It's intended to help break dependencies between packages. For example, if you'd like to be able to send e-mail, you could use the bare-bones MailComposition class, or you could use the full-blown Celeste e-mail client. Instead of choosing one or the other, you can call "MailSender default" (where MailSender is a subclass of AppRegistry), and thus avoid creating a hard-coded dependency on either of the two mail senders. This will only really be useful, of course, for applications that have a very simple, general, well-defined interface. Most of the time, you're probably better off just marking your package as being dependent on a specific other package, and avoiding the hassle of this whole AppRegistry thing. But for simple things like e-mail senders or web browsers, it might be useful. ! !AppRegistry methodsFor: 'notes' stamp: 'ads 4/2/2003 15:04'! seeClassSide "All the code for AppRegistry is on the class side."! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AppRegistry class instanceVariableNames: 'registeredClasses default'! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ads 3/29/2003 13:36'! appName "Defaults to the class name, which is probably good enough, but you could override this in subclasses if you want to." ^ self name! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'md 12/1/2004 23:58'! askForDefault | menu | self registeredClasses isEmpty ifTrue: [self inform: 'There are no ', self appName, ' applications registered.'. ^ default _ nil]. self registeredClasses size = 1 ifTrue: [^ default _ self registeredClasses anyOne]. menu _ CustomMenu new. self registeredClasses do: [:c | menu add: c name printString action: c]. default _ menu startUpWithCaption: 'Which ', self appName, ' would you prefer?'. default ifNil: [default := self registeredClasses first]. ^default.! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ads 3/29/2003 13:11'! default ^ default ifNil: [self askForDefault]! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'nk 3/9/2004 12:33'! default: aClassOrNil "Sets my default to aClassOrNil. Answers the old default." | oldDefault | oldDefault := default. aClassOrNil ifNotNil: [ self register: aClassOrNil ]. default := aClassOrNil. ^ oldDefault! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'nk 3/9/2004 12:35'! defaultOrNil ^ default! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ads 4/2/2003 15:25'! register: aProviderClass (self registeredClasses includes: aProviderClass) ifFalse: [default _ nil. "so it'll ask for a new default, since if you're registering a new app you probably want to use it" self registeredClasses add: aProviderClass].! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ads 3/29/2003 13:01'! registeredClasses ^ registeredClasses ifNil: [registeredClasses _ OrderedCollection new]! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ar 9/27/2005 21:48'! removeObsolete "AppRegistry removeObsoleteClasses" self registeredClasses copy do:[:cls| (cls class isObsolete or:[cls isBehavior and:[cls isObsolete]]) ifTrue:[self unregister: cls]]. self subclasses do:[:cls| cls removeObsolete].! ! !AppRegistry class methodsFor: 'as yet unclassified' stamp: 'ads 3/29/2003 13:03'! unregister: aProviderClass (default = aProviderClass) ifTrue: [default _ nil]. self registeredClasses remove: aProviderClass ifAbsent: [].! ! ObjectSocket subclass: #ArbitraryObjectSocket instanceVariableNames: 'encodingOfLastEncodedObject lastEncodedObject' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Network-ObjectSocket'! !ArbitraryObjectSocket commentStamp: '' prior: 0! A network connection that passes objects instead of bytes. The objects are encoded with SmartRefStreams. ! !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 2/10/2005 20:40'! inBufSize inBuf ifNil: [^0]. ^inBufLastIndex - inBufIndex + 1! ! !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: 'sd 11/20/2005 21:25'! processInput "recieve some data" | inObjectData | [ socket dataAvailable ] whileTrue: [ "read as much data as possible" self addToInBuf: socket receiveAvailableData. "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! ! TestCase subclass: #ArbitraryObjectSocketTestCase instanceVariableNames: 'socket1 socket2 end1 end2' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Network-ObjectSocket'! !ArbitraryObjectSocketTestCase methodsFor: 'setup' stamp: 'sd 11/20/2005 21:25'! setUp "it would be nice to have an in-image loopback socket, so that the tests do not need the underlying platform's sockets to behave nicely" socket1 := Socket newTCP. socket2 := Socket newTCP. socket1 listenOn: 9999. socket2 connectTo: (NetNameResolver localHostAddress) port: 9999. socket1 waitForConnectionFor: 60. socket2 waitForConnectionFor: 60. end1 := ArbitraryObjectSocket on: socket1. end2 := ArbitraryObjectSocket on: socket2. ! ! !ArbitraryObjectSocketTestCase methodsFor: 'testing' stamp: 'ls 2/10/2005 21:26'! testBasics end1 nextPut: 'hello'. end1 nextPut: 42. end1 nextPut: 3@5. end1 processIO. "hopefully one call is enough...." end2 processIO. "hopefully one call is enough...." self should: [ end2 next = 'hello' ]. self should: [ end2 next = 42 ]. self should: [ end2 next = (3@5) ]. ! ! 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"! ! Object subclass: #Archive instanceVariableNames: 'members' classVariableNames: '' poolDictionaries: '' category: 'Compression-Archives'! !Archive commentStamp: '' prior: 0! This is the abstract superclass for file archives. Archives can be read from or written to files, and contain members that represent files and directories.! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 19:09'! addDirectory: aFileName ^self addDirectory: aFileName as: aFileName ! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 14:57'! addDirectory: aFileName as: anotherFileName | newMember | newMember := self memberClass newFromDirectory: aFileName. self addMember: newMember. newMember localFileName: anotherFileName. ^newMember! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/21/2001 18:29'! addFile: aFileName ^self addFile: aFileName as: aFileName! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 15:03'! addFile: aFileName as: anotherFileName | newMember | newMember := self memberClass newFromFile: aFileName. self addMember: newMember. newMember localFileName: anotherFileName. ^newMember! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 19:09'! addMember: aMember ^members addLast: aMember! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 15:03'! addString: aString as: aFileName | newMember | newMember := self memberClass newFromString: aString named: aFileName. self addMember: newMember. newMember localFileName: aFileName. ^newMember! ! !Archive methodsFor: 'archive operations' stamp: 'tak 2/2/2005 13:22'! addTree: aFileNameOrDirectory match: aBlock | nameSize | nameSize := aFileNameOrDirectory isString ifTrue: [aFileNameOrDirectory size] ifFalse: [aFileNameOrDirectory pathName size]. ^ self addTree: aFileNameOrDirectory removingFirstCharacters: nameSize + 1 match: aBlock! ! !Archive methodsFor: 'archive operations' stamp: 'tak 2/2/2005 13:00'! addTree: aFileNameOrDirectory removingFirstCharacters: n ^ self addTree: aFileNameOrDirectory removingFirstCharacters: n match: [:e | true]! ! !Archive methodsFor: 'archive operations' stamp: 'tak 2/15/2005 11:27'! addTree: aFileNameOrDirectory removingFirstCharacters: n match: aBlock | dir newMember fullPath relativePath | dir := (aFileNameOrDirectory isString) ifTrue: [ FileDirectory on: aFileNameOrDirectory ] ifFalse: [ aFileNameOrDirectory ]. fullPath := dir pathName, dir slash. relativePath := fullPath copyFrom: n + 1 to: fullPath size. (dir entries select: [ :entry | aBlock value: entry]) do: [ :ea | | fullName | fullName := fullPath, ea name. newMember := ea isDirectory ifTrue: [ self memberClass newFromDirectory: fullName ] ifFalse: [ self memberClass newFromFile: fullName ]. newMember localFileName: relativePath, ea name. self addMember: newMember. ea isDirectory ifTrue: [ self addTree: fullName removingFirstCharacters: n match: aBlock]. ]. ! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/24/2001 14:12'! canWriteToFileNamed: aFileName "Catch attempts to overwrite existing zip file" ^(members anySatisfy: [ :ea | ea usesFileNamed: aFileName ]) not. ! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 07:57'! contentsOf: aMemberOrName | member | member := self member: aMemberOrName. member ifNil: [ ^nil ]. ^member contents! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 14:48'! extractMember: aMemberOrName | member | member := self member: aMemberOrName. member ifNil: [ ^nil ]. member extractToFileNamed: member localFileName inDirectory: FileDirectory default.! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 07:57'! extractMember: aMemberOrName toFileNamed: aFileName | member | member := self member: aMemberOrName. member ifNil: [ ^nil ]. member extractToFileNamed: aFileName! ! !Archive methodsFor: 'archive operations' stamp: 'nk 11/11/2002 14:09'! extractMemberWithoutPath: aMemberOrName self extractMemberWithoutPath: aMemberOrName inDirectory: FileDirectory default.! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 14:48'! extractMemberWithoutPath: aMemberOrName inDirectory: dir | member | member := self member: aMemberOrName. member ifNil: [ ^nil ]. member extractToFileNamed: (FileDirectory localNameFor: member localFileName) inDirectory: dir! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 14:50'! memberNamed: aString "Return the first member whose zip name or local file name matches aString, or nil" ^members detect: [ :ea | ea fileName = aString or: [ ea localFileName = aString ]] ifNone: [ ]! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/21/2001 18:00'! memberNames ^members collect: [ :ea | ea fileName ]! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/21/2001 17:58'! members ^members! ! !Archive methodsFor: 'archive operations' stamp: 'nk 12/20/2002 14:50'! membersMatching: aString ^members select: [ :ea | (aString match: ea fileName) or: [ aString match: ea localFileName ] ]! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/21/2001 17:59'! numberOfMembers ^members size! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 07:57'! removeMember: aMemberOrName | member | member := self member: aMemberOrName. member ifNotNil: [ members remove: member ]. ^member! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 07:57'! replaceMember: aMemberOrName with: newMember | member | member := self member: aMemberOrName. member ifNotNil: [ members replaceAll: member with: newMember ]. ^member! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/22/2001 17:24'! setContentsOf: aMemberOrName to: aString | newMember oldMember | oldMember := self member: aMemberOrName. newMember := (self memberClass newFromString: aString named: oldMember fileName) copyFrom: oldMember. self replaceMember: oldMember with: newMember.! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/21/2001 20:58'! writeTo: aStream self subclassResponsibility! ! !Archive methodsFor: 'archive operations' stamp: 'nk 2/24/2001 14:15'! writeToFileNamed: aFileName | stream | "Catch attempts to overwrite existing zip file" (self canWriteToFileNamed: aFileName) ifFalse: [ ^self error: (aFileName, ' is needed by one or more members in this archive') ]. stream := StandardFileStream forceNewFileNamed: aFileName. self writeTo: stream. stream close.! ! !Archive methodsFor: 'initialization' stamp: 'nk 2/21/2001 17:58'! initialize members := OrderedCollection new.! ! !Archive methodsFor: 'private' stamp: 'nk 2/22/2001 07:56'! member: aMemberOrName ^(members includes: aMemberOrName) ifTrue: [ aMemberOrName ] ifFalse: [ self memberNamed: aMemberOrName ].! ! !Archive methodsFor: 'private' stamp: 'nk 2/21/2001 18:14'! memberClass self subclassResponsibility! ! Object subclass: #ArchiveMember instanceVariableNames: 'fileName isCorrupt' classVariableNames: '' poolDictionaries: '' category: 'Compression-Archives'! !ArchiveMember commentStamp: '' prior: 0! This is the abstract superclass for archive members, which are files or directories stored in archives.! !ArchiveMember methodsFor: 'accessing' stamp: 'nk 2/22/2001 16:00'! fileName ^fileName! ! !ArchiveMember methodsFor: 'accessing' stamp: 'nk 2/22/2001 16:00'! fileName: aName fileName := aName! ! !ArchiveMember methodsFor: 'accessing' stamp: 'nk 3/7/2004 16:16'! isCorrupt ^isCorrupt ifNil: [ isCorrupt := false ]! ! !ArchiveMember methodsFor: 'accessing' stamp: 'nk 3/7/2004 16:06'! isCorrupt: aBoolean "Mark this member as being corrupt." isCorrupt := aBoolean! ! !ArchiveMember methodsFor: 'accessing' stamp: 'nk 12/20/2002 15:02'! localFileName: aString "Set my internal filename. Returns the (possibly new) filename. aString will be translated from local FS format into Unix format." ^fileName := aString copyReplaceAll: FileDirectory slash with: '/'.! ! !ArchiveMember methodsFor: 'initialization' stamp: 'ar 3/2/2001 18:46'! close ! ! !ArchiveMember methodsFor: 'initialization' stamp: 'nk 3/7/2004 16:05'! initialize fileName := ''. isCorrupt := false.! ! !ArchiveMember methodsFor: 'printing' stamp: 'nk 12/20/2002 15:11'! printOn: aStream super printOn: aStream. aStream nextPut: $(; nextPutAll: self fileName; nextPut: $)! ! !ArchiveMember methodsFor: 'testing' stamp: 'nk 2/21/2001 19:43'! usesFileNamed: aFileName "Do I require aFileName? That is, do I care if it's clobbered?" ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ArchiveMember class instanceVariableNames: ''! !ArchiveMember class methodsFor: 'as yet unclassified' stamp: 'nk 2/21/2001 18:33'! newDirectoryNamed: aString self subclassResponsibility! ! !ArchiveMember class methodsFor: 'as yet unclassified' stamp: 'nk 2/21/2001 18:32'! newFromFile: aFileName self subclassResponsibility! ! !ArchiveMember class methodsFor: 'as yet unclassified' stamp: 'nk 2/21/2001 18:32'! newFromString: aString self subclassResponsibility! ! SystemWindow subclass: #ArchiveViewer instanceVariableNames: 'archive fileName memberIndex viewAllContents' classVariableNames: '' poolDictionaries: '' category: 'Tools-ArchiveViewer'! !ArchiveViewer commentStamp: '' prior: 0! This is a viewer window that allows editing and viewing of Zip archives.! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 2/24/2001 11:03'! archive ^archive! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 4/29/2004 10:36'! directory "For compatibility with file list." ^self error: 'should use readOnlyStream instead!!'! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 2/24/2001 11:03'! fileName ^fileName! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:26'! fullName "For compatibility with FileList services. If this is called, it means that a service that requires a real filename has been requested. So extract the selected member to a temporary file and return that name." | fullName dir | self canExtractMember ifFalse: [ ^nil ]. dir := FileDirectory default directoryNamed: '.archiveViewerTemp'. fullName := dir fullNameFor: self selectedMember localFileName. self selectedMember extractInDirectory: dir. ^fullName! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 2/24/2001 14:56'! members ^archive ifNil: [ #() asOrderedCollection ] ifNotNil: [ archive members asOrderedCollection ]! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 4/29/2004 10:39'! readOnlyStream "Answer a read-only stream on the selected member. For the various stream-reading services." ^self selectedMember ifNotNilDo: [ :mem | mem contentStream ascii ]! ! !ArchiveViewer methodsFor: 'accessing' stamp: 'nk 2/24/2001 11:17'! selectedMember ^memberIndex ifNil: [ nil ] ifNotNil: [ self members at: memberIndex ifAbsent: [ ] ]! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'nk 2/24/2001 13:54'! canCreateNewArchive ^true! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'dgd 2/21/2003 22:36'! canExtractAll ^self members notEmpty! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'nk 2/24/2001 11:12'! canOpenNewArchive ^true! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'nk 2/24/2001 13:55'! canSaveArchive ^archive notNil! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! commentArchive | newName | archive ifNil: [ ^self ]. newName := FillInTheBlankMorph request: 'New comment for archive:' initialAnswer: archive zipFileComment centerAt: Sensor cursorPoint inWorld: self world onCancelReturn: archive zipFileComment acceptOnCR: true. archive zipFileComment: newName.! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! createNewArchive self setLabel: '(new archive)'. archive := ZipArchive new. self memberIndex: 0. self changed: #memberList.! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! extractAll | directory | self canExtractAll ifFalse: [^ self]. directory := FileList2 modalFolderSelector ifNil: [^ self]. archive extractAllTo: directory.! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! extractAllPossibleInDirectory: directory "Answer true if I can extract all the files in the given directory safely. Inform the user as to problems." | conflicts | self canExtractAll ifFalse: [ ^false ]. conflicts := Set new. self members do: [ :ea | | fullName | fullName := directory fullNameFor: ea localFileName. (ea usesFileNamed: fullName) ifTrue: [ conflicts add: fullName ]. ]. conflicts notEmpty ifTrue: [ | str | str := WriteStream on: (String new: 200). str nextPutAll: 'The following file(s) are needed by archive members and cannot be overwritten:'; cr. conflicts do: [ :ea | str nextPutAll: ea ] separatedBy: [ str cr ]. self inform: str contents. ^false. ]. conflicts := Set new. self members do: [ :ea | | fullName | fullName := directory relativeNameFor: ea localFileName. (directory fileExists: fullName) ifTrue: [ conflicts add: fullName ]. ]. conflicts notEmpty ifTrue: [ | str | str := WriteStream on: (String new: 200). str nextPutAll: 'The following file(s) will be overwritten:'; cr. conflicts do: [ :ea | str nextPutAll: ea ] separatedBy: [ str cr ]. str cr; nextPutAll: 'Is this OK?'. ^self confirm: str contents. ]. ^true. ! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'nk 11/11/2002 22:14'! extractDirectoriesIntoDirectory: directory (self members select: [:ea | ea isDirectory]) do: [:ea | ea extractInDirectory: directory]! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'nk 11/11/2002 22:13'! extractFilesIntoDirectory: directory (self members reject: [:ea | ea isDirectory]) do: [:ea | ea extractInDirectory: directory]! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! openNewArchive | menu result | menu := StandardFileMenu oldFileMenu: (FileDirectory default) withPattern: '*.zip'. result := menu startUpWithCaption: 'Select Zip archive to open...'. result ifNil: [ ^self ]. self fileName: (result directory fullNameFor: result name). ! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! saveArchive | result name | self canSaveArchive ifFalse: [ ^self ]. result := StandardFileMenu newFile. result ifNil: [ ^self ]. name := result directory fullNameFor: result name. (archive canWriteToFileNamed: name) ifFalse: [ self inform: name, ' is used by one or more members in your archive, and cannot be overwritten. Try writing to another file name'. ^self ]. [ archive writeToFileNamed: name ] on: Error do: [ :ex | self inform: ex description. ]. self setLabel: name asString. self changed: #memberList "in case CRC's and compressed sizes got set"! ! !ArchiveViewer methodsFor: 'archive operations' stamp: 'sd 11/20/2005 21:26'! writePrependingFile | result name prependedName | self canSaveArchive ifFalse: [ ^self ]. result := (StandardFileMenu newFileMenu: FileDirectory default) startUpWithCaption: 'Destination Zip File Name:'. result ifNil: [ ^self ]. name := result directory fullNameFor: result name. (archive canWriteToFileNamed: name) ifFalse: [ self inform: name, ' is used by one or more members in your archive, and cannot be overwritten. Try writing to another file name'. ^self ]. result := (StandardFileMenu oldFileMenu: FileDirectory default) startUpWithCaption: 'Prepended File:'. result ifNil: [ ^self ]. prependedName := result directory fullNameFor: result name. [ archive writeToFileNamed: name prependingFileNamed: prependedName ] on: Error do: [ :ex | self inform: ex description. ]. self changed: #memberList "in case CRC's and compressed sizes got set"! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! archive: aZipArchive archive := aZipArchive. self model: aZipArchive. self setLabel: 'New Zip Archive'. self memberIndex: 0. self changed: #memberList! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! briefContents "Trim to 5000 characters. If the member is longer, then point out that it is trimmed. Also warn if the member has a corrupt CRC-32." | stream subContents errorMessage | self selectedMember ifNil: [^ '']. errorMessage := ''. stream := WriteStream on: (String new: (self selectedMember uncompressedSize min: 5500)). [ self selectedMember uncompressedSize > 5000 ifTrue: [ | lastLineEndingIndex tempIndex | subContents := self selectedMember contentsFrom: 1 to: 5000. lastLineEndingIndex := subContents lastIndexOf: Character cr. tempIndex := subContents lastIndexOf: Character lf. tempIndex > lastLineEndingIndex ifTrue: [lastLineEndingIndex := tempIndex]. lastLineEndingIndex = 0 ifFalse: [subContents := subContents copyFrom: 1 to: lastLineEndingIndex]] ifFalse: [ subContents := self selectedMember contents ]] on: CRCError do: [ :ex | errorMessage := String streamContents: [ :s | s nextPutAll: '[ '; nextPutAll: (ex messageText copyUpToLast: $( ); nextPutAll: ' ]' ]. ex proceed ]. (errorMessage isEmpty not or: [ self selectedMember isCorrupt ]) ifTrue: [ stream nextPutAll: '********** WARNING!! Member is corrupt!! '; nextPutAll: errorMessage; nextPutAll: ' **********'; cr ]. self selectedMember uncompressedSize > 5000 ifTrue: [ stream nextPutAll: 'File '; print: self selectedMember fileName; nextPutAll: ' is '; print: self selectedMember uncompressedSize; nextPutAll: ' bytes long.'; cr; nextPutAll: 'Click the ''View All Contents'' button above to see the entire file.'; cr; cr; nextPutAll: 'Here are the first '; print: subContents size; nextPutAll: ' characters...'; cr; next: 40 put: $-; cr; nextPutAll: subContents; next: 40 put: $-; cr; nextPutAll: '... end of the first '; print: subContents size; nextPutAll: ' characters.' ] ifFalse: [ stream nextPutAll: self selectedMember contents ]. ^stream contents ! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'nk 6/10/2004 08:58'! buttonColor ^self defaultBackgroundColor darker! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'nk 6/10/2004 08:59'! buttonOffColor ^self defaultBackgroundColor darker! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'nk 6/10/2004 08:59'! buttonOnColor ^self defaultBackgroundColor! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! contents | contents errorMessage | self selectedMember ifNil: [^ '']. viewAllContents ifFalse: [^ self briefContents]. [ contents := self selectedMember contents ] on: CRCError do: [ :ex | errorMessage := String streamContents: [ :stream | stream nextPutAll: '********** WARNING!! Member is corrupt!! [ '; nextPutAll: (ex messageText copyUpToLast: $( ); nextPutAll: '] **********'; cr ]. ex proceed ]. ^self selectedMember isCorrupt ifFalse: [ contents ] ifTrue: [ errorMessage, contents ]! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'nk 2/25/2001 00:04'! contents: aText self shouldNotImplement.! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! createButtonBar | bar button narrowFont registeredFonts | registeredFonts := OrderedCollection new. TextStyle knownTextStylesWithoutDefault do: [:st | (TextStyle named: st) fonts do: [:f | registeredFonts addLast: f]]. narrowFont := registeredFonts detectMin: [:ea | ea widthOfString: 'Contents' from: 1 to: 8]. bar := AlignmentMorph newRow. bar color: self defaultBackgroundColor; rubberBandCells: false; vResizing: #shrinkWrap; cellInset: 6 @ 0. #(#('New\Archive' #canCreateNewArchive #createNewArchive 'Create a new, empty archive and discard this one') #('Load\Archive' #canOpenNewArchive #openNewArchive 'Open another archive and discard this one') #('Save\Archive As' #canSaveArchive #saveArchive 'Save this archive under a new name') #('Extract\All' #canExtractAll #extractAll 'Extract all this archive''s members into a directory') #('Add\File' #canAddMember #addMember 'Add a file to this archive') #('Add from\Clipboard' #canAddMember #addMemberFromClipboard 'Add the contents of the clipboard as a new file') #('Add\Directory' #canAddMember #addDirectory 'Add the entire contents of a directory, with all of its subdirectories') #('Extract\Member As' #canExtractMember #extractMember 'Extract the selected member to a file') #('Delete\Member' #canDeleteMember #deleteMember 'Remove the selected member from this archive') #('Rename\Member' #canRenameMember #renameMember 'Rename the selected member') #('View All\Contents' #canViewAllContents #changeViewAllContents 'Toggle the view of all the selected member''s contents')) do: [:arr | | buttonLabel | buttonLabel := (TextMorph new) string: arr first withCRs fontName: narrowFont familyName size: narrowFont pointSize wrap: false; hResizing: #shrinkWrap; lock; yourself. (button := PluggableButtonMorph on: self getState: arr second action: arr third) vResizing: #shrinkWrap; hResizing: #spaceFill; onColor: self buttonOnColor offColor: self buttonOffColor; label: buttonLabel; setBalloonText: arr fourth. bar addMorphBack: button. buttonLabel composeToBounds]. ^bar! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! createListHeadingUsingFont: font | sm | sm := StringMorph contents: ' uncomp comp CRC-32 date time file name'. font ifNotNil: [ sm font: font ]. ^(AlignmentMorph newColumn) color: self defaultBackgroundColor; addMorph: sm; yourself.! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! createWindow | list heading font text buttonBar | font := (TextStyle named: #DefaultFixedTextStyle) ifNotNilDo: [ :ts | ts fontArray first]. buttonBar := self createButtonBar. self addMorph: buttonBar fullFrame: (LayoutFrame fractions: (0@0 corner: 1.0@0.0) offsets: (0@0 corner: 0@44)). self minimumExtent: (buttonBar fullBounds width + 20) @ 230. self extent: self minimumExtent. heading := self createListHeadingUsingFont: font. self addMorph: heading fullFrame: (LayoutFrame fractions: (0@0 corner: 1.0@0.0) offsets: (0@44 corner: 0@60)). (list := PluggableListMorph new) on: self list: #memberList selected: #memberIndex changeSelected: #memberIndex: menu: #memberMenu:shifted: keystroke: nil. list color: self defaultBackgroundColor. font ifNotNil: [list font: font]. self addMorph: list fullFrame: (LayoutFrame fractions: (0@0 corner: 1.0@0.8) offsets: (0@60 corner: 0@0)). text := PluggableTextMorph on: self text: #contents accept: nil readSelection: nil menu: nil. self addMorph: text frame: (0@0.8 corner: 1.0@1.0). text lock. self setLabel: 'Ned''s Zip Viewer'! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! fileName: aString archive := ZipArchive new readFrom: aString. self setLabel: aString. self memberIndex: 0. self changed: #memberList! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! initialize super initialize. memberIndex := 0. viewAllContents := false. ! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! stream: aStream archive := ZipArchive new readFrom: aStream. self setLabel: aStream fullName. self memberIndex: 0. self changed: #memberList! ! !ArchiveViewer methodsFor: 'initialization' stamp: 'nk 6/10/2004 08:15'! windowIsClosing archive ifNotNil: [ archive close ].! ! !ArchiveViewer methodsFor: 'member list' stamp: 'sd 11/20/2005 21:26'! displayLineFor: aMember | stream dateTime | stream := WriteStream on: (String new: 60). dateTime := Time dateAndTimeFromSeconds: aMember lastModTime. stream nextPutAll: (aMember uncompressedSize printString padded: #left to: 8 with: $ ); space; nextPutAll: (aMember compressedSize printString padded: #left to: 8 with: $ ); space; space; nextPutAll: (aMember crc32String ); space; space. dateTime first printOn: stream format: #(3 2 1 $- 2 1 2). stream space. dateTime second print24: true showSeconds: false on: stream. stream space; space; nextPutAll: (aMember fileName ). ^stream contents! ! !ArchiveViewer methodsFor: 'member list' stamp: 'nk 2/23/2001 22:48'! highlightMemberList: list with: morphList (morphList at: self memberIndex) color: Color red! ! !ArchiveViewer methodsFor: 'member list' stamp: 'nk 2/24/2001 09:40'! memberIndex ^memberIndex! ! !ArchiveViewer methodsFor: 'member list' stamp: 'sd 11/20/2005 21:26'! memberIndex: n memberIndex := n. viewAllContents := false. self changed: #memberIndex. self changed: #contents.! ! !ArchiveViewer methodsFor: 'member list' stamp: 'nk 2/24/2001 11:51'! memberList ^ self members collect: [ :ea | self displayLineFor: ea ]! ! !ArchiveViewer methodsFor: 'member list' stamp: 'sd 11/20/2005 21:26'! memberMenu: menu shifted: shifted | services | menu add: 'Comment archive' target: self selector: #commentArchive; balloonTextForLastItem: 'Add a comment for the entire archive'. self selectedMember ifNotNilDo: [ :member | menu addLine; add: 'Inspect member' target: self selector: #inspectMember; balloonTextForLastItem: 'Inspect the selected member'; add: 'Comment member' target: self selector: #commentMember; balloonTextForLastItem: 'Add a comment for the selected member'; addLine. services := FileList itemsForFile: member fileName. menu addServices2: services for: self extraLines: #(). ]. ^menu! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! addDirectory | directory | self canAddMember ifFalse: [ ^self ]. directory := FileList2 modalFolderSelector. directory ifNil: [^ self]. archive addTree: directory removingFirstCharacters: directory pathName size + 1. self memberIndex: 0. self changed: #memberList.! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! addMember | result relative | self canAddMember ifFalse: [ ^self ]. result := StandardFileMenu oldFile. result ifNil: [ ^self ]. relative := result directory fullNameFor: result name. (relative beginsWith: FileDirectory default pathName) ifTrue: [ relative := relative copyFrom: FileDirectory default pathName size + 2 to: relative size ]. (archive addFile: relative) desiredCompressionMethod: ZipArchive compressionDeflated. self memberIndex: self members size. self changed: #memberList.! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! addMemberFromClipboard | string newName | self canAddMember ifFalse: [ ^self ]. string := Clipboard clipboardText asString. newName := FillInTheBlankMorph request: 'New name for member:' initialAnswer: 'clipboardText'. newName notEmpty ifTrue: [ (archive addString: string as: newName) desiredCompressionMethod: ZipArchive compressionDeflated. self memberIndex: self members size. self changed: #memberList. ] ! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 14:50'! canAddMember ^archive notNil! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 11:02'! canDeleteMember ^memberIndex > 0! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 11:02'! canExtractMember ^memberIndex > 0! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 11:02'! canRenameMember ^memberIndex > 0! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 23:50'! canViewAllContents ^memberIndex > 0 and: [ viewAllContents not ]! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! changeViewAllContents (viewAllContents not and: [ self selectedMember notNil and: [ self selectedMember uncompressedSize > 50000 ]]) ifTrue: [ (self confirm: 'This member''s size is ', (self selectedMember uncompressedSize asString), '; do you really want to see all that data?') ifFalse: [ ^self ] ]. viewAllContents := viewAllContents not. self changed: #contents! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! commentMember | newName | newName := FillInTheBlankMorph request: 'New comment for member:' initialAnswer: self selectedMember fileComment centerAt: Sensor cursorPoint inWorld: self world onCancelReturn: self selectedMember fileComment acceptOnCR: true. self selectedMember fileComment: newName.! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 23:29'! deleteMember self canDeleteMember ifFalse: [ ^self ]. archive removeMember: self selectedMember. self memberIndex: 0. self changed: #memberList. ! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! extractMember "Extract the member after prompting for a filename. Answer the filename, or nil if error." | result name | self canExtractMember ifFalse: [ ^nil ]. result := StandardFileMenu newFile. result ifNil: [ ^nil ]. name := (result directory fullNameFor: result name). (archive canWriteToFileNamed: name) ifFalse: [ self inform: name, ' is used by one or more members in your archive, and cannot be overwritten. Try extracting to another file name'. ^nil ]. self selectedMember extractToFileNamed: name. ^name! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'nk 2/24/2001 13:01'! inspectMember self selectedMember inspect! ! !ArchiveViewer methodsFor: 'member operations' stamp: 'sd 11/20/2005 21:26'! renameMember | newName | self canRenameMember ifFalse: [ ^self ]. newName := FillInTheBlankMorph request: 'New name for member:' initialAnswer: self selectedMember fileName. newName notEmpty ifTrue: [ self selectedMember fileName: newName. self changed: #memberList ]! ! !ArchiveViewer methodsFor: 'menu' stamp: 'sd 11/20/2005 21:26'! buildWindowMenu | menu | menu := super buildWindowMenu. menu addLine. menu add: 'inspect archive' target: archive action: #inspect. menu add: 'write prepending file...' target: self action: #writePrependingFile. ^menu.! ! !ArchiveViewer methodsFor: 'message handling' stamp: 'nk 2/24/2001 13:16'! perform: selector orSendTo: otherTarget ^ self perform: selector! ! !ArchiveViewer methodsFor: 'parts bin' stamp: 'dls 10/22/2001 07:40'! initializeToStandAlone self initialize createWindow.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ArchiveViewer class instanceVariableNames: ''! !ArchiveViewer class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:27'! deleteTemporaryDirectory " ArchiveViewer deleteTemporaryDirectory " | dir | (dir := self temporaryDirectory) exists ifTrue: [ dir recursiveDelete ].! ! !ArchiveViewer class methodsFor: 'class initialization' stamp: 'nk 4/29/2004 10:56'! initialize "ArchiveViewer initialize" FileList registerFileReader: self. Smalltalk addToShutDownList: self.! ! !ArchiveViewer class methodsFor: 'class initialization' stamp: 'sw 2/17/2002 02:35'! serviceOpenInZipViewer "Answer a service for opening in a zip viewer" ^ SimpleServiceEntry provider: self label: 'open in zip viewer' selector: #openOn: description: 'open in zip viewer' buttonLabel: 'open zip'! ! !ArchiveViewer class methodsFor: 'class initialization' stamp: 'nk 4/29/2004 11:06'! shutDown: quitting quitting ifTrue: [ self deleteTemporaryDirectory ].! ! !ArchiveViewer class methodsFor: 'file list services' stamp: 'nk 11/26/2002 12:46'! extractAllFrom: aFileName (self new) fileName: aFileName; extractAll! ! !ArchiveViewer class methodsFor: 'file list services' stamp: 'nk 11/26/2002 12:48'! serviceAddToNewZip "Answer a service for adding the file to a new zip" ^ FileModifyingSimpleServiceEntry provider: self label: 'add file to new zip' selector: #addFileToNewZip: description: 'add file to new zip' buttonLabel: 'to new zip'! ! !ArchiveViewer class methodsFor: 'file list services' stamp: 'nk 11/26/2002 12:15'! serviceExtractAll "Answer a service for opening in a zip viewer" ^ FileModifyingSimpleServiceEntry provider: self label: 'extract all to...' selector: #extractAllFrom: description: 'extract all files to a user-specified directory' buttonLabel: 'extract all'! ! !ArchiveViewer class methodsFor: 'fileIn/Out' stamp: 'sd 11/20/2005 21:27'! fileReaderServicesForFile: fullName suffix: suffix | services | services := OrderedCollection new. services add: self serviceAddToNewZip. ({'zip'.'sar'.'pr'. 'mcz'. '*'} includes: suffix) ifTrue: [services add: self serviceOpenInZipViewer. services add: self serviceExtractAll]. ^ services! ! !ArchiveViewer class methodsFor: 'fileIn/Out' stamp: 'sd 2/1/2002 21:18'! services ^ Array with: self serviceAddToNewZip with: self serviceOpenInZipViewer ! ! !ArchiveViewer class methodsFor: 'fileIn/Out' stamp: 'nk 4/29/2004 10:56'! temporaryDirectory "Answer a directory to use for unpacking files for the file list services." ^FileDirectory default directoryNamed: '.archiveViewerTemp'! ! !ArchiveViewer class methodsFor: 'initialize-release' stamp: 'nk 1/30/2002 10:13'! unload FileList unregisterFileReader: self ! ! !ArchiveViewer class methodsFor: 'instance creation' stamp: 'nk 1/30/2002 10:18'! addFileToNewZip: fullName "Add the currently selected file to a new zip" | zip | zip := (ZipArchive new) addFile: fullName as: (FileDirectory localNameFor: fullName); yourself. (self open) archive: zip ! ! !ArchiveViewer class methodsFor: 'instance creation' stamp: 'nk 2/23/2001 21:52'! open ^(self new) createWindow; openInWorld.! ! !ArchiveViewer class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:27'! openOn: aFileName | newMe | newMe := self new. newMe createWindow; fileName: aFileName; openInWorld. ^newMe! ! !ArchiveViewer class methodsFor: 'parts bin' stamp: 'nk 3/27/2002 11:41'! descriptionForPartsBin ^ self partName: 'Zip Tool' categories: #(Tools) documentation: 'A viewer and editor for Zip archive files' ! ! Error subclass: #ArithmeticError instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! ArrayedCollection variableSubclass: #Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !Array commentStamp: '' prior: 0! I present an ArrayedCollection whose elements are objects.! !Array methodsFor: 'accessing' stamp: 'ar 8/26/2001 22:02'! atWrap: index "Optimized to go through the primitive if possible" ^ self at: index - 1 \\ self size + 1! ! !Array methodsFor: 'accessing' stamp: 'ar 8/26/2001 22:03'! atWrap: index put: anObject "Optimized to go through the primitive if possible" ^ self at: index - 1 \\ self size + 1 put: anObject! ! !Array methodsFor: 'arithmetic' stamp: 'raok 10/22/2002 20:09'! +* aCollection "Premultiply aCollection by self. aCollection should be an Array or Matrix. The name of this method is APL's +.x squished into Smalltalk syntax." ^aCollection preMultiplyByArray: self ! ! !Array methodsFor: 'arithmetic' stamp: 'raok 10/22/2002 20:10'! preMultiplyByArray: a "Answer a+*self where a is an Array. Arrays are always understood as column vectors, so an n element Array is an n*1 Array. This multiplication is legal iff self size = 1." self size = 1 ifFalse: [self error: 'dimensions do not conform']. ^a * self first! ! !Array methodsFor: 'arithmetic' stamp: 'raok 10/22/2002 20:08'! preMultiplyByMatrix: m "Answer m+*self where m is a Matrix." |s| m columnCount = self size ifFalse: [self error: 'dimensions do not conform']. ^(1 to: m rowCount) collect: [:row | s _ 0. 1 to: self size do: [:k | s _ (m at: row at: k) * (self at: k) + s]. s]! ! !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: 'comparing' stamp: 'ajh 2/2/2002 15:03'! literalEqual: other self class == other class ifFalse: [^ false]. self size = other size ifFalse: [^ false]. self with: other do: [:e1 :e2 | (e1 literalEqual: e2) ifFalse: [^ false]]. ^ true! ! !Array methodsFor: 'converting' stamp: 'sma 5/12/2000 17:32'! asArray "Answer with the receiver itself." ^ self! ! !Array methodsFor: 'converting' stamp: 'tpr 11/2/2004 11:31'! 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." otherArray class == Array ifFalse: [^ self error: 'arg must be array']. self size = otherArray size ifFalse: [^ self error: 'arrays must be same size']. (self anySatisfy: [:obj | obj class == SmallInteger]) ifTrue: [^ self error: 'can''t become SmallIntegers']. (otherArray anySatisfy: [:obj | obj class == SmallInteger]) ifTrue: [^ self error: 'can''t become SmallIntegers']. self with: otherArray do:[:a :b| a == b ifTrue:[^self error:'can''t become yourself']]. "Must have failed because not enough space in forwarding table (see ObjectMemory-prepareForwardingTableForBecoming:with:twoWay:). Do GC and try again only once" (Smalltalk bytesLeft: true) = Smalltalk primitiveGarbageCollect ifTrue: [^ self primitiveFailed]. ^ self elementsExchangeIdentityWith: otherArray! ! !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' stamp: 'brp 9/26/2003 08:09'! elementsForwardIdentityTo: otherArray copyHash: copyHash "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' stamp: 'ar 4/10/2005 18:03'! 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 isString and:[each isSymbol not]) ifTrue: [ it _ Compiler evaluate: each]. each class == Array ifTrue: [it _ it evalStrings]. it]! ! !Array methodsFor: 'copying' stamp: 'ar 2/11/2001 01:55'! copyWithDependent: newElement self size = 0 ifTrue:[^DependentsArray with: newElement]. ^self copyWith: newElement! ! !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 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: 'printing' stamp: 'sd 7/31/2005 21:44'! printOn: aStream self isLiteral ifTrue: [self printAsLiteralFormOn: aStream. ^ self]. self isSelfEvaluating ifTrue: [self printAsSelfEvaluatingFormOn: aStream. ^ self]. super printOn: 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: 'self evaluating' stamp: 'sd 7/31/2005 21:44'! isSelfEvaluating ^ (self allSatisfy: [:each | each isSelfEvaluating]) and: [self class == Array]! ! !Array methodsFor: 'self evaluating' stamp: 'sd 7/31/2005 21:44'! printAsLiteralFormOn: aStream aStream nextPut: $#. self printElementsOn: aStream ! ! !Array methodsFor: 'self evaluating' stamp: 'sd 7/31/2005 21:44'! printAsSelfEvaluatingFormOn: aStream aStream nextPut: ${. self do: [:el | aStream print: el] separatedBy: [ aStream nextPutAll: ' . ']. aStream nextPut: $}! ! !Array methodsFor: 'testing' stamp: 'md 7/30/2005 21:19'! isArray ^true! ! !Array methodsFor: 'testing' stamp: 'sma 5/12/2000 14:11'! isLiteral ^ self allSatisfy: [:each | each isLiteral]! ! !Array methodsFor: 'private' stamp: 'md 1/20/2006 16:53'! hasLiteralThorough: literal "Answer true if literal is identical to any literal in this array, even if imbedded in further array structures or closure methods" | lit | 1 to: self size do: [:index | (lit _ self at: index) == literal ifTrue: [^ true]. (lit hasLiteralThorough: literal) ifTrue: [^ true]]. ^ false! ! !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: 'md 3/1/2006 21:09'! hasLiteralSuchThat: testBlock "Answer true if testBlock returns true for any literal in this array, even if imbedded in further Arrays or CompiledMethods. This method is only intended for private use by CompiledMethod hasLiteralSuchThat:" | lit | 1 to: self size do: [:index | (testBlock value: (lit _ self at: index)) ifTrue: [^ true]. (lit hasLiteralSuchThat: testBlock) 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 class instanceVariableNames: ''! !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! ! !Array class methodsFor: 'instance creation' stamp: 'md 7/19/2004 12:34'! new: sizeRequested "Answer an instance of this class with the number of indexable variables specified by the argument, sizeRequested. This is a shortcut (direct call of primitive, no #initialize, for performance" "This method runs primitively if successful" ^ self basicNew: sizeRequested "Exceptional conditions will be handled in basicNew:" ! ! TestCase subclass: #ArrayLiteralTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Compiler-Tests'! !ArrayLiteralTest methodsFor: 'initialize-release' stamp: 'avi 2/16/2004 21:09'! tearDown self class removeSelector: #array! ! !ArrayLiteralTest methodsFor: 'tests' stamp: 'avi 2/16/2004 21:08'! testReservedIdentifiers self class compile: 'array ^ #(nil true false)'. self assert: self array = {nil. true. false}.! ! !ArrayLiteralTest methodsFor: 'tests' stamp: 'avi 2/16/2004 21:09'! testSymbols self class compile: 'array ^ #(#nil #true #false #''nil'' #''true'' #''false'')'. self assert: self array = {#nil. #true. #false. #nil. #true. #false}.! ! ClassTestCase subclass: #ArrayTest instanceVariableNames: 'example1 literalArray selfEvaluatingArray otherArray nonSEArray1 nonSEarray2 example2' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Arrayed'! !ArrayTest commentStamp: '' prior: 0! This is the unit test for the class Array. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category! !ArrayTest methodsFor: 'initialize-release' stamp: 'md 4/25/2006 14:38'! setUp literalArray := #(1 true 3 #four). selfEvaluatingArray := { 1. true. (3/4). Color black. (2 to: 4) . 5 }. nonSEArray1 := { 1 . Set with: 1 }. nonSEarray2 := { Smalltalk associationAt: #Array }. example1 := #(1 2 3 4 5). example2 := {1. 2. 3/4. 4. 5}. ! ! !ArrayTest methodsFor: 'testing' stamp: 'zz 12/5/2005 17:12'! testIsArray self assert: example1 isArray! ! !ArrayTest methodsFor: 'testing' stamp: 'apb 4/21/2006 08:59'! testIsLiteral "We work with a copy of literalArray, to avoid corrupting the code." | l | l := literalArray copy. self assert: l isLiteral. l at: 1 put: self class. self deny: l isLiteral! ! !ArrayTest methodsFor: 'testing' stamp: 'zz 12/5/2005 17:18'! testIsSelfEvaluating self assert: example1 isSelfEvaluating. example1 at: 1 put: Bag new. self deny: example1 isSelfEvaluating. example1 at: 1 put: 1.! ! !ArrayTest methodsFor: 'testing' stamp: 'zz 12/5/2005 17:50'! testLiteralEqual self deny: (example1 literalEqual: example1 asIntegerArray)! ! !ArrayTest methodsFor: 'testing' stamp: 'zz 12/5/2005 17:50'! testPremultiply self assert: example1 +* #(2 ) = #(2 4 6 8 10 ) ! ! !ArrayTest methodsFor: 'testing' stamp: 'apb 4/21/2006 09:25'! testPrinting self assert: literalArray printString = '#(1 true 3 #four)'. self assert: (literalArray = (Compiler evaluate: literalArray printString)). self assert: selfEvaluatingArray printString = '{1 . true . (3/4) . Color black . (2 to: 4) . 5}'. self assert: (selfEvaluatingArray = (Compiler evaluate: selfEvaluatingArray printString)). self assert: nonSEArray1 printString = 'an Array(1 a Set(1))'. self assert: nonSEarray2 printString = 'an Array(#Array)' ! ! 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>=0) 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: 'objects from disk' stamp: 'nk 3/17/2004 15:22'! byteSize ^self basicSize * self bytesPerBasicElement ! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'nk 3/17/2004 16:28'! bytesPerBasicElement "Answer the number of bytes that each of my basic elements requires. In other words: self basicSize * self bytesPerBasicElement should equal the space required on disk by my variable sized representation." ^self class isBytes ifTrue: [ 1 ] ifFalse: [ 4 ]! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'nk 3/17/2004 18:51'! bytesPerElement ^self class isBytes ifTrue: [ 1 ] ifFalse: [ 4 ]. ! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'nk 7/30/2004 17:50'! restoreEndianness "This word object was just read in from a stream. It was stored in Big Endian (Mac) format. Reverse the byte order if the current machine is Little Endian. We only intend this for non-pointer arrays. Do nothing if I contain pointers." self class isPointers | self class isWords not ifTrue: [^self]. SmalltalkImage current isLittleEndian ifTrue: [Bitmap swapBytesIn: self from: 1 to: self basicSize]! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'tk 3/7/2001 17:36'! swapHalves "A normal switch in endianness (byte order in words) reverses the order of 4 bytes. That is not correct for SoundBuffers, which use 2-bytes units. If a normal switch has be done, this method corrects it further by swapping the two halves of the long word. This method is only used for 16-bit quanities in SoundBuffer, ShortIntegerArray, etc." | 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: 0; destY: 0; height: self size; width: 2. blt sourceX: 0; destX: 2; copyBits. "Exchange bytes 0&1 with 2&3" blt sourceX: 2; destX: 0; copyBits. blt sourceX: 0; destX: 2; copyBits.! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'ar 5/17/2001 19:50'! writeOn: aStream "Store the array of bits onto the argument, aStream. (leading byte ~= 16r80) identifies this as raw bits (uncompressed). Always store in Big Endian (Mac) byte order. Do the writing at BitBlt speeds. We only intend this for non-pointer arrays. Do nothing if I contain pointers." self class isPointers | self class isWords not ifTrue: [^ super writeOn: aStream]. "super may cause an error, but will not be called." aStream nextInt32Put: self basicSize. aStream nextWordsPutAll: self.! ! !ArrayedCollection methodsFor: 'objects from disk' stamp: 'tk 3/7/2001 18:07'! writeOnGZIPByteStream: aStream "We only intend this for non-pointer arrays. Do nothing if I contain pointers." self class isPointers | self class isWords not ifTrue: [^ super writeOnGZIPByteStream: aStream]. "super may cause an error, but will not be called." aStream nextPutAllWordArray: 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: '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 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 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' stamp: 'ar 5/17/2001 19:50'! newFromStream: s "Only meant for my subclasses that are raw bits and word-like. For quick unpack form the disk." | len | self isPointers | self isWords not ifTrue: [^ super newFromStream: s]. "super may cause an error, but will not be called." s next = 16r80 ifTrue: ["A compressed format. Could copy what BitMap does, or use a special sound compression format. Callers normally compress their own way." ^ self error: 'not implemented']. s skip: -1. len _ s nextInt32. ^ s nextWordsInto: (self basicNew: len)! ! !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: 'Exceptions-Extensions'! !AssertionFailure commentStamp: 'gh 5/2/2002 20:29' prior: 0! AsssertionFailure is the exception signaled from Object>>assert: when the assertion block evaluates to false.! ParseNode subclass: #AssignmentNode instanceVariableNames: 'variable value' classVariableNames: '' poolDictionaries: '' category: 'Compiler-ParseNodes'! !AssignmentNode commentStamp: '' prior: 0! AssignmentNode comment: 'I represent a (var_expr) construct.'! !AssignmentNode methodsFor: 'code generation' stamp: 'di 9/5/2001 18:46'! emitForEffect: stack on: aStream variable emitLoad: stack on: aStream. value emitForValue: stack on: aStream. variable emitStorePop: stack on: aStream. pc _ aStream position! ! !AssignmentNode methodsFor: 'code generation' stamp: 'di 9/5/2001 21:26'! emitForValue: stack on: aStream variable emitLoad: stack on: aStream. value emitForValue: stack on: aStream. variable emitStore: stack on: aStream. pc _ aStream position! ! !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: 'equation translation'! variable ^variable! ! !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: 'initialize-release' stamp: 'hmm 7/15/2001 21:17'! variable: aVariable value: expression from: encoder sourceRange: range encoder noteSourceRange: range forNode: self. ^self variable: aVariable value: expression from: encoder! ! !AssignmentNode methodsFor: 'printing' stamp: 'md 3/8/2006 09:33'! printOn: aStream indent: level variable printOn: aStream indent: level. aStream nextPutAll: ' := '. value printOn: aStream indent: level + 2.! ! !AssignmentNode methodsFor: 'printing' stamp: 'md 8/14/2005 17:30'! printOn: aStream indent: level precedence: p p < 4 ifTrue: [aStream nextPutAll: '('. self printOn: aStream indent: level. aStream nextPutAll: ')'] ifFalse: [self printOn: aStream indent: level]! ! !AssignmentNode methodsFor: '*eToys-tiles' stamp: 'RAA 2/26/2001 16:17'! asMorphicSyntaxIn: parent ^parent assignmentNode: self variable: variable value: value! ! !AssignmentNode methodsFor: '*eToys-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: 'EToys-Scripting Tiles'! !AssignmentTileMorph methodsFor: 'accessing' stamp: 'tak 12/5/2004 14:04'! options ^ {#(#: #Incr: #Decr: #Mult: ). {nil. nil. nil. nil}}! ! !AssignmentTileMorph methodsFor: 'accessing' stamp: 'tak 12/5/2004 14:09'! value ^ assignmentSuffix! ! !AssignmentTileMorph methodsFor: 'accessing' stamp: 'tak 12/5/2004 14:06'! value: anObject self setAssignmentSuffix: anObject. self acceptNewLiteral! ! !AssignmentTileMorph methodsFor: 'arrow' stamp: 'nk 10/8/2004 14:27'! addArrowsIfAppropriate "If the receiver's slot is of an appropriate type, add arrows to the tile." (Vocabulary vocabularyForType: dataType) ifNotNilDo: [:aVocab | aVocab wantsAssignmentTileVariants ifTrue: [self addArrows]]. (assignmentSuffix = ':') ifTrue: [ self addMorphBack: (ImageMorph new image: (ScriptingSystem formAtKey: #NewGets)). (self findA: StringMorph) ifNotNilDo: [ :sm | (sm contents endsWith: ' :') ifTrue: [ sm contents: (sm contents allButLast: 2) ]]]! ! !AssignmentTileMorph methodsFor: 'as yet unclassified'! fixLayoutOfSubmorphsNotIn: aCollection super fixLayoutOfSubmorphsNotIn: aCollection. self updateLiteralLabel; updateWordingToMatchVocabulary; layoutChanged; fullBounds! ! !AssignmentTileMorph methodsFor: 'code generation' stamp: 'sw 2/6/2002 01:17'! assignmentReceiverTile "Answer the TilePadMorph that should be sent storeCodeOn:indent: to get the receiver of the assignment properly stored on the code stream" ^ owner submorphs first! ! !AssignmentTileMorph methodsFor: 'code generation' stamp: 'sw 2/6/2002 01:25'! operatorForAssignmentSuffix: aString "Answer the operator associated with the receiver, assumed to be one of the compound assignments" | toTest | toTest _ aString asString. #( ('Incr:' '+') ('Decr:' '-') ('Mult:' '*')) do: [:pair | toTest = pair first ifTrue: [^ pair second]]. ^ toTest "AssignmentTileMorph new operatorForAssignmentSuffix: 'Incr:'"! ! !AssignmentTileMorph methodsFor: 'code generation' stamp: 'aoy 2/15/2003 21:09'! 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" aStream nextPutAll: (Utilities setterSelectorFor: assignmentRoot). aStream space."Simple assignment, don't need existing value" assignmentSuffix = ':' ifFalse: ["Assignments that require that old values be retrieved" self assignmentReceiverTile storeCodeOn: aStream indent: tabCount. aStream space. aStream nextPutAll: (Utilities getterSelectorFor: assignmentRoot). aStream space. aStream nextPutAll: (self operatorForAssignmentSuffix: assignmentSuffix). aStream space]! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'yo 1/30/2005 11:11'! computeOperatorOrExpression "Compute the operator or expression to use, and set the wording correectly on the tile face" | aSuffix wording anInterface getter doc | operatorOrExpression _ (assignmentRoot, assignmentSuffix) asSymbol. aSuffix _ self currentVocabulary translatedWordingFor: assignmentSuffix. getter _ Utilities getterSelectorFor: assignmentRoot. anInterface _ self currentVocabulary methodInterfaceAt: getter ifAbsent: [Vocabulary eToyVocabulary methodInterfaceAt: getter ifAbsent: [nil]]. wording _ anInterface ifNotNil: [anInterface wording] ifNil: [assignmentRoot copyWithout: $:]. (anInterface notNil and: [(doc _ anInterface documentation) notNil]) ifTrue: [self setBalloonText: doc]. operatorReadoutString _ wording translated, ' ', aSuffix. self line1: operatorReadoutString. self addArrowsIfAppropriate! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 15:44'! initialize "initialize the state of the receiver" 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: 'yo 1/1/2004 19:50'! setRoot: aString "Establish the assignment root, and update the label on the tile" assignmentRoot _ aString. self updateLiteralLabel! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 2/16/98 01:12'! setRoot: aString dataType: aSymbol assignmentRoot _ aString. assignmentSuffix _ ':'. dataType _ aSymbol. self updateLiteralLabel! ! !AssignmentTileMorph methodsFor: 'initialization' stamp: 'sw 9/12/2001 22:52'! updateWordingToMatchVocabulary "The current vocabulary has changed; change the wording on my face, if appropriate" self computeOperatorOrExpression! ! !AssignmentTileMorph methodsFor: 'player viewer' stamp: 'yo 1/1/2004 19:51'! assignmentRoot "Answer the assignment root" ^ assignmentRoot! ! !AssignmentTileMorph methodsFor: 'player viewer' 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: 'comparing' stamp: 'md 1/27/2004 17:27'! = anAssociation ^ super = anAssociation and: [value = anAssociation value]! ! !Association methodsFor: 'comparing' stamp: 'md 1/27/2004 17:28'! hash "Hash is reimplemented because = is implemented." ^key hash bitXor: value hash.! ! !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 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: 'self evaluating' stamp: 'sd 7/31/2005 21:44'! isSelfEvaluating ^ self class == Association! ! !Association methodsFor: 'testing' stamp: 'ar 8/14/2001 23:06'! isSpecialWriteBinding "Return true if this variable binding is write protected, e.g., should not be accessed primitively but rather by sending #value: messages" ^false! ! !Association methodsFor: 'testing' stamp: 'ar 8/14/2001 22:39'! isVariableBinding "Return true if I represent a literal variable binding" ^true! ! !Association methodsFor: '*services-base-preferences' stamp: 'rr 3/21/2006 11:58'! serviceUpdate self key service perform: self value! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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! ! ClassTestCase subclass: #AssociationTest instanceVariableNames: 'a b' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Support'! !AssociationTest methodsFor: 'setup' stamp: 'zz 12/5/2005 18:33'! setUp a := 1 -> 'one'. b := 1 -> 'een'.! ! !AssociationTest methodsFor: 'tests' stamp: 'md 3/8/2004 16:37'! testEquality self assert: (a key = b key); deny: (a value = b value); deny: (a = b) ! ! !AssociationTest methodsFor: 'tests' stamp: 'md 3/8/2004 16:38'! testHash self assert: (a hash = a copy hash); deny: (a hash = b hash)! ! !AssociationTest methodsFor: 'tests' stamp: 'zz 12/5/2005 18:38'! testIsSelfEvaluating self assert: (a isSelfEvaluating) ! ! Object subclass: #AsyncFile instanceVariableNames: 'name writeable semaphore fileHandle' classVariableNames: 'Busy ErrorCode' poolDictionaries: '' category: 'Files-Kernel'! !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: 'as yet unclassified' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'bootstrap 5/31/2006 20:46'! 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 = ErrorCode ifTrue: [^ self error: 'asynchronous read operation failed']. aBlock value: buffer. ] forkAt: Processor userInterruptPriority. ! ! !AsyncFile methodsFor: 'as yet unclassified' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: ( FileDirectory default fullNameFor: fileName) forWrite: true. self primWriteStart: fileHandle fPosition: 0 fromBuffer: buf1 at: 1 count: byteCount. semaphore wait. bytesWritten := self primWriteResult: fileHandle. self close. self open: ( FileDirectory default fullNameFor: 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: 'bootstrap 5/31/2006 20:45'! 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 = ErrorCode 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 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 class instanceVariableNames: ''! !AsyncFile class methodsFor: 'class initialization' stamp: 'bootstrap 5/31/2006 20:45'! initialize "AsyncFile initialize" "Possible abnormal I/O completion results." Busy := -1. ErrorCode := -2. ! ! EllipseMorph subclass: #AtomMorph instanceVariableNames: 'velocity' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Demo'! !AtomMorph commentStamp: 'tbn 11/25/2004 09:06' prior: 0! AtomMorph represents an atom used in the simulation of an ideal gas. It's container is typically a BouncingAtomsMorph. Try: BouncingAtomsMorph new openInWorld to open the gas simulation or: AtomMorph example to open an instance in the current world! !AtomMorph methodsFor: 'accessing'! infected ^ color = Color red! ! !AtomMorph methodsFor: 'accessing'! infected: aBoolean aBoolean ifTrue: [self color: Color red] ifFalse: [self color: Color blue].! ! !AtomMorph methodsFor: 'accessing'! velocity ^ velocity! ! !AtomMorph methodsFor: 'accessing'! velocity: newVelocity velocity _ newVelocity.! ! !AtomMorph methodsFor: 'drawing'! 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: 'initialization' stamp: 'dgd 3/7/2003 14:13'! defaultBorderWidth "answer the default border width for the receiver" ^ 0! ! !AtomMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:13'! defaultColor "answer the default color/fill style for the receiver" ^ Color blue! ! !AtomMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:13'! initialize "Make a new atom with a random position and velocity." super initialize. "" self extent: 8 @ 7. self randomPositionIn: (0 @ 0 corner: 300 @ 300) maxVelocity: 10! ! !AtomMorph methodsFor: 'initialization' 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: 'private' 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 class instanceVariableNames: ''! !AtomMorph class methodsFor: 'examples' stamp: 'tbn 11/25/2004 09:03'! example " AtomMorph example " |a| a := AtomMorph new openInWorld. a color: Color random. [1000 timesRepeat: [a bounceIn: World bounds. (Delay forMilliseconds: 50) wait]. a delete] fork.! ! !AtomMorph class methodsFor: 'new-morph participation' stamp: 'di 6/22/97 09:07'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! Error subclass: #AttemptToWriteReadOnlyGlobal instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! !AttemptToWriteReadOnlyGlobal commentStamp: 'gh 5/2/2002 20:26' prior: 0! This is a resumable error you get if you try to assign a readonly variable a value. Name definitions in the module system can be read only and are then created using instances of ReadOnlyVariableBinding instead of Association. See also LookupKey>>beReadWriteBinding and LookupKey>>beReadOnlyBinding. ! !AttemptToWriteReadOnlyGlobal methodsFor: 'as yet unclassified' stamp: 'ar 8/17/2001 18:02'! description "Return a textual description of the exception." | desc mt | desc := 'Error'. ^(mt := self messageText) == nil ifTrue: [desc] ifFalse: [desc, ': ', mt]! ! !AttemptToWriteReadOnlyGlobal methodsFor: 'as yet unclassified' stamp: 'ar 8/17/2001 18:02'! isResumable ^true! ! Stream subclass: #AttributedTextStream instanceVariableNames: 'characters attributeRuns attributeValues currentAttributes currentRun' 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: 'access' stamp: 'ls 6/27/1998 15:09'! currentAttributes "return the current attributes" ^currentAttributes! ! !AttributedTextStream methodsFor: 'access' stamp: 'ar 10/16/2001 22:57'! currentAttributes: newAttributes "set the current attributes" (currentRun > 0 and:[currentAttributes ~= newAttributes]) ifTrue:[ attributeRuns nextPut: currentRun. attributeValues nextPut: currentAttributes. currentRun _ 0. ]. 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: 'retrieving the text' stamp: 'ar 10/16/2001 22:39'! contents | ans | currentRun > 0 ifTrue:[ attributeValues nextPut: currentAttributes. attributeRuns nextPut: currentRun. currentRun _ 0]. ans _ Text new: characters size. "this is declared private, but it's exactly what I need, and it's declared as exactly what I want it to do...." ans setString: characters contents setRuns: (RunArray runs: attributeRuns contents values: attributeValues contents). ^ans! ! !AttributedTextStream methodsFor: 'stream protocol' stamp: 'ar 10/16/2001 22:38'! nextPut: aChar currentRun _ currentRun + 1. characters nextPut: aChar! ! !AttributedTextStream methodsFor: 'stream protocol' stamp: 'ar 10/16/2001 22:38'! nextPutAll: aString "add an entire string with the same attributes" currentRun _ currentRun + aString size. characters nextPutAll: aString.! ! !AttributedTextStream methodsFor: 'private-initialization' stamp: 'ar 10/16/2001 22:40'! initialize characters _ WriteStream on: String new. currentAttributes _ OrderedCollection new. currentRun _ 0. attributeValues _ WriteStream on: (Array new: 50). attributeRuns _ WriteStream on: (Array new: 50). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AttributedTextStream class instanceVariableNames: ''! !AttributedTextStream class methodsFor: 'instance creation' stamp: 'gk 2/9/2004 18:50'! new "For this class we override Stream class>>new since this class actually is created using #new, even though it is a Stream." ^self 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: 'Nebraska-Morphic-Collaborative'! !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: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color yellow! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize "initialize the state of the receiver" super initialize. "" transmitWhileRecording := false. handsFreeTalking := false. mycodec := GSMCodec new. myrecorder := ChatNotes new. mytargetip := ''. self start2. self changeTalkButtonLabel! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! ipAddress: aString mytargetip := aString! ! !AudioChatGUI methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21: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: 'sd 11/20/2005 21:25'! handsFreeTalking ^handsFreeTalking ifNil: [handsFreeTalking := false].! ! !AudioChatGUI methodsFor: 'sending' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! transmitWhileRecording ^transmitWhileRecording ifNil: [transmitWhileRecording := false]! ! !AudioChatGUI methodsFor: 'stepping and presenter' stamp: 'sd 11/20/2005 21:25'! 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: 'stepping and presenter' stamp: 'sd 11/20/2005 21:25'! 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: 'stepping and presenter' stamp: 'Tbp 4/11/2000 16:49'! stop myrecorder stop. self send.! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'aoy 2/17/2003 01:01'! 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.']. bText := transmitWhileRecording ifTrue: [bText , ' The message will be sent while you are speaking.'] ifFalse: [bText , ' The message will be sent when you are finished.']. theTalkButton setBalloonText: bText! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'sd 11/20/2005 21: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: '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: '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: 'sd 11/20/2005 21:25'! removeConnectButton theConnectButton ifNotNil: [ theConnectButton delete. theConnectButton := nil. ].! ! !AudioChatGUI methodsFor: 'stuff' stamp: 'sd 11/20/2005 21:25'! 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: 'testing' stamp: 'RAA 8/12/2000 18:09'! stepTime myrecorder ifNil: [^200]. myrecorder isRecording ifFalse: [^200]. ^20! ! !AudioChatGUI methodsFor: 'testing' stamp: 'RAA 8/2/2000 07:47'! stepTimeIn: aSystemWindow ^self stepTime ! ! !AudioChatGUI methodsFor: 'user interface' 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: 'user interface' 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 class instanceVariableNames: ''! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! debugLog: x " AudioChatGUI debugLog: nil AudioChatGUI debugLog: OrderedCollection new DebugLog LiveMessages NewAudioMessages PlayOnArrival " DebugLog := x. ! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! newAudioMessages ^NewAudioMessages ifNil: [NewAudioMessages := SharedQueue new].! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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/6/2000 14:23'! playNextAudioMessage (self newAudioMessages nextOrNil ifNil: [^self]) asSound play.! ! !AudioChatGUI class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! playOnArrival ^PlayOnArrival ifNil: [PlayOnArrival := false]! ! !AudioChatGUI class methodsFor: 'class initialization' 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: 'creation' stamp: 'RAA 8/4/2000 14:06'! openAsMorph AudioChatGUI new openInWorld. "old syswindow version in #start" ! ! !AudioChatGUI class methodsFor: 'parts bin' stamp: 'md 8/10/2006 11:53'! descriptionForPartsBin "Answer a description of the receiver for use in a parts bin" ^ self partName: 'Audio chat' categories: #('Collaborative') documentation: 'A tool for talking to other Squeak users' sampleImageForm: (Form extent: 110@70 depth: 8 fromArray: #( 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193909241 4193845248 4193909241 4193845505 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 33159673 4193845248 4193909241 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843257 4193845248 4193908993 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 4193845248 4193845505 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 33095680 4193845505 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789619457 33095680 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789642629 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466465 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 2239817189 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857048960 2246173153 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850756577 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789653477 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857024481 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789642725 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857013729 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3783321061 3842048257 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857048960 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3783648741 31843813 31843813 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3842106853 3857048965 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3783321061 31843813 3857048833 16901605 3842106625 31843813 3842106625 31843813 3842048257 3857049061 16901605 16843237 3857048960 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3783648741 31843813 3856990693 3856990693 16843237 3842106853 16843237 3842106853 31843813 31843585 3856990693 3842106853 3857048965 3789619457 16842752 4177592577 31580641 3777505320 673720360 673720360 685891880 673720360 673720360 673720545 3777505320 673720360 673720360 685892065 3783321061 31843813 3856990693 3856990693 3842106853 3842106853 3842106853 3842106853 16843009 31785445 3857049061 3842106853 3857048960 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3783648741 31843813 3856990693 3856990693 3842106853 3842106853 3842106853 3842106853 31843813 3856990693 3857049061 3842106853 3857048965 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3783321061 31843813 31785445 3856990693 3842106853 3842106853 3842106853 3842106853 31843813 31843585 3856990693 3842106853 3857048960 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3783648741 3842048257 3857048833 16901605 16843237 16843237 16843237 16843237 3842048257 3857049061 16901605 3856990693 3857048965 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3783321061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857048960 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789642725 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857013729 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789653477 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857024481 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789676933 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850756577 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789677025 2239817189 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857048960 2246173153 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789677025 3789642629 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466373 1535466465 3789677025 3789619457 16842752 4177592577 31580641 3777552865 3789677025 3789677025 685891880 3789677025 3789677025 3789629665 3777552865 3789677025 3789677025 685892065 3789677025 3789677025 3789677025 3789676928 2239792512 2239792512 2239792512 2239792512 2239792512 2239816161 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3777505320 673720360 673720360 685891880 673720360 673720360 673720545 3777505320 673720360 673720360 685892065 3789677025 3789677025 3789677025 3783613413 3857049061 3857049061 3857049061 3857049061 3857049061 3857013637 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 2246436325 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 2246173153 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 16843009 16843009 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789642725 3842048257 16901605 16901605 3857049061 3857049061 3857049061 3857049061 3857013729 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789619457 16843009 16843009 16843233 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789653477 3856990693 3856990693 3842106853 3857049061 3857049061 3857049061 3857049061 3857024481 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 16843009 1888776340 1888776340 1879113985 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789642725 3856990693 3856990693 3842106853 3842048257 3857048833 16901377 16901605 3857013729 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789676801 16880752 2490406000 2490406000 2490405889 16900577 3789677025 3789677025 3789677025 3789677025 3789677025 3789653477 3856990693 3856990693 3842106853 31843813 31843813 31843813 31843813 3857024481 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789619457 26505364 1888776340 1888776340 1888776340 16843233 3789677025 3789677025 3789677025 3789677025 3789677025 3789642725 3856990465 16901605 3842106853 3842048257 31843813 3842106625 3857049061 3857013729 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789619457 2490406000 2490406000 2490406000 2490406000 2483093985 3789677025 3789677025 3789677025 3789677025 3789677025 3789653477 3856990693 3857049061 3842106853 31843813 31843813 3842106625 3857049061 3857024481 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3774939540 1888776340 1888776340 1888776340 1888776340 1888747777 3789677025 3789677025 3789677025 3789677025 3789677025 3789642725 3856990693 3857049061 3842106853 31843813 31843813 3856990693 3857049061 3857013729 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3774939504 2490406000 2490406000 2490406000 2490406000 2490368257 3789677025 3789677025 3789677025 3789677025 3789677025 3789653477 3842048257 3857049061 16843237 3842048257 3842106853 3856990693 3857049061 3857024481 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3774939393 3789677025 16871572 1888776340 1895825407 1888776340 1888776340 1888776193 31580641 3789677025 3789677025 3789677025 3789677025 3789642725 3857049061 3857049061 3857049061 3857049061 3857049061 3842106853 3857049061 3857013729 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16880752 2490406000 4285568112 4285568112 2490406000 2490405889 31580641 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 3857049061 3857049061 3857049061 3857049061 3842106853 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16871572 1888776340 4287918228 4287918228 1888776340 1888776193 31580641 3789677025 3789677025 3789677025 3789677025 3789676933 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16880752 2490406000 4285568112 4285568112 2490406000 2490405889 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 2246436325 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 2246173153 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16871572 1888776340 4287918228 4287918228 1888776340 1888776193 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3783613413 3857049061 3857049061 3857049061 3857049061 3857049061 3857013637 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16880752 2490406000 4285568112 4285568112 2490406000 2490405889 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 2239792512 2239792512 2239792512 2239792512 2239792512 2239816161 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 31580641 31580641 16871572 1888776340 4287918228 4287918228 1888776340 1888776193 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 1535466373 1535466373 1535466373 1535466373 1535466373 1541530081 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3774939393 3789677025 16880752 2490406000 2499805183 2490406000 2490406000 2490405889 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789653376 3857049061 3857049061 3857049061 3857049061 3857049061 3850405345 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 16871572 1888776340 1888776340 1888776340 1888776340 1888776193 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3783648741 3857049061 3857049061 3857049061 3857049061 3857049061 3857048965 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3774939504 2490406000 2490406000 2490406000 2490406000 2490368257 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 2246436325 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 2246173153 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3774939540 1888776340 1888776340 1888776340 1888776340 1888747777 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 1541793253 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 1541530081 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789619457 2490406000 2490406000 2490406000 2490406000 2483093985 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3856990465 16843237 3857049061 3857048833 31843813 31843813 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789619457 26505364 1888776340 1888776340 1888776340 16843233 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3856990693 31785445 3857049061 3857049061 31843585 31843813 3857049061 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789676801 16880752 2490406000 2490406000 2490405889 16900577 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 31843813 3842048257 3857049061 31843813 31843585 31843813 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 16843009 1888776340 1888776340 1879113985 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3857049061 31843813 31843813 31843813 31843813 31785445 3857049061 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789619457 16843009 16843009 16843233 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 31843813 3842048257 31843813 31843813 16901605 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 16843009 16843009 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3857049061 31843813 31843813 31843813 31843813 31785445 3857049061 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 31843813 31843813 31843813 31843813 31843585 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676933 3857048833 16901605 3842048257 3842106625 16901377 16901377 31843813 3850756577 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789676928 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 3850428897 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 1541793253 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 1541530081 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 2246436325 3857049061 3857049061 3857049061 3857049061 3857049061 3857049061 2246173153 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3783648741 3857049061 3857049061 3857049061 3857049061 3857049061 3857048965 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789653376 3857049061 3857049061 3857049061 3857049061 3857049061 3850405345 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 1535466373 1535466373 1535466373 1535466373 1535466373 1541530081 3789677025 3789677025 3789677025 3789619457 16842752 4177592577 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789619457 16842752 4193845505 31580641 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789677025 3789619457 33095680 4193845505 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 33095680 4193908993 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 4193845248 4193909241 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843257 4193845248 4193909241 4193845505 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 33159673 4193845248) offset: 0@0)! ! Object subclass: #Authorizer instanceVariableNames: 'users realm' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !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: '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: 'ar 8/17/2001 18:19'! user: userId "Return the requesting user." ^users at: userId ifAbsent: [ self error: (self class unauthorizedFor: realm) ]! ! !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 class instanceVariableNames: ''! !Authorizer class methodsFor: 'as yet unclassified' stamp: 'ar 8/17/2001 18:19'! unauthorizedFor: realm ^'HTTP/1.0 401 Unauthorized', self crlf, 'WWW-Authenticate: Basic realm="Squeak/',realm,'"', String crlfcrlf, 'Unauthorized

Unauthorized for ',realm, '

' ! ! Object subclass: #AutoStart instanceVariableNames: 'parameters' classVariableNames: 'Active InstalledLaunchers' poolDictionaries: '' category: 'System-Support'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! AutoStart class instanceVariableNames: ''! !AutoStart class methodsFor: 'accessing' stamp: 'mir 7/28/1999 17:47'! addLauncher: launcher self installedLaunchers add: launcher! ! !AutoStart class methodsFor: 'accessing'! addLauncherFirst: launcher self installedLaunchers addFirst: launcher! ! !AutoStart class methodsFor: 'accessing' stamp: 'mir 7/28/1999 17:47'! removeLauncher: launcher self installedLaunchers remove: launcher ifAbsent: []! ! !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 9/30/2004 15:05'! initialize "AutoStart initialize" "Order: ExternalSettings, SecurityManager, AutoStart" Smalltalk addToStartUpList: AutoStart after: SecurityManager. Smalltalk addToShutDownList: AutoStart after: SecurityManager.! ! !AutoStart class methodsFor: 'class initialization' stamp: 'mir 9/30/2004 15:06'! shutDown: quitting self active: false! ! !AutoStart class methodsFor: 'class initialization' stamp: 'bf 11/23/2004 19:01'! startUp: resuming "The image is either being newly started (resuming is true), or it's just been snapshotted. If this has just been a snapshot, skip all the startup stuff." | startupParameters launchers | self active ifTrue: [^self]. self active: true. resuming ifFalse: [^self]. HTTPClient determineIfRunningInBrowser. startupParameters _ AbstractLauncher extractParameters. (startupParameters includesKey: 'apiSupported' asUppercase ) ifTrue: [ HTTPClient browserSupportsAPI: ((startupParameters at: 'apiSupported' asUppercase) asUppercase = 'TRUE'). HTTPClient isRunningInBrowser ifFalse: [HTTPClient isRunningInBrowser: true]]. self checkForUpdates ifTrue: [^self]. self checkForPluginUpdate. 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] fixTemps]]! ! !AutoStart class methodsFor: 'updating' stamp: 'mir 3/5/2004 20:43'! checkForPluginUpdate | pluginVersion updateURL | World ifNotNil: [ World install. ActiveHand position: 100@100]. HTTPClient isRunningInBrowser ifFalse: [^false]. pluginVersion _ AbstractLauncher extractParameters at: (SmalltalkImage current platformName copyWithout: Character space) asUppercase ifAbsent: [^false]. updateURL _ AbstractLauncher extractParameters at: 'UPDATE_URL' ifAbsent: [^false]. ^SystemVersion check: pluginVersion andRequestPluginUpdate: updateURL! ! !AutoStart class methodsFor: 'updating' stamp: 'mir 11/13/2003 19:09'! checkForUpdates | availableUpdate updateServer | World ifNotNil: [ World install. ActiveHand position: 100@100]. HTTPClient isRunningInBrowser ifFalse: [^self processUpdates]. availableUpdate _ (AbstractLauncher extractParameters at: 'UPDATE' ifAbsent: [''] ) asInteger. availableUpdate ifNil: [^false]. updateServer _ AbstractLauncher extractParameters at: 'UPDATESERVER' ifAbsent: [AbstractLauncher extractParameters at: 'UPDATE_SERVER' ifAbsent: ['Squeakland']]. Utilities setUpdateServer: updateServer. ^SystemVersion checkAndApplyUpdates: availableUpdate! ! !AutoStart class methodsFor: 'updating' stamp: 'CdG 10/17/2005 19:34'! processUpdates "Process update files from a well-known update server. This method is called at system startup time, Only if the preference #updateFromServerAtStartup is true is the actual update processing undertaken automatically" | choice | (Preferences valueOfFlag: #updateFromServerAtStartup) ifTrue: [choice _ UIManager default chooseFrom: #('Yes, Update' 'No, Not now' 'Don''t ask again') title: 'Shall I look for new code\updates on the server?' withCRs. choice = 1 ifTrue: [ Utilities updateFromServer]. choice = 3 ifTrue: [ Preferences setPreference: #updateFromServerAtStartup toValue: false. self inform: 'Remember to save you image to make this setting permant.']]. ^false! ! !AutoStart class methodsFor: 'private' stamp: 'mir 9/7/2004 13:34'! active ^ Active == true! ! !AutoStart class methodsFor: 'private' stamp: 'mir 9/7/2004 13:36'! active: aBoolean Active := aBoolean! ! !AutoStart class methodsFor: 'private' stamp: 'mir 7/28/1999 17:43'! installedLaunchers InstalledLaunchers ifNil: [ InstalledLaunchers _ OrderedCollection new]. ^InstalledLaunchers! ! Object subclass: #BDFFontReader instanceVariableNames: 'file properties' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Fonts'! !BDFFontReader commentStamp: '' prior: 0! I am a conversion utility for reading X11 Bitmap Distribution Format fonts. My code is derived from the multilingual Squeak changeset written by OHSHIMA Yoshiki (ohshima@is.titech.ac.jp), although all support for fonts with more than 256 glyphs has been ripped out. See http://www.is.titech.ac.jp/~ohshima/squeak/squeak-multilingual-e.html . My class methods contain tools for fetching BDF source files from a well-known archive site, batch conversion to Squeak's .sf2 format, and installation of these fonts as TextStyles. Also, the legal notices for the standard 75dpi fonts I process this way are included as "x11FontLegalNotices'.! !BDFFontReader methodsFor: 'initialize' stamp: 'nop 1/18/2000 19:44'! initialize properties _ Dictionary new.! ! !BDFFontReader methodsFor: 'initialize' stamp: 'ar 10/25/2005 00:34'! openFileNamed: fileName file := CrLfFileStream readOnlyFileNamed: fileName.! ! !BDFFontReader methodsFor: 'reading' stamp: 'nop 1/18/2000 19:45'! errorFileFormat self error: 'malformed bdf format'! ! !BDFFontReader methodsFor: 'reading' stamp: 'nop 1/18/2000 19:46'! errorUnsupported self error: 'unsupported bdf'! ! !BDFFontReader methodsFor: 'reading' stamp: 'ar 10/25/2005 00:35'! getLine ^file upTo: Character cr.! ! !BDFFontReader methodsFor: 'reading' stamp: 'ar 10/25/2005 00:35'! read | xTable strikeWidth glyphs ascent descent minAscii maxAscii maxWidth chars charsNum height form encoding bbx array width blt lastAscii pointSize ret stream | form _ encoding _ bbx _ nil. self readAttributes. height _ Integer readFromString: ((properties at: #FONTBOUNDINGBOX) at: 2). ascent _ Integer readFromString: (properties at: 'FONT_ASCENT' asSymbol) first. descent _ Integer readFromString: (properties at: 'FONT_DESCENT' asSymbol) first. pointSize _ (Integer readFromString: (properties at: 'POINT_SIZE' asSymbol) first) // 10. maxWidth _ 0. minAscii _ 9999. strikeWidth _ 0. maxAscii _ 0. charsNum _ Integer readFromString: (properties at: #CHARS) first. chars _ Set new: charsNum. 1 to: charsNum do: [:i | array _ self readOneCharacter. stream _ ReadStream on: array. form _ stream next. encoding _ stream next. bbx _ stream next. form ifNotNil: [ width _ bbx at: 1. maxWidth _ maxWidth max: width. minAscii _ minAscii min: encoding. maxAscii _ maxAscii max: encoding. strikeWidth _ strikeWidth + width. chars add: array. ]. ]. chars _ chars asSortedCollection: [:x :y | (x at: 2) <= (y at: 2)]. charsNum _ chars size. "undefined encodings make this different" charsNum > 256 ifTrue: [ "it should be 94x94 charset, and should be fixed width font" strikeWidth _ 94*94*maxWidth. maxAscii _ 94*94. minAscii _ 0. xTable _ XTableForFixedFont new. xTable maxAscii: 94*94. xTable width: maxWidth. ] ifFalse: [ xTable _ (Array new: 258) atAllPut: 0. ]. glyphs _ Form extent: strikeWidth@height. blt _ BitBlt toForm: glyphs. lastAscii _ 0. charsNum > 256 ifTrue: [ 1 to: charsNum do: [:i | stream _ ReadStream on: (chars at: i). form _ stream next. encoding _ stream next. bbx _ stream next. encoding _ ((encoding // 256) - 33) * 94 + ((encoding \\ 256) - 33). blt copy: ((encoding * maxWidth)@0 extent: maxWidth@height) from: 0@0 in: form. ]. ] ifFalse: [ 1 to: charsNum do: [:i | stream _ ReadStream on: (chars at: i). form _ stream next. encoding _ stream next. bbx _ stream next. lastAscii+1 to: encoding-1 do: [:a | xTable at: a+2 put: (xTable at: a+1)]. blt copy: (((xTable at: encoding+1)@(ascent - (bbx at: 2) - (bbx at: 4))) extent: (bbx at: 1)@(bbx at: 2)) from: 0@0 in: form. xTable at: encoding+2 put: (xTable at: encoding+1)+(bbx at: 1). lastAscii _ encoding. ] ]. ret _ Array new: 8. ret at: 1 put: xTable. ret at: 2 put: glyphs. ret at: 3 put: minAscii. ret at: 4 put: maxAscii. ret at: 5 put: maxWidth. ret at: 6 put: ascent. ret at: 7 put: descent. ret at: 8 put: pointSize. ^ret. " ^{xTable. glyphs. minAscii. maxAscii. maxWidth. ascent. descent. pointSize}" ! ! !BDFFontReader methodsFor: 'reading' stamp: 'ar 10/25/2005 00:35'! readAttributes | str a | "I don't handle double-quotes correctly, but it works" file reset. [file atEnd] whileFalse: [ str _ self getLine. (str beginsWith: 'STARTCHAR') ifTrue: [file skip: (0 - str size - 1). ^self]. a _ str substrings. properties at: a first asSymbol put: a allButFirst. ]. self error: 'file seems corrupted'.! ! !BDFFontReader methodsFor: 'reading' stamp: 'ar 10/25/2005 00:35'! readChars | strikeWidth ascent descent minAscii maxAscii maxWidth chars charsNum height form encoding bbx array width pointSize stream | form _ encoding _ bbx _ nil. self readAttributes. height _ Integer readFromString: ((properties at: #FONTBOUNDINGBOX) at: 2). ascent _ Integer readFromString: (properties at: 'FONT_ASCENT' asSymbol) first. descent _ Integer readFromString: (properties at: 'FONT_DESCENT' asSymbol) first. pointSize _ (Integer readFromString: (properties at: 'POINT_SIZE' asSymbol) first) // 10. maxWidth _ 0. minAscii _ 9999. strikeWidth _ 0. maxAscii _ 0. charsNum _ Integer readFromString: (properties at: #CHARS) first. chars _ Set new: charsNum. 1 to: charsNum do: [:i | array _ self readOneCharacter. stream _ ReadStream on: array. form _ stream next. encoding _ stream next. bbx _ stream next. form ifNotNil: [ width _ bbx at: 1. maxWidth _ maxWidth max: width. minAscii _ minAscii min: encoding. maxAscii _ maxAscii max: encoding. strikeWidth _ strikeWidth + width. chars add: array. ]. ]. chars _ chars asSortedCollection: [:x :y | (x at: 2) <= (y at: 2)]. ^ chars. ! ! !BDFFontReader methodsFor: 'reading' stamp: 'ar 10/25/2005 00:35'! readOneCharacter | str a encoding bbx form bits hi low pos | ((str _ self getLine) beginsWith: 'ENDFONT') ifTrue: [^ {nil. nil. nil}]. (str beginsWith: 'STARTCHAR') ifFalse: [self errorFileFormat]. ((str _ self getLine) beginsWith: 'ENCODING') ifFalse: [self errorFileFormat]. encoding _ Integer readFromString: str substrings second. (self getLine beginsWith: 'SWIDTH') ifFalse: [self errorFileFormat]. (self getLine beginsWith: 'DWIDTH') ifFalse: [self errorFileFormat]. ((str _ self getLine) beginsWith: 'BBX') ifFalse: [self errorFileFormat]. a _ str substrings. bbx _ (2 to: 5) collect: [:i | Integer readFromString: (a at: i)]. ((str _ self getLine) beginsWith: 'ATTRIBUTES') ifTrue: [str _ self getLine]. (str beginsWith: 'BITMAP') ifFalse: [self errorFileFormat]. form _ Form extent: (bbx at: 1)@(bbx at: 2). bits _ form bits. pos _ 0. 1 to: (bbx at: 2) do: [:t | 1 to: (((bbx at: 1) - 1) // 8 + 1) do: [:i | hi _ (('0123456789ABCDEF' indexOf: (file next asUppercase)) - 1) bitShift: 4. low _ ('0123456789ABCDEF' indexOf: (file next asUppercase)) - 1. bits byteAt: (pos+i) put: (hi+low). ]. file next ~= Character cr ifTrue: [self errorFileFormat]. pos _ pos + ((((bbx at: 1) // 32) + 1) * 4). ]. (self getLine beginsWith: 'ENDCHAR') ifFalse: [self errorFileFormat]. encoding < 0 ifTrue: [^{nil. nil. nil}]. ^{form. encoding. bbx}. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BDFFontReader class instanceVariableNames: ''! !BDFFontReader class methodsFor: 'documentation' stamp: 'nop 2/11/2001 00:22'! gettingAndInstallingTheFonts "Download the 1.3M of BDF font source files from x.org: BDFFontReader downloadFonts. Convert them to .sf2 StrikeFont files: BDFFontReader convertX11FontsToStrike2. Install them into the system as TextStyles: BDFFontReader installX11Fonts. Read the legal notices in 'BDFFontReader x11FontLegalNotices' before redistributing images containing these fonts."! ! !BDFFontReader class methodsFor: 'documentation' stamp: 'nop 1/23/2000 18:30'! x11FontLegalNotices ^ 'The X11 BDF fonts contain copyright and license information as comments in the font source code. For the font family files "cour" (Courier), "helv" (Helvetica), "ncen" (New Century Schoolbook), and "tim" (Times Roman) the notice reads: COMMENT Copyright 1984-1989, 1994 Adobe Systems Incorporated. COMMENT Copyright 1988, 1994 Digital Equipment Corporation. COMMENT COMMENT Adobe is a trademark of Adobe Systems Incorporated which may be COMMENT registered in certain jurisdictions. COMMENT Permission to use these trademarks is hereby granted only in COMMENT association with the images described in this file. COMMENT COMMENT Permission to use, copy, modify, distribute and sell this software COMMENT and its documentation for any purpose and without fee is hereby COMMENT granted, provided that the above copyright notices appear in all COMMENT copies and that both those copyright notices and this permission COMMENT notice appear in supporting documentation, and that the names of COMMENT Adobe Systems and Digital Equipment Corporation not be used in COMMENT advertising or publicity pertaining to distribution of the software COMMENT without specific, written prior permission. Adobe Systems and COMMENT Digital Equipment Corporation make no representations about the COMMENT suitability of this software for any purpose. It is provided "as COMMENT is" without express or implied warranty. For the font family files "char" (Charter), the notice reads: COMMENT Copyright 1988 Bitstream, Inc., Cambridge, Massachusetts, USA COMMENT Bitstream and Charter are registered trademarks of Bitstream, Inc. COMMENT COMMENT The names "Bitstream" and "Charter" are registered trademarks of COMMENT Bitstream, Inc. Permission to use these trademarks is hereby COMMENT granted only in association with the images described in this file. COMMENT COMMENT Permission to use, copy, modify, and distribute this software and COMMENT its documentation for any purpose and without fee is hereby COMMENT granted, provided that the above copyright notice appear in all COMMENT copies and that both that copyright notice and this permission COMMENT notice appear in supporting documentation, and that the name of COMMENT Bitstream not be used in advertising or publicity pertaining to COMMENT distribution of the software without specific, written prior COMMENT permission. Bitstream makes no representations about the COMMENT suitability of this software for any purpose. It is provided "as COMMENT is" without express or implied warranty. COMMENT COMMENT BITSTREAM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, COMMENT INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN COMMENT NO EVENT SHALL BITSTREAM BE LIABLE FOR ANY SPECIAL, INDIRECT OR COMMENT CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS COMMENT OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, COMMENT NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN COMMENT CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. For the font family files "lu" (Lucida), "lub" (Lucida Bright), and "lut" (Lucida Typewriter), the notice reads: COMMENT (c) Copyright Bigelow & Holmes 1986, 1985. Lucida is a registered COMMENT trademark of Bigelow & Holmes. See LEGAL NOTICE file for terms COMMENT of the license. The LEGAL NOTICE contains: This is the LEGAL NOTICE pertaining to the Lucida fonts from Bigelow & Holmes: NOTICE TO USER: The source code, including the glyphs or icons forming a par of the OPEN LOOK TM Graphic User Interface, on this tape and in these files is copyrighted under U.S. and international laws. Sun Microsystems, Inc. of Mountain View, California owns the copyright and has design patents pending on many of the icons. AT&T is the owner of the OPEN LOOK trademark associated with the materials on this tape. Users and possessors of this source code are hereby granted a nonexclusive, royalty-free copyright and design patent license to use this code in individual and commercial software. A royalty-free, nonexclusive trademark license to refer to the code and output as "OPEN LOOK" compatible is available from AT&T if, and only if, the appearance of the icons or glyphs is not changed in any manner except as absolutely necessary to accommodate the standard resolution of the screen or other output device, the code and output is not changed except as authorized herein, and the code and output is validated by AT&T. Bigelow & Holmes is the owner of the Lucida (R) trademark for the fonts and bit-mapped images associated with the materials on this tape. Users are granted a royalty-free, nonexclusive license to use the trademark only to identify the fonts and bit-mapped images if, and only if, the fonts and bit-mapped images are not modified in any way by the user. Any use of this source code must include, in the user documentation and internal comments to the code, notices to the end user as follows: (c) Copyright 1989 Sun Microsystems, Inc. Sun design patents pending in the U.S. and foreign countries. OPEN LOOK is a trademark of AT&T. Used by written permission of the owners. (c) Copyright Bigelow & Holmes 1986, 1985. Lucida is a registered trademark of Bigelow & Holmes. Permission to use the Lucida trademark is hereby granted only in association with the images and fonts described in this file. SUN MICROSYSTEMS, INC., AT&T, AND BIGELOW & HOLMES MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. SUN MICROSYSTEMS, INC., AT&T AND BIGELOW & HOLMES, SEVERALLY AND INDIVIDUALLY, DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SUN MICROSYSTEMS, INC., AT&T OR BIGELOW & HOLMES BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. '. ! ! !BDFFontReader class methodsFor: 'file creation' stamp: 'nop 1/23/2000 19:00'! convertFilesNamed: fileName toFamilyNamed: familyName inDirectoryNamed: dirName "BDFFontReader convertFilesNamed: 'helvR' toFamilyNamed: 'Helvetica' inDirectoryNamed: '' " "This utility converts X11 BDF font files to Squeak .sf2 StrikeFont files." "For this utility to work as is, the BDF files must be named 'familyNN.bdf', and must reside in the directory named by dirName (use '' for the current directory). The output StrikeFont files will be named familyNN.sf2, and will be placed in the current directory." | f allFontNames sizeChars dir | "Check for matching file names." dir _ dirName isEmpty ifTrue: [FileDirectory default] ifFalse: [FileDirectory default directoryNamed: dirName]. allFontNames _ dir fileNamesMatching: fileName , '##.bdf'. allFontNames isEmpty ifTrue: [^ self error: 'No files found like ' , fileName , 'NN.bdf']. Utilities informUserDuring: [:info | allFontNames do: [:fname | info value: 'Converting ', familyName, ' BDF file ', fname, ' to SF2 format'. sizeChars _ (fname copyFrom: fileName size + 1 to: fname size) copyUpTo: $. . f _ StrikeFont new readBDFFromFile: (dir fullNameFor: fname) name: familyName, sizeChars. f writeAsStrike2named: familyName, sizeChars, '.sf2'. ]. ]! ! !BDFFontReader class methodsFor: 'file creation' stamp: 'yo 5/25/2004 10:52'! new ^ self basicNew. ! ! !BDFFontReader class methodsFor: 'file creation' stamp: 'ar 10/25/2005 00:21'! openFileNamed: fileName ^self new openFileNamed: fileName! ! !BDFFontReader class methodsFor: 'resource download' stamp: 'nop 1/23/2000 18:43'! convertX11FontsToStrike2 "BDFFontReader convertX11FontsToStrike2" "Given a set of standard X11 BDF font files (probably downloaded via BDFFontReader downloadFonts), produce .sf2 format fonts. The source and destination directory is the current directory." "Charter currently tickles a bug in the BDF parser. Skip it for now." "self convertFilesNamed: 'charR' toFamilyNamed: 'Charter' inDirectoryNamed: ''." self convertFilesNamed: 'courR' toFamilyNamed: 'Courier' inDirectoryNamed: ''. self convertFilesNamed: 'helvR' toFamilyNamed: 'Helvetica' inDirectoryNamed: ''. self convertFilesNamed: 'lubR' toFamilyNamed: 'LucidaBright' inDirectoryNamed: ''. self convertFilesNamed: 'luRS' toFamilyNamed: 'Lucida' inDirectoryNamed: ''. self convertFilesNamed: 'lutRS' toFamilyNamed: 'LucidaTypewriter' inDirectoryNamed: ''. self convertFilesNamed: 'ncenR' toFamilyNamed: 'NewCenturySchoolbook' inDirectoryNamed: ''. self convertFilesNamed: 'timR' toFamilyNamed: 'TimesRoman' inDirectoryNamed: ''.! ! !BDFFontReader class methodsFor: 'resource download' stamp: 'nop 2/11/2001 00:24'! downloadFonts "BDFFontReader downloadFonts" "Download a standard set of BDF sources from x.org. The combined size of these source files is around 1.2M; after conversion to .sf2 format they may be deleted." | heads tails filenames baseUrl basePath newUrl newPath document f | heads _ #( 'charR' 'courR' 'helvR' 'lubR' 'luRS' 'lutRS' 'ncenR' 'timR' ). tails _ #( '08' '10' '12' '14' '18' '24'). filenames _ OrderedCollection new. heads do: [:head | filenames addAll: (tails collect: [:tail | head , tail , '.bdf']) ]. baseUrl _ Url absoluteFromText: 'http://ftp.x.org/pub/R6.4/xc/fonts/bdf/75dpi/'. basePath _ baseUrl path. filenames do: [:filename | newUrl _ baseUrl clone. newPath _ OrderedCollection newFrom: basePath. newPath addLast: filename. newUrl path: newPath. Utilities informUser: 'Fetching ' , filename during: [document _ newUrl retrieveContents]. f _ CrLfFileStream newFileNamed: filename. f nextPutAll: document content. f close. ]. ! ! !BDFFontReader class methodsFor: 'resource download' stamp: 'nop 1/23/2000 18:44'! installX11Fonts "BDFFontReader installX11Fonts" "Installs previously-converted .sf2 fonts into the TextConstants dictionary. This makes them available as TextStyles everywhere in the image." | families fontArray textStyle | families _ #( 'Courier' 'Helvetica' 'LucidaBright' 'Lucida' 'LucidaTypewriter' 'NewCenturySchoolbook' 'TimesRoman' ). families do: [:family | fontArray _ StrikeFont readStrikeFont2Family: family. textStyle _ TextStyle fontArray: fontArray. TextConstants at: family asSymbol put: textStyle. ]. ! ! ImageReadWriter subclass: #BMPReadWriter instanceVariableNames: 'bfType bfSize bfOffBits biSize biWidth biHeight biPlanes biBitCount biCompression biSizeImage biXPelsPerMeter biYPelsPerMeter biClrUsed biClrImportant' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Files'! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 6/16/2002 15:36'! nextImage | colors | stream binary. self readHeader. biBitCount = 24 ifTrue:[^self read24BmpFile]. "read the color map" colors := self readColorMap. ^self readIndexedBmpFile: colors! ! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 10/25/2005 13:48'! read24BmpFile "Read 24-bit pixel data from the given a BMP stream." | form formBits pixelLine bitsIndex bitBlt | form _ Form extent: biWidth@biHeight depth: 32. pixelLine := ByteArray new: (((24 * biWidth) + 31) // 32) * 4. bitsIndex := form height - 1 * biWidth + 1. formBits := form bits. 1 to: biHeight do: [:i | pixelLine := stream nextInto: pixelLine. self read24BmpLine: pixelLine into: formBits startingAt: bitsIndex width: biWidth. bitsIndex := bitsIndex - biWidth. ]. bitBlt := BitBlt toForm: form. bitBlt combinationRule: 7 "bitOr:with:". bitBlt halftoneForm: (Bitmap with: 16rFF000000). bitBlt copyBits. ^ form ! ! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 10/25/2005 13:47'! read24BmpLine: pixelLine into: formBits startingAt: formBitsIndex width: width | pixIndex rgb bitsIndex | pixIndex _ 0. "pre-increment" bitsIndex := formBitsIndex-1. "pre-increment" 1 to: width do: [:j | rgb := (pixelLine at: (pixIndex := pixIndex+1)) + ((pixelLine at: (pixIndex := pixIndex+1)) bitShift: 8) + ((pixelLine at: (pixIndex := pixIndex+1)) bitShift: 16). formBits at: (bitsIndex := bitsIndex+1) put: rgb. ]. ! ! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 10/24/2005 20:57'! readColorMap "Read colorCount BMP color map entries from the given binary stream. Answer an array of Colors." | colorCount colors maxLevel b g r ccStream | colorCount _ (bfOffBits - 54) // 4. "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" biBitCount >= 16 ifTrue:[^nil]. colorCount = 0 ifTrue: [ "this BMP file does not have a color map" "default monochrome color map" biBitCount = 1 ifTrue: [^ Array with: Color white with: Color black]. "default gray-scale color map" maxLevel _ (2 raisedTo: biBitCount) - 1. ^ (0 to: maxLevel) collect: [:level | Color gray: (level asFloat / maxLevel)]]. ccStream := ReadStream on: (stream next: colorCount*4). colors _ Array new: colorCount. 1 to: colorCount do: [:i | b _ ccStream next. g _ ccStream next. r _ ccStream next. ccStream next. "skip reserved" colors at: i put: (Color r: r g: g b: b range: 255)]. ^ colors ! ! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 6/16/2002 15:20'! readHeader | reserved | bfType _ stream nextLittleEndianNumber: 2. bfSize _ stream nextLittleEndianNumber: 4. reserved _ stream nextLittleEndianNumber: 4. bfOffBits _ stream nextLittleEndianNumber: 4. biSize _ stream nextLittleEndianNumber: 4. biWidth _ stream nextLittleEndianNumber: 4. biHeight _ stream nextLittleEndianNumber: 4. biPlanes _ stream nextLittleEndianNumber: 2. biBitCount _ stream nextLittleEndianNumber: 2. biCompression _ stream nextLittleEndianNumber: 4. biSizeImage _ stream nextLittleEndianNumber: 4. biXPelsPerMeter _ stream nextLittleEndianNumber: 4. biYPelsPerMeter _ stream nextLittleEndianNumber: 4. biClrUsed _ stream nextLittleEndianNumber: 4. biClrImportant _ stream nextLittleEndianNumber: 4. ! ! !BMPReadWriter methodsFor: 'reading' stamp: 'ar 10/25/2005 13:45'! readIndexedBmpFile: colors "Read uncompressed pixel data of depth d from the given BMP stream, where d is 1, 4, 8, or 16" | form bytesPerRow pixelData pixelLine startIndex map bitBlt mask | colors ifNil:[form _ Form extent: biWidth@biHeight depth: biBitCount] ifNotNil:[form _ ColorForm extent: biWidth@biHeight depth: biBitCount. form colors: colors]. bytesPerRow _ (((biBitCount* biWidth) + 31) // 32) * 4. pixelData _ ByteArray new: bytesPerRow * biHeight. biHeight to: 1 by: -1 do: [:y | pixelLine _ stream next: bytesPerRow. startIndex _ ((y - 1) * bytesPerRow) + 1. pixelData replaceFrom: startIndex to: startIndex + bytesPerRow - 1 with: pixelLine startingAt: 1]. form bits copyFromByteArray: pixelData. biBitCount = 16 ifTrue:[ map := ColorMap shifts: #(8 -8 0 0) masks: #(16rFF 16rFF00 0 0). mask := 16r80008000. ]. biBitCount = 32 ifTrue:[ map := ColorMap shifts: #(24 8 -8 -24) masks: #(16rFF 16rFF00 16rFF0000 16rFF000000). mask := 16rFF000000. ]. map ifNotNil:[ bitBlt := BitBlt toForm: form. bitBlt sourceForm: form. bitBlt colorMap: map. bitBlt combinationRule: Form over. bitBlt copyBits. ]. mask ifNotNil:[ bitBlt := BitBlt toForm: form. bitBlt combinationRule: 7 "bitOr:with:". bitBlt halftoneForm: (Bitmap with: mask). bitBlt copyBits. ]. ^ form ! ! !BMPReadWriter methodsFor: 'testing' stamp: 'ar 6/16/2002 15:27'! understandsImageFormat stream size < 54 ifTrue:[^false]. "min size = BITMAPFILEHEADER+BITMAPINFOHEADER" self readHeader. bfType = 19778 "BM" ifFalse:[^false]. biSize = 40 ifFalse:[^false]. biPlanes = 1 ifFalse:[^false]. bfSize <= stream size ifFalse:[^false]. biCompression = 0 ifFalse:[^false]. ^true! ! !BMPReadWriter methodsFor: 'writing' stamp: 'yo 2/18/2004 17:57'! nextPutImage: aForm | bhSize rowBytes rgb data colorValues depth image ppw scanLineLen | depth := aForm depth. [#(1 4 8 32) includes: depth] whileFalse:[depth := depth + 1 asLargerPowerOfTwo]. image := aForm asFormOfDepth: depth. image unhibernate. bhSize _ 14. "# bytes in file header" biSize _ 40. "info header size in bytes" biWidth := image width. biHeight := image height. biClrUsed _ depth = 32 ifTrue: [0] ifFalse:[1 << depth]. "No. color table entries" bfOffBits _ biSize + bhSize + (4*biClrUsed). rowBytes _ ((depth min: 24) * biWidth + 31 // 32) * 4. biSizeImage _ biHeight * rowBytes. "Write the file header" stream position: 0. stream nextLittleEndianNumber: 2 put: 19778. "bfType = BM" stream nextLittleEndianNumber: 4 put: bfOffBits + biSizeImage. "Entire file size in bytes" stream nextLittleEndianNumber: 4 put: 0. "bfReserved" stream nextLittleEndianNumber: 4 put: bfOffBits. "Offset of bitmap data from start of hdr (and file)" "Write the bitmap info header" stream position: bhSize. stream nextLittleEndianNumber: 4 put: biSize. "info header size in bytes" stream nextLittleEndianNumber: 4 put: image width. "biWidth" stream nextLittleEndianNumber: 4 put: image height. "biHeight" stream nextLittleEndianNumber: 2 put: 1. "biPlanes" stream nextLittleEndianNumber: 2 put: (depth min: 24). "biBitCount" stream nextLittleEndianNumber: 4 put: 0. "biCompression" stream nextLittleEndianNumber: 4 put: biSizeImage. "size of image section in bytes" stream nextLittleEndianNumber: 4 put: 2800. "biXPelsPerMeter" stream nextLittleEndianNumber: 4 put: 2800. "biYPelsPerMeter" stream nextLittleEndianNumber: 4 put: biClrUsed. stream nextLittleEndianNumber: 4 put: 0. "biClrImportant" biClrUsed > 0 ifTrue: [ "write color map; this works for ColorForms, too" colorValues _ image colormapIfNeededForDepth: 32. 1 to: biClrUsed do: [:i | rgb _ colorValues at: i. 0 to: 24 by: 8 do: [:j | stream nextPut: (rgb >> j bitAnd: 16rFF)]]]. depth < 32 ifTrue: [ "depth = 1, 4 or 8." data _ image bits asByteArray. ppw _ 32 // depth. scanLineLen _ biWidth + ppw - 1 // ppw * 4. "# of bytes in line" 1 to: biHeight do: [:i | stream next: scanLineLen putAll: data startingAt: (biHeight-i)*scanLineLen+1. ]. ] ifFalse: [ 1 to: biHeight do:[:i | data _ (image copy: (0@(biHeight-i) extent: biWidth@1)) bits. 1 to: data size do: [:j | stream nextLittleEndianNumber: 3 put: (data at: j)]. 1 to: (data size*3)+3//4*4-(data size*3) do: [:j | stream nextPut: 0 "pad to 32-bits"] ]. ]. stream position = (bfOffBits + biSizeImage) ifFalse: [self error:'Write failure']. stream close.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BMPReadWriter class instanceVariableNames: ''! !BMPReadWriter class methodsFor: 'image reading/writing' stamp: 'nk 7/16/2003 17:56'! typicalFileExtensions "Answer a collection of file extensions (lowercase) which files that I can read might commonly have" ^#('bmp')! ! !BMPReadWriter class methodsFor: 'testing' stamp: 'ar 6/16/2002 18:55'! displayAllFrom: fd "BMPReadWriter displayAllFrom: FileDirectory default" fd fileNames do:[:fName| (fName endsWith: '.bmp') ifTrue:[ [(Form fromBinaryStream: (fd readOnlyFileNamed: fName)) display. Display forceDisplayUpdate] on: Error do:[:nix|]. ]. ]. fd directoryNames do:[:fdName| self displayAllFrom: (fd directoryNamed: fdName) ].! ! !BMPReadWriter class methodsFor: 'testing' stamp: 'ar 6/16/2002 18:56'! readAllFrom: fd "MessageTally spyOn:[BMPReadWriter readAllFrom: FileDirectory default]" fd fileNames do:[:fName| (fName endsWith: '.bmp') ifTrue:[ [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] on: Error do:[:nix]. ]. ]. fd directoryNames do:[:fdName| self readAllFrom: (fd directoryNamed: fdName) ].! ! TestCase subclass: #BMPReadWriterTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'GraphicsTests-Files'! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/25/2005 02:22'! bmpData16bit "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest16b.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk24AAAAAAAAADYAAAAoAAAACAAAAAgAAAABABAAAAAAAIIAAADDDgAAww4AAAAAAAAAAAAA 4APgA+AD4AMfAB8AHwAfAOAD4APgA+ADHwAfAB8AHwDgA+AD/3//f/9//38fAB8A4APgA/9/ /3//f/9/HwAfAAAAAAD/f/9//3//fwB8AHwAAAAA/3//f/9//38AfAB8AAAAAAAAAAAAfAB8 AHwAfAAAAAAAAAAAAHwAfAB8AHwAAA==' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/24/2005 21:27'! bmpData24bit "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest24.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk32AAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAAAAAADEDgAAxA4AAAAAAAAAAAAA AP8AAP8AAP8AAP8A/wAA/wAA/wAA/wAAAP8AAP8AAP8AAP8A/wAA/wAA/wAA/wAAAP8AAP8A /////////////////wAA/wAAAP8AAP8A/////////////////wAA/wAAAAAAAAAA//////// ////////AAD/AAD/AAAAAAAA////////////////AAD/AAD/AAAAAAAAAAAAAAAAAAD/AAD/ AAD/AAD/AAAAAAAAAAAAAAAAAAD/AAD/AAD/AAD/' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/25/2005 02:22'! bmpData32bit "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest32b.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk04AQAAAAAAADYAAAAoAAAACAAAAAgAAAABACAAAAAAAAIBAADDDgAAww4AAAAAAAAAAAAA AP8AAAD/AAAA/wAAAP8AAP8AAAD/AAAA/wAAAP8AAAAA/wAAAP8AAAD/AAAA/wAA/wAAAP8A AAD/AAAA/wAAAAD/AAAA/wAA////AP///wD///8A////AP8AAAD/AAAAAP8AAAD/AAD///8A ////AP///wD///8A/wAAAP8AAAAAAAAAAAAAAP///wD///8A////AP///wAAAP8AAAD/AAAA AAAAAAAA////AP///wD///8A////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8A AAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/24/2005 21:41'! bmpData4bit "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest4.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk12BAAAAAAAADYEAAAoAAAACAAAAAgAAAABAAgAAAAAAEAAAADEDgAAxA4AAAAAAAAAAAAA AAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwADA3MAA8MqmAAAgQAAAIGAAACCAAAAg oAAAIMAAACDgAABAAAAAQCAAAEBAAABAYAAAQIAAAECgAABAwAAAQOAAAGAAAABgIAAAYEAA AGBgAABggAAAYKAAAGDAAABg4AAAgAAAAIAgAACAQAAAgGAAAICAAACAoAAAgMAAAIDgAACg AAAAoCAAAKBAAACgYAAAoIAAAKCgAACgwAAAoOAAAMAAAADAIAAAwEAAAMBgAADAgAAAwKAA AMDAAADA4AAA4AAAAOAgAADgQAAA4GAAAOCAAADgoAAA4MAAAODgAEAAAABAACAAQABAAEAA YABAAIAAQACgAEAAwABAAOAAQCAAAEAgIABAIEAAQCBgAEAggABAIKAAQCDAAEAg4ABAQAAA QEAgAEBAQABAQGAAQECAAEBAoABAQMAAQEDgAEBgAABAYCAAQGBAAEBgYABAYIAAQGCgAEBg wABAYOAAQIAAAECAIABAgEAAQIBgAECAgABAgKAAQIDAAECA4ABAoAAAQKAgAECgQABAoGAA QKCAAECgoABAoMAAQKDgAEDAAABAwCAAQMBAAEDAYABAwIAAQMCgAEDAwABAwOAAQOAAAEDg IABA4EAAQOBgAEDggABA4KAAQODAAEDg4ACAAAAAgAAgAIAAQACAAGAAgACAAIAAoACAAMAA gADgAIAgAACAICAAgCBAAIAgYACAIIAAgCCgAIAgwACAIOAAgEAAAIBAIACAQEAAgEBgAIBA gACAQKAAgEDAAIBA4ACAYAAAgGAgAIBgQACAYGAAgGCAAIBgoACAYMAAgGDgAICAAACAgCAA gIBAAICAYACAgIAAgICgAICAwACAgOAAgKAAAICgIACAoEAAgKBgAICggACAoKAAgKDAAICg 4ACAwAAAgMAgAIDAQACAwGAAgMCAAIDAoACAwMAAgMDgAIDgAACA4CAAgOBAAIDgYACA4IAA gOCgAIDgwACA4OAAwAAAAMAAIADAAEAAwABgAMAAgADAAKAAwADAAMAA4ADAIAAAwCAgAMAg QADAIGAAwCCAAMAgoADAIMAAwCDgAMBAAADAQCAAwEBAAMBAYADAQIAAwECgAMBAwADAQOAA wGAAAMBgIADAYEAAwGBgAMBggADAYKAAwGDAAMBg4ADAgAAAwIAgAMCAQADAgGAAwICAAMCA oADAgMAAwIDgAMCgAADAoCAAwKBAAMCgYADAoIAAwKCgAMCgwADAoOAAwMAAAMDAIADAwEAA wMBgAMDAgADAwKAA8Pv/AKSgoACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////APr6 +vr8/Pz8+vr6+vz8/Pz6+v/////8/Pr6//////z8AAD/////+fkAAP/////5+QAAAAD5+fn5 AAAAAPn5+fk=' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/24/2005 21:27'! bmpData8bit "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest8.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk12BAAAAAAAADYEAAAoAAAACAAAAAgAAAABAAgAAAAAAEAAAADEDgAAxA4AAAAAAAAAAAAA AAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwADA3MAA8MqmAAAgQAAAIGAAACCAAAAg oAAAIMAAACDgAABAAAAAQCAAAEBAAABAYAAAQIAAAECgAABAwAAAQOAAAGAAAABgIAAAYEAA AGBgAABggAAAYKAAAGDAAABg4AAAgAAAAIAgAACAQAAAgGAAAICAAACAoAAAgMAAAIDgAACg AAAAoCAAAKBAAACgYAAAoIAAAKCgAACgwAAAoOAAAMAAAADAIAAAwEAAAMBgAADAgAAAwKAA AMDAAADA4AAA4AAAAOAgAADgQAAA4GAAAOCAAADgoAAA4MAAAODgAEAAAABAACAAQABAAEAA YABAAIAAQACgAEAAwABAAOAAQCAAAEAgIABAIEAAQCBgAEAggABAIKAAQCDAAEAg4ABAQAAA QEAgAEBAQABAQGAAQECAAEBAoABAQMAAQEDgAEBgAABAYCAAQGBAAEBgYABAYIAAQGCgAEBg wABAYOAAQIAAAECAIABAgEAAQIBgAECAgABAgKAAQIDAAECA4ABAoAAAQKAgAECgQABAoGAA QKCAAECgoABAoMAAQKDgAEDAAABAwCAAQMBAAEDAYABAwIAAQMCgAEDAwABAwOAAQOAAAEDg IABA4EAAQOBgAEDggABA4KAAQODAAEDg4ACAAAAAgAAgAIAAQACAAGAAgACAAIAAoACAAMAA gADgAIAgAACAICAAgCBAAIAgYACAIIAAgCCgAIAgwACAIOAAgEAAAIBAIACAQEAAgEBgAIBA gACAQKAAgEDAAIBA4ACAYAAAgGAgAIBgQACAYGAAgGCAAIBgoACAYMAAgGDgAICAAACAgCAA gIBAAICAYACAgIAAgICgAICAwACAgOAAgKAAAICgIACAoEAAgKBgAICggACAoKAAgKDAAICg 4ACAwAAAgMAgAIDAQACAwGAAgMCAAIDAoACAwMAAgMDgAIDgAACA4CAAgOBAAIDgYACA4IAA gOCgAIDgwACA4OAAwAAAAMAAIADAAEAAwABgAMAAgADAAKAAwADAAMAA4ADAIAAAwCAgAMAg QADAIGAAwCCAAMAgoADAIMAAwCDgAMBAAADAQCAAwEBAAMBAYADAQIAAwECgAMBAwADAQOAA wGAAAMBgIADAYEAAwGBgAMBggADAYKAAwGDAAMBg4ADAgAAAwIAgAMCAQADAgGAAwICAAMCA oADAgMAAwIDgAMCgAADAoCAAwKBAAMCgYADAoIAAwKCgAMCgwADAoOAAwMAAAMDAIADAwEAA wMBgAMDAgADAwKAA8Pv/AKSgoACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////APr6 +vr8/Pz8+vr6+vz8/Pz6+v/////8/Pr6//////z8AAD/////+fkAAP/////5+QAAAAD5+fn5 AAAAAPn5+fk=' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/25/2005 14:04'! bmpDataR5G6B5 "This is a BMP file based on BitmapV4Header which is currently unsupported." "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest16-R5G6B5.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk3IAAAAAAAAAEYAAAA4AAAACAAAAAgAAAABABAAAwAAAIIAAADDDgAAww4AAAAAAAAAAAAA APgAAOAHAAAfAAAAAAAAAOAH4AfgB+AHHwAfAB8AHwDgB+AH4AfgBx8AHwAfAB8A4AfgB/// ////////HwAfAOAH4Af//////////x8AHwAAAAAA//////////8A+AD4AAAAAP////////// APgA+AAAAAAAAAAAAPgA+AD4APgAAAAAAAAAAAD4APgA+AD4AAA=' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/25/2005 14:04'! bmpDataX4R4G4B4 "This is a BMP file based on BitmapV4Header which is currently unsupported." "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest16-X4R4G4B4.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk3IAAAAAAAAAEYAAAA4AAAACAAAAAgAAAABABAAAwAAAIIAAADDDgAAww4AAAAAAAAAAAAA AA8AAPAAAAAPAAAAAAAAAPAA8ADwAPAADwAPAA8ADwDwAPAA8ADwAA8ADwAPAA8A8ADwAP8P /w//D/8PDwAPAPAA8AD/D/8P/w//Dw8ADwAAAAAA/w//D/8P/w8ADwAPAAAAAP8P/w//D/8P AA8ADwAAAAAAAAAAAA8ADwAPAA8AAAAAAAAAAAAPAA8ADwAPAAA=' readStream) contents! ! !BMPReadWriterTest methodsFor: 'data' stamp: 'ar 10/25/2005 14:05'! bmpDataX8R8G8B8 "This is a BMP file based on BitmapV4Header which is currently unsupported." "Created via: (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: 'bmptest32-X8R8G8B8.bmp') binary) contents " ^(Base64MimeConverter mimeDecodeToBytes: 'Qk1IAQAAAAAAAEYAAAA4AAAACAAAAAgAAAABACAAAwAAAAIBAADDDgAAww4AAAAAAAAAAAAA AAAA/wAA/wAA/wAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA/wAAAP8AAAD/AAAA/wAAAAD/AAAA /wAAAP8AAAD/AAD/AAAA/wAAAP8AAAD/AAAAAP8AAAD/AAD///8A////AP///wD///8A/wAA AP8AAAAA/wAAAP8AAP///wD///8A////AP///wD/AAAA/wAAAAAAAAAAAAAA////AP///wD/ //8A////AAAA/wAAAP8AAAAAAAAAAAD///8A////AP///wD///8AAAD/AAAA/wAAAAAAAAAA AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAA AP8AAA==' readStream) contents! ! !BMPReadWriterTest methodsFor: 'reading' stamp: 'ar 10/25/2005 13:46'! testBmp16Bit | reader form | reader := BMPReadWriter new on: (ReadStream on: self bmpData16bit). form := reader nextImage. "special black here to compensate for zero-is-transparent effect" self assert: (form colorAt: 7@1) = Color red. self assert: (form colorAt: 1@7) = Color green. self assert: (form colorAt: 7@7) = Color blue. self assert: (form colorAt: 4@4) = Color white. self assert: (form pixelValueAt: 1@1) = 16r8000. ! ! !BMPReadWriterTest methodsFor: 'reading' stamp: 'ar 10/25/2005 13:46'! testBmp24Bit | reader form | reader := BMPReadWriter new on: (ReadStream on: self bmpData24bit). form := reader nextImage. self assert: (form colorAt: 7@1) = Color red. self assert: (form colorAt: 1@7) = Color green. self assert: (form colorAt: 7@7) = Color blue. self assert: (form colorAt: 4@4) = Color white. self assert: (form pixelValueAt: 1@1) = 16rFF000000. ! ! !BMPReadWriterTest methodsFor: 'reading' stamp: 'ar 10/25/2005 13:46'! testBmp32Bit | reader form | reader := BMPReadWriter new on: (ReadStream on: self bmpData32bit). form := reader nextImage. self assert: (form colorAt: 7@1) = Color red. self assert: (form colorAt: 1@7) = Color green. self assert: (form colorAt: 7@7) = Color blue. self assert: (form colorAt: 4@4) = Color white. self assert: (form pixelValueAt: 1@1) = 16rFF000000. ! ! !BMPReadWriterTest methodsFor: 'reading' stamp: 'ar 10/24/2005 21:42'! testBmp4Bit | reader form | reader := BMPReadWriter new on: (ReadStream on: self bmpData4bit). form := reader nextImage. self assert: (form colorAt: 1@1) = Color black. self assert: (form colorAt: 7@1) = Color red. self assert: (form colorAt: 1@7) = Color green. self assert: (form colorAt: 7@7) = Color blue. self assert: (form colorAt: 4@4) = Color white. ! ! !BMPReadWriterTest methodsFor: 'reading' stamp: 'ar 10/24/2005 21:30'! testBmp8Bit | reader form | reader := BMPReadWriter new on: (ReadStream on: self bmpData8bit). form := reader nextImage. self assert: (form colorAt: 1@1) = Color black. self assert: (form colorAt: 7@1) = Color red. self assert: (form colorAt: 1@7) = Color green. self assert: (form colorAt: 7@7) = Color blue. self assert: (form colorAt: 4@4) = Color white. ! ! TransformationMorph subclass: #BOBTransformationMorph instanceVariableNames: 'worldBoundsToShow useRegularWarpBlt' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-AdditionalSupport'! !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 6/4/2001 16:19'! drawSubmorphsOnREAL: aCanvas | newClip | (self innerBounds intersects: aCanvas clipRect) ifFalse: [^self]. newClip _ ((self innerBounds intersect: aCanvas clipRect) expandBy: 1) truncated. useRegularWarpBlt == true ifTrue: [ transform scale asFloat = 1.0 ifFalse: [ newClip _ self innerBounds. "avoids gribblies" ]. ^aCanvas transformBy: transform clippingTo: newClip during: [:myCanvas | submorphs reverseDo:[:m | myCanvas fullDrawMorph: m] ] smoothing: smoothing ]. aCanvas transform2By: transform "#transformBy: for pure WarpBlt" clippingTo: newClip during: [:myCanvas | submorphs reverseDo:[:m | myCanvas fullDrawMorph: m] ] smoothing: smoothing ! ! !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 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! ! !BOBTransformationMorph methodsFor: 'drawing' stamp: 'RAA 6/4/2001 16:21'! drawSubmorphsOn: aCanvas | t | t _ [ self drawSubmorphsOnREAL: aCanvas ] timeToRun. "Q1 at: 3 put: t." ! ! !BOBTransformationMorph methodsFor: 'geometry' 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: 'layout' stamp: 'dgd 2/21/2003 23:02'! layoutChanged "use the version from Morph" | myGuy | fullBounds := nil. owner ifNotNil: [owner layoutChanged]. submorphs notEmpty 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: 'private' 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] ! ! Morph subclass: #BackgroundMorph instanceVariableNames: 'image offset delta running' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'accessing' stamp: 'fc 7/24/2004 12:23'! delta ^delta! ! !BackgroundMorph methodsFor: 'accessing' stamp: 'fc 7/24/2004 12:24'! delta: aPoint delta _ aPoint.! ! !BackgroundMorph methodsFor: 'accessing' stamp: 'fc 7/24/2004 13:50'! offset ^offset! ! !BackgroundMorph methodsFor: 'accessing' stamp: 'fc 7/24/2004 13:50'! offset: aPoint offset _ aPoint! ! !BackgroundMorph methodsFor: 'accessing'! slideBy: inc submorphs isEmpty ifTrue: [^ self]. offset _ offset + inc \\ self subBounds extent. self changed! ! !BackgroundMorph methodsFor: 'accessing'! startRunning running _ true. self changed! ! !BackgroundMorph methodsFor: 'accessing'! stopRunning running _ false. self changed! ! !BackgroundMorph methodsFor: 'accessing' stamp: 'aoy 2/17/2003 01:20'! subBounds "calculate the submorph bounds" | subBounds | subBounds := nil. self submorphsDo: [:m | subBounds := subBounds isNil ifTrue: [m fullBounds] ifFalse: [subBounds merge: m fullBounds]]. ^subBounds! ! !BackgroundMorph methodsFor: 'drawing' 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: 'drawing' stamp: 'ar 12/30/2001 19:16'! fullDrawOn: aCanvas (aCanvas isVisible: self fullBounds) ifFalse:[^self]. running ifFalse: [ ^aCanvas clipBy: (bounds translateBy: aCanvas origin) during:[:clippedCanvas| super fullDrawOn: clippedCanvas]]. (aCanvas isVisible: self bounds) ifTrue:[aCanvas drawMorph: self]. ! ! !BackgroundMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:43'! initialize "initialize the state of the receiver" super initialize. "" offset _ 0 @ 0. delta _ 1 @ 0. running _ true! ! !BackgroundMorph methodsFor: 'layout'! fullBounds ^ self bounds! ! !BackgroundMorph methodsFor: 'layout'! layoutChanged "Do nothing, since I clip my submorphs"! ! !BackgroundMorph methodsFor: 'menus' stamp: 'dgd 8/30/2003 20:56'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. running ifTrue: [aCustomMenu add: 'stop' translated action: #stopRunning] ifFalse: [aCustomMenu add: 'start' translated action: #startRunning]! ! !BackgroundMorph methodsFor: 'stepping and presenter' stamp: 'fc 7/24/2004 13:47'! step running ifTrue: [self slideBy: delta]! ! !BackgroundMorph methodsFor: 'testing'! stepTime "Answer the desired time between steps in milliseconds." ^ 20! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BackgroundMorph class instanceVariableNames: ''! !BackgroundMorph class methodsFor: 'as yet unclassified' stamp: 'kfr 8/7/2004 16:10'! test "BackgroundMorph test" ^(BackgroundMorph new addMorph: (ImageMorph new image: Form fromUser))openInWorld.! ! Object subclass: #BadEqualer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Utilities'! !BadEqualer commentStamp: 'mjr 8/20/2003 13:28' prior: 0! I am an object that doesn't always report #= correctly. Used for testing the EqualityTester.! !BadEqualer methodsFor: 'comparing' stamp: 'mjr 8/20/2003 18:56'! = other self class = other class ifFalse: [^ false]. ^ 100 atRandom < 30 ! ! Object subclass: #BadHasher instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Utilities'! !BadHasher commentStamp: 'mjr 8/20/2003 13:28' prior: 0! I am an object that doesn't always hash correctly. I am used for testing the HashTester.! !BadHasher methodsFor: 'comparing' stamp: 'mjr 8/20/2003 18:56'! hash "answer with a different hash some of the time" 100 atRandom < 30 ifTrue: [^ 1]. ^ 2! ! 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: 'accessing' stamp: 'md 1/20/2006 15:58'! valuesAndCounts ^ contents! ! !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: 'comparing' stamp: 'md 10/17/2004 16:09'! = aBag "Two bags are equal if (a) they are the same 'kind' of thing. (b) they have the same size. (c) each element occurs the same number of times in both of them" (aBag isKindOf: Bag) ifFalse: [^false]. self size = aBag size ifFalse: [^false]. contents associationsDo: [:assoc| (aBag occurrencesOf: assoc key) = assoc value ifFalse: [^false]]. ^true ! ! !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: '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 methodsFor: 'private' stamp: 'sma 5/12/2000 14:49'! setContents: aDictionary contents _ aDictionary! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Bag class instanceVariableNames: ''! !Bag class methodsFor: 'instance creation' stamp: 'nk 3/17/2001 09:52'! contentsClass ^Dictionary! ! !Bag class methodsFor: 'instance creation' stamp: 'sma 5/12/2000 13:31'! new ^ self new: 4! ! !Bag class methodsFor: 'instance creation' stamp: 'nk 3/17/2001 09:52'! new: nElements ^ super new setContents: (self contentsClass 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 "! ! TestCase subclass: #BagTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Unordered'! !BagTest methodsFor: 'basic tests' stamp: 'sd 3/21/2006 22:28'! testAdd "self run: #testAdd" "self debug: #testAdd" | aBag | aBag := Bag new. aBag add: 'a'. aBag add: 'b'. self assert: aBag size = 2. aBag add: 'a'. self assert: aBag size = 3. self assert: (aBag occurrencesOf: 'a') = 2 ! ! !BagTest methodsFor: 'basic tests' stamp: 'sd 3/21/2006 22:28'! testAddWithOccurrences "self debug:#testAddWithOccurrences" | aBag | aBag := Bag new. aBag add: 'a' withOccurrences: 3. self assert: (aBag size = 3). ! ! !BagTest methodsFor: 'basic tests' stamp: 'TJ 3/8/2006 08:42'! testAsBag | aBag | aBag := Bag new. self assert: aBag asBag = aBag.! ! !BagTest methodsFor: 'basic tests' stamp: 'sd 3/21/2006 22:29'! testAsSet | aBag aSet | aBag := Bag new. aBag add:'a' withOccurrences: 4. aBag add:'b' withOccurrences: 2. aSet := aBag asSet. self assert: aSet size = 2. self assert: (aSet occurrencesOf: 'a') = 1 ! ! !BagTest methodsFor: 'basic tests' stamp: 'sd 3/21/2006 22:30'! testCopy "self run: #testCopy" | aBag newBag | aBag := Bag new. aBag add:'a' withOccurrences: 4. aBag add:'b' withOccurrences: 2. newBag := aBag copy. self assert: newBag = newBag. self assert: newBag asSet size = 2.! ! !BagTest methodsFor: 'basic tests' stamp: 'sd 3/21/2006 22:32'! testOccurrencesOf "self debug: #testOccurrencesOf" | aBag | aBag := Bag new. aBag add: 'a' withOccurrences: 3. aBag add: 'b'. aBag add: 'b'. aBag add: 'b'. aBag add: 'b'. self assert: (aBag occurrencesOf:'a') = 3. self assert: (aBag occurrencesOf:'b') = 4. self assert: (aBag occurrencesOf:'c') = 0. self assert: (aBag occurrencesOf: nil) =0. aBag add: nil. self assert: (aBag occurrencesOf: nil) =1. ! ! !BagTest methodsFor: 'tests' stamp: 'EP 2/28/2006 09:56'! testCreation "self run: #testCreation" "self debug: #testCreation" | bag | bag := Bag new. self assert: (bag size) = 0. self assert: (bag isEmpty). ! ! !BagTest methodsFor: 'tests' stamp: 'EP 2/28/2006 10:05'! testCumulativeCounts "self run: #testCumulativeCounts" "self debug: #testCumulativeCounts" | bag cumulativeCounts | bag := Bag new. bag add: '1' withOccurrences: 50. bag add: '2' withOccurrences: 40. bag add: '3' withOccurrences: 10. cumulativeCounts := bag cumulativeCounts. self assert: cumulativeCounts size = 3. self assert: cumulativeCounts first = (50 -> '1'). self assert: cumulativeCounts second = (90 -> '2'). self assert: cumulativeCounts third = (100 -> '3'). ! ! !BagTest methodsFor: 'tests' stamp: 'EP 3/8/2006 08:39'! testEqual "(self run: #testEqual)" "(self debug: #testEqual)" | bag1 bag2 | bag1 := Bag new. bag2 := Bag new. self assert: bag1 = bag2. bag1 add: #a; add: #b. bag2 add: #a; add: #a. self deny: bag1 = bag2. self assert: bag1 = bag1. bag1 add: #a. bag2 add: #b. self assert: bag1 = bag2. bag1 add: #c. self deny: bag1 = bag2. bag2 add: #c. self assert: bag1 = bag2! ! !BagTest methodsFor: 'tests' stamp: 'EP 2/28/2006 09:57'! testRemove "self run: #testRemove" "self debug: #testRemove" | bag item | item := 'test item'. bag := Bag new. bag add: item. self assert: (bag size) = 1. bag remove: item. self assert: bag isEmpty. bag add: item withOccurrences: 2. bag remove: item. bag remove: item. self assert: (bag size) = 0. self should: [bag remove: item.] raise: Error.! ! !BagTest methodsFor: 'tests' stamp: 'EP 2/28/2006 09:48'! testSortedCounts "self run: #testSortedCounts" "self debug: #testSortedCounts" | bag sortedCounts| bag := Bag new. bag add: '1' withOccurrences: 10. bag add: '2' withOccurrences: 1. bag add: '3' withOccurrences: 5. sortedCounts := bag sortedCounts. self assert: sortedCounts size = 3. self assert: sortedCounts first = (10->'1'). self assert: sortedCounts second = (5->'3'). self assert: sortedCounts third = (1->'2'). ! ! !BagTest methodsFor: 'tests' stamp: 'EP 2/28/2006 09:48'! testSortedElements "self run: #testSortedElements" "self debug: #testSortedElements" | bag sortedElements| bag := Bag new. bag add: '2' withOccurrences: 1. bag add: '1' withOccurrences: 10. bag add: '3' withOccurrences: 5. sortedElements := bag sortedElements. self assert: sortedElements size = 3. self assert: sortedElements first = ('1'->10). self assert: sortedElements second = ('2'->1). self assert: sortedElements third = ('3'->5). ! ! Object subclass: #BalloonBezierSimulation instanceVariableNames: 'start end via lastX lastY fwDx fwDy fwDDx fwDDy maxSteps' classVariableNames: 'HeightSubdivisions LineConversions MonotonSubdivisions OverflowSubdivisions' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonBezierSimulation commentStamp: '' prior: 0! This class is a simulation of the code that's run by the Balloon engine. For debugging purposes only.! !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 commentStamp: '' prior: 0! BalloonBuffer is a repository for primitive data used by the BalloonEngine.! !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'! new ^self new: 256.! ! FormCanvas subclass: #BalloonCanvas instanceVariableNames: 'transform colorTransform engine aaLevel deferred' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Balloon'! !BalloonCanvas commentStamp: '' prior: 0! BalloonCanvas is a canvas using the BalloonEngine for drawing wherever possible. It has various methods which other canvases do not support due to the extra features of the balloon engine.! !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 2/13/2001 21:07'! ensuredEngine engine ifNil:[ engine _ BalloonEngine new. "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: '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: 'converting' stamp: 'ar 11/11/1998 22:57'! asBalloonCanvas ^self! ! !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: 'nk 5/1/2004 12:25'! frameRectangle: r width: w color: c "Draw a frame around the given rectangle" ^self frameAndFillRectangle: r fillColor: Color transparent 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: '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 8/26/2001 22:14'! drawPolygon: vertices fillStyle: aFillStyle "Fill the given polygon." self drawPolygon: vertices fillStyle: aFillStyle borderWidth: 0 borderColor: nil! ! !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 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: '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: 'TODO' stamp: 'ar 12/31/2001 02:27'! drawString: s from: firstIndex to: lastIndex in: boundsRect font: fontOrNil color: c (self ifNoTransformWithIn: boundsRect) ifTrue:[^super drawString: s from: firstIndex to: lastIndex in: boundsRect font: fontOrNil color: c]! ! !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: '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: '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: 'nk 5/1/2004 12:54'! 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: form) 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."! ! 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 commentStamp: '' prior: 0! BalloonEngine is the representative for the Balloon engine inside Squeak. For most purposes it should not be used directly but via BalloonCanvas since this ensures proper initialization and is polymorphic with other canvas uses.! !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: '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 3/6/2001 12:06'! copyBits (bitBlt notNil and:[bitBlt destForm notNil]) ifTrue:[bitBlt destForm unhibernate]. 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: '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: 'bf 4/3/2004 01:36'! 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 asSourceForm. 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: '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: '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: 'nk 9/26/2003 10:52'! initialize | w | w := Display width > 2048 ifTrue: [ 4096 ] ifFalse: [ 2048 ]. externals := OrderedCollection new: 100. span := Bitmap new: w. 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: '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: '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: '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: '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: '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]. ].! ! SharedPool subclass: #BalloonEngineConstants instanceVariableNames: '' classVariableNames: 'BEAaLevelIndex BEBalloonEngineSize BEBitBltIndex BEClipRectIndex BEColorTransformIndex BEDeferredIndex BEDestOffsetIndex BEEdgeTransformIndex BEExternalsIndex BEFormsIndex BEPostFlushNeededIndex BESpanIndex BEWorkBufferIndex ETBalloonEdgeDataSize ETIndexIndex ETLinesIndex ETSourceIndex ETXValueIndex ETYValueIndex ETZValueIndex FTBalloonFillDataSize FTDestFormIndex FTIndexIndex FTMaxXIndex FTMinXIndex FTSourceIndex FTYValueIndex GBBaseSize GBBitmapDepth GBBitmapHeight GBBitmapRaster GBBitmapSize GBBitmapWidth GBColormapOffset GBColormapSize GBEndX GBEndY GBFinalX GBMBaseSize GBTileFlag GBUpdateDDX GBUpdateDDY GBUpdateDX GBUpdateDY GBUpdateData GBUpdateX GBUpdateY GBViaX GBViaY GBWideEntry GBWideExit GBWideExtent GBWideFill GBWideSize GBWideUpdateData GBWideWidth GEBaseEdgeSize GEBaseFillSize GEEdgeClipFlag GEEdgeFillsInvalid GEFillIndexLeft GEFillIndexRight GENumLines GEObjectIndex GEObjectLength GEObjectType GEObjectUnused GEPrimitiveBezier GEPrimitiveClippedBitmapFill GEPrimitiveEdge GEPrimitiveEdgeMask GEPrimitiveFill GEPrimitiveFillMask GEPrimitiveLine GEPrimitiveLinearGradientFill GEPrimitiveRadialGradientFill GEPrimitiveRepeatedBitmapFill GEPrimitiveTypeMask GEPrimitiveUnknown GEPrimitiveWide GEPrimitiveWideBezier GEPrimitiveWideEdge GEPrimitiveWideLine GEPrimitiveWideMask GEStateAddingFromGET GEStateBlitBuffer GEStateCompleted GEStateScanningAET GEStateUnlocked GEStateUpdateEdges GEStateWaitingChange GEStateWaitingForEdge GEStateWaitingForFill GEXValue GEYValue GEZValue GErrorAETEntry GErrorBadState GErrorFillEntry GErrorGETEntry GErrorNeedFlush GErrorNoMoreSpace GFDirectionX GFDirectionY GFNormalX GFNormalY GFOriginX GFOriginY GFRampLength GFRampOffset GGBaseSize GLBaseSize GLEndX GLEndY GLError GLErrorAdjDown GLErrorAdjUp GLWideEntry GLWideExit GLWideExtent GLWideFill GLWideSize GLWideWidth GLXDirection GLXIncrement GLYDirection GWAAColorMask GWAAColorShift GWAAHalfPixel GWAALevel GWAAScanMask GWAAShift GWAETStart GWAETUsed GWBezierHeightSubdivisions GWBezierLineConversions GWBezierMonotonSubdivisions GWBezierOverflowSubdivisions GWBufferTop GWClearSpanBuffer GWClipMaxX GWClipMaxY GWClipMinX GWClipMinY GWColorTransform GWCountAddAETEntry GWCountChangeAETEntry GWCountDisplaySpan GWCountFinishTest GWCountInitializing GWCountMergeFill GWCountNextAETEntry GWCountNextFillEntry GWCountNextGETEntry GWCurrentY GWCurrentZ GWDestOffsetX GWDestOffsetY GWEdgeTransform GWFillMaxX GWFillMaxY GWFillMinX GWFillMinY GWFillOffsetX GWFillOffsetY GWGETStart GWGETUsed GWHasClipShapes GWHasColorTransform GWHasEdgeTransform GWHeaderSize GWLastExportedEdge GWLastExportedFill GWLastExportedLeftX GWLastExportedRightX GWMagicIndex GWMagicNumber GWMinimalSize GWNeedsFlush GWObjStart GWObjUsed GWPoint1 GWPoint2 GWPoint3 GWPoint4 GWPointListFirst GWSize GWSpanEnd GWSpanEndAA GWSpanSize GWSpanStart GWState GWStopReason GWTimeAddAETEntry GWTimeChangeAETEntry GWTimeDisplaySpan GWTimeFinishTest GWTimeInitializing GWTimeMergeFill GWTimeNextAETEntry GWTimeNextFillEntry GWTimeNextGETEntry' poolDictionaries: '' category: 'Balloon-Engine'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BalloonEngineConstants class instanceVariableNames: ''! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 19:55'! initEdgeConstants "Initialize the edge constants" "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" ! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 20:08'! initFillConstants "Initialize the fill constants" "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" ! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 19:59'! initPrimitiveConstants "Initialize the primitive constants" "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" ! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 20:00'! initStateConstants "Initialize the state Constants" 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" ! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 20:04'! initWorkBufferConstants "Initialize the work buffer constants" "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" ! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 20:08'! initialize "BalloonEngineConstants initialize" self initStateConstants. self initWorkBufferConstants. self initPrimitiveConstants. self initEdgeConstants. self initFillConstants. self initializeInstVarNames: BalloonEngine prefixedBy: 'BE'. self initializeInstVarNames: BalloonEdgeData prefixedBy: 'ET'. self initializeInstVarNames: BalloonFillData prefixedBy: 'FT'.! ! !BalloonEngineConstants class methodsFor: 'pool definition' stamp: 'ar 5/18/2003 20:07'! initializeInstVarNames: aClass 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. (self bindingOf: token) ifNil:[self addClassVarName: token]. (self bindingOf: token) value: value. ]. token := (aString, aClass name,'Size') asSymbol. (self bindingOf: token) ifNil:[self addClassVarName: token]. (self bindingOf: token) value: aClass instSize.! ! Object subclass: #BalloonFillData instanceVariableNames: 'index minX maxX yValue source destForm' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonFillData commentStamp: '' prior: 0! This class is a simulation of the code that's run by the Balloon engine. For debugging purposes only.! !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! ! TestCase subclass: #BalloonFontTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicTests-Widgets'! !BalloonFontTest methodsFor: 'tests' stamp: 'sd 12/9/2001 21:44'! testDefaultFont "(self selector: #testDefaultFont) debug" self assert: RectangleMorph new balloonFont = BalloonMorph balloonFont. self assert: RectangleMorph new defaultBalloonFont = BalloonMorph balloonFont.! ! !BalloonFontTest methodsFor: 'tests' stamp: 'sd 12/9/2001 21:55'! testSpecificFont "(self selector: #testSpecificFont) debug" | aMorph | aMorph := RectangleMorph new. self assert: RectangleMorph new balloonFont = BalloonMorph balloonFont. self assert: RectangleMorph new defaultBalloonFont = BalloonMorph balloonFont. aMorph balloonFont: (StrikeFont familyName: #ComicPlain size: 19). self assert: aMorph balloonFont = (StrikeFont familyName: #ComicPlain size: 19). "The next test is horrible because I do no know how to access the font with the appropiate interface" self assert: (((BalloonMorph getTextMorph: 'lulu' for: aMorph) text runs at: 1) at: 1) font = (StrikeFont familyName: #ComicPlain size: 19)! ! Object subclass: #BalloonLineSimulation instanceVariableNames: 'start end xIncrement xDirection error errorAdjUp errorAdjDown' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonLineSimulation commentStamp: '' prior: 0! This class is a simulation of the code that's run by the Balloon engine. For debugging purposes only.! !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: 'accessing' stamp: 'ar 10/3/2000 17:19'! balloonOwner ^balloonOwner! ! !BalloonMorph methodsFor: 'initialization' stamp: 'dgd 3/12/2006 14:27'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ self defaultColor muchDarker"Color black"! ! !BalloonMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !BalloonMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ self class balloonColor! ! !BalloonMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:20'! initialize "initialize the state of the receiver" super initialize. "" self beSmoothCurve. 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: 'RAA 7/1/2001 18:48'! popUpForHand: aHand "Pop up the receiver as balloon help for the given hand" | worldBounds | self lock. self fullBounds. "force layout" self setProperty: #morphicLayerNumber toValue: self morphicLayerNumber. 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: 'menus' stamp: 'wiz 12/30/2004 17:14'! adjustedCenter "Return the center of the original textMorph box within the balloon." ^ (self vertices last: 4) average rounded ! ! !BalloonMorph methodsFor: 'stepping and presenter' stamp: 'sma 12/23/1999 14:05'! step "Move with target." target ifNotNil: [self position: target position + offsetFromTarget]. ! ! !BalloonMorph methodsFor: 'testing' stamp: 'di 9/18/97 10:10'! stepTime ^ 0 "every cycle"! ! !BalloonMorph methodsFor: 'WiW support' 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: '*MorphicExtras-classification' stamp: 'ar 9/15/2000 17:56'! isBalloonHelp ^true! ! !BalloonMorph methodsFor: 'private' stamp: 'sma 12/23/1999 14:06'! setTarget: aMorph (target _ aMorph) ifNotNil: [offsetFromTarget _ self position - target position]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'sd 12/5/2001 20:27'! 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 for: morph. 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: '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: 'nk 9/1/2004 10:47'! chooseBalloonFont "BalloonMorph chooseBalloonFont" Preferences chooseFontWithPrompt: 'Select the font to be used for balloon help' translated andSendTo: self withSelector: #setBalloonFontTo: highlight: BalloonFont! ! !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]! ! !BalloonMorph class methodsFor: 'private' stamp: 'wiz 1/24/2005 00:32'! 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 | "wiz 1/8/2005 Choose rect independantly of vertice order or size. Would be nice it this took into account curveBounds but it does not." rect := Rectangle encompassing: vertices. 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: 'sd 12/5/2001 20:28'! getTextMorph: aStringOrMorph for: balloonOwner "Construct text morph." | m text | aStringOrMorph isMorph ifTrue: [m _ aStringOrMorph] ifFalse: [BalloonFont ifNil: [text _ aStringOrMorph] ifNotNil: [text _ Text string: aStringOrMorph attribute: (TextFontReference toFont: balloonOwner balloonFont)]. m _ (TextMorph new contents: text) centered]. m setToAdhereToEdge: #adjustedCenter. ^ m! ! !BalloonMorph class methodsFor: 'private' stamp: 'wiz 1/8/2005 18:05'! 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 // 2 @ 0) with: corners first + (0 - bounds width // 4 @ (bounds height // 2))) , corners! ! RectangleMorph subclass: #BalloonRectangleMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Balloon'! !BalloonRectangleMorph commentStamp: '' prior: 0! BalloonRectangleMorph is an example for drawing using the BalloonEngine.! !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: '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.! ! !BalloonRectangleMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:33'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ GradientFillStyle ramp: {0.0 -> Color black. 1.0 -> Color white}! ! !BalloonRectangleMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 10! ! !BalloonRectangleMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" | result | result _ GradientFillStyle ramp: {0.0 -> Color green. 0.5 -> Color yellow. 1.0 -> Color red}. result radial: true. ^ result! ! !BalloonRectangleMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:41'! initialize "initialize the state of the receiver" super initialize. "" self extent: 100 @ 100! ! !BalloonRectangleMorph methodsFor: 'rotate scale and flex' stamp: 'ar 11/15/1998 22:20'! newTransformationMorph ^MatrixTransformMorph new! ! !BalloonRectangleMorph methodsFor: 'testing' stamp: 'ar 8/25/2001 19:08'! canDrawBorder: aBorderStyle ^aBorderStyle style == #simple! ! Object subclass: #BalloonSolidFillSimulation instanceVariableNames: 'color' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Simulation'! !BalloonSolidFillSimulation commentStamp: '' prior: 0! This class is a simulation of the code that's run by the Balloon engine. For debugging purposes only.! !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 commentStamp: '' prior: 0! This class is a repository for data which needs to be preserved during certain operations of BalloonCanvas.! !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: 'ls 2/10/2001 13:26'! mimeEncode "Convert from data to 6 bit characters." | phase1 phase2 raw nib lineLength | phase1 _ phase2 _ false. lineLength := 0. [dataStream atEnd] whileFalse: [ lineLength >= 70 ifTrue: [ mimeStream cr. lineLength := 0. ]. 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). lineLength := lineLength + 4.]. 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 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! ! TestCase subclass: #Base64MimeConverterTest instanceVariableNames: 'message' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Streams'! !Base64MimeConverterTest commentStamp: '' prior: 0! This is the unit test for the class Base64MimeConverter. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category! !Base64MimeConverterTest methodsFor: 'initialize-release' stamp: 'stephaneducasse 2/4/2006 20:10'! setUp message := ReadWriteStream on: (String new: 10). message nextPutAll: 'Hi There!!'.! ! !Base64MimeConverterTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:23'! testMimeEncodeDecode | encoded | encoded := Base64MimeConverter mimeEncode: message. self assert: (encoded contents = 'SGkgVGhlcmUh'). self assert: ((Base64MimeConverter mimeDecodeToChars: encoded) contents = message contents).! ! AbstractSoundSystem subclass: #BaseSoundSystem instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !BaseSoundSystem commentStamp: 'gk 2/24/2004 08:35' prior: 0! This is the normal sound system in Squeak and is registered in SoundService - an AppRegistry - so that a small highlevel protocol for playing sounds can be used in a pluggable fashion. More information available in superclass.! !BaseSoundSystem methodsFor: 'misc' stamp: 'stephaneducasse 2/4/2006 20:40'! 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 | BaseSoundSystem 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 ! ! !BaseSoundSystem methodsFor: 'misc' stamp: 'ads 7/30/2003 22:18'! sampledSoundChoices ^ SampledSound soundNames! ! !BaseSoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:53'! shutDown SoundPlayer shutDown ! ! !BaseSoundSystem methodsFor: 'misc' stamp: 'ads 7/30/2003 23:17'! soundNamed: soundName ^ SampledSound soundNamed: soundName! ! !BaseSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:20'! beep "There is sound support, so we use the default sampled sound for a beep." Preferences soundsEnabled ifTrue: [ SampledSound beep]! ! !BaseSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:20'! playSampledSound: samples rate: rate Preferences soundsEnabled ifTrue: [ (SampledSound samples: samples samplingRate: rate) play]! ! !BaseSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:23'! playSoundNamed: soundName "There is sound support, so we play the given sound." Preferences soundsEnabled ifTrue: [ SampledSound playSoundNamed: soundName asString]! ! !BaseSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:22'! playSoundNamed: soundName ifAbsentReadFrom: aifFileName Preferences soundsEnabled ifTrue: [ (SampledSound soundNames includes: soundName) ifFalse: [ (FileDirectory default fileExists: aifFileName) ifTrue: [ SampledSound addLibrarySoundNamed: soundName fromAIFFfileNamed: aifFileName]]. (SampledSound soundNames includes: soundName) ifTrue: [ SampledSound playSoundNamed: soundName]]! ! !BaseSoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:23'! playSoundNamedOrBeep: soundName "There is sound support, so we play the given sound instead of beeping." Preferences soundsEnabled ifTrue: [ ^self playSoundNamed: soundName]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BaseSoundSystem class instanceVariableNames: ''! !BaseSoundSystem class methodsFor: 'class initialization' stamp: 'gk 2/23/2004 21:08'! initialize SoundService register: self new.! ! !BaseSoundSystem class methodsFor: 'class initialization' stamp: 'gk 2/23/2004 21:08'! unload SoundService registeredClasses do: [:ss | (ss isKindOf: self) ifTrue: [SoundService unregister: ss]].! ! TestCase subclass: #BasicBehaviorClassMetaclassTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !BasicBehaviorClassMetaclassTest commentStamp: '' prior: 0! This class contains some tests regarding the classes Behavior ClassDescription Class Metaclass --- ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:19'! testBehaviorClassClassDescriptionMetaclassHierarchy "self run: #testBehaviorClassClassDescriptionMetaclassHierarchy" self assert: Class superclass == ClassDescription. self assert: Metaclass superclass == ClassDescription. self assert: ClassDescription superclass == Behavior. self assert: Behavior superclass = Object. self assert: Class class class == Metaclass. self assert: Metaclass class class == Metaclass. self assert: ClassDescription class class == Metaclass. self assert: Behavior class class == Metaclass. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:16'! testClassDescriptionAllSubInstances "self run: #testClassDescriptionAllSubInstances" | cdNo clsNo metaclsNo | cdNo := ClassDescription allSubInstances size. clsNo := Class allSubInstances size . metaclsNo := Metaclass allSubInstances size. self assert: cdNo = (clsNo + metaclsNo). ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:17'! testMetaclass "self run: #testMetaclass" self assert: OrderedCollection class class == Metaclass. self assert: Dictionary class class == Metaclass. self assert: Object class class == Metaclass. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:12'! testMetaclassName "self run: #testMetaclassName" self assert: Dictionary class name = 'Dictionary class'. self assert: OrderedCollection class name = 'OrderedCollection class'. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:12'! testMetaclassNumberOfInstances "self run: #testMetaclassNumberOfInstances" self assert: Dictionary class allInstances size = 1. self assert: OrderedCollection class allInstances size = 1.! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:18'! testMetaclassPointOfCircularity "self run: #testMetaclassPointOfCircularity" self assert: Metaclass class instanceCount = 1. self assert: Metaclass class someInstance == Metaclass. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:13'! testMetaclassSuperclass "self run: #testMetaclassSuperclass" self assert: Dictionary class superclass == Set class. self assert: OrderedCollection class superclass == SequenceableCollection class. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:14'! testMetaclassSuperclassHierarchy "self run: #testMetaclassSuperclassHierarchy" | s | self assert: SequenceableCollection class instanceCount = 1. self assert: Collection class instanceCount = 1. self assert: Object class instanceCount = 1. self assert: ProtoObject class instanceCount = 1. s := OrderedCollection new. s add: SequenceableCollection class. s add: Collection class. s add: Object class. s add: ProtoObject class. s add: Class. s add: ClassDescription. s add: Behavior. s add: Object. s add: ProtoObject. self assert: OrderedCollection class allSuperclasses = s. ! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:21'! testObjectAllSubclasses "self run: #testObjectAllSubclasses" | n2 | n2 := Object allSubclasses size. self assert: n2 = (Object allSubclasses select: [:cls | cls class class == Metaclass or: [cls class == Metaclass]]) size! ! !BasicBehaviorClassMetaclassTest methodsFor: 'testing' stamp: 'sd 6/5/2005 08:13'! testSuperclass "self run: #testSuperclass" | s | self assert: Dictionary superclass == Set. self assert: OrderedCollection superclass == SequenceableCollection. s := OrderedCollection new. s add: SequenceableCollection. s add: Collection. s add: Object. s add: ProtoObject. self assert: OrderedCollection allSuperclasses = s. ! ! RectangleMorph subclass: #BasicButton instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Widgets'! !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 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 methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:33'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color yellow darker! ! !BasicButton methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !BasicButton methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color yellow! ! !BasicButton methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:52'! initialize "initialize the state of the receiver" super initialize. "" self label: 'Button'; useRoundedCorners! ! !BasicButton methodsFor: 'menus' stamp: 'dgd 8/30/2003 20:56'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'change label...' translated action: #setLabel! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BasicButton class instanceVariableNames: ''! !BasicButton class methodsFor: 'printing' stamp: 'sw 6/16/1998 16:58'! defaultNameStemForInstances ^ 'button'! ! Categorizer subclass: #BasicClassOrganizer instanceVariableNames: 'subject classComment commentStamp' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:02'! classComment classComment ifNil: [^ '']. ^ classComment text ifNil: ['']! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! classComment: aString "Store the comment, aString, associated with the object that refers to the receiver." (aString isKindOf: RemoteString) ifTrue: [classComment _ aString] ifFalse: [(aString == nil or: [aString size = 0]) ifTrue: [classComment _ nil] ifFalse: [ self error: 'use aClass classComment:'. classComment _ RemoteString newString: aString onFileNumber: 2]] "Later add priorSource and date and initials?"! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! classComment: aString stamp: aStamp "Store the comment, aString, associated with the object that refers to the receiver." self commentStamp: aStamp. (aString isKindOf: RemoteString) ifTrue: [classComment _ aString] ifFalse: [(aString == nil or: [aString size = 0]) ifTrue: [classComment _ nil] ifFalse: [self error: 'use aClass classComment:'. classComment _ RemoteString newString: aString onFileNumber: 2]] "Later add priorSource and date and initials?"! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! commentRemoteStr ^ classComment! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! commentStamp "Answer the comment stamp for the class" ^ commentStamp! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! commentStamp: aStamp commentStamp _ aStamp! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! dateCommentLastSubmitted "Answer a Date object indicating when my class comment was last submitted. If there is no date stamp, or one of the old-time guys, return nil" "RecentMessageSet organization dateCommentLastSubmitted" | aStamp tokens | (aStamp _ self commentStamp) isEmptyOrNil ifTrue: [^ nil]. tokens _ aStamp findBetweenSubStrs: ' '. "space is expected delimiter, but cr is sometimes seen, though of mysterious provenance" ^ tokens size > 1 ifTrue: [[tokens second asDate] ifError: [nil]] ifFalse: [nil]! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:03'! hasNoComment "Answer whether the class classified by the receiver has a comment." ^classComment == nil! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:04'! hasSubject ^ self subject notNil! ! !BasicClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 16:04'! subject ^ subject.! ! !BasicClassOrganizer methodsFor: 'fileIn/Out' stamp: 'NS 4/7/2004 16:03'! fileOutCommentOn: aFileStream moveSource: moveSource toFile: fileIndex "Copy the class comment to aFileStream. If moveSource is true (as in compressChanges or compressSources, then update classComment to point to the new file." | fileComment | classComment ifNotNil: [aFileStream cr. fileComment _ RemoteString newString: classComment text onFileNumber: fileIndex toFile: aFileStream. moveSource ifTrue: [classComment _ fileComment]]! ! !BasicClassOrganizer methodsFor: 'fileIn/Out' stamp: 'NS 4/7/2004 16:04'! moveChangedCommentToFile: aFileStream numbered: fileIndex "If the comment is in the changes file, then move it to a new file." (classComment ~~ nil and: [classComment sourceFileNumber > 1]) ifTrue: [self fileOutCommentOn: aFileStream moveSource: true toFile: fileIndex]! ! !BasicClassOrganizer methodsFor: 'fileIn/Out' stamp: 'NS 4/7/2004 16:04'! objectForDataStream: refStrm | dp | "I am about to be written on an object file. Write a path to me in the other system instead." self hasSubject ifTrue: [ (refStrm insideASegment and: [self subject isSystemDefined not]) ifTrue: [ ^ self]. "do trace me" (self subject isKindOf: Class) ifTrue: [ dp _ DiskProxy global: self subject name selector: #organization args: #(). refStrm replace: self with: dp. ^ dp]]. ^ self "in desparation" ! ! !BasicClassOrganizer methodsFor: 'fileIn/Out' stamp: 'NS 4/7/2004 16:04'! putCommentOnFile: aFileStream numbered: sourceIndex moveSource: moveSource forClass: aClass "Store the comment about the class onto file, aFileStream." | header | classComment 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]! ! !BasicClassOrganizer methodsFor: 'private' stamp: 'NS 4/7/2004 16:04'! setSubject: aClassDescription subject _ aClassDescription! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BasicClassOrganizer class instanceVariableNames: ''! !BasicClassOrganizer class methodsFor: 'instance creation' stamp: 'NS 4/7/2004 16:04'! class: aClassDescription ^ self new setSubject: aClassDescription! ! !BasicClassOrganizer class methodsFor: 'instance creation' stamp: 'NS 4/7/2004 16:04'! class: aClassDescription defaultList: aSortedCollection | inst | inst _ self defaultList: aSortedCollection. inst setSubject: aClassDescription. ^ inst! ! !BasicClassOrganizer class methodsFor: 'constants' stamp: 'NS 4/19/2004 15:52'! ambiguous ^ #ambiguous! ! Inspector subclass: #BasicInspector instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Inspector'! !BasicInspector methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! inspect: anObject "Initialize the receiver so that it is inspecting anObject. There is no current selection." self initialize. object := anObject. selectionIndex := 0. contents := ''! ! Object subclass: #BasicRequestor instanceVariableNames: 'caption answer' classVariableNames: '' poolDictionaries: '' category: 'Services-Base'! !BasicRequestor commentStamp: 'rr 7/10/2006 14:44' prior: 0! This class is the root of the Requestor hierarchy. Requestors are interfaces between services and the system. ServiceActions are given an instance of a Requestor, and they ask it for the data they need. The requestor is determined by the model of the application. A class used as a model can implement the #requestor message to return the most suited requestor. A requestor knows how to query its model and the user if needed. Requestor are defined in hierarchies so that the protocol they rely on (methods starting with 'get') can be easily reused.! !BasicRequestor methodsFor: 'executing' stamp: 'rr 5/31/2004 22:43'! get: aString self caption: aString. ^ self getSymbol! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 6/1/2004 21:50'! caption: aString caption := aString! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 10/1/2005 13:13'! getString | result | result _ FillInTheBlank request:caption initialAnswer:answer contents. self newCaption. result isEmpty |result isNil ifTrue:[ServiceCancelled signal]. ^ result! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 5/31/2004 22:18'! getStringCollection caption := caption, Character cr asString, 'Separate items with space'. ^ (self getString findTokens: ' ') collect: [:each | each copyWithoutAll: ' ' ]! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 5/31/2004 22:19'! getSymbol ^ self getString asSymbol! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 5/31/2004 22:20'! getSymbolCollection ^[self getStringCollection collect: [:each | each asSymbol]] on: ServiceCancelled do: [#()]! ! !BasicRequestor methodsFor: 'generic requests' stamp: 'rr 10/1/2005 13:13'! newCaption caption _ 'Enter text'. answer _ WriteStream on:''! ! !BasicRequestor methodsFor: 'initialize-release' stamp: 'rr 1/9/2006 12:06'! initialize self newCaption! ! TestCase subclass: #BecomeTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-VM'! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:28'! testBecome "Test the two way become. Note. we cannot use string literals for this test" | a b c d | a := 'ab' copy. b := 'cd' copy. c := a. d := b. a become: b. self assert: a = 'cd'; assert: b = 'ab'; assert: c = 'cd'; assert: d = 'ab'. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:28'! testBecomeForward "Test the forward become." | a b c d | a := 'ab' copy. b := 'cd' copy. c := a. d := b. a becomeForward: b. self assert: a = 'cd'; assert: b = 'cd'; assert: c = 'cd'; assert: d = 'cd'. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 17:36'! testBecomeForwardDontCopyIdentityHash "Check that 1. the argument to becomeForward: is NOT modified to have the receiver's identity hash. 2. the receiver's identity hash is unchanged." | a b hb | a := 'ab' copy. b := 'cd' copy. hb := b identityHash. a becomeForward: b copyHash: false. self assert: a identityHash = hb; assert: b identityHash = hb. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:29'! testBecomeForwardHash | a b c hb | a := 'ab' copy. b := 'cd' copy. c := a. hb := b hash. a becomeForward: b. self assert: a hash = hb; assert: b hash = hb; assert: c hash = hb. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:27'! testBecomeForwardIdentityHash "Check that 1. the argument to becomeForward: is modified to have the receiver's identity hash. 2. the receiver's identity hash is unchanged." | a b ha | a := 'ab' copy. b := 'cd' copy. ha := a identityHash. a becomeForward: b. self assert: a identityHash = ha; assert: b identityHash = ha. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:30'! testBecomeHash | a b c d ha hb | a := 'ab' copy. b := 'cd' copy. c := a. d := b. ha := a hash. hb := b hash. a become: b. self assert: a hash = hb; assert: b hash = ha; assert: c hash = hb; assert: d hash = ha. ! ! !BecomeTest methodsFor: 'Testing' stamp: 'brp 9/19/2003 15:31'! testBecomeIdentityHash "Note. The identity hash of both objects seems to change after the become:" | a b c d | a := 'ab' copy. b := 'cd' copy. c := a. d := b. a become: b. self assert: a identityHash = c identityHash; assert: b identityHash = d identityHash; deny: a identityHash = b identityHash. ! ! Object subclass: #Beeper instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !Beeper commentStamp: 'gk 2/26/2004 22:44' prior: 0! Beeper provides simple audio (or in some other way) feedback to the user. The recommended use is "Beeper beep" to give the user the equivalence of a beep. If you want to force the beep to use the primitive in the VM for beeping, then use "Beeper beepPrimitive". In either case, if sounds are disabled there will be no beep. The actual beeping, when you use "Beeper beep", is done by sending a #play message to a registered playable object. You can register your own playable object by invoking the class side method #setDefault: passing in an object that responds to the #play message. The default playable object is an instance of Beeper itself which implements #play on the instance side. That implementation delegates the playing of the beep to the default SoundService. Note that #play is introduced as a common interface between AbstractSound and Beeper. This way we can register instances of AbstractSound as playable entities, for example: Beeper setDefault: (SampledSound new setSamples: self coffeeCupClink samplingRate: 12000). Then "Beeper beep" will play the coffeeCup sound.! !Beeper methodsFor: 'play interface' stamp: 'gk 2/24/2004 23:25'! play "This is how the default Beeper makes a beep, by sending beep to the default sound service. The sound system will check if sounds are enabled." SoundService default beep! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Beeper class instanceVariableNames: 'default'! !Beeper class methodsFor: 'beeping' stamp: 'gk 2/24/2004 08:38'! beep "The preferred way of producing an audible feedback. The default playable entity (an instance of Beeper) also uses the pluggable SoundService mechanism, so it will use the primitive beep only if there is no other sound mechanism available." self default play ! ! !Beeper class methodsFor: 'beeping' stamp: 'gk 2/24/2004 08:38'! beepPrimitive "Make a primitive beep. Only use this if you want to force this to be a primitive beep. Otherwise use Beeper class>>beep since this method bypasses the current registered playable entity." Preferences soundsEnabled ifTrue: [ self primitiveBeep]! ! !Beeper class methodsFor: 'customize' stamp: 'gk 2/22/2004 17:51'! clearDefault "Clear the default playable. Will be lazily initialized in Beeper class >>default." default := nil! ! !Beeper class methodsFor: 'customize' stamp: 'gk 2/22/2004 17:55'! default "When the default is not defined it is initialized using #newDefault." default isNil ifTrue: [default := self newDefault ]. ^ default! ! !Beeper class methodsFor: 'customize' stamp: 'gk 2/24/2004 22:12'! newDefault "Subclasses may override me to provide a default beep. This base implementation returns an instance of Beeper which uses the pluggable sound service." ^ self new! ! !Beeper class methodsFor: 'customize' stamp: 'gk 2/22/2004 17:54'! setDefault: aPlayableEntity "Set the playable entity used when making a beep. The playable entity should implement the message #play." default := aPlayableEntity! ! !Beeper class methodsFor: 'private' stamp: 'gk 2/24/2004 23:51'! primitiveBeep "Make a primitive beep. Not to be called directly. It is much better to use Beeper class>>beep or Beeper class>>beepPrimitive since this method bypasses the current registered playable entity and does not check Preferences class>>soundsEnabled." self primitiveFailed! ! Object subclass: #Behavior uses: TPureBehavior @ {#basicAddTraitSelector:withMethod:->#addTraitSelector:withMethod:} instanceVariableNames: 'superclass methodDict format' classVariableNames: 'ObsoleteSubclasses' poolDictionaries: '' category: 'Kernel-Classes'! !Behavior commentStamp: 'al 12/8/2005 20:44' 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: 'accessing' stamp: 'ajh 9/19/2001 17:30'! classDepth superclass ifNil: [^ 1]. ^ superclass classDepth + 1! ! !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/7/2001 17:05'! methodDict methodDict == nil ifTrue: [self recoverFromMDFaultWithTrace]. ^ 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'! 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: 'accessing class hierarchy' stamp: 'nb 5/6/2003 17:11'! 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! ! !Behavior methodsFor: 'accessing class hierarchy' stamp: 'sd 3/28/2003 15:06'! allSubclassesWithLevelDo: classAndLevelBlock startingLevel: level "Walk the tree of subclasses, giving the class and its level" | subclassNames | 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 | (self environment 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' stamp: 'sd 3/14/2004 18:09'! subclasses "slow implementation since Behavior does not keep trace of subclasses" ^ self class allInstances select: [:each | each superclass = self ]! ! !Behavior methodsFor: 'accessing class hierarchy'! superclass "Answer the receiver's superclass, a Class." ^superclass! ! !Behavior methodsFor: 'accessing 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: 'accessing class hierarchy'! withAllSubclasses "Answer a Set of the receiver, the receiver's descendent's, and the receiver's descendent's subclasses." ^ self allSubclasses add: self; yourself! ! !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' 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'! 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: 'ajh 10/17/2002 11:03'! allowsSubInstVars "Classes that allow instances to change classes among its subclasses will want to override this and return false, so inst vars are not accidentally added to its subclasses." ^ true! ! !Behavior methodsFor: 'accessing instances and variables' stamp: 'tpr 5/30/2003 13:04'! allSharedPools "Answer a Set of the names of the pools (Dictionaries or SharedPool subclasses) 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' stamp: 'sw 5/21/2001 22:51'! inspectAllInstances "Inpsect all instances of the receiver. 1/26/96 sw" | all allSize prefix | all _ self allInstances. (allSize _ all size) == 0 ifTrue: [^ self inform: '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' stamp: 'sw 5/21/2001 22:51'! 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 inform: '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'! 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'! 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'! 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: 'accessing method dictionary' stamp: 'NS 12/12/2003 15:57'! allSelectors "Answer all selectors understood by instances of the receiver" | coll | coll _ OrderedCollection new. self withAllSuperclasses do: [:aClass | coll addAll: aClass selectors]. ^ coll asIdentitySet! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'al 6/12/2006 10:48'! basicLocalSelectors "Direct accessor for the instance variable localSelectors. Because of hardcoded ivar indexes of Behavior and Class in the VM, Class and Metaclass declare the needed ivar and override this method as an accessor. By returning nil instead of declaring this method as a subclass responsibility, Behavior can be instantiated for creating anonymous classes." ^nil! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'al 3/25/2006 13:17'! basicLocalSelectors: aSetOrNil self subclassResponsibility ! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'mga 3/20/2005 11:11'! commentsAt: 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." ^self commentsIn: (self sourceCodeAt: selector) asString. "Behavior commentsAt: #commentsAt:"! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'mga 3/21/2005 10:53'! commentsIn: sourceString | commentStart nextQuotePos someComments aPos | ('*"*' match: sourceString) ifFalse: [^#()]. someComments:= OrderedCollection new. sourceString size == 0 ifTrue: [^ someComments]. aPos:=1. nextQuotePos:= 0. [commentStart _ sourceString findString: '"' startingAt: aPos. nextQuotePos:= self nextQuotePosIn: sourceString startingFrom: commentStart. (commentStart ~= 0 and: [nextQuotePos >commentStart])] whileTrue: [ commentStart ~= nextQuotePos ifTrue: [ someComments add: ((sourceString copyFrom: commentStart + 1 to: nextQuotePos - 1) copyReplaceAll: '""' with: '"').]. aPos := nextQuotePos+1]. ^someComments! ! !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: 'dvf 9/27/2005 17:08'! methodDict: aDictionary methodDict _ aDictionary! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'mga 3/21/2005 12:04'! nextQuotePosIn: sourceString startingFrom: commentStart | pos nextQuotePos | pos _ commentStart + 1. [((nextQuotePos _ sourceString findString: '"' startingAt: pos) == (sourceString findString: '""' startingAt: pos)) and: [nextQuotePos ~= 0]] whileTrue: [pos _ nextQuotePos + 2]. ^nextQuotePos! ! !Behavior methodsFor: 'accessing method dictionary'! 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 nil if none found." | aSuper aComment | ^ (aComment _ self firstPrecodeCommentFor: selector) isEmptyOrNil ifTrue: [(self == Behavior or: [superclass == nil or: [(aSuper _ superclass whichClassIncludesSelector: selector) == nil]]) ifFalse: [aSuper precodeCommentOrInheritedCommentFor: selector] "ActorState precodeCommentOrInheritedCommentFor: #printOn:"] ifFalse: [aComment]! ! !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: 'md 1/2/2006 18:56'! selectorsWithArgs: numberOfArgs "Return all selectors defined in this class that take this number of arguments" ^ self selectors select: [:selector | selector numArgs = numberOfArgs]! ! !Behavior methodsFor: 'accessing method dictionary'! supermostPrecodeCommentFor: selector "Answer a string representing the precode comment in the most distant superclass's implementation of the selector. Return nil if none found." | aSuper superComment | (self == Behavior or: [superclass == nil or: [(aSuper _ superclass whichClassIncludesSelector: selector) == nil]]) ifFalse: ["There is a super implementor" superComment _ aSuper supermostPrecodeCommentFor: selector]. ^ superComment ifNil: [self firstPrecodeCommentFor: selector "ActorState supermostPrecodeCommentFor: #printOn:"]! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'al 12/6/2004 11:36'! 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]]! ! !Behavior methodsFor: 'accessing method dictionary' stamp: 'sd 11/19/2004 15:18'! zapAllMethods "Remove all methods in this class which is assumed to be obsolete" methodDict _ self emptyMethodDictionary. self class isMeta ifTrue: [self class zapAllMethods]! ! !Behavior methodsFor: 'enumerating' stamp: 'apb 7/13/2004 00:40'! allInstancesDo: aBlock "Evaluate the argument, aBlock, for each of the current instances of the receiver. Because aBlock might change the class of inst (for example, using become:), it is essential to compute next before aBlock value: inst." | inst next | self == UndefinedObject ifTrue: [^ aBlock value: nil]. inst _ self someInstance. [inst == nil] whileFalse: [ next _ inst nextInstance. aBlock value: inst. inst _ next]! ! !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: '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'! 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: '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'! 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: 'nk 2/14/2001 12:09'! withAllSuperAndSubclassesDoGently: aBlock self allSuperclassesDo: aBlock. aBlock value: self. self allSubclassesDoGently: 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: 'initialize-release' stamp: 'NS 1/28/2004 11:17'! forgetDoIts "get rid of old DoIt methods" self basicRemoveSelector: #DoIt; basicRemoveSelector: #DoItIn:! ! !Behavior methodsFor: 'initialize-release' stamp: 'kwl 6/22/2006 14:51'! initialize "moved here from the class side's #new" self methodDictionary: self emptyMethodDictionary. self superclass: Object. self setFormat: Object format! ! !Behavior methodsFor: 'initialize-release' stamp: 'sd 3/28/2003 15:07'! 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]. ^ self environment at: obsName asSymbol! ! !Behavior methodsFor: 'initialize-release' stamp: 'al 12/12/2003 20:59'! 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. self traitComposition: nil! ! !Behavior methodsFor: 'instance creation' stamp: 'sd 3/28/2003 15:06'! 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" self environment signalLowSpace. ^ self basicNew "retry if user proceeds" ! ! !Behavior methodsFor: 'instance creation' stamp: 'sd 3/28/2003 15:06'! 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." self environment 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: 'Noury Bouraqadi 8/23/2003 14:51'! new "Answer a new initialized instance of the receiver (which is a class) with no indexable variables. Fail if the class is indexable." ^ self basicNew initialize ! ! !Behavior methodsFor: 'instance creation' stamp: 'sd 5/20/2004 11:20'! new: sizeRequested "Answer an initialized instance of this class with the number of indexable variables specified by the argument, sizeRequested." ^ (self basicNew: sizeRequested) initialize ! ! !Behavior methodsFor: 'obsolete subclasses' stamp: 'apb 7/12/2004 23:13'! addObsoleteSubclass: aClass "Weakly remember that aClass was a subclass of the receiver and is now obsolete" | obs | obs _ ObsoleteSubclasses at: self ifAbsent:[WeakArray new]. (obs includes: aClass) ifTrue:[^self]. obs _ obs copyWithout: nil. obs _ obs copyWith: aClass. ObsoleteSubclasses at: self put: obs. ! ! !Behavior methodsFor: 'obsolete subclasses' stamp: 'apb 7/12/2004 23:20'! obsoleteSubclasses "Return all the weakly remembered obsolete subclasses of the receiver" | obs | obs := ObsoleteSubclasses at: self ifAbsent: [^ #()]. ^ obs copyWithout: nil! ! !Behavior methodsFor: 'obsolete subclasses' stamp: 'apb 7/12/2004 23:21'! removeAllObsoleteSubclasses "Remove all the obsolete subclasses of the receiver" ObsoleteSubclasses removeKey: self ifAbsent: []. ! ! !Behavior methodsFor: 'obsolete subclasses' stamp: 'apb 7/12/2004 23:22'! removeObsoleteSubclass: aClass "Remove aClass from the weakly remembered obsolete subclasses" | obs | obs _ ObsoleteSubclasses at: self ifAbsent:[^ self]. (obs includes: aClass) ifFalse:[^self]. obs _ obs copyWithout: aClass. obs _ obs copyWithout: nil. ObsoleteSubclasses at: self put: obs! ! !Behavior methodsFor: 'printing' stamp: 'md 8/3/2005 00:04'! formatterClass ^self compilerClass! ! !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' 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'! printOn: aStream "Refer to the comment in Object|printOn:." aStream nextPutAll: 'a descendent of '. superclass printOn: aStream! ! !Behavior methodsFor: 'queries' stamp: 'dvf 9/17/2001 00:18'! whichClassDefinesClassVar: aString ^self whichSuperclassSatisfies: [:aClass | (aClass classVarNames collect: [:each | each asString]) includes: aString asString]! ! !Behavior methodsFor: 'queries' stamp: 'dvf 9/17/2001 00:18'! whichClassDefinesInstVar: aString ^self whichSuperclassSatisfies: [:aClass | aClass instVarNames includes: aString]! ! !Behavior methodsFor: 'queries' stamp: 'bh 3/6/2000 00:51'! whichSelectorsAssign: instVarName "Answer a Set of selectors whose methods store into the argument, instVarName, as a named instance variable." ^self whichSelectorsStoreInto: instVarName! ! !Behavior methodsFor: 'queries' stamp: 'bh 3/6/2000 00:52'! whichSelectorsRead: instVarName "Answer a Set of selectors whose methods access the argument, instVarName, as a named instance variable." ^self whichSelectorsAccess: instVarName! ! !Behavior methodsFor: 'queries' stamp: 'dvf 9/17/2001 00:18'! whichSuperclassSatisfies: aBlock (aBlock value: self) ifTrue: [^self]. ^superclass isNil ifTrue: [nil] ifFalse: [superclass whichSuperclassSatisfies: aBlock]! ! !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: 'tk 10/26/2001 16:06'! startUpFrom: anImageSegment "Override this when a per-instance startUp message needs to be sent. For example, to correct the order of 16-bit non-pointer data when it came from a different endian machine." ^ nil! ! !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 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: 'dvf 9/27/2005 14:57'! isMeta ^ false! ! !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: 'sd 3/28/2003 15:07'! 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" ^(self environment compactClassesArray includes: self) or:[(self environment specialObjectsArray includes: self) or:[self isKindOf: self]]! ! !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'! 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' stamp: 'al 2/29/2004 14:18'! bindingOf: varName "Answer the binding of some variable resolved in the scope of the receiver" ^superclass bindingOf: varName! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'sd 5/7/2006 09:58'! canPerform: selector "Answer whether the receiver can safely perform to the message whose selector is the argument: it is not an abstract or cancelled method" ^ self classAndMethodFor: selector do: [:c :m | m isProvided] ifAbsent: [false].! ! !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: 'ar 5/18/2003 18:13'! classBindingOf: varName "Answer the binding of some variable resolved in the scope of the receiver's class" ^self bindingOf: varName! ! !Behavior methodsFor: 'testing method dictionary'! 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." "Rectangle whichClassIncludesSelector: #inspect." (self includesSelector: aSymbol) ifTrue: [^ self]. superclass == nil ifTrue: [^ nil]. ^ superclass whichClassIncludesSelector: aSymbol! ! !Behavior methodsFor: 'testing method dictionary' stamp: 'NS 3/30/2004 14:25'! 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: [^IdentitySet 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: 'NS 3/30/2004 14:25'! 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: [^IdentitySet new]. ^ self methodDict keys select: [:sel | (self methodDict at: sel) writesField: instVarIndex] "Point whichSelectorsStoreInto: 'x'."! ! !Behavior methodsFor: 'traits' stamp: 'al 9/16/2005 16:04'! addTraitSelector: aSymbol withMethod: aCompiledMethod self basicAddTraitSelector: aSymbol withMethod: aCompiledMethod. aCompiledMethod sendsToSuper ifTrue: [ self recompile: aSymbol]! ! !Behavior methodsFor: 'traits' stamp: 'al 3/25/2006 12:39'! hasTraitComposition self subclassResponsibility ! ! !Behavior methodsFor: 'traits' stamp: 'al 3/25/2006 12:36'! traitComposition self subclassResponsibility! ! !Behavior methodsFor: 'traits' stamp: 'al 3/25/2006 12:39'! traitComposition: aTraitComposition self subclassResponsibility ! ! !Behavior methodsFor: 'user interface' stamp: 'md 8/27/2005 17:18'! allLocalCallsOn: aSymbol "Answer a SortedCollection of all the methods that call on aSymbol, anywhere in my class hierarchy." | aSet special byte cls | aSet _ Set new. cls _ self theNonMetaClass. special _ self environment hasSpecialSelector: aSymbol ifTrueSetByte: [:b | byte _ b ]. cls withAllSuperAndSubclassesDoGently: [ :class | (class whichSelectorsReferTo: aSymbol special: special byte: byte) do: [:sel | sel isDoIt ifFalse: [aSet add: class name , ' ', sel]]]. cls class withAllSuperAndSubclassesDoGently: [ :class | (class whichSelectorsReferTo: aSymbol special: special byte: byte) do: [:sel | sel isDoIt ifFalse: [aSet add: class name , ' ', sel]]]. ^aSet! ! !Behavior methodsFor: 'user interface' stamp: 'md 8/27/2005 17:17'! 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 isDoIt ifFalse: [any _ true]]]]. any]! ! !Behavior methodsFor: 'user interface' stamp: 'al 11/28/2005 21:58'! 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 isDoIt ifFalse: [any _ true]]]. any] "Ob unreferencedInstanceVariables"! ! !Behavior methodsFor: 'user interface' stamp: 'RAA 5/28/2001 12:00'! withAllSubAndSuperclassesDo: aBlock self withAllSubclassesDo: aBlock. self allSuperclassesDo: aBlock. ! ! !Behavior methodsFor: '*39Deprecated' stamp: 'md 1/17/2006 17:56'! scopeHas: varName ifTrue: aBlock "Obsolete. Kept around for possible spurios senders which we don't know about" self deprecated: 'Obsolete'. (self bindingOf: varName) ifNotNilDo:[:binding| aBlock value: binding. ^true]. ^false! ! !Behavior methodsFor: '*39Deprecated' stamp: 'md 2/17/2006 18:48'! selectorAtMethod: method setClass: classResultBlock "Answer both the message selector associated with the compiled method and the class in which that selector is defined." | sel | self deprecated: 'please call #methodClass and #selector on the method'. sel _ self methodDict keyAtIdentityValue: method ifAbsent: [superclass == nil ifTrue: [classResultBlock value: self. ^method defaultSelector]. sel _ superclass selectorAtMethod: method setClass: classResultBlock. "Set class to be self, rather than that returned from superclass. " sel == method defaultSelector ifTrue: [classResultBlock value: self]. ^sel]. classResultBlock value: self. ^sel! ! !Behavior methodsFor: '*system-support' stamp: 'tpr 12/17/2003 16:04'! allCallsOn "Answer a SortedCollection of all the methods that refer to me by name or as part of an association in a global dict." ^ (self systemNavigation allCallsOn: (self environment associationAt: self theNonMetaClass name)), (self systemNavigation allCallsOn: self theNonMetaClass name) ! ! !Behavior methodsFor: '*system-support' stamp: 'dvf 8/23/2003 12:43'! allCallsOn: aSymbol "Answer a SortedCollection of all the methods that call on aSymbol." ^ self systemNavigation allCallsOn: aSymbol from: self . ! ! !Behavior methodsFor: '*system-support' stamp: 'dvf 8/23/2003 12:43'! allUnsentMessages "Answer an array of all the messages defined by the receiver that are not sent anywhere in the system." ^ self environment allUnSentMessagesIn: self selectors! ! !Behavior methodsFor: '*Traits-requires' stamp: 'NS 5/26/2005 12:11'! classAndMethodFor: aSymbol do: binaryBlock ifAbsent: absentBlock "Looks up the selector aSymbol in the class chain. If it is found, binaryBlock is evaluated with the class that defines the selector and the associated method. Otherwise absentBlock is evaluated." | method | self withAllSuperclassesDo: [:class | method _ class compiledMethodAt: aSymbol ifAbsent: [nil]. method ifNotNil: [^ binaryBlock value: class value: method]. ]. ^ absentBlock value.! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/9/2005 19:45'! classesComposedWithMe ^{self}! ! !Behavior methodsFor: '*Traits-requires' stamp: 'NS 5/26/2005 14:27'! computeSelfSendersFromInheritedSelfSenders: inheritedCollection localSelfSenders: localCollection "Compute the set of all self-senders from the set of inherited self-senders and the set of local self-senders." | result mDict | mDict _ self methodDict. result _ IdentitySet new: inheritedCollection size + localCollection size. "This if-statement is just a performance optimization. Both branches are semantically equivalent." inheritedCollection size > mDict size ifTrue: [ result addAll: inheritedCollection. mDict keysDo: [:each | result remove: each ifAbsent: []]. ] ifFalse: [ inheritedCollection do: [:each | (mDict includesKey: each) ifFalse: [result add: each]]. ]. result addAll: localCollection. ^ result.! ! !Behavior methodsFor: '*Traits-requires' stamp: 'NS 5/26/2005 14:11'! computeTranslationsAndUpdateUnreachableSet: unreachableCollection "This method computes the set of unreachable selectors in the superclass by altering the set of unreachable selectors in this class. In addition, it builds a dictionary mapping super-sent selectors to the selectors of methods sending these selectors." | translations reachableSenders oldUnreachable | oldUnreachable _ unreachableCollection copy. translations _ IdentityDictionary new. "Add selectors implemented in this class to unreachable set." self methodDict keysDo: [:s | unreachableCollection add: s]. "Fill translation dictionary and remove super-reachable selectors from unreachable." self sendCaches superSentSelectorsAndSendersDo: [:sent :senders | reachableSenders _ FixedIdentitySet readonlyWithAll: senders notIn: oldUnreachable. reachableSenders isEmpty ifFalse: [ translations at: sent put: reachableSenders. unreachableCollection remove: sent ifAbsent: []. ]. ]. ^ translations! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 8/4/2005 16:48'! findSelfSendersOf: selector unreachable: unreachableCollection noInheritedSelfSenders: noInheritedBoolean "This method answers a subset of all the reachable methods (local or inherited) that self-send selector (empty set => no self-senders). See Nathanael Schärli's PhD for more details." | selfSenders reachableSelfSenders translations | "Check whether there are local methods that self-send selector and are reachable." selfSenders := self sendCaches selfSendersOf: selector. reachableSelfSenders := FixedIdentitySet readonlyWithAll: selfSenders notIn: unreachableCollection. (self superclass isNil or: [noInheritedBoolean or: [reachableSelfSenders notEmpty]]) ifTrue: [^ reachableSelfSenders]. "Compute the set of unreachable superclass methods and super-send translations and recurse." translations := self computeTranslationsAndUpdateUnreachableSet: unreachableCollection. reachableSelfSenders := superclass findSelfSendersOf: selector unreachable: unreachableCollection noInheritedSelfSenders: false. "Use the translations to replace selectors that are super-sent with the methods that issue the super-sends." reachableSelfSenders := self translateReachableSelfSenders: reachableSelfSenders translations: translations. ^ reachableSelfSenders.! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/12/2005 11:44'! requiredSelectors ^RequiredSelectors current for: self! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/6/2005 13:14'! requiredSelectorsCache ^RequiredSelectors current cacheFor: self! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/1/2005 16:36'! sendCaches ^LocalSends current for: self! ! !Behavior methodsFor: '*Traits-requires' stamp: 'NS 5/24/2005 16:38'! translateReachableSelfSenders: senderCollection translations: translationDictionary | result superSenders | (translationDictionary isEmptyOrNil or: [senderCollection isEmpty]) ifTrue: [^ senderCollection]. result _ FixedIdentitySet new. senderCollection do: [:s | superSenders _ translationDictionary at: s ifAbsent: [nil]. superSenders isNil ifTrue: [result add: s] ifFalse: [result addAll: superSenders]. result isFull ifTrue: [^ result]. ]. ^ result.! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/12/2005 18:28'! updateRequiredStatusFor: selector inSubclasses: someClasses "Updates the requirements cache to reflect whether selector is required in this class and some of its subclasses." | inheritedMethod | inheritedMethod := self superclass ifNotNil: [self superclass lookupSelector: selector]. ^self updateRequiredStatusFor: selector inSubclasses: someClasses parentSelfSenders: FixedIdentitySet new providedInParent: inheritedMethod noInheritedSelfSenders: false accumulatingInto: IdentitySet new.! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 8/9/2005 17:02'! updateRequiredStatusFor: selector inSubclasses: someClasses parentSelfSenders: inheritedSelfSenders providedInParent: providedBoolean noInheritedSelfSenders: noInheritedBoolean "Updates the requirements cache to reflect whether selector is required in this class and all of its subclasses. The parameter inheritedSelfSenders is a subset of the methods in the parent of this class that are known to self-send selector. providedBoolean indicates whether selector is provided in the parent. noInheritedBoolean is true if no self-senders could be found in the superclass. See Nathanael Schärli's PhD for more details." | selfSenders provided m | "Remove from the inherited selfSenders methods that are potentially unreachable." selfSenders := inheritedSelfSenders reject: [:each | self includesSelector: each]. "Check whether the method is provided." m := self compiledMethodAt: selector ifAbsent: [nil]. providedBoolean ifTrue: [ provided := m isNil or: [m isDisabled not and: [m isExplicitlyRequired not and: [m isSubclassResponsibility not]]]. ] ifFalse: [ provided := m notNil and: [m isProvided]. ]. provided ifTrue: [ "If it is provided, it cannot be required." self setRequiredStatusOf: selector to: false. ] ifFalse: [ "If there are non-overridden inherited selfSenders we know that it must be required. Otherwise, we search for self-senders." selfSenders isEmpty ifTrue: [selfSenders := self findSelfSendersOf: selector unreachable: IdentitySet new noInheritedSelfSenders: noInheritedBoolean]. self setRequiredStatusOf: selector to: selfSenders notEmpty. ]. "Do the same for all subclasses." self subclassesDo: [:each | (someClasses includes: each) ifTrue: [each updateRequiredStatusFor: selector inSubclasses: someClasses parentSelfSenders: selfSenders providedInParent: provided noInheritedSelfSenders: (provided not and: [selfSenders isEmpty])]].! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 9/12/2005 19:43'! updateRequiredStatusFor: selector inSubclasses: someClasses parentSelfSenders: inheritedSelfSenders providedInParent: inheritedMethod noInheritedSelfSenders: noInheritedBoolean accumulatingInto: requiringClasses "Updates the requirements cache to reflect whether selector is required in this class and all of its subclasses. The parameter inheritedSelfSenders is a subset of the methods in the parent of this class that are known to self-send selector. providedBoolean indicates whether selector is provided in the parent. noInheritedBoolean is true if no self-senders could be found in the superclass. See Nathanael Schärli's PhD for more details." "Remove from the inherited selfSenders methods that are potentially unreachable." | selfSenders m relevantMethod required lookedForInheritedSelfSenders | lookedForInheritedSelfSenders := false. selfSenders := inheritedSelfSenders reject: [:each | self includesSelector: each]. "Check whether the method is provided." m := self compiledMethodAt: selector ifAbsent: [nil]. relevantMethod := m ifNotNil: [m] ifNil: [inheritedMethod]. relevantMethod ifNotNil: [required := relevantMethod isSubclassResponsibility or: [ relevantMethod isDisabled or: [ relevantMethod isExplicitlyRequired]]] ifNil: ["If there are non-overridden inherited selfSenders we know that it must be required. Otherwise, we search for self-senders." selfSenders isEmpty ifTrue: [selfSenders := self findSelfSendersOf: selector unreachable: IdentitySet new noInheritedSelfSenders: noInheritedBoolean. lookedForInheritedSelfSenders := true]. required := selfSenders notEmpty]. required ifTrue: [requiringClasses add: self]. "Do the same for all subclasses." self subclassesDo: [:each | (someClasses includes: each) ifTrue: [each updateRequiredStatusFor: selector inSubclasses: someClasses parentSelfSenders: selfSenders providedInParent: relevantMethod noInheritedSelfSenders: (lookedForInheritedSelfSenders and: [selfSenders isEmpty]) accumulatingInto: requiringClasses]]. ^requiringClasses! ! !Behavior methodsFor: '*Traits-requires' stamp: 'dvf 8/9/2005 15:39'! withInheritanceTraitCompositionIncludes: aTrait ^self withAllSuperclasses anySatisfy: [:c | c traitCompositionIncludes: aTrait]! ! !Behavior methodsFor: 'private' stamp: 'sd 3/28/2003 15:06'! 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 _ self environment 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: 'sd 3/28/2003 15:06'! 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 _ self environment 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' stamp: 'sd 3/28/2003 15:06'! becomeUncompact | cct index | cct _ self environment 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: 'sd 11/19/2004 15:13'! setFormat: aFormatInstanceDescription "only use this method with extreme care since it modifies the format of the class ie a description of the number of instance variables and whether the class is compact, variable sized" format := aFormatInstanceDescription ! ! !Behavior methodsFor: 'as yet unclassified' stamp: 'md 2/16/2006 17:50'! hash ^ self name hash! ! !Behavior methodsFor: '*omnibrowser-converting' stamp: 'cwp 4/17/2006 12:16'! asAnnouncement ^ self new! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Behavior class instanceVariableNames: ''! !Behavior class methodsFor: 'class initialization' stamp: 'apb 7/12/2004 23:23'! flushObsoleteSubclasses "Behavior flushObsoleteSubclasses" ObsoleteSubclasses finalizeValues.! ! !Behavior class methodsFor: 'class initialization' stamp: 'apb 7/12/2004 23:51'! initialize "Behavior initialize" "Never called for real" ObsoleteSubclasses ifNil: [self initializeObsoleteSubclasses] ifNotNil: [| newDict | newDict := WeakKeyToCollectionDictionary newFrom: ObsoleteSubclasses. newDict rehash. ObsoleteSubclasses := newDict]! ! !Behavior class methodsFor: 'class initialization' stamp: 'apb 7/12/2004 23:46'! initializeObsoleteSubclasses ObsoleteSubclasses _ WeakKeyToCollectionDictionary new.! ! !Behavior class methodsFor: 'testing' stamp: 'dvf 9/27/2005 16:12'! canZapMethodDictionary "Return false since zapping the method dictionary of Behavior class or its subclasses will cause the system to fail." ^false! ! TestCase subclass: #BehaviorTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !BehaviorTest methodsFor: 'tests' stamp: 'sd 3/14/2004 18:11'! testBehaviorSubclasses "self run: #testBehaviorSubclasses" | b b2 | b := Behavior new. b superclass: OrderedCollection. b methodDictionary: MethodDictionary new. self shouldnt: [b subclasses ] raise: Error. self shouldnt: [b withAllSubclasses] raise: Error. self shouldnt: [b allSubclasses] raise: Error. b2 := Behavior new. b2 superclass: b. b2 methodDictionary: MethodDictionary new. self assert: (b subclasses includes: b2). self assert: (b withAllSubclasses includes: b).! ! !BehaviorTest methodsFor: 'tests' stamp: 'sd 11/19/2004 15:38'! testBehaviornewnewShouldNotCrash Behavior new new. "still not working correctly but at least does not crash the image" ! ! !BehaviorTest methodsFor: 'tests' stamp: 'ar 9/27/2005 21:43'! testChange "self debug: #testChange" | behavior model | behavior := Behavior new. behavior superclass: Model. behavior setFormat: Model format. model := Model new. model primitiveChangeClassTo: behavior new. behavior compile: 'thisIsATest ^ 2'. self assert: model thisIsATest = 2. self should: [Model new thisIsATest] raise: MessageNotUnderstood. ! ! !BehaviorTest methodsFor: 'as yet unclassified' stamp: 'md 2/18/2006 16:42'! testBinding self assert: Object binding value = Object. self assert: Object binding key = #Object. self assert: Object class binding value = Object class. "returns nil for Metaclasses... like Encoder>>#associationFor:" self assert: Object class binding key = nil.! ! 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: 'accessing' stamp: 'ar 11/2/1998 12:14'! bounds "Return the bounds containing the receiver" ^super bounds encompass: via! ! !Bezier2Segment methodsFor: 'accessing' stamp: 'ar 6/8/2003 00:07'! degree ^2! ! !Bezier2Segment methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:14'! via "Return the control point" ^via! ! !Bezier2Segment methodsFor: 'bezier clipping' stamp: 'ar 6/7/2003 23:45'! bezierClipHeight: dir | dirX dirY uMin uMax dx dy u | dirX := dir x. dirY := dir y. uMin := 0.0. uMax := (dirX * dirX) + (dirY * dirY). dx := via x - start x. dy := via y - start y. u := (dirX * dx) + (dirY * dy). u < uMin ifTrue:[uMin := u]. u > uMax ifTrue:[uMax := u]. ^uMin@uMax! ! !Bezier2Segment methodsFor: 'converting' stamp: 'ar 6/8/2003 04:19'! asBezier2Points: error ^Array with: start with: via with: end! ! !Bezier2Segment methodsFor: 'converting' stamp: 'ar 11/2/1998 12:17'! asBezier2Segment "Represent the receiver as quadratic bezier segment" ^self! ! !Bezier2Segment methodsFor: 'converting' stamp: 'ar 6/7/2003 21:05'! asBezier3Segment "Represent the receiver as cubic bezier segment" ^Bezier3Segment from: start via: 2*via+start / 3.0 and: 2*via+end / 3.0 to: end! ! !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: 'converting' stamp: 'ar 6/7/2003 20:58'! asTangentSegment ^LineSegment from: via-start to: end-via! ! !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 6/7/2003 22:37'! 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 * 0.5).! ! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 6/6/2003 03:03'! 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 := 1.0 / (2 * parameter * (1.0 - parameter)). t3 := parameter squared. via := (pointOnCurve - (start * t1) - (end * t3)) * t2! ! !Bezier2Segment methodsFor: 'initialize' stamp: 'ar 6/7/2003 00:09'! initializeFrom: controlPoints controlPoints size = 3 ifFalse:[self error:'Wrong number of control points']. start := controlPoints at: 1. via := controlPoints at: 2. end := controlPoints at: 3.! ! !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 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 6/7/2003 00:08'! controlPoints ^{start. via. end}! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 23:39'! controlPointsDo: aBlock aBlock value: start; value: via; value: end! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/8/2003 00:08'! curveFrom: param1 to: param2 "Return a new curve from param1 to param2" | newStart newEnd newVia tan1 tan2 d1 d2 | tan1 := via - start. tan2 := end - via. param1 <= 0.0 ifTrue:[ newStart := start. ] ifFalse:[ d1 := tan1 * param1 + start. d2 := tan2 * param1 + via. newStart := (d2 - d1) * param1 + d1 ]. param2 >= 1.0 ifTrue:[ newEnd := end. ] ifFalse:[ d1 := tan1 * param2 + start. d2 := tan2 * param2 + via. newEnd := (d2 - d1) * param2 + d1. ]. tan2 := (tan2 - tan1 * param1 + tan1) * (param2 - param1). newVia := newStart + tan2. ^self clone from: newStart to: newEnd via: newVia.! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'nk 8/23/2003 12:59'! end: aPoint end := aPoint.! ! !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 6/7/2003 17:21'! lineSegments: steps do: aBlock "Evaluate aBlock with the receiver's line segments" "Note: We could use forward differencing here." | last deltaStep t next | 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 6/7/2003 00:04'! outlineSegment: width | delta newStart newEnd param newMid | delta := self tangentAtStart normalized * width. delta := delta y @ delta x negated. newStart := start + delta. delta := self tangentAtEnd normalized * width. delta := delta y @ delta x negated. newEnd := end + delta. param := 0.5. "self tangentAtStart r / (self tangentAtStart r + self tangentAtEnd r)." delta := (self tangentAt: param) normalized * width. delta := delta y @ delta x negated. newMid := (self valueAt: param) + delta. ^self class from: newStart to: newEnd withMidPoint: newMid at: param! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/9/2003 03:43'! parameterAtExtremeX "Note: Only valid for non-monoton receivers" ^self parameterAtExtreme: 0.0@1.0. ! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/9/2003 03:43'! parameterAtExtremeY "Note: Only valid for non-monoton receivers" ^self parameterAtExtreme: 1.0@0.0. ! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/9/2003 03:43'! parameterAtExtreme: tangentDirection "Compute the parameter value at which the tangent reaches tangentDirection. We need to find the parameter value t at which the following holds ((t * dir + in) crossProduct: tangentDirection) = 0. Since this is pretty ugly we use the normal direction rather than the tangent and compute the equivalent relation using the dot product as ((t * dir + in) dotProduct: nrm) = 0. Reformulation yields ((t * dir x + in x) * nrm x) + ((t * dir y + in y) * nrm y) = 0. (t * dir x * nrm x) + (in x * nrm x) + (t * dir y * nrm y) + (in y * nrm y) = 0. (t * dir x * nrm x) + (t * dir y * nrm y) = 0 - ((in x * nrm x) + (in y * nrm y)). (in x * nrm x) + (in y * nrm y) t = 0 - --------------------------------------- (dir x * nrm x) + (dir y * nrm y) And that's that. Note that we can get rid of the negation by computing 'dir' the other way around (e.g., in the above it would read '-dir') which is trivial to do. Note also that the above does not generalize easily beyond 2D since its not clear how to express the 'normal direction' of a tangent plane. " | inX inY dirX dirY nrmX nrmY | "Compute in" inX := via x - start x. inY := via y - start y. "Compute -dir" dirX := inX - (end x - via x). dirY := inY - (end y - via y). "Compute nrm" nrmX := tangentDirection y. nrmY := 0 - tangentDirection x. "Compute result" ^((inX * nrmX) + (inY * nrmY)) / ((dirX * nrmX) + (dirY * nrmY))! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'nk 12/27/2003 13:00'! roundTo: quantum super roundTo: quantum. via := via roundTo: quantum. ! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'nk 8/23/2003 12:59'! start: aPoint start := aPoint.! ! !Bezier2Segment methodsFor: 'vector functions' stamp: 'ar 6/8/2003 00:54'! tangentAtMid "Return the tangent at the given parametric value along the receiver" | in out | in := self tangentAtStart. out := self tangentAtEnd. ^in + out * 0.5! ! !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 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! ! !Bezier2Segment class methodsFor: 'utilities' stamp: 'ar 6/7/2003 18:33'! makeEllipseSegments: aRectangle "Answer a set of bezier segments approximating an ellipsoid fitting the given rectangle. This method creates eight bezier segments (two for each quadrant) approximating the oval." "EXAMPLE: This example draws an oval with a red border and overlays the approximating bezier segments on top of the oval (drawn in black), thus giving an impression of how closely the bezier resembles the oval. Change the rectangle to see how accurate the approximation is for various radii of the oval. | rect | rect := 100@100 extent: 1200@500. Display getCanvas fillOval: rect color: Color yellow borderWidth: 1 borderColor: Color red. (Bezier2Segment makeEllipseSegments: rect) do:[:seg| seg lineSegmentsDo:[:last :next| Display getCanvas line: last to: next width: 1 color: Color black]]. " "EXAMPLE: | minRadius maxRadius | maxRadius := 300. minRadius := 20. maxRadius to: minRadius by: -10 do:[:rad| | rect | rect := 400@400 - rad corner: 400@400 + rad. Display getCanvas fillOval: rect color: Color yellow borderWidth: 1 borderColor: Color red. (Bezier2Segment makeEllipseSegments: rect) do:[:seg| seg lineSegmentsDo:[:last :next| Display getCanvas line: last to: next width: 1 color: Color black]]]. " | nrm topCenter leftCenter rightCenter bottomCenter dir scale seg1a topRight seg1b seg2a bottomRight seg2b center bottomLeft topLeft seg3a seg3b seg4a seg4b | dir := aRectangle width * 0.5. nrm := aRectangle height * 0.5. "Compute the eight control points on the oval" scale := 0.7071067811865475. "45 degreesToRadians cos = 45 degreesToRadians sin = 2 sqrt / 2" center := aRectangle origin + aRectangle corner * 0.5. topCenter := aRectangle topCenter. rightCenter := aRectangle rightCenter. leftCenter := aRectangle leftCenter. bottomCenter := aRectangle bottomCenter. topRight := (center x + (dir * scale)) @ (center y - (nrm * scale)). bottomRight := (center x + (dir * scale)) @ (center y + (nrm * scale)). bottomLeft := (center x - (dir * scale)) @ (center y + (nrm * scale)). topLeft := (center x - (dir * scale)) @ (center y - (nrm * scale)). scale := 0.414213562373095. "2 sqrt - 1" dir := (dir * scale) @ 0. nrm := 0 @ (nrm * scale). seg1a := self from: topCenter via: topCenter + dir to: topRight. seg1b := self from: topRight via: rightCenter - nrm to: rightCenter. seg2a := self from: rightCenter via: rightCenter + nrm to: bottomRight. seg2b := self from: bottomRight via: bottomCenter + dir to: bottomCenter. seg3a := self from: bottomCenter via: bottomCenter - dir to: bottomLeft. seg3b := self from: bottomLeft via: leftCenter + nrm to: leftCenter. seg4a := self from: leftCenter via: leftCenter - nrm to: topLeft. seg4b := self from: topLeft via: topCenter - dir to: topCenter. ^{seg1a. seg1b. seg2a. seg2b. seg3a. seg3b. seg4a. seg4b}! ! 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: 'accessing' stamp: 'DSM 10/15/1999 15:20'! bounds ^ ((super bounds encompassing: via1) encompassing: via2)! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'ar 6/8/2003 00:07'! degree ^3! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'ar 6/6/2003 21:59'! length "Answer a gross approximation of the length of the receiver" ^(start dist: via1) + (via1 dist: via2) + (via2 dist: end)! ! !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: 'ar 6/6/2003 22:37'! via1 ^via1! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/14/1999 15:31'! via1: aPoint via1 := aPoint! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'ar 6/6/2003 22:37'! via2 ^via2! ! !Bezier3Segment methodsFor: 'accessing' stamp: 'DSM 10/14/1999 15:31'! via2: aPoint via2 := aPoint! ! !Bezier3Segment methodsFor: 'bezier clipping' stamp: 'ar 6/7/2003 23:45'! bezierClipHeight: dir "Check if the argument overlaps the receiver somewhere along the line from start to end. Optimized for speed." | u dirX dirY dx dy uMin uMax | dirX := dir x. dirY := dir y. uMin := 0.0. uMax := (dirX * dirX) + (dirY * dirY). dx := via1 x - start x. dy := via1 y - start y. u := (dirX * dx) + (dirY * dy). u < uMin ifTrue:[uMin := u]. u > uMax ifTrue:[uMax := u]. dx := via2 x - start x. dy := via2 y - start y. u := (dirX * dx) + (dirY * dy). u < uMin ifTrue:[uMin := u]. u > uMax ifTrue:[uMax := u]. ^uMin@uMax! ! !Bezier3Segment methodsFor: 'converting' stamp: 'ar 6/7/2003 21:07'! asBezier2Points: error "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: error. pts := Array 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: 'ar 6/7/2003 21:07'! asBezier2Segments "Demote a cubic bezier to a set of approximating quadratic beziers." ^self asBezier2Segments: 0.5! ! !Bezier3Segment methodsFor: 'converting' stamp: 'ar 6/6/2003 22:23'! asBezierShape "Demote a cubic bezier to a set of approximating quadratic beziers." ^self asBezierShape: 0.5! ! !Bezier3Segment methodsFor: 'converting' stamp: 'ar 6/7/2003 21:09'! asBezierShape: error "Demote a cubic bezier to a set of approximating quadratic beziers. Should convert to forward differencing someday" ^(self asBezier2Points: error) asPointArray.! ! !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: 'ar 6/7/2003 20:58'! asTangentSegment ^Bezier2Segment from: via1-start via: via2-via1 to: end-via2! ! !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: '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: 'initialization' stamp: 'ar 6/7/2003 00:09'! initializeFrom: controlPoints controlPoints size = 4 ifFalse:[self error:'Wrong number of control points']. start := controlPoints at: 1. via1 := controlPoints at: 2. via2 := controlPoints at: 3. end := controlPoints at: 4.! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 00:08'! controlPoints ^{start. via1. via2. end}! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 23:39'! controlPointsDo: aBlock aBlock value: start; value: via1; value: via2; value: end! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/6/2003 21:52'! 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].! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 17:21'! lineSegments: steps do: aBlock "Evaluate aBlock with the receiver's line segments" "Note: We could use forward differencing here." | last deltaStep t next | 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].! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 00:04'! outlineSegment: width | tan1 nrm1 tan2 nrm2 newStart newVia1 newEnd newVia2 dist | tan1 := (via1 - start) normalized. nrm1 := tan1 * width. nrm1 := nrm1 y @ nrm1 x negated. tan2 := (end - via2) normalized. nrm2 := tan2 * width. nrm2 := nrm2 y @ nrm2 x negated. newStart := start + nrm1. newEnd := end + nrm2. dist := (newStart dist: newEnd) * 0.3. newVia1 := newStart + (tan1 * dist). newVia2 := newEnd - (tan2 * dist). ^self class from: newStart via: newVia1 and: newVia2 to: newEnd. ! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/6/2003 22:02'! tangentAtEnd ^end - via2! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/8/2003 00:56'! tangentAtMid | tan1 tan2 tan3 | tan1 := via1 - start. tan2 := via2 - via1. tan3 := end - via2. ^(tan1 + (2*tan2) + tan3) * 0.25 ! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/6/2003 22:01'! tangentAtStart ^via1 - start! ! !Bezier3Segment methodsFor: 'vector functions' stamp: 'ar 6/7/2003 19:25'! tangentAt: parameter | tan1 tan2 tan3 t1 t2 t3 | tan1 := via1 - start. tan2 := via2 - via1. tan3 := end - via2. t1 := (1.0 - parameter) squared. t2 := 2 * parameter * (1.0 - parameter). t3 := parameter squared. ^(tan1 * t1) + (tan2 * t2) + (tan3 * t3)! ! !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: '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! ! !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: 'utilities' stamp: 'ar 6/8/2003 03:25'! makeEllipseSegments: aRectangle "Answer a set of bezier segments approximating an ellipsoid fitting the given rectangle. This method creates four bezier segments (one for each quadrant) approximating the oval." "EXAMPLE: This example draws an oval with a red border and overlays the approximating bezier segments on top of the oval (drawn in black), thus giving an impression of how closely the bezier resembles the oval. Change the rectangle to see how accurate the approximation is for various radii of the oval. | rect | rect := 100@100 extent: 500@200. Display getCanvas fillOval: rect color: Color yellow borderWidth: 1 borderColor: Color red. (Bezier3Segment makeEllipseSegments: rect) do:[:seg| seg lineSegmentsDo:[:last :next| Display getCanvas line: last to: next width: 1 color: Color black]]. " "EXAMPLE: | minRadius maxRadius | maxRadius := 300. minRadius := 20. maxRadius to: minRadius by: -10 do:[:rad| | rect | rect := 400@400 - rad corner: 400@400 + rad. Display getCanvas fillOval: rect color: Color yellow borderWidth: 1 borderColor: Color red. (Bezier3Segment makeEllipseSegments: rect) do:[:seg| seg lineSegmentsDo:[:last :next| Display getCanvas line: last to: next width: 1 color: Color black]]]. " ^self makeEllipseSegments: aRectangle count: 4! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'ar 6/8/2003 03:24'! makeEllipseSegments: aRectangle count: segmentCount "Answer a set of bezier segments approximating an ellipsoid fitting the given rectangle. This method creates segmentCount bezier segments (one for each quadrant) approximating the oval." | count angle seg center scale | center := aRectangle origin + aRectangle corner * 0.5. scale := aRectangle extent * 0.5. count := segmentCount max: 2. "need at least two segments" angle := 360.0 / count. ^(1 to: count) collect:[:i| seg := self makeUnitPieSegmentFrom: i-1*angle to: i*angle. self controlPoints: (seg controlPoints collect:[:pt| pt * scale + center]) ].! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'ar 6/8/2003 03:53'! makePieSegments: aRectangle from: angle1 to: angle2 "Create a series of cubic bezier segments for the oval inscribed in aRectangle between angle1 and angle2. The segments are oriented clockwise, to get counter-clockwise segments simply switch angle1 and angle2." angle2 < angle1 ifTrue:[ "ccw segments" ^(self makePieSegments: aRectangle from: angle2 to: angle1) reversed collect:[:seg| seg reversed] ]. "Split up segments if larger than 120 degrees" angle2 - angle1 > 120 ifTrue:["subdivide" | midAngle | midAngle := angle1 + angle2 * 0.5. ^(self makePieSegments: aRectangle from: angle1 to: midAngle), (self makePieSegments: aRectangle from: midAngle to: angle2). ]. "Create actual pie segment" ^self makePieSegment: aRectangle from: angle1 to: angle2 ! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'ar 6/8/2003 03:26'! makePieSegment: aRectangle from: angle1 to: angle2 "Create a single pie segment for the oval inscribed in aRectangle between angle1 and angle2. If angle1 is less than angle2 this method creates a CW pie segment, otherwise it creates a CCW pie segment." | seg center scale | angle1 > angle2 ifTrue:["ccw" ^(self makePieSegment: aRectangle from: angle2 to: angle1) reversed ]. "create a unit circle pie segment from angle1 to angle2" seg := self makeUnitPieSegmentFrom: angle1 to: angle2. "scale the segment to fit aRectangle" center := aRectangle origin + aRectangle corner * 0.5. scale := aRectangle extent * 0.5. ^self controlPoints: (seg controlPoints collect:[:pt| pt * scale + center])! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'ar 6/8/2003 03:59'! makeUnitPieSegmentFrom: angle1 to: angle2 "Create a clockwise unit pie segment from angle1 to angle2, that is a pie segment for a circle centered at zero with radius one. Note: This method can be used to create at most a quarter circle." | pt1 pt2 rad1 rad2 | rad1 := angle1 degreesToRadians. rad2 := angle2 degreesToRadians. pt1 := rad1 sin @ rad1 cos negated. pt2 := rad2 sin @ rad2 cos negated. ^self makeUnitPieSegmentWith: pt1 and: pt2! ! !Bezier3Segment class methodsFor: 'utilities' stamp: 'ar 6/8/2003 04:45'! makeUnitPieSegmentWith: point1 and: point2 "Create a clockwise unit pie segment from point1 to point2, that is a pie segment for a circle centered at zero with radius one." | pt1 pt2 dir1 dir2 mid length scale cp1 cp2 pt3 magic | "point1 and point2 are the points on the unit circle for accuracy (or broken input), renormalize them." pt1 := point1 normalized. pt2 := point2 normalized. "compute the normal vectors - those are tangent directions for the bezier" dir1 := pt1 y negated @ pt1 x. dir2 := pt2 y negated @ pt2 x. "Okay, now that we have the points and tangents on the unit circle, let's do the magic. For fitting a cubic bezier onto a circle section we know that we want the end points be on the circle and the tangents to point towards the right direction (both of which we have in the above). What we do NOT know is how to scale the tangents so that midpoint of the bezier is exactly on the circle. The good news is that there is a linear relation between the length of the tangent vectors and the distance of the midpoint from the circle's origin. The bad news is that I don't know how to derive it analytically. So what I do here is simply sampling the bezier twice (not really - the first sample is free) and then to compute the distance from the sample." "The first sample is just between the two points on the curve" mid := pt1 + pt2 * 0.5. "The second sample will be taken from the curve with coincident control points at the intersection of dir1 and dir2, which simplifies significantly with a little understanding about trigonometry, since the angle formed between mid, pt1 and the intersection is the same as between the center, pt1 and mid." length := mid r. "length is not only the distance from the center of the unit circle but also the sine of the angle between the circle's center, pt1 and mid (since center is at zero and pt1 has unit length). Therefore, to scale dir1 to the intersection with dir2 we can use mid's distance from pt1 and simply divide it by the sine value." scale := (mid dist: pt1). length > 0.0 ifTrue:[ scale := scale / length]. "now sample the cubic bezier (optimized version for coincident control points)" cp1 := pt1 + (dir1 * (scale * 0.75)). cp2 := pt2 - (dir2 * (scale * 0.75)). pt3 := cp1 + cp2 * 0.5. "compute the magic constant" scale := (pt3 - mid) r / scale. magic := 1.0 - length / scale. "and finally answer the pie segment" ^self from: pt1 via: pt1 + (dir1 * magic) and: pt2 - (dir2 * magic) to: pt2! ! 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 either word array (ie Bitmap) with 2^n elements, where n is the pixel depth of the source, or a fully specified ColorMap which may contain a lookup table (ie Bitmap) and/or four separate masks and shifts which are applied to the pixels. For every source pixel, BitBlt will first perform masking and shifting and then index the lookup table, 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 12/30/2001 20:31'! clipBy: aRectangle | aPoint right bottom | right _ clipX + clipWidth. bottom _ clipY + clipHeight. aPoint _ aRectangle origin. aPoint x > clipX ifTrue:[clipX _ aPoint x]. aPoint y > clipY ifTrue:[clipY _ aPoint y]. aPoint _ aRectangle corner. aPoint x < right ifTrue:[right _ aPoint x]. aPoint y < bottom ifTrue:[bottom _ aPoint y]. clipWidth _ right - clipX. clipHeight _ bottom - clipY. clipWidth < 0 ifTrue:[clipWidth _ 0]. clipHeight < 0 ifTrue:[clipHeight _ 0].! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 12/30/2001 20:33'! clipByX1: x1 y1: y1 x2: x2 y2: y2 | right bottom | right _ clipX + clipWidth. bottom _ clipY + clipHeight. x1 > clipX ifTrue:[clipX _ x1]. y1 > clipY ifTrue:[clipY _ y1]. x2 < right ifTrue:[right _ x2]. y2 < bottom ifTrue:[bottom _ y2]. clipWidth _ right - clipX. clipHeight _ bottom - clipY. clipWidth < 0 ifTrue:[clipWidth _ 0]. clipHeight < 0 ifTrue:[clipHeight _ 0].! ! !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' stamp: 'tk 8/15/2001 10:56'! color "Return the current fill color as a Color. Gives the wrong answer if the halftoneForm is a complex pattern of more than one word." halftoneForm ifNil: [^ Color black]. ^ Color colorFromPixelValue: halftoneForm first depth: destForm depth! ! !BitBlt methodsFor: 'accessing'! colorMap ^ colorMap! ! !BitBlt methodsFor: 'accessing' stamp: 'ar 5/4/2001 15:45'! colorMap: map "See last part of BitBlt comment. 6/18/96 tk" 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' stamp: 'ar 5/14/2001 23:25'! 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 ! ! !BitBlt methodsFor: 'accessing' stamp: 'tbn 9/14/2004 20:38'! halftoneForm "Returns the receivers half tone form. See class commment." ^halftoneForm! ! !BitBlt methodsFor: 'accessing' stamp: 'tbn 9/14/2004 20:39'! halftoneForm: aBitmap "Sets the receivers half tone form. See class commment." halftoneForm := aBitmap ! ! !BitBlt methodsFor: 'accessing'! height: anInteger "Set the receiver's destination form height to be the argument, anInteger." height _ anInteger! ! !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: 'ar 5/14/2001 23:32'! 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! ! !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: 'yo 3/15/2005 09:54'! 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 isForm) and: [sourceForm unhibernate]) ifTrue: [^ self copyBits]. ((destForm isForm) and: [destForm unhibernate]) ifTrue: [^ self copyBits]. ((halftoneForm isForm) and: [halftoneForm unhibernate]) ifTrue: [^ self copyBits]. "Check for unimplmented rules" combinationRule = Form oldPaint ifTrue: [^ self paintBits]. combinationRule = Form oldErase1bitShape ifTrue: [^ self eraseBits]. "Check if BitBlt doesn't support full color maps" (colorMap notNil and:[colorMap isColormap]) ifTrue:[ colorMap _ colorMap colors. ^self copyBits]. "Check if clipping gots us way out of range" self clipRange ifTrue:[self roundVariables. ^self copyBitsAgain]. self error: 'Bad BitBlt arg (Fraction?); proceed to convert.'. "Convert all numeric parameters to integers and try again." self roundVariables. ^ self copyBitsAgain! ! !BitBlt methodsFor: 'copying' stamp: 'ar 2/13/2001 21:12'! copyBitsSimulated ^Smalltalk at: #BitBltSimulation ifPresent:[:bb| bb copyBitsFrom: self].! ! !BitBlt methodsFor: 'copying' stamp: 'nk 4/17/2004 19:42'! 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 isForm) and: [sourceForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. ((destForm isForm) and: [destForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. ((halftoneForm isForm) and: [halftoneForm unhibernate]) ifTrue: [^ self copyBitsTranslucent: factor]. self primitiveFailed "Later do nicer error recovery -- share copyBits recovery"! ! !BitBlt methodsFor: 'copying' stamp: 'ar 5/14/2001 23:32'! copyForm: srcForm to: destPt rule: rule ^ self copyForm: srcForm to: destPt rule: rule colorMap: (srcForm colormapIfNeededFor: destForm)! ! !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: 'ar 5/14/2001 23:32'! 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! ! !BitBlt methodsFor: 'copying' stamp: 'yo 5/20/2004 14:30'! 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. ^ destX@destY. ! ! !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' stamp: 'ar 3/1/2004 13:49'! 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 unhibernate. "before poking" destForm bits at: 1 put: 0. "Just to be sure" self copyBits. ^ destForm bits at: 1! ! !BitBlt methodsFor: 'copying' stamp: 'ar 3/1/2004 13:49'! 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 unhibernate. "before poking" 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: 'text display' stamp: 'ar 10/24/2005 21:49'! displayString: aString from: startIndex to: stopIndex at: aPoint kern: kernDelta baselineY: baselineY font: aFont "Double dispatch into the font. This method is present so that other-than-bitblt entities can be used by CharacterScanner and friends to display text." ^ aFont displayString: aString on: self from: startIndex to: stopIndex at: aPoint kern: kernDelta baselineY: baselineY! ! !BitBlt methodsFor: 'text display' stamp: 'ar 10/25/2005 01:12'! displayString: aString from: startIndex to: stopIndex at: aPoint kern: kernDelta font: aFont "Double dispatch into the font. This method is present so that other-than-bitblt entities can be used by CharacterScanner and friends to display text." ^ aFont displayString: aString on: self from: startIndex to: stopIndex at: aPoint kern: kernDelta! ! !BitBlt methodsFor: 'text display' stamp: 'ar 10/24/2005 21:48'! installFont: aFont foregroundColor: foregroundColor backgroundColor: backgroundColor "Double dispatch into the font. This method is present so that other-than-bitblt entities can be used by CharacterScanner and friends to display text." ^aFont installOn: self foregroundColor: foregroundColor backgroundColor: backgroundColor! ! !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 3/8/2003 00:34'! clipRange "clip and adjust source origin and extent appropriately" "first in x" | sx sy dx dy bbW bbH | "fill in the lazy state if needed" destX ifNil:[destX := 0]. destY ifNil:[destY := 0]. width ifNil:[width := destForm width]. height ifNil:[height := destForm height]. sourceX ifNil:[sourceX := 0]. sourceY ifNil:[sourceY := 0]. clipX ifNil:[clipX := 0]. clipY ifNil:[clipY := 0]. clipWidth ifNil:[clipWidth := destForm width]. clipHeight ifNil:[clipHeight := destForm height]. 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))]. sourceForm ifNotNil:[ sx < 0 ifTrue: [dx _ dx - sx. bbW _ bbW + sx. sx _ 0]. sx + bbW > sourceForm width ifTrue: [bbW _ bbW - (sx + bbW - sourceForm width)]. sy < 0 ifTrue: [dy _ dy - sy. bbH _ bbH + sy. sy _ 0]. sy + bbH > sourceForm height ifTrue: [bbH _ bbH - (sy + bbH - sourceForm height)]. ]. (bbW <= 0 or:[bbH <= 0]) ifTrue:[ sourceX := sourceY := destX := destY := clipX := clipY := width := height := 0. ^true]. (sx = sourceX and:[sy = sourceY and:[dx = destX and:[dy = destY and:[bbW = width and:[bbH = height]]]]]) ifTrue:[^false]. sourceX := sx. sourceY := sy. destX := dx. destY := dy. width := bbW. height := bbH. ^true! ! !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: 'ar 5/14/2001 23:43'! 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: (destForm pixelValueFor: backgroundColor)]. sourceForm depth = 1 ifTrue: [colorMap at: 2 put: (destForm pixelValueFor: foregroundColor). "Ignore any halftone pattern since we use a color map approach here" halftoneForm _ nil]. sourceY _ 0. height _ aStrikeFont height. ! ! !BitBlt methodsFor: 'private' stamp: 'yo 1/8/2005 09:12'! installTTCFont: aTTCFont foregroundColor: foregroundColor backgroundColor: backgroundColor "Set up the parameters. Since the glyphs in a TTCFont is 32bit depth form, it tries to use rule=34 to get better AA result if possible." ((aTTCFont depth = 32)) ifTrue: [ destForm depth <= 8 ifTrue: [ self colorMap: (self cachedFontColormapFrom: aTTCFont depth to: destForm depth). self combinationRule: Form paint. ] ifFalse: [ self colorMap: nil. self combinationRule: 34. ]. halftoneForm _ nil. sourceY _ 0. height _ aTTCFont 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: 'yo 3/11/2005 14:49'! primDisplayString: aString from: startIndex to: stopIndex map: glyphMap xTable: xTable kern: kernDelta | ascii | startIndex to: stopIndex do:[:charIndex| ascii _ (aString at: charIndex) asciiValue. sourceX _ xTable at: ascii + 1. width _ (xTable at: ascii + 2) - sourceX. self copyBits. destX _ destX + width + kernDelta. ].! ! !BitBlt methodsFor: 'private' stamp: 'yo 3/15/2005 09:47'! roundVariables | maxVal minVal | maxVal _ SmallInteger maxVal. minVal _ SmallInteger minVal. destX _ destX asInteger min: maxVal max: minVal. destY _ destY asInteger min: maxVal max: minVal. width _ width asInteger min: maxVal max: minVal. height _ height asInteger min: maxVal max: minVal. sourceX _ sourceX asInteger min: maxVal max: minVal. sourceY _ sourceY asInteger min: maxVal max: minVal. clipX _ clipX asInteger min: maxVal max: minVal. clipY _ clipY asInteger min: maxVal max: minVal. clipWidth _ clipWidth asInteger min: maxVal max: minVal. clipHeight _ clipHeight asInteger min: maxVal max: minVal. ! ! !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: 'ar 5/14/2001 23:32'! 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]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BitBlt class instanceVariableNames: ''! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 4/24/2001 23:49'! 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.01. Transcript show: improvement printString; tab; tab. log print: improvement; tab; tab]. ] ifFalse:[ Transcript cr; show: oldLine. log cr; nextPutAll: oldLine. ]. ]. ^log contents! ! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 5/14/2001 23:31'! 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 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! ! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 5/14/2001 23:31'! benchmark2 "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 colormapIfNeededFor: dest). bb combinationRule: rule. "Measure speed of copyBits" t _ Time millisecondsToRun:[1 to: 10 do:[:i| 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:[1 to: 4 do:[:i| 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! ! !BitBlt class methodsFor: 'benchmarks' stamp: 'ar 4/26/2001 21:04'! benchmark3 "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 colormapIfNeededFor: dest). bb combinationRule: rule. "Measure speed of copyBits" t _ Time millisecondsToRun:[1 to: 10 do:[:i| 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:[1 to: 4 do:[:i| 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! ! !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' stamp: 'ar 5/4/2001 16:02'! exampleColorMap "BitBlt exampleColorMap" "This example shows what one can do with the fixed part of a color map. The color map, as setup below, rotates the bits of a pixel all the way around. Thus you'll get a (sometime strange looking ;-) animation of colors which will end up exactly the way it looked at the beginning. The example is given to make you understand that the masks and shifts can be used for a lot more than simply color converting pixels. In this example, for instance, we use only two of the four independent shifters." | cc bb | cc _ ColorMap masks: { 1 << (Display depth-1). "mask out high bit of color component" 1 << (Display depth-1) - 1. "mask all other bits" 0. 0} shifts: { 1 - Display depth. "shift right to bottom most position" 1. "shift all other pixels one bit left" 0. 0}. bb _ BitBlt toForm: Display. bb sourceForm: Display; combinationRule: 3; colorMap: cc. 1 to: Display depth do:[:i| bb copyBits. Display forceDisplayUpdate. ]. ! ! !BitBlt class methodsFor: 'examples' stamp: 'dew 9/18/2001 02:30'! 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). This only works at Display depth of 1. (Rule 15 does not work?)" | path displayDepth | displayDepth _ Display depth. Display newDepth: 1. 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: nil]. [Sensor anyButtonPressed] whileFalse: []. Display newDepth: displayDepth. "BitBlt exampleOne"! ! !BitBlt class methodsFor: 'examples' stamp: 'jrm 2/21/2001 23:43'! 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. This only works at Depth of 1." | f aBitBlt displayDepth | "create a small black Form source as a brush. " displayDepth _ Display depth. Display newDepth: 1. 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 over 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]. Display newDepth: displayDepth. "BitBlt exampleTwo"! ! !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: 'private' stamp: 'jrm 2/21/2001 23:45'! 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. Only works with Display depth of 1" | 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: 0 fillColor: nil" ! ! TestCase subclass: #BitBltClipBugs instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:33'! testDrawingWayOutside | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: 100; height: 100. self shouldnt:[bb copyBits] raise: Error. ! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:33'! testDrawingWayOutside2 | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: 0@0. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. self shouldnt:[bb copyBits] raise: Error.! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:33'! testDrawingWayOutside3 | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. self shouldnt:[bb copyBits] raise: Error. ! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:34'! testDrawingWayOutside4 | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: 100; height: 100. bb sourceOrigin: SmallInteger maxVal squared asPoint. self shouldnt:[bb copyBits] raise: Error. ! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:34'! testDrawingWayOutside5 | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: 0@0. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. bb sourceOrigin: SmallInteger maxVal squared asPoint. self shouldnt:[bb copyBits] raise: Error.! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:34'! testDrawingWayOutside6 | f1 bb f2 | f1 := Form extent: 100@100 depth: 1. f2 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb sourceForm: f2. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. bb sourceOrigin: SmallInteger maxVal squared asPoint. self shouldnt:[bb copyBits] raise: Error. ! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:32'! testFillingWayOutside | f1 bb | f1 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb fillColor: Color black. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: 100; height: 100. self shouldnt:[bb copyBits] raise: Error. ! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:32'! testFillingWayOutside2 | f1 bb | f1 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb fillColor: Color black. bb destOrigin: 0@0. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. self shouldnt:[bb copyBits] raise: Error.! ! !BitBltClipBugs methodsFor: 'as yet unclassified' stamp: 'ar 3/8/2003 00:32'! testFillingWayOutside3 | f1 bb | f1 := Form extent: 100@100 depth: 1. bb := BitBlt toForm: f1. bb combinationRule: 3. bb fillColor: Color black. bb destOrigin: SmallInteger maxVal squared asPoint. bb width: SmallInteger maxVal squared; height: SmallInteger maxVal squared. self shouldnt:[bb copyBits] raise: Error. ! ! ClassTestCase subclass: #BitBltTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'GraphicsTests-Primitives'! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:12'! testAlphaCompositing "self run: #testAlphaCompositing" | bb f1 f2 mixColor result eps | f1 := Form extent: 1@1 depth: 32. f2 := Form extent: 1@1 depth: 32. eps := 0.5 / 255. 0 to: 255 do:[:i| f1 colorAt: 0@0 put: Color blue. mixColor := Color red alpha: i / 255.0. f2 colorAt: 0@0 put: mixColor. mixColor := f2 colorAt: 0@0. bb := BitBlt toForm: f1. bb sourceForm: f2. bb combinationRule: Form blend. bb copyBits. result := f1 colorAt: 0@0. self assert: (result red - mixColor alpha) abs < eps. self assert: (result blue - (1.0 - mixColor alpha)) abs < eps. self assert: result alpha = 1.0. ].! ! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:12'! testAlphaCompositing2 "self run: #testAlphaCompositing2" | bb f1 f2 mixColor result eps | f1 := Form extent: 1@1 depth: 32. f2 := Form extent: 1@1 depth: 32. eps := 0.5 / 255. 0 to: 255 do:[:i| f1 colorAt: 0@0 put: Color transparent. mixColor := Color red alpha: i / 255.0. f2 colorAt: 0@0 put: mixColor. mixColor := f2 colorAt: 0@0. bb := BitBlt toForm: f1. bb sourceForm: f2. bb combinationRule: Form blend. bb copyBits. result := f1 colorAt: 0@0. self assert: (result red - mixColor alpha) abs < eps. self assert: result alpha = mixColor alpha. ].! ! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:13'! testAlphaCompositing2Simulated "self run: #testAlphaCompositing2Simulated" | bb f1 f2 mixColor result eps | Smalltalk at: #BitBltSimulation ifPresent: [:bitblt| f1 := Form extent: 1@1 depth: 32. f2 := Form extent: 1@1 depth: 32. eps := 0.5 / 255. 0 to: 255 do:[:i| f1 colorAt: 0@0 put: Color transparent. mixColor := Color red alpha: i / 255.0. f2 colorAt: 0@0 put: mixColor. mixColor := f2 colorAt: 0@0. bb := BitBlt toForm: f1. bb sourceForm: f2. bb combinationRule: Form blend. bb copyBitsSimulated. result := f1 colorAt: 0@0. self assert: (result red - mixColor alpha) abs < eps. self assert: result alpha = mixColor alpha. ].]! ! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:13'! testAlphaCompositingSimulated "self run: #testAlphaCompositingSimulated" | bb f1 f2 mixColor result eps | Smalltalk at: #BitBltSimulation ifPresent:[:bitblt| f1 := Form extent: 1@1 depth: 32. f2 := Form extent: 1@1 depth: 32. eps := 0.5 / 255. 0 to: 255 do:[:i| f1 colorAt: 0@0 put: Color blue. mixColor := Color red alpha: i / 255.0. f2 colorAt: 0@0 put: mixColor. mixColor := f2 colorAt: 0@0. bb := BitBlt toForm: f1. bb sourceForm: f2. bb combinationRule: Form blend. bb copyBitsSimulated. result := f1 colorAt: 0@0. self assert: (result red - mixColor alpha) abs < eps. self assert: (result blue - (1.0 - mixColor alpha)) abs < eps. self assert: result alpha = 1.0. ]].! ! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:13'! testPeekerUnhibernateBug "self run: #testPeekerUnhibernateBug" | bitBlt | bitBlt := BitBlt bitPeekerFromForm: Display. bitBlt destForm hibernate. self shouldnt:[bitBlt pixelAt: 1@1] raise: Error.! ! !BitBltTest methodsFor: 'bugs' stamp: 'sd 6/5/2005 10:14'! testPokerUnhibernateBug "self run: #testPokerUnhibernateBug" | bitBlt | bitBlt := BitBlt bitPokerToForm: Display. bitBlt sourceForm hibernate. self shouldnt:[bitBlt pixelAt: 1@1 put: 0] raise: Error.! ! 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: '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: 'initialize-release'! release super release. squareForm release. squareForm _ nil! ! !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: 'CdG 10/17/2005 20:51'! fileOut | fileName | fileName := UIManager default request: 'File name?' translated initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. Cursor normal showWhile: [model writeOnFileNamed: fileName]. ! ! !BitEditor methodsFor: 'menu messages' stamp: 'BG 12/5/2003 13:53'! getCurrentColor | formExtent form c | c := Color colorFromPixelValue: color depth: Display depth. formExtent _ 30@30" min: 10@ 10//(2+1@2)". "compute this better" form _ Form extent: formExtent depth: Display depth. form borderWidth: 5. form border: form boundingBox width: 4 fillColor: Color white. form fill: form boundingBox fillColor: c. ^form! ! !BitEditor methodsFor: 'menu messages' stamp: 'BG 12/5/2003 13:21'! 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: Display depth. squareForm fillColor: aColor. self changed: #getCurrentColor! ! !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 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 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: '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: '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: 'private' stamp: 'BG 12/4/2003 10:18'! 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 _ ColorSystemView 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: 'BG 12/5/2003 13:40'! 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. aSwitchView _ PluggableButtonView on: button getState: #isOn action: #turnOn label: #getCurrentColor. index = 1 ifTrue: [button onAction: [menuView model setColor: Color fromUser. aSwitchView label: menuView model getCurrentColor; displayView ] ] ifFalse: [button onAction: [menuView model setTransparentColor]]. 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: 'accessing' stamp: 'ar 3/3/2001 16:11'! atAllPut: value "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." super atAllPut: value.! ! !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' stamp: 'ar 9/21/2001 23:06'! 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 | (byte < 0 or:[byte > 255]) ifTrue:[^self errorImproperStore]. 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: 'ar 3/3/2001 16:18'! byteSize ^self size * 4! ! !Bitmap methodsFor: 'accessing' stamp: 'nk 7/30/2004 17:53'! copyFromByteArray: byteArray "This method should work with either byte orderings" | myHack byteHack | myHack := Form new hackBits: self. byteHack := Form new hackBits: byteArray. SmalltalkImage current isLittleEndian ifTrue: [byteHack swapEndianness]. byteHack displayOn: myHack! ! !Bitmap methodsFor: 'accessing' stamp: 'ar 11/2/1998 12:19'! defaultElement "Return the default element of the receiver" ^0! ! !Bitmap methodsFor: 'accessing' stamp: 'ar 3/3/2001 22:41'! 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]! ! !Bitmap methodsFor: 'accessing' stamp: 'ar 3/3/2001 22:42'! integerAt: index put: anInteger "Store the integer at the given index" | word | anInteger < 0 ifTrue:["word _ 16r100000000 + anInteger" word _ (anInteger + 1) negated bitInvert32] ifFalse:[word _ anInteger]. self basicAt: index put: word. ^anInteger! ! !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: 'as yet unclassified' stamp: 'yo 2/18/2004 18:28'! asByteArray "Faster way to make a byte array from me. copyFromByteArray: makes equal Bitmap." | f bytes hack | f _ Form extent: 4@self size depth: 8 bits: self. bytes _ ByteArray new: self size * 4. hack _ Form new hackBits: bytes. SmalltalkImage current isLittleEndian ifTrue:[hack swapEndianness]. hack copyBits: f boundingBox from: f at: (0@0) clippingBox: hack boundingBox rule: Form over fillColor: nil map: nil. "f displayOn: hack." ^ bytes. ! ! !Bitmap methodsFor: 'as yet unclassified' stamp: 'RAA 7/28/2000 21:51'! copy ^self clone! ! !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' stamp: 'nk 12/31/2003 16:02'! storeBits: startBit to: stopBit on: aStream "Store my bits as a hex string, breaking the lines every 100 bytes or so to comply with the maximum line length limits of Postscript (255 bytes). " | lineWidth | lineWidth := 0. self do: [:word | startBit to: stopBit by: -4 do: [:shift | aStream nextPut: (word >> shift bitAnd: 15) asHexDigit. lineWidth := lineWidth + 1]. (lineWidth > 100) ifTrue: [aStream cr. lineWidth := 0]]. lineWidth > 0 ifTrue: [ aStream cr ].! ! !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: '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: '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: '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 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)]! ! !Bitmap class methodsFor: 'utilities' stamp: 'sd 6/28/2003 09:33'! swapBytesIn: aNonPointerThing from: start to: stop "Perform a bigEndian/littleEndian byte reversal of my words. We only intend this for non-pointer arrays. Do nothing if I contain pointers." | hack blt | "The implementation is a hack, but fast for large ranges" hack _ Form new hackBits: aNonPointerThing. 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. ! ! TestCase subclass: #BitmapBugz instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !BitmapBugz methodsFor: 'as yet unclassified' stamp: 'ar 8/2/2003 19:21'! testBitmapByteAt | bm | bm := Bitmap new: 1. 1 to: 4 do:[:i| self should:[bm byteAt: i put: 1000] raise: Error. ].! ! OrientedFillStyle subclass: #BitmapFillStyle instanceVariableNames: 'form tileFlag' classVariableNames: '' poolDictionaries: '' category: 'Balloon-Fills'! !BitmapFillStyle commentStamp: '' prior: 0! A BitmapFillStyle fills using any kind of form. Instance variables: form
The form to be used as fill. tileFlag If true, then the form is repeatedly drawn to fill the area.! !BitmapFillStyle methodsFor: 'accessing' stamp: 'wiz 1/16/2005 20:17'! direction ^direction ifNil:[direction :=( (normal y @ normal x negated) * form width / form height ) rounded]! ! !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: 'wiz 1/16/2005 20:18'! normal ^normal ifNil:[normal := ((direction y negated @ direction x) * form height / form width ) rounded]! ! !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: 'converting' stamp: 'ar 11/11/1998 22:41'! asColor ^form colorAt: 0@0! ! !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: '*Morphic-Balloon' stamp: 'dgd 10/17/2003 22:34'! addFillStyleMenuItems: aMenu hand: aHand from: aMorph "Add the items for changing the current fill style of the receiver" aMenu add: 'choose new graphic' translated target: self selector: #chooseNewGraphicIn:event: argument: aMorph. aMenu add: 'grab new graphic' translated target: self selector: #grabNewGraphicIn:event: argument: aMorph. super addFillStyleMenuItems: aMenu hand: aHand from: aMorph.! ! !BitmapFillStyle methodsFor: '*Morphic-Balloon' stamp: 'nk 6/12/2004 09:59'! chooseNewGraphicIn: aMorph event: evt "Used by any morph that can be represented by a graphic" | aGraphicalMenu | aGraphicalMenu := GraphicalMenu new initializeFor: self withForms: aMorph reasonableBitmapFillForms coexist: true. aGraphicalMenu selector: #newForm:forMorph:; argument: aMorph. evt hand attachMorph: aGraphicalMenu! ! !BitmapFillStyle methodsFor: '*Morphic-Balloon' stamp: 'wiz 8/30/2003 16:54'! grabNewGraphicIn: aMorph event: evt "Used by any morph that can be represented by a graphic" | fill | fill _ Form fromUser. fill boundingBox area = 0 ifTrue: [^ self]. self form: fill. self direction: fill width @ 0. self normal: 0 @ fill height. aMorph changed! ! !BitmapFillStyle methodsFor: '*Morphic-Balloon' 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: 'KLC 1/27/2004 13:33'! fromForm: aForm | fs | fs := self form: aForm. fs origin: 0@0. 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! ! TestCase subclass: #BitmapStreamTests instanceVariableNames: 'random array stream' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !BitmapStreamTests commentStamp: 'nk 3/7/2004 14:26' prior: 0! This is an incomplete test suite for storing and reading various word- and short-word subclasses of ArrayedCollection. It demonstrates some problems with filing in of certain kinds of arrayed objects, including: ShortPointArray ShortIntegerArray ShortRunArray WordArray MatrixTransform2x3 In 3.6b-5331, I get 8 passed/6 failed/6 errors (not counting the MatrixTransform2x3 tests, which were added later). I ran into problems when trying to read back the SqueakLogo flash character morph, after I'd done a 'save morph to disk' from its debug menu. The words within the ShortPointArrays and ShortRunArrays were reversed. ! !BitmapStreamTests methodsFor: 'Running' stamp: 'stephaneducasse 2/3/2006 22:39'! setUp random := Random new.! ! !BitmapStreamTests methodsFor: 'tests-MatrixTransform2x3' stamp: 'stephaneducasse 2/3/2006 22:39'! testMatrixTransform2x3WithImageSegment array := MatrixTransform2x3 new. 1 to: 6 do: [ :i | array at: i put: self randomFloat ]. self validateImageSegment ! ! !BitmapStreamTests methodsFor: 'tests-MatrixTransform2x3' stamp: 'stephaneducasse 2/3/2006 22:39'! testMatrixTransform2x3WithRefStream array := MatrixTransform2x3 new. 1 to: 6 do: [ :i | array at: i put: self randomFloat ]. self validateRefStream ! ! !BitmapStreamTests methodsFor: 'tests-MatrixTransform2x3' stamp: 'stephaneducasse 2/3/2006 22:39'! testMatrixTransform2x3WithRefStreamOnDisk array := MatrixTransform2x3 new. 1 to: 6 do: [ :i | array at: i put: self randomFloat ]. self validateRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-MatrixTransform2x3' stamp: 'stephaneducasse 2/3/2006 22:39'! testMatrixTransform2x3WithSmartRefStream array := MatrixTransform2x3 new. 1 to: 6 do: [ :i | array at: i put: self randomFloat ]. self validateSmartRefStream ! ! !BitmapStreamTests methodsFor: 'tests-MatrixTransform2x3' stamp: 'stephaneducasse 2/3/2006 22:39'! testMatrixTransform2x3WithSmartRefStreamOnDisk array := MatrixTransform2x3 new. 1 to: 6 do: [ :i | array at: i put: self randomFloat ]. self validateSmartRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-misc' stamp: 'stephaneducasse 2/3/2006 22:39'! testOtherClasses #(WordArrayForSegment FloatArray PointArray IntegerArray SoundBuffer String ShortPointArray ShortIntegerArray WordArray Array DependentsArray ByteArray Bitmap ColorArray ) do: [:s | | a | a := (Smalltalk at: s) new: 3. self assert: (a basicSize * a bytesPerBasicElement = a byteSize). ] ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayReadRefStream2 |refStrm| refStrm := ReferenceStream on: ((RWBinaryOrTextStream with: (ByteArray withAll: #(20 6 17 83 104 111 114 116 73 110 116 101 103 101 114 65 114 114 97 121 0 0 0 2 0 0 0 1 0 2 0 3))) reset; binary). self assert: (refStrm next = (ShortIntegerArray with: 0 with: 1 with: 2 with: 3)).! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithImageSegment array := ShortIntegerArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortInt ]. self validateImageSegment ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithRefStream array := ShortIntegerArray with: 0 with: 1 with: 2 with: 3. self validateRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithRefStream2 array := ShortIntegerArray with: 0 with: 1 with: 2 with: 3. self validateRefStream. self assert: stream byteStream contents = (ByteArray withAll: #(20 6 17 83 104 111 114 116 73 110 116 101 103 101 114 65 114 114 97 121 0 0 0 2 0 0 0 1 0 2 0 3)) ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithRefStreamOnDisk array := ShortIntegerArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortInt ]. self validateRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithSmartRefStream array := ShortIntegerArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortInt ]. self validateSmartRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithSmartRefStream2 array := ShortIntegerArray with: 0 with: 1 with: 2 with: 3. self validateSmartRefStream. self assert: (stream contents asByteArray last: 15) = (ByteArray withAll: #(0 0 0 2 0 0 0 1 0 2 0 3 33 13 13)) ! ! !BitmapStreamTests methodsFor: 'tests-ShortIntegerArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortIntegerArrayWithSmartRefStreamOnDisk array := ShortIntegerArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortInt ]. self validateSmartRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithImageSegment array := ShortPointArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortPoint ]. self validateImageSegment ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithRefStream array := ShortPointArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortPoint ]. self validateRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithRefStream2 array := ShortPointArray with: 0@1 with: 2@3. self validateRefStream. self assert: stream byteStream contents = (ByteArray withAll: #(20 6 15 83 104 111 114 116 80 111 105 110 116 65 114 114 97 121 0 0 0 2 0 0 0 1 0 2 0 3 )) ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithRefStreamOnDisk array := ShortPointArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortPoint ]. self validateRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithSmartRefStream array := ShortPointArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortPoint ]. self validateSmartRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithSmartRefStream2 array := ShortPointArray with: 0@1 with: 2@3. self validateSmartRefStream. self assert: (stream contents asByteArray last: 15) = (ByteArray withAll: #(0 0 0 2 0 0 0 1 0 2 0 3 33 13 13)) ! ! !BitmapStreamTests methodsFor: 'tests-ShortPointArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortPointArrayWithSmartRefStreamOnDisk array := ShortPointArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomShortPoint ]. self validateSmartRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'nk 3/17/2004 16:39'! createSampleShortRunArray ^ShortRunArray newFrom: { 0. 1. 1. 2. 2. 2. 3. 3. 3. 3 }! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithImageSegment array := self createSampleShortRunArray. self validateImageSegment ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithRefStream array := self createSampleShortRunArray. self validateRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithRefStreamOnDisk array := self createSampleShortRunArray. self validateRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithSmartRefStream array := self createSampleShortRunArray. self validateSmartRefStream ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithSmartRefStream2 array := self createSampleShortRunArray. self validateSmartRefStream. self assert: (stream contents asByteArray last: 23) = (ByteArray withAll: #(0 0 0 4 0 1 0 0 0 2 0 1 0 3 0 2 0 4 0 3 33 13 13)) ! ! !BitmapStreamTests methodsFor: 'tests-ShortRunArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testShortRunArrayWithSmartRefStreamOnDisk array := self createSampleShortRunArray. self validateSmartRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-WordArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testWordArrayWithImageSegment array := WordArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomWord ]. self validateImageSegment ! ! !BitmapStreamTests methodsFor: 'tests-WordArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testWordArrayWithRefStream array := WordArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomWord ]. self validateRefStream ! ! !BitmapStreamTests methodsFor: 'tests-WordArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testWordArrayWithRefStreamOnDisk array := WordArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomWord ]. self validateRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'tests-WordArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testWordArrayWithSmartRefStream array := WordArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomWord ]. self validateSmartRefStream ! ! !BitmapStreamTests methodsFor: 'tests-WordArray' stamp: 'stephaneducasse 2/3/2006 22:39'! testWordArrayWithSmartRefStreamOnDisk array := WordArray new: 10. 1 to: 10 do: [ :i | array at: i put: self randomWord ]. self validateSmartRefStreamOnDisk ! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! randomFloat "Answer a random 32-bit float" | w | random seed: (w := random nextValue). ^w! ! !BitmapStreamTests methodsFor: 'private' stamp: 'nk 7/5/2003 16:33'! randomShortInt ^((random next * 65536) - 32768) truncated! ! !BitmapStreamTests methodsFor: 'private' stamp: 'nk 7/5/2003 16:00'! randomShortPoint ^(((random next * 65536) @ (random next * 65536)) - (32768 @ 32768)) truncated! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! randomWord "Answer a random 32-bit integer" | w | random seed: (w := random nextValue). ^w truncated! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! validateImageSegment "array is set up with an array." | other filename | filename := 'bitmapStreamTest.extSeg'. FileDirectory default deleteFileNamed: filename ifAbsent: [ ]. (ImageSegment new copyFromRootsForExport: (Array with: array)) writeForExport: filename. other := (FileDirectory default readOnlyFileNamed: filename) fileInObjectAndCode. self assert: array = other originalRoots first! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! validateRefStream "array is set up with an array." | other rwstream | rwstream := RWBinaryOrTextStream on: (ByteArray new: array basicSize * 6). stream := ReferenceStream on: rwstream. stream nextPut: array; close. rwstream position: 0. stream := ReferenceStream on: rwstream. other := stream next. stream close. self assert: array = other! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! validateRefStreamOnDisk "array is set up with an array." | other filename | filename := 'bitmapStreamTest.ref'. FileDirectory default deleteFileNamed: filename ifAbsent: [ ]. stream := ReferenceStream fileNamed: filename. stream nextPut: array; close. stream := ReferenceStream fileNamed: filename. other := stream next. stream close. self assert: array = other! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! validateSmartRefStream "array is set up with an array." | other | stream := RWBinaryOrTextStream on: (ByteArray new: array basicSize * 6). stream binary. stream fileOutClass: nil andObject: array. stream position: 0. stream binary. other := stream fileInObjectAndCode. self assert: array = other! ! !BitmapStreamTests methodsFor: 'private' stamp: 'stephaneducasse 2/3/2006 22:39'! validateSmartRefStreamOnDisk "array is set up with an array." | other filename | filename := 'bitmapStreamTest.ref'. FileDirectory default deleteFileNamed: filename ifAbsent: [ ]. stream := FileDirectory default fileNamed: filename. stream fileOutClass: nil andObject: array. stream close. stream := FileDirectory default fileNamed: filename. other := stream fileInObjectAndCode. stream close. self assert: array = other! ! BlobMorph subclass: #BlobMPEGMorph instanceVariableNames: 'mpegLogic form movieDrawArea primary quadNumber' classVariableNames: '' poolDictionaries: '' category: 'Movies-Player'! !BlobMPEGMorph commentStamp: '' prior: 0! Ok this is a little follow on to David's BlobMorph. Why not embedded a movie in the blob I thought. So with a few minutes of help from John Maloney we have something very interesting. Enjoy John M McIntosh Dec 2000. (Christmas early)! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 10/19/2000 16:02'! form ^form! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:48'! form: aForm form := aForm! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 12/19/2000 16:01'! movieDrawArea ^movieDrawArea! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 10/19/2000 15:54'! mpegLogic ^mpegLogic! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:48'! mpegLogic: aValue mpegLogic := aValue! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 12/19/2000 15:45'! primary ^primary! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 12/19/2000 21:52'! quadNumber ^quadNumber! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:48'! quadNumber: aNumber quadNumber := aNumber! ! !BlobMPEGMorph methodsFor: 'access' stamp: 'JMM 10/19/2000 15:59'! stream ^0! ! !BlobMPEGMorph methodsFor: 'drawing' stamp: 'stephaneducasse 2/4/2006 20:48'! drawOn: aCanvas "Display the receiver, a spline curve, approximated by straight line segments. Fill with the MPEG movie" | cm f filled quadRect | cm := Bitmap new: 2. cm at: 1 put: 0. cm at: 2 put: 32767. f := Form extent: self extent depth: 16. filled := self filledForm. (BitBlt toForm: f) sourceForm: filled; sourceRect: filled boundingBox; destRect: (0 @ 0 extent: filled extent); colorMap: cm; combinationRule: Form over; copyBits. quadNumber = 1 ifTrue: [quadRect := Rectangle origin: form boundingBox topLeft corner: form boundingBox center]. quadNumber = 2 ifTrue: [quadRect := Rectangle origin: form boundingBox topCenter corner: form boundingBox rightCenter]. quadNumber = 3 ifTrue: [quadRect := Rectangle origin: form boundingBox leftCenter corner: form boundingBox bottomCenter]. quadNumber = 4 ifTrue: [quadRect := Rectangle origin: form boundingBox center corner: form boundingBox bottomRight]. (BitBlt toForm: f) sourceForm: form; sourceRect: quadRect; destRect: (0 @ 0 extent: f extent); combinationRule: Form and; copyBits. aCanvas image: f at: self position. self drawBorderOn: aCanvas. self drawArrowsOn: aCanvas! ! !BlobMPEGMorph methodsFor: 'drawing' stamp: 'JMM 1/4/2001 11:07'! playStream: aStream mpegLogic playStream: aStream. ! ! !BlobMPEGMorph methodsFor: 'drawing' stamp: 'JMM 12/19/2000 16:41'! playVideoStream: aStream mpegLogic playVideoStream: aStream. ! ! !BlobMPEGMorph methodsFor: 'initialization' stamp: 'aoy 2/15/2003 21:43'! initialize: primaryFlag mpegPlayer: aMpegPlayerOrFileName | rect sizeToOverLapBoundary | primary := primaryFlag. rect := self bounds. sizeToOverLapBoundary := 3.0. mpegLogic := primary ifTrue: [form := Form extent: ((sizeToOverLapBoundary * rect width) @ (sizeToOverLapBoundary * rect height)) truncated depth: 32. movieDrawArea := SketchMorph withForm: form. MPEGPlayer playFile: aMpegPlayerOrFileName onMorph: movieDrawArea] ifFalse: [form := aMpegPlayerOrFileName form. movieDrawArea := aMpegPlayerOrFileName movieDrawArea. aMpegPlayerOrFileName mpegLogic]! ! !BlobMPEGMorph methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:48'! initializeBlobShape | verts modifier | verts := {59@40. 74@54. 79@74. 77@93. 57@97. 37@97. 22@83. 15@67. 22@50. 33@35. 47@33}. modifier := 0 @ 0. (self quadNumber = 2) ifTrue: [ modifier := 0 @ 75]. (self quadNumber = 3) ifTrue: [ modifier := 75 @ 0]. (self quadNumber = 4) ifTrue: [ modifier := 75 @ 75]. verts := verts + modifier. self vertices: verts color: self color borderWidth: 1 borderColor: Color black! ! !BlobMPEGMorph methodsFor: 'initialization' stamp: 'JMM 1/4/2001 10:54'! initializeChildMpegPlayer: aMpegPlayerOrFileName self initialize: false mpegPlayer: aMpegPlayerOrFileName ! ! !BlobMPEGMorph methodsFor: 'initialization' stamp: 'JMM 1/4/2001 10:54'! initializePrimaryMpegPlayer: aMpegPlayerOrFileName self initialize: true mpegPlayer: aMpegPlayerOrFileName ! ! !BlobMPEGMorph methodsFor: 'stepping' stamp: 'JMM 10/19/2000 15:57'! adjustColors ^self! ! !BlobMPEGMorph methodsFor: 'stepping' stamp: 'JMM 12/19/2000 15:39'! 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: 60) max: 20) "was min: 80" degrees: (((thisDeg min: nextDeg-5) max: prevDeg+5) min: dincr*2+prevDeg)) ]. ^ new ! ! !BlobMPEGMorph methodsFor: 'stepping' stamp: 'JMM 12/19/2000 15:29'! mergeBlobs ^self! ! !BlobMPEGMorph methodsFor: 'testing' stamp: 'JMM 10/19/2000 16:29'! stepTime ^1.0 / (self mpegLogic videoFrameRate: self stream) * 1000! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BlobMPEGMorph class instanceVariableNames: ''! !BlobMPEGMorph class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:48'! buildMorphics: aFileName | primary child | primary := (self basicNew quadNumber: 1) initialize. self remember: primary. primary initializePrimaryMpegPlayer: aFileName. primary openInWorld. 2 to: 4 do: [:i | child := (self basicNew quadNumber: i) initialize. self remember: child. child initializeChildMpegPlayer: primary. child openInWorld]. ^primary ! ! !BlobMPEGMorph class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:48'! newWithMovie: aFileName | primary | primary := self buildMorphics: aFileName. primary playStream: 0. ^primary ! ! !BlobMPEGMorph class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:48'! newWithMovieNoSound: aFileName | primary | primary := self buildMorphics: aFileName. primary playVideoStream: 0. ^primary ! ! PolygonMorph subclass: #BlobMorph instanceVariableNames: 'random velocity sneaky' classVariableNames: 'AllBlobs' poolDictionaries: '' category: 'MorphicExtras-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: 'debug and other' stamp: 'sma 2/12/2000 13:08'! installModelIn: aWorld "Overwritten to not add handles to the receiver."! ! !BlobMorph methodsFor: 'geometry' stamp: 'tk 7/14/2001 11:06'! setConstrainedPosition: aPoint hangOut: partiallyOutside "Deal with dragging the blob over another blob which results in spontaneous deletations." self owner ifNil: [^ self]. super setConstrainedPosition: aPoint hangOut: false. "note that we keep them from overlapping"! ! !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 methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ random next < 0.25 ifTrue: [Color random] ifFalse: [Color random alpha: random next * 0.4 + 0.4]! ! !BlobMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:42'! initialize "initialize the state of the receiver" random _ Random new. sneaky _ random next < 0.75. super initialize. "" self beSmoothCurve; initializeBlobShape; 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: '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: 'ccn 8/28/2001 20:51'! 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 isNil or: [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 and presenter' 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: 'submorphs-add/remove' stamp: 'sma 2/6/2000 17:41'! delete self class delete: self. super delete! ! !BlobMorph methodsFor: 'testing' stamp: 'sma 2/6/2000 18:41'! stepTime "Answer the desired time between steps in milliseconds." ^ 125! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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! ! !BlobMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 01:20'! descriptionForPartsBin ^ self partName: 'Blob' categories: #('Demo') documentation: 'A patch of primordial slime'! ! ParseNode subclass: #BlockArgsNode instanceVariableNames: 'temporaries' classVariableNames: '' poolDictionaries: '' category: 'Compiler-ParseNodes'! Error subclass: #BlockCannotReturn instanceVariableNames: 'result deadHome' classVariableNames: '' poolDictionaries: '' category: '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: 'ajh 2/6/2002 11:12'! deadHome ^ deadHome! ! !BlockCannotReturn methodsFor: 'accessing' stamp: 'ajh 2/6/2002 11:12'! deadHome: context deadHome _ context! ! !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! ! Object subclass: #BlockClosure instanceVariableNames: 'method environment' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Contexts'! !BlockClosure commentStamp: 'ajh 7/19/2004 14:57' prior: 0! A BlockClosure is a block of Smalltalk code (enclosed within []) that may be executed later by sending #valueWithArguments: (or one of its variants) to it. A block can take arguments by specifying the names of the arguments in the beginning of the block, as in "[:arg1 :arg2 | ...]", and can have its own local temps, as in "[:arg1 | | temp1 temp2 | ...]". The block may reference variables outside its scope directly by name. It also may return from its home context by using ^, otherwise, the value of the last statement is returned to the sender of valueWithArguments:. Structure: method CompiledMethod2 Contains the block's code. It has its own method separate from its home method. environment ClosureEnvironment | Object The lexical environment the block was created in. The environment only contains variables that were captured/reference by this block or other sister blocks. If only self and/or its instance variables are captured then the environment is simply the receiver object. Each non-inlined blocks has its own CompiledMethod. These block methods are held in the literals of the home method and sent the #createBlock: message at runtime to create BlockClosures. Home method temps captured by inner blocks are placed inside a ClosureEnvironment when the home method is started. This environment is supplied as the argument to each #createBlock:. When #value... is sent to a block closure, its method is executed in a new MethodContext with its closure environment as the receiver. The block method accesses its free variables (captured home temps) via this environment. Closure environments are nested mirroring the nesting of blocks. Each environment points to its parent environment (the top method environment has no parent). However, for efficiency, environments that have no captured temps are skipped (never created). For example, an environment's parent may actually be its grand-parent. There is no special parent variable in ClosureEnvironment, it is just another named variable such as 'self' or 'parent env' (special var with space so it can't be referenced by user code), or it may not be their at all. A block closure that returns to its home context does so by finding the thisContext sender that owns the top environment. A return inside a block forces the home environment to be created even if it has no captured temps. Each context holds its local environment (which holds its captured temps) in its #myEnv instance variable (previously the unused #receiverMap variable). Code that references captured temps goes through the #myEnv context variable. Block closures are totally separate from their home context. They are reentrant and each activation has its own block-local temps. So except for the thisContext psuedo-variable, contexts are now LIFO (assuming we get rid of old block contexts and recompile the whole image). ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 6/24/2004 03:40'! env ^ environment! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 2/6/2003 13:24'! hasLiteralSuchThat: testBlock (testBlock value: method) ifTrue: [^ true]. ^ method hasLiteralSuchThat: testBlock! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 1/31/2003 16:59'! hasLiteralThorough: literal "Answer true if literal is identical to any literal imbedded in my method" method == literal ifTrue: [^ true]. ^ method hasLiteralThorough: literal! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 2/6/2003 13:27'! hasMethodReturn "Answer whether the receiver has a return ('^') in its code." ^ self method remoteReturns! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 1/21/2003 13:16'! isBlock ^ true! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 5/21/2001 14:01'! method ^ method! ! !BlockClosure methodsFor: 'accessing' stamp: 'ajh 5/28/2001 14:37'! numArgs ^ method numArgs! ! !BlockClosure methodsFor: 'comparing' stamp: 'ajh 6/24/2004 03:56'! = other self class == other class ifFalse: [^ false]. self env = other env ifFalse: [^ false]. ^ self method = other method! ! !BlockClosure methodsFor: 'comparing' stamp: 'ajh 10/4/2002 17:12'! hash ^ method hash! ! !BlockClosure methodsFor: 'controlling' stamp: 'md 10/14/2004 17:04'! doWhileFalse: conditionBlock "Evaluate the receiver once, then again as long the value of conditionBlock is false." | result | [result _ self value. conditionBlock value] whileFalse. ^ result! ! !BlockClosure methodsFor: 'controlling' stamp: 'md 10/14/2004 17:04'! doWhileTrue: conditionBlock "Evaluate the receiver once, then again as long the value of conditionBlock is true." | result | [result _ self value. conditionBlock value] whileTrue. ^ result! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! repeat "Evaluate the receiver repeatedly, ending only if the block explicitly returns." [self value. true] whileTrue! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! 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! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! 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: []! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! 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]! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! 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: []! ! !BlockClosure methodsFor: 'controlling' stamp: 'ajh 7/15/2001 16:03'! 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]! ! !BlockClosure methodsFor: 'evaluating' stamp: 'md 10/14/2004 17:02'! bench "See how many times I can value in 5 seconds. I'll answer a meaningful description." | startTime endTime count | count _ 0. endTime _ Time millisecondClockValue + 5000. startTime _ Time millisecondClockValue. [ Time millisecondClockValue > endTime ] whileFalse: [ self value. count _ count + 1 ]. endTime _ Time millisecondClockValue. ^count = 1 ifTrue: [ ((endTime - startTime) // 1000) printString, ' seconds.' ] ifFalse: [ ((count * 1000) / (endTime - startTime)) asFloat printString, ' per second.' ]! ! !BlockClosure methodsFor: 'evaluating' stamp: 'md 10/14/2004 17:03'! durationToRun "Answer the duration taken to execute this block." ^ Duration milliSeconds: self timeToRun ! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 1/13/2002 13:04'! ifError: errorHandlerBlock "Evaluate the block represented by the receiver, and normally return it's value. If an error occurs, the errorHandlerBlock is evaluated, and it's value is instead returned. The errorHandlerBlock must accept zero, one, or two parameters (the error message and the receiver)." "Examples: [1 whatsUpDoc] ifError: [:err :rcvr | 'huh?']. [1 / 0] ifError: [:err :rcvr | 'ZeroDivide' = err ifTrue: [Float infinity] ifFalse: [self error: err]] " ^ self on: Error do: [:ex | errorHandlerBlock valueWithPossibleArgs: {ex description. ex receiver}]! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/15/2001 15:57'! timeToRun "Answer the number of milliseconds taken to execute this block." ^ Time millisecondsToRun: self ! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:43'! value "Evaluate the block with no args. Fail if the block expects other than 0 arguments." ^ environment executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'md 3/28/2006 20:17'! valueWithExit self value: [ ^nil ]! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:43'! value: arg1 "Evaluate the block with the given args. Fail if the block expects other than 1 arguments." ^ environment with: arg1 executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:43'! value: arg1 value: arg2 "Evaluate the block with the given args. Fail if the block expects other than 2 arguments." ^ environment with: arg1 with: arg2 executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:42'! value: arg1 value: arg2 value: arg3 "Evaluate the block with the given args. Fail if the block expects other than 3 arguments." ^ environment with: arg1 with: arg2 with: arg3 executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:42'! value: arg1 value: arg2 value: arg3 value: arg4 "Evaluate the block with the given args. Fail if the block expects other than 4 arguments." ^ environment with: arg1 with: arg2 with: arg3 with: arg4 executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 7/19/2004 14:42'! valueWithArguments: anArray "Evaluate the block with given args. Fail if the block expects other than the given number of arguments." ^ environment withArgs: anArray executeMethod: method! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ajh 1/28/2003 14:44'! valueWithPossibleArgs: anArray | n | (n _ self numArgs) = 0 ifTrue: [^ self value]. n = anArray size ifTrue: [^ self valueWithArguments: anArray]. ^ self valueWithArguments: (n > anArray size ifTrue: [anArray, (Array new: n - anArray size)] ifFalse: [anArray copyFrom: 1 to: n])! ! !BlockClosure methodsFor: 'evaluating' stamp: 'md 10/14/2004 17:03'! valueWithPossibleArgument: anArg "Evaluate the block represented by the receiver. If the block requires one argument, use anArg, if it requires more than one, fill up the rest with nils." self numArgs = 0 ifTrue: [^self value]. self numArgs = 1 ifTrue: [^self value: anArg]. self numArgs > 1 ifTrue: [^self valueWithArguments: {anArg}, (Array new: self numArgs - 1)]! ! !BlockClosure methodsFor: 'evaluating' stamp: 'ar 2/23/2005 11:48'! valueWithin: aDuration onTimeout: timeoutBlock "Evaluate the receiver. If the evaluation does not complete in less than aDuration evaluate the timeoutBlock instead" | theProcess delay watchdog done result | aDuration <= Duration zero ifTrue: [^ timeoutBlock value ]. "the block will be executed in the current process" theProcess := Processor activeProcess. delay := aDuration asDelay. "make a watchdog process" watchdog := [ delay wait. "wait for timeout or completion" done ifFalse: [ theProcess signalException: TimedOut ] ] newProcess. "watchdog needs to run at high priority to do its job" watchdog priority: Processor timingPriority. "catch the timeout signal" ^ [ done := false. watchdog resume. "start up the watchdog" result := self value. "evaluate the receiver" done := true. "it has completed, so ..." delay delaySemaphore signal. "arrange for the watchdog to exit" result ] on: TimedOut do: [ :e | timeoutBlock value ]. ! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 7/15/2001 16:14'! assert self assert: self! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 5/20/2004 17:37'! ensure: aBlock "Evaluate a termination block after evaluating the receiver, regardless of whether the receiver's evaluation completes." | returnValue b | returnValue := self value. "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: [ "nil out aBlock temp before evaluating aBlock so it is not executed again if aBlock remote returns" b _ aBlock. thisContext tempAt: 1 put: nil. "aBlock _ nil" b value. ]. ^ returnValue! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 1/21/2003 17:50'! ifCurtailed: aBlock "Evaluate the receiver with an abnormal termination action." ^ self value! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 2/1/2003 00:30'! on: exception do: handlerAction "Evaluate the receiver in the scope of an exception handler." | handlerActive | "just a marker, fail and execute the following" handlerActive _ true. ^ self value! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 1/31/2003 20:41'! onDNU: selector do: handleBlock "Catch MessageNotUnderstood exceptions but only those of the given selector (DNU stands for doesNotUnderstand:)" ^ self on: MessageNotUnderstood do: [:exception | exception message selector = selector ifTrue: [handleBlock valueWithPossibleArgs: {exception}] ifFalse: [exception pass] ]! ! !BlockClosure methodsFor: 'exceptions' stamp: 'ajh 7/26/2002 11:49'! valueUninterruptably "Prevent remote returns from escaping the sender. Even attempts to terminate (unwind) this process will be halted and the process will resume here. A terminate message is needed for every one of these in the sender chain to get the entire process unwound." ^ self ifCurtailed: [^ self]! ! !BlockClosure methodsFor: 'initializing' stamp: 'ajh 6/24/2004 03:50'! env: aClosureEnvironment "the outer environment" environment _ aClosureEnvironment! ! !BlockClosure methodsFor: 'initializing' stamp: 'ajh 5/28/2001 18:37'! method: compiledMethod "compiledMethod will be the code I execute when I'm evaluated" method _ compiledMethod! ! !BlockClosure methodsFor: 'printing' stamp: 'ajh 9/10/2002 16:53'! printOn: aStream super printOn: aStream. aStream space; nextPutAll: self identityHashPrintString! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 6/24/2004 03:43'! asContext "Create a MethodContext that is ready to execute self. Assumes self takes no args (if it does the args will be nil)" ^ MethodContext sender: nil receiver: environment method: method arguments: #()! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 1/27/2003 18:51'! callCC "Call with current continuation, ala Scheme. Evaluate self against a copy of the sender's call stack, which can be resumed later" ^ self value: thisContext sender asContinuation! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 7/15/2001 16:03'! fork "Create and schedule a Process running the code in the receiver." ^ self newProcess resume! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 10/16/2002 11:14'! forkAndWait "Suspend current process while self runs" | semaphore | semaphore _ Semaphore new. [self ensure: [semaphore signal]] fork. semaphore wait. ! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 9/29/2001 21:00'! forkAt: priority "Create and schedule a Process running the code in the receiver at the given priority. Answer the newly created process." ^ self newProcess priority: priority; resume! ! !BlockClosure methodsFor: 'scheduling' stamp: 'md 10/14/2004 17:04'! forkAt: priority named: name "Create and schedule a Process running the code in the receiver at the given priority and having the given name. Answer the newly created process." | forkedProcess | forkedProcess := self newProcess. forkedProcess priority: priority. forkedProcess name: name. ^ forkedProcess resume! ! !BlockClosure methodsFor: 'scheduling' stamp: 'md 10/14/2004 17:05'! forkNamed: aString "Create and schedule a Process running the code in the receiver and having the given name." ^ self newProcess name: aString; resume! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 2/10/2003 14:23'! newProcess "Answer a Process running the code in the receiver. The process is not scheduled." "Simulation guard" ^ Process forContext: [self value. Processor terminateActive] asContext priority: Processor activePriority! ! !BlockClosure methodsFor: 'scheduling' stamp: 'ajh 7/27/2002 12:26'! simulate "Like run except interpret self using Smalltalk instead of VM. It is much slower." ^ self newProcess simulate! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 1/16/2002 18:32'! copyForSaving "obsolete"! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 1/16/2002 18:32'! fixTemps "obsolete"! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 1/31/2003 12:53'! reentrant! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 7/15/2001 16:13'! valueError self error: 'Incompatible number of args'! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 7/26/2002 11:47'! valueUnpreemptively "Evaluate the receiver (block), without the possibility of preemption by higher priority processes. Use this facility VERY sparingly!!" "Think about using Block>>valueUninterruptably first, and think about using Semaphore>>critical: before that, and think about redesigning your application even before that!! After you've done all that thinking, go right ahead and use it..." | activeProcess oldPriority result | activeProcess _ Processor activeProcess. oldPriority _ activeProcess priority. activeProcess priority: Processor highestPriority. result _ self ensure: [activeProcess priority: oldPriority]. "Yield after restoring priority to give the preempted processes a chance to run" Processor yield. ^result! ! !BlockClosure methodsFor: 'private' stamp: 'ajh 6/24/2004 03:34'! veryDeepInner: deepCopier "Do not copy my method (which can be shared because CompiledMethod2 are basically treated as immutables) or my home context (MethodContexts are treated as immutables too)" super veryDeepInner: deepCopier. method _ method. environment _ environment. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BlockClosure class instanceVariableNames: ''! 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: 'accessing' stamp: 'RAH 4/25/2000 19:49'! argumentCount "Answers the number of arguments needed to evaluate the receiver." #Valuable. ^ self numArgs! ! !BlockContext methodsFor: 'accessing' stamp: 'ajh 1/24/2003 12:35'! blockHome ^ self home! ! !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' stamp: 'ajh 1/31/2003 23:29'! finalBlockHome ^ self home! ! !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: 'md 4/27/2006 15:14'! hasInstVarRef "Answer whether the receiver references an instance variable." | method scanner end printer | home ifNil: [^false]. method _ self method. end _ self endPC. 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' stamp: 'ajh 1/21/2003 13:16'! isBlock ^ true! ! !BlockContext methodsFor: 'accessing' stamp: 'ajh 1/31/2003 12:12'! isExecutingBlock ^ true! ! !BlockContext methodsFor: 'accessing' stamp: 'ajh 9/28/2001 02:16'! isMethodContext ^ false! ! !BlockContext methodsFor: 'accessing'! method "Answer the compiled method in which the receiver was defined." ^home method! ! !BlockContext methodsFor: 'accessing' stamp: 'mdr 4/10/2001 10:34'! numArgs "Answer the number of arguments that must be used to evaluate this block" ^nargs! ! !BlockContext methodsFor: 'accessing'! receiver "Refer to the comment in ContextPart|receiver." ^home receiver! ! !BlockContext methodsFor: 'accessing' stamp: 'ajh 1/30/2003 15:45'! reentrant "Copy before calling so multiple activations can exist" ^ self copy! ! !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: 'controlling' stamp: 'jf 9/3/2003 16:45'! doWhileFalse: conditionBlock "Evaluate the receiver once, then again as long the value of conditionBlock is false." | result | [result _ self value. conditionBlock value] whileFalse. ^ result! ! !BlockContext methodsFor: 'controlling' stamp: 'jf 9/3/2003 16:39'! doWhileTrue: conditionBlock "Evaluate the receiver once, then again as long the value of conditionBlock is true." | result | [result _ self value. conditionBlock value] whileTrue. ^ result! ! !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: 'evaluating' stamp: 'cmm 2/16/2003 16:08'! bench "See how many times I can value in 5 seconds. I'll answer a meaningful description." | startTime endTime count | count _ 0. endTime _ Time millisecondClockValue + 5000. startTime _ Time millisecondClockValue. [ Time millisecondClockValue > endTime ] whileFalse: [ self value. count _ count + 1 ]. endTime _ Time millisecondClockValue. ^count = 1 ifTrue: [ ((endTime - startTime) // 1000) printString, ' seconds.' ] ifFalse: [ ((count * 1000) / (endTime - startTime)) asFloat printString, ' per second.' ]! ! !BlockContext methodsFor: 'evaluating' stamp: 'brp 9/25/2003 13:49'! durationToRun "Answer the duration taken to execute this block." ^ Duration milliSeconds: self timeToRun ! ! !BlockContext methodsFor: 'evaluating' stamp: 'ajh 1/13/2002 13:36'! ifError: errorHandlerBlock "Evaluate the block represented by the receiver, and normally return it's value. If an error occurs, the errorHandlerBlock is evaluated, and it's value is instead returned. The errorHandlerBlock must accept zero, one, or two parameters (the error message and the receiver)." "Examples: [1 whatsUpDoc] ifError: [:err :rcvr | 'huh?']. [1 / 0] ifError: [:err :rcvr | 'ZeroDivide' = err ifTrue: [Float infinity] ifFalse: [self error: err]] " ^ self on: Error do: [:ex | errorHandlerBlock valueWithPossibleArgs: {ex description. ex receiver}]! ! !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' stamp: 'nk 3/11/2001 11:49'! valueWithEnoughArguments: anArray "call me with enough arguments from anArray" | args | (anArray size == self numArgs) ifTrue: [ ^self valueWithArguments: anArray ]. args _ Array new: self numArgs. args replaceFrom: 1 to: (anArray size min: args size) with: anArray startingAt: 1. ^ self valueWithArguments: args! ! !BlockContext methodsFor: 'evaluating' stamp: 'md 3/28/2006 20:17'! valueWithExit self value: [ ^nil ]! ! !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 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: 'jrp 10/10/2004 22:28'! valueSupplyingAnswer: anObject ^ (anObject isCollection and: [anObject isString not]) ifTrue: [self valueSupplyingAnswers: {anObject}] ifFalse: [self valueSupplyingAnswers: {{'*'. anObject}}]! ! !BlockContext methodsFor: 'evaluating' stamp: 'md 11/10/2004 18:43'! valueSupplyingAnswers: aListOfPairs "evaluate the block using a list of questions / answers that might be called upon to automatically respond to Object>>confirm: or FillInTheBlank requests" ^ [self value] on: ProvideAnswerNotification do: [:notify | | answer caption | caption _ notify messageText withSeparatorsCompacted. "to remove new lines" answer _ aListOfPairs detect: [:each | caption = each first or: [caption includesSubstring: each first caseSensitive: false] or: [each first match: caption]] ifNone: [nil]. answer ifNotNil: [notify resume: answer second] ifNil: [ | outerAnswer | outerAnswer _ ProvideAnswerNotification signal: notify messageText. outerAnswer ifNil: [notify resume] ifNotNil: [notify resume: outerAnswer]]]! ! !BlockContext methodsFor: 'evaluating' stamp: 'jrp 10/10/2004 22:28'! valueSuppressingAllMessages ^ self valueSuppressingMessages: #('*')! ! !BlockContext methodsFor: 'evaluating' stamp: 'jrp 10/4/2004 18:59'! valueSuppressingMessages: aListOfStrings ^ self valueSuppressingMessages: aListOfStrings supplyingAnswers: #()! ! !BlockContext methodsFor: 'evaluating' stamp: 'jrp 10/4/2004 18:58'! valueSuppressingMessages: aListOfStrings supplyingAnswers: aListOfPairs ^ self valueSupplyingAnswers: aListOfPairs, (aListOfStrings collect: [:each | {each. true}])! ! !BlockContext methodsFor: 'evaluating' stamp: 'md 7/30/2005 21:22'! 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." anArray isArray ifFalse: [^self error: 'valueWithArguments: expects an array']. self numArgs = anArray size ifTrue: [self error: 'Attempt to evaluate a block that is already being evaluated.'] ifFalse: [self error: 'This block accepts ' ,self numArgs printString, ' argument', (self numArgs = 1 ifTrue:[''] ifFalse:['s']) , ', but was called with ', anArray size printString, '.'] ! ! !BlockContext methodsFor: 'evaluating' stamp: 'md 10/7/2004 15:24'! valueWithPossibleArgs: anArray "Evaluate the block represented by the receiver. If the block requires arguments, take them from anArray. If anArray is too large, the rest is ignored, if it is too small, use nil for the other arguments" 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) ! ! !BlockContext methodsFor: 'evaluating' stamp: 'md 10/7/2004 15:26'! valueWithPossibleArgument: anArg "Evaluate the block represented by the receiver. If the block requires one argument, use anArg, if it requires more than one, fill up the rest with nils." self numArgs = 0 ifTrue: [^self value]. self numArgs = 1 ifTrue: [^self value: anArg]. self numArgs > 1 ifTrue: [^self valueWithArguments: {anArg}, (Array new: self numArgs - 1)]! ! !BlockContext methodsFor: 'evaluating' stamp: 'ar 2/23/2005 11:48'! valueWithin: aDuration onTimeout: timeoutBlock "Evaluate the receiver. If the evaluation does not complete in less than aDuration evaluate the timeoutBlock instead" | theProcess delay watchdog done result | aDuration <= Duration zero ifTrue: [^ timeoutBlock value ]. "the block will be executed in the current process" theProcess := Processor activeProcess. delay := aDuration asDelay. "make a watchdog process" watchdog := [ delay wait. "wait for timeout or completion" done ifFalse: [ theProcess signalException: TimedOut ] ] newProcess. "watchdog needs to run at high priority to do its job" watchdog priority: Processor timingPriority. "catch the timeout signal" ^ [ done := false. watchdog resume. "start up the watchdog" result := self value. "evaluate the receiver" done := true. "it has completed, so ..." delay delaySemaphore signal. "arrange for the watchdog to exit" result ] on: TimedOut do: [ :e | timeoutBlock value ]. ! ! !BlockContext methodsFor: 'exceptions' stamp: 'sma 5/11/2000 19:38'! assert self assert: self! ! !BlockContext methodsFor: 'exceptions' stamp: 'ajh 3/4/2004 22:36'! ensure: aBlock "Evaluate a termination block after evaluating the receiver, regardless of whether the receiver's evaluation completes." | returnValue b | returnValue := self value. "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: [ "nil out aBlock temp before evaluating aBlock so it is not executed again if aBlock remote returns" b _ aBlock. thisContext tempAt: 1 put: nil. "aBlock _ nil" b value. ]. ^ returnValue! ! !BlockContext methodsFor: 'exceptions' stamp: 'ajh 1/24/2003 21:43'! ifCurtailed: aBlock "Evaluate the receiver with an abnormal termination action." ^ self value! ! !BlockContext methodsFor: 'exceptions' stamp: 'ar 3/6/2001 14:25'! on: exception do: handlerAction "Evaluate the receiver in the scope of an exception handler." | handlerActive | handlerActive _ true. ^self value! ! !BlockContext methodsFor: 'exceptions' stamp: 'ajh 10/9/2001 16:51'! onDNU: selector do: handleBlock "Catch MessageNotUnderstood exceptions but only those of the given selector (DNU stands for doesNotUnderstand:)" ^ self on: MessageNotUnderstood do: [:exception | exception message selector = selector ifTrue: [handleBlock valueWithPossibleArgs: {exception}] ifFalse: [exception pass] ]! ! !BlockContext methodsFor: 'exceptions' stamp: 'ajh 1/24/2003 21:53'! valueUninterruptably "Temporarily make my home Context unable to return control to its sender, to guard against circumlocution of the ensured behavior." ^ self ifCurtailed: [^ self]! ! !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: 'initialize-release' stamp: 'ajh 7/18/2003 21:49'! privRefresh "Reinitialize the receiver so that it is in the state it was at its creation." pc _ startpc. self stackp: 0. nargs timesRepeat: [ "skip arg popping" self nextInstruction selector = #popIntoTemporaryVariable: ifFalse: [self halt: 'unexpected bytecode instruction'] ]. ! ! !BlockContext methodsFor: 'instruction decoding' stamp: 'ajh 1/24/2003 16:35'! 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 from: self. home _ save. sender _ nil. ^ dest! ! !BlockContext methodsFor: 'printing' stamp: 'md 2/22/2006 15:53'! decompile ^ home method decompilerClass new decompileBlock: self! ! !BlockContext methodsFor: 'printing' stamp: 'md 2/20/2006 13:46'! decompileString ^self decompile decompileString.! ! !BlockContext methodsFor: 'printing' stamp: 'LC 1/6/2002 13:07'! fullPrintOn: aStream aStream print: self; cr. (self decompile ifNil: ['--source missing--']) fullPrintOn: aStream ! ! !BlockContext methodsFor: 'printing' stamp: 'md 2/27/2006 12:36'! printOn: aStream | blockString truncatedBlockString code | home == nil ifTrue: [^aStream nextPutAll: 'a BlockContext with home=nil']. aStream nextPutAll: '[] in '. super printOn: aStream. aStream nextPutAll: ' '. code := self decompile. blockString := code ifNil: [''] ifNotNil: [(code printString replaceAll: Character cr with: Character space) replaceAll: Character tab with: Character space]. truncatedBlockString _ blockString truncateWithElipsisTo: 80. truncatedBlockString size < blockString size ifTrue: [truncatedBlockString _ truncatedBlockString, ']}']. aStream nextPutAll: truncatedBlockString. ! ! !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: 'scheduling' stamp: 'ajh 2/10/2003 14:23'! asContext ^ self! ! !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: 'ajh 10/16/2002 11:14'! forkAndWait "Suspend current process and execute self in new process, when it completes resume current process" | semaphore | semaphore _ Semaphore new. [self ensure: [semaphore signal]] fork. semaphore wait. ! ! !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: 'svp 6/23/2003 10:59'! forkAt: priority named: name "Create and schedule a Process running the code in the receiver at the given priority and having the given name. Answer the newly created process." | forkedProcess | forkedProcess := self newProcess. forkedProcess priority: priority. forkedProcess name: name. ^ forkedProcess resume! ! !BlockContext methodsFor: 'scheduling' stamp: 'svp 6/23/2003 10:59'! forkNamed: aString "Create and schedule a Process running the code in the receiver and having the given name." ^ self newProcess name: aString; resume! ! !BlockContext methodsFor: 'scheduling' stamp: 'ajh 2/10/2003 14:25'! newProcess "Answer a Process running the code in the receiver. The process is not scheduled." "Simulation guard" ^Process forContext: [self value. Processor terminateActive] asContext priority: Processor activePriority! ! !BlockContext methodsFor: 'scheduling' stamp: 'ajh 2/10/2003 14:25'! 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] asContext priority: Processor activePriority! ! !BlockContext methodsFor: 'scheduling' stamp: 'sr 6/14/2004 15:19'! valueAt: blockPriority "Evaluate the receiver (block), with another priority as the actual one and restore it afterwards. The caller should be careful with using higher priorities." | activeProcess result outsidePriority | activeProcess := Processor activeProcess. outsidePriority := activeProcess priority. activeProcess priority: blockPriority. result := self ensure: [activeProcess priority: outsidePriority]. "Yield after restoring lower priority to give the preempted processes a chance to run." blockPriority > outsidePriority ifTrue: [Processor yield]. ^ result! ! !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: 'system simulation' stamp: 'hmm 7/30/2001 18:03'! stepToSendOrReturn pc = startpc ifTrue: [ "pop args first" self numArgs timesRepeat: [self step]]. ^super stepToSendOrReturn! ! !BlockContext methodsFor: '*services-base' stamp: 'rr 3/21/2006 11:53'! valueWithRequestor: aRequestor "To do later: make the fillInTheBlank display more informative captions. Include the description of the service, and maybe record steps" ^ self numArgs isZero ifTrue: [self value] ifFalse: [self value: aRequestor]! ! !BlockContext methodsFor: 'private' stamp: 'ajh 1/24/2003 20:36'! aboutToReturn: result through: firstUnwindContext "Called from VM when an unwindBlock is found between self and its home. Return to home's sender, executing unwind blocks on the way." self home return: result! ! !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: 'ajh 1/27/2003 21:18'! copyTo: aContext blocks: dict "Copy self and my sender chain down to, but not including, aContext. End of copied chain will have nil sender. BlockContexts whose home is also copied will point to the copy. However, blockContexts that are not on the stack but may be later will not have their home pointing in the new copied thread. So an error will be raised if one of these tries to return directly to its home." | copy | self == aContext ifTrue: [^ nil]. copy _ self copy. (dict at: self home ifAbsentPut: [OrderedCollection new]) add: copy. self sender ifNotNil: [ copy privSender: (self sender copyTo: aContext blocks: dict)]. ^ copy! ! !BlockContext methodsFor: 'private' stamp: 'md 4/27/2006 15:14'! endPC "Determine end of block from long jump preceding it" ^(self method at: startpc - 2) \\ 16 - 4 * 256 + (self method at: startpc - 1) + startpc - 1.! ! !BlockContext methodsFor: 'private' stamp: 'tfei 3/20/2000 00:24'! hideFromDebugger ^home ~~ nil and: [home hideFromDebugger]! ! !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' stamp: 'ajh 7/7/2004 13:43'! myEnv "polymorphic with MethodContext" ^ nil! ! !BlockContext methodsFor: 'private' stamp: 'ajh 1/27/2003 21:08'! privHome: context home _ context! ! !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: 'private' stamp: 'ar 3/2/2001 01:16'! valueUnpreemptively "Evaluate the receiver (block), without the possibility of preemption by higher priority processes. Use this facility VERY sparingly!!" "Think about using Block>>valueUninterruptably first, and think about using Semaphore>>critical: before that, and think about redesigning your application even before that!! After you've done all that thinking, go right ahead and use it..." | activeProcess oldPriority result | activeProcess _ Processor activeProcess. oldPriority _ activeProcess priority. activeProcess priority: Processor highestPriority. result _ self ensure: [activeProcess priority: oldPriority]. "Yield after restoring priority to give the preempted processes a chance to run" Processor yield. ^result! ! !BlockContext methodsFor: 'private-exceptions' stamp: 'ar 3/9/2001 01:18'! ifProperUnwindSupportedElseSignalAboutToReturn "A really ugly hack to simulate the necessary unwind behavior for VMs not having proper unwind support" "The above indicates new EH primitives supported. In this case is identical to #value. Sender is expected to use [nil] ifProperUnwindSupportedElseSignalAboutToReturn." ^ExceptionAboutToReturn signal.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BlockContext class instanceVariableNames: ''! TestCase subclass: #BlockContextTest instanceVariableNames: 'aBlockContext contextOfaBlockContext' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'! !BlockContextTest commentStamp: 'jrp 10/17/2004 12:22' prior: 0! I am an SUnit Test of BlockContext and its supertype ContextPart. See also MethodContextTest. My fixtures are: aBlockContext - just some trivial block, i.e., [100@100 corner: 200@200]. NOTES ABOUT AUTOMATING USER INPUTS When executing non-interactive programs you will inevitably run into programs (like SqueakMap or Monticello installation packages -- and other programs, to be fair) that require user input during their execution and these sort of problems shoot the whole non-interactiveness of your enclosing program. BlockContext helper methods have been made available and tests of these helpers are provided in this class to demonstrate that it can intercept PopUpMenu and FillInTheBlankMorph requests for user interaction. Of course, PopUpMenu and FillInTheBlankMorph were modified to first signal a ProvideAnswerNotification and if someone handles that (e.g. the enclosing block) then the user interaction will be circumvented and the provided answer of the enclosing block will be used. The basic syntax looks like: [self confirm: 'Install spyware?'] valueSupplyingAnswer: #('Install spyware?' false) There a few variants on this theme making it easy to provide a literal list of answers for the block so that you can handle a bunch of questions in a block with appropriate answers. Additionally, it is possible to suppress Object>>inform: modal dialog boxes as these get in the way of automating anything. After applying this changeset you should be able to tryout the following code snippets to see the variants on this theme that are available. Examples: So you don't need any introduction here -- this one works like usual. [self inform: 'hello'. #done] value. Now let's suppress all inform: messages. [self inform: 'hello'; inform: 'there'. #done] valueSuppressingAllMessages. Here we can just suppress a single inform: message. [self inform: 'hi'; inform: 'there'. #done] valueSuppressingMessages: #('there') Here you see how you can suppress a list of messages. [self inform: 'hi'; inform: 'there'; inform: 'bill'. #done] valueSuppressingMessages: #('hi' 'there') Enough about inform:, let's look at confirm:. As you see this one works as expected. [self confirm: 'You like Squeak?'] value Let's supply answers to one of the questions -- check out the return value. [{self confirm: 'You like Smalltalk?'. self confirm: 'You like Squeak?'}] valueSupplyingAnswer: #('You like Smalltalk?' true) Here we supply answers using only substrings of the questions (for simplicity). [{self confirm: 'You like Squeak?'. self confirm: 'You like MVC?'}] valueSupplyingAnswers: #( ('Squeak' true) ('MVC' false) ) This time let's answer all questions exactly the same way. [{self confirm: 'You like Squeak?'. self confirm: 'You like Morphic?'}] valueSupplyingAnswer: true And, of course, we can answer FillInTheBlank questions in the same manner. [FillInTheBlank request: 'What day is it?'] valueSupplyingAnswer: 'the first day of the rest of your life' We can also return whatever the initialAnswer of the FillInTheBlank was by using the #default answer. [FillInTheBlank request: 'What day is it?' initialAnswer: DateAndTime now dayOfWeekName] valueSupplyingAnswer: #default Finally, you can also do regex matches on any of the question text (or inform text) (should you have VB-Regex enhancements in your image). [FillInTheBlank request: 'What day is it?'] valueSupplyingAnswers: { {'What day.*\?'. DateAndTime now dayOfWeekName} }! !BlockContextTest methodsFor: 'setup' stamp: 'md 9/6/2005 19:56'! setUp super setUp. aBlockContext _ [100@100 corner: 200@200]. contextOfaBlockContext _ thisContext.! ! !BlockContextTest methodsFor: 'testing' stamp: 'rbb 3/1/2005 10:23'! testSupplyAnswerOfFillInTheBlank self should: ['blue' = ([UIManager default request: 'Your favorite color?'] valueSupplyingAnswer: #('Your favorite color?' 'blue'))]! ! !BlockContextTest methodsFor: 'testing' stamp: 'rbb 3/1/2005 10:24'! testSupplyAnswerOfFillInTheBlankUsingDefaultAnswer self should: ['red' = ([UIManager default request: 'Your favorite color?' initialAnswer: 'red'] valueSupplyingAnswer: #('Your favorite color?' #default))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 14:00'! testBlockIsBottomContext self should: [aBlockContext client ] raise: Error. "block's sender is nil, a block has no client" self assert: aBlockContext bottomContext = aBlockContext. self assert: aBlockContext secondFromBottom isNil.! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 13:49'! testCopyStack self assert: aBlockContext copyStack printString = aBlockContext printString.! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 13:55'! testFindContextSuchThat self assert: (aBlockContext findContextSuchThat: [:each| true]) printString = aBlockContext printString. self assert: (aBlockContext hasContext: aBlockContext). ! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 13:13'! testNew self should: [ContextPart new: 5] raise: Error. [ContextPart new: 5] ifError: [:error :receiver | error = 'Error: Contexts must only be created with newForMethod:']. [ContextPart new] ifError: [:error :receiver | error = 'Error: Contexts must only be created with newForMethod:']. [ContextPart basicNew] ifError: [:error :receiver | error = 'Error: Contexts must only be created with newForMethod:']. ! ! !BlockContextTest methodsFor: 'tests' stamp: 'mjr 8/24/2003 18:27'! testNoArguments [10 timesRepeat: [:arg | 1 + 2]] ifError: [:err :rcvr | self deny: err = 'This block requires 1 arguments.']. [10 timesRepeat: [:arg1 :arg2 | 1 + 2]] ifError: [:err :rcvr | self deny: err = 'This block requires 2 arguments.'] ! ! !BlockContextTest methodsFor: 'tests' stamp: 'mjr 8/24/2003 18:25'! testOneArgument | c | c _ OrderedCollection new. c add: 'hello'. [c do: [1 + 2]] ifError: [:err :rcvr | self deny: err = 'This block requires 0 arguments.']. [c do: [:arg1 :arg2 | 1 + 2]] ifError: [:err :rcvr | self deny: err = 'This block requires 2 arguments.'] ! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 12:50'! testRunSimulated self assert: (ContextPart runSimulated: aBlockContext) class = Rectangle.! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 13:59'! testSetUp "Note: In addition to verifying that the setUp worked the way it was expected to, testSetUp is used to illustrate the meaning of the simple access methods, methods that are not normally otherwise 'tested'" self deny: aBlockContext isBlockClosure. self deny: aBlockContext isMethodContext. self deny: aBlockContext isPseudoContext. self deny: aBlockContext isDead. self assert: aBlockContext home = contextOfaBlockContext. self assert: aBlockContext blockHome = contextOfaBlockContext. self assert: aBlockContext receiver = self. self assert: (aBlockContext method isKindOf: CompiledMethod). self assert: aBlockContext methodNode selector = 'setUp'. self assert: (aBlockContext methodNodeFormattedAndDecorated: true) selector = 'setUp'.! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/10/2004 22:19'! testSupplyAnswerThroughNestedBlocks self should: [true = ([[self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: #('Blub' false)] valueSupplyingAnswer: #('Smalltalk' true))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/4/2004 19:27'! testSupplyAnswerUsingOnlySubstringOfQuestion self should: [false = ([self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: #('like' false))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/10/2004 22:31'! testSupplyAnswerUsingRegexMatchOfQuestion (String includesSelector: #matchesRegex:) ifFalse: [^ self]. self should: [true = ([self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: #('.*Smalltalk\?' true))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/10/2004 22:17'! testSupplyAnswerUsingTraditionalMatchOfQuestion self should: [true = ([self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: #('*Smalltalk#' true))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/4/2004 19:25'! testSupplySameAnswerToAllQuestions self should: [true = ([self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: true)]. self should: [#(true true) = ([{self confirm: 'One'. self confirm: 'Two'}] valueSupplyingAnswer: true)].! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/4/2004 19:39'! testSupplySeveralAnswersToSeveralQuestions self should: [#(false true) = ([{self confirm: 'One'. self confirm: 'Two'}] valueSupplyingAnswers: #( ('One' false) ('Two' true) ))]. self should: [#(true false) = ([{self confirm: 'One'. self confirm: 'Two'}] valueSupplyingAnswers: #( ('One' true) ('Two' false) ))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/4/2004 19:26'! testSupplySpecificAnswerToQuestion self should: [false = ([self confirm: 'You like Smalltalk?'] valueSupplyingAnswer: #('You like Smalltalk?' false))]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/4/2004 19:35'! testSuppressInform self should: [[nil inform: 'Should not see this message or this test failed!!'] valueSuppressingAllMessages isNil]! ! !BlockContextTest methodsFor: 'tests' stamp: 'jrp 10/10/2004 22:29'! testSuppressInformUsingStringMatchOptions self should: [([nil inform: 'Should not see this message or this test failed!!'] valueSuppressingMessages: #('Should not see this message or this test failed!!')) isNil]. self should: [([nil inform: 'Should not see this message or this test failed!!'] valueSuppressingMessages: #('not see this message')) isNil]. self should: [([nil inform: 'Should not see this message or this test failed!!'] valueSuppressingMessages: #('*message*failed#')) isNil]. ! ! !BlockContextTest methodsFor: 'tests' stamp: 'md 9/6/2005 19:58'! testTallyInstructions self assert: (ContextPart tallyInstructions: aBlockContext) size = 15.! ! !BlockContextTest methodsFor: 'tests' stamp: 'md 9/6/2005 19:58'! testTallyMethods self assert: (ContextPart tallyMethods: aBlockContext) size = 3.! ! !BlockContextTest methodsFor: 'tests' stamp: 'tlk 5/31/2004 12:48'! testTrace self assert: (ContextPart trace: aBlockContext) class = Rectangle.! ! !BlockContextTest methodsFor: 'tests - evaluating' stamp: 'tlk 5/31/2004 17:14'! testValueWithArguments self should: [aBlockContext valueWithArguments: #(1 )] raise: Error. self shouldnt: [aBlockContext valueWithArguments: #()] raise: Error. [aBlockContext valueWithArguments: #(1 )] ifError: [:err :rcvr | self assert: err = 'Error: This block accepts 0 arguments, but was called with 1.']. [[:i | 3 + 4] valueWithArguments: #(1 2)] ifError: [:err :rcvr | self assert: err = 'Error: This block accepts 1 argument, but was called with 2.']! ! !BlockContextTest methodsFor: 'tests - evaluating' stamp: 'md 3/23/2006 13:52'! testValueWithExitBreak | val | [ :break | 1 to: 10 do: [ :i | val := i. i = 4 ifTrue: [break value]. ] ] valueWithExit. self assert: val = 4.! ! !BlockContextTest methodsFor: 'tests - evaluating' stamp: 'md 3/23/2006 13:52'! testValueWithExitContinue | val last | val := 0. 1 to: 10 do: [ :i | [ :continue | i = 4 ifTrue: [continue value]. val := val + 1. last := i ] valueWithExit. ]. self assert: val = 9. self assert: last = 10. ! ! !BlockContextTest methodsFor: 'tests - evaluating' stamp: 'md 10/7/2004 13:52'! testValueWithPossibleArgs | block blockWithArg blockWith2Arg | block := [1]. blockWithArg := [:arg | arg]. blockWith2Arg := [:arg1 :arg2 | {arg1. arg2}]. self assert: (block valueWithPossibleArgs: #()) = 1. self assert: (block valueWithPossibleArgs: #(1)) = 1. self assert: (blockWithArg valueWithPossibleArgs: #()) = nil. self assert: (blockWithArg valueWithPossibleArgs: #(1)) = 1. self assert: (blockWithArg valueWithPossibleArgs: #(1 2)) = 1. self assert: (blockWith2Arg valueWithPossibleArgs: #()) = {nil .nil}. self assert: (blockWith2Arg valueWithPossibleArgs: #(1)) = {1 . nil}. self assert: (blockWith2Arg valueWithPossibleArgs: #(1 2)) = #(1 2). self assert: (blockWith2Arg valueWithPossibleArgs: #(1 2 3)) = #(1 2). ! ! !BlockContextTest methodsFor: 'tests - evaluating' stamp: 'md 10/7/2004 13:59'! testValueWithPossibleArgument | block blockWithArg blockWith2Arg | block := [1]. blockWithArg := [:arg | arg]. blockWith2Arg := [:arg1 :arg2 | {arg1. arg2}]. self assert: (block valueWithPossibleArgument: 1) = 1. self assert: (blockWithArg valueWithPossibleArgument: 1) = 1. self assert: (blockWith2Arg valueWithPossibleArgument: 1) = {1 . nil}. ! ! !BlockContextTest methodsFor: 'tests - printing' stamp: 'md 2/22/2006 15:39'! testDecompile self assert: ([3 + 4] decompile printString = '{[3 + 4]}').! ! ParseNode subclass: #BlockNode instanceVariableNames: 'arguments statements returns nArgsNode size remoteCopyNode temporaries' classVariableNames: '' poolDictionaries: '' category: 'Compiler-ParseNodes'! !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: '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: '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' stamp: 'hmm 7/17/2001 21:02'! 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. pc _ aStream position. ]. 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: 'equation translation'! statements ^statements! ! !BlockNode methodsFor: 'equation translation'! statements: val statements _ val! ! !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: 'hmm 7/15/2001 22:23'! arguments: argNodes statements: statementsCollection returns: returnBool from: encoder sourceRange: range "Compile." encoder noteSourceRange: range forNode: self. ^self arguments: argNodes statements: statementsCollection returns: returnBool from: encoder! ! !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: 'printing' stamp: 'md 2/22/2006 16:37'! decompileString "Answer a string description of the parse tree whose root is the receiver." ^ self decompileText asString ! ! !BlockNode methodsFor: 'printing' stamp: 'md 2/22/2006 16:37'! decompileText "Answer a text description of the parse tree whose root is the receiver." ^ ColoredCodeStream contents: [:strm | self printOn: strm indent: 0] ! ! !BlockNode methodsFor: 'printing' stamp: 'md 8/14/2005 17:32'! printArgumentsOn: aStream indent: level arguments size = 0 ifTrue: [^ self]. 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: 'md 2/20/2006 13:37'! printOn: aStream indent: 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: '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: '*eToys-tiles' stamp: 'RAA 2/27/2001 09:48'! asMorphicCollectSyntaxIn: parent ^parent blockNodeCollect: self arguments: arguments statements: statements! ! !BlockNode methodsFor: '*eToys-tiles' stamp: 'RAA 2/16/2001 09:08'! asMorphicSyntaxIn: parent ^parent blockNode: self arguments: arguments statements: statements! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'yo 5/17/2004 23:03'! withJust: aNode ^ self statements: (OrderedCollection with: aNode) returns: false! ! SmallLandColorTheme subclass: #BlueSmallLandColorTheme instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !BlueSmallLandColorTheme methodsFor: 'initialization' stamp: 'dgd 3/12/2006 13:45'! baseColors " BlueSmallLandColorTheme apply. " ^ Array with: (Color fromArray: #(0.2 0.3 0.9 )) with: (Color fromArray: #(0.6 0.7 1.0 )) with: (Color fromArray: #(0.85 0.9 1.0 ))! ! BooklikeMorph subclass: #BookMorph instanceVariableNames: 'pages currentPage' classVariableNames: 'MethodHolders VersionNames VersionTimes' poolDictionaries: '' category: 'MorphicExtras-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: 'accessing' stamp: 'tk 12/12/2001 15:36'! cardsOrPages "The turnable and printable entities" ^ pages! ! !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: 'caching' stamp: 'tk 3/11/2002 12:05'! releaseCachedState "Release the cached state of all my pages." super releaseCachedState. self removeProperty: #allText. "the cache for text search" pages do: [:page | page == currentPage ifFalse: [page fullReleaseCachedState]]. ! ! !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: '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: 'halos and balloon help' 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: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color white! ! !BookMorph methodsFor: 'initialization' stamp: 'dgd 2/21/2003 23:10'! 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 second) 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 second. dict := remote first. 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: 'ar 4/10/2005 18:42'! 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 isString ifTrue: [self inform: 'Sorry, ',strm. ^ nil]. self setProperty: #url toValue: url. self fromRemoteStream: strm. ^ self! ! !BookMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 21:09'! initialize "initialize the state of the receiver" 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: 'dgd 2/14/2003 22:07'! setInitialState self listDirection: #topToBottom; wrapCentering: #topLeft; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 5. pageSize _ 160 @ 300. self enableDragNDrop! ! !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: 'dgd 9/21/2003 17:45'! deletePage | message | message _ 'Are you certain that you want to delete this page and everything that is on it? ' translated. (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: 'dgd 2/21/2003 23:10'! insertPage: aPage pageSize: aPageSize atIndex: anIndex | sz predecessor | sz := aPageSize ifNil: [currentPage isNil ifTrue: [pageSize] ifFalse: [currentPage extent]] ifNotNil: [aPageSize]. aPage extent: sz. (pages isEmpty | anIndex isNil or: [anIndex > pages size]) ifTrue: [pages add: aPage] ifFalse: [anIndex <= 1 ifTrue: [pages addFirst: aPage] ifFalse: [predecessor := anIndex isNil ifTrue: [currentPage] ifFalse: [pages at: anIndex]. self pages add: aPage after: predecessor]]. self goToPageMorph: aPage! ! !BookMorph methodsFor: 'insert and delete' stamp: 'dgd 2/21/2003 23:11'! insertPageColored: aColor "Insert a new page for the receiver, using the given color as its background color" | sz newPage bw bc | bc := currentPage isNil ifTrue: [sz := pageSize. bw := 0. Color blue muchLighter] ifFalse: [sz := currentPage extent. bw := currentPage borderWidth. 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 vResizeToFit: 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: 'dgd 2/21/2003 23:11'! insertPageSilentlyAtEnd "Create a new page at the end of the book. Do not turn to it." | sz newPage bw bc cc | cc := currentPage isNil ifTrue: [sz := pageSize. bw := 0. bc := Color blue muchLighter. color] ifFalse: [sz := currentPage extent. bw := currentPage borderWidth. bc := currentPage borderColor. 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 vResizeToFit: false. pages isEmpty ifTrue: [pages add: (currentPage := newPage) "had been none"] ifFalse: [pages add: newPage after: pages last]. ^newPage! ! !BookMorph methodsFor: 'layout' 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: 'menu' stamp: 'dgd 8/30/2003 21:13'! addBookMenuItemsTo: aMenu hand: aHandMorph | controlsShowing subMenu | subMenu _ MenuMorph new defaultTarget: self. subMenu add: 'previous page' translated action: #previousPage. subMenu add: 'next page' translated action: #nextPage. subMenu add: 'goto page' translated action: #goToPage. subMenu add: 'insert a page' translated action: #insertPage. subMenu add: 'delete this page' translated action: #deletePage. controlsShowing _ self hasSubmorphWithProperty: #pageControl. controlsShowing ifTrue: [subMenu add: 'hide page controls' translated action: #hidePageControls. subMenu add: 'fewer page controls' translated action: #fewerPageControls] ifFalse: [subMenu add: 'show page controls' translated action: #showPageControls]. self isInFullScreenMode ifTrue: [ subMenu add: 'exit full screen' translated action: #exitFullScreen. ] ifFalse: [ subMenu add: 'show full screen' translated action: #goFullScreen. ]. subMenu addLine. subMenu add: 'sound effect for all pages' translated action: #menuPageSoundForAll:. subMenu add: 'sound effect this page only' translated action: #menuPageSoundForThisPage:. subMenu add: 'visual effect for all pages' translated action: #menuPageVisualForAll:. subMenu add: 'visual effect this page only' translated action: #menuPageVisualForThisPage:. subMenu addLine. subMenu add: 'sort pages' translated action: #sortPages:. subMenu add: 'uncache page sorter' translated action: #uncachePageSorter. (self hasProperty: #dontWrapAtEnd) ifTrue: [subMenu add: 'wrap after last page' translated selector: #setWrapPages: argument: true] ifFalse: [subMenu add: 'stop at last page' translated selector: #setWrapPages: argument: false]. subMenu addLine. subMenu add: 'search for text' translated action: #textSearch. (aHandMorph pasteBuffer class isKindOf: PasteUpMorph class) ifTrue: [subMenu add: 'paste book page' translated action: #pasteBookPage]. subMenu add: 'send all pages to server' translated action: #savePagesOnURL. subMenu add: 'send this page to server' translated action: #saveOneOnURL. subMenu add: 'reload all from server' translated action: #reload. subMenu add: 'copy page url to clipboard' translated action: #copyUrl. subMenu add: 'keep in one file' translated action: #keepTogether. subMenu add: 'save as new-page prototype' translated action: #setNewPagePrototype. newPagePrototype ifNotNil: [subMenu add: 'clear new-page prototype' translated action: #clearNewPagePrototype]. aMenu add: 'book...' translated subMenu: subMenu ! ! !BookMorph methodsFor: 'menu' stamp: 'dgd 10/26/2003 13:03'! 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' translated. 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' translated. 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: 'dgd 10/26/2003 13:04'! 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.' translated initialAnswer: ( self valueOfProperty: #nameOfThreadOfProjects ifAbsent: ['Projects on Parade' translated] ). threadName isEmptyOrNil ifTrue: [^self]. InternalThreadNavigationMorph know: projectNames as: threadName; openThreadNamed: threadName atIndex: nil. ! ! !BookMorph methodsFor: 'menu' stamp: 'dgd 10/26/2003 13:05'! 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.' translated]. Clipboard clipboardText: str asText. ! ! !BookMorph methodsFor: 'menu' stamp: 'md 9/27/2005 22:57'! 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" allText ifNotEmpty: [ 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: 'gm 2/22/2003 13:17'! 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 old | good := true. start := startIndex. strings := oldContainer ifNil: ["normal case" rawStrings] 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 first) 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) startAt: startIndex container: oldContainer pageNum: pageNum "recompute"]]. (old := self valueOfProperty: #searchContainer) ifNotNil: [(old respondsTo: #editor) ifTrue: [old editor selectFrom: 1 to: 0. "trying to remove the previous selection!!" old changed]]. good ifTrue: ["have the exact string object" (container := oldContainer) ifNil: [container := self highlightText: keys first at: place in: insideOf] ifNotNil: [container userString == insideOf ifFalse: [container := self highlightText: keys first at: place in: insideOf] ifTrue: [(container isTextMorph) ifTrue: [container editor selectFrom: place to: keys first size - 1 + place. container changed]]]. self setProperty: #searchContainer toValue: container. self setProperty: #searchOffset toValue: place. self setProperty: #searchKey toValue: keys. "override later" ActiveHand newKeyboardFocus: container. ^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: 'dgd 10/26/2003 13:06'! 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://' translated initialAnswer: initial]. ^ SqueakPage stemUrl: url! ! !BookMorph methodsFor: 'menu' stamp: 'dgd 10/26/2003 12:59'! goToPage | pageNum | pageNum _ FillInTheBlank request: 'Page?' translated initialAnswer: '0'. pageNum isEmptyOrNil ifTrue: [^true]. self goToPage: pageNum asNumber. ! ! !BookMorph methodsFor: 'menu' stamp: 'gm 2/22/2003 13:17'! 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 isTextMorph) ifTrue: [container editor selectFrom: index to: stringToHilite size - 1 + index]. container changed. ^container! ! !BookMorph methodsFor: 'menu' stamp: 'dgd 4/3/2006 13:08'! invokeBookMenu "Invoke the book's control panel menu." | aMenu | aMenu _ MenuMorph new defaultTarget: self. aMenu addTitle: 'Book' translated. Preferences noviceMode ifFalse:[aMenu addStayUpItem]. aMenu add: 'find...' translated action: #textSearch. aMenu add: 'go to page...' translated action: #goToPage. aMenu addLine. aMenu addList: { {'sort pages' translated. #sortPages}. {'uncache page sorter' translated. #uncachePageSorter}}. (self hasProperty: #dontWrapAtEnd) ifTrue: [aMenu add: 'wrap after last page' translated selector: #setWrapPages: argument: true] ifFalse: [aMenu add: 'stop at last page' translated selector: #setWrapPages: argument: false]. aMenu addList: { {'make bookmark' translated. #bookmarkForThisPage}. {'make thumbnail' translated. #thumbnailForThisPage}}. aMenu addUpdating: #showingPageControlsString action: #toggleShowingOfPageControls. aMenu addUpdating: #showingFullScreenString action: #toggleFullScreen. aMenu addLine. aMenu add: 'sound effect for all pages' translated action: #menuPageSoundForAll:. aMenu add: 'sound effect this page only' translated action: #menuPageSoundForThisPage:. aMenu add: 'visual effect for all pages' translated action: #menuPageVisualForAll:. aMenu add: 'visual effect this page only' translated action: #menuPageVisualForThisPage:. aMenu addLine. (self primaryHand pasteBuffer class isKindOf: PasteUpMorph class) ifTrue: [aMenu add: 'paste book page' translated action: #pasteBookPage]. aMenu add: 'save as new-page prototype' translated action: #setNewPagePrototype. newPagePrototype ifNotNil: [ aMenu add: 'clear new-page prototype' translated action: #clearNewPagePrototype]. aMenu add: (self dragNDropEnabled ifTrue: ['close dragNdrop'] ifFalse: ['open dragNdrop']) translated action: #toggleDragNDrop. aMenu add: 'make all pages this size' translated action: #makeUniformPageSize. aMenu addUpdating: #keepingUniformPageSizeString target: self action: #toggleMaintainUniformPageSize. aMenu addLine. aMenu add: 'send all pages to server' translated action: #savePagesOnURL. aMenu add: 'send this page to server' translated action: #saveOneOnURL. aMenu add: 'reload all from server' translated action: #reload. aMenu add: 'copy page url to clipboard' translated action: #copyUrl. aMenu add: 'keep in one file' translated action: #keepTogether. aMenu addLine. aMenu add: 'load PPT images from slide #1' translated action: #loadImagesIntoBook. aMenu add: 'background color for all pages...' translated action: #setPageColor. aMenu add: 'make a thread of projects in this book' translated 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: 'nk 6/12/2004 09:23'! 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) isNil ifTrue: [self savePagesOnURL] ifFalse: [self saveAsNumberedURLs]]. form := Form fromFileNamed: (directory fullNameFor: 'Slide' , filenumber asString). newpage := PasteUpMorph new extent: form extent. newpage addMorph: (World drawingClass 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) isNil ifTrue: [self savePagesOnURL] ifFalse: [self saveAsNumberedURLs]! ! !BookMorph methodsFor: 'menu' stamp: 'nb 6/17/2003 12:25'! makeUniformPageSize "Make all pages be of the same size as the current page." currentPage ifNil: [^ Beeper beep]. self resizePagesTo: currentPage extent. newPagePrototype ifNotNil: [newPagePrototype extent: currentPage extent]! ! !BookMorph methodsFor: 'menu' stamp: 'em 3/30/2005 14:35'! menuPageSoundFor: target event: evt | tSpec menu | tSpec _ self transitionSpecFor: target. menu _ (MenuMorph entitled: 'Choose a sound (it is now ' translated, tSpec first translated, ')') defaultTarget: target. SoundService default sampledSoundChoices do: [:soundName | menu add: soundName translated 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: 'dgd 10/26/2003 13:39'! menuPageVisualFor: target event: evt | tSpec menu subMenu directionChoices | tSpec _ self transitionSpecFor: target. menu _ (MenuMorph entitled: ('Choose an effect (it is now {1})' translated format:{tSpec second asString translated})) defaultTarget: target. TransitionMorph allEffects do: [:effect | directionChoices _ TransitionMorph directionsForEffect: effect. directionChoices isEmpty ifTrue: [menu add: effect asString translated 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 asString translated target: target selector: #setProperty:toValue: argumentList: (Array with: #transitionSpec with: (Array with: tSpec first with: effect with: dir))]. menu add: effect asString translated 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: 'dgd 10/26/2003 13: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://' translated initialAnswer: (self getStemUrl, '.bo'). url notEmpty 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 notNil and: [sq contentsMorph isInMemory]) ifTrue: [((out sqkPage lastChangeTime > sq lastChangeTime) or: [sq contentsMorph isNil]) 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: 'dgd 2/21/2003 23:12'! 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) isEmpty 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: 'dgd 2/21/2003 23:12'! 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) isNil. stem := self getStemUrl. "user must approve" stem isEmpty 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: 'ar 4/10/2005 18: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 isString ifTrue: [^ self saveIndexOnURL]. strm exists ifFalse: [^ self saveIndexOnURL]. "write whole thing if missing" strm _ strm asStream. strm isString ifTrue: [^ self saveIndexOnURL]. remote _ strm fileInObjectAndCode. dict _ remote first. 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.' translated]. (pageURL _ aPage url) ifNil: [self error: 'just had one!!' translated]. 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: 'dgd 2/21/2003 23:12'! 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 isEmpty 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: 'dgd 10/26/2003 13:18'! 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.' translated. ^ self]. "Don't give the chance to put in a different place. Assume named by number" ((self valueOfProperty: #url) isNil and: [pages first url notNil]) ifTrue: [ response _ (PopUpMenu labels: 'Old book New book sharing old pages' translated) startUpWithCaption: 'Modify the old book, or make a new book sharing its pages?' translated. 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) isEmpty 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 isEmpty ifTrue: [^ self]. ind _ pages identityIndexOf: pageMorph ifAbsent: [self error: 'where is the page?' translated]. 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: 'dgd 10/26/2003 13:20'! 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.' translated. ^ 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' translated) 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?' translated. 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) isEmpty 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) isNil. 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: 'dgd 10/26/2003 12:58'! 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.' translated initialAnswer: str. wants _ wanted findTokens: Character separators. wants isEmpty ifTrue: [^ self]. self getAllText. "save in allText, allTextUrls" ^ self findText: wants "goes to the page and highlights the text"! ! !BookMorph methodsFor: 'menu' stamp: 'dgd 2/21/2003 23:14'! textSearch: stringWithKeys "search the text on all pages of this book" | wants | wants := stringWithKeys findTokens: Character separators. wants isEmpty 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: 'menu commands' stamp: 'di 1/4/1999 13:52'! sortPages currentPage ifNotNil: [currentPage updateCachedThumbnail]. ^ super sortPages! ! !BookMorph methodsFor: 'menus' stamp: 'yo 7/2/2004 13:05'! printPSToFile "Ask the user for a filename and print this morph as postscript." | fileName rotateFlag | fileName _ ('MyBook') translated asFileName. fileName _ FillInTheBlank request: 'File name? (".ps" will be added to end)' translated initialAnswer: fileName. fileName isEmpty ifTrue: [^ Beeper beep]. (fileName endsWith: '.ps') ifFalse: [fileName _ fileName,'.ps']. rotateFlag _ ((PopUpMenu labels: 'portrait (tall) landscape (wide)' translated) startUpWithCaption: 'Choose orientation...' translated) = 2. (FileStream newFileNamed: fileName asFileName) nextPutAll: (DSCPostscriptCanvas morphAsPostscript: self rotated: rotateFlag); close. ! ! !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: 'sw 11/8/2002 13:31'! goToPage: pageNumber transitionSpec: transitionSpec runTransitionScripts: aBoolean "Go the the given page number; use the transitionSpec supplied, and if the boolean parameter is true, run opening and closing scripts as appropriate" | pageMorph | pages isEmpty ifTrue: [^ self]. pageMorph _ (self hasProperty: #dontWrapAtEnd) ifTrue: [pages atPin: pageNumber] ifFalse: [pages atWrap: pageNumber]. ^ self goToPageMorph: pageMorph transitionSpec: transitionSpec runTransitionScripts: aBoolean! ! !BookMorph methodsFor: 'navigation' stamp: 'sw 11/8/2002 21:30'! goToPageMorph: aMorph "Set the given morph as the current page; run closing and opening scripts as appropriate" self goToPageMorph: aMorph runTransitionScripts: true! ! !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: 'sw 11/8/2002 13:34'! goToPageMorph: aMorph runTransitionScripts: aBoolean "Set the given morph as the current page. If the boolean parameter is true, then opening and closing scripts will be run" self goToPage: (pages identityIndexOf: aMorph ifAbsent: [^ self "abort"]) transitionSpec: nil runTransitionScripts: aBoolean ! ! !BookMorph methodsFor: 'navigation' stamp: 'dgd 2/22/2003 18:49'! 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 isNil or: [newPage == currentPage]) ifTrue: [nil] ifFalse: [oldPageIndex < pageIndex]. tSpec := transitionSpec ifNil: ["If transition not specified by requestor..." newPage valueOfProperty: #transitionSpec ifAbsent: [" ... then consult new page" 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 notNil 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: 'dgd 2/22/2003 18:49'! goToPageMorph: newPage transitionSpec: transitionSpec runTransitionScripts: aBoolean "Install the given page as the new current page; use the given transition spec, and if the boolean parameter is true, run closing and opening scripts on the outgoing and incoming players" | 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 isNil or: [newPage == currentPage]) ifTrue: [nil] ifFalse: [oldPageIndex < pageIndex]. tSpec := transitionSpec ifNil: ["If transition not specified by requestor..." newPage valueOfProperty: #transitionSpec ifAbsent: [" ... then consult new page" 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 notNil ifTrue: [(((pages at: pageIndex) owner isKindOf: TransitionMorph) and: [(pages at: pageIndex) isInWorld]) ifTrue: [^self "In the process of a prior pageTurn"]. aBoolean ifTrue: [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]. aBoolean ifTrue: [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: 'dgd 2/21/2003 23:10'! 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 first]]. 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: 'dgd 2/21/2003 23:11'! nextPage currentPage isNil 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: 'dgd 2/21/2003 23:11'! previousPage currentPage isNil 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: 'other' stamp: 'sw 6/6/2003 13:55'! adjustCurrentPageForFullScreen "Adjust current page to conform to whether or not I am in full-screen mode. Also, enforce uniform page size constraint if appropriate" 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]. self uniformPageSize ifNotNilDo: [:anExtent | currentPage extent: anExtent]]. (self valueOfProperty: #floatingPageControls) ifNotNilDo: [:pc | pc isInWorld ifFalse: [pc openInWorld]]! ! !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: 'tk 2/19/2001 18:35'! 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 label: ' < ' ; actionSelector: #previousPage). "copy 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. aButton _ SimpleButtonMorph new target: self; borderColor: Color black; color: aColor; borderWidth: 0. aRow addMorphBack: (but _ aButton 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: 'sw 10/1/1998 13:40'! resizePagesTo: anExtent pages do: [:aPage | aPage extent: anExtent]! ! !BookMorph methodsFor: 'other' stamp: 'sw 6/6/2003 17:21'! setExtentFromHalo: anExtent "The user has dragged the grow box such that the receiver's extent would be anExtent. Do what's needed. For a BookMorph, we assume any resizing attempt is a request that the book-page currently being viewed be resized accoringly; this will typically not affect unseen book pages, though there is a command that can be issued to harmonize all book-page sizes, and also an option to set that will maintain all pages at the same size no matter what." currentPage isInWorld ifFalse: "doubtful case mostly" [super setExtentFromHalo: anExtent] ifTrue: [currentPage width: anExtent x. currentPage height: (anExtent y - (self innerBounds height - currentPage height)). self maintainsUniformPageSize ifTrue: [self setProperty: #uniformPageSize toValue: currentPage extent]]! ! !BookMorph methodsFor: 'Postscript Canvases'! asPostscript ^self asPostscriptPrintJob. ! ! !BookMorph methodsFor: 'Postscript Canvases' stamp: 'mpw 9/13/1999 20:22'! fullDrawPostscriptOn:aCanvas ^aCanvas fullDrawBookMorph:self. ! ! !BookMorph methodsFor: 'parts bin' stamp: 'sw 8/2/2001 16:52'! initializeToStandAlone self initialize. self removeEverything; pageSize: 360@228; color: (Color gray: 0.9). self borderWidth: 1; borderColor: Color black. self beSticky. self showPageControls; insertPage. ^ self! ! !BookMorph methodsFor: 'printing' stamp: 'RAA 2/1/2001 17:41'! pagesHandledAutomatically ^true! ! !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 methodsFor: 'sorting' stamp: 'ar 4/10/2005 18:42'! 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 isString ifTrue: ["a url" toAdd := pages detect: [:aPage | aPage url = toAdd] ifNone: [toAdd]]. toAdd isString 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 isEmpty ifTrue: [self insertPage]. rejects notEmpty 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 12:12'! sortPages: evt ^ self sortPages! ! !BookMorph methodsFor: 'submorphs-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: 'submorphs-add/remove' 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: 'uniform page size' stamp: 'sw 3/3/2004 18:39'! keepingUniformPageSizeString "Answer a string characterizing whether I am currently maintaining uniform page size" ^ (self maintainsUniformPageSize ifTrue: [''] ifFalse: ['']), 'keep all pages the same size' translated! ! !BookMorph methodsFor: 'uniform page size' stamp: 'sw 6/6/2003 13:56'! maintainsUniformPageSize "Answer whether I am currently set up to maintain uniform page size" ^ self uniformPageSize notNil! ! !BookMorph methodsFor: 'uniform page size' stamp: 'sw 6/6/2003 13:56'! maintainsUniformPageSize: aBoolean "Set the property governing whether I maintain uniform page size" aBoolean ifFalse: [self removeProperty: #uniformPageSize] ifTrue: [self setProperty: #uniformPageSize toValue: currentPage extent]! ! !BookMorph methodsFor: 'uniform page size' stamp: 'sw 6/6/2003 13:57'! toggleMaintainUniformPageSize "Toggle whether or not the receiver should maintain uniform page size" self maintainsUniformPageSize: self maintainsUniformPageSize not! ! !BookMorph methodsFor: 'uniform page size' stamp: 'sw 6/6/2003 13:57'! uniformPageSize "Answer the uniform page size to maintain, or nil if the option is not set" ^ self valueOfProperty: #uniformPageSize ifAbsent: [nil]! ! !BookMorph methodsFor: '*eToys-e-toy support' stamp: 'sw 10/2/97 15:22'! configureForKids super configureForKids. pages do: [:aPage | aPage configureForKids].! ! !BookMorph methodsFor: '*eToys-e-toy support' 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 class instanceVariableNames: ''! !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! ! !BookMorph class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 10:31'! initialize FileList registerFileReader: self. self registerInFlapsRegistry. ! ! !BookMorph class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 10:37'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(BookMorph nextPageButton 'NextPage' 'A button that takes you to the next page') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(BookMorph previousPageButton 'PreviousPage' 'A button that takes you to the previous page') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(BookMorph authoringPrototype 'Book' 'A multi-paged structure') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(BookMorph nextPageButton 'NextPage' 'A button that takes you to the next page') forFlapNamed: 'Supplies'. cl registerQuad: #(BookMorph previousPageButton 'PreviousPage' 'A button that takes you to the previous page') forFlapNamed: 'Supplies'. cl registerQuad: #(BookMorph authoringPrototype 'Book' 'A multi-paged structure') forFlapNamed: 'Supplies']! ! !BookMorph class methodsFor: 'fileIn/Out' stamp: 'sd 2/6/2002 21:28'! fileReaderServicesForFile: fullName suffix: suffix ^(suffix = 'bo') | (suffix = '*') ifTrue: [ Array with: self serviceLoadAsBook] ifFalse: [#()] ! ! !BookMorph class methodsFor: 'fileIn/Out' stamp: 'LEG 10/25/2001 00:06'! openFromFile: fullName "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 _ FileStream oldFileNamed: fullName. book _ BookMorph new. book setProperty: #url toValue: aFileStream url. book fromRemoteStream: aFileStream. aFileStream close. Smalltalk isMorphic ifTrue: [ActiveWorld addMorphsAndModel: book] ifFalse: [book isMorph ifFalse: [^self inform: 'Can only load a single morph into an mvc project via this mechanism.']. book openInWorld]. book goToPage: 1! ! !BookMorph class methodsFor: 'fileIn/Out' stamp: 'sd 2/1/2002 21:33'! serviceLoadAsBook ^ SimpleServiceEntry provider: self label: 'load as book' selector: #openFromFile: description: 'open as bookmorph'! ! !BookMorph class methodsFor: 'fileIn/Out' stamp: 'sd 2/1/2002 21:33'! services ^ Array with: self serviceLoadAsBook! ! !BookMorph class methodsFor: 'initialize-release' stamp: 'asm 4/11/2003 12:31'! unload "Unload the receiver from global registries" self environment at: #FileList ifPresent: [:cl | cl unregisterFileReader: self]. self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self]! ! !BookMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 01:20'! descriptionForPartsBin ^ self partName: 'Book' categories: #('Presentation') documentation: 'Multi-page structures'! ! !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: 'scripting' stamp: 'sw 6/13/2001 17:14'! nextPageButton "Answer a button that will take the user to the next page of its enclosing book" | aButton | aButton _ SimpleButtonMorph new. aButton target: aButton; actionSelector: #nextOwnerPage; label: '->'; color: Color yellow. aButton setNameTo: 'next'. ^ aButton! ! !BookMorph class methodsFor: 'scripting' stamp: 'sw 6/13/2001 17:13'! previousPageButton "Answer a button that will take the user to the previous page of its enclosing book" | aButton | aButton _ SimpleButtonMorph new. aButton target: aButton; actionSelector: #previousOwnerPage; color: Color yellow; label: '<-'. aButton setNameTo: 'previous'. ^ aButton! ! !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: '*eToys-scripting' stamp: 'sw 11/7/2002 13:20'! 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') (slot pageNumber 'The ordinal number of the current page' Number readWrite Player getPageNumber Player setPageNumber:))))! ! AlignmentMorph subclass: #BookPageSorterMorph instanceVariableNames: 'book pageHolder' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Books'! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'jm 6/17/1998 21:27'! acceptSort book acceptSortedContentsFrom: pageHolder. self delete. ! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'dgd 10/26/2003 13:21'! addControls | bb r aButton str | r _ AlignmentMorph newRow color: Color transparent; borderWidth: 0; layoutInset: 0. r wrapCentering: #center; cellPositioning: #topCenter; hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. bb _ SimpleButtonMorph new target: self; borderColor: Color black. r addMorphBack: (self wrapperFor: (bb label: 'Okay' translated; actionSelector: #acceptSort)). bb _ SimpleButtonMorph new target: self; borderColor: Color black. r addMorphBack: (self wrapperFor: (bb label: 'Cancel' translated; 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' translated. 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: 'dgd 2/17/2003 19:56'! changeExtent: aPoint self extent: aPoint. pageHolder extent: self extent - self borderWidth! ! !BookPageSorterMorph methodsFor: 'as yet unclassified' stamp: 'dgd 10/26/2003 13:22'! 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 label: 'Close' translated; 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: 'RAA 6/28/2000 11:57'! getPartsBinStatus ^pageHolder isPartsBin! ! !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: 'RAA 6/28/2000 12:10'! wrapperFor: aMorph ^self columnWith: (self rowWith: aMorph) ! ! !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: 'dropping/grabbing' 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: 'initialization' stamp: 'dgd 2/17/2003 19:55'! defaultBorderWidth "answer the default border width for the receiver" ^ 2! ! !BookPageSorterMorph methodsFor: 'initialization' stamp: 'dgd 2/17/2003 19:55'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightGray! ! !BookPageSorterMorph methodsFor: 'initialization' stamp: 'dgd 2/17/2003 19:56'! initialize "initialize the state of the receiver" super initialize. "" self extent: Display extent - 100; listDirection: #topToBottom; wrapCentering: #topLeft; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 3. pageHolder _ PasteUpMorph new behaveLikeHolder extent: self extent -self borderWidth. pageHolder hResizing: #shrinkWrap. "pageHolder cursor: 0." "causes a walkback as of 5/25/2000" self addControls. self addMorphBack: pageHolder! ! SketchMorph subclass: #BookPageThumbnailMorph instanceVariableNames: 'page pageNumber bookMorph flipOnClick' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: '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 1/4/1999 12:52'! inBook: book bookMorph _ book! ! !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: '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: '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! ! !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: 'event handling' stamp: 'di 1/4/1999 12:19'! handlesMouseDown: event ^ event shiftPressed or: [flipOnClick and: [event controlKeyPressed not]]! ! !BookPageThumbnailMorph methodsFor: 'event handling' stamp: 'tk 7/25/2001 18:09'! mouseDown: event "turn the book to that page" "May need to lie to it so mouseUp won't go to menu that may come up during fetch of a page in doPageFlip. (Is this really true? --tk)" self doPageFlip. ! ! !BookPageThumbnailMorph methodsFor: 'fileIn/Out' stamp: 'ar 4/10/2005 18:45'! 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 isString) & (page isString) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph isNil) & (page isString) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph isNil) & (page url notNil) ifTrue: [ ^ super objectForDataStream: refStrm]. (bookMorph isNil) & (page url isNil) ifTrue: [ self error: 'page should already have a url' translated. "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: 'ar 4/10/2005 18:45'! objectsInMemory "See if page or bookMorph need to be brought in from a server." | bookUrl bk wld try | bookMorph ifNil: ["fetch the page" page isString ifFalse: [^ self]. "a morph" try _ (SqueakPageCache atURL: page) fetchContents. try ifNotNil: [page _ try]. ^ self]. bookMorph isString 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' translated]. bk == #out ifTrue: [ (bk _ BookMorph new fromURL: bookUrl) ifNil: [^ self]]. bookMorph _ bk]. page isString ifTrue: [ page _ (bookMorph pages detect: [:pg | pg url = page] ifNone: [bookMorph pages first])]. ! ! !BookPageThumbnailMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightGray! ! !BookPageThumbnailMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 21:53'! initialize "initialize the state of the receiver" | f | super initialize. "" flipOnClick _ false. f _ Form extent: 60 @ 80 depth: Display depth. f fill: f boundingBox fillColor: color. self form: f! ! !BookPageThumbnailMorph methodsFor: 'menus' stamp: 'dgd 8/30/2003 20:57'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. aCustomMenu add: 'make a flex morph' translated selector: #makeFlexMorphFor: argument: aHandMorph. flipOnClick ifTrue: [aCustomMenu add: 'disable bookmark action' translated action: #toggleBookmark] ifFalse: [aCustomMenu add: 'enable bookmark action' translated action: #toggleBookmark]. (bookMorph isKindOf: BookMorph) ifTrue: [aCustomMenu add: 'set page sound' translated action: #setPageSound:. aCustomMenu add: 'set page visual' translated action: #setPageVisual:] ! ! !BookPageThumbnailMorph methodsFor: '*sound-piano rolls' 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]! ! AlignmentMorph subclass: #BooklikeMorph instanceVariableNames: 'pageSize newPagePrototype' classVariableNames: 'PageFlipSoundOn' poolDictionaries: '' category: 'MorphicExtras-Books'! !BooklikeMorph commentStamp: '' prior: 0! A common superclass for BookMorph and WebBookMorph! !BooklikeMorph methodsFor: 'e-toy support' 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: '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: 'menus' 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: 'misc' stamp: 'dgd 8/30/2003 21:13'! addBookMenuItemsTo: aCustomMenu hand: aHandMorph (self hasSubmorphWithProperty: #pageControl) ifTrue: [aCustomMenu add: 'hide page controls' translated action: #hidePageControls] ifFalse: [aCustomMenu add: 'show page controls' translated action: #showPageControls]! ! !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: 'gk 2/24/2004 08:27'! playPageFlipSound: soundName self presenter ifNil: [^ self]. "Avoid failures when called too early" PageFlipSoundOn "mechanism to suppress sounds at init time" ifTrue: [self playSoundNamed: soundName]. ! ! !BooklikeMorph methodsFor: 'misc' stamp: 'dgd 9/19/2003 11:04'! showingFullScreenString ^ (self isInFullScreenMode ifTrue: ['exit full screen'] ifFalse: ['show full screen']) translated! ! !BooklikeMorph methodsFor: 'misc' stamp: 'dgd 9/19/2003 11:04'! showingPageControlsString ^ (self pageControlsVisible ifTrue: ['hide page controls'] ifFalse: ['show page controls']) translated! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 6/6/2003 14:10'! addPageControlMorph: aMorph "Add the morph provided as a page control, at the appropriate place" aMorph setProperty: #pageControl toValue: true. self addMorph: aMorph asElementNumber: self indexForPageControls! ! !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: 'yo 1/14/2005 19:25'! fullControlSpecs ^ { #spacer. #variableSpacer. {'-'. #deletePage. 'Delete this page' translated}. #spacer. {'«'. #firstPage. 'First page' translated}. #spacer. {'<'. #previousPage. 'Previous page' translated}. #spacer. {'·'. #invokeBookMenu. 'Click here to get a menu of options for this book.' translated}. #spacer. {'>'. #nextPage. 'Next page' translated}. #spacer. { '»'. #lastPage. 'Final page' translated}. #spacer. {'+'. #insertPage. 'Add a new page after this one' translated}. #variableSpacer. {'³'. #fewerPageControls. 'Fewer controls' translated} } ! ! !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: 'sw 6/6/2003 17:00'! indexForPageControls "Answer which submorph should hold the page controls" ^ (submorphs size > 0 and: [submorphs first hasProperty: #header]) ifTrue: [2] ifFalse: [1]! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'tk 2/19/2001 18:34'! 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 col row b lastGuy | c _ (color saturation > 0.1) ifTrue: [color slightlyLighter] ifFalse: [color slightlyDarker]. 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 _ SimpleButtonMorph new target: self; borderWidth: 1; borderColor: Color veryLightGray; color: c. b 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 6/6/2003 22:44'! setEventHandlerForPageControls: controls "Set the controls' event handler if appropriate. Default is to let the tool be dragged by the controls" controls eventHandler: (EventHandler new on: #mouseDown send: #move to: self)! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'dgd 9/19/2003 11:35'! shortControlSpecs ^ { #spacer. #variableSpacer. {'<'. #previousPage. 'Previous page' translated}. #spacer. {'·'. #invokeBookMenu. 'Click here to get a menu of options for this book.' translated}. #spacer. {'>'. #nextPage. 'Next page' translated}. #spacer. #variableSpacer. {'³'. #showMoreControls. 'More controls' translated} } ! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 3/2/1999 15:01'! showPageControls self showPageControls: self shortControlSpecs! ! !BooklikeMorph methodsFor: 'page controls' stamp: 'sw 6/6/2003 13:58'! showPageControls: controlSpecs "Remove any existing page controls, and add fresh controls at the top of the receiver (or in position 2 if the receiver's first submorph is one with property #header). Add a single column of controls." | pageControls column | self hidePageControls. column _ AlignmentMorph newColumn beTransparent. pageControls _ self makePageControlsFrom: controlSpecs. pageControls borderWidth: 0; layoutInset: 4. pageControls beSticky. pageControls setNameTo: 'Page Controls'. self setEventHandlerForPageControls: pageControls. column addMorphBack: pageControls. self addPageControlMorph: column! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BooklikeMorph class instanceVariableNames: ''! !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! ! !BooklikeMorph class methodsFor: 'class initialization' stamp: 'sw 7/4/1998 15:59'! initialize "BooklikeMorph initialize" PageFlipSoundOn _ true ! ! 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: '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' stamp: 'zz 3/2/2004 23:44'! and: block1 and: block2 "Nonevaluating conjunction without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as false, then return false immediately, without evaluating any further blocks. If all return true, then return true." self ifFalse: [^ false]. block1 value ifFalse: [^ false]. block2 value ifFalse: [^ false]. ^ true! ! !Boolean methodsFor: 'controlling' stamp: 'zz 3/2/2004 23:44'! and: block1 and: block2 and: block3 "Nonevaluating conjunction without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as false, then return false immediately, without evaluating any further blocks. If all return true, then return true." self ifFalse: [^ false]. block1 value ifFalse: [^ false]. block2 value ifFalse: [^ false]. block3 value ifFalse: [^ false]. ^ true! ! !Boolean methodsFor: 'controlling' stamp: 'zz 3/2/2004 23:44'! and: block1 and: block2 and: block3 and: block4 "Nonevaluating conjunction without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as false, then return false immediately, without evaluating any further blocks. If all return true, then return true." self ifFalse: [^ false]. block1 value ifFalse: [^ false]. block2 value ifFalse: [^ false]. block3 value ifFalse: [^ false]. block4 value ifFalse: [^ false]. ^ true! ! !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: 'controlling' stamp: 'zz 3/2/2004 23:45'! or: block1 or: block2 "Nonevaluating alternation without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as true, then return true immediately, without evaluating any further blocks. If all return false, then return false." self ifTrue: [^ true]. block1 value ifTrue: [^ true]. block2 value ifTrue: [^ true]. ^ false! ! !Boolean methodsFor: 'controlling' stamp: 'zz 3/2/2004 23:45'! or: block1 or: block2 or: block3 "Nonevaluating alternation without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as true, then return true immediately, without evaluating any further blocks. If all return false, then return false." self ifTrue: [^ true]. block1 value ifTrue: [^ true]. block2 value ifTrue: [^ true]. block3 value ifTrue: [^ true]. ^ false! ! !Boolean methodsFor: 'controlling' stamp: 'zz 3/2/2004 23:45'! or: block1 or: block2 or: block3 or: block4 "Nonevaluating alternation without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as true, then return true immediately, without evaluating any further blocks. If all return false, then return false." self ifTrue: [^ true]. block1 value ifTrue: [^ true]. block2 value ifTrue: [^ true]. block3 value ifTrue: [^ true]. block4 value ifTrue: [^ true]. ^ false! ! !Boolean methodsFor: 'controlling' stamp: 'dgd 9/26/2004 19:05'! or: block1 or: block2 or: block3 or: block4 or: block5 "Nonevaluating alternation without deep nesting. The receiver is evaluated, followed by the blocks in order. If any of these evaluates as true, then return true immediately, without evaluating any further blocks. If all return false, then return false." self ifTrue: [^ true]. block1 value ifTrue: [^ true]. block2 value ifTrue: [^ true]. block3 value ifTrue: [^ true]. block4 value ifTrue: [^ true]. block5 value ifTrue: [^ true]. ^ false! ! !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: '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' stamp: 'PH 10/3/2003 08:10'! ==> aBlock "this is material implication, a ==> b, also known as: b if a a implies b if a then b b is a consequence of a a therefore b (but note: 'it is raining therefore it is cloudy' is implication; 'it is autumn therefore the leaves are falling' is equivalence). Here is the truth table for material implication (view in a monospaced font): p | q | p ==> q -------|-------|------------- T | T | T T | F | F F | T | T F | F | T " ^self not or: [aBlock value]! ! !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: 'printing' stamp: 'sw 9/27/2001 17:19'! basicType "Answer a symbol representing the inherent type of the receiver" ^ #Boolean! ! !Boolean methodsFor: 'printing' stamp: 'apb 4/21/2006 09:22'! isLiteral ^ true! ! !Boolean methodsFor: 'printing'! storeOn: aStream "Refer to the comment in Object|storeOn:." self printOn: aStream! ! !Boolean methodsFor: 'self evaluating' stamp: 'sd 7/31/2005 21:45'! isSelfEvaluating ^ true! ! !Boolean methodsFor: '*eToys-*morphic' 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! ! PreferenceView subclass: #BooleanPreferenceView instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !BooleanPreferenceView commentStamp: '' prior: 0! I am responsible for building the visual representation of a preference that accepts true and false values! !BooleanPreferenceView methodsFor: 'user interface' stamp: 'hpt 9/24/2004 22:36'! offerPreferenceNameMenu: aPanel with: ignored1 in: ignored2 "the user clicked on a preference name -- put up a menu" | aMenu | ActiveHand showTemporaryCursor: nil. aMenu := MenuMorph new defaultTarget: self preference. aMenu addTitle: self preference name. (Preferences okayToChangeProjectLocalnessOf: self preference name) ifTrue: [aMenu addUpdating: #isProjectLocalString target: self preference action: #toggleProjectLocalness. aMenu balloonTextForLastItem: 'Some preferences are best applied uniformly to all projects, and others are best set by each individual project. If this item is checked, then this preference will be printed in bold and will have a separate value for each project']. aMenu add: 'browse senders' target: self systemNavigation selector: #browseAllCallsOn: argument: self preference name. aMenu balloonTextForLastItem: 'This will open a method-list browser on all methods that the send the preference "', self preference name, '".'. aMenu add: 'show category...' target: aPanel selector: #findCategoryFromPreference: argument: self preference name. aMenu balloonTextForLastItem: 'Allows you to find out which category, or categories, this preference belongs to.'. Smalltalk isMorphic ifTrue: [aMenu add: 'hand me a button for this preference' target: self selector: #tearOffButton. aMenu balloonTextForLastItem: 'Will give you a button that governs this preference, which you may deposit wherever you wish']. aMenu add: 'copy this name to clipboard' target: self preference selector: #copyName. aMenu balloonTextForLastItem: 'Copy the name of the preference to the text clipboard, so that you can paste into code somewhere'. aMenu popUpInWorld! ! !BooleanPreferenceView methodsFor: 'user interface' stamp: 'md 9/5/2005 15:40'! representativeButtonWithColor: aColor inPanel: aPreferencesPanel "Return a button that controls the setting of prefSymbol. It will keep up to date even if the preference value is changed in a different place" | outerButton aButton str miniWrapper | outerButton := AlignmentMorph newRow height: 24. outerButton color: (aColor ifNil: [Color r: 0.645 g: 1.0 b: 1.0]). outerButton hResizing: (aPreferencesPanel ifNil: [#shrinkWrap] ifNotNil: [#spaceFill]). outerButton vResizing: #shrinkWrap. outerButton addMorph: (aButton := UpdatingThreePhaseButtonMorph checkBox). aButton target: self preference; actionSelector: #togglePreferenceValue; getSelector: #preferenceValue. outerButton addTransparentSpacerOfSize: (2 @ 0). str := StringMorph contents: self preference name font: (StrikeFont familyName: 'NewYork' size: 12). self preference localToProject ifTrue: [str emphasis: TextEmphasis bold emphasisCode]. miniWrapper := AlignmentMorph newRow hResizing: #shrinkWrap; vResizing: #shrinkWrap. miniWrapper beTransparent addMorphBack: str lock. aPreferencesPanel ifNotNil: "We're in a Preferences panel" [miniWrapper on: #mouseDown send: #offerPreferenceNameMenu:with:in: to: self withValue: aPreferencesPanel. miniWrapper on: #mouseEnter send: #menuButtonMouseEnter: to: miniWrapper. miniWrapper on: #mouseLeave send: #menuButtonMouseLeave: to: miniWrapper. miniWrapper setBalloonText: 'Click here for a menu of options regarding this preference. Click on the checkbox to the left to toggle the setting of this preference'] ifNil: "We're a naked button, not in a panel" [miniWrapper setBalloonText: self preference helpString; setProperty: #balloonTarget toValue: aButton]. outerButton addMorphBack: miniWrapper. outerButton setNameTo: self preference name. aButton setBalloonText: self preference helpString. ^ outerButton "(Preferences preferenceAt: #balloonHelpEnabled) view tearOffButton"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BooleanPreferenceView class instanceVariableNames: ''! !BooleanPreferenceView class methodsFor: 'class initialization' stamp: 'hpt 9/26/2004 15:55'! initialize PreferenceViewRegistry ofBooleanPreferences register: self.! ! !BooleanPreferenceView class methodsFor: 'class initialization' stamp: 'hpt 9/26/2004 15:55'! unload PreferenceViewRegistry ofBooleanPreferences unregister: self.! ! !BooleanPreferenceView class methodsFor: 'view registry' stamp: 'hpt 9/26/2004 16:10'! handlesPanel: aPreferencePanel ^aPreferencePanel isKindOf: PreferencesPanel! ! ScriptEditorMorph subclass: #BooleanScriptEditor instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Support'! !BooleanScriptEditor commentStamp: '' prior: 0! A ScriptEditor required to hold a Boolean! !BooleanScriptEditor methodsFor: 'dropping/grabbing' stamp: 'sw 3/15/2005 22:43'! wantsDroppedMorph: aMorph event: evt "Answer whether the receiver would be interested in accepting the morph" (submorphs detect: [:m | m isAlignmentMorph] ifNone: [nil]) ifNotNil: [^ false]. ((aMorph isKindOf: ParameterTile) and: [aMorph scriptEditor == self topEditor]) ifTrue: [^ true]. ^ (aMorph isKindOf: PhraseTileMorph orOf: WatcherWrapper) and: [(#(#Command #Unknown) includes: aMorph resultType capitalized) not]! ! !BooleanScriptEditor methodsFor: 'other' stamp: 'tk 3/1/2001 11:24'! hibernate "do nothing"! ! !BooleanScriptEditor methodsFor: 'other' stamp: 'dgd 2/22/2003 14:44'! storeCodeOn: aStream indent: tabCount (submorphs notEmpty and: [submorphs first submorphs notEmpty]) ifTrue: [aStream nextPutAll: '(('. super storeCodeOn: aStream indent: tabCount. aStream nextPutAll: ') ~~ false)'. ^self]. aStream nextPutAll: ' true '! ! !BooleanScriptEditor methodsFor: 'other' stamp: 'tk 2/28/2001 21:07'! unhibernate "do nothing"! ! ClassTestCase subclass: #BooleanTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Objects'! !BooleanTest commentStamp: '' prior: 0! This is the unit test for the class Boolean. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category ! !BooleanTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:00'! testBasicType self assert: (true basicType = #Boolean). self assert: (false basicType = #Boolean).! ! !BooleanTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:02'! testBooleanInitializedInstance self assert: (Boolean initializedInstance = nil).! ! !BooleanTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:02'! testBooleanNew self should: [Boolean new] raise: TestResult error. self should: [True new] raise: TestResult error. self should: [False new] raise: TestResult error. ! ! !BooleanTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:02'! testNew self should: [Boolean new] raise: TestResult error. ! ! !BooleanTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:01'! testNewTileMorphRepresentative self assert: (false newTileMorphRepresentative isKindOf: TileMorph). self assert: (false newTileMorphRepresentative literal = false). self assert: (true newTileMorphRepresentative literal = true).! ! TileMorph subclass: #BooleanTile instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Tiles'! !BooleanTile commentStamp: '' prior: 0! A tile whose result type is boolean.! !BooleanTile methodsFor: 'accessing' stamp: 'sw 9/27/2001 17:19'! resultType "Answer the result type of the receiver" ^ #Boolean! ! DataType subclass: #BooleanType instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Type Vocabularies'! !BooleanType commentStamp: 'sw 1/5/2005 22:15' prior: 0! A data type representing Boolean values, i.e., true or false.! !BooleanType methodsFor: 'initial value' stamp: 'sw 9/27/2001 17:20'! initialValueForASlotFor: aPlayer "Answer the value to give initially to a newly created slot of the given type in the given player" ^ true! ! !BooleanType methodsFor: 'initialization' stamp: 'sw 9/27/2001 17:23'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" super initialize. self vocabularyName: #Boolean! ! !BooleanType methodsFor: 'tiles' stamp: 'yo 2/18/2005 16:39'! setFormatForDisplayer: aDisplayer "Set up the displayer to have the right format characteristics" aDisplayer useSymbolFormat. aDisplayer growable: true ! ! !BooleanType methodsFor: '*eToys-color' stamp: 'sw 9/27/2001 17:20'! typeColor "Answer the color for tiles to be associated with objects of this type" ^ self subduedColorFromTriplet: #(0.94 1.0 0.06)! ! !BooleanType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:20'! defaultArgumentTile "Answer a tile to represent the type" ^ true newTileMorphRepresentative typeColor: self typeColor! ! Object subclass: #BorderStyle instanceVariableNames: '' classVariableNames: 'Default' poolDictionaries: '' category: 'Morphic-Borders'! !BorderStyle commentStamp: 'kfr 10/27/2003 10:19' prior: 0! See BorderedMorph BorderedMorh new borderStyle: (BorderStyle inset width: 2); openInWorld.! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 18:15'! baseColor ^Color transparent! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 18:15'! baseColor: aColor "Ignored"! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:07'! color ^Color transparent! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:07'! color: aColor "Ignored"! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 11/26/2001 15:22'! colorsAtCorners ^Array new: 4 withAll: self color! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:07'! dotOfSize: diameter forDirection: aDirection | form | form _ Form extent: diameter@diameter depth: Display depth. form getCanvas fillOval: form boundingBox color: self color. ^form! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 17:51'! style ^#none! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:07'! width ^0! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:07'! width: aNumber "Ignored"! ! !BorderStyle methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:08'! widthForRounding ^self width! ! !BorderStyle methodsFor: 'color tracking' stamp: 'ar 8/25/2001 17:29'! trackColorFrom: aMorph "If necessary, update our color to reflect a change in aMorphs color"! ! !BorderStyle methodsFor: 'comparing' stamp: 'ar 8/25/2001 18:38'! = aBorderStyle ^self species = aBorderStyle species and:[self style == aBorderStyle style and:[self width = aBorderStyle width and:[self color = aBorderStyle color]]].! ! !BorderStyle methodsFor: 'comparing' stamp: 'ar 8/25/2001 16:08'! hash "hash is implemented because #= is implemented" ^self species hash bitXor: (self width hash bitXor: self color hash)! ! !BorderStyle methodsFor: 'drawing' stamp: 'ar 8/25/2001 17:01'! drawLineFrom: startPoint to: stopPoint on: aCanvas ^aCanvas line: startPoint to: stopPoint width: self width color: self color! ! !BorderStyle methodsFor: 'drawing' stamp: 'ar 8/25/2001 16:24'! frameOval: aRectangle on: aCanvas "Frame the given rectangle on aCanvas" aCanvas frameOval: aRectangle width: self width color: self color! ! !BorderStyle methodsFor: 'drawing' stamp: 'ar 8/25/2001 16:57'! framePolygon: vertices on: aCanvas "Frame the given rectangle on aCanvas" self framePolyline: vertices on: aCanvas. self drawLineFrom: vertices last to: vertices first on: aCanvas.! ! !BorderStyle methodsFor: 'drawing' stamp: 'dgd 2/21/2003 22:59'! framePolyline: vertices on: aCanvas "Frame the given rectangle on aCanvas" | prev next | prev := vertices first. 2 to: vertices size do: [:i | next := vertices at: i. self drawLineFrom: prev to: next on: aCanvas. prev := next]! ! !BorderStyle methodsFor: 'drawing' stamp: 'ar 8/25/2001 16:24'! frameRectangle: aRectangle on: aCanvas "Frame the given rectangle on aCanvas" aCanvas frameRectangle: aRectangle width: self width color: self color! ! !BorderStyle methodsFor: 'initialize' stamp: 'ar 8/25/2001 16:06'! releaseCachedState "Release any associated cached state"! ! !BorderStyle methodsFor: 'testing' stamp: 'ar 8/25/2001 16:08'! isBorderStyle ^true! ! !BorderStyle methodsFor: 'testing' stamp: 'ar 8/26/2001 19:30'! isComplex ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BorderStyle class instanceVariableNames: ''! !BorderStyle class methodsFor: 'instance creation' stamp: 'sw 11/26/2001 16:05'! borderStyleChoices "Answer the superset of all supported borderStyle symbols" ^ #(simple inset raised complexAltFramed complexAltInset complexAltRaised complexFramed complexInset complexRaised)! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'yo 7/2/2004 17:21'! borderStyleForSymbol: sym "Answer a border style corresponding to the given symbol" | aSymbol | aSymbol _ sym == #none ifTrue: [#simple] ifFalse: [sym]. ^ self perform: aSymbol " | aSymbol selector | aSymbol _ sym == #none ifTrue: [#simple] ifFalse: [sym]. selector _ Vocabulary eToyVocabulary translationKeyFor: aSymbol. selector isNil ifTrue: [selector _ aSymbol]. ^ self perform: selector " ! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 8/25/2001 23:52'! color: aColor width: aNumber ^self width: aNumber color: aColor! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:01'! complexAltFramed ^ComplexBorder style: #complexAltFramed! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:01'! complexAltInset ^ComplexBorder style: #complexAltInset! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:00'! complexAltRaised ^ComplexBorder style: #complexAltRaised! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:00'! complexFramed ^ComplexBorder style: #complexFramed! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:00'! complexInset ^ComplexBorder style: #complexInset! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 15:00'! complexRaised ^ComplexBorder style: #complexRaised! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 8/25/2001 17:26'! default ^Default ifNil:[Default _ self new]! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 14:59'! inset ^InsetBorder new! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 11/26/2001 14:59'! raised ^RaisedBorder new! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'sw 11/27/2001 15:22'! simple "Answer a simple border style" ^ SimpleBorder new! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'rr 6/21/2005 13:50'! thinGray ^ self width: 1 color: Color gray! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 8/25/2001 16:19'! width: aNumber ^self width: aNumber color: Color black! ! !BorderStyle class methodsFor: 'instance creation' stamp: 'ar 8/25/2001 16:19'! width: aNumber color: aColor ^SimpleBorder new color: aColor; width: aNumber; yourself! ! Morph subclass: #BorderedMorph instanceVariableNames: 'borderWidth borderColor' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Kernel'! !BorderedMorph commentStamp: 'kfr 10/27/2003 11:17' prior: 0! BorderedMorph introduce borders to morph. Borders have the instanceVariables borderWidth and borderColor. BorderedMorph new borderColor: Color red; borderWidth: 10; openInWorld. BorderedMorph also have a varaity of border styles: simple, inset, raised, complexAltFramed, complexAltInset, complexAltRaised, complexFramed, complexInset, complexRaised. These styles are set using the classes BorderStyle, SimpleBorder, RaisedBorder, InsetBorder and ComplexBorder. BorderedMorph new borderStyle: (SimpleBorder width: 1 color: Color white); openInWorld. BorderedMorph new borderStyle: (BorderStyle inset width: 2); openInWorld. ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 8/6/97 14:34'! borderColor ^ borderColor! ! !BorderedMorph methodsFor: 'accessing' stamp: 'ar 8/17/2001 16:52'! borderColor: colorOrSymbolOrNil self doesBevels ifFalse:[ colorOrSymbolOrNil isColor ifFalse:[^self]]. 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: 'aoy 2/17/2003 01:19'! borderStyle "Work around the borderWidth/borderColor pair" | style | borderColor ifNil: [^BorderStyle default]. borderWidth isZero ifTrue: [^BorderStyle default]. style := self valueOfProperty: #borderStyle ifAbsent: [BorderStyle default]. (borderWidth = style width and: ["Hah!! Try understanding this..." borderColor == style style or: ["#raised/#inset etc" #simple == style style and: [borderColor = style color]]]) ifFalse: [style := borderColor isColor ifTrue: [BorderStyle width: borderWidth color: borderColor] ifFalse: [(BorderStyle perform: borderColor) width: borderWidth "argh."]. self setProperty: #borderStyle toValue: style]. ^style trackColorFrom: self! ! !BorderedMorph methodsFor: 'accessing' stamp: 'dgd 2/21/2003 22:42'! borderStyle: aBorderStyle "Work around the borderWidth/borderColor pair" aBorderStyle = self borderStyle ifTrue: [^self]. "secure against invalid border styles" (self canDrawBorder: aBorderStyle) ifFalse: ["Replace the suggested border with a simple one" ^self borderStyle: (BorderStyle width: aBorderStyle width color: (aBorderStyle trackColorFrom: self) color)]. aBorderStyle width = self borderStyle width ifFalse: [self changed]. (aBorderStyle isNil or: [aBorderStyle == BorderStyle default]) ifTrue: [self removeProperty: #borderStyle. borderWidth := 0. ^self changed]. self setProperty: #borderStyle toValue: aBorderStyle. borderWidth := aBorderStyle width. borderColor := aBorderStyle style == #simple ifTrue: [aBorderStyle color] ifFalse: [aBorderStyle style]. self changed! ! !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: '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'! useRoundedCorners self cornerStyle: #rounded! ! !BorderedMorph methodsFor: 'accessing' stamp: 'sw 11/29/1999 17:37'! useSquareCorners self cornerStyle: #square! ! !BorderedMorph methodsFor: 'drawing' stamp: 'dgd 2/17/2003 19:57'! areasRemainingToFill: aRectangle (color isColor and: [color isTranslucent]) ifTrue: [^ Array with: aRectangle]. self wantsRoundedCorners ifTrue: [(self borderWidth > 0 and: [self borderColor isColor and: [self borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: (self innerBounds intersect: self boundsWithinCorners)] ifFalse: [^ aRectangle areasOutside: self boundsWithinCorners]] ifFalse: [(self borderWidth > 0 and: [self borderColor isColor and: [self borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: self innerBounds] ifFalse: [^ aRectangle areasOutside: self bounds]]! ! !BorderedMorph methodsFor: 'geometry' stamp: 'sw 5/18/2001 22:52'! acquireBorderWidth: aBorderWidth "Gracefully acquire the new border width, keeping the interior area intact and not seeming to shift" | delta | (delta _ aBorderWidth- self borderWidth) == 0 ifTrue: [^ self]. self bounds: ((self bounds origin - (delta @ delta)) corner: (self bounds corner + (delta @ delta))). self borderWidth: aBorderWidth. self layoutChanged! ! !BorderedMorph methodsFor: 'geometry' stamp: 'nk 4/5/2001 14:24'! closestPointTo: aPoint "account for round corners. Still has a couple of glitches at upper left and right corners" | pt | pt _ self bounds pointNearestTo: aPoint. self wantsRoundedCorners ifFalse: [ ^pt ]. self bounds corners with: (self bounds insetBy: 6) corners do: [ :out :in | (pt - out) abs < (6@6) ifTrue: [ ^(in + (Point r: 5.0 degrees: (pt - in) degrees)) asIntegerPoint ]. ]. ^pt.! ! !BorderedMorph methodsFor: 'geometry' stamp: 'nk 4/5/2001 14:23'! intersectionWithLineSegmentFromCenterTo: aPoint "account for round corners. Still has a couple of glitches at upper left and right corners" | pt | pt _ super intersectionWithLineSegmentFromCenterTo: aPoint. self wantsRoundedCorners ifFalse: [ ^pt ]. self bounds corners with: (self bounds insetBy: 6) corners do: [ :out :in | (pt - out) abs < (6@6) ifTrue: [ ^(in + (Point r: 5.0 degrees: (pt - in) degrees)) asIntegerPoint ]. ]. ^pt.! ! !BorderedMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 15:53'! borderInitialize "initialize the receiver state related to border" borderColor_ self defaultBorderColor. borderWidth _ self defaultBorderWidth! ]style[(16 2 49 15 4 22 11 3 4 19)f2b,f2,f2c146044000,f2,f2cmagenta;,f2,f2cmagenta;,f2,f2cmagenta;,f2! ! !BorderedMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:33'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color black! ! !BorderedMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 2! ! !BorderedMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:07'! initialize "initialize the state of the receiver" super initialize. "" self borderInitialize! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jmv 2/2/2006 13:35'! addCornerGrips self addMorphBack: (TopLeftGripMorph new target: self). self addMorphBack: (TopRightGripMorph new target: self). self addMorphBack: (BottomLeftGripMorph new target: self). self addMorphBack: (BottomRightGripMorph new target: self)! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 21:30'! addPaneHSplitterBetween: topMorph and: bottomMorphs | targetY minX maxX splitter | targetY _ topMorph layoutFrame bottomFraction. minX _ (bottomMorphs detectMin: [:each | each layoutFrame leftFraction]) layoutFrame leftFraction. maxX _ (bottomMorphs detectMax: [:each | each layoutFrame rightFraction]) layoutFrame rightFraction. splitter _ ProportionalSplitterMorph new beSplitsTopAndBottom; yourself. splitter layoutFrame: (LayoutFrame fractions: (minX @ targetY corner: maxX @ targetY) offsets: (((topMorph layoutFrame leftOffset ifNil: [0]) @ 0 corner: (topMorph layoutFrame rightOffset ifNil: [0]) @ 4) translateBy: 0 @ (topMorph layoutFrame bottomOffset ifNil: [0]))). self addMorphBack: splitter! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 22:23'! addPaneSplitters | splitter remaining target targetX sameX minY maxY targetY sameY minX maxX | self removePaneSplitters. self removeCornerGrips. remaining _ submorphs reject: [:each | each layoutFrame rightFraction = 1]. [remaining notEmpty] whileTrue: [target _ remaining first. targetX _ target layoutFrame rightFraction. sameX _ submorphs select: [:each | each layoutFrame rightFraction = targetX]. minY _ (sameX detectMin: [:each | each layoutFrame topFraction]) layoutFrame topFraction. maxY _ (sameX detectMax: [:each | each layoutFrame bottomFraction]) layoutFrame bottomFraction. splitter _ ProportionalSplitterMorph new. splitter layoutFrame: (LayoutFrame fractions: (targetX @ minY corner: targetX @ maxY) offsets: ((0 @ (target layoutFrame topOffset ifNil: [0]) corner: 4 @ (target layoutFrame bottomOffset ifNil: [0])) translateBy: (target layoutFrame rightOffset ifNil: [0]) @ 0)). self addMorphBack: splitter. remaining _ remaining copyWithoutAll: sameX]. remaining _ submorphs copy reject: [:each | each layoutFrame bottomFraction = 1]. [remaining notEmpty] whileTrue: [target _ remaining first. targetY _ target layoutFrame bottomFraction. sameY _ submorphs select: [:each | each layoutFrame bottomFraction = targetY]. minX _ (sameY detectMin: [:each | each layoutFrame leftFraction]) layoutFrame leftFraction. maxX _ (sameY detectMax: [:each | each layoutFrame rightFraction]) layoutFrame rightFraction. splitter _ ProportionalSplitterMorph new beSplitsTopAndBottom; yourself. splitter layoutFrame: (LayoutFrame fractions: (minX @ targetY corner: maxX @ targetY) offsets: (((target layoutFrame leftOffset ifNil: [0]) @ 0 corner: (target layoutFrame rightOffset ifNil: [0]) @ 4) translateBy: 0 @ (target layoutFrame bottomOffset ifNil: [0]))). self addMorphBack: splitter. remaining _ remaining copyWithoutAll: sameY]. self linkSubmorphsToSplitters. self splitters do: [:each | each comeToFront]. ! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 21:26'! addPaneVSplitterBetween: leftMorph and: rightMorphs | targetX minY maxY splitter | targetX _ leftMorph layoutFrame rightFraction. minY _ (rightMorphs detectMin: [:each | each layoutFrame topFraction]) layoutFrame topFraction. maxY _ (rightMorphs detectMax: [:each | each layoutFrame bottomFraction]) layoutFrame bottomFraction. splitter _ ProportionalSplitterMorph new. splitter layoutFrame: (LayoutFrame fractions: (targetX @ minY corner: targetX @ maxY) offsets: ((0 @ (leftMorph layoutFrame topOffset ifNil: [0]) corner: (4@ (leftMorph layoutFrame bottomOffset ifNil: [0]))) translateBy: (leftMorph layoutFrame rightOffset ifNil: [0]) @ 0)). self addMorphBack: splitter! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 22:16'! linkSubmorphsToSplitters self splitters do: [:each | each splitsTopAndBottom ifTrue: [self submorphsDo: [:eachMorph | (eachMorph ~= each and: [eachMorph layoutFrame bottomFraction = each layoutFrame topFraction]) ifTrue: [each addLeftOrTop: eachMorph]. (eachMorph ~= each and: [eachMorph layoutFrame topFraction = each layoutFrame bottomFraction]) ifTrue: [each addRightOrBottom: eachMorph]]] ifFalse: [self submorphsDo: [:eachMorph | (eachMorph ~= each and: [eachMorph layoutFrame rightFraction = each layoutFrame leftFraction]) ifTrue: [each addLeftOrTop: eachMorph]. (eachMorph ~= each and: [eachMorph layoutFrame leftFraction = each layoutFrame rightFraction]) ifTrue: [each addRightOrBottom: eachMorph]]]]! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 00:03'! removeCornerGrips | corners | corners _ self submorphsSatisfying: [:each | each isKindOf: CornerGripMorph]. corners do: [:each | each delete]! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 22:28'! removePaneSplitters self splitters do: [:each | each delete]! ! !BorderedMorph methodsFor: 'lookenhancements' stamp: 'jrp 7/23/2005 22:16'! splitters ^ self submorphsSatisfying: [:each | each isKindOf: ProportionalSplitterMorph]! ! !BorderedMorph methodsFor: 'menu' stamp: 'dgd 9/18/2004 19:16'! addBorderStyleMenuItems: aMenu hand: aHandMorph "Add border-style menu items" | subMenu | subMenu _ MenuMorph new defaultTarget: self. "subMenu addTitle: 'border' translated." subMenu addStayUpItemSpecial. subMenu addList: {{'border color...' translated. #changeBorderColor:}. {'border width...' translated. #changeBorderWidth:}}. subMenu addLine. BorderStyle borderStyleChoices do: [:sym | (self borderStyleForSymbol: sym) ifNotNil: [subMenu add: sym translated target: self selector: #setBorderStyle: argument: sym]]. aMenu add: 'border style' translated subMenu: subMenu ! ! !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: 'em 3/24/2005 14:36'! 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 ifNotNilDo: [:halo | halo addHandles]. self rememberCommand: (Command new cmdWording: 'border change' translated; 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.' translated hand: evt hand. handle startStepping! ! !BorderedMorph methodsFor: '*flexibleVocabularies-scripting' stamp: 'nk 9/4/2004 11:47'! understandsBorderVocabulary "Replace the 'isKindOf: BorderedMorph' so that (for instance) Connectors can have their border vocabulary visible in viewers." ^true! ! !BorderedMorph methodsFor: '*MorphicExtras-initialization' stamp: 'dgd 2/14/2003 22:00'! basicInitialize "Do basic generic initialization of the instance variables" super basicInitialize. "" self borderInitialize! ! !BorderedMorph methodsFor: '*MorphicExtras-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.! ! StringMorph subclass: #BorderedStringMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Widgets'! !BorderedStringMorph methodsFor: 'accessing' stamp: 'ar 12/12/2001 03:03'! measureContents ^super measureContents +2.! ! !BorderedStringMorph methodsFor: 'drawing' stamp: 'ar 12/31/2001 02:34'! drawOn: aCanvas | nameForm | font _ self fontToUse. nameForm _ Form extent: bounds extent depth: 8. nameForm getCanvas drawString: contents at: 0@0 font: self fontToUse color: Color black. (bounds origin + 1) eightNeighbors do: [ :pt | aCanvas stencil: nameForm at: pt color: self borderColor. ]. aCanvas stencil: nameForm at: bounds origin + 1 color: color. ! ! !BorderedStringMorph methodsFor: 'initialization' stamp: 'ar 12/14/2001 20:02'! initWithContents: aString font: aFont emphasis: emphasisCode super initWithContents: aString font: aFont emphasis: emphasisCode. self borderStyle: (SimpleBorder width: 1 color: Color white).! ! !BorderedStringMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:42'! initialize "initialize the state of the receiver" super initialize. "" self borderStyle: (SimpleBorder width: 1 color: Color white)! ! BorderedMorph subclass: #BorderedSubpaneDividerMorph instanceVariableNames: 'resizingEdge' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !BorderedSubpaneDividerMorph methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! firstEnter: evt "The first time this divider is activated, find its window and redirect further interaction there." | window | window := self firstOwnerSuchThat: [:m | m respondsTo: #secondaryPaneTransition:divider:]. window ifNil: [ self suspendEventHandler. ^ self ]. "not working out" window secondaryPaneTransition: evt divider: self. self on: #mouseEnter send: #secondaryPaneTransition:divider: to: window. ! ! !BorderedSubpaneDividerMorph methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! horizontal self hResizing: #spaceFill.! ! !BorderedSubpaneDividerMorph methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! resizingEdge ^resizingEdge ! ! !BorderedSubpaneDividerMorph methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! resizingEdge: edgeSymbol (#(top bottom) includes: edgeSymbol) ifFalse: [ self error: 'resizingEdge must be #top or #bottom' ]. resizingEdge := edgeSymbol. self on: #mouseEnter send: #firstEnter: to: self. ! ! !BorderedSubpaneDividerMorph methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! vertical self vResizing: #spaceFill.! ! !BorderedSubpaneDividerMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:09'! defaultBorderWidth "answer the default border width for the receiver" ^ 0! ! !BorderedSubpaneDividerMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:09'! defaultColor "answer the default color/fill style for the receiver" ^ Color black! ! !BorderedSubpaneDividerMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:09'! initialize "initialize the state of the receiver" super initialize. "" self extent: 1 @ 1! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BorderedSubpaneDividerMorph class instanceVariableNames: ''! !BorderedSubpaneDividerMorph class methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:24'! forBottomEdge ^self new horizontal resizingEdge: #bottom! ! !BorderedSubpaneDividerMorph class methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:25'! forTopEdge ^self new horizontal resizingEdge: #top! ! !BorderedSubpaneDividerMorph class methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:25'! horizontal ^self new horizontal! ! !BorderedSubpaneDividerMorph class methodsFor: 'as yet unclassified' stamp: 'ar 8/15/2001 23:25'! vertical ^self new vertical! ! CornerGripMorph subclass: #BottomLeftGripMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !BottomLeftGripMorph commentStamp: 'jmv 1/29/2006 17:17' prior: 0! I am the handle in the left bottom of windows used for resizing them.! !BottomLeftGripMorph methodsFor: 'target resize' stamp: 'jmv 1/29/2006 18:06'! apply: delta | oldBounds | oldBounds := target bounds. target bounds: (oldBounds origin + (delta x @ 0) corner: oldBounds corner + (0 @ delta y))! ! !BottomLeftGripMorph methodsFor: 'drawing' stamp: 'jmv 2/19/2006 14:23'! drawOn: aCanvas | dotBounds alphaCanvas windowBorderWidth dotBounds2 | windowBorderWidth _ SystemWindow borderWidth. bounds _ self bounds. alphaCanvas _ aCanvas asAlphaBlendingCanvas: 0.7. "alphaCanvas frameRectangle: bounds color: Color blue." dotBounds _ (bounds insetBy: 1). dotBounds2 _ dotBounds right: (dotBounds left + windowBorderWidth). dotBounds2 _ dotBounds2 top: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds left: (dotBounds left + windowBorderWidth). dotBounds2 _ dotBounds2 top: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds2 left: (dotBounds2 left + 7). dotBounds2 _ dotBounds2 right: (dotBounds2 right - 7). alphaCanvas fillRectangle: dotBounds2 color: self dotColor. dotBounds2 _ dotBounds right: (dotBounds left + windowBorderWidth). dotBounds2 _ dotBounds2 bottom: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds2 top: (dotBounds2 top + 7). dotBounds2 _ dotBounds2 bottom: (dotBounds2 bottom - 7). alphaCanvas fillRectangle: dotBounds2 color: self dotColor! ! !BottomLeftGripMorph methodsFor: 'accessing' stamp: 'jmv 2/2/2006 14:26'! gripLayoutFrame ^ LayoutFrame fractions: (0 @ 1 corner: 0 @ 1) offsets: (0 @ (0 - self defaultHeight) corner: self defaultWidth @ 0)! ! !BottomLeftGripMorph methodsFor: 'accessing' stamp: 'md 2/24/2006 22:43'! ptName ^#bottomLeft! ! !BottomLeftGripMorph methodsFor: 'accessing' stamp: 'jmv 1/29/2006 17:52'! resizeCursor ^ Cursor resizeForEdge: #bottomLeft! ! CornerGripMorph subclass: #BottomRightGripMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !BottomRightGripMorph commentStamp: 'jmv 1/29/2006 17:18' prior: 0! I am the handle in the right bottom of windows used for resizing them.! !BottomRightGripMorph methodsFor: 'target resize' stamp: 'jmv 1/29/2006 17:59'! apply: delta | oldBounds | oldBounds := target bounds. target bounds: (oldBounds origin corner: oldBounds corner + delta)! ! !BottomRightGripMorph methodsFor: 'drawing' stamp: 'jmv 2/19/2006 14:23'! drawOn: aCanvas | dotBounds alphaCanvas windowBorderWidth dotBounds2 | windowBorderWidth _ SystemWindow borderWidth. bounds _ self bounds. alphaCanvas _ aCanvas asAlphaBlendingCanvas: 0.7. "alphaCanvas frameRectangle: bounds color: Color blue." dotBounds _ (bounds insetBy: 1). dotBounds2 _ dotBounds left: (dotBounds right - windowBorderWidth). dotBounds2 _ dotBounds2 top: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds right: (dotBounds right - windowBorderWidth). dotBounds2 _ dotBounds2 top: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds2 left: (dotBounds2 left + 7). dotBounds2 _ dotBounds2 right: (dotBounds2 right - 7). alphaCanvas fillRectangle: dotBounds2 color: self dotColor. dotBounds2 _ dotBounds left: (dotBounds right - windowBorderWidth). dotBounds2 _ dotBounds2 bottom: (dotBounds2 bottom - windowBorderWidth). alphaCanvas fillRectangle: dotBounds2 color: self handleColor. dotBounds2 _ dotBounds2 top: (dotBounds2 top + 7). dotBounds2 _ dotBounds2 bottom: (dotBounds2 bottom - 7). alphaCanvas fillRectangle: dotBounds2 color: self dotColor! ! !BottomRightGripMorph methodsFor: 'accessing' stamp: 'jmv 2/2/2006 14:27'! gripLayoutFrame ^ LayoutFrame fractions: (1 @ 1 corner: 1 @ 1) offsets: (0 - self defaultWidth @ (0 - self defaultHeight) corner: 0 @ 0)! ! !BottomRightGripMorph methodsFor: 'accessing' stamp: 'md 2/24/2006 22:43'! ptName ^#bottomRight! ! !BottomRightGripMorph methodsFor: 'accessing' stamp: 'jmv 1/29/2006 17:51'! resizeCursor ^ Cursor resizeForEdge: #bottomRight! ! Morph subclass: #BouncingAtomsMorph instanceVariableNames: 'damageReported infectionHistory transmitInfection recentTemperatures temperature' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'change reporting' 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: 'drawing' stamp: 'di 1/4/1999 20:22'! areasRemainingToFill: aRectangle color isTranslucent ifTrue: [^ Array with: aRectangle] ifFalse: [^ aRectangle areasOutside: self bounds]! ! !BouncingAtomsMorph methodsFor: 'drawing'! drawOn: aCanvas "Clear the damageReported flag when redrawn." super drawOn: aCanvas. damageReported _ false.! ! !BouncingAtomsMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:14'! defaultColor "answer the default color/fill style for the receiver" ^ Color r: 0.8 g: 1.0 b: 0.8! ! !BouncingAtomsMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:14'! initialize "initialize the state of the receiver" super initialize. "" damageReported _ false. self extent: 400 @ 250. infectionHistory _ OrderedCollection new. transmitInfection _ false. self addAtoms: 30! ! !BouncingAtomsMorph methodsFor: 'initialization' stamp: 'ar 8/13/2003 11:41'! intoWorld: aWorld "Make sure report damage at least once" damageReported _ false. super intoWorld: aWorld.! ! !BouncingAtomsMorph methodsFor: 'menu' stamp: 'dgd 8/30/2003 21:15'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'startInfection' translated action: #startInfection. aCustomMenu add: 'set atom count' translated action: #setAtomCount. aCustomMenu add: 'show infection history' translated 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: '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' stamp: 'aoy 2/15/2003 21:38'! 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. continue := 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)]. (j := j + 1) <= count] ifFalse: [false]]]. ^collisions! ! !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: 'dgd 2/22/2003 13:36'! 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 isNil ifTrue: [recentTemperatures := OrderedCollection new. 20 timesRepeat: [recentTemperatures add: 0]]. recentTemperatures removeLast. recentTemperatures addFirst: currentTemperature. temperature := recentTemperatures sum asFloat / recentTemperatures size! ! !BouncingAtomsMorph methodsFor: 'stepping and presenter' 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: 'submorphs-add/remove'! 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: 'testing' stamp: 'jm 6/28/1998 18:10'! stepTime "As fast as possible." ^ 0 ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BouncingAtomsMorph class instanceVariableNames: ''! !BouncingAtomsMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:57'! initialize self registerInFlapsRegistry. ! ! !BouncingAtomsMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:58'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(BouncingAtomsMorph new 'Bouncing Atoms' 'Atoms, mate') forFlapNamed: 'Widgets']! ! !BouncingAtomsMorph class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 12:32'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !BouncingAtomsMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 01:21'! descriptionForPartsBin ^ self partName: 'BouncingAtoms' categories: #('Demo') documentation: 'The original, intensively-optimized bouncing-atoms simulation by John Maloney'! ! ParseNode subclass: #BraceNode instanceVariableNames: 'elements sourceLocations emitNode' classVariableNames: '' poolDictionaries: '' category: 'Compiler-ParseNodes'! !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: '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: '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: '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: '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: '*eToys-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 )" ! ! Halt subclass: #BreakPoint instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Tools'! !BreakPoint commentStamp: 'md 11/18/2003 09:32' prior: 0! This exception is raised on executing a breakpoint. "BreakPoint signal" is called from "Object>>break".! Object subclass: #BreakpointManager instanceVariableNames: '' classVariableNames: 'Installed' poolDictionaries: '' category: 'System-Tools'! !BreakpointManager commentStamp: 'emm 5/30/2002 14:20' prior: 0! This class manages methods that include breakpoints. It has several class methods to install and uninstall breakpoints. Evaluating "BreakpointManager clear" will remove all installed breakpoints in the system. Known issues: - currently, only break-on-entry type of breakpoints are supported - emphasis change not implemented for MVC browsers - uninstalling the breakpoint doesn't auto-update other browsers - uninstalling a breakpoint while debugging should restart-simulate the current method Ernest Micklei, 2002 Send comments to emicklei@philemonworks.com! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BreakpointManager class instanceVariableNames: ''! !BreakpointManager class methodsFor: 'examples' stamp: 'emm 5/30/2002 14:12'! testBreakpoint "In the menu of the methodList, click on -toggle break on entry- and evaluate the following:" "BreakpointManager testBreakpoint" Transcript cr; show: 'Breakpoint test'! ! !BreakpointManager class methodsFor: 'install-uninstall' stamp: 'emm 5/30/2002 09:37'! installInClass: aClass selector: aSymbol "Install a new method containing a breakpoint. The receiver will remember this for unstalling it later" | breakMethod | breakMethod _ self compilePrototype: aSymbol in: aClass. breakMethod isNil ifTrue: [^ nil]. self installed at: breakMethod put: aClass >> aSymbol. "old method" aClass methodDictionary at: aSymbol put: breakMethod.! ! !BreakpointManager class methodsFor: 'install-uninstall' stamp: 'md 2/15/2006 21:25'! unInstall: breakMethod | class selector oldMethod | oldMethod := self installed at: breakMethod ifAbsent:[^self]. class := breakMethod methodClass. selector := breakMethod selector. (class>>selector) == breakMethod ifTrue:[ class methodDictionary at: selector put: oldMethod]. self installed removeKey: breakMethod! ! !BreakpointManager class methodsFor: 'intialization-release' stamp: 'emm 5/30/2002 09:08'! clear "BreakpointManager clear" self installed copy keysDo:[ :breakMethod | self unInstall: breakMethod]. ! ! !BreakpointManager class methodsFor: 'testing' stamp: 'emm 5/30/2002 09:22'! methodHasBreakpoint: aMethod ^self installed includesKey: aMethod! ! !BreakpointManager class methodsFor: 'private' stamp: 'emm 5/30/2002 09:36'! breakpointMethodSourceFor: aSymbol in: aClass "Compose new source containing a break statement (currently it will be the first, later we want to insert it in any place)" | oldSource methodNode breakOnlyMethodNode sendBreakMessageNode | oldSource := aClass sourceCodeAt: aSymbol. methodNode := aClass compilerClass new compile: oldSource in: aClass notifying: nil ifFail: [self error: '[breakpoint] unable to install breakpoint']. breakOnlyMethodNode := aClass compilerClass new compile: 'temporaryMethodSelectorForBreakpoint self break. ^self' in: aClass notifying: nil ifFail: [self error: '[breakpoint] unable to install breakpoint']. sendBreakMessageNode := breakOnlyMethodNode block statements first. methodNode block statements addFirst: sendBreakMessageNode. ^methodNode printString ! ! !BreakpointManager class methodsFor: 'private' stamp: 'md 2/13/2006 14:35'! compilePrototype: aSymbol in: aClass "Compile and return a new method containing a break statement" | source node method | source := self breakpointMethodSourceFor: aSymbol in: aClass. node := aClass compilerClass new compile: source in: aClass notifying: nil ifFail: [self error: '[breakpoint] unable to install breakpoint']. node isNil ifTrue: [^nil]. "dunno what the arguments mean..." method := node generate. ^method! ! !BreakpointManager class methodsFor: 'private' stamp: 'emm 4/24/2002 23:24'! installed Installed isNil ifTrue:[Installed := IdentityDictionary new]. ^Installed! ! TestCase subclass: #BrowseTest instanceVariableNames: 'originalBrowserClass originalHierarchyBrowserClass' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser-Tests'! !BrowseTest methodsFor: 'running' stamp: 'mu 3/11/2004 15:57'! setUp | systemNavigation | systemNavigation := SystemNavigation default. originalBrowserClass := systemNavigation browserClass. originalHierarchyBrowserClass := systemNavigation hierarchyBrowserClass. systemNavigation browserClass: nil. systemNavigation hierarchyBrowserClass: nil. ! ! !BrowseTest methodsFor: 'running' stamp: 'mu 3/11/2004 15:57'! tearDown | systemNavigation | systemNavigation := SystemNavigation default. systemNavigation browserClass: originalBrowserClass. systemNavigation hierarchyBrowserClass: originalHierarchyBrowserClass.! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/6/2004 15:43'! testBrowseClass "self debug: #testBrowseClass" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentBrowsers. 1 class browse. browsersAfter := self currentBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == SmallInteger). opened delete ! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/11/2004 15:56'! testBrowseHierarchyClass "self debug: #testBrowseHierarchyClass" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentHierarchyBrowsers. 1 class browseHierarchy. browsersAfter := self currentHierarchyBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == SmallInteger). opened delete ! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/11/2004 15:52'! testBrowseHierarchyInstance "self debug: #testBrowseHierarchyInstance" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentHierarchyBrowsers. 1 browseHierarchy. browsersAfter := self currentHierarchyBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == SmallInteger). opened delete ! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/11/2004 16:00'! testBrowseHierarchyMataclass "self debug: #testBrowseHierarchyMataclass" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentHierarchyBrowsers. 1 class class browseHierarchy. browsersAfter := self currentHierarchyBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == Metaclass). opened delete ! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/6/2004 15:43'! testBrowseInstance "self debug: #testBrowseInstance" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentBrowsers. 1 browse. browsersAfter := self currentBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == SmallInteger). opened delete ! ! !BrowseTest methodsFor: 'testing' stamp: 'mu 3/6/2004 15:44'! testBrowseMetaclass "self debug: #testBrowseMetaclass" | browsersBefore browsersAfter opened | self ensureMorphic. browsersBefore := self currentBrowsers. 1 class class browse. browsersAfter := self currentBrowsers. self assert: (browsersAfter size = (browsersBefore size + 1)). opened := browsersAfter removeAll: browsersBefore; yourself. self assert: (opened size = 1). opened := opened asArray first. self assert: (opened model selectedClass == Metaclass). opened delete ! ! !BrowseTest methodsFor: 'private' stamp: 'mu 3/6/2004 15:41'! currentBrowsers ^ (ActiveWorld submorphs select: [:each | (each isKindOf: SystemWindow) and: [each model isKindOf: Browser]]) asSet! ! !BrowseTest methodsFor: 'private' stamp: 'mu 3/11/2004 15:52'! currentHierarchyBrowsers ^ (ActiveWorld submorphs select: [:each | (each isKindOf: SystemWindow) and: [each model isKindOf: HierarchyBrowser]]) asSet! ! !BrowseTest methodsFor: 'private' stamp: 'mu 3/6/2004 15:27'! ensureMorphic self isMorphic ifFalse: [self error: 'This test should be run in Morphic'].! ! !BrowseTest methodsFor: 'private' stamp: 'mu 3/6/2004 15:26'! isMorphic ^Smalltalk isMorphic! ! 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: 'al 12/6/2005 22:36'! 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 == #newTrait ifTrue: [^Trait newTemplateIn: self selectedSystemCategoryName]. 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 classDefinitionText]. editSelection == #editComment ifTrue: [(theClass _ self selectedClass) ifNil: [^ '']. comment _ theClass comment. currentCompiledMethod _ theClass organization commentRemoteStr. ^ comment size = 0 ifTrue: ['This class has not yet been commented.'] ifFalse: [comment]]. editSelection == #hierarchy ifTrue: [ self selectedClassOrMetaClass isTrait ifTrue: [^''] ifFalse: [^self selectedClassOrMetaClass printHierarchy]]. editSelection == #editMessageCategories ifTrue: [^ self classOrMetaClassOrganizer printString]. editSelection == #newMessage ifTrue: [^ (theClass _ self selectedClassOrMetaClass) ifNil: [''] ifNotNil: [theClass sourceCodeTemplate]]. editSelection == #editMessage ifTrue: [self showingByteCodes ifTrue: [^ self selectedBytecodes]. currentCompiledMethod _ latestCompiledMethod. ^ self selectedMessage]. self error: 'Browser internal error: unknown edit selection.'! ! !Browser methodsFor: 'accessing' stamp: 'drs 1/6/2003 16:11'! contentsSelection "Return the interval of text in the code pane to select when I set the pane's contents" messageCategoryListIndex > 0 & (messageListIndex = 0) ifTrue: [^ 1 to: 500] "entire empty method template" ifFalse: [^ 1 to: 0] "null selection"! ! !Browser methodsFor: 'accessing' stamp: 'al 4/24/2004 12:01'! 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 == #newTrait ifTrue: [^self defineTrait: input asString notifying: aController]. editSelection == #editSystemCategories ifTrue: [^ self changeSystemCategories: aString]. editSelection == #editClass | (editSelection == #newClass) ifTrue: [^ self defineClass: aString notifying: aController]. editSelection == #editComment ifTrue: [theClass _ self selectedClass. theClass ifNil: [self inform: 'You must select a class before giving it a comment.'. ^ false]. theClass comment: aText stamp: Utilities changeStamp. self changed: #classCommentText. ^ 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: [self inform: 'This text cannot be accepted in this part of the browser.'. ^ false]. self error: 'unacceptable accept'! ! !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: 'sd 11/20/2005 21:26'! editSelection: aSelection "Set the editSelection as requested." editSelection := aSelection. self changed: #editSelection.! ! !Browser methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:26'! 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: 'rbb 3/1/2005 10:26'! request: prompt initialAnswer: initialAnswer ^ UIManager default 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: 'accessing' stamp: 'sw 9/26/2002 17:56'! suggestCategoryToSpawnedBrowser: aBrowser "aBrowser is a message-category browser being spawned from the receiver. Tell it what it needs to know to get its category info properly set up." (self isMemberOf: Browser) "yecch, but I didn't invent the browser hierarchy" ifTrue: [aBrowser messageCategoryListIndex: (self messageCategoryList indexOf: self categoryOfCurrentMethod ifAbsent: [2])] ifFalse: [aBrowser setOriginalCategoryIndexForCurrentMethod]! ! !Browser methodsFor: 'annotation' stamp: 'sd 11/20/2005 21:26'! 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 | (aClass := self selectedClassOrMetaClass) == nil ifTrue: [^ '------']. self editSelection == #editComment ifTrue: [^ self annotationForSelector: #Comment ofClass: aClass]. self editSelection == #editClass ifTrue: [^ self annotationForSelector: #Definition ofClass: aClass]. (aSelector := self selectedMessageName) ifNil: [^ '------']. ^ self annotationForSelector: aSelector ofClass: aClass! ! !Browser methodsFor: 'breakpoints' stamp: 'emm 5/30/2002 09:23'! toggleBreakOnEntry "Install or uninstall a halt-on-entry breakpoint" | selectedMethod | self selectedClassOrMetaClass isNil ifTrue:[^self]. selectedMethod := self selectedClassOrMetaClass >> self selectedMessageName. selectedMethod hasBreakpoint ifTrue: [BreakpointManager unInstall: selectedMethod] ifFalse: [BreakpointManager installInClass: self selectedClassOrMetaClass selector: self selectedMessageName]. self changed: #messageList ! ! !Browser methodsFor: 'class comment pane' stamp: 'nk 2/15/2004 13:20'! buildMorphicCommentPane "Construct the pane that shows the class comment. Respect the Preference for standardCodeFont." | commentPane | commentPane := BrowserCommentTextMorph on: self text: #classCommentText accept: #classComment:notifying: readSelection: nil menu: #codePaneMenu:shifted:. commentPane font: Preferences standardCodeFont. ^ commentPane! ! !Browser methodsFor: 'class comment pane' stamp: 'dew 3/5/2005 23:10'! classComment: aText notifying: aPluggableTextMorph "The user has just entered aText. It may be all red (a side-effect of replacing the default comment), so remove the color if it is." | theClass cleanedText redRange | theClass := self selectedClassOrMetaClass. theClass ifNotNil: [cleanedText := aText asText. redRange := cleanedText rangeOf: TextColor red startingAt: 1. redRange size = cleanedText size ifTrue: [cleanedText removeAttribute: TextColor red from: 1 to: redRange last ]. theClass comment: aText stamp: Utilities changeStamp]. self changed: #classCommentText. ^ true! ! !Browser methodsFor: 'class comment pane' stamp: 'md 2/24/2006 15:23'! noCommentNagString ^ Text string: 'THIS CLASS HAS NO COMMENT!!' translated attribute: TextColor red. ! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! 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 | ChangeSet current 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: 'sd 11/20/2005 21:26'! classCommentText "return the text to display for the comment of the currently selected class" | theClass | theClass := self selectedClassOrMetaClass. theClass ifNil: [ ^'']. ^ theClass hasComment ifTrue: [ theClass comment ] ifFalse: [ self noCommentNagString ]! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! classDefinitionText "return the text to display for the definition of the currently selected class" | theClass | theClass := self selectedClassOrMetaClass. theClass ifNil: [ ^'']. ^theClass definitionST80.! ! !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: 'rr 7/10/2006 11:47'! classListMenu: aMenu shifted: shifted "Set up the menu to apply to the receiver's class list, honoring the #shifted boolean" ServiceGui browser: self classMenu: aMenu. ServiceGui onlyServices ifTrue: [^aMenu]. shifted ifTrue: [^ self shiftedClassListMenu: aMenu]. aMenu addList: #( - ('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) ('find method wildcard...' findMethodWithWildcard) - ('more...' offerShiftedClassListMenu)). ^ aMenu ! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:30'! createInstVarAccessors "Create getters and setters for all inst vars defined at the level of the current class selection, except do NOT clobber or override any selectors already understood by the instances of the selected class" | aClass newMessage setter | (aClass := self selectedClassOrMetaClass) ifNotNil: [aClass instVarNames do: [:aName | (aClass canUnderstand: aName asSymbol) ifFalse: [newMessage := aName, ' "Answer the value of ', aName, '" ^ ', aName. aClass compile: newMessage classified: 'accessing' notifying: nil]. (aClass canUnderstand: (setter := aName, ':') asSymbol) ifFalse: [newMessage := setter, ' anObject "Set the value of ', aName, '" ', aName, ' := anObject'. aClass compile: newMessage classified: 'accessing' notifying: nil]]]! ! !Browser methodsFor: 'class functions' stamp: 'md 3/3/2006 11:02'! 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. ((defTokens first = 'Trait' and: [defTokens second = 'named:']) or: [defTokens second = 'classTrait']) ifTrue: [^self defineTrait: defString notifying: aController]. keywdIx _ defTokens findFirst: [:x | x beginsWith: 'category']. envt _ Smalltalk. keywdIx _ defTokens findFirst: [:x | '*subclass*' match: x]. newClassName _ (defTokens at: keywdIx+1) copyWithoutAll: '#()'. ((oldClass isNil or: [oldClass theNonMetaClass name asString ~= newClassName]) and: [envt includesKey: 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: #systemCategoryList. self changed: #classList. self clearUserEditFlag. self setClass: class selector: nil. "self clearUserEditFlag; editClass." ^ true] ifFalse: [^ false]! ! !Browser methodsFor: 'class functions' stamp: 'nk 2/15/2004 13:23'! editClass "Retrieve the description of the class definition." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. self editSelection: #editClass. self changed: #contents. self changed: #classCommentText. ! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! editComment "Retrieve the description of the class comment." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. metaClassIndicated := false. self editSelection: #editComment. self changed: #classSelectionChanged. self changed: #messageCategoryList. self changed: #messageList. self decorateButtons. self contentsChanged ! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! 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 := self systemNavigation 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 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: 'rbb 3/1/2005 10:25'! findMethod "Pop up a list of the current class's methods, and select the one chosen by the user" | aClass selectors reply cat messageCatIndex messageIndex | self classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. aClass := self selectedClassOrMetaClass. selectors := aClass selectors asSortedArray. selectors isEmpty ifTrue: [self inform: aClass name, ' has no methods.'. ^ self]. reply := (SelectionMenu labelList: (Array with: 'Enter Wildcard'), selectors lines: #(1) selections: (Array with: 'EnterWildcard'), selectors) startUp. reply == nil ifTrue: [^ self]. reply = 'EnterWildcard' ifTrue: [ reply := UIManager default request: 'Enter partial method name:'. (reply isNil or: [reply isEmpty]) ifTrue: [^self]. (reply includes: $*) ifFalse: [reply := '*', reply, '*']. selectors := selectors select: [:each | reply match: each]. selectors isEmpty ifTrue: [self inform: aClass name, ' has no matching methods.'. ^ self]. reply := selectors size = 1 ifTrue: [selectors first] ifFalse: [ (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: 'rbb 3/1/2005 10:26'! findMethodWithWildcard "Pop up a list of the current class's methods, and select the one chosen by the user" | aClass selectors reply cat messageCatIndex messageIndex | self classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. aClass := self selectedClassOrMetaClass. selectors := aClass selectors asSortedArray. selectors isEmpty ifTrue: [self inform: aClass name, ' has no methods.'. ^ self]. reply := UIManager default request: 'Enter partial method name:'. (reply isNil or: [reply isEmpty]) ifTrue: [^self]. (reply includes: $*) ifFalse: [reply := '*', reply, '*']. selectors := selectors select: [:each | reply match: each]. selectors isEmpty ifTrue: [self inform: aClass name, ' has no matching methods.'. ^ self]. reply := selectors size = 1 ifTrue: [selectors first] ifFalse: [ (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: 'nk 2/14/2004 15:09'! hierarchy "Display the inheritance hierarchy of the receiver's selected class." classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self messageCategoryListIndex: 0. self editSelection: #hierarchy. self changed: #editComment. self contentsChanged. ^ self! ! !Browser methodsFor: 'class functions' stamp: 'nk 2/14/2004 15:07'! makeNewSubclass self selectedClassOrMetaClass ifNil: [^ self]. self okToChange ifFalse: [^ self]. self editSelection: #newClass. self contentsChanged! ! !Browser methodsFor: 'class functions' stamp: 'nk 2/14/2004 15:09'! plusButtonHit "Cycle among definition, comment, and hierachy" editSelection == #editComment ifTrue: [self hierarchy. ^ self]. editSelection == #hierarchy ifTrue: [self editSelection: #editClass. classListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. self changed: #editComment. self contentsChanged. ^ self]. self editComment! ! !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: 'sw 3/5/2001 18:04'! removeClass "If the user confirms the wish to delete the class, do so" super removeClass ifTrue: [self classListIndex: 0]! ! !Browser methodsFor: 'class functions' stamp: 'sd 11/20/2005 21:26'! 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 := self systemNavigation allCallsOn: (Smalltalk associationAt: newName). obs isEmpty ifFalse: [self systemNavigation browseMessageList: obs name: 'Obsolete References to ' , oldName autoSelect: oldName]! ! !Browser methodsFor: 'class functions' stamp: 'md 7/29/2005 15:59'! shiftedClassListMenu: aMenu "Set up the menu to apply to the receiver's class list when the shift key is down" ^ aMenu addList: #( - ('unsent methods' browseUnusedMethods 'browse all methods defined by this class that have no senders') ('unreferenced inst vars' showUnreferencedInstVars 'show a list of all instance variables that are not referenced in methods') ('unreferenced class vars' showUnreferencedClassVars 'show a list of all class variables that are not referenced in methods') ('subclass template' makeNewSubclass 'put a template into the code pane for defining of a subclass of this class') - ('sample instance' makeSampleInstance 'give me a sample instance of this class, if possible') ('inspect instances' inspectInstances 'open an inspector on all the extant instances of this class') ('inspect subinstances' inspectSubInstances 'open an inspector on all the extant instances of this class and of all of its subclasses') - ('add all meths to current chgs' addAllMethodsToCurrentChangeSet 'place all the methods defined by this class into the current change set') ('create inst var accessors' createInstVarAccessors 'compile instance-variable access methods for any instance variables that do not yet have them') - ('more...' offerUnshiftedClassListMenu 'return to the standard class-list menu'))! ! !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: 'sd 11/20/2005 21:26'! classListIndex: anInteger "Set anInteger to be the index of the current class selection." | className | classListIndex := anInteger. self setClassOrganizer. messageCategoryListIndex := 0. messageListIndex := 0. self classCommentIndicated ifTrue: [] ifFalse: [self 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: #classCommentText. self changed: #classListIndex. "update my selection" self changed: #messageCategoryList. self changed: #messageList. self changed: #relabel. self contentsChanged! ! !Browser methodsFor: 'class list' stamp: 'sd 11/20/2005 21:26'! classListSingleton | name | name := self selectedClassName. ^ name ifNil: [Array new] ifNotNil: [Array with: name]! ! !Browser methodsFor: 'class list' stamp: 'sd 11/20/2005 21:26'! 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: [^ Beeper 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'rr 7/10/2006 11:48'! codePaneMenu: aMenu shifted: shifted ServiceGui browser: self codePaneMenu: aMenu. ServiceGui onlyServices ifTrue: [^ aMenu]. super codePaneMenu: aMenu shifted: shifted. ^ aMenu! ! !Browser methodsFor: 'code pane' stamp: 'sd 11/20/2005 21:26'! 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 ifNil: [ self selectOriginalCategoryForCurrentMethod ifFalse:["Select the '--all--' category" self messageCategoryListIndex: 1]]. 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: 'sw 5/18/2001 20:55'! showBytecodes "Show or hide the bytecodes of the selected method -- an older protocol now mostly not relevant." self toggleShowingByteCodes! ! !Browser methodsFor: 'construction' stamp: 'sd 11/20/2005 21:26'! addLowerPanesTo: window at: nominalFractions with: editString | commentPane | super addLowerPanesTo: window at: nominalFractions with: editString. commentPane := self buildMorphicCommentPane. window addMorph: commentPane fullFrame: (LayoutFrame fractions: (0@0.75 corner: 1@1)). self changed: #editSelection.! ! !Browser methodsFor: 'copying' stamp: 'sd 11/20/2005 21:26'! 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: 'nk 6/12/2004 17:43'! 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: [ | srcClass srcSelector srcCategory | srcClass := transferMorph passenger key. srcSelector := transferMorph passenger value. srcCategory := srcBrowser selectedMessageCategoryName. srcCategory ifNil: [srcCategory := srcClass organization categoryOfElement: srcSelector]. success := self acceptMethod: srcSelector messageCategory: srcCategory class: srcClass 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! ! !Browser methodsFor: 'drag and drop' stamp: 'sd 11/20/2005 21:26'! 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: 'al 4/24/2004 11:50'! 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 classSide] 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: 'sd 11/20/2005 21:26'! 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: 'nk 4/22/2004 18:00'! changeMessageCategoryForMethod: methodSel dstMessageCategory: dstMessageCategorySel srcMessageCategory: srcMessageCategorySel insideClassOrMeta: classOrMeta internal: internal copySemantic: copyFlag "Recategorize the method named by methodSel. If the dstMessageCategorySel is the allCategory, then recategorize it from its parents." | success messageCategorySel | copyFlag ifTrue: [^ false]. "only move semantic" messageCategorySel := dstMessageCategorySel ifNil: [srcMessageCategorySel]. (success := messageCategorySel notNil and: [messageCategorySel ~= srcMessageCategorySel]) ifTrue: [success := messageCategorySel == ClassOrganizer allCategory ifTrue: [self recategorizeMethodSelector: methodSel] ifFalse: [(classOrMeta organization categories includes: messageCategorySel) and: [classOrMeta organization classify: methodSel under: messageCategorySel suppressIfDefault: false. true]]]. success ifTrue: [self changed: #messageList. internal ifFalse: [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: 'sd 11/20/2005 21:26'! dragPassengerFor: item inMorph: dragSource | transferType smn | (dragSource isKindOf: PluggableListMorph) ifFalse: [^nil]. transferType := self dragTransferTypeForMorph: dragSource. transferType == #classList ifTrue: [^self selectedClass]. transferType == #messageList ifFalse: [ ^nil ]. smn := self selectedMessageName ifNil: [ ^nil ]. (MessageSet isPseudoSelector: smn) ifTrue: [ ^nil ]. ^ self selectedClassOrMetaClass -> smn. ! ! !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: 'ls 6/22/2001 23:21'! dstCategoryDstListMorph: dstListMorph ^(dstListMorph getListSelector == #systemCategoryList) ifTrue: [dstListMorph potentialDropItem ] ifFalse: [self selectedSystemCategoryName]! ! !Browser methodsFor: 'drag and drop' stamp: 'sd 11/20/2005 21:26'! dstClassDstListMorph: dstListMorph | dropItem | ^(dstListMorph getListSelector == #classList) ifTrue: [(dropItem := dstListMorph potentialDropItem) ifNotNil: [Smalltalk at: dropItem withBlanksCondensed asSymbol]] ifFalse: [dstListMorph model selectedClass]! ! !Browser methodsFor: 'drag and drop' stamp: 'sd 11/20/2005 21:26'! dstMessageCategoryDstListMorph: dstListMorph | dropItem | ^dstListMorph getListSelector == #messageCategoryList ifTrue: [dropItem := dstListMorph potentialDropItem. dropItem ifNotNil: [dropItem asSymbol]] ifFalse: [self selectedMessageCategoryName ifNil: [ Categorizer default ]]! ! !Browser methodsFor: 'drag and drop' stamp: 'sd 11/20/2005 21:26'! 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' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'md 2/24/2006 15:24'! addAListPane: aListPane to: window at: nominalFractions plus: verticalOffset | row switchHeight divider | row := AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 1; borderWidth: 1; layoutPolicy: ProportionalLayout new. switchHeight := 25. self addMorphicSwitchesTo: row at: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@(1-switchHeight) corner: 0@0) ). divider := BorderedSubpaneDividerMorph forTopEdge. divider extent: 4@4; color: Color transparent; borderColor: #raised; borderWidth: 0. row addMorph: divider fullFrame: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@switchHeight negated corner: 0@(1-switchHeight)) ). row addMorph: aListPane fullFrame: ( LayoutFrame fractions: (0@0 corner: 1@1) offsets: (0@0 corner: 0@(switchHeight negated)) ). 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: 'rr 6/21/2005 13:24'! addMorphicSwitchesTo: window at: aLayoutFrame window addMorph: self buildMorphicSwitches fullFrame: aLayoutFrame. ! ! !Browser methodsFor: 'initialize-release' stamp: 'rww 8/18/2002 09:31'! browseSelectionInPlace "In place code - incomplete" " self systemCategoryListIndex: (self systemCategoryList indexOf: self selectedClass category). self classListIndex: (self classList indexOf: self selectedClass name)" self spawnHierarchy.! ! !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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'wiz 7/19/2005 23:38'! buildMorphicClassList | myClassList | (myClassList := PluggableListMorph new) setProperty: #highlightSelector toValue: #highlightClassList:with:; on: self list: #classList selected: #classListIndex changeSelected: #classListIndex: menu: #classListMenu:shifted: keystroke: #classListKey:from:. myClassList borderWidth: 0. myClassList enableDragNDrop: Preferences browseWithDragNDrop. myClassList doubleClickSelector: #browseSelectionInPlace. "For doubleClick to work best disable autoDeselect" myClassList autoDeselect: false . ^myClassList ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! buildMorphicMessageCatList | myMessageCatList | (myMessageCatList := PluggableMessageCategoryListMorph new) setProperty: #highlightSelector toValue: #highlightMessageCategoryList:with:; on: self list: #messageCategoryList selected: #messageCategoryListIndex changeSelected: #messageCategoryListIndex: menu: #messageCategoryMenu: keystroke: #arrowKey:from: getRawListSelector: #rawMessageCategoryList. myMessageCatList enableDragNDrop: Preferences browseWithDragNDrop. ^myMessageCatList ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! buildMorphicMessageList "Build a morphic message list, with #messageList as its list-getter" | aListMorph | (aListMorph := PluggableListMorph new) setProperty: #highlightSelector toValue: #highlightMessageList:with:; setProperty: #balloonTextSelectorForSubMorphs toValue: #balloonTextForMethodString; on: self list: #messageList selected: #messageListIndex changeSelected: #messageListIndex: menu: #messageListMenu:shifted: keystroke: #messageListKey:from:. aListMorph enableDragNDrop: Preferences browseWithDragNDrop. aListMorph menuTitleSelector: #messageListSelectorTitle. ^aListMorph ! ! !Browser methodsFor: 'initialize-release' stamp: 'md 2/24/2006 15:25'! buildMorphicSwitches | instanceSwitch commentSwitch classSwitch row aColor | instanceSwitch := PluggableButtonMorph on: self getState: #instanceMessagesIndicated action: #indicateInstanceMessages. instanceSwitch label: 'instance'; askBeforeChanging: true; borderWidth: 1; borderColor: Color gray. commentSwitch := PluggableButtonMorph on: self getState: #classCommentIndicated action: #plusButtonHit. commentSwitch label: '?' asText allBold; askBeforeChanging: true; setBalloonText: 'class comment'; borderWidth: 1; borderColor: Color gray. classSwitch := PluggableButtonMorph on: self getState: #classMessagesIndicated action: #indicateClassMessages. classSwitch label: 'class'; askBeforeChanging: true; borderWidth: 1; borderColor: Color gray. row := AlignmentMorph newRow hResizing: #spaceFill; vResizing: #spaceFill; cellInset: 1; borderWidth: 0; addMorphBack: instanceSwitch; addMorphBack: commentSwitch; addMorphBack: classSwitch. aColor := Color white. row color: aColor muchLighter. {instanceSwitch. commentSwitch. classSwitch} do: [:m | m color: aColor; onColor: aColor darker darker offColor: aColor; hResizing: #spaceFill; vResizing: #spaceFill.]. ^ row ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! buildMorphicSystemCatList | dragNDropFlag myCatList | dragNDropFlag := Preferences browseWithDragNDrop. (myCatList := PluggableListMorph new) setProperty: #highlightSelector toValue: #highlightSystemCategoryList:with:; on: self list: #systemCategoryList selected: #systemCategoryListIndex changeSelected: #systemCategoryListIndex: menu: #systemCategoryMenu: keystroke: #systemCatListKey:from:. myCatList enableDragNDrop: dragNDropFlag. ^myCatList ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! 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'! highlightSystemCategoryList: list with: morphList! ! !Browser methodsFor: 'initialize-release' stamp: 'nk 2/13/2001 13:25'! labelString ^self selectedClass ifNil: [ self defaultBrowserTitle ] ifNotNil: [ self defaultBrowserTitle, ': ', self selectedClass printString ]. ! ! !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: 'sd 11/20/2005 21:26'! 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. self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString. 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) ). window setUpdatablePanesFrom: #(messageCategoryList messageList). ^ window ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! 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. "The method SystemWindow>>addMorph:fullFrame: checks scrollBarsOnRight, then adds the morph at the back if true, otherwise it is added in front. But flopout hScrollbars need the lowerpanes to be behind the upper ones in the draw order. Hence the value of scrollBarsOnRight affects the order in which the lowerpanes are added. " Preferences scrollBarsOnRight ifFalse: [self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString]. 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). Preferences scrollBarsOnRight ifTrue: [self addLowerPanesTo: window at: (0@hSepFrac corner: 1@1) with: editString]. window setUpdatablePanesFrom: #(systemCategoryList classList messageCategoryList messageList). ^ window ! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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. mySingletonList hideScrollBarsIndefinitely. 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: 'sd 11/20/2005 21:26'! 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 controller terminateDuringSelect: true. 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:26'! 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:26'! 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:26'! 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:26'! 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 controller terminateDuringSelect: true. 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:26'! setClass: aBehavior selector: aSymbol "Set the state of a new, uninitialized Browser." | isMeta aClass messageCatIndex | aBehavior ifNil: [^ self]. (aBehavior isKindOf: Metaclass) ifTrue: [ isMeta := true. aClass := aBehavior soleInstance] ifFalse: [ isMeta := false. aClass := aBehavior]. self selectCategoryForClass: aClass. self classListIndex: ( (systemOrganizer listAtCategoryNamed: self selectedSystemCategoryName) indexOf: aClass name). self metaClassIndicated: isMeta. aSymbol ifNil: [^ self]. messageCatIndex := aBehavior organization numberOfCategoryOfElement: aSymbol. self messageCategoryListIndex: (messageCatIndex > 0 ifTrue: [messageCatIndex + 1] ifFalse: [0]). messageCatIndex = 0 ifTrue: [^ self]. self messageListIndex: ( (aBehavior organization listAtCategoryNumber: messageCatIndex) indexOf: aSymbol).! ! !Browser methodsFor: 'initialize-release' stamp: 'sd 11/20/2005 21:26'! 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' stamp: 'sd 11/20/2005 21:26'! systemOrganizer: aSystemOrganizer "Initialize the receiver as a perspective on the system organizer, aSystemOrganizer. Typically there is only one--the system variable SystemOrganization." contents := nil. systemOrganizer := aSystemOrganizer. systemCategoryListIndex := 0. classListIndex := 0. messageCategoryListIndex := 0. messageListIndex := 0. metaClassIndicated := false. self setClassOrganizer. self editSelection: #none.! ! !Browser methodsFor: 'message category functions' stamp: 'sd 11/20/2005 21:26'! 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 := (UIManager default chooseFrom: labels lines: lines title: '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: 'NS 4/7/2004 22:47'! alphabetizeMessageCategories classListIndex = 0 ifTrue: [^ false]. self okToChange ifFalse: [^ false]. 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: 'sd 11/20/2005 21:26'! 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. self class openBrowserView: (newBrowser openMessageCatEditString: aString) label: 'Message Category Browser (' , newBrowser selectedClassOrMetaClassName , ')']! ! !Browser methodsFor: 'message category functions' stamp: 'sw 10/8/2001 14:10'! canShowMultipleMessageCategories "Answer whether the receiver is capable of showing multiple message categories" ^ true! ! !Browser methodsFor: 'message category functions' stamp: 'sd 11/20/2005 21:26'! categoryOfCurrentMethod "Determine the method category associated with the receiver at the current moment, or nil if none" | aCategory | ^ super categoryOfCurrentMethod ifNil: [(aCategory := self messageCategoryListSelection) == ClassOrganizer allCategory ifTrue: [nil] ifFalse: [aCategory]]! ! !Browser methodsFor: 'message category functions' stamp: 'NS 4/7/2004 22: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." self classOrMetaClassOrganizer changeFromString: aString. self clearUserEditFlag. self editClass. self classListIndex: classListIndex. ^ true! ! !Browser methodsFor: 'message category functions' stamp: 'nk 2/14/2004 15:06'! 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. self 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: 'emm 5/30/2002 09:20'! highlightMessageList: list with: morphList "Changed by emm to add emphasis in case of breakpoint" morphList do:[:each | | classOrNil methodOrNil | classOrNil := self selectedClassOrMetaClass. methodOrNil := classOrNil isNil ifTrue:[nil] ifFalse:[classOrNil methodDictionary at: each contents ifAbsent:[]]. (methodOrNil notNil and:[methodOrNil hasBreakpoint]) ifTrue:[each contents: ((each contents ,' [break]') asText allBold)]]! ! !Browser methodsFor: 'message category functions' stamp: 'rr 7/10/2006 11:49'! messageCategoryMenu: aMenu ServiceGui browser: self messageCategoryMenu: aMenu. ServiceGui onlyServices ifTrue: [^aMenu]. ^ aMenu labels: 'browse printOut fileOut reorganize alphabetize remove empty categories categorize all uncategorized new category... rename... remove' lines: #(3 8) selections: #(buildMessageCategoryBrowser printOutMessageCategories fileOutMessageCategories editMessageCategories alphabetizeMessageCategories removeEmptyCategories categorizeAllUncategorizedMethods 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: 'nk 4/23/2004 09:18'! removeEmptyCategories self okToChange ifFalse: [^ self]. self selectedClassOrMetaClass organization removeEmptyCategories. self changed: #messageCategoryList ! ! !Browser methodsFor: 'message category functions' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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]. self classOrMetaClassOrganizer renameCategory: oldName toBe: newName. self classListIndex: classListIndex. self messageCategoryListIndex: oldIndex. self changed: #messageCategoryList. ! ! !Browser methodsFor: 'message category functions' stamp: 'sd 11/20/2005 21:26'! showHomeCategory "Show the home category of the selected method. This is only really useful if one is in a tool that supports the showing of categories. Thus, it's good in browsers and hierarchy browsers but not in message-list browsers" | aSelector | self okToChange ifTrue: [(aSelector := self selectedMessageName) ifNotNil: [self selectOriginalCategoryForCurrentMethod. self selectedMessageName: aSelector]]! ! !Browser methodsFor: 'message category list' stamp: 'sd 11/20/2005 21:26'! categorizeAllUncategorizedMethods "Categorize methods by looking in parent classes for a method category." | organizer organizers | organizer := self classOrMetaClassOrganizer. organizers := self selectedClassOrMetaClass withAllSuperclasses collect: [:ea | ea organization]. (organizer listAtCategoryNamed: ClassOrganizer default) do: [:sel | | found | found := (organizers collect: [ :org | org categoryOfElement: sel]) detect: [:ea | ea ~= ClassOrganizer default and: [ ea ~= nil]] ifNone: []. found ifNotNil: [organizer classify: sel under: found]]. self changed: #messageCategoryList! ! !Browser methodsFor: 'message category list' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! messageCategoryListIndex: anInteger "Set the selected message category to be the one indexed by anInteger." messageCategoryListIndex := anInteger. messageListIndex := 0. self changed: #messageCategorySelectionChanged. self changed: #messageCategoryListIndex. "update my selection" self changed: #messageList. self editSelection: (anInteger > 0 ifTrue: [#newMessage] ifFalse: [self classListIndex > 0 ifTrue: [#editClass] ifFalse: [#newClass]]). contents := nil. 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: 'nk 4/22/2004 17:59'! recategorizeMethodSelector: sel "Categorize method named sel by looking in parent classes for a method category. Answer true if recategorized." | thisCat | self selectedClassOrMetaClass allSuperclasses do: [:ea | thisCat := ea organization categoryOfElement: sel. (thisCat ~= ClassOrganizer default and: [thisCat notNil]) ifTrue: [self classOrMetaClassOrganizer classify: sel under: thisCat. self changed: #messageCategoryList. ^ true]]. ^ false! ! !Browser methodsFor: 'message category list' stamp: 'nk 6/13/2004 06:20'! selectMessageCategoryNamed: aSymbol "Given aSymbol, select the category with that name. Do nothing if aSymbol doesn't exist." self messageCategoryListIndex: (self messageCategoryList indexOf: aSymbol ifAbsent: [ 1])! ! !Browser methodsFor: 'message category list' stamp: 'sd 11/20/2005 21:26'! 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 selectorName | aSymbol := self categoryOfCurrentMethod. selectorName := self selectedMessageName. (aSymbol notNil and: [aSymbol ~= ClassOrganizer allCategory]) ifTrue: [messageCategoryListIndex := (self messageCategoryList indexOf: aSymbol). messageListIndex := (self messageList indexOf: selectorName). self changed: #messageCategorySelectionChanged. self changed: #messageCategoryListIndex. "update my selection" self changed: #messageList. self changed: #messageListIndex. ^ 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: 'sd 11/20/2005 21:26'! 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'! 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: 'sd 1/5/2002 21:11'! 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]. ^ self class openMessageBrowserForClass: self selectedClassOrMetaClass selector: self selectedMessageName editString: aString! ! !Browser methodsFor: 'message functions' stamp: 'sd 11/20/2005 21:26'! 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: 'md 2/20/2006 18:42'! 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 := (self selectedClassOrMetaClass parserClass 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: 'al 4/24/2004 12:48'! inspectInstances "Inspect all instances of the selected class. 1/26/96 sw" | myClass | ((myClass _ self selectedClassOrMetaClass) isNil or: [myClass isTrait]) ifFalse: [myClass theNonMetaClass inspectAllInstances] ! ! !Browser methodsFor: 'message functions' stamp: 'al 4/24/2004 12:48'! inspectSubInstances "Inspect all instances of the selected class and all its subclasses 1/26/96 sw" | aClass | ((aClass _ self selectedClassOrMetaClass) isNil or: [aClass isTrait]) ifFalse: [ aClass _ aClass theNonMetaClass. aClass inspectSubInstances]. ! ! !Browser methodsFor: 'message functions' stamp: 'rr 7/10/2006 11:50'! messageListMenu: aMenu shifted: shifted "Answer the message-list menu" ServiceGui browser: self messageListMenu: aMenu. ServiceGui onlyServices ifTrue: [^ aMenu]. shifted ifTrue: [^ self shiftedMessageListMenu: aMenu]. aMenu addList: #( ('what to show...' offerWhatToShowMenu) ('toggle break on entry' toggleBreakOnEntry) - ('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: 'al 4/24/2004 11:49'! 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 systemNavigation confirmRemovalOf: messageName on: self selectedClassOrMetaClass. confirmation == 3 ifTrue: [^ self]. (self selectedClassOrMetaClass includesLocalSelector: messageName) ifTrue: [self selectedClassOrMetaClass removeSelector: messageName] ifFalse: [self removeNonLocalSelector: messageName]. self messageListIndex: 0. self changed: #messageList. self setClassOrganizer. "In case organization not cached" confirmation == 2 ifTrue: [self systemNavigation 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: 'md 7/29/2005 15:59'! shiftedMessageListMenu: aMenu "Fill aMenu with the items appropriate when the shift key is held down" Smalltalk isMorphic ifTrue: [aMenu addStayUpItem]. aMenu addList: #( ('method pane' makeIsolatedCodePane) ('tile scriptor' openSyntaxView) ('toggle diffing (D)' toggleDiffing) ('implementors of sent messages' browseAllMessages) - ('local senders of...' browseLocalSendersOfMessages) ('local implementors of...' browseLocalImplementors) - ('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)). self canShowMultipleMessageCategories ifTrue: [aMenu addList: #(('show category (C)' showHomeCategory))]. aMenu addList: #( - ('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) ('copy up or copy down...' copyUpOrCopyDown) - ('more...' unshiftedYellowButtonActivity)). ^ aMenu ! ! !Browser methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! 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: [ ^ self classOrMetaClassOrganizer ifNil: [Array new] ifNotNil: [self classOrMetaClassOrganizer allMethodSelectors] "^ 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: 'sd 11/20/2005 21:26'! messageListIndex: anInteger "Set the selected message selector to be the one indexed by anInteger." messageListIndex := anInteger. self editSelection: (anInteger > 0 ifTrue: [#editMessage] ifFalse: [self messageCategoryListIndex > 0 ifTrue: [#newMessage] ifFalse: [self classListIndex > 0 ifTrue: [#editClass] ifFalse: [#newClass]]]). contents := nil. self changed: #messageListIndex. "update my selection" self contentsChanged. self decorateButtons.! ! !Browser methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! 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: 'md 2/20/2006 15:01'! selectedMessage "Answer a copy of the source code for the selected message." | class selector method | contents == nil ifFalse: [^ contents copy]. self showingDecompile ifTrue: [^ self decompiledSourceIntoContents]. class := self selectedClassOrMetaClass. selector := self selectedMessageName. method := class compiledMethodAt: selector ifAbsent: [^ '']. "method deleted while in another project" currentCompiledMethod := method. ^ contents := (self showingDocumentation ifFalse: [ self sourceStringPrettifiedAndDiffed ] ifTrue: [ self commentContents ]) copy asText makeSelectorBoldIn: class! ! !Browser methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! selectedMessageName "Answer the message selector of the currently selected message, if any. Answer nil otherwise." | aList | messageListIndex = 0 ifTrue: [^ nil]. ^ (aList := self messageList) size >= messageListIndex ifTrue: [aList at: messageListIndex] ifFalse: [nil]! ! !Browser methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! selectedMessageName: aSelector "Make the given selector be the selected message name" | anIndex | anIndex := self messageList indexOf: aSelector. anIndex > 0 ifTrue: [self messageListIndex: anIndex]! ! !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: '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: 'sd 11/20/2005 21:26'! metaClassIndicated: trueOrFalse "Indicate whether browsing instance or class messages." metaClassIndicated := trueOrFalse. self setClassOrganizer. systemCategoryListIndex > 0 ifTrue: [self editSelection: (classListIndex = 0 ifTrue: [metaClassIndicated ifTrue: [#none] ifFalse: [#newClass]] ifFalse: [#editClass])]. messageCategoryListIndex := 0. messageListIndex := 0. contents := nil. self changed: #classSelectionChanged. self changed: #messageCategoryList. self changed: #messageList. self changed: #contents. self changed: #annotation. self decorateButtons ! ! !Browser methodsFor: 'metaclass' stamp: 'al 4/24/2004 11:47'! selectedClassOrMetaClass "Answer the selected class/trait or metaclass/classTrait." | cls | ^self metaClassIndicated ifTrue: [(cls _ self selectedClass) ifNil: [nil] ifNotNil: [cls classSide]] ifFalse: [self selectedClass]! ! !Browser methodsFor: 'metaclass'! selectedClassOrMetaClassName "Answer the selected class name or metaclass name." ^self selectedClassOrMetaClass name! ! !Browser methodsFor: 'metaclass' stamp: 'md 2/18/2006 16:31'! setClassOrganizer "Install whatever organization is appropriate" | theClass | classOrganizer _ nil. metaClassOrganizer _ nil. classListIndex = 0 ifTrue: [^ self]. theClass _ self selectedClass ifNil: [ ^self ]. classOrganizer _ theClass organization. metaClassOrganizer _ theClass classSide organization.! ! !Browser methodsFor: 'system category functions' stamp: 'sd 11/20/2005 21:26'! 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: [self systemCategoryList size] ifFalse: [oldIndex]). self changed: #systemCategoryList.! ! !Browser methodsFor: 'system category functions' stamp: 'brp 8/4/2003 21:38'! alphabetizeSystemCategories self okToChange ifFalse: [^ false]. systemOrganizer sortCategories. self systemCategoryListIndex: 0. self changed: #systemCategoryList. ! ! !Browser methodsFor: 'system category functions' stamp: 'sd 11/20/2005 21:26'! browseAllClasses "Create and schedule a new browser on all classes alphabetically." | newBrowser | newBrowser := HierarchyBrowser new initAlphabeticListing. self class 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: 'sd 11/20/2005 21:26'! buildSystemCategoryBrowserEditString: aString "Create and schedule a new system category browser with initial textual contents set to aString." | newBrowser | systemCategoryListIndex > 0 ifTrue: [newBrowser := self class new. newBrowser systemCategoryListIndex: systemCategoryListIndex. newBrowser setClass: self selectedClassOrMetaClass selector: self selectedMessageName. self class 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: 'nk 2/14/2004 15:09'! editSystemCategories "Retrieve the description of the class categories of the system organizer." self okToChange ifFalse: [^ self]. self systemCategoryListIndex: 0. self 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: 'al 1/13/2006 11:24'! findClass "Search for a class by name." | pattern foundClassOrTrait | self okToChange ifFalse: [^ self classNotFound]. pattern := UIManager default request: 'Class name or fragment?'. pattern isEmpty ifTrue: [^ self classNotFound]. foundClassOrTrait := Utilities classFromPattern: pattern withCaption: ''. foundClassOrTrait ifNil: [^ self classNotFound]. self selectCategoryForClass: foundClassOrTrait. self selectClass: foundClassOrTrait. ! ! !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: 'sd 11/20/2005 21:26'! 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: 'rr 7/10/2006 11:52'! systemCategoryMenu: aMenu ServiceGui browser: self classCategoryMenu: aMenu. ServiceGui onlyServices ifTrue: [^aMenu]. ^ aMenu labels: 'find class... (f) recent classes... (r) browse all browse printOut fileOut reorganize alphabetize update add item... rename... remove' lines: #(2 4 6 8) selections: #(findClass recent browseAllClasses buildSystemCategoryBrowser printOutSystemCategory fileOutSystemCategory editSystemCategories alphabetizeSystemCategories 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: 'md 3/3/2006 11:02'! selectedEnvironment "Answer the name of the selected system category or nil." systemCategoryListIndex = 0 ifTrue: [^nil]. ^ Smalltalk! ! !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: 'sd 11/20/2005 21:26'! 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. self 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 changed: #relabel. self contentsChanged! ! !Browser methodsFor: 'system category list' stamp: 'sd 11/20/2005 21:26'! 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 methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 14:19'! buildWith: builder "Create the ui for the browser" | windowSpec listSpec textSpec buttonSpec panelSpec max | windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec label: 'System Browser'. windowSpec children: OrderedCollection new. max := self wantsOptionalButtons ifTrue:[0.43] ifFalse:[0.5]. listSpec := builder pluggableListSpec new. listSpec model: self; list: #systemCategoryList; getIndex: #systemCategoryListIndex; setIndex: #systemCategoryListIndex:; menu: #systemCategoryMenu:; keyPress: #systemCatListKey:from:; frame: (0@0 corner: 0.25@max). windowSpec children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #classList; getIndex: #classListIndex; setIndex: #classListIndex:; menu: #classListMenu:; keyPress: #classListKey:from:; frame: (0.25@0 corner: 0.5@(max-0.1)). windowSpec children add: listSpec. panelSpec := builder pluggablePanelSpec new. panelSpec frame: (0.25@(max-0.1) corner: 0.5@max). panelSpec children: OrderedCollection new. windowSpec children addLast: panelSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: 'instance'; state: #instanceMessagesIndicated; action: #indicateInstanceMessages; frame: (0@0 corner: 0.4@1). panelSpec children addLast: buttonSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: '?'; state: #classCommentIndicated; action: #plusButtonHit; frame: (0.4@0 corner: 0.6@1). panelSpec children addLast: buttonSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: 'class'; state: #classMessagesIndicated; action: #indicateClassMessages; frame: (0.6@0 corner: 1@1). panelSpec children addLast: buttonSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #messageCategoryList; getIndex: #messageCategoryListIndex; setIndex: #messageCategoryListIndex:; menu: #messageCategoryMenu:; keyPress: #arrowKey:from:; frame: (0.5@0 corner: 0.75@max). windowSpec children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #messageList; getIndex: #messageListIndex; setIndex: #messageListIndex:; menu: #messageListMenu:shifted:; keyPress: #messageListKey:from:; frame: (0.75@0 corner: 1@max). windowSpec children add: listSpec. self wantsOptionalButtons ifTrue:[ panelSpec := self buildOptionalButtonsWith: builder. panelSpec frame: (0@0.43 corner: 1@0.5). windowSpec children add: panelSpec. ]. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #contents; setText: #contents:notifying:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0@0.5corner: 1@1). windowSpec children add: textSpec. ^builder build: windowSpec! ! !Browser methodsFor: 'traits' stamp: 'al 1/9/2006 18:29'! addSpecialMenu: aMenu aMenu addList: #( - ('new class' newClass) ('new trait' newTrait) -). self selectedClass notNil ifTrue: [ aMenu addList: #( ('add trait' addTrait) -) ]. aMenu addList: #(-). ^ aMenu! ! !Browser methodsFor: 'traits' stamp: 'al 1/9/2006 18:26'! addTrait | input trait | input := UIManager default request: 'add trait'. input isEmptyOrNil ifFalse: [ trait := Smalltalk classNamed: input. (trait isNil or: [trait isTrait not]) ifTrue: [ ^self inform: 'Input invalid. ' , input , ' does not exist or is not a trait']. self selectedClass addToComposition: trait. self contentsChanged]. ! ! !Browser methodsFor: 'traits' stamp: 'md 3/3/2006 10:58'! defineTrait: defString notifying: aController | defTokens keywdIx envt oldTrait newTraitName trait | oldTrait _ self selectedClassOrMetaClass. defTokens _ defString findTokens: Character separators. keywdIx _ defTokens findFirst: [:x | x = 'category']. envt _ self selectedEnvironment. keywdIx _ defTokens findFirst: [:x | x = 'named:']. newTraitName _ (defTokens at: keywdIx+1) copyWithoutAll: '#()'. ((oldTrait isNil or: [oldTrait baseTrait name asString ~= newTraitName]) and: [envt includesKey: newTraitName asSymbol]) ifTrue: ["Attempting to define new class/trait over existing one when not looking at the original one in this browser..." (self confirm: ((newTraitName , ' is an existing class/trait in this system. Redefining it might cause serious problems. Is this really what you want to do?') asText makeBoldFrom: 1 to: newTraitName size)) ifFalse: [^ false]]. trait _ Compiler evaluate: defString notifying: aController logged: true. ^(trait isKindOf: TraitBehavior) ifTrue: [ self changed: #classList. self classListIndex: (self classList indexOf: trait baseTrait name). self clearUserEditFlag; editClass. true] ifFalse: [ false ] ! ! !Browser methodsFor: 'traits' stamp: 'al 4/24/2004 11:48'! newClass (self selectedClassOrMetaClass notNil and: [self selectedClassOrMetaClass isTrait]) ifTrue: [self classListIndex: 0]. self editClass. editSelection _ #newClass. self contentsChanged! ! !Browser methodsFor: 'traits' stamp: 'al 4/24/2004 11:48'! newTrait self classListIndex: 0. self editClass. editSelection _ #newTrait. self contentsChanged! ! !Browser methodsFor: 'traits' stamp: 'al 4/24/2004 11:49'! removeNonLocalSelector: aSymbol | traits isAlias | traits _ self selectedClassOrMetaClass traitsProvidingSelector: aSymbol. isAlias _ self selectedClassOrMetaClass isLocalAliasSelector: aSymbol. isAlias ifTrue: [ self assert: traits size = 1. self selectedClassOrMetaClass removeAlias: aSymbol of: traits first] ifFalse: [ traits do: [:each | self selectedClassOrMetaClass addExclusionOf: aSymbol to: each ]] ! ! !Browser methodsFor: 'user interface' stamp: 'hpt 9/30/2004 20:51'! addModelItemsToWindowMenu: aMenu "Add model-related items to the window menu" super addModelItemsToWindowMenu: aMenu. SystemBrowser addRegistryMenuItemsTo: aMenu inAccountOf: self.! ! !Browser methodsFor: '*services-base' stamp: 'rr 6/28/2005 15:50'! browseReference: ref self okToChange ifTrue: [ self selectCategoryForClass: ref actualClass theNonMetaClass. self selectClass: ref actualClass theNonMetaClass . ref actualClass isMeta ifTrue: [self indicateClassMessages]. self changed: #classSelectionChanged. self selectMessageCategoryNamed: ref category. self selectedMessageName: ref methodSymbol. ]! ! !Browser methodsFor: '*services-base' stamp: 'rr 8/5/2005 10:03'! methodReference | cls sel | cls := self selectedClassOrMetaClass. sel := self selectedMessageName. cls isNil | sel isNil ifTrue: [^nil]. ^ MethodReference class: cls selector: sel! ! !Browser methodsFor: '*services-base' stamp: 'rr 3/10/2006 16:01'! optionalButtonRow ^ServiceGui browserButtonRow: self inlinedIn: super optionalButtonRow! ! !Browser methodsFor: '*services-base' stamp: 'rr 8/3/2005 17:16'! selectReference: ref self browseReference: ref! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Browser class instanceVariableNames: ''! !Browser class methodsFor: 'class initialization' stamp: 'hpt 8/5/2004 19:41'! initialize "Browser initialize" RecentClasses := OrderedCollection new. self registerInFlapsRegistry; registerInAppRegistry ! ! !Browser class methodsFor: 'class initialization' stamp: 'hpt 8/5/2004 19:41'! registerInAppRegistry "Register the receiver in the SystemBrowser AppRegistry" SystemBrowser register: self.! ! !Browser class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:32'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(#Browser #prototypicalToolWindow 'Browser' 'A Browser is a tool that allows you to view all the code of all the classes in the system' ) forFlapNamed: 'Tools']! ! !Browser class methodsFor: 'class initialization' stamp: 'hpt 8/5/2004 19:42'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self]. SystemBrowser unregister: self.! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:27'! fullOnClass: aClass "Open a new full browser set to class." | brow | brow := self new. brow setClass: aClass selector: nil. ^ self openBrowserView: (brow openEditString: nil) label: 'System Browser'! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! fullOnClass: aClass selector: aSelector "Open a new full browser set to class." | brow classToUse | classToUse := SystemBrowser default. brow := classToUse new. brow setClass: aClass selector: aSelector. ^ classToUse openBrowserView: (brow openEditString: nil) label: brow labelString! ! !Browser class methodsFor: 'instance creation' stamp: 'di 10/18/1999 22:03'! new ^super new systemOrganizer: SystemOrganization! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! newOnCategory: aCategory "Browse the system category of the given name. 7/13/96 sw" "Browser newOnCategory: 'Interface-Browser'" | newBrowser catList | newBrowser := self new. catList := newBrowser systemCategoryList. newBrowser systemCategoryListIndex: (catList indexOf: aCategory asSymbol ifAbsent: [^ self inform: 'No such category']). ^ self 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: 'sd 11/20/2005 21:28'! newOnClass: aClass label: aLabel "Open a new class browser on this class." | newBrowser | newBrowser := self new. newBrowser setClass: aClass selector: nil. ^ self openBrowserView: (newBrowser openOnClassWithEditString: nil) label: aLabel ! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! newOnClass: aClass selector: aSymbol "Open a new class browser on this class." | newBrowser | newBrowser := self new. newBrowser setClass: aClass selector: aSymbol. ^ self openBrowserView: (newBrowser openOnClassWithEditString: nil) label: 'Class Browser: ', aClass name ! ! !Browser class methodsFor: 'instance creation' stamp: 'md 3/10/2006 21:46'! open ^self openBrowser ! ! !Browser class methodsFor: 'instance creation' stamp: 'jcg 10/29/2003 23:11'! openBrowser "Create and schedule a BrowserView with default browser label. The view consists of five subviews, starting with the list view of system categories of SystemOrganization. The initial text view part is empty." | br | br := self new. ^ self openBrowserView: (br openEditString: nil) label: br defaultBrowserTitle. ! ! !Browser class methodsFor: 'instance creation' stamp: 'sps 3/9/2004 15:54'! 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]. ^ aBrowserView model ! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! 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 := self new) setClass: aBehavior selector: aSymbol. ^ self openBrowserView: (newBrowser openMessageEditString: aString) label: newBrowser selectedClassOrMetaClassName , ' ' , newBrowser selectedMessageName ! ! !Browser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! prototypicalToolWindow "Answer an example of myself seen in a tool window, for the benefit of parts-launching tools" | aWindow | aWindow := self new openAsMorphEditing: nil. aWindow setLabel: 'System Browser'; applyModelExtent. ^ aWindow! ! !Browser class methodsFor: 'instance creation' stamp: 'nk 6/2/2004 12:55'! systemOrganizer: anOrganizer ^(super new) systemOrganizer: anOrganizer; yourself! ! !Browser class methodsFor: 'window color' stamp: 'sw 2/26/2002 13:46'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Browser' brightColor: #lightGreen pastelColor: #paleGreen helpMessage: 'The standard "system browser" tool that allows you to browse through all the code in the system'! ! PluggableTextMorph subclass: #BrowserCommentTextMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser'! !BrowserCommentTextMorph commentStamp: '' prior: 0! I am a PluggableTextMorph that knows enough to make myself invisible when necessary.! !BrowserCommentTextMorph methodsFor: 'accessing' stamp: 'nk 2/15/2004 14:12'! lowerPane "Answer the AlignmentMorph that I live beneath" ^self valueOfProperty: #browserLowerPane! ! !BrowserCommentTextMorph methodsFor: 'accessing' stamp: 'nk 2/15/2004 14:07'! window ^self owner ifNil: [ self valueOfProperty: #browserWindow ].! ! !BrowserCommentTextMorph methodsFor: 'displaying' stamp: 'nk 2/15/2004 13:41'! hideOrShowPane (self model editSelection == #editClass) ifTrue: [ self showPane ] ifFalse: [ self hidePane ]! ! !BrowserCommentTextMorph methodsFor: 'displaying' stamp: 'sd 11/20/2005 21:26'! hidePane | win | self window ifNotNilDo: [:window | window removePaneSplitters]. self lowerPane ifNotNilDo: [ :lp | lp layoutFrame bottomFraction: self layoutFrame bottomFraction. lp layoutFrame bottomOffset: SystemWindow borderWidth negated]. win := self window ifNil: [ ^self ]. self delete. win updatePanesFromSubmorphs. win addPaneSplitters! ! !BrowserCommentTextMorph methodsFor: 'displaying' stamp: 'sd 11/20/2005 21:26'! showPane owner ifNil: [ | win | win := self window ifNil: [ ^self ]. win addMorph: self fullFrame: self layoutFrame. win updatePanesFromSubmorphs ]. self lowerPane ifNotNilDo: [ :lp | lp layoutFrame bottomFraction: self layoutFrame topFraction ]. self window ifNotNilDo: [:win | win addPaneSplitters]! ! !BrowserCommentTextMorph methodsFor: 'updating' stamp: 'nk 2/15/2004 14:11'! noteNewOwner: win super noteNewOwner: win. self setProperty: #browserWindow toValue: win. win ifNil: [ ^self ]. win setProperty: #browserClassCommentPane toValue: self. self setProperty: #browserLowerPane toValue: (win submorphThat: [ :m | m isAlignmentMorph and: [ m layoutFrame bottomFraction = 1 ]] ifNone: []). ! ! !BrowserCommentTextMorph methodsFor: 'updating' stamp: 'nk 2/15/2004 13:42'! update: anAspect super update: anAspect. anAspect == #editSelection ifFalse: [ ^self ]. self hideOrShowPane! ! ServiceProvider subclass: #BrowserProvider instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Services-Base-Providers'! !BrowserProvider commentStamp: 'rr 7/10/2006 15:17' prior: 0! I define the default categories of services dealing with browsing: - the class category menu (service identifier: browserClassCategoryMenu) - the class menu (browserClassMenu) - the method category menu (browserMethodCategoryMenu) - the browser method menu (browserMethodMenu) - the browser button bar (browserButtonBar) - the browser code pane/selection menu (browserCodePaneMenu)! !BrowserProvider methodsFor: 'saved preferences'! browserClassMenushortcut ^ #(#'Shortcut for browserClassMenu:' '' 1000 )! ! !BrowserProvider methodsFor: 'saved preferences'! browserMethodMenushortcut ^ #(#'Shortcut for browserMethodMenu:' '' 1000 )! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 10/23/2005 14:42'! browser ^ ServiceCategory text: 'Browser' button: 'browser' description: 'The browser menus'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 18:59'! browserButtonBar ^ ServiceCategory text:'button bar' button:'button' description:'the browser button bar'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 19:00'! browserClassCategoryMenu ^ ServiceCategory text:'Class Category' button:'class cat' description:'The browser class category menu'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 19:00'! browserClassMenu ^ ServiceCategory text:'Class' button:'class' description:'The browser class menu'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 19:01'! browserCodePaneMenu ^ ServiceCategory text: 'Code Pane' button: 'pane' description: 'The browser code pane menu'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 19:00'! browserMethodCategoryMenu ^ ServiceCategory text:'Method Category' button:'method cat' description:'The browser method menu'! ! !BrowserProvider methodsFor: 'services' stamp: 'rr 1/9/2006 19:01'! browserMethodMenu ^ ServiceCategory text:'Method' button:'method' description:'The browser method menu'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BrowserProvider class instanceVariableNames: ''! !BrowserProvider class methodsFor: 'initialize-release' stamp: 'rr 1/10/2006 12:53'! initialize ServiceRegistry current buildProvider: self new! ! TextRequestor subclass: #BrowserRequestor instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Services-Base-Requestors'! !BrowserRequestor commentStamp: 'rr 7/10/2006 15:24' prior: 0! I am a requestor specialized to fetch information in a Browser. I can ask a browser its selected class and selected method for example. If the RB is installed too, I can also fetch ast nodes in the browser's selected method. I am the default requestor for CodeHolder and it's subclasses. To be integrated with services, alternative browsers, such as the OmniBrowser and Whisker should define a specialized requestor subclassing this one. A few core messages would need to be redefined, such as getClass, getMessage ... to be adapted to the browser's data structures. Only a few of them have to be overridden, the majority of the requests rely on a few base ones.! !BrowserRequestor methodsFor: 'initialize-release' stamp: 'rr 8/27/2005 15:52'! browser: b self model: b! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/27/2005 15:43'! getBrowser ^ self getModel! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/27/2005 15:51'! getClass ^ self getBrowser selectedClassOrMetaClass! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 9/11/2004 14:06'! getInitializingExpressionForTheNewParameter ^ FillInTheBlank request: 'enter default parameter code' initialAnswer: '42'! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 1/9/2006 11:57'! getNewSelectorName ^ FillInTheBlank request: 'enter the new selector name' initialAnswer: self getSelector! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 9/11/2004 16:50'! getNewVariableName ^ FillInTheBlank request: 'Enter the new variable name' initialAnswer: 'foo'! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/4/2005 14:41'! getPackage self getSelector ifNil: [ ^ PackageInfo named:( self getClass ifNil: [self getSystemCategory] ifNotNilDo: [:c | c category copyUpTo: $-])]. ^ PackageOrganizer default packageOfMethod: (MethodReference class: self getClass selector: self getSelector) ifNone: [PackageInfo named: (self getClass category copyUpTo: $-)] ! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 5/31/2004 22:10'! getPackageForCategory "answers a packageinfo for the current class category" ^ PackageInfo named: self getClass theNonMetaClass category! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 5/31/2004 22:10'! getPackageForCategoryName "answers a packageinfo for the current class category" ^ self getPackageForCategory packageName! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 5/31/2004 22:10'! getPackageName ^ self getPackage packageName! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 1/9/2006 19:27'! getPackageProvider | provs classes | provs := ServiceProvider registeredProviders. classes := self getPackage classes. ^ classes detect: [:e | provs includes: e] ifNone: [ServiceProvider newProviderFor: self getPackageName]! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/27/2005 15:52'! getSelection self getBrowser selectedInterval ifEmpty: [^super getSelection]. ^ self getBrowser selectedInterval! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 1/9/2006 11:58'! getSelector | s | s := self getBrowser selectedMessageName. ^ s ifNil: [super getSelector] ifNotNil: [s]! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 10/11/2005 15:06'! getSelectorCollection self caption: 'enter selector list'. ^ self getSymbolCollection ! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/27/2005 15:51'! getSelectorName ^ self getBrowser selectedMessageName! ! !BrowserRequestor methodsFor: 'requests' stamp: 'rr 8/27/2005 15:51'! getSystemCategory ^ self getBrowser selectedSystemCategoryName ! ! 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! ! PluggableCanvas subclass: #BufferedCanvas instanceVariableNames: 'remote previousVersion lastTick dirtyRect mirrorOfScreen' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Morphic-Remote'! !BufferedCanvas methodsFor: 'accessing' stamp: 'RAA 11/7/2000 13:04'! clipRect ^0@0 extent: 99999@99999 ! ! !BufferedCanvas methodsFor: 'accessing' stamp: 'RAA 7/31/2000 22:36'! extent ^Display extent! ! !BufferedCanvas methodsFor: 'accessing' stamp: 'RAA 7/31/2000 22:36'! origin ^0@0! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/8/2000 15:04'! asBufferedCanvas ^self! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! connection: connection clipRect: newClipRect transform: transform remoteCanvas: remoteCanvas remote := remoteCanvas. lastTick := 0. ! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'RAA 11/8/2000 15:06'! purgeOutputQueue! ! !BufferedCanvas methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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. ! ! !BufferedCanvas methodsFor: 'drawing-general' stamp: 'RAA 7/31/2000 20:32'! drawMorph: x ! ! !BufferedCanvas methodsFor: 'drawing-support' stamp: 'RAA 7/31/2000 20:44'! clipBy: aRectangle during: aBlock ! ! !BufferedCanvas methodsFor: 'Nebraska/embeddedWorlds' stamp: 'RAA 11/7/2000 13:54'! displayIsFullyUpdated self checkIfTimeToDisplay! ! !BufferedCanvas methodsFor: 'other' stamp: 'sd 11/20/2005 21:25'! 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]. ! ! Switch subclass: #Button instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ST80-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! ! SymbolListType subclass: #ButtonPhaseType instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Type Vocabularies'! !ButtonPhaseType methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:39'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" super initialize. self vocabularyName: #ButtonPhase. symbols := #(buttonDown whilePressed buttonUp)! ! !ButtonPhaseType methodsFor: 'queries' stamp: 'mir 7/15/2004 10:35'! representsAType "Answer whether this vocabulary represents an end-user-sensible data type" ^true! ! !ButtonPhaseType methodsFor: '*eToys-color' stamp: 'sw 9/27/2001 17:20'! typeColor "Answer the color for tiles to be associated with objects of this type" ^ self subduedColorFromTriplet: #(0.806 1.0 0.806) ! ! Object subclass: #ButtonProperties instanceVariableNames: 'target actionSelector arguments actWhen wantsRolloverIndicator mouseDownTime nextTimeToFire visibleMorph delayBetweenFirings mouseOverHaloWidth mouseOverHaloColor mouseDownHaloWidth mouseDownHaloColor stateCostumes currentLook' classVariableNames: '' poolDictionaries: '' category: 'EToys-Buttons'! !ButtonProperties commentStamp: '' prior: 0! ButtonProperties test1 ButtonProperties test2 ButtonProperties test3 ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/9/2001 09:43'! actWhen ^ actWhen! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/9/2001 09:43'! actWhen: condition (#(buttonDown mouseDown) includes: condition) ifTrue: [ actWhen _ #mouseDown ]. (#(buttonUp mouseUp) includes: condition) ifTrue: [ actWhen _ #mouseUp ]. (#(whilePressed mouseStillDown) includes: condition) ifTrue: [ actWhen _ #mouseStillDown ]. self setEventHandlers: true.! ! !ButtonProperties methodsFor: 'accessing'! actionSelector ^ actionSelector ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 08:46'! actionSelector: aSymbolOrString aSymbolOrString isEmptyOrNil ifTrue: [^actionSelector _ nil]. aSymbolOrString = 'nil' ifTrue: [^actionSelector _ nil]. actionSelector _ aSymbolOrString asSymbol. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/15/2001 09:35'! addTextToButton: aStringOrText | tm existing | existing _ self currentTextMorphsInButton. existing do: [ :x | x delete]. aStringOrText ifNil: [^self]. tm _ TextMorph new contents: aStringOrText. tm fullBounds; lock; align: tm center with: visibleMorph center; setProperty: #textAddedByButtonProperties toValue: true; setToAdhereToEdge: #center. "maybe the user would like personal control here" "visibleMorph extent: (tm extent * 1.5) rounded." visibleMorph addMorphFront: tm. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 07:35'! adjustPositionsAfterSizeChange "re-center label, etc??"! ! !ButtonProperties methodsFor: 'accessing'! arguments ^ arguments ! ! !ButtonProperties methodsFor: 'accessing'! arguments: aCollection arguments _ aCollection asArray copy. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/9/2001 11:40'! bringUpToDate self establishEtoyLabelWording ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/15/2001 09:18'! currentLook ^currentLook ifNil: [currentLook _ #normal]! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:18'! currentTextInButton | existing | existing _ self currentTextMorphsInButton. existing isEmpty ifTrue: [^nil]. ^existing first ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:17'! currentTextMorphsInButton ^visibleMorph submorphsSatisfying: [ :x | x hasProperty: #textAddedByButtonProperties ] ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'yo 11/30/2005 14:20'! establishEtoyLabelWording "Set the label wording, unless it has already been manually edited" | itsName | self isTileScriptingElement ifFalse: [^self]. itsName _ target externalName. self addTextToButton: itsName, ' ', arguments first. visibleMorph setBalloonText: ('click to run the script "{1}" in player named "{2}"' translated format: {arguments first. itsName}). ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 19:01'! figureOutScriptSelector self halt! ! !ButtonProperties methodsFor: 'accessing' stamp: 'nk 8/29/2004 17:16'! isTileScriptingElement actionSelector == #runScript: ifFalse: [^false]. arguments isEmptyOrNil ifTrue: [^false]. ^target isPlayerLike! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:19'! lockAnyText self currentTextMorphsInButton do: [ :x | x lock: true].! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:15'! mouseDownHaloColor ^mouseDownHaloColor! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:15'! mouseDownHaloColor: x mouseDownHaloColor _ x! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:31'! mouseDownHaloWidth ^mouseDownHaloWidth! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:31'! mouseDownHaloWidth: x mouseDownHaloWidth _ x! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:43'! mouseDownLook: aFormOrMorph self setLook: #mouseDown to: aFormOrMorph ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:43'! mouseEnterLook: aFormOrMorph self setLook: #mouseEnter to: aFormOrMorph ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:16'! mouseOverHaloColor ^mouseOverHaloColor! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:16'! mouseOverHaloColor: x mouseOverHaloColor _ x! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:30'! mouseOverHaloWidth ^mouseOverHaloWidth! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 16:31'! mouseOverHaloWidth: x mouseOverHaloWidth _ x! ! !ButtonProperties methodsFor: 'accessing' stamp: 'gm 2/22/2003 14:53'! privateSetLook: aSymbol to: aFormOrMorph | f | f := (aFormOrMorph isForm) ifTrue: [aFormOrMorph] ifFalse: [aFormOrMorph imageForm]. self stateCostumes at: aSymbol put: f! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/10/2001 13:57'! setEventHandlers: enabled enabled ifTrue: [ visibleMorph on: #mouseDown send: #mouseDown: to: self. visibleMorph on: #mouseStillDown send: #mouseStillDown: to: self. visibleMorph on: #mouseUp send: #mouseUp: to: self. visibleMorph on: #mouseEnter send: #mouseEnter: to: self. visibleMorph on: #mouseLeave send: #mouseLeave: to: self. ] ifFalse: [ #(mouseDown mouseStillDown mouseUp mouseEnter mouseLeave) do: [ :sel | visibleMorph on: sel send: nil to: nil ]. ]. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/15/2001 09:14'! setLook: aSymbol to: aFormOrMorph (self stateCostumes includesKey: #normal) ifFalse: [ self privateSetLook: #normal to: visibleMorph. ]. self privateSetLook: aSymbol to: aFormOrMorph. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:30'! stateCostumes ^stateCostumes ifNil: [stateCostumes _ Dictionary new]! ! !ButtonProperties methodsFor: 'accessing'! target ^ target ! ! !ButtonProperties methodsFor: 'accessing'! target: anObject target _ anObject ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/14/2001 18:19'! unlockAnyText self currentTextMorphsInButton do: [ :x | x lock: false].! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 15:43'! visibleMorph: x visibleMorph ifNotNil: [self setEventHandlers: false]. visibleMorph _ x. visibleMorph ifNotNil: [self setEventHandlers: true]. ! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/8/2001 09:09'! wantsRolloverIndicator ^wantsRolloverIndicator ifNil: [false]! ! !ButtonProperties methodsFor: 'accessing' stamp: 'RAA 3/10/2001 13:59'! wantsRolloverIndicator: aBoolean wantsRolloverIndicator _ aBoolean. wantsRolloverIndicator ifTrue: [ self setEventHandlers: true. ].! ! !ButtonProperties methodsFor: 'copying' stamp: 'jm 7/28/97 11:52'! updateReferencesUsing: aDictionary "If the arguments array points at a morph we are copying, then point at the new copy. And also copies the array, which is important!!" super updateReferencesUsing: aDictionary. arguments _ arguments collect: [:old | aDictionary at: old ifAbsent: [old]]. ! ! !ButtonProperties methodsFor: 'copying' stamp: 'tk 1/6/1999 17:55'! 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. target _ deepCopier references at: target ifAbsent: [target]. arguments _ arguments collect: [:each | deepCopier references at: each ifAbsent: [each]]. ! ! !ButtonProperties methodsFor: 'copying' stamp: 'RAA 3/16/2001 08:21'! 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. "target _ target. Weakly copied" "actionSelector _ actionSelector. a Symbol" "arguments _ arguments. All weakly copied" actWhen _ actWhen veryDeepCopyWith: deepCopier. "oldColor _ oldColor veryDeepCopyWith: deepCopier." visibleMorph _ visibleMorph. "I guess this will have been copied already if needed" delayBetweenFirings _ delayBetweenFirings. mouseDownHaloColor _ mouseDownHaloColor. stateCostumes _ stateCostumes veryDeepCopyWith: deepCopier. currentLook _ currentLook.! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/16/2001 08:28'! addMouseOverHalo self wantsRolloverIndicator ifTrue: [ visibleMorph addMouseActionIndicatorsWidth: mouseOverHaloWidth color: mouseOverHaloColor. ]. ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/8/2001 09:29'! delayBetweenFirings ^delayBetweenFirings! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/8/2001 09:55'! delayBetweenFirings: millisecondsOrNil delayBetweenFirings _ millisecondsOrNil! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/15/2001 09:21'! displayCostume: aSymbol self currentLook == aSymbol ifTrue: [^true]. self stateCostumes at: aSymbol ifPresent: [ :aForm | currentLook _ aSymbol. visibleMorph wearCostume: aForm. ^true ]. ^false ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/9/2001 08:58'! doButtonAction self doButtonAction: nil! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/9/2001 17:08'! doButtonAction: evt | arity | target ifNil: [^self]. actionSelector ifNil: [^self]. arguments ifNil: [arguments _ #()]. Cursor normal showWhile: [ arity _ actionSelector numArgs. arity = arguments size ifTrue: [ target perform: actionSelector withArguments: arguments ]. arity = (arguments size + 1) ifTrue: [ target perform: actionSelector withArguments: {evt},arguments ]. arity = (arguments size + 2) ifTrue: [ target perform: actionSelector withArguments: {evt. visibleMorph},arguments ]. ]! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/14/2001 19:01'! editButtonsScript: evt "The user has touched my Scriptor halo-handle. Bring up a Scriptor on the script of the button." | cardsPasteUp cardsPlayer anEditor scriptSelector | cardsPasteUp _ self pasteUpMorph. (cardsPlayer _ cardsPasteUp assuredPlayer) assureUniClass. scriptSelector _ self figureOutScriptSelector. scriptSelector ifNil: [ scriptSelector _ cardsPasteUp scriptSelectorToTriggerFor: self. anEditor _ cardsPlayer newTextualScriptorFor: scriptSelector. evt hand attachMorph: anEditor. ^self ]. (cardsPlayer class selectors includes: scriptSelector) ifTrue: [ anEditor _ cardsPlayer scriptEditorFor: scriptSelector. evt hand attachMorph: anEditor. ^self ]. "Method somehow got removed; I guess we start aftresh" scriptSelector _ nil. ^ self editButtonsScript! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/14/2001 18:40'! mouseDown: evt self displayCostume: #mouseDown. mouseDownTime _ Time millisecondClockValue. nextTimeToFire _ nil. delayBetweenFirings ifNotNil: [ nextTimeToFire _ mouseDownTime + delayBetweenFirings. ]. self wantsRolloverIndicator ifTrue: [ visibleMorph addMouseActionIndicatorsWidth: mouseDownHaloWidth color: mouseDownHaloColor. ]. actWhen == #mouseDown ifFalse: [^self]. (visibleMorph containsPoint: evt cursorPoint) ifFalse: [^self]. self doButtonAction: evt. "===== aMorph . now _ Time millisecondClockValue. oldColor _ color. actWhen == #buttonDown ifTrue: [self doButtonAction] ifFalse: [ self updateVisualState: evt; refreshWorld]. dt _ Time millisecondClockValue - now max: 0. dt < 200 ifTrue: [(Delay forMilliseconds: 200-dt) wait]. self mouseStillDown: evt. ====="! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/16/2001 08:29'! mouseEnter: evt self displayCostume: #mouseEnter. self addMouseOverHalo. ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/14/2001 18:39'! mouseLeave: evt self displayCostume: #normal. visibleMorph deleteAnyMouseActionIndicators. ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/8/2001 07:57'! mouseMove: evt actWhen == #mouseDown ifTrue: [^ self]. self updateVisualState: evt.! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/9/2001 08:57'! mouseStillDown: evt (visibleMorph containsPoint: evt cursorPoint) ifFalse: [^self]. nextTimeToFire ifNil: [^self]. nextTimeToFire <= Time millisecondClockValue ifTrue: [ self doButtonAction: evt. nextTimeToFire _ Time millisecondClockValue + self delayBetweenFirings. ^self ]. ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/16/2001 08:29'! mouseUp: evt (self displayCostume: #mouseEnter) ifFalse: [self displayCostume: #normal]. self addMouseOverHalo. ! ! !ButtonProperties methodsFor: 'events' stamp: 'RAA 3/9/2001 12:27'! replaceVisibleMorph: aNewMorph | old oldOwner oldText | old _ visibleMorph. oldText _ self currentTextInButton. self visibleMorph: nil. old buttonProperties: nil. aNewMorph buttonProperties: self. self visibleMorph: aNewMorph. self addTextToButton: oldText. oldOwner _ old owner ifNil: [^self]. oldOwner replaceSubmorph: old by: aNewMorph.! ! !ButtonProperties methodsFor: 'initialization' stamp: 'ar 3/17/2001 20:12'! adaptToWorld: aWorld super adaptToWorld: aWorld. target _ target adaptedToWorld: aWorld.! ! !ButtonProperties methodsFor: 'initialization' stamp: 'RAA 3/9/2001 09:47'! initialize wantsRolloverIndicator _ false. delayBetweenFirings _ nil. mouseOverHaloWidth _ 10. mouseOverHaloColor _ Color blue alpha: 0.3. mouseDownHaloWidth _ 15. mouseDownHaloColor _ Color blue alpha: 0.7. arguments _ #().! ! !ButtonProperties methodsFor: 'menu' stamp: 'yo 3/16/2005 20:58'! setActWhen | selections | selections _ #(mouseDown mouseUp mouseStillDown). actWhen _ (SelectionMenu labels: (selections collect: [:t | t translated]) selections: selections) startUpWithCaption: 'Choose one of the following conditions' translated ! ! !ButtonProperties methodsFor: 'menu' stamp: 'yo 3/16/2005 20:53'! setActionSelector | newSel | newSel _ FillInTheBlank request: 'Please type the selector to be sent to the target when this button is pressed' translated initialAnswer: actionSelector. newSel isEmpty ifFalse: [self actionSelector: newSel]. ! ! !ButtonProperties methodsFor: 'menu' stamp: 'yo 3/14/2005 13:07'! setArguments | s newArgs newArgsArray | s _ WriteStream on: ''. arguments do: [:arg | arg printOn: s. s nextPutAll: '. ']. newArgs _ FillInTheBlank request: 'Please type the arguments to be sent to the target when this button is pressed separated by periods' translated initialAnswer: s contents. newArgs isEmpty ifFalse: [ newArgsArray _ Compiler evaluate: '{', newArgs, '}' for: self logged: false. self arguments: newArgsArray]. ! ! !ButtonProperties methodsFor: 'menu'! setLabel | newLabel | newLabel _ FillInTheBlank request: 'Please a new label for this button' initialAnswer: self label. newLabel isEmpty ifFalse: [self label: newLabel]. ! ! !ButtonProperties methodsFor: 'menu' stamp: 'di 12/20/1998 16:55'! setPageSound: event ^ target menuPageSoundFor: self event: event! ! !ButtonProperties methodsFor: 'menu' stamp: 'di 12/20/1998 16:55'! setPageVisual: event ^ target menuPageVisualFor: self event: event! ! !ButtonProperties methodsFor: 'menu' stamp: 'dgd 2/22/2003 18:52'! setTarget: evt | rootMorphs | rootMorphs := self world rootMorphsAt: evt hand targetOffset. target := rootMorphs size > 1 ifTrue: [rootMorphs second] ifFalse: [nil]! ! !ButtonProperties methodsFor: 'visual properties' stamp: 'RAA 3/8/2001 14:24'! updateVisualState: evt " oldColor ifNil: [^self]. self color: ((self containsPoint: evt cursorPoint) ifTrue: [oldColor mixed: 1/2 with: Color white] ifFalse: [oldColor])"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ButtonProperties class instanceVariableNames: ''! !ButtonProperties class methodsFor: 'as yet unclassified' stamp: 'RAA 3/8/2001 17:29'! ellipticalButtonWithText: aStringOrText | m prop | m _ EllipseMorph new. prop _ m ensuredButtonProperties. prop target: #(1 2 3); actionSelector: #inspect; actWhen: #mouseUp; addTextToButton: aStringOrText; wantsRolloverIndicator: true. ^m! ! !ButtonProperties class methodsFor: 'as yet unclassified' stamp: 'RAA 3/8/2001 08:31'! test1 | m prop | m _ EllipseMorph new. prop _ m ensuredButtonProperties. prop target: #(1 2 3); actionSelector: #inspect; actWhen: #mouseUp. m openInWorld.! ! !ButtonProperties class methodsFor: 'as yet unclassified' stamp: 'RAA 3/8/2001 08:41'! test2 (self ellipticalButtonWithText: 'Hello world') openInWorld.! ! !ButtonProperties class methodsFor: 'as yet unclassified' stamp: 'nb 6/17/2003 12:25'! test3 | m | (m _ self ellipticalButtonWithText: 'Hello world') openInWorld. m ensuredButtonProperties target: Beeper; actionSelector: #beep; delayBetweenFirings: 1000.! ! !ButtonProperties class methodsFor: 'printing' stamp: 'sw 2/16/98 01:31'! defaultNameStemForInstances ^ 'button'! ! WidgetStub subclass: #ButtonStub instanceVariableNames: 'enabled' classVariableNames: '' poolDictionaries: '' category: 'ToolBuilder-SUnit'! !ButtonStub methodsFor: 'events' stamp: 'cwp 4/22/2005 22:01'! eventAccessors ^ #(label color state enabled)! ! !ButtonStub methodsFor: 'simulating' stamp: 'stephaneducasse 2/3/2006 22:32'! click | action | action := spec action. action isSymbol ifTrue: [self model perform: action] ifFalse: [action value]! ! !ButtonStub methodsFor: 'simulating' stamp: 'cwp 4/22/2005 22:44'! color ^ self model perform: spec color! ! !ButtonStub methodsFor: 'simulating' stamp: 'cwp 7/14/2006 11:09'! isEnabled enabled ifNil: [enabled _ spec model perform: spec enabled]. ^ enabled! ! 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: 'ar 4/12/2005 17:35'! asWideString ^ WideString fromByteArray: self. ! ! !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: 'ar 3/3/2001 16:17'! byteSize ^self size! ! !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: 'comparing' stamp: 'SqR 8/13/2002 10:52'! hash "#hash is implemented, because #= is implemented" ^self class hashBytes: self startingWith: self species hash! ! !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: 'platform independent access' stamp: 'jmb 12/3/2004 14:54'! doubleAt: index bigEndian: bool "Return a 64 bit float starting from the given byte index" | w1 w2 dbl | w1 _ self unsignedLongAt: index bigEndian: bool. w2 _ self unsignedLongAt: index + 4 bigEndian: bool. dbl _ Float new: 2. bool ifTrue: [dbl basicAt: 1 put: w1. dbl basicAt: 2 put: w2] ifFalse: [dbl basicAt: 1 put: w2. dbl basicAt: 2 put: w1]. ^ dbl! ! !ByteArray methodsFor: 'platform independent access' stamp: 'jmb 12/3/2004 14:54'! doubleAt: index put: value bigEndian: bool "Store a 64 bit float starting from the given byte index" | w1 w2 | bool ifTrue: [w1 _ value basicAt: 1. w2 _ value basicAt: 2] ifFalse: [w1 _ value basicAt: 2. w2 _ value basicAt: 1]. self unsignedLongAt: index put: w1 bigEndian: bool. self unsignedLongAt: index + 4 put: w2 bigEndian: bool. ^ value! ! !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 8/2/2003 19:29'! 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). b0 < 0 ifTrue:[b0 := 256 + b0]. 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: 'zip archive' stamp: 'nk 8/21/2004 15:23'! lastIndexOfPKSignature: aSignature "Answer the last index in me where aSignature (4 bytes long) occurs, or 0 if not found" | a b c d | a _ aSignature first. b _ aSignature second. c _ aSignature third. d _ aSignature fourth. (self size - 3) to: 1 by: -1 do: [ :i | (((self at: i) = a) and: [ ((self at: i + 1) = b) and: [ ((self at: i + 2) = c) and: [ ((self at: i + 3) = d) ]]]) ifTrue: [ ^i ] ]. ^0! ! !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'! 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 class instanceVariableNames: ''! !ByteArray class methodsFor: 'byte based hash' stamp: 'SqR 8/21/2002 16:21'! hashBytes: aByteArray startingWith: speciesHash "Answer the hash of a byte-indexed collection, using speciesHash as the initial value. See SmallInteger>>hashMultiply. The primitive should be renamed at a suitable point in the future" | byteArraySize hash low | self var: #aHash declareC: 'int speciesHash'. self var: #aByteArray declareC: 'unsigned char *aByteArray'. byteArraySize _ aByteArray size. hash _ speciesHash bitAnd: 16rFFFFFFF. 1 to: byteArraySize do: [:pos | hash _ hash + (aByteArray basicAt: pos). "Begin hashMultiply" low _ hash bitAnd: 16383. hash _ (16r260D * low + ((16r260D * (hash bitShift: -14) + (16r0065 * low) bitAnd: 16383) * 16384)) bitAnd: 16r0FFFFFFF. ]. ^ hash! ! TestCase subclass: #ByteArrayBugz instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !ByteArrayBugz methodsFor: 'as yet unclassified' stamp: 'ar 8/2/2003 19:28'! testByteArrayLongAt | ba value | ba := ByteArray new: 4. value := -1. self shouldnt:[ba longAt: 1 put: value bigEndian: true] raise: Error. self assert: (ba longAt: 1 bigEndian: true) = value. self shouldnt:[ba longAt: 1 put: value bigEndian: false] raise: Error. self assert: (ba longAt: 1 bigEndian: false) = value. ! ! FlattenEncoder subclass: #ByteEncoder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Postscript Filters'! !ByteEncoder methodsFor: 'accessing' stamp: 'MPW 1/1/1901 22:45'! numberDefaultBase ^self class numberDefaultBase. ! ! !ByteEncoder methodsFor: 'filter streaming' stamp: 'MPW 1/1/1901 01:33'! elementSeparator ^' '.! ! !ByteEncoder methodsFor: 'writing' stamp: 'MPW 1/1/1901 15:17'! cr ^target cr. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'nk 12/31/2003 16:01'! nextPut: encodedObject "pass through for stream compatibility" ^target nextPut: encodedObject. ! ! !ByteEncoder methodsFor: 'writing' stamp: 'nk 12/31/2003 16:00'! nextPutAll: encodedObject "pass through for stream compatibility" ^target nextPutAll: encodedObject. ! ! !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. ! ! String variableByteSubclass: #ByteString instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Strings'! !ByteString commentStamp: '' prior: 0! This class represents the array of 8 bit wide characters. ! !ByteString methodsFor: 'accessing' stamp: 'yo 8/26/2002 20:33'! at: index "Primitive. Answer the Character stored in the field of the receiver indexed by the argument. Fail if the index argument is not an Integer or is out of bounds. Essential. See Object documentation whatIsAPrimitive." ^ Character value: (super at: index)! ! !ByteString methodsFor: 'accessing' stamp: 'ar 4/12/2005 17:33'! at: index put: aCharacter "Primitive. Store the Character in the field of the receiver indicated by the index. Fail if the index is not an Integer or is out of bounds, or if the argument is not a Character. Essential. See Object documentation whatIsAPrimitive." aCharacter isCharacter ifFalse:[^self errorImproperStore]. aCharacter isOctetCharacter ifFalse:[ "Convert to WideString" self becomeForward: (WideString from: self). ^self at: index put: aCharacter. ]. index isInteger ifTrue: [self errorSubscriptBounds: index] ifFalse: [self errorNonIntegerIndex]! ! !ByteString methodsFor: 'accessing' stamp: 'ar 12/27/1999 13:44'! byteAt: index ^(self at: index) asciiValue! ! !ByteString methodsFor: 'accessing' stamp: 'ar 12/27/1999 13:44'! byteAt: index put: value self at: index put: value asCharacter. ^value! ! !ByteString methodsFor: 'accessing' stamp: 'ar 3/3/2001 16:17'! byteSize ^self size! ! !ByteString methodsFor: 'accessing' stamp: 'ar 4/12/2005 17:33'! 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." replacement class == WideString ifTrue: [ self becomeForward: (WideString from: self). ]. super replaceFrom: start to: stop with: replacement startingAt: repStart. ! ! !ByteString methodsFor: 'comparing' stamp: 'bf 8/31/2004 13:50'! findSubstring: key in: body startingAt: start matchTable: matchTable "Answer the index in the string body at which the substring key first occurs, at or beyond start. The match is determined using matchTable, which can be used to effect, eg, case-insensitive matches. If no match is found, zero will be returned. The algorithm below is not optimum -- it is intended to be translated to C which will go so fast that it wont matter." | index | self var: #key declareC: 'unsigned char *key'. self var: #body declareC: 'unsigned char *body'. self var: #matchTable declareC: 'unsigned char *matchTable'. key size = 0 ifTrue: [^ 0]. start to: body size - key size + 1 do: [:startIndex | index _ 1. [(matchTable at: (body at: startIndex+index-1) asciiValue + 1) = (matchTable at: (key at: index) asciiValue + 1)] whileTrue: [index = key size ifTrue: [^ startIndex]. index _ index+1]]. ^ 0 " ' ' findSubstring: 'abc' in: 'abcdefabcd' startingAt: 1 matchTable: CaseSensitiveOrder 1 ' ' findSubstring: 'abc' in: 'abcdefabcd' startingAt: 2 matchTable: CaseSensitiveOrder 7 ' ' findSubstring: 'abc' in: 'abcdefabcd' startingAt: 8 matchTable: CaseSensitiveOrder 0 ' ' findSubstring: 'abc' in: 'abcdefABcd' startingAt: 2 matchTable: CaseSensitiveOrder 0 ' ' findSubstring: 'abc' in: 'abcdefABcd' startingAt: 2 matchTable: CaseInsensitiveOrder 7 "! ! !ByteString methodsFor: 'converting' stamp: 'ar 4/10/2005 17:20'! asByteArray | ba sz | sz := self byteSize. ba := ByteArray new: sz. ba replaceFrom: 1 to: sz with: self startingAt: 1. ^ba! ! !ByteString methodsFor: 'converting' stamp: 'yo 8/28/2002 16:52'! asOctetString ^ self. ! ! !ByteString methodsFor: 'converting' stamp: 'yo 11/11/2002 12:20'! convertFromCompoundText | readStream writeStream converter | readStream _ self readStream. writeStream _ String new writeStream. converter _ CompoundTextConverter new. converter ifNil: [^ self]. [readStream atEnd] whileFalse: [ writeStream nextPut: (converter nextFromStream: readStream)]. ^ writeStream contents ! ! !ByteString methodsFor: 'converting' stamp: 'mir 7/20/2004 15:50'! convertFromSystemString | readStream writeStream converter | readStream _ self readStream. writeStream _ String new writeStream. converter _ LanguageEnvironment defaultSystemConverter. converter ifNil: [^ self]. [readStream atEnd] whileFalse: [ writeStream nextPut: (converter nextFromStream: readStream)]. ^ writeStream contents ! ! !ByteString methodsFor: 'testing' stamp: 'ar 4/10/2005 18:04'! isByteString "Answer whether the receiver is a ByteString" ^true! ! !ByteString methodsFor: 'testing' stamp: 'ar 4/10/2005 17:28'! isOctetString "Answer whether the receiver can be represented as a byte string. This is different from asking whether the receiver *is* a ByteString (i.e., #isByteString)" ^ true. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ByteString class instanceVariableNames: ''! !ByteString class methodsFor: 'primitives' stamp: 'yo 12/15/2005 13:44'! compare: string1 with: string2 collated: order "Return 1, 2 or 3, if string1 is <, =, or > string2, with the collating order of characters given by the order array." | len1 len2 c1 c2 | self var: #string1 declareC: 'unsigned char *string1'. self var: #string2 declareC: 'unsigned char *string2'. self var: #order declareC: 'unsigned char *order'. len1 _ string1 size. len2 _ string2 size. 1 to: (len1 min: len2) do: [:i | c1 _ order at: (string1 basicAt: i) + 1. c2 _ order at: (string2 basicAt: i) + 1. c1 = c2 ifFalse: [c1 < c2 ifTrue: [^ 1] ifFalse: [^ 3]]]. len1 = len2 ifTrue: [^ 2]. len1 < len2 ifTrue: [^ 1] ifFalse: [^ 3]. ! ! !ByteString class methodsFor: 'primitives' stamp: 'ar 2/3/2001 16:12'! findFirstInString: aString inSet: inclusionMap startingAt: start | i stringSize | self var: #aString declareC: 'unsigned char *aString'. self var: #inclusionMap declareC: 'char *inclusionMap'. inclusionMap size ~= 256 ifTrue: [ ^0 ]. i _ start. stringSize _ aString size. [ i <= stringSize and: [ (inclusionMap at: (aString at: i) asciiValue+1) = 0 ] ] whileTrue: [ i _ i + 1 ]. i > stringSize ifTrue: [ ^0 ]. ^i! ! !ByteString class methodsFor: 'primitives' stamp: 'ar 2/3/2001 16:13'! indexOfAscii: anInteger inString: aString startingAt: start | stringSize | self var: #aCharacter declareC: 'int anInteger'. self var: #aString declareC: 'unsigned char *aString'. stringSize _ aString size. start to: stringSize do: [:pos | (aString at: pos) asciiValue = anInteger ifTrue: [^ pos]]. ^ 0 ! ! !ByteString class methodsFor: 'primitives' stamp: 'ar 9/28/2001 04:35'! stringHash: aString initialHash: speciesHash | stringSize hash low | self var: #aHash declareC: 'int speciesHash'. self var: #aString declareC: 'unsigned char *aString'. stringSize _ aString size. hash _ speciesHash bitAnd: 16rFFFFFFF. 1 to: stringSize do: [:pos | hash _ hash + (aString at: pos) asciiValue. "Begin hashMultiply" low _ hash bitAnd: 16383. hash _ (16r260D * low + ((16r260D * (hash bitShift: -14) + (16r0065 * low) bitAnd: 16383) * 16384)) bitAnd: 16r0FFFFFFF. ]. ^ hash! ! !ByteString class methodsFor: 'primitives' stamp: 'ar 2/3/2001 16:12'! translate: aString from: start to: stop table: table "translate the characters in the string by the given table, in place" self var: #table declareC: 'unsigned char *table'. self var: #aString declareC: 'unsigned char *aString'. start to: stop do: [ :i | aString at: i put: (table at: (aString at: i) asciiValue+1) ]! ! Symbol variableByteSubclass: #ByteSymbol instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Strings'! !ByteSymbol commentStamp: '' prior: 0! This class represents the symbols containing 8bit characters.! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/10/2005 22:10'! at: index "Primitive. Answer the Character stored in the field of the receiver indexed by the argument. Fail if the index argument is not an Integer or is out of bounds. Essential. See Object documentation whatIsAPrimitive." ^ Character value: (super at: index)! ! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/10/2005 22:10'! at: anInteger put: anObject "You cannot modify the receiver." self errorNoModification! ! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/10/2005 22:10'! byteAt: index ^(self at: index) asciiValue! ! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/10/2005 22:10'! byteAt: anInteger put: anObject "You cannot modify the receiver." self errorNoModification! ! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/10/2005 22:11'! byteSize ^self size! ! !ByteSymbol methodsFor: 'accessing' stamp: 'ar 4/12/2005 17:51'! species "Answer the preferred class for reconstructing the receiver." ^ByteString ! ! !ByteSymbol methodsFor: 'comparing' stamp: 'ar 4/10/2005 22:14'! findSubstring: key in: body startingAt: start matchTable: matchTable "Answer the index in the string body at which the substring key first occurs, at or beyond start. The match is determined using matchTable, which can be used to effect, eg, case-insensitive matches. If no match is found, zero will be returned." ^super findSubstring: key in: body startingAt: start matchTable: matchTable! ! !ByteSymbol methodsFor: 'converting' stamp: 'ar 4/10/2005 22:12'! asByteArray | ba sz | sz := self byteSize. ba := ByteArray new: sz. ba replaceFrom: 1 to: sz with: self startingAt: 1. ^ba! ! !ByteSymbol methodsFor: 'converting' stamp: 'ar 4/10/2005 22:12'! asOctetString ^ self! ! !ByteSymbol methodsFor: 'testing' stamp: 'ar 4/10/2005 22:14'! isByteString "Answer whether the receiver is a ByteString" ^true! ! !ByteSymbol methodsFor: 'testing' stamp: 'ar 4/10/2005 22:14'! isOctetString "Answer whether the receiver can be represented as a byte string. This is different from asking whether the receiver *is* a ByteString (i.e., #isByteString)" ^ true. ! ! !ByteSymbol methodsFor: 'private' stamp: 'ar 4/11/2005 00:08'! pvtAt: index put: aCharacter "Primitive. Store the Character in the field of the receiver indicated by the index. Fail if the index is not an Integer or is out of bounds, or if the argument is not a Character. Essential. See Object documentation whatIsAPrimitive." aCharacter isCharacter ifFalse:[^self errorImproperStore]. index isInteger ifTrue: [self errorSubscriptBounds: index] ifFalse: [self errorNonIntegerIndex]! ! !ByteSymbol methodsFor: 'private' stamp: 'ar 4/10/2005 23:02'! string: aString 1 to: aString size do: [:j | self pvtAt: j put: (aString at: j)]. ^self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ByteSymbol class instanceVariableNames: ''! !ByteSymbol class methodsFor: 'primitives' stamp: 'ar 8/18/2005 13:46'! findFirstInString: aString inSet: inclusionMap startingAt: start ^ByteString findFirstInString: aString inSet: inclusionMap startingAt: start! ! !ByteSymbol class methodsFor: 'primitives' stamp: 'ar 8/18/2005 13:46'! indexOfAscii: anInteger inString: aString startingAt: start ^ByteString indexOfAscii: anInteger inString: aString startingAt: start! ! !ByteSymbol class methodsFor: 'primitives' stamp: 'ar 8/18/2005 13:46'! stringHash: aString initialHash: speciesHash ^ByteString stringHash: aString initialHash: speciesHash! ! !ByteSymbol class methodsFor: 'primitives' stamp: 'ar 8/18/2005 13:47'! translate: aString from: start to: stop table: table ^ByteString translate: aString from: start to: stop table: table! ! EUCTextConverter subclass: #CNGBTextConverter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CNGBTextConverter commentStamp: '' prior: 0! Text converter for Simplified Chinese variation of EUC. (Even though the name doesn't look so, it is what it is.)! !CNGBTextConverter methodsFor: 'private' stamp: 'yo 3/17/2004 00:41'! languageEnvironment ^ SimplifiedChineseEnvironment. ! ! !CNGBTextConverter methodsFor: 'private' stamp: 'yo 10/23/2002 14:42'! leadingChar ^ GB2312 leadingChar ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CNGBTextConverter class instanceVariableNames: ''! !CNGBTextConverter class methodsFor: 'utilities' stamp: 'yo 10/23/2002 14:42'! encodingNames ^ #('gb2312' ) copy ! ! ClipboardInterpreter subclass: #CP1250ClipboardInterpreter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CP1250ClipboardInterpreter methodsFor: 'as yet unclassified' stamp: 'ar 4/10/2005 16:09'! fromSystemClipboard: aString | result converter | result := WriteStream on: (String new: aString size). converter := CP1250TextConverter new. aString do: [:each | result nextPut: (converter toSqueak: each macToSqueak) asCharacter. ]. ^ result contents. ! ! !CP1250ClipboardInterpreter methodsFor: 'as yet unclassified' stamp: 'ar 4/10/2005 16:05'! toSystemClipboard: aString | result converter r | aString isAsciiString ifTrue: [^ aString asOctetString]. "optimization" result _ WriteStream on: (String new: aString size). converter _ CP1250TextConverter new. aString do: [:each | r _ converter fromSqueak: each. r charCode < 255 ifTrue: [ result nextPut: r squeakToMac]]. ^ result contents. ! ! KeyboardInputInterpreter subclass: #CP1250InputInterpreter instanceVariableNames: 'converter' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CP1250InputInterpreter methodsFor: 'as yet unclassified' stamp: 'pk 1/19/2005 20:40'! initialize converter _ CP1250TextConverter new. ! ! !CP1250InputInterpreter methodsFor: 'as yet unclassified' stamp: 'ar 4/10/2005 16:10'! nextCharFrom: sensor firstEvt: evtBuf "Input from the Czech keyboard under Windows doesn't correspond to cp-1250 or iso-8859-2 encoding!!" | keyValue | keyValue := evtBuf third. ^ converter toSqueak: keyValue asCharacter macToSqueak. ! ! TextConverter subclass: #CP1250TextConverter instanceVariableNames: '' classVariableNames: 'FromTable' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CP1250TextConverter commentStamp: '' prior: 0! Text converter for CP1250. Windows code page used in Eastern Europe.! !CP1250TextConverter methodsFor: 'conversion' stamp: 'pk 1/19/2005 14:34'! nextFromStream: aStream | character1 | aStream isBinary ifTrue: [^ aStream basicNext]. character1 _ aStream basicNext. character1 isNil ifTrue: [^ nil]. ^ self toSqueak: character1. ! ! !CP1250TextConverter methodsFor: 'conversion' stamp: 'ar 4/9/2005 22:28'! nextPut: aCharacter toStream: aStream aStream isBinary ifTrue: [^aCharacter storeBinaryOn: aStream]. aCharacter charCode < 128 ifTrue: [ aStream basicNextPut: aCharacter. ] ifFalse: [ aStream basicNextPut: ((Character value: (self fromSqueak: aCharacter) charCode)). ]. ! ! !CP1250TextConverter methodsFor: 'private' stamp: 'yo 2/9/2005 05:29'! fromSqueak: char ^ Character value: (FromTable at: char charCode ifAbsent: [char asciiValue])! ! !CP1250TextConverter methodsFor: 'private' stamp: 'ar 4/9/2005 22:24'! toSqueak: char | value | value _ char charCode. value < 129 ifTrue: [^ char]. value > 255 ifTrue: [^ char]. ^ Character leadingChar: Latin2Environment leadingChar code: (#( 16r0081 16r201A 16r0083 16r201E 16r2026 16r2020 16r2021 16r0088 16r2030 16r0160 16r2039 16r015A 16r0164 16r017D 16r0179 16r0090 16r2018 16r2019 16r201C 16r201D 16r2022 16r2013 16r2014 16r0098 16r2122 16r0161 16r203A 16r015B 16r0165 16r017E 16r017A 16r00A0 16r02C7 16r02D8 16r0141 16r00A4 16r0104 16r00A6 16r00A7 16r00A8 16r00A9 16r015E 16r00AB 16r00AC 16r00AD 16r00AE 16r017B 16r00B0 16r00B1 16r02DB 16r0142 16r00B4 16r00B5 16r00B6 16r00B7 16r00B8 16r0105 16r015F 16r00BB 16r013D 16r02DD 16r013E 16r017C 16r0154 16r00C1 16r00C2 16r0102 16r00C4 16r0139 16r0106 16r00C7 16r010C 16r00C9 16r0118 16r00CB 16r011A 16r00CD 16r00CE 16r010E 16r0110 16r0143 16r0147 16r00D3 16r00D4 16r0150 16r00D6 16r00D7 16r0158 16r016E 16r00DA 16r0170 16r00DC 16r00DD 16r0162 16r00DF 16r0155 16r00E1 16r00E2 16r0103 16r00E4 16r013A 16r0107 16r00E7 16r010D 16r00E9 16r0119 16r00EB 16r011B 16r00ED 16r00EE 16r010F 16r0111 16r0144 16r0148 16r00F3 16r00F4 16r0151 16r00F6 16r00F7 16r0159 16r016F 16r00FA 16r0171 16r00FC 16r00FD 16r0163 16r02D9 ) at: (value - 129 + 1)). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CP1250TextConverter class instanceVariableNames: ''! !CP1250TextConverter class methodsFor: 'class initialization' stamp: 'pk 1/19/2005 19:35'! initialize " CP1250TextConverter initialize " FromTable _ Dictionary new. FromTable at: 16r0081 put: 16r81. FromTable at: 16r201A put: 16r82. FromTable at: 16r0083 put: 16r83. FromTable at: 16r201E put: 16r84. FromTable at: 16r2026 put: 16r85. FromTable at: 16r2020 put: 16r86. FromTable at: 16r2021 put: 16r87. FromTable at: 16r0088 put: 16r88. FromTable at: 16r2030 put: 16r89. FromTable at: 16r0160 put: 16r8A. FromTable at: 16r2039 put: 16r8B. FromTable at: 16r015A put: 16r8C. FromTable at: 16r0164 put: 16r8D. FromTable at: 16r017D put: 16r8E. FromTable at: 16r0179 put: 16r8F. FromTable at: 16r0090 put: 16r90. FromTable at: 16r2018 put: 16r91. FromTable at: 16r2019 put: 16r92. FromTable at: 16r201C put: 16r93. FromTable at: 16r201D put: 16r94. FromTable at: 16r2022 put: 16r95. FromTable at: 16r2013 put: 16r96. FromTable at: 16r2014 put: 16r97. FromTable at: 16r0098 put: 16r98. FromTable at: 16r2122 put: 16r99. FromTable at: 16r0161 put: 16r9A. FromTable at: 16r203A put: 16r9B. FromTable at: 16r015B put: 16r9C. FromTable at: 16r0165 put: 16r9D. FromTable at: 16r017E put: 16r9E. FromTable at: 16r017A put: 16r9F. FromTable at: 16r00A0 put: 16rA0. FromTable at: 16r02C7 put: 16rA1. FromTable at: 16r02D8 put: 16rA2. FromTable at: 16r0141 put: 16rA3. FromTable at: 16r00A4 put: 16rA4. FromTable at: 16r0104 put: 16rA5. FromTable at: 16r00A6 put: 16rA6. FromTable at: 16r00A7 put: 16rA7. FromTable at: 16r00A8 put: 16rA8. FromTable at: 16r00A9 put: 16rA9. FromTable at: 16r015E put: 16rAA. FromTable at: 16r00AB put: 16rAB. FromTable at: 16r00AC put: 16rAC. FromTable at: 16r00AD put: 16rAD. FromTable at: 16r00AE put: 16rAE. FromTable at: 16r017B put: 16rAF. FromTable at: 16r00B0 put: 16rB0. FromTable at: 16r00B1 put: 16rB1. FromTable at: 16r02DB put: 16rB2. FromTable at: 16r0142 put: 16rB3. FromTable at: 16r00B4 put: 16rB4. FromTable at: 16r00B5 put: 16rB5. FromTable at: 16r00B6 put: 16rB6. FromTable at: 16r00B7 put: 16rB7. FromTable at: 16r00B8 put: 16rB8. FromTable at: 16r0105 put: 16rB9. FromTable at: 16r015F put: 16rBA. FromTable at: 16r00BB put: 16rBB. FromTable at: 16r013D put: 16rBC. FromTable at: 16r02DD put: 16rBD. FromTable at: 16r013E put: 16rBE. FromTable at: 16r017C put: 16rBF. FromTable at: 16r0154 put: 16rC0. FromTable at: 16r00C1 put: 16rC1. FromTable at: 16r00C2 put: 16rC2. FromTable at: 16r0102 put: 16rC3. FromTable at: 16r00C4 put: 16rC4. FromTable at: 16r0139 put: 16rC5. FromTable at: 16r0106 put: 16rC6. FromTable at: 16r00C7 put: 16rC7. FromTable at: 16r010C put: 16rC8. FromTable at: 16r00C9 put: 16rC9. FromTable at: 16r0118 put: 16rCA. FromTable at: 16r00CB put: 16rCB. FromTable at: 16r011A put: 16rCC. FromTable at: 16r00CD put: 16rCD. FromTable at: 16r00CE put: 16rCE. FromTable at: 16r010E put: 16rCF. FromTable at: 16r0110 put: 16rD0. FromTable at: 16r0143 put: 16rD1. FromTable at: 16r0147 put: 16rD2. FromTable at: 16r00D3 put: 16rD3. FromTable at: 16r00D4 put: 16rD4. FromTable at: 16r0150 put: 16rD5. FromTable at: 16r00D6 put: 16rD6. FromTable at: 16r00D7 put: 16rD7. FromTable at: 16r0158 put: 16rD8. FromTable at: 16r016E put: 16rD9. FromTable at: 16r00DA put: 16rDA. FromTable at: 16r0170 put: 16rDB. FromTable at: 16r00DC put: 16rDC. FromTable at: 16r00DD put: 16rDD. FromTable at: 16r0162 put: 16rDE. FromTable at: 16r00DF put: 16rDF. FromTable at: 16r0155 put: 16rE0. FromTable at: 16r00E1 put: 16rE1. FromTable at: 16r00E2 put: 16rE2. FromTable at: 16r0103 put: 16rE3. FromTable at: 16r00E4 put: 16rE4. FromTable at: 16r013A put: 16rE5. FromTable at: 16r0107 put: 16rE6. FromTable at: 16r00E7 put: 16rE7. FromTable at: 16r010D put: 16rE8. FromTable at: 16r00E9 put: 16rE9. FromTable at: 16r0119 put: 16rEA. FromTable at: 16r00EB put: 16rEB. FromTable at: 16r011B put: 16rEC. FromTable at: 16r00ED put: 16rED. FromTable at: 16r00EE put: 16rEE. FromTable at: 16r010F put: 16rEF. FromTable at: 16r0111 put: 16rF0. FromTable at: 16r0144 put: 16rF1. FromTable at: 16r0148 put: 16rF2. FromTable at: 16r00F3 put: 16rF3. FromTable at: 16r00F4 put: 16rF4. FromTable at: 16r0151 put: 16rF5. FromTable at: 16r00F6 put: 16rF6. FromTable at: 16r00F7 put: 16rF7. FromTable at: 16r0159 put: 16rF8. FromTable at: 16r016F put: 16rF9. FromTable at: 16r00FA put: 16rFA. FromTable at: 16r0171 put: 16rFB. FromTable at: 16r00FC put: 16rFC. FromTable at: 16r00FD put: 16rFD. FromTable at: 16r0163 put: 16rFE. FromTable at: 16r02D9 put: 16rFF! ! !CP1250TextConverter class methodsFor: 'utilities' stamp: 'pk 1/19/2005 14:35'! encodingNames ^ #('cp-1250') copy ! ! TextConverter subclass: #CP1253TextConverter instanceVariableNames: '' classVariableNames: 'FromTable' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CP1253TextConverter commentStamp: '' prior: 0! Text converter for CP1253. Windows code page used for Greek.! !CP1253TextConverter methodsFor: 'conversion' stamp: 'yo 2/19/2004 10:12'! nextFromStream: aStream | character1 | aStream isBinary ifTrue: [^ aStream basicNext]. character1 _ aStream basicNext. character1 isNil ifTrue: [^ nil]. ^ self toSqueak: character1. ! ! !CP1253TextConverter methodsFor: 'private' stamp: 'ar 4/9/2005 22:24'! toSqueak: char | value | value _ char charCode. value < 128 ifTrue: [^ char]. value > 255 ifTrue: [^ char]. ^ Character leadingChar: GreekEnvironment leadingChar code: (#( 16r20AC 16rFFFD 16r201A 16r0192 16r201E 16r2026 16r2020 16r2021 16rFFFD 16r2030 16rFFFD 16r2039 16rFFFD 16rFFFD 16rFFFD 16rFFFD 16rFFFD 16r2018 16r2019 16r201C 16r201D 16r2022 16r2013 16r2014 16rFFFD 16r2122 16rFFFD 16r203A 16rFFFD 16rFFFD 16rFFFD 16rFFFD 16r00A0 16r0385 16r0386 16r00A3 16r00A4 16r00A5 16r00A6 16r00A7 16r00A8 16r00A9 16rFFFD 16r00AB 16r00AC 16r00AD 16r00AE 16r2015 16r00B0 16r00B1 16r00B2 16r00B3 16r0384 16r00B5 16r00B6 16r00B7 16r0388 16r0389 16r038A 16r00BB 16r038C 16r00BD 16r038E 16r038F 16r0390 16r0391 16r0392 16r0393 16r0394 16r0395 16r0396 16r0397 16r0398 16r0399 16r039A 16r039B 16r039C 16r039D 16r039E 16r039F 16r03A0 16r03A1 16rFFFD 16r03A3 16r03A4 16r03A5 16r03A6 16r03A7 16r03A8 16r03A9 16r03AA 16r03AB 16r03AC 16r03AD 16r03AE 16r03AF 16r03B0 16r03B1 16r03B2 16r03B3 16r03B4 16r03B5 16r03B6 16r03B7 16r03B8 16r03B9 16r03BA 16r03BB 16r03BC 16r03BD 16r03BE 16r03BF 16r03C0 16r03C1 16r03C2 16r03C3 16r03C4 16r03C5 16r03C6 16r03C7 16r03C8 16r03C9 16r03CA 16r03CB 16r03CC 16r03CD 16r03CE 16rFFFD ) at: (value - 128 + 1)). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CP1253TextConverter class instanceVariableNames: ''! !CP1253TextConverter class methodsFor: 'utilities' stamp: 'yo 2/19/2004 10:11'! encodingNames ^ #('cp-1253') copy ! ! Model subclass: #CPUWatcher instanceVariableNames: 'tally watcher threshold' classVariableNames: 'CurrentCPUWatcher' poolDictionaries: '' category: 'Tools-Process Browser'! !CPUWatcher commentStamp: '' prior: 0! CPUWatcher implements a simple runaway process monitoring tool that will suspend a process that is taking up too much of Squeak's time and allow user interaction. By default it watches for a Process that is taking more than 80% of the time; this threshold can be changed. CPUWatcher can also be used to show cpu percentages for each process from within the ProcessBrowser. CPUWatcher startMonitoring. "process period 20 seconds, sample rate 100 msec" CPUWatcher current monitorProcessPeriod: 10 sampleRate: 20. CPUWatcher current threshold: 0.5. "change from 80% to 50%" CPUWatcher stopMonitoring. ! !CPUWatcher methodsFor: 'accessing' stamp: 'nk 3/14/2001 07:56'! isMonitoring ^watcher notNil! ! !CPUWatcher methodsFor: 'accessing' stamp: 'nk 3/8/2001 18:36'! tally ^tally copy! ! !CPUWatcher methodsFor: 'accessing' stamp: 'nk 3/8/2001 18:49'! threshold "What fraction of the time can a process be the active process before we stop it?" ^threshold! ! !CPUWatcher methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:27'! threshold: thresh "What fraction of the time can a process be the active process before we stop it?" threshold := (thresh max: 0.02) min: 1.0! ! !CPUWatcher methodsFor: 'accessing' stamp: 'nk 3/14/2001 08:26'! watcherProcess ^watcher! ! !CPUWatcher methodsFor: 'porcine capture' stamp: 'sd 11/20/2005 21:27'! catchThePig: aProcess | rules | "nickname, allow-stop, allow-debug" rules := ProcessBrowser nameAndRulesFor: aProcess. (ProcessBrowser isUIProcess: aProcess) ifTrue: [ "aProcess debugWithTitle: 'Interrupted from the CPUWatcher'." ] ifFalse: [ rules second ifFalse: [ ^self ]. ProcessBrowser suspendProcess: aProcess. self openWindowForSuspendedProcess: aProcess ] ! ! !CPUWatcher methodsFor: 'porcine capture' stamp: 'sd 11/20/2005 21:27'! findThePig "tally has been updated. Look at it to see if there is a bad process. This runs at a very high priority, so make it fast" | countAndProcess | countAndProcess := tally sortedCounts first. (countAndProcess key / tally size > self threshold) ifTrue: [ | proc | proc := countAndProcess value. proc == Processor backgroundProcess ifTrue: [ ^self ]. "idle process? OK" self catchThePig: proc ]. ! ! !CPUWatcher methodsFor: 'porcine capture' stamp: 'nk 3/8/2001 18:34'! openMVCWindowForSuspendedProcess: aProcess ProcessBrowser new openAsMVC.! ! !CPUWatcher methodsFor: 'porcine capture' stamp: 'sd 11/20/2005 21:27'! openMorphicWindowForSuspendedProcess: aProcess | menu rules | menu := MenuMorph new. "nickname allow-stop allow-debug" rules := ProcessBrowser nameAndRulesFor: aProcess. menu add: 'Dismiss this menu' target: menu selector: #delete; addLine. menu add: 'Open Process Browser' target: ProcessBrowser selector: #open. menu add: 'Resume' target: self selector: #resumeProcess:fromMenu: argumentList: { aProcess . menu }. menu add: 'Terminate' target: self selector: #terminateProcess:fromMenu: argumentList: { aProcess . menu }. rules third ifTrue: [ menu add: 'Debug at a lower priority' target: self selector: #debugProcess:fromMenu: argumentList: { aProcess . menu }. ]. menu addTitle: aProcess identityHash asString, ' ', rules first, ' is taking too much time and has been suspended. What do you want to do with it?'. menu stayUp: true. menu popUpInWorld ! ! !CPUWatcher methodsFor: 'porcine capture' stamp: 'nk 3/8/2001 18:35'! openWindowForSuspendedProcess: aProcess Smalltalk isMorphic ifTrue: [ WorldState addDeferredUIMessage: [ self openMorphicWindowForSuspendedProcess: aProcess ] ] ifFalse: [ [ self openMVCWindowForSuspendedProcess: aProcess ] forkAt: Processor userSchedulingPriority ] ! ! !CPUWatcher methodsFor: 'process operations' stamp: 'sd 11/20/2005 21:27'! debugProcess: aProcess | uiPriority oldPriority | uiPriority := Processor activeProcess priority. aProcess priority >= uiPriority ifTrue: [ oldPriority := ProcessBrowser setProcess: aProcess toPriority: uiPriority - 1 ]. ProcessBrowser debugProcess: aProcess.! ! !CPUWatcher methodsFor: 'process operations' stamp: 'nk 3/8/2001 17:27'! debugProcess: aProcess fromMenu: aMenuMorph aMenuMorph delete. self debugProcess: aProcess.! ! !CPUWatcher methodsFor: 'process operations' stamp: 'nk 3/8/2001 17:21'! resumeProcess: aProcess fromMenu: aMenuMorph aMenuMorph delete. ProcessBrowser resumeProcess: aProcess.! ! !CPUWatcher methodsFor: 'process operations' stamp: 'nk 3/8/2001 17:24'! terminateProcess: aProcess fromMenu: aMenuMorph aMenuMorph delete. ProcessBrowser terminateProcess: aProcess.! ! !CPUWatcher methodsFor: 'startup-shutdown' stamp: 'sd 11/20/2005 21:27'! monitorProcessPeriod: secs sampleRate: msecs self stopMonitoring. watcher := [ [ | promise | promise := Processor tallyCPUUsageFor: secs every: msecs. tally := promise value. promise := nil. self findThePig. ] repeat ] forkAt: Processor highestPriority. Processor yield ! ! !CPUWatcher methodsFor: 'startup-shutdown' stamp: 'nk 3/14/2001 08:07'! startMonitoring self monitorProcessPeriod: 20 sampleRate: 100! ! !CPUWatcher methodsFor: 'startup-shutdown' stamp: 'sd 11/20/2005 21:27'! stopMonitoring watcher ifNotNil: [ ProcessBrowser terminateProcess: watcher. watcher := nil. ]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CPUWatcher class instanceVariableNames: ''! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 3/8/2001 18:45'! current ^CurrentCPUWatcher ! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 3/14/2001 08:28'! currentWatcherProcess ^CurrentCPUWatcher ifNotNil: [ CurrentCPUWatcher watcherProcess ] ! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 3/8/2001 21:43'! dumpTallyOnTranscript self current ifNotNil: [ ProcessBrowser dumpTallyOnTranscript: self current tally ]! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 6/18/2003 07:15'! initialize "CPUWatcher initialize" Smalltalk addToStartUpList: self. Smalltalk addToShutDownList: self.! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 3/14/2001 08:06'! isMonitoring ^CurrentCPUWatcher notNil and: [ CurrentCPUWatcher isMonitoring ] ! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 10/31/2001 10:50'! monitorPreferenceChanged Preferences cpuWatcherEnabled ifTrue: [ self startMonitoring ] ifFalse: [ self stopMonitoring ]! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 6/18/2003 07:14'! shutDown self stopMonitoring.! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 3/14/2001 08:17'! startMonitoring "CPUWatcher startMonitoring" ^self startMonitoringPeriod: 20 rate: 100 threshold: 0.8! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:28'! startMonitoringPeriod: pd rate: rt threshold: th "CPUWatcher startMonitoring" CurrentCPUWatcher ifNotNil: [ ^CurrentCPUWatcher startMonitoring. ]. CurrentCPUWatcher := (self new) monitorProcessPeriod: pd sampleRate: rt; threshold: th; yourself. ^CurrentCPUWatcher ! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'nk 6/18/2003 07:14'! startUp self monitorPreferenceChanged.! ! !CPUWatcher class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:28'! stopMonitoring "CPUWatcher stopMonitoring" CurrentCPUWatcher ifNotNil: [ CurrentCPUWatcher stopMonitoring. ]. CurrentCPUWatcher := nil. ! ! Error subclass: #CRCError instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Compression-Streams'! !CRCError methodsFor: 'as yet unclassified' stamp: 'nk 3/7/2004 15:56'! isResumable ^true! ! HTTPDownloadRequest subclass: #CachedHTTPDownloadRequest instanceVariableNames: 'cachedName' classVariableNames: '' poolDictionaries: '' category: 'System-Download'! !CachedHTTPDownloadRequest methodsFor: 'accessing' stamp: 'ar 12/14/1999 14:53'! cachedName ^cachedName! ! !CachedHTTPDownloadRequest methodsFor: 'accessing' stamp: 'ar 12/14/1999 14:53'! cachedName: aString cachedName _ aString.! ! !CachedHTTPDownloadRequest 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].! ! PluggableCanvas subclass: #CachingCanvas instanceVariableNames: 'cacheCanvas mainCanvas' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'accessing' stamp: 'ls 3/27/2000 22:50'! contentsOfArea: area into: aForm ^cacheCanvas contentsOfArea: area into: aForm! ! !CachingCanvas methodsFor: 'accessing' stamp: 'ls 3/26/2000 20:21'! form ^cacheCanvas form! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'RAA 7/20/2000 13:08'! allocateForm: extentPoint ^cacheCanvas form allocateForm: extentPoint! ! !CachingCanvas methodsFor: 'canvas methods' stamp: 'RAA 7/21/2000 09:54'! showAt: pt invalidRects: rects mainCanvas showAt: pt invalidRects: rects! ! !CachingCanvas methodsFor: 'initialization' stamp: 'ls 4/8/2000 22:35'! mainCanvas: mainCanvas0 mainCanvas := mainCanvas0. cacheCanvas := FormCanvas extent: mainCanvas extent depth: mainCanvas depth.! ! !CachingCanvas methodsFor: 'private' stamp: 'ls 3/26/2000 13:35'! apply: aBlock aBlock value: cacheCanvas. aBlock value: mainCanvas.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'System-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: 'avi 4/30/2004 01:40'! httpRequestClass ^CachedHTTPDownloadRequest ! ! Morph subclass: #CachingMorph instanceVariableNames: 'damageRecorder cacheCanvas' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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' stamp: 'dgd 2/21/2003 23:03'! updateCacheCanvas: aCanvas "Update the cached image of the morphs being held by this hand." | myBnds rectList | myBnds := self fullBounds. (cacheCanvas isNil 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]]! ! !CachingMorph methodsFor: 'caching' stamp: 'jm 11/13/97 16:31'! releaseCachedState super releaseCachedState. cacheCanvas _ nil. ! ! !CachingMorph methodsFor: 'change reporting' 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: 'drawing'! drawOn: aCanvas submorphs isEmpty ifTrue: [^ super drawOn: aCanvas]. ! ! !CachingMorph methodsFor: 'drawing' stamp: 'ar 12/30/2001 19:14'! fullDrawOn: aCanvas (aCanvas isVisible: self fullBounds) ifFalse:[^self]. self updateCacheCanvas: aCanvas. aCanvas cache: self fullBounds using: cacheCanvas form during:[:cachingCanvas| super fullDrawOn: cachingCanvas]. ! ! !CachingMorph methodsFor: 'drawing' stamp: 'ar 5/28/2000 17:12'! imageForm self updateCacheCanvas: Display getCanvas. ^ cacheCanvas form offset: self fullBounds topLeft ! ! !CachingMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color veryLightGray! ! !CachingMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 21:48'! initialize "initialize the state of the receiver" super initialize. "" damageRecorder _ DamageRecorder new! ! FileStreamException subclass: #CannotDeleteFileException instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! 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: '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: 'converting' stamp: 'ar 8/8/2001 14:22'! asAlphaBlendingCanvas: alpha ^(AlphaBlendingCanvas on: self) alpha: alpha! ! !Canvas methodsFor: 'converting' stamp: 'ar 6/24/1999 17:46'! asShadowDrawingCanvas ^self asShadowDrawingCanvas: (Color black alpha: 0.5).! ! !Canvas methodsFor: 'converting' stamp: 'ar 8/8/2001 14:14'! asShadowDrawingCanvas: aColor ^(ShadowDrawingCanvas on: self) shadowColor: aColor! ! !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: '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: 'aoy 2/15/2003 21:41'! 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). segmentLength := segmentOffset < s1 ifTrue: [phase := 1. s1 - segmentOffset] ifFalse: [phase := 2. 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: '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-general' stamp: 'ar 5/29/1999 05:14'! draw: anObject ^anObject drawOn: self! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 12/30/2001 15:23'! drawMorph: aMorph self draw: aMorph! ! !Canvas methodsFor: 'drawing-general'! fullDraw: anObject ^anObject fullDrawOn: self! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 12/30/2001 15:23'! fullDrawMorph: aMorph self fullDraw: aMorph! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 12/30/2001 18:47'! roundCornersOf: aMorph during: aBlock ^self roundCornersOf: aMorph in: aMorph bounds during: aBlock! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 12/30/2001 18:47'! roundCornersOf: aMorph in: bounds during: aBlock ^aBlock value! ! !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/10/2004 17:19'! 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 isTranslucent not]) ifTrue:[^self paintImage: aForm at: aPoint sourceRect: sourceRect]. self image: aForm at: aPoint sourceRect: sourceRect rule: Form blend! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 12/28/2001 23:44'! warpImage: aForm transform: aTransform "Warp the given form using aTransform" ^self warpImage: aForm transform: aTransform at: 0@0! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 12/28/2001 23:54'! warpImage: aForm transform: aTransform at: extraOffset "Warp the given form using aTransform. TODO: Use transform to figure out appropriate cell size" ^self warpImage: aForm transform: aTransform at: extraOffset sourceRect: aForm boundingBox cellSize: 1! ! !Canvas methodsFor: 'drawing-images' stamp: 'ar 12/29/2001 00:20'! warpImage: aForm transform: aTransform at: extraOffset sourceRect: sourceRect cellSize: cellSize "Warp the given using the appropriate transform and offset." ^self subclassResponsibility! ! !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: '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-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 8/25/2001 17:27'! fillRectangle: aRectangle fillStyle: aFillStyle borderStyle: aBorderStyle "Fill the given rectangle." self fillRectangle: (aRectangle insetBy: aBorderStyle width) fillStyle: aFillStyle. aBorderStyle frameRectangle: aRectangle on: self! ! !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-support' stamp: 'gm 2/22/2003 14:53'! 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 isForm) 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-text' stamp: 'ar 12/30/2001 20:35'! drawString: s at: pt ^ self drawString: s from: 1 to: s size at: pt font: nil color: Color black! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:25'! drawString: s at: pt font: aFont color: aColor ^ self drawString: s from: 1 to: s size at: pt font: aFont color: aColor! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/30/2001 20:36'! drawString: s from: firstIndex to: lastIndex at: pt font: font color: aColor self drawString: s from: firstIndex to: lastIndex in: (pt extent: 10000@10000) font: font color: aColor! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/30/2001 20:37'! drawString: s from: firstIndex to: lastIndex in: boundsRect font: fontOrNil color: c ^self subclassResponsibility! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:39'! drawString: s in: boundsRect ^self drawString: s from: 1 to: s size in: boundsRect font: nil color: Color black! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:13'! drawString: s in: boundsRect font: fontOrNil color: c ^self drawString: s from: 1 to: s size in: boundsRect font: fontOrNil color: c! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:40'! text: s at: pt font: fontOrNil color: c "OBSOLETE" ^ self drawString: s at: pt font: fontOrNil color: c! ! !Canvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:40'! text: s bounds: boundsRect font: fontOrNil color: c "OBSOLETE" ^self drawString: s in: boundsRect font: fontOrNil color: c! ! !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: '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 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: '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: 'nk 1/1/2004 21:09'! isPostscriptCanvas ^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: '*MorphicExtras-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: '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: 'private' stamp: 'ar 8/8/2001 14:21'! image: aForm at: aPoint sourceRect: sourceRect rule: rule alpha: sourceAlpha "Privately used for blending forms w/ constant alpha. Fall back to simpler case by defaul." ^self image: aForm at: aPoint sourceRect: sourceRect rule: rule! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Canvas class instanceVariableNames: ''! !Canvas class methodsFor: 'configuring'! filterSelector ^#drawOnCanvas:.! ! CharacterScanner subclass: #CanvasCharacterScanner instanceVariableNames: 'canvas fillBlt foregroundColor runX lineY defaultTextColor' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Support'! !CanvasCharacterScanner commentStamp: '' prior: 0! A displaying scanner which draws its output to a Morphic canvas.! !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: 'object fileIn' stamp: 'nk 6/17/2003 15:30'! convertToCurrentVersion: varDict refStream: smartRefStrm "From Squeak3.5 [latest update: #5180] on 17 June 2003" varDict at: 'defaultTextColor' put: Color black. ^ super convertToCurrentVersion: varDict refStream: smartRefStrm! ! !CanvasCharacterScanner methodsFor: 'scanning' stamp: 'aoy 2/15/2003 21:24'! displayLine: textLine offset: offset leftInRun: leftInRun "largely copied from DisplayScanner's routine" | nowLeftInRun done startLoc startIndex stopCondition | line := textLine. foregroundColor ifNil: [foregroundColor := Color black]. leftMargin := (line leftMarginForAlignment: alignment) + offset x. rightMargin := line rightMargin + offset x. lineY := line top + offset y. lastIndex := textLine first. nowLeftInRun := leftInRun <= 0 ifTrue: [self setStopConditions. "also sets the font" text runLengthFor: lastIndex] ifFalse: [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 drawString: text string from: startIndex to: lastIndex at: startLoc font: font color: foregroundColor. "handle the stop condition" done := self perform: stopCondition]. ^runStopIndex - lastIndex! ! !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: 'ar 10/18/2004 14:31'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. self setConditionArray: (alignment = Justified ifTrue: [#paddedSpace]). ! ! !CanvasCharacterScanner methodsFor: 'stop conditions' stamp: 'ar 12/15/2001 23:28'! tab destX _ (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! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'mu 8/9/2003 22:40'! defaultTextColor defaultTextColor ifNil:[defaultTextColor _ Color black]. ^defaultTextColor! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'yo 6/23/2003 18:09'! defaultTextColor: color "This defaultTextColor inst var is equivalent to paragraphColor of DisplayScanner." defaultTextColor _ color. ! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'ls 9/26/1999 10:03'! doesDisplaying ^false "it doesn't do displaying using copyBits"! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'mu 8/9/2003 22:40'! setFont foregroundColor _ self defaultTextColor. super setFont. destY _ lineY + line baseline - font ascent! ! !CanvasCharacterScanner methodsFor: 'private' stamp: 'ls 9/25/1999 16:24'! textColor: color foregroundColor _ color! ! Object subclass: #CanvasDecoder instanceVariableNames: 'drawingCanvas clipRect transform connection fonts' classVariableNames: 'CachedForms DecodeTable' poolDictionaries: '' category: 'Nebraska-Morphic-Remote'! !CanvasDecoder commentStamp: '' prior: 0! Decodes commands encoded by MREncoder, and draws them onto a canvas.! !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: 'decoding' stamp: 'yo 10/23/2002 23:36'! addFontSetToCache: command | index font | index := self class decodeInteger: command second. font := self class decodeFontSet: 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: '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: 'yo 3/21/2003 23:02'! addTTCFontToCache: command | index font | index := self class decodeInteger: command second. font := self class decodeTTCFont: 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: 'dgd 2/22/2003 18:42'! drawBalloonOval: command | aRectangle aFillStyle borderWidth borderColor | aRectangle := self class decodeRectangle: command second. aFillStyle := self class decodeFillStyle: command third. borderWidth := self class decodeInteger: command fourth. borderColor := self class decodeColor: (command fifth). self drawCommand: [:c | c asBalloonCanvas fillOval: aRectangle fillStyle: aFillStyle borderWidth: borderWidth borderColor: borderColor]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'dgd 2/22/2003 18:42'! drawBalloonRect: command | aRectangle aFillStyle | aRectangle := self class decodeRectangle: (command second). aFillStyle := self class decodeFillStyle: command third. 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: 'nk 6/25/2003 12:24'! drawImage: command | image point sourceRect rule cacheID cacheNew previousImage | image := self class decodeImage: command second. point := self class decodePoint: command third. sourceRect := self class decodeRectangle: command fourth. rule := self class decodeInteger: command fifth. command size >= 7 ifTrue: [false ifTrue: [self showSpaceUsed]. "debugging" cacheID := self class decodeInteger: (command sixth). cacheNew := (self class decodeInteger: command seventh) = 1. cacheID > 0 ifTrue: [ 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: 'dgd 2/22/2003 18:43'! drawInfiniteFill: command | aRectangle aFillStyle | aRectangle := self class decodeRectangle: (command second). aFillStyle := InfiniteForm with: (self class decodeImage: command third). self drawCommand: [:c | c asBalloonCanvas fillRectangle: aRectangle fillStyle: aFillStyle]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'sd 11/20/2005 21:25'! drawLine: command | verb pt1Enc pt2Enc widthEnc colorEnc pt1 pt2 width color | verb := command first. pt1Enc := command second. pt2Enc := command third. widthEnc := command fourth. colorEnc := command fifth. "" 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: 'sd 11/20/2005 21:25'! drawMultiText: command | boundsEnc colorEnc text bounds color fontIndexEnc fontIndex | text := WideString fromByteArray: (command at: 2) asByteArray. "text asByteArray printString displayAt: 800@0." "self halt." 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 drawString: text in: bounds font: (fonts at: fontIndex) color: color ] ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'sd 11/20/2005 21:25'! drawOval: command | verb rectEnc colorEnc borderWidthEnc borderColorEnc rect color borderWidth borderColor | verb := command first. rectEnc := command second. colorEnc := command third. borderWidthEnc := command fourth. borderColorEnc := command fifth. "" 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: 'dgd 2/22/2003 18:43'! drawPoly: command | verticesEnc fillColorEnc borderWidthEnc borderColorEnc vertices fillColor borderWidth borderColor | fillColorEnc := command second. borderWidthEnc := command third. borderColorEnc := command fourth. 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: 'sd 11/20/2005 21:25'! drawRect: command | verb rectEnc fillColorEnc borderWidthEnc borderColorEnc rect fillColor borderWidth borderColor | verb := command first. rectEnc := command second. fillColorEnc := command third. borderWidthEnc := command fourth. borderColorEnc := command fifth. "" 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: 'dgd 2/22/2003 18:44'! drawStencil: command | stencilFormEnc locationEnc sourceRectEnc colorEnc stencilForm location sourceRect color | stencilFormEnc := command second. locationEnc := command third. sourceRectEnc := command fourth. colorEnc := command fifth. 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: 'dgd 2/22/2003 18:44'! drawText: command | boundsEnc colorEnc text bounds color fontIndexEnc fontIndex | text := command second. boundsEnc := command third. fontIndexEnc := command fourth. colorEnc := command fifth. bounds := self class decodeRectangle: boundsEnc. fontIndex := self class decodeInteger: fontIndexEnc. color := self class decodeColor: colorEnc. self drawCommand: [:c | c drawString: text in: bounds font: (fonts at: fontIndex) color: color]! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'dgd 2/22/2003 18:44'! extentDepth: command | depth extent | extent := self class decodePoint: (command second). depth := self class decodeInteger: (command third). 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: 'sd 11/20/2005 21:25'! processCommand: command onForceDo: forceBlock "Decode the given string command and perform the required action. If the command is a forceToScreen command, also pass the forceBlock. The previous chained equality tests and conditionals have been replaced by a lookup table in my class variable DecodeTable, which is set in the class-side initialize method." | verb verbCode selector | command isEmpty ifTrue: [ ^self ]. verb := command first. verbCode := verb first. selector := DecodeTable at: (verbCode asciiValue + 1) ifAbsent: [ self error: 'unknown command: ', verb ]. "note: codeForce is the only odd one" ^(selector == #forceToScreen:) ifTrue: [ self forceToScreen: command withBlock: forceBlock ] ifFalse: [ self perform: selector withArguments: { command } ] ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'dgd 2/22/2003 18:44'! releaseImage: command | cacheID | CachedForms ifNil: [^self]. cacheID := self class decodeInteger: (command second). CachedForms at: cacheID put: nil! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'dgd 2/22/2003 18:44'! setClip: command | clipRectEnc | clipRectEnc := command second. clipRect := self class decodeRectangle: clipRectEnc! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'dgd 2/22/2003 18:45'! setTransform: command | transformEnc | transformEnc := command second. transform := self class decodeTransform: transformEnc! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'RAA 3/3/2001 18:29'! shadowColor: command drawingCanvas shadowColor: ( command second = '0' ifTrue: [nil] ifFalse: [self class decodeColor: command second] ) ! ! !CanvasDecoder methodsFor: 'decoding' stamp: 'sd 11/20/2005 21:25'! 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: 'initialization' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! connection: aStringSocket "set this terminal to talk over the given socket" connection := aStringSocket! ! !CanvasDecoder methodsFor: 'network' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'shutting down' stamp: 'ls 4/9/2000 14:33'! delete connection ifNotNil: [ connection destroy ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CanvasDecoder class instanceVariableNames: ''! !CanvasDecoder class methodsFor: 'class initialization' stamp: 'yo 12/15/2005 16:07'! initialize "CanvasDecoder initialize" "Set up my cache and decode table if necessary." CachedForms ifNil: [CachedForms := Array new: 100]. DecodeTable ifNotNil: [ ^self ]. DecodeTable _ Array new: 128. #((codeClip setClip:) (codeTransform setTransform:) (codeText drawText:) (codeLine drawLine:) (codeRect drawRect:) (codeBalloonRect drawBalloonRect:) (codeBalloonOval drawBalloonOval:) (codeInfiniteFill drawInfiniteFill:) (codeOval drawOval:) (codeImage drawImage:) (codeReleaseCache releaseImage:) (codePoly drawPoly:) (codeStencil drawStencil:) (codeForce forceToScreen:) (codeFont addFontToCache:) (codeFontSet addFontSetToCache:) (codeMultiText drawMultiText:) (codeTTCFont addTTCFontToCache:) (codeExtentDepth extentDepth:) (codeShadowColor shadowColor:)) do: [ :arr | (DecodeTable at: ((CanvasEncoder perform: arr first) asciiValue + 1)) ifNotNil: [self error: 'duplicated code']. DecodeTable at: ((CanvasEncoder perform: arr first) asciiValue + 1) put: arr second ]. ! ! !CanvasDecoder class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:26'! reinitialize "CanvasDecoder reinitialize" "Set up my cache and decode table, removing old contents." CachedForms := nil. DecodeTable := nil. self initialize. ! ! !CanvasDecoder class methodsFor: 'decode table modification' stamp: 'nk 6/25/2003 12:49'! decodeVerb: verb toSelector: selector "verb is a single character which will be ferformed by my instances using selector" DecodeTable at: verb asciiValue + 1 put: selector. ! ! !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: 'yo 12/17/2005 22:44'! decodeFont: fontString ^StrikeFont decodedFromRemoteCanvas: fontString. ! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'yo 12/17/2005 22:42'! decodeFontSet: fontString ^ StrikeFontSet decodedFromRemoteCanvas: fontString ! ! !CanvasDecoder class methodsFor: 'decoding' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! decodeTTCFont: fontString "Decode a string that consists of (e.g. 'ComicSansMS 12 0') into a proper instance." | first second | first := fontString indexOf: $ startingAt: 1. second := fontString indexOf: $ startingAt: first + 1. (first ~= 0 and: [second ~= 0]) ifTrue: [ ^ (TTCFont family: (fontString copyFrom: 1 to: (first - 1)) size: (fontString copyFrom: first + 1 to: second - 1) asNumber) emphasized: (fontString copyFrom: second + 1 to: fontString size) asNumber. ]. ^ TextStyle defaultFont. ! ! !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: 'nk 7/30/2004 21:51'! connection: aConnection ^(self new) connection: aConnection; yourself! ! Object subclass: #CanvasEncoder instanceVariableNames: 'connection lastClipRect lastTransform fontCache cachedObjects cachingEnabled' classVariableNames: 'SentTypesAndSizes SimpleCounters' poolDictionaries: '' category: 'Nebraska-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: '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: 'sd 11/20/2005 21:25'! 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: 'connection' stamp: 'RAA 8/1/2000 00:17'! backlog ^connection backlog! ! !CanvasEncoder methodsFor: 'connection' stamp: 'sd 11/20/2005 21:25'! connection: aStringSocket "set this connection to talk over the given socket" cachingEnabled := true. connection := aStringSocket! ! !CanvasEncoder methodsFor: 'connection' stamp: 'sd 11/20/2005 21:25'! 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: '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: 'sd 11/20/2005 21:25'! 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: 'yo 12/17/2005 22:44'! drawString: s from: firstIndex to: lastIndex in: boundsRect font: fontOrNil color: c | fontIndex str | fontIndex := self establishFont: (fontOrNil ifNil: [ TextStyle defaultFont ]). str _ s asString copyFrom: firstIndex to: lastIndex. str isWideString ifTrue: [ self sendCommand: { String with: CanvasEncoder codeMultiText. str asByteArray asString. self class encodeRectangle: boundsRect. self class encodeInteger: fontIndex. self class encodeColor: c } ] ifFalse: [ self sendCommand: { String with: CanvasEncoder codeText. str. self class encodeRectangle: boundsRect. self class encodeInteger: fontIndex. self class encodeColor: c } ]. ! ! !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: 'yo 10/6/2005 17:00'! image: aForm at: aPoint sourceRect: sourceRect rule: argRule | cacheID cacheNew cacheReply formToSend cacheEntry destRect visRect aFormArea d2 rule | rule _ argRule. "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). formToSend depth = 32 ifTrue: [formToSend _ formToSend asFormOfDepth: 16. rule = 24 ifTrue: [rule _ 25]]. ^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]). ]. (formToSend notNil and: [formToSend depth = 32]) ifTrue: [formToSend _ formToSend asFormOfDepth: 16. rule = 24 ifTrue: [rule _ 25]]. self image: formToSend at: aPoint sourceRect: sourceRect rule: rule cacheID: cacheID newToCache: cacheNew. ! ! !CanvasEncoder methodsFor: 'drawing' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'RAA 3/3/2001 18:26'! shadowColor: aFillStyle self sendCommand: { String with: CanvasEncoder codeShadowColor. aFillStyle ifNil: ['0'] ifNotNil: [aFillStyle encodeForRemoteCanvas]. }! ! !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: 'dgd 2/22/2003 19:01'! 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 second) + 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: 'sd 11/20/2005 21:25'! 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: '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: 'sd 11/20/2005 21:25'! sendFont: aFont atIndex: index "Transmits the given fint to the other side" | code | code := CanvasEncoder codeFont. aFont isTTCFont ifTrue: [code := CanvasEncoder codeTTCFont]. self sendCommand: { String with: code. self class encodeInteger: index. self class encodeFont: aFont }. ! ! !CanvasEncoder methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize cachingEnabled := true. fontCache := FontCache new: 5.! ! !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: 'objects from disk' stamp: 'sd 11/20/2005 21:25'! convertToCurrentVersion: varDict refStream: smartRefStrm cachingEnabled ifNil: [cachingEnabled := true]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !CanvasEncoder methodsFor: 'private' stamp: 'dgd 2/22/2003 14:41'! 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 first + 1. bucket at: 2 put: (bucket second) + (stringArray inject: 4 into: [:sum :array | sum + (array size + 4)])! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CanvasEncoder class instanceVariableNames: ''! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! beginStats SentTypesAndSizes := Dictionary new.! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! clearTestVars " CanvasEncoder clearTestVars " SimpleCounters := nil ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! explainTestVars " CanvasEncoder explainTestVars " | answer total oneBillion data putter nReps | SimpleCounters ifNil: [^ Beeper 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: 'sd 11/20/2005 21:26'! killStats SentTypesAndSizes := nil! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! nameForCode: aStringOrChar | ch | ch := (aStringOrChar isString) 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 codeTTCFont ifTrue: [^'codeTTCFont']. 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']. ch == self codeShadowColor ifTrue: [^'shadowColor']. ^'????' ! ! !CanvasEncoder class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! showStats " CanvasEncoder showStats " | answer bucket | SentTypesAndSizes ifNil: [^Beeper 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: 'sd 11/20/2005 21:26'! 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'. ! ! !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: 'yo 10/23/2002 23:41'! codeFontSet ^ $S ! ! !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: 'yo 10/23/2002 23:42'! codeMultiText ^ $c ! ! !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: 'RAA 3/3/2001 18:24'! codeShadowColor ^$s! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'ls 3/27/2000 22:34'! codeStencil ^$I! ! !CanvasEncoder class methodsFor: 'codes' stamp: 'yo 3/21/2003 23:00'! codeTTCFont ^ $T. ! ! !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: '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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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 10/20/1999 21:17'! on: connection ^self new connection: connection! ! Player subclass: #CardPlayer instanceVariableNames: 'privateMorphs' classVariableNames: '' poolDictionaries: '' category: 'EToys-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. privateMorphs -- OrderedCollection of objects specific to this card. Individual CardPlayer classes need to store the search results of any instances that are templates. As a hack, we use a class variable TemplateMatches in each individual class (CardPlayer21). It is initialized in #matchIndex:. TemplateMatches an IndentityDictionary of (aCardPlayer -> (list of matching cards, index in that list)) ! !CardPlayer methodsFor: 'as template' stamp: 'tk 5/29/2001 22:44'! matchIndex | tms | "Index of one we are looking at, in the cards that matched the last search with this template." tms _ self class classPool at: #TemplateMatches ifAbsent: [^ 0]. ^ (tms at: self ifAbsent: [#(0 0)]) second. ! ! !CardPlayer methodsFor: 'as template' stamp: 'tk 5/29/2001 22:47'! matchIndex: newPlace | tms pair | "One we are looking at, in cards that matched the last template search." tms _ self class classPool at: #TemplateMatches ifAbsent: [ self class addClassVarName: 'TemplateMatches'. self class classPool at: #TemplateMatches put: IdentityDictionary new]. pair _ tms at: self ifAbsent: [tms at: self put: (Array new: 2)]. pair at: 2 put: newPlace. newPlace = 0 ifTrue: [^ self]. pair first ifNil: [^ self]. (costume valueOfProperty: #myStack ifAbsent: [^ self]) goToCard: ((pair first "list") at: newPlace). self changed: #matchIndex. "update my selection" ! ! !CardPlayer methodsFor: 'as template' stamp: 'tk 5/31/2001 16:46'! matchNames | list str ll tms stk crds | "List of names of cards that matched the last template search." tms _ self class classPool at: #TemplateMatches ifAbsent: [^ #()]. list _ (tms at: self ifAbsent: [#(#() 0)]) first. stk _ costume valueOfProperty: #myStack ifAbsent: [nil]. crds _ stk ifNil: [#()] ifNotNil: [stk cards]. ^ list collect: [:cd | str _ ''. (ll _ cd allStringsAfter: nil) ifNotNil: [ str _ ll inject: '' into: [:strr :this | strr, this]]. (str copyFrom: 1 to: (30 min: str size)), '... (' , (crds indexOf: cd) printString, ')']. "Maybe include a card title?"! ! !CardPlayer methodsFor: 'as template' stamp: 'tk 5/29/2001 22:49'! results "Return my (cardlist index) pair from the last search" ^ (self class classPool at: #TemplateMatches ifAbsent: [^ Array new: 2]) at: self ! ! !CardPlayer methodsFor: 'card data' stamp: 'dgd 2/22/2003 14:43'! allStringsAfter: aText "return an OrderedCollection of strings of text in my instance vars. If aText is non-nil, begin with that object." | list ok instVarValue string | list := OrderedCollection new. ok := aText isNil. self class variableDocks do: [:vdock | instVarValue := self perform: vdock playerGetSelector. ok ifFalse: [ok := instVarValue == aText]. "and do this one too" ok ifTrue: [string := nil. instVarValue isString ifTrue: [string := instVarValue]. instVarValue isText ifTrue: [string := instVarValue string]. instVarValue isNumber ifTrue: [string := instVarValue printString]. instVarValue isMorph ifTrue: [string := instVarValue userString]. "not used" string ifNotNil: [string isString ifTrue: [list add: string] ifFalse: [list addAll: string]]]]. privateMorphs ifNotNil: [privateMorphs do: [:mm | list addAll: (mm allStringsAfter: nil)]]. ^list! ! !CardPlayer methodsFor: 'card data' stamp: 'tk 5/25/2001 17:42'! asKeys | keys kk vd gotData | "Take my fields, tokenize the text, and return as an array in the same order as variableDocks. Simple background fields on the top level. If no data, return nil." keys _ self class variableDocks copy. gotData _ false. 1 to: keys size do: [:ind | kk _ nil. vd _ self class variableDocks at: ind. vd type == #text ifTrue: [ kk _ (self perform: vd playerGetSelector) string findTokens: Character separators. kk isEmpty ifTrue: [kk _ nil] ifFalse: [gotData _ true]]. keys at: ind put: kk]. ^ gotData ifTrue: [keys] ifFalse: [nil]! ! !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 5/25/2001 17:02'! match: keys fields: docks | longString | "see if each key occurs in my corresponding text instance." keys withIndexDo: [:kk :ind | kk ifNotNil: [ longString _ (self perform: (docks at: ind) playerGetSelector) string. kk do: [:aKey | ((longString findString: aKey startingAt: 1 caseSensitive: false) > 0) ifFalse: [^ false]]]]. "all keys must match" ^ true! ! !CardPlayer methodsFor: 'card data' stamp: 'tk 1/30/2001 23:42'! privateMorphs ^ privateMorphs! ! !CardPlayer methodsFor: 'card data' stamp: 'tk 5/7/2001 15:51'! url "For now, don't know we could be on a server" ^ nil! ! !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: 'scripts-kernel' stamp: 'svp 10/15/2001 14:44'! renameScript: oldSelector newSelector: newSelector "Find all buttons that fire this script and tell them the new name" | stack | super renameScript: oldSelector newSelector: newSelector. costume allMorphsDo: [:mm | self retargetButton: mm oldSelector: oldSelector newSelector: newSelector]. stack _ costume valueOfProperty: #myStack. stack ifNotNil: [stack cards do: [:cc | cc privateMorphs do: [:pp | pp allMorphsDo: [:mm | self retargetButton: mm oldSelector: oldSelector newSelector: newSelector]]]]! ! !CardPlayer methodsFor: 'scripts-kernel' stamp: 'tk 9/29/2001 10:27'! retargetButton: mm oldSelector: oldSelector newSelector: newSelector "changing the name of a script -- tell any buttons that fire it" (mm respondsTo: #scriptSelector) ifTrue: [ mm scriptSelector == oldSelector ifTrue: [ mm scriptSelector: newSelector. mm setNameTo: newSelector]]. (mm respondsTo: #actionSelector) ifTrue: [ mm actionSelector == oldSelector ifTrue: [ mm target class == self class ifTrue: [ mm actionSelector: newSelector. mm setNameTo: newSelector]]]. ! ! !CardPlayer methodsFor: 'slots-kernel' stamp: 'sw 7/28/2004 21:03'! tileReferringToSelf "Answer a tile that refers to the receiver. For CardPlayer, want 'self', not the specific name of this card. Script needs to work for any card of the background." Preferences universalTiles ifTrue: [^ self universalTileReferringToSelf]. ^ TileMorph new setToReferTo: self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CardPlayer class instanceVariableNames: 'variableDocks'! !CardPlayer class methodsFor: 'compiling' 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: 'compiling' stamp: 'tk 9/28/2001 11:42'! 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]) or: [Preferences universalTiles]! ! !CardPlayer class methodsFor: 'instance creation' 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: 'slots' stamp: 'NS 1/28/2004 14:41'! compileAccessorsFor: varName "Compile instance-variable accessor methods for the given variable name" | nameString | nameString _ varName asString capitalized. self compileSilently: ('get', nameString, ' ^ ', varName) classified: 'access'. self compileSilently: ('set', nameString, ': val ', varName, ' _ val') classified: 'access'! ! !CardPlayer class methodsFor: 'slots' stamp: 'NS 1/30/2004 13:11'! removeAccessorsFor: varName "Remove the instance-variable accessor methods associated with varName" | nameString | nameString _ varName asString capitalized. self removeSelectorSilently: ('get', nameString) asSymbol. self removeSelectorSilently: ('set', nameString, ':') asSymbol! ! !CardPlayer class methodsFor: 'testing' 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 12/6/2001 20:36'! resortInstanceVariables: newList "Accept a new ordering for instance variables" variableDocks _ newList collect: [:aName | variableDocks detect: [:d | d variableName = aName]]. self setNewInstVarNames: newList asOrderedCollection. self newVariableDocks: variableDocks. ! ! !CardPlayer class methodsFor: 'user-defined inst vars' stamp: 'tk 8/26/2001 16:58'! 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. instVarString _ String streamContents: [:aStream | listOfStrings do: [:aString | aStream nextPutAll: aString; nextPut: $ ]]. superclass subclass: self name instanceVariableNames: instVarString classVariableNames: '' poolDictionaries: '' category: self categoryForUniclasses. firstAppearing do: [:newName | self compileAccessorsFor: newName]. ! ! !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: 'md 3/1/2006 09:17'! 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: [:dock | "Generate fresh slots from variable docks" 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: 'Compiler-ParseNodes'! !CascadeNode commentStamp: '' prior: 0! The first message has the common receiver, the rest have receiver == nil, which signifies cascading.! !CascadeNode methodsFor: 'accessing' stamp: 'tk 10/22/2000 16:55'! receiver ^receiver! ! !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: 'initialize-release'! receiver: receivingObject messages: msgs " Transcript show: 'abc'; cr; show: 'def' " receiver _ receivingObject. messages _ msgs! ! !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: '*eToys-tiles' stamp: 'RAA 2/22/2001 13:56'! asMorphicSyntaxIn: parent ^parent cascadeNode: self receiver: receiver messages: messages ! ! Object subclass: #Categorizer instanceVariableNames: 'categoryArray categoryStops elementArray' classVariableNames: 'Default NullCategory' poolDictionaries: '' category: 'Kernel-Classes'! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! addCategory: newCategory ^ self addCategory: newCategory before: nil ! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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]! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! allMethodSelectors "give a list of all method selectors." ^ elementArray copy sort! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:29'! categories "Answer an Array of categories (names)." categoryArray isNil ifTrue: [^ nil]. (categoryArray size = 1 and: [categoryArray first = Default & (elementArray size = 0)]) ifTrue: [^Array with: NullCategory]. ^categoryArray! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! categoryOfElement: element "Answer the category associated with the argument, element." | index | index _ self numberOfCategoryOfElement: element. index = 0 ifTrue: [^nil] ifFalse: [^categoryArray at: index]! ! !Categorizer methodsFor: 'accessing' stamp: 'hmm 2/25/2005 10:53'! 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 | | selectors | catSpec _ categorySpecs at: i. newCategories at: i put: catSpec first asSymbol. selectors := catSpec allButFirst collect: [:each | each isSymbol ifTrue: [each] ifFalse: [each printString asSymbol]]. selectors 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].! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! classify: element under: heading self classify: element under: heading suppressIfDefault: true! ! !Categorizer methodsFor: 'accessing' stamp: 'al 11/28/2005 22:05'! 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 basicRemoveElement: 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]. ((categoryArray includes: Default) and: [(self listAtCategoryNamed: Default) size = 0]) ifTrue: [self removeCategory: Default]. self assertInvariant.! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! classifyAll: aCollection under: heading aCollection do: [:element | self classify: element under: heading]! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:20'! elementCategoryDict | dict firstIndex lastIndex | elementArray isNil ifTrue: [^ nil]. dict _ Dictionary new: elementArray size. 1to: categoryStops size do: [:cat | firstIndex _ self firstIndexOfCategoryNumber: cat. lastIndex _ self lastIndexOfCategoryNumber: cat. firstIndex to: lastIndex do: [:el | dict at: (elementArray at: el) put: (categoryArray at: cat)]. ]. ^ dict.! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/15/2004 12:33'! isEmptyCategoryNamed: categoryName | i | i _ categoryArray indexOf: categoryName ifAbsent: [^false]. ^self isEmptyCategoryNumber: i! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/15/2004 12:33'! isEmptyCategoryNumber: anInteger | firstIndex lastIndex | (anInteger < 1 or: [anInteger > categoryStops size]) ifTrue: [^ true]. firstIndex _ self firstIndexOfCategoryNumber: anInteger. lastIndex _ self lastIndexOfCategoryNumber: anInteger. ^ firstIndex > lastIndex! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! listAtCategoryNamed: categoryName "Answer the array of elements associated with the name, categoryName." | i | i _ categoryArray indexOf: categoryName ifAbsent: [^Array new]. ^self listAtCategoryNumber: i! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/6/2004 13:51'! 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 | (anInteger < 1 or: [anInteger > categoryStops size]) ifTrue: [^ nil]. firstIndex _ self firstIndexOfCategoryNumber: anInteger. lastIndex _ self lastIndexOfCategoryNumber: anInteger. ^elementArray copyFrom: firstIndex to: lastIndex! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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] ! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/12/2004 20:50'! removeElement: element ^ self basicRemoveElement: element! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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]."! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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! ! !Categorizer methodsFor: 'accessing' stamp: 'NS 4/5/2004 17:44'! 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! ! !Categorizer methodsFor: 'fileIn/Out' stamp: 'NS 4/5/2004 17:44'! 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.! ! !Categorizer methodsFor: 'printing' stamp: 'NS 4/5/2004 17:44'! 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]! ! !Categorizer methodsFor: 'printing' stamp: 'NS 4/5/2004 17:44'! 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]! ! !Categorizer methodsFor: 'printing' stamp: 'lr 6/22/2005 08:12'! printString ^ String streamContents: [ :stream | self printOn: stream ].! ! !Categorizer methodsFor: 'private' stamp: 'dvf 8/11/2005 22:38'! assertInvariant self assert: (elementArray size = categoryStops last)! ! !Categorizer methodsFor: 'private' stamp: 'dvf 8/10/2005 14:19'! basicRemoveElement: 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. self assertInvariant.! ! !Categorizer methodsFor: 'private' stamp: 'NS 4/5/2004 17:44'! elementArray ^ elementArray! ! !Categorizer methodsFor: 'private' stamp: 'NS 4/6/2004 13:51'! firstIndexOfCategoryNumber: anInteger anInteger < 1 ifTrue: [^ nil]. ^ (anInteger > 1 ifTrue: [(categoryStops at: anInteger - 1) + 1] ifFalse: [1]).! ! !Categorizer methodsFor: 'private' stamp: 'NS 4/6/2004 13:52'! lastIndexOfCategoryNumber: anInteger anInteger > categoryStops size ifTrue: [^ nil]. ^ categoryStops at: anInteger! ! !Categorizer methodsFor: 'private' stamp: 'NS 4/5/2004 17:50'! setDefaultList: aSortedCollection categoryArray _ Array with: Default. categoryStops _ Array with: aSortedCollection size. elementArray _ aSortedCollection asArray! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Categorizer class instanceVariableNames: ''! !Categorizer class methodsFor: 'class initialization' stamp: 'NS 4/5/2004 17:44'! allCategory "Return a symbol that represents the virtual all methods category." ^ '-- all --' asSymbol! ! !Categorizer class methodsFor: 'class initialization' stamp: 'NS 4/5/2004 17:44'! default ^ Default! ! !Categorizer class methodsFor: 'class initialization' stamp: 'NS 4/6/2004 11:48'! initialize " self initialize " Default _ 'as yet unclassified' asSymbol. NullCategory _ 'no messages' asSymbol.! ! !Categorizer class methodsFor: 'class initialization' stamp: 'NS 4/5/2004 17:44'! nullCategory ^ NullCategory! ! !Categorizer class methodsFor: 'documentation' stamp: 'NS 4/5/2004 17:44'! 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." ! ! !Categorizer class methodsFor: 'housekeeping' stamp: 'NS 4/6/2004 11:48'! sortAllCategories self allSubInstances do: [:x | x sortCategories]! ! !Categorizer class methodsFor: 'instance creation' stamp: 'NS 4/5/2004 17:44'! defaultList: aSortedCollection "Answer an instance of me with initial elements from the argument, aSortedCollection." ^self new setDefaultList: aSortedCollection! ! Viewer subclass: #CategoryViewer instanceVariableNames: 'namePane chosenCategorySymbol' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'categories' stamp: 'sw 7/7/2004 21:59'! adjustColorsAndBordersWithin "Adjust the colors and borders of submorphs to suit current fashion" self allMorphsDo: [:aMorph | (aMorph isKindOf: ViewerLine) ifTrue: [aMorph layoutInset: 1]. (aMorph isKindOf: TilePadMorph) ifTrue: [aMorph beTransparent]. (aMorph isKindOf: PhraseTileMorph) ifTrue: [aMorph beTransparent. aMorph borderWidth: 0]. (aMorph isKindOf: TileMorph) ifTrue: [aMorph borderWidth: 1]]. self borderWidth: 1! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 8/22/2002 14:00'! beReplacedByCategory: chosenCategory "Be replaced by a category pane pointed at the chosen category" self outerViewer replaceSubmorph: self by: (self outerViewer categoryViewerFor: chosenCategory) ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/13/2001 19:55'! categoryNameWhoseTranslatedWordingIs: aWording "Answer the category name with the given wording" | result | result _ self currentVocabulary categoryWhoseTranslatedWordingIs: aWording. ^ result ifNotNil: [result categoryName] ifNil: [aWording]! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/12/2001 20:34'! categoryWhoseTranslatedWordingIs: aWording "Answer the elementCategory with the given wording" ^ self currentVocabulary categoryWhoseTranslatedWordingIs: aWording! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 10/30/2001 13:45'! categoryWording: aCategoryWording "Make the category with the given wording be my current one." | actualPane | (actualPane _ namePane renderedMorph) firstSubmorph contents: aCategoryWording; color: Color black. actualPane extent: actualPane firstSubmorph extent. self removeAllButFirstSubmorph. "that being the header" self addAllMorphs: ((scriptedPlayer tilePhrasesForCategory: chosenCategorySymbol inViewer: self)). self enforceTileColorPolicy. self secreteCategorySymbol. self world ifNotNil: [self world startSteppingSubmorphsOf: self]. self adjustColorsAndBordersWithin. owner ifNotNil: [owner isStandardViewer ifTrue: [owner fitFlap]]! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 3/2/2004 23:53'! chooseCategory "The mouse went down on my category-list control; pop up a list of category choices" | aList aMenu reply aLinePosition lineList | aList _ scriptedPlayer categoriesForViewer: self. aLinePosition _ aList indexOf: #miscellaneous ifAbsent: [nil]. aList _ aList collect: [:aCatSymbol | self currentVocabulary categoryWordingAt: aCatSymbol]. lineList _ aLinePosition ifNil: [#()] ifNotNil: [Array with: aLinePosition]. aList size == 0 ifTrue: [aList add: ScriptingSystem nameForInstanceVariablesCategory translated]. aMenu _ CustomMenu labels: aList lines: lineList selections: aList. reply _ aMenu startUpWithCaption: 'category' translated. reply ifNil: [^ self]. self chooseCategoryWhoseTranslatedWordingIs: reply asSymbol ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/13/2001 19:56'! chooseCategoryWhoseTranslatedWordingIs: aWording "Choose the category with the given wording" self chosenCategorySymbol: (self categoryNameWhoseTranslatedWordingIs: aWording) ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 5/29/2001 22:43'! chosenCategorySymbol "Answer the inherent category currently being shown, not necessarily the same as the translated word." ^ chosenCategorySymbol ifNil: [self secreteCategorySymbol]! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/13/2001 19:49'! chosenCategorySymbol: aCategorySymbol "Make the given category be my current one." | aCategory wording | chosenCategorySymbol _ aCategorySymbol. aCategory _ self currentVocabulary categoryAt: chosenCategorySymbol. wording _ aCategory ifNil: [aCategorySymbol] ifNotNil: [aCategory wording]. self categoryWording: wording! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 2/23/2001 22:29'! currentCategory "Answer the symbol representing the receiver's currently-selected category" | current | current _ namePane renderedMorph 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 9/13/2001 19:57'! nextCategory "Change the receiver to point at the category following the one currently seen" | aList anIndex newIndex already aChoice | aList _ (scriptedPlayer categoriesForViewer: self) collect: [:aCatSymbol | self currentVocabulary categoryWordingAt: aCatSymbol]. 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 chooseCategoryWhoseTranslatedWordingIs: aChoice! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/13/2001 19:53'! previousCategory "Change the receiver to point at the category preceding the one currently seen" | aList anIndex newIndex already aChoice | aList _ (scriptedPlayer categoriesForViewer: self) collect: [:aCatSymbol | self currentVocabulary categoryWordingAt: aCatSymbol]. 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 chooseCategoryWhoseTranslatedWordingIs: aChoice! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 9/13/2001 19:50'! secreteCategorySymbol "Set my chosenCategorySymbol by translating back from its representation in the namePane. Answer the chosenCategorySymbol" | aCategory | aCategory _ self currentVocabulary categoryWhoseTranslatedWordingIs: self currentCategory. ^ chosenCategorySymbol _ aCategory ifNotNil: [aCategory categoryName] ifNil: [self currentCategory]! ! !CategoryViewer methodsFor: 'categories' stamp: 'nk 9/2/2004 19:37'! showCategoriesFor: aSymbol "Put up a pop-up list of categories in which aSymbol is filed; replace the receiver with a CategoryViewer for the one the user selects, if any" | allCategories aVocabulary hits meths chosen | aVocabulary _ self currentVocabulary. allCategories _ scriptedPlayer categoriesForVocabulary: aVocabulary limitClass: ProtoObject. hits _ allCategories select: [:aCategory | meths _ aVocabulary allMethodsInCategory: aCategory forInstance: scriptedPlayer ofClass: scriptedPlayer class. meths includes: aSymbol]. hits isEmpty ifTrue: [ ^self ]. chosen _ (SelectionMenu selections: hits) startUp. chosen isEmptyOrNil ifFalse: [self outerViewer addCategoryViewerFor: chosen atEnd: true] ! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 10/24/1998 14:25'! upArrowHit self nextCategory! ! !CategoryViewer methodsFor: 'categories' stamp: 'sw 5/29/2001 11:41'! updateCategoryNameTo: aName "Update the category name, because of a language change." | actualPane | (actualPane _ namePane firstSubmorph) contents: aName; color: Color black. namePane extent: actualPane extent. self world ifNotNil: [self world startSteppingSubmorphsOf: self] ! ! !CategoryViewer methodsFor: 'editing pane' stamp: 'nb 6/17/2003 12:25'! contents: c notifying: k "later, spruce this up so that it can accept input such as new method source" Beeper beep. ^ false! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 7/25/2004 15:37'! addIsOverColorDetailTo: aRow "Special-casee code for the boolean-valued phrase variously known as is-over-color or sees-color." | clrTile | aRow addMorphBack: (Morph new color: self color; extent: 2@10). "spacer" aRow addMorphBack: (clrTile _ Color blue newTileMorphRepresentative). "The following commented-out code put a readout up; the readout was very nice, but was very consumptive of cpu time, which is why the is-over-color tile got removed from the viewer long ago. Now is-over-color is reinstated to the viewer, minus the expensive readout..." " aRow addMorphBack: (AlignmentMorph new beTransparent). 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: 'md 11/14/2003 16:20'! addOverlapsDetailTo: aRow "Disreputable magic: add necessary items to a viewer row abuilding for the overlaps phrase" aRow addMorphBack: (Morph new color: self color; extent: 2@10). "spacer" aRow addMorphBack: self tileForSelf. aRow addMorphBack: (AlignmentMorph new beTransparent). "flexible spacer" ! ! !CategoryViewer methodsFor: 'entries' stamp: 'md 11/14/2003 16:21'! addTouchesADetailTo: aRow | clrTile | 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: 'md 11/14/2003 16:20'! 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. If no such button is appropriate, answer a transparent graphic that fills the same space." | aButton | (self wantsRowMenuFor: aScriptOrSlotSymbol) ifFalse: ["Fill the space with sweet nothing, since there is no meaningful menu to offer" aButton _ RectangleMorph new beTransparent extent: (17@20). aButton borderWidth: 0. ^ aButton]. aButton _ IconicButton new labelGraphic: Cursor menu. aButton target: scriptedPlayer; actionSelector: #infoFor:inViewer:; arguments: (Array with:aScriptOrSlotSymbol with: self); color: Color transparent; borderWidth: 0; shedSelvedge; actWhen: #buttonDown. aButton setBalloonText: 'Press here to get a menu' translated. ^ aButton! ! !CategoryViewer methodsFor: 'entries' stamp: 'yo 8/18/2005 10:37'! phraseForCommandFrom: aMethodInterface "Answer a phrase for the non-slot-like command represented by aMethodInterface - classic tiles" | aRow resultType cmd names argType argTile selfTile aPhrase balloonTextSelector stat inst aDocString universal tileBearingHelp | aDocString _ aMethodInterface documentation. aDocString = 'no help available' ifTrue: [aDocString _ nil]. names _ scriptedPlayer class namedTileScriptSelectors. resultType _ aMethodInterface resultType. cmd _ aMethodInterface selector. (universal _ scriptedPlayer isUniversalTiles) ifTrue: [aPhrase _ scriptedPlayer universalTilesForInterface: aMethodInterface] ifFalse: [cmd numArgs == 0 ifTrue: [aPhrase _ PhraseTileMorph new vocabulary: self currentVocabulary. aPhrase setOperator: cmd type: resultType rcvrType: #Player] ifFalse: ["only one arg supported in classic tiles, so if this is fed with a selector with > 1 arg, results will be very strange" argType _ aMethodInterface typeForArgumentNumber: 1. aPhrase _ PhraseTileMorph new vocabulary: self currentVocabulary. (self isSpecialPatchReceiver: scriptedPlayer and: cmd) ifTrue: [ aPhrase setOperator: cmd type: resultType rcvrType: #Patch argType: argType. ] ifFalse: [ aPhrase setOperator: cmd type: resultType rcvrType: #Player argType: argType. ]. (self isSpecialPatchCase: scriptedPlayer and: cmd) ifTrue: [ argTile _ (Vocabulary vocabularyForType: argType) defaultArgumentTileFor: scriptedPlayer. ] ifFalse: [ argTile _ ScriptingSystem tileForArgType: argType. ]. (#(bounce: wrap:) includes: cmd) ifTrue: ["help for the embattled bj" argTile setLiteral: 'silence'; updateLiteralLabel]. argTile position: aPhrase lastSubmorph position. aPhrase lastSubmorph addMorph: argTile]]. (scriptedPlayer slotInfo includesKey: cmd) ifTrue: [balloonTextSelector _ #userSlot]. (scriptedPlayer belongsToUniClass and: [scriptedPlayer class includesSelector: cmd]) ifTrue: [aDocString ifNil: [aDocString _ (scriptedPlayer class userScriptForPlayer: scriptedPlayer selector: cmd) documentation]. aDocString ifNil: [balloonTextSelector _ #userScript]]. tileBearingHelp _ universal ifTrue: [aPhrase submorphs second] ifFalse: [aPhrase operatorTile]. aDocString ifNotNil: [tileBearingHelp setBalloonText: aDocString] ifNil: [balloonTextSelector ifNil: [tileBearingHelp setProperty: #inherentSelector toValue: cmd. balloonTextSelector _ #methodComment]. tileBearingHelp balloonTextSelector: balloonTextSelector]. aPhrase markAsPartsDonor. cmd == #emptyScript ifTrue: [aPhrase setProperty: #newPermanentScript toValue: true. aPhrase setProperty: #newPermanentPlayer toValue: scriptedPlayer. aPhrase submorphs second setBalloonText: 'drag and drop to add a new script' translated]. universal ifFalse: [selfTile _ self tileForSelf. selfTile position: aPhrase firstSubmorph position. aPhrase firstSubmorph addMorph: selfTile]. aRow _ ViewerLine newRow borderWidth: 0; color: self color. aRow elementSymbol: cmd asSymbol. aRow addMorphBack: (ScriptingSystem tryButtonFor: aPhrase). aRow addMorphBack: (Morph new extent: 2@2; beTransparent). aRow addMorphBack: (self infoButtonFor: cmd). aRow addMorphBack: aPhrase. aPhrase on: #mouseEnter send: #addCommandFeedback to: aRow. aPhrase on: #mouseLeave send: #removeHighlightFeedback to: aRow. aPhrase on: #mouseLeaveDragging send: #removeHighlightFeedback to: aRow. (names includes: cmd) ifTrue: [aPhrase userScriptSelector: cmd. cmd numArgs == 0 ifTrue: [aPhrase beTransparent. aRow addMorphBack: AlignmentMorph newVariableTransparentSpacer. aRow addMorphBack: (stat _ (inst _ scriptedPlayer scriptInstantiationForSelector: cmd) statusControlMorph). inst updateStatusMorph: stat]]. aRow beSticky; disableDragNDrop. ^ aRow! ! !CategoryViewer methodsFor: 'entries' stamp: 'yo 3/7/2005 08:45'! phraseForVariableFrom: aMethodInterface "Return a structure consisting of tiles and controls and a readout representing a 'variable' belonging to the player, complete with an appropriate readout when indicated. Functions in both universalTiles mode and classic mode. Slightly misnamed in that this path is used for any methodInterface that indicates an interesting resultType." | anArrow slotName getterButton cover inner aRow doc setter tryer universal hotTileForSelf spacer buttonFont varName | aRow _ ViewerLine newRow color: self color; beSticky; elementSymbol: (slotName _ aMethodInterface selector); wrapCentering: #center; cellPositioning: #leftCenter. (universal _ scriptedPlayer isUniversalTiles) ifFalse: [buttonFont _ Preferences standardEToysFont. aRow addMorphBack: (Morph new color: self color; extent: (((buttonFont widthOfString: '!!') + 8) @ (buttonFont height + 6)); yourself)]. "spacer" aRow addMorphBack: (self infoButtonFor: slotName). aRow addMorphBack: (Morph new color: self color; extent: 0@10). " vertical spacer" universal ifTrue: [inner _ scriptedPlayer universalTilesForGetterOf: aMethodInterface. cover _ Morph new color: Color transparent. cover extent: inner fullBounds extent. (getterButton _ cover copy) addMorph: cover; addMorphBack: inner. cover on: #mouseDown send: #makeUniversalTilesGetter:event:from: to: self withValue: aMethodInterface. aRow addMorphFront: (tryer _ ScriptingSystem tryButtonFor: inner). tryer color: tryer color lighter lighter] ifFalse: [hotTileForSelf _ self tileForSelf bePossessive. hotTileForSelf on: #mouseDown send: #makeGetter:event:from: to: self withValue: (Array with: aMethodInterface selector with: aMethodInterface resultType). aRow addMorphBack: hotTileForSelf. aRow addMorphBack: (spacer _ Morph new color: self color; extent: 2@10). spacer on: #mouseEnter send: #addGetterFeedback to: aRow. spacer on: #mouseLeave send: #removeHighlightFeedback to: aRow. spacer on: #mouseLeaveDragging send: #removeHighlightFeedback to: aRow. spacer on: #mouseDown send: #makeGetter:event:from: to: self withValue: (Array with: aMethodInterface selector with: aMethodInterface resultType). hotTileForSelf on: #mouseEnter send: #addGetterFeedback to: aRow. hotTileForSelf on: #mouseLeave send: #removeHighlightFeedback to: aRow. hotTileForSelf on: #mouseLeaveDragging send: #removeHighlightFeedback to: aRow. getterButton _ self getterButtonFor: aMethodInterface selector type: aMethodInterface resultType]. aRow addMorphBack: getterButton. getterButton on: #mouseEnter send: #addGetterFeedback to: aRow. getterButton on: #mouseLeave send: #removeHighlightFeedback to: aRow. getterButton on: #mouseLeaveDragging send: #removeHighlightFeedback to: aRow. (doc _ aMethodInterface documentation) ifNotNil: [getterButton setBalloonText: doc]. (scriptedPlayer slotInfo includesKey: (varName _ Utilities inherentSelectorForGetter: slotName)) "user slot" ifTrue: ["aRow addTransparentSpacerOfSize: 3@0. aRow addMorphBack: (self slotTypeMenuButtonFor: varName)"]. universal ifFalse: [(slotName == #seesColor:) ifTrue: [self addIsOverColorDetailTo: aRow. ^ aRow]. (slotName == #touchesA:) ifTrue: [self addTouchesADetailTo: aRow. ^ aRow]. (slotName == #overlaps: or: [ slotName == #overlapsAny:]) ifTrue: [self addOverlapsDetailTo: aRow. ^ aRow]]. aRow addMorphBack: (AlignmentMorph new beTransparent). "flexible spacer" (setter _ aMethodInterface companionSetterSelector) ifNotNil: [aRow addMorphBack: (Morph new color: self color; extent: 2@10). " spacer" anArrow _ universal ifTrue: [self arrowSetterButton: #newMakeSetterFromInterface:evt:from: args: aMethodInterface] ifFalse: [self arrowSetterButton: #makeSetter:from:forPart: args: (Array with: slotName with: aMethodInterface resultType)]. anArrow beTransparent. universal ifFalse: [anArrow on: #mouseEnter send: #addSetterFeedback to: aRow. anArrow on: #mouseLeave send: #removeHighlightFeedback to: aRow. anArrow on: #mouseLeaveDragging send: #removeHighlightFeedback to: aRow]. aRow addMorphBack: anArrow]. (#(color:sees: playerSeeingColor copy touchesA: overlaps: getTurtleAt: getTurtleOf:) includes: slotName) ifFalse: [(universal and: [slotName == #seesColor:]) ifFalse: [aMethodInterface wantsReadoutInViewer ifTrue: [aRow addMorphBack: (self readoutFor: slotName type: aMethodInterface resultType readOnly: setter isNil getSelector: aMethodInterface selector putSelector: setter)]]]. anArrow ifNotNil: [anArrow step]. ^ aRow! ! !CategoryViewer methodsFor: 'entries' stamp: 'sw 7/4/2004 01:09'! readoutFor: partName type: partType readOnly: readOnly getSelector: getSelector putSelector: putSelector "Answer a readout morph for the given part" | readout delta | readout _ (Vocabulary vocabularyForType: partType) updatingTileForTarget: scriptedPlayer partName: partName getter: getSelector setter: putSelector. (partType == #Number) ifTrue: [(delta _ scriptedPlayer arrowDeltaFor: getSelector) = 1 ifFalse: [readout setProperty: #arrowDelta toValue: delta]. scriptedPlayer setFloatPrecisionFor: readout updatingStringMorph]. readout step. ^ readout! ! !CategoryViewer methodsFor: 'entries' stamp: 'nk 10/14/2004 10:54'! wantsRowMenuFor: aSymbol "Answer whether a viewer row for the given symbol should have a menu button on it" | elementType | true ifTrue: [^ true]. "To allow show categories item. So someday this method can be removed, and its sender can stop sending it..." elementType _ scriptedPlayer elementTypeFor: aSymbol vocabulary: self currentVocabulary. (elementType == #systemScript) ifTrue: [^ false]. ((elementType == #systemSlot) and: [#(color:sees: touchesA: overlaps: overlapsAny:) includes: aSymbol]) ifTrue: [^ false]. ^ true! ! !CategoryViewer methodsFor: 'e-toy support' stamp: 'sw 9/13/2001 19:16'! adoptVocabulary: aVocabulary "Adopt the given vocabulary as the one used in this viewer." | aCategory | chosenCategorySymbol ifNil: [^ self delete]. aCategory _ aVocabulary categoryAt: chosenCategorySymbol. aCategory ifNil: [self delete] ifNotNil: [self updateCategoryNameTo: aCategory wording]. super adoptVocabulary: aVocabulary! ! !CategoryViewer methodsFor: 'e-toy support' stamp: 'mir 7/15/2004 15:19'! localeChanged "Update myself to reflect the change in locale" chosenCategorySymbol ifNil: [^ self delete]. self updateCategoryNameTo: ((self currentVocabulary ifNil: [Vocabulary eToyVocabulary]) categoryWordingAt: chosenCategorySymbol)! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'dgd 9/1/2003 13:51'! 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.' translated. m on: #mouseDown send: sel to: self withValue: argArray. ^ m ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'dgd 9/1/2003 13:51'! 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.' translated. m on: #mouseDown send: #makeSetter:event:from: to: self withValue: (Array with: partName with: partType). ^ m ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 9/27/2001 04:23'! getterButtonFor: getterSelector type: partType "Answer a classic-tiles getter button for a part of the given name" | m inherent wording | m _ TileMorph new adoptVocabulary: self currentVocabulary. inherent _ Utilities inherentSelectorForGetter: getterSelector. wording _ (scriptedPlayer slotInfo includesKey: inherent) ifTrue: [inherent] ifFalse: [self currentVocabulary tileWordingForSelector: getterSelector]. m setOperator: getterSelector andUseWording: wording. m typeColor: (ScriptingSystem colorForType: partType). m on: #mouseDown send: #makeGetter:event:from: to: self withValue: (Array with: getterSelector with: partType). ^ m! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'yo 3/24/2005 13:12'! getterTilesFor: getterSelector type: aType "Answer classic getter for the given name/type" "aPhrase _ nil, assumed" | selfTile selector aPhrase | (#(#color:sees: #colorSees) includes: getterSelector) ifTrue: [aPhrase := self colorSeesPhrase]. (#(#getPatchValueIn:) includes: getterSelector) ifTrue: [aPhrase _ self patchValuePhrase]. (#(#getRedComponentIn:) includes: getterSelector) ifTrue: [aPhrase _ self colorComponentPhraseFor: #red]. (#(#getGreenComponentIn:) includes: getterSelector) ifTrue: [aPhrase _ self colorComponentPhraseFor: #green]. (#(#getBlueComponentIn:) includes: getterSelector) ifTrue: [aPhrase _ self colorComponentPhraseFor: #blue]. (#(#getUphillIn:) includes: getterSelector) ifTrue: [aPhrase _ self patchUphillPhrase]. (#(#bounceOn:) includes: getterSelector) ifTrue: [aPhrase _ self bounceOnPhrase]. (#(#bounceOn:color: #bounceOnColor:) includes: getterSelector) ifTrue: [aPhrase _ self bounceOnColorPhrase]. (getterSelector = #getDistanceTo:) ifTrue: [aPhrase _ self distanceToPhrase]. (getterSelector = #getAngleTo:) ifTrue: [aPhrase _ self angleToPhrase]. (getterSelector = #getTurtleOf:) ifTrue: [aPhrase _ self turtleOfPhrase]. (#(#seesColor: #isOverColor) includes: getterSelector) ifTrue: [aPhrase := self seesColorPhrase]. (#(#overlaps: #overlaps) includes: getterSelector) ifTrue: [aPhrase := self overlapsPhrase]. (#(#overlapsAny: #overlapsAny) includes: getterSelector) ifTrue: [aPhrase := self overlapsAnyPhrase]. (#(#touchesA: #touchesA) includes: getterSelector) ifTrue: [aPhrase := self touchesAPhrase]. aPhrase ifNil: [aPhrase := PhraseTileMorph new setSlotRefOperator: getterSelector asSymbol type: aType]. selfTile := self tileForSelf bePossessive. selfTile position: aPhrase firstSubmorph position. aPhrase firstSubmorph addMorph: selfTile. selector := aPhrase submorphs second. (#(#getPatchValueIn: getUphillIn:) includes: getterSelector) ifFalse: [ (Vocabulary vocabularyNamed: aType capitalized) ifNotNilDo: [:aVocab | aVocab wantsSuffixArrow ifTrue: [selector addSuffixArrow]]. ]. selector updateLiteralLabel. aPhrase enforceTileColorPolicy. ^aPhrase! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 4/6/2001 00:59'! makeGetter: args event: evt from: aMorph "Hand the user tiles representing a classic getter on the slot represented by aMorph" | tiles | tiles _ self getterTilesFor: args first type: args second. owner ifNotNil: [self primaryHand attachMorph: tiles] ifNil: [^ tiles] ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'ar 3/18/2001 17:26'! makeGetter: arg1 from: arg2 forPart: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self makeGetter: arg1 event: arg2 from: arg3! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'yo 8/10/2005 17:29'! makeSetterForColorComponent: selectorAndTypePair componentName: componentName event: evt from: aMorph | argType m argTile selfTile argValue actualGetter | argType := selectorAndTypePair second. componentName = #red ifTrue: [actualGetter _ #setRedComponentIn:]. componentName = #green ifTrue: [actualGetter _ #setGreenComponentIn:]. componentName = #blue ifTrue: [actualGetter _ #setBlueComponentIn:]. m := PhraseTileMorph new setColorComponentRoot: actualGetter componentName: componentName type: #command rcvrType: #Patch argType: argType vocabulary: self currentVocabulary. argValue := self scriptedPlayer perform: selectorAndTypePair first asSymbol with: nil. (argValue isKindOf: Player) ifTrue: [argTile := argValue tileReferringToSelf] ifFalse: [argTile := ScriptingSystem tileForArgType: argType. (argType == #Number and: [argValue isNumber]) ifTrue: [(scriptedPlayer decimalPlacesForGetter: actualGetter) ifNotNilDo: [:places | (argTile findA: UpdatingStringMorph) decimalPlaces: places]]. 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. m submorphs second setPatchDefaultTo: scriptedPlayer defaultPatchPlayer. m openInHand! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'yo 3/31/2005 15:54'! makeSetterForGetPatchValue: selectorAndTypePair event: evt from: aMorph | argType m argTile selfTile argValue actualGetter | argType := selectorAndTypePair second. actualGetter := #patchValueIn:. m := PhraseTileMorph new setPixelValueRoot: actualGetter type: #command rcvrType: #Player argType: argType vocabulary: self currentVocabulary. argValue := self scriptedPlayer perform: selectorAndTypePair first asSymbol with: nil. (argValue isPlayerLike) ifTrue: [argTile := argValue tileReferringToSelf] ifFalse: [argTile := ScriptingSystem tileForArgType: argType. (argType == #Number and: [argValue isNumber]) ifTrue: [(scriptedPlayer decimalPlacesForGetter: actualGetter) ifNotNilDo: [:places | (argTile findA: UpdatingStringMorph) decimalPlaces: places]]. 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. m submorphs second setPatchDefaultTo: scriptedPlayer defaultPatchPlayer. m openInHand! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'yo 7/22/2005 16:52'! makeSetter: selectorAndTypePair event: evt from: aMorph "Classic tiles: make a Phrase that comprises a setter of a slot, and hand it to the user." | argType m argTile selfTile argValue actualGetter | selectorAndTypePair first = #getPatchValueIn: ifTrue: [^ self makeSetterForGetPatchValue: selectorAndTypePair event: evt from: aMorph]. selectorAndTypePair first = #getRedComponentIn: ifTrue: [^ self makeSetterForColorComponent: selectorAndTypePair componentName: #red event: evt from: aMorph]. selectorAndTypePair first = #getBlueComponentIn: ifTrue: [^ self makeSetterForColorComponent: selectorAndTypePair componentName: #blue event: evt from: aMorph]. selectorAndTypePair first = #getGreenComponentIn: ifTrue: [^ self makeSetterForColorComponent: selectorAndTypePair componentName: #green event: evt from: aMorph]. argType := selectorAndTypePair second. actualGetter := selectorAndTypePair first asSymbol. m := PhraseTileMorph new setAssignmentRoot: (Utilities inherentSelectorForGetter: actualGetter) type: #command rcvrType: #Player argType: argType vocabulary: self currentVocabulary. argValue := self scriptedPlayer perform: selectorAndTypePair first asSymbol. argValue isPlayerLike ifTrue: [argTile := argValue tileReferringToSelf] ifFalse: [argTile := ScriptingSystem tileForArgType: argType. (argType == #Number and: [argValue isNumber]) ifTrue: [(scriptedPlayer decimalPlacesForGetter: actualGetter) ifNotNilDo: [:places | (argTile findA: UpdatingStringMorph) decimalPlaces: places]]. 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. m openInHand! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'ar 3/18/2001 17:27'! makeSetter: arg1 from: arg2 forPart: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self makeSetter: arg1 event: arg2 from: arg3! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'RAA 4/6/2001 13:28'! makeUniversalTilesGetter: aMethodInterface event: evt from: aMorph "Button in viewer performs this to make a universal-tiles getter and attach it to hand." | newTiles | newTiles _ self newGetterTilesFor: scriptedPlayer methodInterface: aMethodInterface. newTiles setProperty: #beScript toValue: true. owner ifNotNil: [ActiveHand attachMorph: newTiles. newTiles align: newTiles topLeft with: evt hand position + (7@14)] ifNil: [^ newTiles] ! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'nk 10/14/2004 10:53'! newGetterTilesFor: aPlayer methodInterface: aMethodInterface "Return universal tiles for a getter on this property. Record who self is." | ms argTile argArray | ms _ MessageSend receiver: aPlayer selector: aMethodInterface selector arguments: #(). "Handle three idiosyncratic cases..." aMethodInterface selector == #color:sees: ifTrue: [argTile _ ScriptingSystem tileForArgType: #Color. argArray _ Array with: argTile colorSwatch color with: argTile colorSwatch color copy. ms arguments: argArray]. aMethodInterface selector == #seesColor: ifTrue: [argTile _ ScriptingSystem tileForArgType: #Color. ms arguments: (Array with: argTile colorSwatch color)]. (#(touchesA: overlaps: overlapsAny:) includes: aMethodInterface selector) ifTrue: [argTile _ ScriptingSystem tileForArgType: #Player. ms arguments: (Array with: argTile actualObject)]. ^ ms asTilesIn: aPlayer class globalNames: (aPlayer class officialClass ~~ CardPlayer) "For CardPlayers, use 'self'. For others, name it, and use its name."! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 3/28/2001 14:17'! newMakeGetter: arg event: evt from: aMorph "Button in viewer performs this to makea universal-tiles header tile and attach to hand." ^ self makeUniversalTilesGetter: arg event: evt from: aMorph! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 3/28/2001 13:04'! newMakeGetter: arg1 from: arg2 forMethodInterface: arg3 "Button in viewer performs this to make a new style tile and attach to hand. (Reorder the arguments for existing event handlers)" (arg3 isMorph and: [arg3 eventHandler notNil]) ifTrue: [arg3 eventHandler fixReversedValueMessages]. ^ self makeUniversalTilesGetter: arg1 event: arg2 from: arg3! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'ar 3/18/2001 17:27'! newMakeGetter: arg1 from: arg2 forPart: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self newMakeGetter: arg1 event: arg2 from: arg3! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'ar 3/17/2001 14:17'! newMakeSetter: aSpec event: evt from: aMorph "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: 'ar 3/18/2001 17:27'! newMakeSetter: arg1 from: arg2 forPart: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self newMakeSetter: arg1 event: arg2 from: arg3! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'RAA 4/6/2001 13:28'! newMakeSetterFromInterface: aMethodInterface evt: evt from: aMorph "Button in viewer performs this to make a new style tile and attach to hand." | m | m _ self newSetterTilesFor: scriptedPlayer methodInterface: aMethodInterface. m setProperty: #beScript toValue: true. 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 9/30/2001 11:20'! newSetterTilesFor: aPlayer methodInterface: aMethodInterface "Return universal tiles for a setter on this property. Record who self is." | ms argValue makeSelfGlobal phrase | argValue _ aPlayer perform: aMethodInterface selector. ms _ MessageSend receiver: aPlayer selector: aMethodInterface companionSetterSelector arguments: (Array with: argValue). makeSelfGlobal _ aPlayer class officialClass ~~ CardPlayer. phrase _ ms asTilesIn: aPlayer class globalNames: makeSelfGlobal. "For CardPlayers, use 'self'. For others, name it, and use its name." makeSelfGlobal ifFalse: [phrase setProperty: #scriptedPlayer toValue: aPlayer]. ^ phrase! ! !CategoryViewer methodsFor: 'get/set slots' stamp: 'sw 11/16/2001 14:44'! newTilesFor: aPlayer setter: aSpec | ms argValue | "Return universal tiles for a getter on this property. Record who self is." argValue _ aPlayer perform: (Utilities getterSelectorFor: aSpec second asSymbol). ms _ MessageSend receiver: aPlayer selector: aSpec ninth arguments: (Array with: argValue). ^ ms asTilesIn: aPlayer class globalNames: (aPlayer class officialClass ~~ CardPlayer) "For CardPlayers, use 'self'. For others, name it, and use its name."! ! !CategoryViewer methodsFor: 'header pane' stamp: 'sw 8/31/2004 14:01'! addHeaderMorph "Add the header at the top of the viewer, with a control for choosing the category, etc." | header aButton | header _ AlignmentMorph newRow color: self color; wrapCentering: #center; cellPositioning: #leftCenter. aButton _ self tanOButton. header addMorph: aButton. aButton actionSelector: #delete; setBalloonText: 'remove this pane from the screen don''t worry -- nothing will be lost!!.' translated. self maybeAddArrowsTo: header. header beSticky. self addMorph: header. self addNamePaneTo: header. chosenCategorySymbol _ #basic! ! !CategoryViewer methodsFor: 'header pane' stamp: 'nk 7/12/2004 23:15'! addNamePaneTo: header "Add the namePane, which may be a popup or a type-in depending on the type of CategoryViewer" | aButton | namePane := RectangleMorph newSticky color: Color brown veryMuchLighter. namePane borderWidth: 0. aButton := (StringButtonMorph contents: '-----' font: Preferences standardButtonFont) 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)' translated. header addMorphBack: namePane. (namePane isKindOf: RectangleMorph) ifTrue: [namePane addDropShadow. namePane shadowColor: Color gray] ! ! !CategoryViewer methodsFor: 'header pane' stamp: 'dgd 9/1/2003 13:47'! maybeAddArrowsTo: header "Maybe add up/down arrows to the header" | wrpr | header addTransparentSpacerOfSize: 5@5. header addUpDownArrowsFor: self. (wrpr _ header submorphs last) submorphs second setBalloonText: 'previous category' translated. wrpr submorphs first setBalloonText: 'next category' translated! ! !CategoryViewer methodsFor: 'initialization' stamp: 'sw 8/22/2002 23:08'! establishContents "Perform any initialization steps that needed to wait until I am installed in my outer viewer"! ! !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: 'dgd 8/16/2004 21:51'! 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 chooseCategoryWhoseTranslatedWordingIs: aChoice ! ! !CategoryViewer methodsFor: 'initialization' stamp: 'sw 8/17/2002 01:23'! setCategorySymbolFrom: aChoice "Set my category symbol" self chosenCategorySymbol: aChoice asSymbol ! ! !CategoryViewer methodsFor: 'macpal' stamp: 'sw 5/4/2001 05:24'! currentVocabulary "Answer the vocabulary currently installed in the viewer. The outer StandardViewer object holds this information." | outerViewer | ^ (outerViewer _ self outerViewer) ifNotNil: [outerViewer currentVocabulary] ifNil: [(self world ifNil: [ActiveWorld]) currentVocabularyFor: scriptedPlayer]! ! !CategoryViewer methodsFor: 'scripting' stamp: 'sw 9/12/2001 22:58'! isTileScriptingElement "Answer whether the receiver is a tile-scripting element" ^ true! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 9/28/2004 18:24'! booleanPhraseForBounceOnColorOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setBounceOnColorOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. "getterPhrase submorphs second setPatchDefaultTo: (scriptedPlayer defaultPatchPlayer)." getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 9/27/2004 15:00'! booleanPhraseForBounceOnOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setBounceOnOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. "getterPhrase submorphs second setPatchDefaultTo: (scriptedPlayer defaultPatchPlayer)." getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 6/17/2004 10:02'! booleanPhraseForGetAngleToOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setAngleToOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setTurtleDefaultTo: scriptedPlayer. getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 10/13/2004 01:39'! booleanPhraseForGetColorComponentOfType: retrieverType componentName: componentName 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setGetColorComponentOperator: retrieverOp componentName: componentName type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setPatchDefaultTo: (scriptedPlayer defaultPatchPlayer). getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 6/15/2004 18:45'! booleanPhraseForGetDistanceToOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setDistanceToOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setTurtleDefaultTo: scriptedPlayer. getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 6/15/2004 16:38'! booleanPhraseForGetPatchValueOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setGetPixelOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setPatchDefaultTo: (scriptedPlayer defaultPatchPlayer). getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 10/9/2004 10:39'! booleanPhraseForGetTurtleAtOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setTurtleAtOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setTurtleDefaultTo: scriptedPlayer. getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 3/7/2005 08:41'! booleanPhraseForGetTurtleOfOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setTurtleOfOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. "getterPhrase submorphs second setTurtleDefaultTo: scriptedPlayer." getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 6/15/2004 16:39'! booleanPhraseForGetUpHillOfType: 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setUpHillOperator: retrieverOp type: retrieverType rcvrType: #Player argType: nil. getterPhrase submorphs third delete. getterPhrase submorphs second setSlotRefOperator: retrieverOp. getterPhrase submorphs second setPatchDefaultTo: (scriptedPlayer defaultPatchPlayer). getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. "self halt." receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem 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 9/27/2001 13:28'! 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 _ (Vocabulary vocabularyForType: retrieverType) comparatorForSampleBoolean. outerPhrase _ PhraseTileMorph new setOperator: rel type: #Boolean rcvrType: retrieverType argType: retrieverType. getterPhrase _ PhraseTileMorph new setOperator: retrieverOp type: retrieverType rcvrType: #Player. getterPhrase submorphs last setSlotRefOperator: retrieverOp. getterPhrase submorphs first changeTableLayout. receiverTile _ aPlayer tileToRefer bePossessive. receiverTile position: getterPhrase firstSubmorph position. getterPhrase firstSubmorph addMorph: receiverTile. outerPhrase firstSubmorph addMorph: getterPhrase. finalTile _ ScriptingSystem tileForArgType: retrieverType. "comes with arrows" outerPhrase submorphs last addMorph: finalTile. outerPhrase submorphs second submorphs last setBalloonText: (ScriptingSystem helpStringForOperator: rel). ^ outerPhrase! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 3/24/2005 11:18'! booleanPhraseFromPhrase: phrase "Answer, if possible, a boolean-valued phrase derived from the phrase provided" | retrieverOp retrieverTile | (phrase isKindOf: ParameterTile) ifTrue: [^ phrase booleanComparatorPhrase]. phrase isBoolean ifTrue: [^ phrase]. ((scriptedPlayer respondsTo: #costume) and:[scriptedPlayer costume isInWorld not]) ifTrue: [^ Array new]. ((retrieverTile _ phrase submorphs last) isKindOf: TileMorph) ifFalse: [^ phrase]. retrieverOp _ retrieverTile operatorOrExpression. (Vocabulary vocabularyForType: phrase resultType) affordsCoercionToBoolean ifTrue: [ retrieverOp = #getPatchValueIn: ifTrue: [ ^ self booleanPhraseForGetPatchValueOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getRedComponentIn: ifTrue: [ ^ self booleanPhraseForGetColorComponentOfType: phrase resultType componentName: #red retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getGreenComponentIn: ifTrue: [ ^ self booleanPhraseForGetColorComponentOfType: phrase resultType componentName: #green retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getBlueComponentIn: ifTrue: [ ^ self booleanPhraseForGetColorComponentOfType: phrase resultType componentName: #blue retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getUphillIn: ifTrue: [ ^ self booleanPhraseForGetUpHillOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getDistanceTo: ifTrue: [ ^ self booleanPhraseForGetDistanceToOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getAngleTo: ifTrue: [ ^ self booleanPhraseForGetAngleToOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #bounceOn: ifTrue: [ ^ self booleanPhraseForBounceOnOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. (retrieverOp = #bounceOn:color: or: [retrieverOp = #bounceOnColor:]) ifTrue: [ ^ self booleanPhraseForBounceOnColorOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getTurtleAt: ifTrue: [ ^ self booleanPhraseForGetTurtleAtOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. retrieverOp = #getTurtleOf: ifTrue: [ ^ self booleanPhraseForGetTurtleOfOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. ^ self booleanPhraseForRetrieverOfType: phrase resultType retrieverOp: retrieverOp player: phrase actualObject. ]. ^ phrase! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 8/17/2002 01:11'! categoryRestorationInfo "Answer info needed to reincarnate myself" ^ self chosenCategorySymbol! ! !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: 'yo 8/11/2005 14:54'! isSpecialPatchCase: aPlayer and: cmd ((aPlayer costume renderedMorph class = KedamaMorph) and: [cmd = #addToPatchDisplayList:]) ifTrue: [ ^ true. ]. (aPlayer costume renderedMorph class = KedamaPatchMorph) ifTrue: [ (#(#redComponentInto: #greenComponentInto: #blueComponentInto: #redComponentFrom: #greenComponentFrom: #blueComponentFrom:) includes: cmd) ifTrue: [ ^ true. ]. ]. ^ false. ! ! !CategoryViewer methodsFor: 'support' stamp: 'yo 8/11/2005 14:48'! isSpecialPatchReceiver: aPlayer and: cmd ^ (aPlayer costume renderedMorph class = KedamaPatchMorph) and: [ (#(#redComponentInto: #greenComponentInto: #blueComponentInto: #redComponentFrom: #greenComponentFrom: #blueComponentFrom:) includes: cmd) ]. ! ! !CategoryViewer methodsFor: 'support' stamp: 'sw 8/6/2001 19:42'! limitClass "Answer the receiver's limitClass" | outer | ^ (outer _ self outerViewer) ifNotNil: [outer limitClass] ifNil: [ProtoObject]! ! !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 5/4/2001 05:32'! tileForSelf "Return a tile representing the receiver's viewee" ^ scriptedPlayer tileToRefer ! ! WorldViewModel subclass: #CautiousModel instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-ST80'! !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: 'updating' stamp: 'nb 6/17/2003 12:25'! okToChange Preferences cautionBeforeClosing ifFalse: [^ true]. Sensor leftShiftDown ifTrue: [^ true]. Beeper 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"! ! SystemChangeTestRoot subclass: #ChangeHooksTest instanceVariableNames: 'previousChangeSet testsChangeSet capturedEvents generatedTestClass generatedTestClassX createdMethodName createdMethod doItExpression' classVariableNames: '' poolDictionaries: '' category: 'SystemChangeNotification-Tests'! !ChangeHooksTest commentStamp: 'rw 4/5/2006 17:14' prior: 0! This class implements unit tests to verify that when the system changes, notification messages are sent around correctly. Therefore the test messages make a system change, after registering to receive an event ater the change occured. In this event (sent immediately after the change), the actual assertions take place. Note that the system changes are *really* made to the system, but in a change set that is created in the setUp method, while the previous one is restored in the tearDown method.! !ChangeHooksTest methodsFor: 'Private' stamp: 'rw 8/1/2003 17:03'! addSingleEvent: anEvent capturedEvents isEmpty ifFalse: [self assert: false]. capturedEvents add: anEvent! ! !ChangeHooksTest methodsFor: 'Private' stamp: 'rw 7/11/2003 09:55'! checkEvent: anEvent kind: changeKind item: item itemKind: itemKind self assert: (anEvent perform: ('is' , changeKind) asSymbol). self assert: anEvent item = item. self assert: anEvent itemKind = itemKind! ! !ChangeHooksTest methodsFor: 'Private' stamp: 'rw 8/1/2003 17:01'! checkForOnlySingleEvent self assert: capturedEvents size = 1! ! !ChangeHooksTest methodsFor: 'Private' stamp: 'rw 7/2/2003 18:07'! commentStringForTesting ^'Added this comment as part of the unit test in SystemChangeTest>>testClassCommentedBasicEvents. You should never see this, unless you are debugging the system somewhere in between the tests.'! ! !ChangeHooksTest methodsFor: 'Private' stamp: 'rw 4/5/2006 17:22'! removeGeneratedTestClasses "Remove all classes that were possibly generated during testing." | possiblyToRemove | possiblyToRemove := OrderedCollection with: self generatedTestClassName with: self generatedTestClassNameX with: self renamedTestClassName with: self newlyCreatedClassName. possiblyToRemove do: [:name | (Smalltalk hasClassNamed: name) ifTrue: [(Smalltalk at: name) removeFromSystemUnlogged]]! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:15'! classCommentedEvent: event self addSingleEvent: event. self assert: generatedTestClass comment = self commentStringForTesting. self checkEvent: event kind: #Commented item: generatedTestClass itemKind: AbstractEvent classKind! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:15'! classCreationEvent: event | classCreated | self addSingleEvent: event. classCreated := Smalltalk classNamed: self newlyCreatedClassName. self assert: classCreated notNil. self assert: ((Smalltalk organization listAtCategoryNamed: #'System-Change Notification') includes: self newlyCreatedClassName). self checkEvent: event kind: #Added item: classCreated itemKind: AbstractEvent classKind! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:15'! classRecategorizedEvent: event self addSingleEvent: event. self checkEvent: event kind: #Recategorized item: generatedTestClass itemKind: AbstractEvent classKind. self assert: event oldCategory = #'System-Change Notification'! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:15'! classRedefinitionEvent: event self addSingleEvent: event. self checkEvent: event kind: #Modified item: generatedTestClass itemKind: AbstractEvent classKind.! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:16'! classRemovalEvent: event "This event used to be sent efter the class was removed. This was changed, and therefore this test is useless currently." self addSingleEvent: event. self assert: (Smalltalk classNamed: self generatedTestClassName) isNil. self checkEvent: event kind: #Removed item: self generatedTestClassName itemKind: AbstractEvent classKind! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:16'! classRenameEvent: event | renamedClass | self addSingleEvent: event. renamedClass := Smalltalk classNamed: self renamedTestClassName. self assert: renamedClass notNil. self assert: (Smalltalk classNamed: self generatedTestClassName) isNil. self checkEvent: event kind: #Renamed item: renamedClass itemKind: AbstractEvent classKind. self assert: event oldName = self generatedTestClassName! ! !ChangeHooksTest methodsFor: 'Events-Classes' stamp: 'rw 4/5/2006 17:16'! classSuperChangedEvent: event self addSingleEvent: event. self checkEvent: event kind: #Modified item: generatedTestClass itemKind: AbstractEvent classKind. self assert: generatedTestClass superclass = Model! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 22:10'! generatedTestClassName ^#'AutoGeneratedClassForTestingSystemChanges'! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 22:10'! generatedTestClassNameX ^#'AutoGeneratedClassXForTestingSystemChanges'! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 21:41'! generateTestClass generatedTestClass := Object subclass: self generatedTestClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'.! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 21:41'! generateTestClassX generatedTestClassX := Object subclass: self generatedTestClassNameX instanceVariableNames: 'x' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'.! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 22:18'! newlyCreatedClassName ^#'AutoGeneratedClassWhileTestingSystemChanges'! ! !ChangeHooksTest methodsFor: 'Private-Generation' stamp: 'rw 4/4/2006 22:10'! renamedTestClassName ^#'AutoRenamedClassForTestingSystemChanges'! ! !ChangeHooksTest methodsFor: 'Events-Instance Variables' stamp: 'rw 4/5/2006 17:16'! instanceVariableCreationEvent: event self addSingleEvent: event. self assert: event isModified. self assert: event item = generatedTestClass. self assert: event itemKind = AbstractEvent classKind. self assert: event areInstVarsModified. self deny: event isSuperclassModified. self deny: event areClassVarsModified. self deny: event areSharedPoolsModified. ! ! !ChangeHooksTest methodsFor: 'Events-Instance Variables' stamp: 'rw 4/5/2006 17:18'! instanceVariableRemovedEvent: event self addSingleEvent: event. self assert: event isModified. self assert: event item = generatedTestClassX. self assert: event itemKind = AbstractEvent classKind. self assert: event areInstVarsModified. self deny: event isSuperclassModified. self deny: event areClassVarsModified. self deny: event areSharedPoolsModified. ! ! !ChangeHooksTest methodsFor: 'Events-Methods' stamp: 'rw 4/5/2006 17:18'! methodCreationEvent1: event | methodCreated | self addSingleEvent: event. self shouldnt: [methodCreated := generatedTestClass >> createdMethodName] raise: Error. self checkEvent: event kind: #Added item: methodCreated itemKind: AbstractEvent methodKind! ! !ChangeHooksTest methodsFor: 'Events-Methods' stamp: 'rw 4/5/2006 17:18'! methodCreationEvent2: event | methodCreated | self addSingleEvent: event. self shouldnt: [methodCreated := generatedTestClass >> createdMethodName] raise: Error. self checkEvent: event kind: #Added item: methodCreated itemKind: AbstractEvent methodKind! ! !ChangeHooksTest methodsFor: 'Events-Methods' stamp: 'rw 4/5/2006 17:18'! methodRecategorizationEvent: event | methodCreated | self addSingleEvent: event. self shouldnt: [methodCreated := generatedTestClass >> createdMethodName] raise: Error. self assert: ((generatedTestClass organization categoryOfElement: createdMethodName) = #newCategory). self assert: event oldCategory = #testing. self checkEvent: event kind: #Recategorized item: methodCreated itemKind: AbstractEvent methodKind.! ! !ChangeHooksTest methodsFor: 'Events-Methods' stamp: 'rw 4/5/2006 17:18'! methodRemovedEvent1: event self addSingleEvent: event. self should: [generatedTestClass >> createdMethodName] raise: Error. self checkEvent: event kind: #Removed item: createdMethod itemKind: AbstractEvent methodKind. event itemClass = generatedTestClass. event itemMethod = createdMethodName. self assert: ((generatedTestClass organization categoryOfElement: createdMethodName) isNil).! ! !ChangeHooksTest methodsFor: 'Events-Methods' stamp: 'rw 4/5/2006 17:18'! methodRemovedEvent2: event self methodRemovedEvent1: event! ! !ChangeHooksTest methodsFor: 'Events-Expression' stamp: 'rw 4/5/2006 17:16'! methodDoItEvent1: event self addSingleEvent: event. self checkEvent: event kind: #DoIt item: doItExpression itemKind: AbstractEvent expressionKind. self assert: event context isNil.! ! !ChangeHooksTest methodsFor: 'Events-General' stamp: 'rw 8/1/2003 17:11'! rememberEvent: event capturedEvents add: event! ! !ChangeHooksTest methodsFor: 'Events-General' stamp: 'rw 8/1/2003 16:41'! shouldNotBeCalledEvent: anEvent "This event should not be called, so fail the test." self assert: false! ! !ChangeHooksTest methodsFor: 'Running' stamp: 'rw 4/4/2006 22:59'! setUp previousChangeSet := ChangeSet current. testsChangeSet := ChangeSet new. ChangeSet newChanges: testsChangeSet. capturedEvents := OrderedCollection new. self generateTestClass. self generateTestClassX. super setUp! ! !ChangeHooksTest methodsFor: 'Running' stamp: 'rw 4/5/2006 17:23'! tearDown super tearDown. self removeGeneratedTestClasses. ChangeSet newChanges: previousChangeSet. ChangeSorter removeChangeSet: testsChangeSet. previousChangeSet := nil. testsChangeSet := nil. ! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:19'! testClassCommentedEvent self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classCommentedEvent:. generatedTestClass comment: self commentStringForTesting. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:19'! testClassCreationEvent self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classCreationEvent:. Object subclass: self newlyCreatedClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:19'! testClassRecategorizedEvent1 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classRecategorizedEvent:. Object subclass: generatedTestClass name instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Abstract'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:19'! testClassRecategorizedEvent2 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classRecategorizedEvent:. generatedTestClass category: 'Collections-Abstract'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:19'! testClassRedefinition self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classRedefinitionEvent:. self generateTestClass! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:20'! testClassRemovalEvent "This event used to be sent efter the class was removed. This was changed, and therefore this test is useless currently." "Keep it, since I really want to check with the responsible for the ChangeSet, and it is very likely this will be reintroduced afterwards!!" " | createdClass | createdClass := self compileUniqueClass. self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classRemovalEvent:. createdClass removeFromSystem. self checkForOnlySingleEvent "! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:20'! testClassRenamedEvent self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classRenameEvent:. generatedTestClass rename: self renamedTestClassName. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Classes' stamp: 'rw 4/5/2006 17:20'! testClassSuperChangedEvent self systemChangeNotifier notify: self ofAllSystemChangesUsing: #classSuperChangedEvent:. Model subclass: generatedTestClass name instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Expression' stamp: 'rw 4/5/2006 17:20'! testDoItEvent1 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodDoItEvent1:. doItExpression := '1 + 2'. Compiler evaluate: doItExpression logged: true. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Expression' stamp: 'rw 4/5/2006 17:20'! testDoItEvent2 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #shouldNotBeCalledEvent:. doItExpression := '1 + 2'. Compiler evaluate: doItExpression logged: false.! ! !ChangeHooksTest methodsFor: 'Testing-Instance Variables' stamp: 'rw 4/5/2006 17:20'! testInstanceVariableCreationEvent1 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #instanceVariableCreationEvent:. Object subclass: self generatedTestClassName instanceVariableNames: 'x' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Instance Variables' stamp: 'rw 4/5/2006 17:17'! testInstanceVariableCreationEvent2 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #instanceVariableCreationEvent:. generatedTestClass addInstVarName: 'x'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Instance Variables' stamp: 'rw 4/5/2006 17:18'! testInstanceVariableRemovedEvent1 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #instanceVariableRemovedEvent:. Object subclass: generatedTestClassX name instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Instance Variables' stamp: 'rw 4/5/2006 17:17'! testInstanceVariableRemovedEvent2 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #instanceVariableRemovedEvent:. generatedTestClassX removeInstVarName: 'x'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Instance Variables' stamp: 'rw 4/5/2006 17:21'! testInstanceVariableRenamedSilently self systemChangeNotifier notify: self ofAllSystemChangesUsing: #shouldNotBeCalledEvent:. generatedTestClassX renameSilentlyInstVar: 'x' to: 'y'! ! !ChangeHooksTest methodsFor: 'Testing-Methods' stamp: 'rw 4/5/2006 17:21'! testMethodCreationEvent1 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodCreationEvent1:. createdMethodName := #testCreation. generatedTestClass compile: createdMethodName , ' ^1'. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Methods' stamp: 'rw 4/5/2006 17:21'! testMethodCreationEvent2 self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodCreationEvent2:. createdMethodName := #testCreation. generatedTestClass compile: createdMethodName , ' ^1' classified: #testing. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Methods' stamp: 'rw 4/5/2006 17:21'! testMethodRecategorizationEvent createdMethodName := #testCreation. generatedTestClass compile: createdMethodName , ' ^1' classified: #testing. self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodRecategorizationEvent:. generatedTestClass organization classify: createdMethodName under: #newCategory suppressIfDefault: false. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Methods' stamp: 'rw 4/5/2006 17:21'! testMethodRemovedEvent1 createdMethodName := #testCreation. generatedTestClass compile: createdMethodName , ' ^1'. createdMethod := generatedTestClass >> createdMethodName. self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodRemovedEvent1:. generatedTestClass removeSelector: createdMethodName. self checkForOnlySingleEvent! ! !ChangeHooksTest methodsFor: 'Testing-Methods' stamp: 'rw 4/5/2006 17:21'! testMethodRemovedEvent2 createdMethodName := #testCreation. generatedTestClass compile: createdMethodName , ' ^1'. createdMethod := generatedTestClass >> createdMethodName. self systemChangeNotifier notify: self ofAllSystemChangesUsing: #methodRemovedEvent2:. Smalltalk removeSelector: (Array with: generatedTestClass name with: createdMethodName). self checkForOnlySingleEvent! ! 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: 'accessing'! changeList ^ changeList! ! !ChangeList methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:26'! changes: changeRecords file: aFile file := aFile. changeList := OrderedCollection new. list := OrderedCollection new. listIndex := 0. changeRecords do: [:each | (each respondsTo: #methodClass) ifFalse: [self addItem: ChangeRecord new text: each asString] ifTrue: [self addItem: each text: ('method: ' , each methodClass name , (each isMetaClassChange ifTrue: [' class '] ifFalse: [' ']) , each methodSelector , '; ' , each stamp)]]. listSelections := Array new: list size withAll: false! ! !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: 'sd 11/20/2005 21:26'! listSelections listSelections ifNil: [ list ifNotNil: [ listSelections := Array new: list size withAll: false]]. ^ listSelections! ! !ChangeList methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:26'! setLostMethodPointer: sourcePointer lostMethodPointer := sourcePointer! ! !ChangeList methodsFor: 'accessing' stamp: 'sw 10/19/1999 15:11'! showsVersions ^ false! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! initialize "Initialize a blank ChangeList. Set the contentsSymbol to reflect whether diffs will initally be shown or not" contentsSymbol := Preferences diffsInChangeList ifTrue: [self defaultDiffsSymbol] ifFalse: [#source]. changeList := OrderedCollection new. list := OrderedCollection new. listIndex := 0. super initialize! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sd 11/20/2005 21:26'! 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 on: self list: #list primarySelection: #listIndex changePrimarySelection: #toggleListIndex: listSelection: #listSelectionAt: changeListSelection: #listSelectionAt:put: menu: (self showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:])] 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: 'sd 11/20/2005 21:26'! optionalButtonRow "Answer a row of buttons to occur in a tool pane" | aRow aButton | aRow := AlignmentMorph newRow. aRow hResizing: #spaceFill. aRow clipSubmorphs: true. aRow layoutInset: 2@2; cellInset: 3. aRow wrapCentering: #center; cellPositioning: #leftCenter. self changeListButtonSpecs do: [:triplet | aButton := PluggableButtonMorph on: self getState: nil action: triplet second. aButton hResizing: #spaceFill; vResizing: #spaceFill; label: triplet first asString; askBeforeChanging: true; onColor: Color white offColor: Color white. aRow addMorphBack: aButton. aButton setBalloonText: triplet third]. aRow addMorphBack: self regularDiffButton. self wantsPrettyDiffOption ifTrue: [aRow addMorphBack: self prettyDiffButton]. ^ aRow! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sd 11/20/2005 21:26'! 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: #showingAnyKindOfDiffs action: #toggleDiffing. button label: 'diffs' asParagraph; window: (offset@0 extent: (vWidth - offset)@bHeight). view addSubView: button toRightOf: previousView. ^ view! ! !ChangeList methodsFor: 'initialization-release' stamp: 'sw 8/15/2002 22:34'! wantsPrettyDiffOption "Answer whether pretty-diffs are meaningful for this tool" ^ true! ! !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: 'sd 11/20/2005 21:26'! browseAllVersionsOfSelections "Opens a Versions browser on all the currently selected methods, showing each alongside all of their historical versions." | oldSelection aList | oldSelection := self listIndex. aList := OrderedCollection new. Cursor read showWhile: [ 1 to: changeList size do: [:i | (listSelections at: i) ifTrue: [ listIndex := i. self browseVersions. aList add: i. ]]]. listIndex := oldSelection. aList size == 0 ifTrue: [^ self inform: 'no selected methods have in-memory counterparts']. ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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: ( MethodReference new setStandardClass: aClass methodSymbol: 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: 'sd 11/20/2005 21:26'! browseVersions | change class browser | listIndex = 0 ifTrue: [^ nil ]. change := changeList at: listIndex. ((class := change methodClass) notNil and: [class includesSelector: change methodSelector]) ifFalse: [ ^nil ]. browser := super browseVersions. browser ifNotNil: [ browser addedChangeRecord: change ]. ^browser! ! !ChangeList methodsFor: 'menu actions' stamp: 'nk 6/2/2006 08:03'! buildMorphicCodePaneWith: editString | codePane | codePane := AcceptableCleanTextMorph on: self text: #contents accept: #contents: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. codePane font: Preferences standardCodeFont. 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: 'nk 1/7/2004 11:11'! 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 'import the selected items into the image') ('fileOut selections... ' fileOutSelections 'create a new file containing the selected items') - ('compare to current' compareToCurrentVersion 'open a separate window which shows the text differences between the on-file version and the in-image version.' ) ('toggle diffing (D)' toggleDiffing 'start or stop showing diffs in the code pane.') - ('select conflicts with any changeset' selectAllConflicts 'select methods in the file which also occur in any change-set in the system') ('select conflicts with current changeset' selectConflicts 'select methods in the file which also occur in the current change-set') ('select conflicts with...' selectConflictsWith 'allows you to designate a file or change-set against which to check for code conflicts.') - ('select unchanged methods' selectUnchangedMethods 'select methods in the file whose in-image versions are the same as their in-file counterparts' ) ('select new methods' selectNewMethods 'select methods in the file that do not current occur in the image') ('select methods for this class' selectMethodsForThisClass 'select all methods in the file that belong to the currently-selected class') - ('select all (a)' selectAll 'select all the items in the list') ('deselect all' deselectAll 'deselect all the items in the list') ('invert selections' invertSelections 'select every item that is not currently selected, and deselect every item that *is* currently selected') - ('browse all versions of single selection' browseVersions 'open a version browser showing the versions of the currently selected method') ('browse all versions of selections' browseAllVersionsOfSelections 'open a version browser showing all the versions of all the selected methods') ('browse current versions of selections' browseCurrentVersionsOfSelections 'open a message-list browser showing the current (in-image) counterparts of the selected methods') ('destroy current methods of selections' destroyCurrentCodeOfSelections 'remove (*destroy*) the in-image counterparts of all selected methods') - ('remove doIts' removeDoIts 'remove all items that are doIts rather than methods') ('remove older versions' removeOlderMethodVersions 'remove all but the most recent versions of methods in the list') ('remove up-to-date versions' removeExistingMethodVersions 'remove all items whose code is the same as the counterpart in-image code') ('remove selected items' removeSelections 'remove the selected items from the change-list') ('remove unselected items' removeNonSelections 'remove all the items not currently selected from the change-list')). ^ aMenu ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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 prettyDiffs: self showingPrettyDiffs)) openLabel: 'Comparison to Current Version'] ifFalse: [self flash]! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'rbb 3/1/2005 10:27'! fileOutSelections | fileName internalStream | fileName := UIManager default request: 'Enter the base of file name' initialAnswer: 'Filename'. internalStream := WriteStream on: (String new: 1000). internalStream header; timeStamp. listSelections with: changeList do: [:selected :item | selected ifTrue: [item fileOutOn: internalStream]]. FileStream writeSourceCodeFrom: internalStream baseName: fileName isSt: true useHtml: false. ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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: '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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! removeExistingMethodVersions "Remove all up to date version of entries from the receiver" | newChangeList newList str keep cls sel | newChangeList := OrderedCollection new. newList := OrderedCollection new. changeList with: list do:[:chRec :strNstamp | keep := true. (cls := chRec methodClass) ifNotNil:[ str := chRec string. sel := cls parserClass new parseSelector: str. keep := (cls sourceCodeAt: sel ifAbsent:['']) asString ~= str. ]. keep ifTrue:[ newChangeList add: chRec. newList add: strNstamp]]. 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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' stamp: 'sd 11/20/2005 21:26'! 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' stamp: 'sd 11/20/2005 21:26'! selectAll listIndex := 0. listSelections atAllPut: true. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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 doesAnyChangeSetHaveClass: aClass andSelector: aChange methodSelector]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! selectConflicts "Selects all method definitions for which there is ALSO an entry in changes" | 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: [(ChangeSet current atSelector: change methodSelector class: class) ~~ #none]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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 := ChangeSorter allChangeSets copy) 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 := (UIManager default chooseFrom: (aStream contents substrings)). index > 0 ifTrue: [ self selectConflicts: (all at: index)]. ! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! selectMethodsForThisClass | name | self currentChange ifNil: [ ^self ]. name := self currentChange methodClassName. name ifNil: [ ^self ]. ^self selectSuchThat: [ :change | change methodClassName = name ].! ! !ChangeList methodsFor: 'menu actions' stamp: 'sd 11/20/2005 21:26'! selectNewMethods "Selects all method definitions for which there is no counterpart method in the current image" | 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) isNil) or: [(class includesSelector: change methodSelector) not]]))]]. self changed: #allSelections! ! !ChangeList methodsFor: 'menu actions' stamp: 'rbb 3/1/2005 10:27'! 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 := UIManager default 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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 withBlanksCondensed = (class sourceCodeAt: change methodSelector) asString withBlanksCondensed ]])]]. self changed: #allSelections! ! !ChangeList methodsFor: 'scanning' stamp: 'sd 11/20/2005 21:26'! 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: 'md 2/21/2006 09:42'! 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: [' ']) , (self class parserClass new parseSelector: method) , (stamp isEmpty ifTrue: [''] ifFalse: ['; ' , stamp])]! ! !ChangeList methodsFor: 'scanning' stamp: 'sd 11/20/2005 21:26'! 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: 'toolbuilder' stamp: 'ar 2/12/2005 13:50'! buildWith: builder ^self buildWith: builder multiSelect: true! ! !ChangeList methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 14:46'! buildWith: builder multiSelect: multiSelect "Open a morphic view for the messageSet, whose label is labelString. The listView may be either single or multiple selection type" | windowSpec max listSpec panelSpec textSpec | windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec label: 'System Browser'. windowSpec children: OrderedCollection new. max := self wantsOptionalButtons ifTrue:[0.33] ifFalse:[0.4]. multiSelect ifTrue:[ listSpec := builder pluggableMultiSelectionListSpec new. listSpec getSelectionList: #listSelectionAt:. listSpec setSelectionList: #listSelectionAt:put:. ] ifFalse:[ listSpec := builder pluggableListSpec new. ]. listSpec model: self; list: #list; getIndex: #listIndex; setIndex: #toggleListIndex:; menu: (self showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:]); keyPress: #changeListKey:from:; frame: (0@0 corner: 1@max). windowSpec children add: listSpec. self wantsOptionalButtons ifTrue:[ panelSpec := self buildOptionalButtonsWith: builder. panelSpec frame: (0@0.33 corner: 1@0.4). windowSpec children add: panelSpec. ]. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #contents; setText: #contents:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0@0.4corner: 1@1). windowSpec children add: textSpec. ^builder build: windowSpec! ! !ChangeList methodsFor: 'viewing access' stamp: 'sd 11/20/2005 21:26'! 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, and if the corresponding method is missing from the in-image version, mention that fact." | annot aChange aClass | annot := super annotation. annot asString = '------' ifTrue: [^ annot]. ^ ((aChange := self currentChange) notNil and: [aChange methodSelector notNil]) ifFalse: [annot] ifTrue: [((aClass := aChange methodClass) isNil or: [(aClass includesSelector: aChange methodSelector) not]) ifTrue: [aChange methodClassName, ' >> ', aChange methodSelector, ' is not present in the current image.'] ifFalse: ['current version: ', annot]]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 9/5/2001 13:52'! contents "Answer the contents string, obeying diffing directives if needed" ^ self showingAnyKindOfDiffs 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: 'sd 11/20/2005 21:26'! contentsDiffedFromCurrent "Answer the contents diffed forward from current (in-memory) method version" | 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 prettyDiffs: self showingPrettyDiffs] ifFalse: [(changeList at: listIndex) text]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sw 11/13/2001 09:12'! contentsSymbolQuints "Answer a list of quintuplets representing information on the alternative views available in the code pane" ^ self sourceAndDiffsQuintsOnly! ! !ChangeList methodsFor: 'viewing access' stamp: 'sd 11/20/2005 21:26'! diffedVersionContents "Answer diffed version contents, maybe pretty maybe not" | change class earlier later | (listIndex = 0 or: [changeList size < listIndex]) ifTrue: [^ '']. change := changeList at: listIndex. later := change text. class := change methodClass. (listIndex == changeList size or: [class == nil]) ifTrue: [^ later]. earlier := (changeList at: listIndex + 1) text. ^ TextDiffBuilder buildDisplayPatchFrom: earlier to: later inClass: class prettyDiffs: self showingPrettyDiffs! ! !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: 'sd 11/20/2005 21:26'! 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 addSelectorSilently: 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 basicRemoveSelector: selector]. ^ true! ! !ChangeList methodsFor: 'viewing access' stamp: 'nk 2/26/2004 13:50'! selectedClass ^(self selectedClassOrMetaClass ifNil: [ ^nil ]) theNonMetaClass ! ! !ChangeList methodsFor: 'viewing access' stamp: 'sd 11/20/2005 21:26'! selectedClassOrMetaClass | c | ^ (c := self currentChange) ifNotNil: [c methodClass]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sd 11/20/2005 21:26'! selectedMessageName | c | ^ (c := self currentChange) ifNotNil: [c methodSelector]! ! !ChangeList methodsFor: 'viewing access' stamp: 'sd 11/20/2005 21:26'! 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: '*monticello' stamp: 'stephaneducasse 2/4/2006 20:47'! changeTo: changeSubset | newList newChangeList | newChangeList := OrderedCollection new. newList := OrderedCollection new. 1 to: changeList size do: [:i | (changeSubset includes: (changeList at: i)) ifTrue: [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 class instanceVariableNames: ''! !ChangeList class methodsFor: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'md 10/22/2003 16:13'! browseChangesFile: fullName "Browse the selected file in fileIn format." fullName ifNotNil: [ChangeList browseStream: (FileStream readOnlyFileNamed: fullName)] ifNil: [Beeper beep]! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'tak 3/16/2005 11:46'! browseCompressedChangesFile: fullName "Browse the selected file in fileIn format." | zipped unzipped stream | fullName ifNil: [^Beeper beep]. stream := FileStream readOnlyFileNamed: fullName. [stream converter: Latin1TextConverter new. zipped := GZipReadStream on: stream. unzipped := zipped contents asString] ensure: [stream close]. stream := (MultiByteBinaryOrTextStream with: unzipped) reset. ChangeList browseStream: stream! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'sd 11/20/2005 21:28'! fileReaderServicesForFile: fullName suffix: suffix | services | services := OrderedCollection new. (FileStream isSourceFileSuffix: suffix) | (suffix = '*') ifTrue: [ services add: self serviceBrowseChangeFile ]. (suffix = 'changes') | (suffix = '*') ifTrue: [ services add: self serviceBrowseDotChangesFile ]. (fullName asLowercase endsWith: '.cs.gz') | (suffix = '*') ifTrue: [ services add: self serviceBrowseCompressedChangeFile ]. ^services! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'nk 4/29/2004 10:35'! serviceBrowseChangeFile "Answer a service for opening a changelist browser on a file" ^ (SimpleServiceEntry provider: self label: 'changelist browser' selector: #browseStream: description: 'open a changelist tool on this file' buttonLabel: 'changes') argumentGetter: [ :fileList | fileList readOnlyStream ]! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'nk 12/13/2002 12:03'! serviceBrowseCompressedChangeFile "Answer a service for opening a changelist browser on a file" ^ SimpleServiceEntry provider: self label: 'changelist browser' selector: #browseCompressedChangesFile: description: 'open a changelist tool on this file' buttonLabel: 'changes'! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'sw 7/4/2002 18:37'! serviceBrowseDotChangesFile "Answer a service for opening a changelist browser on the tail end of a .changes file" ^ SimpleServiceEntry provider: self label: 'recent changes in file' selector: #browseRecentLogOnPath: description: 'open a changelist tool on recent changes in file' buttonLabel: 'recent changes'! ! !ChangeList class methodsFor: 'fileIn/Out' stamp: 'nk 12/13/2002 12:04'! services "Answer potential file services associated with this class" ^ { self serviceBrowseChangeFile. self serviceBrowseDotChangesFile. self serviceBrowseCompressedChangeFile }! ! !ChangeList class methodsFor: 'initialize-release' stamp: 'hg 8/3/2000 18:14'! initialize FileList registerFileReader: self! ! !ChangeList class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! 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 on: aChangeList list: #list primarySelection: #listIndex changePrimarySelection: #toggleListIndex: listSelection: #listSelectionAt: changeListSelection: #listSelectionAt:put: menu: (aChangeList showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:])] ifFalse: [PluggableListView on: aChangeList list: #list selected: #listIndex changeSelected: #toggleListIndex: menu: (aChangeList showsVersions ifTrue: [#versionsMenu:] ifFalse: [#changeListMenu:])]). 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 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: 'HK 4/18/2002 15:02'! browseRecent: charCount "ChangeList browseRecent: 5000" "Opens a changeList on the end of the changes log file" ^ self browseRecent: charCount on: (SourceFiles at: 2) ! ! !ChangeList class methodsFor: 'public access' stamp: 'sd 11/20/2005 21:28'! browseRecent: charCount on: origChangesFile "Opens a changeList on the end of the specified changes log file" | changeList end changesFile | changesFile := origChangesFile readOnlyCopy. changesFile setConverterForCode. 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: 'sd 11/16/2003 14:10'! browseRecentLog "ChangeList browseRecentLog" "Prompt with a menu of how far back to go to browse the current image's changes log file" ^ self browseRecentLogOn: (SourceFiles at: 2) startingFrom: SmalltalkImage current lastQuitLogPosition! ! !ChangeList class methodsFor: 'public access' stamp: 'sd 11/20/2005 21:28'! browseRecentLogOn: origChangesFile "figure out where the last snapshot or quit was, then browse the recent entries." | end done block pos chunk changesFile positions prevBlock | changesFile := origChangesFile readOnlyCopy. positions := SortedCollection new. end := changesFile size. prevBlock := end. block := end - 1024 max: 0. done := false. [done or: [positions size > 0]] whileFalse: [changesFile position: block. "ignore first fragment" changesFile nextChunk. [changesFile position < prevBlock] whileTrue: [pos := changesFile position. chunk := changesFile nextChunk. ((chunk indexOfSubCollection: '----' startingAt: 1) = 1) ifTrue: [ ({ '----QUIT'. '----SNAPSHOT' } anySatisfy: [ :str | chunk beginsWith: str ]) ifTrue: [positions add: pos]]]. block = 0 ifTrue: [done := true] ifFalse: [prevBlock := block. block := block - 1024 max: 0]]. changesFile close. positions isEmpty ifTrue: [self inform: 'File ' , changesFile name , ' does not appear to be a changes file'] ifFalse: [self browseRecentLogOn: origChangesFile startingFrom: positions last]! ! !ChangeList class methodsFor: 'public access' stamp: 'sd 11/20/2005 21:28'! browseRecentLogOn: origChangesFile startingFrom: initialPos "Prompt with a menu of how far back to go when browsing a changes file." | end banners positions pos chunk i changesFile | changesFile := origChangesFile readOnlyCopy. banners := OrderedCollection new. positions := OrderedCollection new. end := changesFile size. changesFile setConverterForCode. pos := initialPos. [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. banners size == 0 ifTrue: [^ self inform: 'this image has never been saved since changes were compressed']. pos := (SelectionMenu labelList: banners selections: positions) startUpWithCaption: 'Browse as far back as...'. pos == nil ifTrue: [^ self]. self browseRecent: end - pos on: origChangesFile! ! !ChangeList class methodsFor: 'public access' stamp: 'nb 6/17/2003 12:25'! browseRecentLogOnPath: fullName "figure out where the last snapshot or quit was, then browse the recent entries." fullName ifNotNil: [self browseRecentLogOn: (FileStream readOnlyFileNamed: fullName)] ifNil: [Beeper beep] ! ! !ChangeList class methodsFor: 'public access' stamp: 'sd 11/20/2005 21:28'! browseStream: changesFile "Opens a changeList on a fileStream" | changeList charCount | changesFile readOnly. changesFile setConverterForCode. 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]]. "changesFile setEncoderForSourceCodeNamed: changesFile name." 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: 'sd 11/20/2005 21:28'! 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 := SmalltalkImage current 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: 'window color' stamp: 'sw 2/26/2002 14:07'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Change List' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'A tool that presents a list of all the changes found in an external file.'! ! !ChangeList class methodsFor: '*monticello' stamp: 'stephaneducasse 2/4/2006 20:47'! recentLogOn: origChangesFile startingFrom: initialPos "Prompt with a menu of how far back to go when browsing a changes file." | end banners positions pos chunk i changesFile | changesFile := origChangesFile readOnlyCopy. banners := OrderedCollection new. positions := OrderedCollection new. end := changesFile size. pos := initialPos. [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. banners size == 0 ifTrue: [^self recent: end on: origChangesFile]. pos := (SelectionMenu labelList: banners selections: positions) startUpWithCaption: 'Browse as far back as...'. pos == nil ifTrue: [^ self]. ^self recent: end - pos on: origChangesFile! ! !ChangeList class methodsFor: '*monticello' stamp: 'stephaneducasse 2/4/2006 20:47'! recent: charCount on: origChangesFile "Opens a changeList on the end of the specified changes log file" | changeList end changesFile | changesFile := origChangesFile readOnlyCopy. end := changesFile size. Cursor read showWhile: [changeList := self new scanFile: changesFile from: (0 max: end - charCount) to: end]. changesFile close. ^changeList! ! ChangeList subclass: #ChangeListForProjects instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeListForProjects commentStamp: '' prior: 0! A ChangeList that looks at the changes in a revokable project. This class has no users at present.! !ChangeListForProjects methodsFor: 'contents' stamp: 'sw 9/5/2001 15:25'! contents ^ self showingAnyKindOfDiffs ifFalse: [self undiffedContents] ifTrue: [self currentDiffedFromContents] "Current is writing over one in list. Show how I would change it"! ! !ChangeListForProjects methodsFor: 'contents' stamp: 'sd 11/20/2005 21:26'! currentDiffedFromContents "Answer the current in-memory method diffed from the current contents" | 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 prettyDiffs: self showingPrettyDiffs] ifFalse: [(changeList at: listIndex) text]! ! Object subclass: #ChangeRecord instanceVariableNames: 'file position type class category meta stamp' classVariableNames: '' poolDictionaries: '' category: 'System-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: 'sumim 9/1/2003 18:27'! fileIndex ^ (SourceFiles collect: [ :sf | sf name]) indexOf: file name ifAbsent: [^ nil]. ! ! !ChangeRecord methodsFor: 'access' stamp: 'nk 1/7/2004 10:28'! fileName ^(file ifNotNil: [ file name ]) ifNil: [ '' ]! ! !ChangeRecord methodsFor: 'access' stamp: 'sw 10/20/2002 02:53'! fileOutOn: aFileStream "File the receiver out on the given file stream" | aString | type == #method ifTrue: [aFileStream nextPut: $!!. aString _ class asString , (meta ifTrue: [' class methodsFor: '] ifFalse: [' methodsFor: ']) , category asString printString. stamp ifNotNil: [aString _ aString, ' stamp: ''', stamp, '''']. aFileStream nextChunkPut: aString. aFileStream cr]. type == #preamble ifTrue: [aFileStream nextPut: $!!]. type == #classComment ifTrue: [aFileStream nextPut: $!!. aFileStream nextChunkPut: class asString, ' commentStamp: ', stamp storeString. aFileStream cr]. aFileStream nextChunkPut: self string. type == #method ifTrue: [aFileStream nextChunkPut: ' ']. aFileStream 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' stamp: 'md 2/21/2006 08:54'! methodSelector type == #method ifFalse: [^ nil]. ^ self class parserClass new parseSelector: self string! ! !ChangeRecord methodsFor: 'access' stamp: 'ar 7/15/2005 22:57'! originalChangeSetForSelector: methodSelector "Returns the original changeset which contained this method version. If it is contained in the .sources file, return #sources. If it is in neither (e.g. its changeset was deleted), return nil. (The selector is passed in purely as an optimization.)" | likelyChangeSets originalChangeSet | (file localName findTokens: '.') last = 'sources' ifTrue: [^ #sources]. likelyChangeSets _ ChangeSet allChangeSets select: [:cs | (cs atSelector: methodSelector class: self methodClass) ~~ #none]. originalChangeSet _ likelyChangeSets detect: [:cs | cs containsMethodAtPosition: position] ifNone: [nil]. ^ originalChangeSet "(still need to check for sources file)"! ! !ChangeRecord methodsFor: 'access' stamp: 'sumim 9/2/2003 14:07'! position ^ position! ! !ChangeRecord methodsFor: 'access' stamp: 'sumim 9/2/2003 13:33'! prior | currFile preamble prevPos tokens prevFileIndex | currFile _ file readOnlyCopy. currFile position: (0 max: position - 150). [currFile position < (position - 1)] whileTrue: [preamble _ currFile nextChunk]. currFile close. prevPos _ nil. (preamble findString: 'methodsFor:' startingAt: 1) > 0 ifTrue: [tokens _ Scanner new scanTokens: preamble] ifFalse: [tokens _ Array new]. ((tokens size between: 7 and: 8) and: [(tokens at: tokens size - 5) == #methodsFor:]) ifTrue: [ (tokens at: tokens size - 3) == #stamp: ifTrue: [ prevPos _ tokens last. prevFileIndex _ SourceFiles fileIndexFromSourcePointer: prevPos. prevPos _ SourceFiles filePositionFromSourcePointer: prevPos] ifFalse: [ prevPos _ tokens at: tokens size - 2. prevFileIndex _ tokens last]. (prevPos = 0 or: [prevFileIndex = 0]) ifTrue: [prevPos _ nil]]. prevPos ifNil: [^ nil]. ^ {prevFileIndex. prevPos. SourceFiles sourcePointerFromFileIndex: prevFileIndex andPosition: prevPos}! ! !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' stamp: 'nk 11/25/2003 09:44'! timeStamp "Answer a TimeStamp that corresponds to my (text) stamp" | tokens date time | tokens := self stamp findTokens: Character separators. ^ tokens size > 2 ifTrue: [[date := Date fromString: (tokens at: tokens size - 1). time := Time fromString: tokens last. TimeStamp date: date time: time] on: Error do: [:ex | ex return: (TimeStamp fromSeconds: 0)]] ifFalse: [TimeStamp fromSeconds: 0]! ! !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! ! !ChangeRecord methodsFor: 'initialization' stamp: 'nk 11/26/2002 12:07'! fileIn "File the receiver in. If I represent a method or a class-comment, file the method in and make a note of it in the recent-submissions list; if I represent a do-it, then, well, do it." | methodClass s aSelector | Cursor read showWhile: [(methodClass _ self methodClass) notNil ifTrue: [methodClass compile: self text classified: category withStamp: stamp notifying: nil. (aSelector _ self methodSelector) ifNotNil: [Utilities noteMethodSubmission: aSelector forClass: methodClass]]. (type == #doIt) ifTrue: [((s _ self string) beginsWith: '----') ifFalse: [Compiler evaluate: s]]. (type == #classComment) ifTrue: [ | cls | (cls _ Smalltalk at: class asSymbol) comment: self text stamp: stamp. Utilities noteMethodSubmission: #Comment forClass: cls ]]! ! !ChangeRecord methodsFor: '*monticello' stamp: 'avi 9/14/2004 14:27'! asMethodDefinition ^ MCMethodDefinition className: class classIsMeta: meta selector: self methodSelector category: category timeStamp: stamp source: self string! ! Object subclass: #ChangeSet instanceVariableNames: 'name preamble postscript revertable isolationSet isolatedProject changeRecords structures superclasses' classVariableNames: 'AllChangeSets PreviousSet' poolDictionaries: '' category: 'System-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: 'accessing' stamp: 'BJP 4/24/2001 00:23'! author | author | self assurePreambleExists. author _ self preambleString lineNumber: 3. author _ author copyFrom: 8 to: author size. "Strip the 'Author:' prefix. Ugly ugly." ^author withBlanksTrimmed. ! ! !ChangeSet methodsFor: 'accessing' stamp: 'di 4/1/2000 12:00'! classRemoves ^ changeRecords keys select: [:className | (changeRecords at: className) isClassRemoval]! ! !ChangeSet methodsFor: 'accessing' stamp: 'ar 7/16/2005 18:59'! editPostscript "edit the receiver's postscript, in a separate window. " self assurePostscriptExists. UIManager default edit: self postscript label: 'Postscript for ChangeSet named ', name accept:[:aString| self postscript: aString].! ! !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: 'ar 7/16/2005 18:04'! postscriptHasDependents ^false! ! !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: '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: 'NS 1/19/2004 18:30'! 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]. class isMeta ifFalse: [self atClass: class add: #change] "normal" ifTrue: [((self classChangeAt: class theNonMetaClass name) includes: #add) ifTrue: [self atClass: class add: #add] "When a class is defined, the metaclass is not recorded, even though it was added. A further change is really just part of the original add." ifFalse: [self atClass: class add: #change]]. self addCoherency: class name. (self changeRecorderFor: class) notePriorDefinition: oldClass. self noteClassStructure: oldClass! ! !ChangeSet methodsFor: 'change logging' stamp: 'NS 2/17/2005 18:57'! event: anEvent "Hook for SystemChangeNotifier" anEvent itemKind = SystemChangeNotifier classKind ifTrue: [ anEvent isRemoved ifTrue: [self noteRemovalOf: anEvent item]. anEvent isAdded ifTrue: [self addClass: anEvent item]. anEvent isModified ifTrue: [anEvent anyChanges ifTrue: [self changeClass: anEvent item from: anEvent oldItem]]. anEvent isCommented ifTrue: [self commentClass: anEvent item]. anEvent isRenamed ifTrue: [self renameClass: anEvent item as: anEvent newName]. anEvent isReorganized ifTrue: [self reorganizeClass: anEvent item]. anEvent isRecategorized ifTrue: [self changeClass: anEvent item from: anEvent item]. ]. anEvent itemKind = SystemChangeNotifier methodKind ifTrue: [ anEvent isAdded ifTrue: [self noteNewMethod: anEvent item forClass: anEvent itemClass selector: anEvent itemSelector priorMethod: nil]. anEvent isModified ifTrue: [self noteNewMethod: anEvent item forClass: anEvent itemClass selector: anEvent itemSelector priorMethod: anEvent oldItem]. anEvent isRemoved ifTrue: [self removeSelector: anEvent itemSelector class: anEvent itemClass priorMethod: anEvent item lastMethodInfo: {anEvent item sourcePointer. anEvent itemProtocol}]. anEvent isRecategorized ifTrue: [self reorganizeClass: anEvent itemClass]. ].! ! !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: 'tk 6/8/2001 09:27'! 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. self noteClassStructure: class. 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: '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' stamp: 'NS 1/26/2004 09:46'! commentClass: class "Include indication that a class comment has been changed." class wantsChangeSetLogging ifFalse: [^ self]. self atClass: class add: #comment! ! !ChangeSet methodsFor: 'class changes' stamp: 'nk 6/26/2002 12:30'! containsClass: aClass ^ self changedClasses includes: aClass! ! !ChangeSet methodsFor: 'class changes' stamp: 'dvf 9/27/2005 19:04'! fatDefForClass: class | newDef oldDef oldStrm newStrm outStrm oldVars newVars addedVars | class isBehavior ifFalse: [^class definition]. 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: 'dvf 9/27/2005 19:05'! 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 isBehavior ifFalse: [^ 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: 'NS 1/19/2004 17:49'! noteRemovalOf: class "The class is about to be removed from the system. Adjust the receiver to reflect that fact." class wantsChangeSetLogging ifFalse: [^ self]. (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: '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: 'tk 11/26/2004 05:56'! convertToCurrentVersion: varDict refStream: smartRefStrm "major change - 4/4/2000" | newish | varDict at: 'classChanges' ifPresent: [ :x | newish _ self convertApril2000: varDict using: smartRefStrm. newish == self ifFalse: [^ newish]. ]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/15/2005 21:53'! askAddedInstVars: classList | pairList pairClasses index pls newStruct oldStruct | "Ask the author whether these newly added inst vars need to be non-nil" pairList _ OrderedCollection new. pairClasses _ OrderedCollection new. "Class version numbers: If it must change, something big happened. Do need a conversion method then. Ignore them here." classList do: [:cls | newStruct _ (cls allInstVarNames). oldStruct _ (structures at: cls name ifAbsent: [#(0), newStruct]) allButFirst. newStruct do: [:instVarName | (oldStruct includes: instVarName) ifFalse: [ pairList add: cls name, ' ', instVarName. pairClasses add: cls]]]. pairList isEmpty ifTrue: [^ #()]. [index _ UIManager default chooseFrom: pairList, #('all of these need a non-nil value' 'all of these are OK with a nil value') title: 'These instance variables were added. When an old project comes in, newly added instance variables will have the value nil. Click on items to remove them from the list. Click on any for which nil is an OK value.'. (index <= (pls _ pairList size)) & (index > 0) ifTrue: [ pairList removeAt: index. pairClasses removeAt: index]. index = (pls + 2) ifTrue: ["all are OK" ^ #()]. pairList isEmpty | (index = (pls + 1)) "all need conversion, exit"] whileFalse. ^ pairClasses asSet asArray "non redundant"! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/15/2005 21:54'! askRemovedInstVars: classList | pairList pairClasses index pls newStruct oldStruct | "Ask the author whether these newly removed inst vars need to have their info saved" pairList _ OrderedCollection new. pairClasses _ OrderedCollection new. "Class version numbers: If it must change, something big happened. Do need a conversion method then. Ignore them here." classList do: [:cls | newStruct _ (cls allInstVarNames). oldStruct _ (structures at: cls name ifAbsent: [#(0), newStruct]) allButFirst. oldStruct do: [:instVarName | (newStruct includes: instVarName) ifFalse: [ pairList add: cls name, ' ', instVarName. pairClasses add: cls]]]. pairList isEmpty ifTrue: [^ #()]. [index _ UIManager default chooseFrom: pairList, #('all of these need a conversion method' 'all of these have old values that can be erased') title: 'These instance variables were removed. When an old project comes in, instance variables that have been removed will lose their contents. Click on items to remove them from the list. Click on any whose value is unimportant and need not be saved.'. (index <= (pls _ pairList size)) & (index > 0) ifTrue: [ pairList removeAt: index. pairClasses removeAt: index]. index = (pls + 2) ifTrue: ["all are OK" ^ #()]. pairList isEmpty | (index = (pls + 1)) "all need conversion, exit"] whileFalse. ^ pairClasses asSet asArray "non redundant"! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/15/2005 21:51'! askRenames: renamed addTo: msgSet using: smart | list rec ans oldStruct newStruct | "Go through the renamed classes. Ask the user if it could be in a project. Add a method in SmartRefStream, and a conversion method in the new class." list _ OrderedCollection new. renamed do: [:cls | rec _ changeRecords at: cls name. rec priorName ifNotNil: [ ans _ UIManager default chooseFrom: #('Yes, write code to convert those instances' 'No, no instances are in projects') title: 'You renamed class ', rec priorName, ' to be ', rec thisName, '.\Could an instance of ', rec priorName, ' be in a project on someone''s disk?'. ans = 1 ifTrue: [ oldStruct _ structures at: rec priorName ifAbsent: [nil]. newStruct _ (Array with: cls classVersion), (cls allInstVarNames). oldStruct ifNotNil: [ smart writeConversionMethodIn: cls fromInstVars: oldStruct to: newStruct renamedFrom: rec priorName. smart writeClassRename: cls name was: rec priorName. list add: cls name, ' convertToCurrentVersion:refStream:']] ifFalse: [structures removeKey: rec priorName ifAbsent: []]]]. list isEmpty ifTrue: [^ msgSet]. msgSet messageList ifNil: [msgSet initializeMessageList: list] ifNotNil: [list do: [:item | msgSet addItem: item]]. ^ msgSet! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:05'! 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 _ '"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: 'ar 7/16/2005 18:02'! 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 isEmptyOrNil) ifTrue: [preamble _ self preambleTemplate]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ls 10/21/2001 21:09'! buildMessageForMailOutWithUser: userName | message compressBuffer compressStream data compressedStream compressTarget | "prepare the message" message := MailMessage empty. message setField: 'from' toString: userName. message setField: 'to' toString: 'squeak-dev@lists.squeakfoundation.org'. message setField: 'subject' toString: (self chooseSubjectPrefixForEmail, name). message body: (MIMEDocument contentType: 'text/plain' content: (String streamContents: [ :str | str nextPutAll: 'from preamble:'; cr; cr. self fileOutPreambleOn: str ])). "Prepare the gzipped data" data _ WriteStream on: String new. data header; timeStamp. 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. message addAttachmentFrom: compressedStream withName: (name, '.cs.gz'). ^ message! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sd 4/16/2003 09:15'! 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: [self systemNavigation browseMessageList: aList name: 'methods in "', self name, '" whose authoring stamps do not start with "', initials, '"']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sd 4/16/2003 09:16'! checkForAnyAlienAuthorship "Check to see if there are any versions of 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 methodsWithAnyInitialsOtherThan: initials) size > 0 ifFalse: [^ self inform: 'All versions of all methods in "', self name, '" have authoring stamps which start with "', initials, '"'] ifTrue: [self systemNavigation browseMessageList: aList name: 'methods in "', self name, '" with any authoring stamps not starting with "', initials, '"']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/17/2005 11:13'! checkForConversionMethods "See if any conversion methods are needed" | oldStruct newStruct tell choice list need sel smart restore renamed listAdd listDrop msgSet rec nn | Preferences conversionMethodsAtFileOut ifFalse: [^ self]. "Check preference" structures ifNil: [^ self]. list _ OrderedCollection new. renamed _ OrderedCollection new. self changedClasses do: [:class | need _ (self atClass: class includes: #new) not. need ifTrue: ["Renamed classes." (self atClass: class includes: #rename) ifTrue: [ rec _ changeRecords at: class name. rec priorName ifNotNil: [ (structures includesKey: rec priorName) ifTrue: [ renamed add: class. need _ false]]]]. need ifTrue: [need _ (self atClass: class includes: #change)]. need ifTrue: [oldStruct _ structures at: class name ifAbsent: [need _ false. #()]]. need ifTrue: [ newStruct _ (Array with: class classVersion), (class allInstVarNames). need _ (oldStruct ~= newStruct)]. need ifTrue: [sel _ #convertToCurrentVersion:refStream:. (#(add change) includes: (self atSelector: sel class: class)) ifFalse: [ list add: class]]. ]. list isEmpty & renamed isEmpty ifTrue: [^ self]. "Ask user if want to do this" tell _ 'If there might be instances of ', (list asArray, renamed asArray) printString, '\in a project (.pr file) on someone''s disk, \please ask to write a conversion method.\' withCRs, 'After you edit the conversion method, you''ll need to fileOut again.\' withCRs, 'The preference conversionMethodsAtFileOut in category "fileout" controls this feature.'. choice _ UIManager default chooseFrom: '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.' withCRs title: tell. choice = 4 ifTrue: [Preferences disable: #conversionMethodsAtFileOut]. choice = 2 ifTrue: ["Don't consider this class again in the changeSet" list do: [:cls | structures removeKey: cls name ifAbsent: []]. renamed do: [:cls | nn _ (changeRecords at: cls name) priorName. structures removeKey: nn ifAbsent: []]]. choice ~= 1 ifTrue: [^ self]. "exit if choice 2,3,4" listAdd _ self askAddedInstVars: list. "Go through each inst var that was added" listDrop _ self askRemovedInstVars: list. "Go through each inst var that was removed" list _ (listAdd, listDrop) asSet asArray. smart _ SmartRefStream on: (RWBinaryOrTextStream on: '12345'). smart structures: structures. smart superclasses: superclasses. (restore _ self class current) == self ifFalse: [ self class newChanges: self]. "if not current one" msgSet _ smart conversionMethodsFor: list. "each new method is added to self (a changeSet). Then filed out with the rest." self askRenames: renamed addTo: msgSet using: smart. "renamed classes, add 2 methods" restore == self ifFalse: [self class newChanges: restore]. msgSet isEmpty ifTrue: [^ self]. self inform: 'Remember to fileOut again after modifying these methods.'. ToolSet browseMessageSet: msgSet name: 'Conversion methods for ', self name autoSelect: false.! ! !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: 'sd 4/16/2003 09:16'! checkForUnclassifiedMethods "Open a message list browser on all methods in the current change set that have not been categorized," | aList | (aList _ self methodsWithoutClassifications) size > 0 ifFalse: [^ self inform: 'All methods in "', self name, '" are categorized.'] ifTrue: [self systemNavigation browseMessageList: aList name: 'methods in "', self name, '" which have not been categorized']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/17/2005 10:48'! checkForUncommentedClasses "Check to see if any classes involved in this change set do not have class comments. Open up a browser showing all such classes." | aList | aList _ self changedClasses select: [:aClass | aClass theNonMetaClass organization classComment isEmptyOrNil] thenCollect: [:aClass | aClass theNonMetaClass name]. aList size > 0 ifFalse: [^ self inform: 'All classes involved in this change set have class comments'] ifTrue: [ToolSet openClassListBrowser: aList asSet asSortedArray title: 'Classes in Change Set ', self name, ': classes that lack class comments']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sd 4/16/2003 09:16'! 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: [self systemNavigation browseMessageList: aList name: 'methods in "', self name, '" that lack comments']! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sd 4/29/2003 20:19'! 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 | nameLine _ '"' , self name , '"'. allChangedSelectors _ Set new. (augList _ self changedMessageListAugmented) do: [:each | each isValid ifTrue: [allChangedSelectors add: each methodSymbol]]. unsent _ self systemNavigation allUnSentMessagesIn: allChangedSelectors. unsent size = 0 ifTrue: [^ self inform: 'There are no unsent messages in change set ' , nameLine]. self systemNavigation browseMessageList: (augList select: [:each | unsent includes: each methodSymbol]) name: 'Unsent messages in ' , nameLine! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'rbb 2/18/2005 14:21'! chooseSubjectPrefixForEmail | subjectIndex | subjectIndex _ (UIManager default chooseFrom: #('Bug fix [FIX]' 'Enhancement [ENH]' 'Goodie [GOODIE]' 'Test suite [TEST]' 'None of the above (will not be archived)') title: 'What type of change set\are you submitting to the list?' withCRs). ^ #('[CS] ' '[FIX] ' '[ENH] ' '[GOODIE] ' '[TEST] ' '[CS] ') at: subjectIndex + 1! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'nk 10/15/2003 09:55'! defaultChangeSetDirectory ^self class defaultChangeSetDirectory! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'md 2/22/2006 21:16'! 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 'changeSetVersionNumbers'" | slips nameToUse internalStream | self checkForConversionMethods. ChangeSet promptForDefaultChangeSetDirectoryIfNecessary. nameToUse := Preferences changeSetVersionNumbers ifTrue: [self defaultChangeSetDirectory nextNameFor: self name extension: FileStream cs] ifFalse: [self name , FileDirectory dot , Utilities dateTimeSuffix, FileDirectory dot , FileStream cs]. nameToUse := self defaultChangeSetDirectory fullNameFor: nameToUse. Cursor write showWhile: [ internalStream _ WriteStream on: (String new: 10000). internalStream header; timeStamp. self fileOutPreambleOn: internalStream. self fileOutOn: internalStream. self fileOutPostscriptOn: internalStream. internalStream trailer. FileStream writeSourceCodeFrom: internalStream baseName: (nameToUse copyFrom: 1 to: nameToUse size - 3) isSt: false useHtml: false. ]. 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: [self systemNavigation 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: 'dvf 9/27/2005 19:04'! fileOutOn: stream "Write out all the changes the receiver knows about" | classList traits classes traitList list | (self isEmpty and: [stream isKindOf: FileStream]) ifTrue: [self inform: 'Warning: no changes to file out']. traits _ self changedClasses reject: [:each | each isBehavior]. classes _ self changedClasses select: [:each | each isBehavior]. traitList _ self class traitsOrder: traits asOrderedCollection. classList _ self class superclassOrder: classes asOrderedCollection. list _ OrderedCollection new addAll: traitList; addAll: classList; yourself. "First put out rename, max classDef and comment changes." list do: [:aClass | self fileOutClassDefinition: aClass on: stream]. "Then put out all the method changes" list do: [:aClass | self fileOutChangesFor: aClass on: stream]. "Finally put out removals, final class defs and reorganization if any" list 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: 'rbb 2/18/2005 14:16'! 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?']. (UIManager default chooseFrom: #('Ignore' 'Browse slips') title: msg) = 2 ifTrue: [self systemNavigation browseMessageList: slips name: 'Possible slips in ', name]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'sd 4/16/2003 09:16'! mailOut "Email a compressed version of this changeset to the squeak-dev list, so that it can be shared with everyone. (You will be able to edit the email before it is sent.)" | userName message slips | userName _ MailSender userName. self checkForConversionMethods. Cursor write showWhile: [message _ self buildMessageForMailOutWithUser: userName]. MailSender sendMessage: message. 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: [self systemNavigation browseMessageList: slips name: 'Possible slips in ' , name] ! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/15/2005 21:27'! 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: #ChangeSet selector: #existingOrNewChangeSetNamed: args: (Array with: self name) "=== refStrm replace: self with: nil. ^ nil ===" ! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:03'! postscript "Answer the string representing the postscript. " ^postscript ifNotNil:[postscript isString ifTrue:[postscript] ifFalse:[postscript contents asString]]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:03'! postscriptString "Answer the string representing the postscript. " ^self postscript! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:03'! postscriptString: aString "Establish aString as the new contents of the postscript. " self postscript: aString! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:03'! postscript: aString "Answer the string representing the postscript. " postscript := aString! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 17:55'! preamble "Answer the string representing the preamble" ^preamble ifNotNil:[preamble isString ifTrue:[preamble] ifFalse:[preamble contents asString]]! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:00'! preambleString "Answer the string representing the preamble" ^self preamble! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:00'! preambleString: aString "Establish aString as the new contents of the preamble. " self preamble: aString.! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'nk 7/2/2003 10:47'! 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: '"'] "ChangeSet current preambleTemplate"! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:00'! preamble: aString "Establish aString as the new contents of the preamble. " preamble _ aString! ! !ChangeSet methodsFor: 'fileIn/Out' stamp: 'ar 7/16/2005 18:02'! setPreambleToSay: aString "Make aString become the preamble of this change set" self preamble: aString! ! !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: 'fileIn/Out' stamp: 'sd 1/16/2004 21:31'! verboseFileOut "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 'changeSetVersionNumbers'" ChangeSet current fileOut. Transcript cr; show: 'Changes filed out ', Date dateAndTimeNow printString! ! !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/6/2001 09:40'! initialize "Initialize the receiver to be empty." name ifNil: [^ self error: 'All changeSets must be registered, as in ChangeSorter newChangeSet']. revertable _ false. self clear. ! ! !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: '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: '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: 'md 8/27/2005 16:37'! atSelector: selector class: class put: changeType selector isDoIt ifTrue: [^ self]. (self changeRecorderFor: class) atSelector: selector put: changeType. ! ! !ChangeSet methodsFor: 'method changes' stamp: 'sw 6/26/2001 12:15'! changedMessageList "Used by a message set browser to access the list view information." | messageList classNameInFull classNameInParts | messageList _ OrderedCollection new. changeRecords associationsDo: [:clAssoc | classNameInFull _ clAssoc key asString. classNameInParts _ classNameInFull findTokens: ' '. (clAssoc value allChangeTypes includes: #comment) ifTrue: [messageList add: (MethodReference new setClassSymbol: classNameInParts first asSymbol classIsMeta: false methodSymbol: #Comment stringVersion: classNameInFull, ' Comment')]. clAssoc value methodChangeTypes associationsDo: [:mAssoc | (#(remove addedThenRemoved) includes: mAssoc value) ifFalse: [messageList add: (MethodReference new setClassSymbol: classNameInParts first asSymbol classIsMeta: classNameInParts size > 1 methodSymbol: mAssoc key stringVersion: classNameInFull, ' ' , mAssoc key)]]]. ^ messageList asSortedArray! ! !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: 'sw 4/19/2001 19:45'! hasAnyChangeForSelector: aSelector "Answer whether the receiver has any change under the given selector, whether it be add, change, or remove, for any class" changeRecords do: [:aRecord | (aRecord changedSelectors includes: aSelector) ifTrue: [^ true]]. ^ false! ! !ChangeSet methodsFor: 'method changes' stamp: 'RAA 5/28/2001 12:05'! messageListForChangesWhich: aBlock ifNone: ifEmptyBlock | answer | answer _ self changedMessageListAugmented select: [ :each | aBlock value: each actualClass value: each methodSymbol ]. answer isEmpty ifTrue: [^ifEmptyBlock value]. ^answer ! ! !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: '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: 'ar 7/16/2005 18:59'! editPreamble "edit the receiver's preamble, in a separate window. " self assurePreambleExists. UIManager default edit: self preamble label: 'Preamble for ChangeSet named ', name accept:[:aString| self preamble: aString]! ! !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: 'nk 3/30/2002 09:13'! methodsWithAnyInitialsOtherThan: myInits "Return a collection of method refs whose author appears to be different from the given one, even historically" | slips method aTimeStamp | slips _ Set 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: [ (aClass changeRecordsAt: mAssoc key) do: [ :chg | aTimeStamp _ chg stamp. (aTimeStamp notNil and: [(aTimeStamp beginsWith: myInits) not]) ifTrue: [slips add: aClass name , ' ' , mAssoc key]]]]]]. ^ slips! ! !ChangeSet methodsFor: 'moving changes' stamp: 'nk 7/2/2003 10:47'! 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: (ChangeSet current methodsWithInitialsOtherThan: 'sw') name: 'authoring problems'"! ! !ChangeSet methodsFor: 'moving changes' stamp: 'nk 7/2/2003 10:47'! methodsWithoutComments "Return a collection representing methods in the receiver which have no precode 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 firstPrecodeCommentFor: mAssoc key) isEmptyOrNil ifTrue: [slips add: aClass name , ' ' , mAssoc key]]]]]. ^ slips "Smalltalk browseMessageList: (ChangeSet current 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: 'yo 8/30/2002 13:59'! removeClassChanges: class "Remove all memory of changes associated with this class" | cname | (class isString) 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: '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: 'sw 8/10/2002 22:21'! containsMethodAtPosition: aFilePosition "Answer whether the receiver contains the method logged at the given file position" "class: aClassSymbol" "(need class parameter to speed up?)" "<- dew 9/6/2001" changeRecords values do: [:classChangeRecord | classChangeRecord methodChanges values do: [:methodChangeRecord | | changeType | changeType _ methodChangeRecord changeType. ((changeType == #add or: [changeType == #change]) and: [methodChangeRecord currentMethod notNil and: [methodChangeRecord currentMethod filePosition = aFilePosition]]) 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: 'nk 7/2/2003 10:47'! methodsWithoutClassifications "Return a collection representing methods in the receiver which have not been categorized" | slips notClassified aSelector | notClassified _ {'as yet unclassified' asSymbol. #all}. slips _ OrderedCollection new. self changedClasses do: [:aClass | (self methodChangesAtClass: aClass name) associationsDo: [:mAssoc | (aClass selectors includes: (aSelector _ mAssoc key)) ifTrue: [(notClassified includes: (aClass organization categoryOfElement: aSelector)) ifTrue: [slips add: aClass name , ' ' , aSelector]]]]. ^ slips "Smalltalk browseMessageList: (ChangeSet current methodsWithoutClassifications) name: 'unclassified methods'"! ! !ChangeSet methodsFor: 'testing' stamp: 'sw 8/3/1998 16:25'! okayToRemove ^ self okayToRemoveInforming: true! ! !ChangeSet methodsFor: 'testing' stamp: 'sd 5/23/2003 14:24'! 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 == self class current 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: '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'! changed: anAspectSymbol with: aParameter "Allow objects to depend on the ChangeSet class instead of a particular instance of ChangeSet (which may be switched using projects)." ChangeSet changed: anAspectSymbol with: aParameter. super changed: anAspectSymbol with: aParameter! ! !ChangeSet methodsFor: 'private' stamp: 'yo 8/30/2002 13:59'! changeRecorderFor: class | cname | (class isString) 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: 'tk 3/7/2001 14:06'! 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'. DeepCopier new checkClass: class. "If veryDeepCopy weakly copies some inst vars in this class, warn author when new ones are added." ] ifFalse: [ (self atClass: class includes: #add) ifTrue: [ "use current definition for add" stream command: 'H3'; nextChunkPut: class definition; cr; command: '/H3'. DeepCopier new checkClass: class. "If veryDeepCopy weakly copies some inst vars in this class, warn author when new ones are added." ]. ]. (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: 'current'! !ChangeSet class methodsFor: 'class initialization' stamp: 'ar 7/15/2005 21:12'! initialize "ChangeSet initialize" AllChangeSets == nil ifTrue: [AllChangeSets _ OrderedCollection new]. self gatherChangeSets. FileServices registerFileReader: self. ! ! !ChangeSet class methodsFor: 'current changeset' stamp: 'ar 7/17/2005 10:48'! browseChangedMessages "Create and schedule a message browser on each method that has been changed." current isEmpty ifTrue: [^ self inform: 'There are no changed messages in the current change set.']. ToolSet openChangedMessageSet: current.! ! !ChangeSet class methodsFor: 'current changeset' stamp: 'sd 5/22/2003 21:53'! current "return the current changeset" ^ current! ! !ChangeSet class methodsFor: 'current changeset' stamp: 'em 3/31/2005 11:48'! currentChangeSetString "ChangeSet current currentChangeSetString" ^ 'Current Change Set: ' translated, self current name! ! !ChangeSet class methodsFor: 'current changeset' stamp: 'NS 1/16/2004 14:49'! newChanges: aChangeSet "Set the system ChangeSet to be the argument, aChangeSet. Tell the current project that aChangeSet is now its change set. When called from Project enter:, the setChangeSet: call is redundant but harmless; when called from code that changes the current-change-set from within a project, it's vital" SystemChangeNotifier uniqueInstance noMoreNotificationsFor: current. current isolationSet: nil. current _ aChangeSet. SystemChangeNotifier uniqueInstance notify: aChangeSet ofAllSystemChangesUsing: #event:. Smalltalk currentProjectDo: [:proj | proj setChangeSet: aChangeSet. aChangeSet isolationSet: proj isolationSet]! ! !ChangeSet class methodsFor: 'current changeset' stamp: 'sd 5/22/2003 22:18'! noChanges "Initialize the system ChangeSet." current initialize! ! !ChangeSet class methodsFor: 'defaults' stamp: 'nk 7/18/2004 16:13'! defaultChangeSetDirectory "Answer the directory in which to store ChangeSets. Answer the default directory if the preferred directory doesn't exist." | dir directoryName | directoryName := Preferences parameterAt: #defaultChangeSetDirectoryName ifAbsentPut: ['']. dir := directoryName isEmptyOrNil ifTrue: [ FileDirectory default ] ifFalse: [ FileDirectory default directoryNamed: directoryName ]. dir exists ifTrue: [^ dir]. ^ FileDirectory default! ! !ChangeSet class methodsFor: 'defaults' stamp: 'nk 3/24/2004 15:52'! defaultChangeSetDirectory: dirOrName "Set the Preference for storing change sets to the given directory or name (possibly relative). Rewrite directory names below the default directory as relative names. If dirOrName is an empty string, use the default directory." "ChangeSet defaultChangeSetDirectory: 'changeSets'" | dirName defaultFullName | dirName := dirOrName isString ifTrue: [FileDirectory default fullNameFor: dirOrName] ifFalse: [dirOrName fullName]. defaultFullName := FileDirectory default fullName. dirName = defaultFullName ifTrue: [dirName := ''] ifFalse: [(dirName beginsWith: defaultFullName , FileDirectory slash) ifTrue: [dirName := dirName copyFrom: defaultFullName size + 2 to: dirName size]]. Preferences setParameter: #defaultChangeSetDirectoryName to: dirName! ! !ChangeSet class methodsFor: 'defaults' stamp: 'dgd 9/6/2003 19:56'! defaultName ^ self uniqueNameLike: 'Unnamed' translated! ! !ChangeSet class methodsFor: 'defaults' stamp: 'ar 7/16/2005 19:35'! promptForDefaultChangeSetDirectoryIfNecessary "Check the Preference (if any), and prompt the user to change it if necessary. The default if the Preference is unset is the current directory. Answer the directory." "ChangeSet promptForDefaultChangeSetDirectoryIfNecessary" | choice directoryName dir | directoryName := Preferences parameterAt: #defaultChangeSetDirectoryName ifAbsentPut: ['']. [dir := FileDirectory default directoryNamed: directoryName. dir exists] whileFalse: [choice := PopUpMenu withCaption: ('The preferred change set directory (''{1}'') does not exist. Create it or use the default directory ({2})?' translated format: { directoryName. FileDirectory default pathName }) chooseFrom: (#('Create directory' 'Use default directory and forget preference' 'Choose another directory' ) collect: [ :ea | ea translated ]). choice = 1 ifTrue: [dir assureExistence ]. choice = 3 ifTrue: [dir := UIManager default chooseDirectory. directoryName := dir ifNil: [ '' ] ifNotNil: [dir pathName ]]]. self defaultChangeSetDirectory: directoryName. ^dir! ! !ChangeSet class methodsFor: 'defaults' stamp: 'ar 7/15/2005 21:24'! uniqueNameLike: aString | try | (self named: aString) ifNil: [^ aString]. 1 to: 999999 do: [:i | try _ aString , i printString. (self named: try) ifNil: [^ try]]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:20'! allChangeSetNames ^ self allChangeSets collect: [:c | c name]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:10'! allChangeSets "Return the list of all current ChangeSets" ^ AllChangeSets! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:22'! allChangeSetsWithClass: class selector: selector class ifNil: [^ #()]. ^ self allChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:17'! allChangeSets: aCollection "Return the list of all current ChangeSets" AllChangeSets := aCollection.! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:14'! basicNewChangeSet: newName | newSet | newName ifNil: [^ nil]. (self named: newName) ifNotNil: [self inform: 'Sorry that name is already used'. ^ nil]. newSet _ self basicNewNamed: newName. AllChangeSets add: newSet. ^ newSet! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:11'! changeSetsNamedSuchThat: nameBlock "(ChangeSet changeSetsNamedSuchThat: [:name | name first isDigit and: [name initialInteger >= 373]]) do: [:cs | AllChangeSets remove: cs wither]" ^ AllChangeSets select: [:aChangeSet | nameBlock value: aChangeSet name]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:26'! changeSet: aChangeSet containsClass: aClass | theClass | theClass _ Smalltalk classNamed: aClass. theClass ifNil: [^ false]. ^ aChangeSet containsClass: theClass! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:26'! deleteChangeSetsNumberedLowerThan: anInteger "Delete all changes sets whose names start with integers smaller than anInteger" self removeChangeSetsNamedSuchThat: [:aName | aName first isDigit and: [aName initialIntegerOrNil < anInteger]]. "ChangeSet deleteChangeSetsNumberedLowerThan: (ChangeSorterPlus highestNumberedChangeSet name initialIntegerOrNil - 500)" ! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:24'! existingOrNewChangeSetNamed: aName | newSet | ^(self named: aName) ifNil: [ newSet _ self basicNewNamed: aName. AllChangeSets add: newSet. newSet ]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:12'! gatherChangeSets "ChangeSet 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! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:20'! highestNumberedChangeSet "ChangeSorter highestNumberedChangeSet" | aList | aList _ (self allChangeSetNames select: [:aString | aString startsWithDigit] thenCollect: [:aString | aString initialIntegerOrNil]). ^ (aList size > 0) ifTrue: [aList max] ifFalse: [nil] ! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:29'! mostRecentChangeSetWithChangeForClass: class selector: selector | hits | hits _ self allChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]. hits isEmpty ifTrue: [^ 'not in any change set']. ^ 'recent cs: ', hits last name! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:11'! named: aName "Return the change set of the given name, or nil if none found. 1/22/96 sw" ^ AllChangeSets detect: [:aChangeSet | aChangeSet name = aName] ifNone: [nil]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:13'! promoteToTop: aChangeSet "Make aChangeSet the first in the list from now on" AllChangeSets remove: aChangeSet ifAbsent: [^ self]. AllChangeSets add: aChangeSet! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:26'! removeChangeSetsNamedSuchThat: nameBlock (self changeSetsNamedSuchThat: nameBlock) do: [:cs | self removeChangeSet: cs]! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:13'! removeChangeSet: aChangeSet "Remove the given changeSet. Caller must assure that it's cool to do this" AllChangeSets remove: aChangeSet ifAbsent: []. aChangeSet wither ! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:26'! 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 | self removeChangeSet: cs]. self inform: toGo size printString, ' change set(s) removed.'! ! !ChangeSet class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:13'! secondaryChangeSet "Answer a likely change set to use as the second initial one in a Dual Change Sorter. " AllChangeSets size = 1 ifTrue: [^ AllChangeSets first]. AllChangeSets last == ChangeSet current ifTrue: [^ AllChangeSets at: (AllChangeSets size - 1)] ifFalse: [^ AllChangeSets last]! ! !ChangeSet class methodsFor: 'file list services' stamp: 'ar 7/15/2005 21:36'! fileReaderServicesForFile: fullName suffix: suffix ^ (FileStream isSourceFileSuffix: suffix) ifTrue: [ self services] ifFalse: [#()]! ! !ChangeSet class methodsFor: 'file list services' stamp: 'ar 7/15/2005 21:35'! serviceFileIntoNewChangeSet "Answer a service for installing a file into a new change set" ^ SimpleServiceEntry provider: self label: 'install into new change set' selector: #fileIntoNewChangeSet: description: 'install the file as a body of code in the image: create a new change set and file-in the selected file into it' buttonLabel: 'install'! ! !ChangeSet class methodsFor: 'file list services' stamp: 'ar 7/15/2005 21:36'! services ^ Array with: self serviceFileIntoNewChangeSet! ! !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: 'fileIn/Out' stamp: 'al 7/18/2004 18:45'! traitsOrder: aCollection "Answer an OrderedCollection. The traits are ordered so they can be filed in." | traits | traits _ aCollection asSortedCollection: [:t1 :t2 | (t1 isBaseTrait and: [t1 classTrait == t2]) or: [ (t2 traitComposition allTraits includes: t1) or: [ (t1 traitComposition allTraits includes: t2) not]]]. ^traits asArray! ! !ChangeSet class methodsFor: 'instance creation' stamp: 'di 4/6/2001 09:43'! basicNewNamed: aName ^ (self basicNew name: aName) initialize! ! !ChangeSet class methodsFor: 'instance creation' stamp: 'ar 7/16/2005 15:17'! new "All current changeSets must be registered in the AllChangeSets collection. Due to a quirk of history, this is maintained as class variable of ChangeSorter." ^ self basicNewChangeSet: ChangeSet defaultName! ! !ChangeSet class methodsFor: 'scanning' stamp: 'ar 7/16/2005 19:22'! 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 _ SmalltalkImage current 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 _ UIManager default chooseFrom: banners values: positions title: aPrompt. pos == nil ifTrue: [^ nil]. ^ end - pos! ! !ChangeSet class methodsFor: 'scanning' stamp: 'ar 7/16/2005 15:12'! scanCategory: file "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" ^{(ChangeRecord new file: file position: itemPosition type: #preamble)}]. 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 scanFile: file category: tokens third class: tokens first meta: false stamp: stamp]. tokens third == #methodsFor: ifTrue: [^ self scanFile: file category: tokens fourth class: tokens first meta: true stamp: stamp]]. tokens second == #commentStamp: ifTrue: [stamp _ tokens third. item := (ChangeRecord new file: file position: file position type: #classComment class: tokens first category: nil meta: false stamp: stamp). file nextChunk. file skipStyleChunk. ^Array with: item]. ^#()! ! !ChangeSet class methodsFor: 'scanning' stamp: 'ar 7/16/2005 15:11'! scanFile: file category: cat class: class meta: meta stamp: stamp | itemPosition method items | items := OrderedCollection new. [itemPosition _ file position. method _ file nextChunk. file skipStyleChunk. method size > 0] whileTrue:[ items add: (ChangeRecord new file: file position: itemPosition type: #method class: class category: cat meta: meta stamp: stamp)]. ^items! ! !ChangeSet class methodsFor: 'scanning' stamp: 'ar 7/16/2005 15:14'! scanFile: file from: startPosition to: stopPosition | itemPosition item prevChar changeList | changeList _ OrderedCollection new. file position: startPosition. 'Scanning ', file 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: [changeList addAll: (self scanCategory: file)]. ] ifFalse:[ itemPosition _ file position. item _ file nextChunk. file skipStyleChunk. item size > 0 ifTrue:[ changeList add: (ChangeRecord new file: file position: itemPosition type: #doIt). ]. ]. ]]. ^changeList! ! !ChangeSet class methodsFor: 'scanning' stamp: 'ar 7/16/2005 20:19'! scanVersionsOf: method class: class meta: meta category: cat selector: selector | position prevPos prevFileIndex preamble tokens sourceFilesCopy stamp changeList file | changeList _ OrderedCollection new. position _ method filePosition. sourceFilesCopy _ SourceFiles collect:[:x | x ifNotNil:[x readOnlyCopy]]. method fileIndex == 0 ifTrue: [^ nil]. file _ sourceFilesCopy at: method fileIndex. [position notNil & file notNil] whileTrue:[ file position: (0 max: position-150). "Skip back to before the preamble" preamble _ method getPreambleFrom: file at: (0 max: position - 3). "Preamble is likely a linked method preamble, if we're in a changes file (not the sources file). Try to parse it for prior source position and file index" prevPos _ nil. stamp _ ''. (preamble findString: 'methodsFor:' startingAt: 1) > 0 ifTrue: [tokens _ Scanner new scanTokens: preamble] ifFalse: [tokens _ Array new "ie cant be back ref"]. ((tokens size between: 7 and: 8) and: [(tokens at: tokens size-5) = #methodsFor:]) ifTrue:[ (tokens at: tokens size-3) = #stamp: ifTrue:[ "New format gives change stamp and unified prior pointer" stamp _ tokens at: tokens size-2. prevPos _ tokens last. prevFileIndex _ sourceFilesCopy fileIndexFromSourcePointer: prevPos. prevPos _ sourceFilesCopy filePositionFromSourcePointer: prevPos. ] ifFalse: ["Old format gives no stamp; prior pointer in two parts" prevPos _ tokens at: tokens size-2. prevFileIndex _ tokens last. ]. (prevPos = 0 or: [prevFileIndex = 0]) ifTrue: [prevPos _ nil] ]. ((tokens size between: 5 and: 6) and: [(tokens at: tokens size-3) = #methodsFor:]) ifTrue:[ (tokens at: tokens size-1) = #stamp: ifTrue: [ "New format gives change stamp and unified prior pointer" stamp _ tokens at: tokens size. ] ]. changeList add: (ChangeRecord new file: file position: position type: #method class: class name category: cat meta: meta stamp: stamp). position _ prevPos. prevPos notNil ifTrue:[file _ sourceFilesCopy at: prevFileIndex]. ]. sourceFilesCopy do: [:x | x ifNotNil:[x close]]. ^changeList! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:30'! assuredChangeSetNamed: aName "Answer a change set of the given name. If one already exists, answer that, else create a new one and answer it." | existing | ^ (existing _ self named: aName) ifNotNil: [existing] ifNil: [self basicNewChangeSet: aName]! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:28'! 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 allChangeSets do: [:aChangeSet | aChangeSet == aggregateChangeSet ifFalse: [aggregateChangeSet assimilateAllChangesFoundIn: aChangeSet]] "ChangeSet buildAggregateChangeSet" ! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:37'! countOfChangeSetsWithClass: aClass andSelector: aSelector "Answer how many change sets record a change for the given class and selector" ^ (self allChangeSetsWithClass: aClass selector: aSelector) size! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:38'! doesAnyChangeSetHaveClass: aClass andSelector: aSelector "Answer whether any known change set bears a change for the given class and selector" ^ (self countOfChangeSetsWithClass: aClass andSelector: aSelector) > 0! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:35'! fileIntoNewChangeSet: fullName "File in all of the contents of the currently selected file, if any, into a new change set." | fn ff | fullName ifNil: [^ Beeper beep]. ff _ FileStream readOnlyFileNamed: (fn _ GZipReadStream uncompressedFileName: fullName). ChangeSet newChangesFromStream: ff named: (FileDirectory localNameFor: fn)! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:37'! fileOutChangeSetsNamed: nameList "File out the list of change sets whose names are provided" "ChangeSorter fileOutChangeSetsNamed: #('New Changes' 'miscTidies-sw')" | notFound aChangeSet infoString empty | notFound _ OrderedCollection new. empty _ OrderedCollection new. nameList do: [:aName | (aChangeSet _ self named: aName) ifNotNil: [aChangeSet isEmpty ifTrue: [empty add: aName] ifFalse: [aChangeSet fileOut]] ifNil: [notFound add: aName]]. infoString _ (nameList size - notFound size) printString, ' change set(s) filed out'. notFound size > 0 ifTrue: [infoString _ infoString, ' ', notFound size printString, ' change set(s) not found:'. notFound do: [:aName | infoString _ infoString, ' ', aName]]. empty size > 0 ifTrue: [infoString _ infoString, ' ', empty size printString, ' change set(s) were empty:'. empty do: [:aName | infoString _ infoString, ' ', aName]]. self inform: infoString! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:31'! 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 _ UIManager default request: 'Please name the new change set:' initialAnswer: ChangeSet defaultName. newName isEmptyOrNil ifTrue: [^ nil]. newSet _ self basicNewChangeSet: newName. newSet ifNotNil: [self newChanges: newSet]. ^ newSet! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:31'! 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! ! !ChangeSet class methodsFor: 'services' stamp: 'ar 7/15/2005 21:33'! 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 newStream | oldChanges _ ChangeSet current. PreviousSet _ oldChanges name. "so a Bumper update can find it" newName _ aName sansPeriodSuffix. newSet _ self basicNewChangeSet: newName. [newSet ifNotNil:[ (aStream respondsTo: #converter:) ifFalse: [ newStream _ MultiByteBinaryOrTextStream with: (aStream contentsOfEntireFile). newStream reset. ] ifTrue: [ newStream _ aStream. ]. self newChanges: newSet. newStream setConverterForCode. newStream fileInAnnouncing: 'Loading ', newName, '...'. Transcript cr; show: 'File ', aName, ' successfully filed in to change set ', newName]. aStream close] ensure: [self newChanges: oldChanges]. PreviousSet := nil. ^ newSet! ! ChangeSorter subclass: #ChangeSetBrowser instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSetBrowser commentStamp: '' prior: 0! A tool allowing you to browse the methods of a single change set.! !ChangeSetBrowser methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! addModelItemsToWindowMenu: aMenu "Add model-related items to the given window menu" | oldTarget | oldTarget := aMenu defaultTarget. aMenu defaultTarget: self. aMenu addLine. aMenu add: 'rename change set' action: #rename. aMenu add: 'make changes go to me' action: #newCurrent. aMenu addLine. aMenu add: 'file out' action: #fileOut. aMenu add: 'browse methods' action: #browseChangeSet. aMenu addLine. myChangeSet hasPreamble ifTrue: [aMenu add: 'edit preamble' action: #addPreamble. aMenu add: 'remove preamble' action: #removePreamble] ifFalse: [aMenu add: 'add preamble' 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' action: #remove. aMenu addLine. Smalltalk isMorphic ifTrue: [aMenu addLine. aMenu add: 'what to show...' target: self action: #offerWhatToShowMenu]. aMenu addLine. aMenu add: 'more...' action: #offerShiftedChangeSetMenu. aMenu defaultTarget: oldTarget. ^ aMenu! ! !ChangeSetBrowser methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! openAsMorphIn: window rect: rect "Add a set of changeSetBrowser views to the given top view offset by the given amount" | aHeight | contents := ''. aHeight := 0.25. self addDependent: window. "so it will get changed: #relabel" window addMorph: (PluggableListMorphByItem on: self list: #classList selected: #currentClassName changeSelected: #currentClassName: menu: #classListMenu:shifted: keystroke: #classListKey:from:) frame: (((0.0@0 extent: 0.5 @ aHeight) 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.5@0 extent: 0.5 @ aHeight) scaleBy: rect extent) translateBy: rect origin). self addLowerPanesTo: window at: (((0@aHeight corner: 1@1) scaleBy: rect extent) translateBy: rect origin) with: nil! ! !ChangeSetBrowser methodsFor: 'initialization' stamp: 'sw 3/14/2001 10:03'! wantsAnnotationPane "This kind of browser always wants annotation panes, so answer true" ^ true! ! !ChangeSetBrowser methodsFor: 'initialization' stamp: 'sw 3/9/2001 15:02'! wantsOptionalButtons "Sure, why not?" ^ true! ! !ChangeSetBrowser methodsFor: 'menu' stamp: 'sw 3/12/2001 14:07'! offerUnshiftedChangeSetMenu "The user chose 'more' from the shifted window menu; go back to the regular window menu" self containingWindow ifNotNil: [self containingWindow offerWindowMenu] ! ! !ChangeSetBrowser methodsFor: 'menu' stamp: 'sw 7/20/2002 18:33'! 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.'. 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'. aMenu add: 'check for uncommented classes' action: #checkForUncommentedClasses. aMenu balloonTextForLastItem: 'Check for classes with code in this changeset which lack class 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 add: 'check for any other authors' action: #checkForAnyAlienAuthorship. aMenu balloonTextForLastItem: 'Check this change set for methods any of whose previous authoring stamps do not start with "', Utilities authorInitials, '"']. aMenu add: 'check for uncategorized methods' action: #checkForUnclassifiedMethods. aMenu balloonTextForLastItem: 'Check to see if any methods in the selected change set have not yet been assigned to a category. If any are found, open a browser on them.'. 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: '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: 'more...' action: #offerUnshiftedChangeSetMenu. aMenu balloonTextForLastItem: 'Takes you back to the primary change-set menu.'. ^ aMenu! ! ElementCategory subclass: #ChangeSetCategory instanceVariableNames: 'membershipSelector' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSetCategory commentStamp: '' prior: 0! A ChangeSetCategory represents a list of change sets to be shown in a ChangeSorter. It computes whether a given change set is in the list by sending its membershipSelector to ChangeSorter (i.e. the class object) with the change set as message argument.! !ChangeSetCategory methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! membershipSelector: aSelector "Set the membershipSelector" membershipSelector := aSelector! ! !ChangeSetCategory methodsFor: 'miscellaneous' stamp: 'sd 5/23/2003 14:25'! defaultChangeSetToShow "Answer the name of a change-set to show" ^ ChangeSet current! ! !ChangeSetCategory methodsFor: 'miscellaneous' stamp: 'sd 11/20/2005 21:26'! reconstituteList "Clear out the receiver's elements and rebuild them" | newMembers | "First determine newMembers and check if they have not changed..." newMembers := ChangeSorter allChangeSets select: [:aChangeSet | ChangeSorter perform: membershipSelector with: aChangeSet]. (newMembers collect: [:cs | cs name]) = keysInOrder ifTrue: [^ self "all current"]. "Things have changed. Need to recompute the whole category" self clear. newMembers do: [:aChangeSet | self fasterElementAt: aChangeSet name asSymbol put: aChangeSet] ! ! !ChangeSetCategory methodsFor: 'queries' stamp: 'sw 4/11/2001 16:11'! acceptsManualAdditions "Answer whether the user is allowed manually to manipulate the contents of the change-set-category." ^ false! ! !ChangeSetCategory methodsFor: 'queries' stamp: 'sd 11/20/2005 21:26'! changeSetList "Answer the list of change-set names in the category" | aChangeSet | self reconstituteList. keysInOrder size == 0 ifTrue: ["don't tolerate emptiness, because ChangeSorters gag when they have no change-set selected" aChangeSet := ChangeSorter assuredChangeSetNamed: 'New Changes'. self elementAt: aChangeSet name put: aChangeSet]. ^ keysInOrder reversed! ! !ChangeSetCategory methodsFor: 'queries' stamp: 'sw 4/5/2001 17:26'! hasChangeForClassName: aClassName selector: aSelector otherThanIn: excludedChangeSet "Answer whether any change set in this category, other than the excluded one, has a change marked for the given class and selector" self elementsInOrder do: [:aChangeSet | (aChangeSet ~~ excludedChangeSet and: [((aChangeSet methodChangesAtClass: aClassName) includesKey: aSelector)]) ifTrue: [^ true]]. ^ false! ! !ChangeSetCategory methodsFor: 'queries' stamp: 'sw 3/30/2001 14:04'! includesChangeSet: aChangeSet "Answer whether the receiver includes aChangeSet in its retrieval list" ^ ChangeSorter perform: membershipSelector with: aChangeSet! ! !ChangeSetCategory methodsFor: 'services' stamp: 'sd 11/20/2005 21:26'! fileOutAllChangeSets "File out all the nonempty change sets in the current category, suppressing the checks for slips that might otherwise ensue. Obtain user confirmation before undertaking this possibly prodigious task." | aList | aList := self elementsInOrder select: [:aChangeSet | aChangeSet isEmpty not]. aList size == 0 ifTrue: [^ self inform: 'sorry, all the change sets in this category are empty']. (self confirm: 'This will result in filing out ', aList size printString, ' change set(s) Are you certain you want to do this?') ifFalse: [^ self]. Preferences setFlag: #checkForSlips toValue: false during: [ChangeSorter fileOutChangeSetsNamed: (aList collect: [:m | m name]) asSortedArray]! ! !ChangeSetCategory methodsFor: 'services' stamp: 'sd 11/20/2005 21:26'! fillAggregateChangeSet "Create a change-set named Aggregate and pour into it all the changes in all the change-sets of the currently-selected category" | aggChangeSet | aggChangeSet := ChangeSorter assuredChangeSetNamed: #Aggregate. aggChangeSet clear. aggChangeSet setPreambleToSay: '"Change Set: Aggregate Created at ', Time now printString, ' on ', Date today printString, ' by combining all the changes in all the change sets in the category ', categoryName printString, '"'. (self elementsInOrder copyWithout: aggChangeSet) do: [:aChangeSet | aggChangeSet assimilateAllChangesFoundIn: aChangeSet]. Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup] ! ! ChangeSetCategory subclass: #ChangeSetCategoryWithParameters instanceVariableNames: 'parameters' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSetCategoryWithParameters methodsFor: 'as yet unclassified' stamp: 'nk 6/26/2002 12:34'! acceptsManualAdditions "Answer whether the user is allowed manually to manipulate the contents of the change-set-category." ^ true! ! !ChangeSetCategoryWithParameters methodsFor: 'as yet unclassified' stamp: 'nk 6/26/2002 12:43'! addChangeSet: aChangeSet self inform: 'sorry, you can''t do that'! ! !ChangeSetCategoryWithParameters methodsFor: 'as yet unclassified' stamp: 'nk 6/26/2002 12:08'! includesChangeSet: aChangeSet "Answer whether the receiver includes aChangeSet in its retrieval list" ^ ChangeSorter perform: membershipSelector withArguments: { aChangeSet } , parameters! ! !ChangeSetCategoryWithParameters methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! parameters: anArray parameters := anArray! ! !ChangeSetCategoryWithParameters methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! reconstituteList "Clear out the receiver's elements and rebuild them" | newMembers | "First determine newMembers and check if they have not changed..." newMembers := ChangeSorter allChangeSets select: [:aChangeSet | ChangeSorter perform: membershipSelector withArguments: { aChangeSet }, parameters]. (newMembers collect: [:cs | cs name]) = keysInOrder ifTrue: [^ self "all current"]. "Things have changed. Need to recompute the whole category" self clear. newMembers do: [:aChangeSet | self fasterElementAt: aChangeSet name asSymbol put: aChangeSet]! ! TestCase subclass: #ChangeSetClassChangesTest instanceVariableNames: 'saveCurrentChangeSet addedChangeSetAccessor' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !ChangeSetClassChangesTest commentStamp: 'dtl 2/19/2005 13:21' prior: 0! Class category changes are not being properly added to the default changeset in Squeak 3.7. This test case will pass in Squeak 3.6, and fail in Squeak 3.[7-9]. ! !ChangeSetClassChangesTest methodsFor: 'running' stamp: 'dtl 2/19/2005 12:17'! tearDown (Smalltalk classNamed: #JunkClass) ifNotNilDo: [:c | c removeFromSystem: true]. SystemOrganization removeCategory: #'DeleteMe-1'. SystemOrganization removeCategory: #'DeleteMe-2'. ChangeSet current removeClassChanges: 'JunkClass' ! ! !ChangeSetClassChangesTest methodsFor: 'support' stamp: 'dtl 2/19/2005 13:08'! isDefinition: firstString equivalentTo: secondString "When a class definition is reconstructed with #fatDefForClass, it may contain extra trailing space characters in parts of the definition. This is probably a minor bug, but it should be overlooked for purposes of testing the change set update mechanism. The expedient here is to just remove spaces before comparing the definition strings." ^ firstString notNil and: [(firstString copyReplaceAll: ' ''' with: '''') = (secondString copyReplaceAll: ' ''' with: '''')]! ! !ChangeSetClassChangesTest methodsFor: 'testing' stamp: 'stephaneducasse 2/3/2006 22:39'! testAddInstanceVariable "Adding an instance variable to the class should result in a change record being added to the current change set." | saveClassDefinition | "Define a class and save its definition" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. saveClassDefinition := (Smalltalk classNamed: #JunkClass) definition. self assert: (self isDefinition: saveClassDefinition equivalentTo: (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass))). "Redefine the class, adding one instance variable" Object subclass: #JunkClass instanceVariableNames: 'zzz aaa' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. "Assert that the class definition has changed" self deny: (self isDefinition: (Smalltalk classNamed: #JunkClass) definition equivalentTo: saveClassDefinition). self deny: (self isDefinition: saveClassDefinition equivalentTo: (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass))). self assert: (self isDefinition: (Smalltalk classNamed: #JunkClass) definition equivalentTo: (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass))). "Assert that the change has been recorded in the current change set" self assert: (self isDefinition: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) priorDefinition equivalentTo: saveClassDefinition). ! ! !ChangeSetClassChangesTest methodsFor: 'testing' stamp: 'dtl 2/19/2005 11:55'! testAddInstanceVariableAddsNewChangeRecord "Changing the class category for a class should result in a change record being updated in the current change set." "At the start of this test, JunkClass should not exist, and there should be no change records pertaining to it in the change set." self deny: (Smalltalk hasClassNamed: 'JunkClass'). self assert: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) thisName = 'nil'. "Remove bogus change records created as side effect of preceding assert" ChangeSet current removeClassChanges: 'nil'. "Define a class and save its definition" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. "Forget about JunkClass in the change set" ChangeSet current removeClassChanges: 'JunkClass'. "Redefine the class, adding one instance variable" Object subclass: #JunkClass instanceVariableNames: 'zzz aaa' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. "A change record should now exist in the change set" self assert: (self isDefinition: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) priorDefinition equivalentTo: 'Object subclass: #JunkClass instanceVariableNames: ''zzz '' classVariableNames: '''' poolDictionaries: '''' category: ''DeleteMe-1''') ! ! !ChangeSetClassChangesTest methodsFor: 'testing' stamp: 'stephaneducasse 2/3/2006 22:39'! testChangeClassCategory "Changing the class category for a class should result in a change record being added to the current change set." | saveClassDefinition | "Define a class and save its definition" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. saveClassDefinition := (Smalltalk classNamed: #JunkClass) definition. self assert: saveClassDefinition = (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass)). "Redefine the class, changing only the class category" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-2'. "Assert that the class definition has changed" self deny: (self isDefinition: (Smalltalk classNamed: #JunkClass) definition equivalentTo: saveClassDefinition). self deny: (self isDefinition: saveClassDefinition equivalentTo: (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass))). self assert: (self isDefinition: (Smalltalk classNamed: #JunkClass) definition equivalentTo: (ChangeSet current fatDefForClass: (Smalltalk classNamed: #JunkClass))). "Assert that the change has been recorded in the current change set" self assert: (self isDefinition: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) priorDefinition equivalentTo: 'Object subclass: #JunkClass instanceVariableNames: ''zzz '' classVariableNames: '''' poolDictionaries: '''' category: ''DeleteMe-2''')! ! !ChangeSetClassChangesTest methodsFor: 'testing' stamp: 'dtl 2/19/2005 12:01'! testChangeClassCategoryAddsNewChangeRecord "Changing the class category for a class should result in a change record being updated in the current change set." "At the start of this test, JunkClass should not exist, and there should be no change records pertaining to it in the change set." self deny: (Smalltalk hasClassNamed: 'JunkClass'). self assert: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) thisName = 'nil'. "Remove bogus change records created as side effect of preceding assert" ChangeSet current removeClassChanges: 'nil'. "Define a class and save its definition" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-1'. "Forget about JunkClass in the change set" ChangeSet current removeClassChanges: 'JunkClass'. "Redefine the class, changing only the class category" Object subclass: #JunkClass instanceVariableNames: 'zzz' classVariableNames: '' poolDictionaries: '' category: 'DeleteMe-2'. "A change record should now exist in the change set" self assert: (self isDefinition: (ChangeSet current changeRecorderFor: (Smalltalk classNamed: #JunkClass)) priorDefinition equivalentTo: 'Object subclass: #JunkClass instanceVariableNames: ''zzz '' classVariableNames: '''' poolDictionaries: '''' category: ''DeleteMe-2''')! ! CodeHolder subclass: #ChangeSorter instanceVariableNames: 'parent myChangeSet currentClassName currentSelector priorChangeSetList changeSetCategory' classVariableNames: 'ChangeSetCategories ChangeSetNamesInRelease RecentUpdateMarker' poolDictionaries: '' category: 'Tools-Changes'! !ChangeSorter commentStamp: '' prior: 0! I display a ChangeSet. Two of me are in a DualChangeSorter.! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/29/1998 08:22'! changeSet ^ myChangeSet! ! !ChangeSorter methodsFor: 'access' stamp: 'sw 3/29/2001 14:45'! changeSetCategory "Answer the current changeSetCategory object that governs which change sets are shown in this ChangeSorter" ^ changeSetCategory ifNil: [self setDefaultChangeSetCategory]! ! !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: 'sd 5/23/2003 14:25'! labelString "The label for my entire window. The large button that displays my name is gotten via mainButtonName" ^ String streamContents: [:aStream | aStream nextPutAll: (ChangeSet current == myChangeSet ifTrue: ['Changes go to "', myChangeSet name, '"'] ifFalse: ['ChangeSet: ', myChangeSet name]). (self changeSetCategory categoryName ~~ #All) ifTrue: [aStream nextPutAll: ' - ', self parenthesizedCategoryName]]! ! !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: 'sd 11/20/2005 21:26'! myChangeSet: anObject myChangeSet := anObject! ! !ChangeSorter methodsFor: 'access' stamp: 'tk 4/24/1998 08:42'! parent ^ parent! ! !ChangeSorter methodsFor: 'access' stamp: 'sd 11/20/2005 21:26'! parent: anObject parent := anObject! ! !ChangeSorter methodsFor: 'access' stamp: 'sw 3/29/2001 22:51'! parenthesizedCategoryName "Answer my category name in parentheses" ^ ' (', self changeSetCategory categoryName, ')'! ! !ChangeSorter methodsFor: 'access' stamp: 'sd 11/20/2005 21:26'! 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: 'di 4/5/2001 21:20'! showChangeSetNamed: aName self showChangeSet: (ChangeSorter changeSetNamed: aName) ! ! !ChangeSorter methodsFor: 'annotation' stamp: 'sw 2/22/2001 10:35'! addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream "Add an annotation detailing the prior versions count. Specially handled here for the case of a selector no longer in the system, whose prior version is pointed to by the lost-method pointer in the change held on to by the changeset" (aClass includesSelector: aSelector) ifTrue: [^ super addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream]. aStream nextPutAll: ((myChangeSet methodInfoFromRemoval: {aClass name. aSelector}) ifNil: ['no prior versions'] ifNotNil: ['version(s) retrievable here']), self annotationSeparator! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! addPreamble myChangeSet assurePreambleExists. self okToChange ifTrue: [currentClassName := nil. currentSelector := nil. self showChangeSet: myChangeSet]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! addToCategoryOpposite "Add the current change set to the category viewed on the opposite side, if it's of the sort to accept things like that" | categoryOpposite | categoryOpposite := (parent other: self) changeSetCategory. categoryOpposite acceptsManualAdditions ifTrue: [categoryOpposite addChangeSet: myChangeSet. categoryOpposite reconstituteList. self update] ifFalse: [self inform: 'sorry, this command only makes sense if the category showing on the opposite side is a static category whose members are manually maintained']! ! !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: 'sd 11/20/2005 21:26'! 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 | aList := myChangeSet messageListForChangesWhich: [ :aClass :aSelector | (ChangeSorter allChangeSetsWithClass: aClass selector: aSelector) size > 1 ] ifNone: [^ 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: 'sw 8/12/2002 17:29'! categorySubmenu: aMenu shifted: shiftedIgnored "Fill aMenu with less-frequently-needed category items" aMenu title: 'Change set category'. aMenu addStayUpItem. aMenu addList: #( ('make a new category...' makeNewCategory 'Creates a new change-set-category (you will be asked to supply a name) which will start out its life with this change set in it') ('make a new category with class...' makeNewCategoryShowingClassChanges 'Creates a new change-set-category that includes change sets that change a particular class (you will be asked to supply a name)') ('rename this category' renameCategory 'Rename this change-set category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.') ('remove this category' removeCategory 'Remove this change-set category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.') ('show categories of this changeset' showCategoriesOfChangeSet 'Show a list of all the change-set categories that contain this change-set; if the you choose one of the categories from this pop-up, that category will be installed in this change sorter') -). parent ifNotNil: [aMenu addList: #( ('add change set to category opposite' addToCategoryOpposite 'Adds this change set to the category on the other side of the change sorter. Only applies if the category shown on the opposite side is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.'))]. aMenu addList: #( ('remove change set from this category' removeFromCategory 'Removes this change set from the current category. Only applies when the current category being shown is a manually-maintained, user-defined category, such as you can create for yourself by choosing "make a new category..." from this same menu.') - ('file out category''s change sets' fileOutAllChangeSets 'File out every change set in this category that has anything in it. The usual checks for slips are suppressed when this command is done.') ('set recent-updates marker' setRecentUpdatesMarker 'Allows you to specify a number that will demarcate which updates are considered "recent" and which are not. This will govern which updates are included in the RecentUpdates category in a change sorter') ('fill aggregate change set' fillAggregateChangeSet 'Creates a change-set named Aggregate into which all the changes in all the change sets in this category will be copied.') - ('back to main menu' offerUnshiftedChangeSetMenu 'Takes you back to the shifted change-set menu.') ('back to shifted menu' offerShiftedChangeSetMenu 'Takes you back to the primary change-set menu.')). ^ aMenu! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/30/2001 00:00'! changeSetList "Answer a list of ChangeSet names to be shown in the change sorter." ^ self changeSetCategory changeSetList! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 7/17/2002 11:37'! 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 == $B ifTrue: [^ self openChangeSetBrowser]. aChar == $c ifTrue: [^ self copyAllToOther]. aChar == $D ifTrue: [^ self toggleDiffing]. aChar == $f ifTrue: [^ self findCngSet]. aChar == $m ifTrue: [^ self newCurrent]. aChar == $n ifTrue: [^ self newSet]. aChar == $o ifTrue: [^ self fileOut]. aChar == $p ifTrue: [^ self addPreamble]. aChar == $r ifTrue: [^ self rename]. aChar == $s ifTrue: [^ self chooseChangeSetCategory]. aChar == $x ifTrue: [^ self remove]. aChar == $- ifTrue: [^ self subtractOtherSide]. ^ self messageListKey: aChar from: view! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 7/17/2002 11:37'! 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 category... (s)' action: #chooseChangeSetCategory. aMenu balloonTextForLastItem: 'Lets you choose which change sets should be listed in this change sorter'. aMenu add: 'select change set...' action: #chooseCngSet. aMenu addLine. aMenu add: 'rename change set (r)' action: #rename. aMenu add: 'file out (o)' action: #fileOut. aMenu add: 'mail to list' action: #mailOut. aMenu add: 'browse methods (b)' action: #browseChangeSet. aMenu add: 'browse change set (B)' action: #openChangeSetBrowser. 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: 'category functions...' action: #offerCategorySubmenu. aMenu balloonTextForLastItem: 'Various commands relating to change-set-categories'. aMenu addLine. aMenu add: 'destroy change set (x)' action: #remove. aMenu addLine. aMenu add: 'more...' action: #offerShiftedChangeSetMenu. ^ 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: 'nk 3/30/2002 08:56'! checkForAnyAlienAuthorship "Open a message list browser on all uncommented methods in the current change set that have alien authorship, even historically" myChangeSet checkForAnyAlienAuthorship ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/29/2001 12:47'! checkForUnclassifiedMethods "Open a message list browser on all methods in the current change set that have not been categorized" myChangeSet checkForUnclassifiedMethods ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 7/18/2002 17:58'! checkForUncommentedClasses "Open a class list browser on classes in the change set that lack class comments" myChangeSet checkForUncommentedClasses! ! !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: 'sd 11/20/2005 21:26'! chooseChangeSetCategory "Present the user with a list of change-set-categories and let her choose one" | cats aMenu result | self okToChange ifFalse: [^ self]. Smalltalk isMorphic ifTrue: [^ self chooseChangeSetCategoryInMorphic]. "gives balloon help" cats := ChangeSetCategories elementsInOrder. aMenu := SelectionMenu labels: (cats collect: [:cat | cat categoryName]) selections: cats. result := aMenu startUp. result ifNotNil: [changeSetCategory := result. self changed: #changeSetList. (self changeSetList includes: myChangeSet name) ifFalse: [self showChangeSet: (ChangeSorter changeSetNamed: self changeSetList first)]. self changed: #relabel]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! chooseChangeSetCategoryInMorphic "Present the user with a list of change-set-categories and let her choose one. In this morphic variant, we include balloon help" | aMenu | aMenu := MenuMorph new defaultTarget: self. aMenu title: 'Choose the category of change sets to show in this Change Sorter (red = current choice)'. ChangeSetCategories elementsInOrder do: [:aCategory | aMenu add: aCategory categoryName target: self selector: #showChangeSetCategory: argument: aCategory. aCategory == changeSetCategory ifTrue: [aMenu lastItem color: Color red]. aMenu balloonTextForLastItem: aCategory documentation]. aMenu popUpInWorld! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! chooseCngSet "Present the user with an alphabetical list of change set names, and let her choose one" | changeSetsSortedAlphabetically chosen | self okToChange ifFalse: [^ self]. changeSetsSortedAlphabetically := self changeSetList asSortedCollection: [:a :b | a asLowercase withoutLeadingDigits < b asLowercase withoutLeadingDigits]. chosen := (SelectionMenu selections: changeSetsSortedAlphabetically) startUp. chosen ifNil: [^ self]. self showChangeSet: (ChangeSorter changeSetNamed: chosen)! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'nk 1/4/2004 17:07'! 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." | aNewChangeSet stream | self okToChange ifFalse: [^ self]. ChangeSet promptForDefaultChangeSetDirectoryIfNecessary. stream := StandardFileMenu oldFileStreamFrom: ChangeSet defaultChangeSetDirectory. stream ifNil: [^ self]. aNewChangeSet := self class newChangesFromStream: stream named: (FileDirectory localNameFor: stream name). 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 3/30/2001 00:57'! fileOutAllChangeSets "File out all nonempty change sets in the current category, probably" self changeSetCategory fileOutAllChangeSets! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/30/2001 01:26'! fillAggregateChangeSet "Create a change-set named Aggregate and pour into it all the changes in all the change-sets of the currently-selected category" self changeSetCategory fillAggregateChangeSet! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! 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 nameList | self okToChange ifFalse: [^ self]. pattern := UIManager default request: 'ChangeSet name or fragment?'. pattern isEmpty ifTrue: [^ self]. nameList := self changeSetList asSet. candidates := ChangeSet allChangeSets select: [:c | (nameList includes: c name) and: [c name includesSubstring: pattern caseSensitive: false]]. candidates size = 0 ifTrue: [^ Beeper 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: 'sd 11/20/2005 21:26'! 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: 'rbb 3/1/2005 10:28'! makeNewCategory "Create a new, static change-set category, which will be populated entirely by change sets that have been manually placed in it" | catName aCategory | catName := UIManager default request: 'Please give the new category a name' initialAnswer: ''. catName isEmptyOrNil ifTrue: [^ self]. catName := catName asSymbol. (ChangeSetCategories includesKey: catName) ifTrue: [^ self inform: 'Sorry, there is already a category of that name']. aCategory := StaticChangeSetCategory new categoryName: catName. ChangeSetCategories elementAt: catName put: aCategory. aCategory addChangeSet: myChangeSet. self showChangeSetCategory: aCategory! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'rbb 3/1/2005 10:28'! makeNewCategoryShowingClassChanges "Create a new, static change-set category, which will be populated entirely by change sets that have been manually placed in it" | catName aCategory clsName | clsName := self selectedClass ifNotNil: [self selectedClass name ] ifNil: ['']. clsName := UIManager default request: 'Which class?' initialAnswer: clsName. clsName isEmptyOrNil ifTrue: [^ self]. catName := ('Changes to ', clsName) asSymbol. (ChangeSetCategories includesKey: catName) ifTrue: [^ self inform: 'Sorry, there is already a category of that name']. aCategory := ChangeSetCategoryWithParameters new categoryName: catName. aCategory membershipSelector: #changeSet:containsClass: ; parameters: { clsName }. ChangeSetCategories elementAt: catName put: aCategory. aCategory reconstituteList. self showChangeSetCategory: aCategory! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! methodConflictsWithOppositeCategory "Check to see if ANY change set on the other side shares any methods with the selected change set; if so, open a browser on all such." | aList otherCategory | otherCategory := (parent other: self) changeSetCategory. aList := myChangeSet messageListForChangesWhich: [ :aClass :aSelector | aClass notNil and: [otherCategory hasChangeForClassName: aClass name selector: aSelector otherThanIn: myChangeSet] ] ifNone: [^ self inform: 'There are no methods that appear both in this change set and in any change set (other than this one) on the other side.']. MessageSet openMessageList: aList name: 'Methods in "', myChangeSet name, '" also in some other change set in category ', otherCategory categoryName,' (', aList size printString, ')' ! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! 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 other | self checkThatSidesDiffer: [^ self]. other := (parent other: self) changeSet. aList := myChangeSet messageListForChangesWhich: [ :aClass :aSelector | aClass notNil and: [(other methodChangesAtClass: aClass name) includesKey: aSelector] ] ifNone: [^ 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: 'sd 5/23/2003 15:15'! newCurrent "make my change set be the current one that changes go into" ChangeSet newChanges: myChangeSet. self update. "Because list of changes in a category may thus have changed" self changed: #relabel.! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! 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 changeSetCategory acceptsManualAdditions ifTrue: [changeSetCategory addChangeSet: aSet]. self update. (changeSetCategory includesChangeSet: aSet) ifTrue: [self showChangeSet: aSet]. self changed: #relabel]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 4/11/2001 17:41'! offerCategorySubmenu "Offer a menu of category-related items" self offerMenuFrom: #categorySubmenu:shifted: shifted: false! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 2/27/2001 21:55'! offerShiftedChangeSetMenu "Offer the shifted version of the change set menu" self offerMenuFrom: #changeSetMenu:shifted: shifted: true! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/6/2001 14:41'! offerUnshiftedChangeSetMenu "Offer the unshifted version of the change set menu" self offerMenuFrom: #changeSetMenu:shifted: shifted: false! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/9/2001 15:30'! openChangeSetBrowser "Open a ChangeSet browser on the current change set" Smalltalk isMorphic ifFalse: [self browseChangeSet] "msg-list browser only" ifTrue: [(ChangeSetBrowser new myChangeSet: myChangeSet) openAsMorph]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 12/13/2003 18:14'! promoteToTopChangeSet "Move the selected change-set to the top of the list" self class promoteToTop: myChangeSet. (parent ifNil: [self]) modelWakeUp! ! !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: 'sd 11/20/2005 21:26'! removeCategory "Remove the current category" | itsName | self changeSetCategory acceptsManualAdditions ifFalse: [^ self inform: 'sorry, you can only remove manually-added categories.']. (self confirm: 'Really remove the change-set-category named ', (itsName := changeSetCategory categoryName), '?') ifFalse: [^ self]. ChangeSetCategories removeElementAt: itsName. self setDefaultChangeSetCategory. self update! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'rbb 3/1/2005 10:29'! removeContainedInClassCategories | matchExpression | myChangeSet removePreamble. matchExpression := UIManager default request: 'Enter class category name (wildcard is ok)' initialAnswer: 'System-*'. (Smalltalk organization categories select: [:each | matchExpression match: each]) do: [:eachCat | | classNames | classNames := Smalltalk organization listAtCategoryNamed: eachCat. classNames do: [:eachClassName | myChangeSet removeClassChanges: eachClassName. myChangeSet removeClassChanges: eachClassName , ' class']. self showChangeSet: myChangeSet]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! removeFromCategory "Add the current change set to the category viewed on the opposite side, if it's of the sort to accept things like that" | aCategory | (aCategory := self changeSetCategory) acceptsManualAdditions ifTrue: [aCategory removeElementAt: myChangeSet name. aCategory reconstituteList. self update] ifFalse: [self inform: 'sorry, this command only makes sense for static categories whose members are manually maintained']! ! !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: 'sd 11/20/2005 21:26'! 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 changeSetNumber msg | 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 ifTrue: [msg := myChangeSet hasPreamble ifTrue: [myChangeSet hasPostscript ifTrue: ['a preamble and a postscript'] ifFalse: ['a preamble']] ifFalse: [myChangeSet hasPostscript ifTrue: ['a postscript'] ifFalse: ['']]. msg isEmpty ifFalse: [(self confirm: 'Caution!! This change set has ', msg, ' 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" changeSetNumber := myChangeSet name initialIntegerOrNil. changeSetNumber ifNotNil: [SystemVersion current unregisterUpdate: changeSetNumber]. ChangeSorter removeChangeSet: myChangeSet. self showChangeSet: ChangeSet current.! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'rbb 3/1/2005 10:29'! rename "Store a new name string into the selected ChangeSet. reject duplicate name; allow user to back out" | newName | newName := UIManager default request: 'New name for this change set' initialAnswer: myChangeSet name. (newName = myChangeSet name or: [newName size == 0]) ifTrue: [^ Beeper beep]. (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: 'rbb 3/1/2005 10:29'! renameCategory "Obtain a new name for the category and, if acceptable, apply it" | catName oldName | self changeSetCategory acceptsManualAdditions ifFalse: [^ self inform: 'sorry, you can only rename manually-added categories.']. catName := UIManager default request: 'Please give the new category a name' initialAnswer: (oldName := changeSetCategory categoryName). catName isEmptyOrNil ifTrue: [^ self]. (catName := catName asSymbol) = oldName ifTrue: [^ self inform: 'no change.']. (ChangeSetCategories includesKey: catName) ifTrue: [^ self inform: 'Sorry, there is already a category of that name']. changeSetCategory categoryName: catName. ChangeSetCategories removeElementAt: oldName. ChangeSetCategories elementAt: catName put: changeSetCategory. self update! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sw 3/5/2001 11:03'! reorderChangeSets "apply a standard reordering -- let the class handle this" ^ self class reorderChangeSets! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! setRecentUpdatesMarker "Allow the user to change the recent-updates marker" | result | result := FillInTheBlank request: ('Enter the lowest change-set number that you wish to consider "recent"? (note: highest change-set number in this image at this time is ', ChangeSet highestNumberedChangeSet asString, ')') initialAnswer: self class recentUpdateMarker asString. (result notNil and: [result startsWithDigit]) ifTrue: [self class recentUpdateMarker: result asInteger. Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'mu 12/11/2003 20:05'! 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]. "CONFLICTS SECTION" 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 change set opposite' 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 add: 'conflicts with category opposite' action: #methodConflictsWithOppositeCategory. aMenu balloonTextForLastItem: 'Browse all methods that occur both in this change set and in ANY change set in the category list on the opposite side of this change sorter, other of course than this change set itself. (Caution -- this could be VERY slow)']. aMenu addLine. "CHECKS SECTION" 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'. aMenu add: 'check for uncommented classes' action: #checkForUncommentedClasses. aMenu balloonTextForLastItem: 'Check for classes with code in this changeset which lack class 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 add: 'check for any other authors' action: #checkForAnyAlienAuthorship. aMenu balloonTextForLastItem: 'Check this change set for methods any of whose authoring stamps do not start with "', Utilities authorInitials, '"']. aMenu add: 'check for uncategorized methods' action: #checkForUnclassifiedMethods. aMenu balloonTextForLastItem: 'Check to see if any methods in the selected change set have not yet been assigned to a category. If any are found, open a browser on them.'. 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: 'remove contained in class categories...' action: #removeContainedInClassCategories. aMenu balloonTextForLastItem: ' Drops any changes in given class categories'. 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: 'reorder all change sets' action: #reorderChangeSets. aMenu balloonTextForLastItem: 'Applies a standard reordering of all change-sets in the system -- at the bottom will come the sets that come with the release; next will come all the numbered updates; finally, at the top, will come all other change sets'. aMenu addLine. aMenu add: 'more...' action: #offerUnshiftedChangeSetMenu. aMenu balloonTextForLastItem: 'Takes you back to the primary change-set menu.'. ^ aMenu! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! showCategoriesOfChangeSet "Show a list of all the categories in which the selected change-set occurs at the moment. Install the one the user chooses, if any." | aMenu | Smalltalk isMorphic ifFalse: [self inform: 'Only available in morphic, right now, sorry. It would not take much to make this also work in mvc, so if you are inclined to do that, thanks in advance...'] ifTrue: [aMenu := MenuMorph new defaultTarget: self. aMenu title: 'Categories which contain change set "', myChangeSet name, '"'. ChangeSetCategories elementsInOrder do: [:aCategory | (aCategory includesChangeSet: myChangeSet) ifTrue: [aMenu add: aCategory categoryName target: self selector: #showChangeSetCategory: argument: aCategory. aCategory == changeSetCategory ifTrue: [aMenu lastItem color: Color red]]. aMenu balloonTextForLastItem: aCategory documentation]. aMenu popUpInWorld]! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! showChangeSetCategory: aChangeSetCategory "Show the given change-set category" changeSetCategory := aChangeSetCategory. self changed: #changeSetList. (self changeSetList includes: myChangeSet name) ifFalse: [self showChangeSet: (ChangeSorter changeSetNamed: self changeSetList first)]. self changed: #relabel! ! !ChangeSorter methodsFor: 'changeSet menu' stamp: 'sd 11/20/2005 21:26'! 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 all | 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. all := ChangeSet allChangeSets. nextToView := ((all includes: myChangeSet) and: [(i := all indexOf: myChangeSet) < all size]) ifTrue: [all at: i+1] ifFalse: [other]. self removePrompting: false. self showChangeSet: nextToView. parent modelWakeUp. ! ! !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: 'sd 11/20/2005 21:26'! updateIfNecessary "Recompute all of my panes." | newList | self okToChange ifFalse: [^ self]. myChangeSet ifNil: [^ self]. "Has been known to happen though shouldn't" (myChangeSet isMoribund or: [(changeSetCategory notNil and: [changeSetCategory includesChangeSet: myChangeSet]) not]) ifTrue: [self changed: #changeSetList. ^ self showChangeSet: self changeSetCategory defaultChangeSetToShow]. newList := self changeSetList. (priorChangeSetList == nil or: [priorChangeSetList ~= newList]) ifTrue: [priorChangeSetList := newList. self changed: #changeSetList]. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 3/29/2001 15:19'! classList "Computed. View should try to preserve selections, even though index changes" ^ myChangeSet ifNotNil: [myChangeSet changedClassNames] ifNil: [OrderedCollection new] ! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 3/5/2001 18:24'! classListKey: aChar from: view "Respond to a Command key in the class-list pane." aChar == $x ifTrue: [^ self removeClass]. aChar == $d ifTrue: [^ self forgetClass]. ^ self messageListKey: aChar from: view "picks up b,h,p"! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 11/3/2001 09:34'! classListMenu: 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 from change set (d)' forgetClass) ('remove class from system (x)' removeClass) - ('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...' offerShiftedClassListMenu))] ifTrue: [#( - ('unsent methods' browseUnusedMethods) ('unreferenced inst vars' showUnreferencedInstVars) ('unreferenced class vars' showUnreferencedClassVars) - ('sample instance' makeSampleInstance) ('inspect instances' inspectInstances) ('inspect subinstances' inspectSubInstances) - ('more...' offerUnshiftedClassListMenu ))]). ^ aMenu! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 2/26/2001 12:00'! classMenu: aMenu "Set up aMenu for the class-list. Retained for backward compatibility with old change sorters in image segments" ^ self classListMenu: aMenu shifted: false! ! !ChangeSorter methodsFor: 'class list' stamp: 'sw 3/6/2001 12:40'! classMenu: aMenu shifted: shifted "Fill aMenu with items appropriate for the class list. Retained for bkwd compatibility" ^ self classListMenu: aMenu shifted: shifted! ! !ChangeSorter methodsFor: 'class list' stamp: 'sd 11/20/2005 21:26'! copyClassToOther "Place these changes in the other changeSet also" | otherSorter otherChangeSet | self checkThatSidesDiffer: [^ self]. self okToChange ifFalse: [^ Beeper beep]. currentClassName ifNil: [^ Beeper 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: 'sd 11/20/2005 21:26'! currentClassName: aString currentClassName := aString. currentSelector := nil. "fix by wod" self changed: #currentClassName. self changed: #messageList. self setContents. self contentsChanged.! ! !ChangeSorter methodsFor: 'class list' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sw 3/5/2001 18:30'! messageListKey: aChar from: view "Respond to a Command key in the message-list pane." aChar == $d ifTrue: [^ self forget]. super messageListKey: aChar from: view! ! !ChangeSorter methodsFor: 'class list' stamp: 'nb 6/17/2003 12:25'! moveClassToOther "Place class changes in the other changeSet and remove them from this one" self checkThatSidesDiffer: [^ self]. (self okToChange and: [currentClassName notNil]) ifFalse: [^ Beeper beep]. self copyClassToOther. self forgetClass! ! !ChangeSorter methodsFor: 'class list' stamp: 'sd 11/20/2005 21:26'! 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: 'code pane' stamp: 'sd 11/20/2005 21:26'! 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: 'md 7/17/2006 10:41'! 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. (#(prettyPrint colorPrint prettyDiffs) includes: contentsSymbol) ifTrue: [contents := class prettyPrinterClass format: contents in: class notifying: nil contentsSymbol: contentsSymbol]. self showingAnyKindOfDiffs 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 11/13/2001 07:34'! toggleDiffing "Toggle whether diffs should be shown in the code pane" self okToChange ifTrue: [super toggleDiffing. self changed: #contents. self update] ! ! !ChangeSorter methodsFor: 'code pane' stamp: 'JW 2/2/2001 21:41'! wantsOptionalButtons "No optional buttons for ChangeSorter" ^false! ! !ChangeSorter methodsFor: 'creation' stamp: 'sd 11/20/2005 21:26'! morphicWindow "ChangeSorter new openAsMorph" | window | myChangeSet ifNil: [self myChangeSet: ChangeSet current]. window := (SystemWindow labelled: self labelString) model: self. self openAsMorphIn: window rect: (0@0 extent: 1@1). ^ window ! ! !ChangeSorter methodsFor: 'creation' stamp: 'sd 11/20/2005 21:26'! open "ChangeSorterPluggable new open" | topView | Smalltalk isMorphic | Sensor leftShiftDown ifTrue: [^ self openAsMorph]. topView := StandardSystemView new. topView model: self. myChangeSet ifNil: [self myChangeSet: ChangeSet current]. 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: 'sd 11/20/2005 21:26'! 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" "The method SystemWindow>>addMorph:fullFrame: checks scrollBarsOnRight, then adds the morph at the back if true, otherwise it is added in front. But flopout hScrollbars needs the crrentSelector pane to be behind the upper ones in the draw order. Hence the value of scrollBarsOnRight affects the order in which the lowerpanes are added." Preferences scrollBarsOnRight ifFalse: [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)]. 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: #classListMenu:shifted: keystroke: #classListKey:from:) frame: (((0.5@0 extent: 0.5@csListHeight) scaleBy: rect extent) translateBy: rect origin). Preferences scrollBarsOnRight ifTrue: [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: 'sd 11/20/2005 21:26'! 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: #classListMenu: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: 'sd 11/20/2005 21:26'! setDefaultChangeSetCategory "Set a default ChangeSetCategory for the receiver, and answer it" ^ changeSetCategory := self class changeSetCategoryNamed: #All! ! !ChangeSorter methodsFor: 'creation' stamp: 'sd 11/20/2005 21:26'! veryDeepFixupWith: deepCopier super veryDeepFixupWith: deepCopier. parent := deepCopier references at: parent ifAbsent: [parent]. self updateIfNecessary! ! !ChangeSorter methodsFor: 'creation' stamp: 'sd 11/20/2005 21:26'! 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. changeSetCategory := changeSetCategory. ! ! !ChangeSorter methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! currentSelector: messageName currentSelector := messageName. self changed: #currentSelector. self setContents. self contentsChanged.! ! !ChangeSorter methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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 3/9/2001 14:27'! messageListMenu: aMenu shifted: shifted "Fill aMenu with items appropriate for the message list; could be for a single or double changeSorter" ^ self messageMenu: aMenu shifted: shifted! ! !ChangeSorter methodsFor: 'message list' stamp: 'sw 3/5/2001 18:26'! 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 changeSet (d)' 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: 'nk 7/30/2004 17:58'! moveMethodToOther "Place this change in the other changeSet and remove it from this side" | other cls sel | self checkThatSidesDiffer: [^self]. self okToChange ifFalse: [^Beeper beep]. currentSelector ifNotNil: [other := (parent other: self) changeSet. other == myChangeSet ifTrue: [^Beeper 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: 'sd 11/20/2005 21:26'! removeFromCurrentChanges "Redisplay after removal in case we are viewing the current changeSet" super removeFromCurrentChanges. currentSelector := nil. self showChangeSet: myChangeSet! ! !ChangeSorter methodsFor: 'message list' stamp: 'sd 11/20/2005 21:26'! 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 systemNavigation confirmRemovalOf: (sel := self selectedMessageName) on: self selectedClassOrMetaClass. confirmation == 3 ifTrue: [^ self]. self selectedClassOrMetaClass removeSelector: sel. self update. confirmation == 2 ifTrue: [self systemNavigation browseAllCallsOn: sel]]! ! !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: 'toolbuilder' stamp: 'ar 2/11/2005 20:32'! buildWith: builder " MorphicUIBuilder open: ChangeSorter. " | windowSpec | windowSpec := builder pluggableWindowSpec new. windowSpec label: 'Change Sorter'. windowSpec model: self. windowSpec children: OrderedCollection new. self buildWith: builder in: windowSpec rect: (0@0 extent: 1@1). ^builder build: windowSpec! ! !ChangeSorter methodsFor: 'toolbuilder' stamp: 'sd 11/20/2005 21:26'! buildWith: builder in: window rect: rect | csListHeight msgListHeight csMsgListHeight listSpec textSpec | contents := ''. csListHeight := 0.25. msgListHeight := 0.25. csMsgListHeight := csListHeight + msgListHeight. listSpec := builder pluggableListSpec new. listSpec model: self; list: #changeSetList; getSelected: #currentCngSet; setSelected: #showChangeSetNamed:; menu: #changeSetMenu:shifted:; keyPress: #changeSetListKey:from:; autoDeselect: false; frame: (((0@0 extent: 0.5@csListHeight) scaleBy: rect extent) translateBy: rect origin). window children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #classList; getSelected: #currentClassName; setSelected: #currentClassName:; menu: #classListMenu:shifted:; keyPress: #classListKey:from:; frame: (((0.5@0 extent: 0.5@csListHeight) scaleBy: rect extent) translateBy: rect origin). window children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #messageList; getSelected: #currentSelector; setSelected: #currentSelector:; menu: #messageMenu:shifted:; keyPress: #messageListKey:from:; frame: (((0@csListHeight extent: 1@msgListHeight) scaleBy: rect extent) translateBy: rect origin). window children add: listSpec. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #contents; setText: #contents:notifying:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (((0@csMsgListHeight corner: 1@1) scaleBy: rect extent) translateBy: rect origin). window children add: textSpec. ^window! ! !ChangeSorter methodsFor: 'traits' stamp: 'al 7/18/2004 11:44'! selectedClassOrMetaClass "Careful, the class may have been removed!!" | cName tName | currentClassName ifNil: [^ nil]. (currentClassName endsWith: ' class') ifTrue: [cName _ (currentClassName copyFrom: 1 to: currentClassName size-6) asSymbol. ^ (Smalltalk at: cName ifAbsent: [^nil]) class]. (currentClassName endsWith: ' classTrait') ifTrue: [tName _ (currentClassName copyFrom: 1 to: currentClassName size-11) asSymbol. ^ (Smalltalk at: tName ifAbsent: [^nil]) classTrait]. cName _ currentClassName asSymbol. ^ Smalltalk at: cName ifAbsent: [nil]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangeSorter class instanceVariableNames: ''! !ChangeSorter class methodsFor: 'adding' stamp: 'ar 7/15/2005 21:15'! basicNewChangeSet: newName ^ChangeSet basicNewChangeSet: newName! ! !ChangeSorter class methodsFor: 'adding' stamp: 'rbb 3/1/2005 10:29'! 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 := UIManager default request: 'Please name the new change set:' initialAnswer: ChangeSet defaultName. newName isEmptyOrNil ifTrue: [^ nil]. newSet := self basicNewChangeSet: newName. newSet ifNotNil: [ChangeSet newChanges: newSet]. ^ newSet! ! !ChangeSorter class methodsFor: 'adding' stamp: 'sd 11/20/2005 21:28'! 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: 'ar 7/16/2005 14:20'! newChangesFromStream: aStream named: aName ^ChangeSet newChangesFromStream: aStream named: aName ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 16:01'! belongsInAdditions: aChangeSet "Answer whether a change set belongs in the Additions category, which is fed by all change sets that are neither numbered nor in the initial release" ^ (((self belongsInProjectsInRelease: aChangeSet) or: [self belongsInNumbered: aChangeSet])) not! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 12:38'! belongsInAll: aChangeSet "Answer whether a change set belongs in the All category" ^ true ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 12:47'! belongsInMyInitials: aChangeSet "Answer whether a change set belongs in the MyInitials category. " ^ aChangeSet name endsWith: ('-', Utilities authorInitials)! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 12:45'! belongsInNumbered: aChangeSet "Answer whether a change set belongs in the Numbered category. " ^ aChangeSet name startsWithDigit! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 12:49'! belongsInProjectChangeSets: aChangeSet "Answer whether a change set belongs in the MyInitials category. " ^ aChangeSet belongsToAProject! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:28'! belongsInProjectsInRelease: aChangeSet "Answer whether a change set belongs in the ProjectsInRelease category. You can hand-tweak this to suit your working style. This just covers the space of project names in the 2.9, 3.0, and 3.1a systems" | aString | ^ ((aString := aChangeSet name) beginsWith: 'Play With Me') or: [self changeSetNamesInReleaseImage includes: aString]! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/30/2001 12:56'! belongsInRecentUpdates: aChangeSet "Answer whether a change set belongs in the RecentUpdates category." ^ aChangeSet name startsWithDigit and: [aChangeSet name asInteger >= self recentUpdateMarker]! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 3/29/2001 14:44'! changeSetCategoryNamed: aName "Answer the changeSetCategory of the given name, or nil if none" ^ ChangeSetCategories elementAt: aName asSymbol ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:28'! changeSetNamesInReleaseImage "Answer a list of names of project change sets that come pre-shipped in the latest sytem release. On the brink of shipping a new release, call 'ChangeSorter noteChangeSetsInRelease' " ^ ChangeSetNamesInRelease ifNil: [ChangeSetNamesInRelease := self changeSetNamesInThreeOh]! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sw 4/16/2002 00:45'! changeSetNamesInThreeOh "Hard-coded: answer a list of names of project change sets that came pre-shipped in Squeak 3.0" ^ #('The Worlds of Squeak' 'Fun with Morphic' 'Games' 'Fun With Music' 'Building with Squeak' 'Squeak and the Internet' 'Squeak in 3D' 'More About Sound' ) ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'ar 9/27/2005 19:55'! initialize "Initialize the class variables" ChangeSetCategories ifNil: [self initializeChangeSetCategories]. RecentUpdateMarker := 0. "ChangeSorter initialize" FileList registerFileReader: self. self registerInFlapsRegistry. ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:28'! initializeChangeSetCategories "Initialize the set of change-set categories" "ChangeSorter initializeChangeSetCategories" | aCategory | ChangeSetCategories := ElementCategory new categoryName: #ChangeSetCategories. aCategory := ChangeSetCategory new categoryName: #All. aCategory membershipSelector: #belongsInAll:. aCategory documentation: 'All change sets known to the system'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #Additions. aCategory membershipSelector: #belongsInAdditions:. aCategory documentation: 'All unnumbered change sets except those representing projects in the system as initially released.'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #MyInitials. aCategory membershipSelector: #belongsInMyInitials:. aCategory documentation: 'All change sets whose names end with the current author''s initials.'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #Numbered. aCategory membershipSelector: #belongsInNumbered:. aCategory documentation: 'All change sets whose names start with a digit -- normally these will be the official updates to the system.'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #ProjectChangeSets. aCategory membershipSelector: #belongsInProjectChangeSets:. aCategory documentation: 'All change sets that are currently associated with projects present in the system right now.'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #ProjectsInRelease. aCategory membershipSelector: #belongsInProjectsInRelease:. aCategory documentation: 'All change sets belonging to projects that were shipped in the initial release of this version of Squeak'. ChangeSetCategories addCategoryItem: aCategory. aCategory := ChangeSetCategory new categoryName: #RecentUpdates. aCategory membershipSelector: #belongsInRecentUpdates:. aCategory documentation: 'Updates whose numbers are at or beyond the number I have designated as the earliest one to qualify as Recent'. ChangeSetCategories addCategoryItem: aCategory. ChangeSetCategories elementsInOrder do: [:anElem | anElem reconstituteList] ! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:28'! noteChangeSetsInRelease "Freshly compute what the change sets in the release are; to be called manually just before a release" ChangeSetNamesInRelease := (Project allProjects collect: [:p | p name]) asSet asOrderedCollection. "ChangeSorter noteChangeSetsInRelease"! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:42'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(ChangeSorter prototypicalToolWindow 'Change Set' 'A tool that allows you to view and manipulate all the code changes in a single change set') forFlapNamed: 'Tools']! ! !ChangeSorter class methodsFor: 'class initialization' stamp: 'ar 9/27/2005 19:56'! unload "Unload the receiver from global registries" self environment at: #FileList ifPresent: [:cl | cl unregisterFileReader: self]. self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'di 4/5/2001 21:33'! allChangeSetNames ^ self allChangeSets collect: [:c | c name]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:15'! allChangeSets "Return the list of all current ChangeSets" ^ChangeSet allChangeSets! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'di 4/5/2001 21:34'! allChangeSetsWithClass: class selector: selector class ifNil: [^ #()]. ^ self allChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sd 11/20/2005 21:28'! changeSet: aChangeSet containsClass: aClass | theClass | theClass := Smalltalk classNamed: aClass. theClass ifNil: [^ false]. ^ aChangeSet containsClass: theClass! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:15'! changeSetNamed: aName "Return the change set of the given name, or nil if none found. 1/22/96 sw" ^ChangeSet named: aName! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:16'! changeSetsNamedSuchThat: nameBlock ^ChangeSet changeSetsNamedSuchThat: nameBlock! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:16'! existingOrNewChangeSetNamed: aName ^ChangeSet existingOrNewChangeSetNamed: aName! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'ar 7/15/2005 21:16'! gatherChangeSets "ChangeSorter gatherChangeSets" ^ChangeSet gatherChangeSets! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sd 11/20/2005 21:28'! highestNumberedChangeSet "ChangeSorter highestNumberedChangeSet" | aList | aList := (ChangeSet allChangeSetNames select: [:aString | aString startsWithDigit] thenCollect: [:aString | aString initialIntegerOrNil]). ^ (aList size > 0) ifTrue: [aList max] ifFalse: [nil] ! ! !ChangeSorter class methodsFor: 'enumerating' stamp: 'sd 11/20/2005 21:28'! mostRecentChangeSetWithChangeForClass: class selector: selector | hits | hits := self allChangeSets 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: 'ar 7/15/2005 21:16'! promoteToTop: aChangeSet "Make aChangeSet the first in the list from now on" ^ChangeSet promoteToTop: aChangeSet! ! !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: 'ar 7/15/2005 21:16'! removeChangeSet: aChangeSet "Remove the given changeSet. Caller must assure that it's cool to do this" ^ChangeSet removeChangeSet: aChangeSet! ! !ChangeSorter class methodsFor: 'removing' stamp: 'di 4/5/2001 21:12'! removeChangeSetsNamedSuchThat: nameBlock (ChangeSorter changeSetsNamedSuchThat: nameBlock) do: [:cs | self removeChangeSet: cs]! ! !ChangeSorter class methodsFor: 'removing' stamp: 'sd 11/20/2005 21:28'! 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 | self removeChangeSet: cs]. self inform: toGo size printString, ' change set(s) removed.'! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! assuredChangeSetNamed: aName "Answer a change set of the given name. If one already exists, answer that, else create a new one and answer it." | existing | ^ (existing := self changeSetNamed: aName) ifNotNil: [existing] ifNil: [self basicNewChangeSet: aName]! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! browseChangeSetsWithClass: class selector: selector "Put up a menu comprising a list of change sets that hold changes for the given class and selector. If the user selects one, open a single change-sorter onto it" | hits index | hits := self allChangeSets select: [:cs | (cs atSelector: selector class: class) ~~ #none]. hits isEmpty ifTrue: [^ self inform: class name, '.', selector , ' is not in any change set']. index := hits size == 1 ifTrue: [1] ifFalse: [(UIManager default chooseFrom: (hits collect: [:cs | cs name]) lines: #())]. index = 0 ifTrue: [^ self]. (ChangeSorter new myChangeSet: (hits at: index)) open. ! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! browseChangeSetsWithSelector: aSelector "Put up a list of all change sets that contain an addition, deletion, or change of any method with the given selector" | hits index | hits := self allChangeSets select: [:cs | cs hasAnyChangeForSelector: aSelector]. hits isEmpty ifTrue: [^ self inform: aSelector , ' is not in any change set']. index := hits size == 1 ifTrue: [1] ifFalse: [(UIManager default chooseFrom: (hits collect: [:cs | cs name]) lines: #())]. index = 0 ifTrue: [^ self]. (ChangeSetBrowser new myChangeSet: (hits at: index)) open "ChangeSorter browseChangeSetsWithSelector: #clearPenTrails" ! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! 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 allChangeSets do: [:aChangeSet | aChangeSet == aggregateChangeSet ifFalse: [aggregateChangeSet assimilateAllChangesFoundIn: aChangeSet]] "ChangeSorter buildAggregateChangeSet" ! ! !ChangeSorter class methodsFor: 'services' stamp: 'sw 6/6/2001 12:51'! countOfChangeSetsWithClass: aClass andSelector: aSelector "Answer how many change sets record a change for the given class and selector" ^ (self allChangeSetsWithClass: aClass selector: aSelector) size! ! !ChangeSorter class methodsFor: 'services' stamp: 'sw 6/6/2001 12:52'! doesAnyChangeSetHaveClass: aClass andSelector: aSelector "Answer whether any known change set bears a change for the given class and selector" ^ (self countOfChangeSetsWithClass: aClass andSelector: aSelector) > 0! ! !ChangeSorter class methodsFor: 'services' stamp: 'sw 6/13/2001 00:56'! prototypicalToolWindow "Answer a window representing a prototypical instance of the receiver" ^ self new morphicWindow applyModelExtent! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! recentUpdateMarker "Answer the number representing the threshold of what counts as 'recent' for an update number. This allow you to use the RecentUpdates category in a ChangeSorter to advantage" ^ RecentUpdateMarker ifNil: [RecentUpdateMarker := 0]! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! recentUpdateMarker: aNumber "Set the recent update marker as indicated" ^ RecentUpdateMarker := aNumber! ! !ChangeSorter class methodsFor: 'services' stamp: 'sd 11/20/2005 21:28'! reorderChangeSets "Change the order of the change sets to something more convenient: First come the project changesets that come with the release. These are mostly empty. Next come all numbered updates. Next come all remaining changesets In a ChangeSorter, they will appear in the reversed order." "ChangeSorter reorderChangeSets" | newHead newMid newTail | newHead := OrderedCollection new. newMid := OrderedCollection new. newTail := OrderedCollection new. ChangeSet allChangeSets do: [:aChangeSet | (self belongsInProjectsInRelease: aChangeSet) ifTrue: [newHead add: aChangeSet] ifFalse: [(self belongsInNumbered: aChangeSet) ifTrue: [newMid add: aChangeSet] ifFalse: [newTail add: aChangeSet]]]. ChangeSet allChangeSets: newHead, newMid, newTail. Smalltalk isMorphic ifTrue: [SystemWindow wakeUpTopWindowUponStartup]! ! !ChangeSorter class methodsFor: 'services' stamp: 'ar 7/15/2005 21:17'! secondaryChangeSet ^ChangeSet secondaryChangeSet! ! !ChangeSorter class methodsFor: 'utilities' stamp: 'sd 11/20/2005 21:28'! fileOutChangeSetsNamed: nameList "File out the list of change sets whose names are provided" "ChangeSorter fileOutChangeSetsNamed: #('New Changes' 'miscTidies-sw')" | notFound aChangeSet infoString empty | notFound := OrderedCollection new. empty := OrderedCollection new. nameList do: [:aName | (aChangeSet := self changeSetNamed: aName) ifNotNil: [aChangeSet isEmpty ifTrue: [empty add: aName] ifFalse: [aChangeSet fileOut]] ifNil: [notFound add: aName]]. infoString := (nameList size - notFound size) printString, ' change set(s) filed out'. notFound size > 0 ifTrue: [infoString := infoString, ' ', notFound size printString, ' change set(s) not found:'. notFound do: [:aName | infoString := infoString, ' ', aName]]. empty size > 0 ifTrue: [infoString := infoString, ' ', empty size printString, ' change set(s) were empty:'. empty do: [:aName | infoString := infoString, ' ', aName]]. self inform: infoString! ! !ChangeSorter class methodsFor: 'window color' stamp: 'sw 2/26/2002 14:09'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Change Sorter' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'A tool that lets you see the code for one change set at a time.'! ! MessageSet subclass: #ChangedMessageSet instanceVariableNames: 'changeSet' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser'! !ChangedMessageSet commentStamp: '' prior: 0! A ChangedMessageSet is a message set associated with a change-set; it bears an entry for every method added or changed in the change set, as well as for every class-comment of which the change-set bears a note.! !ChangedMessageSet methodsFor: 'acceptance' stamp: 'md 2/20/2006 18:42'! contents: aString notifying: aController "Accept the string as new source for the current method, and make certain the annotation pane gets invalidated" | existingSelector existingClass superResult newSelector | existingSelector := self selectedMessageName. existingClass := self selectedClassOrMetaClass. superResult := super contents: aString notifying: aController. superResult ifTrue: "succeeded" [newSelector := existingClass parserClass new parseSelector: aString. newSelector ~= existingSelector ifTrue: "Selector changed -- maybe an addition" [self reformulateList. self changed: #messageList. self messageList doWithIndex: [:aMethodReference :anIndex | (aMethodReference actualClass == existingClass and: [aMethodReference methodSymbol == newSelector]) ifTrue: [self messageListIndex: anIndex]]]]. ^ superResult! ! !ChangedMessageSet methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! changeSet: aChangeSet changeSet := aChangeSet! ! !ChangedMessageSet methodsFor: 'message list' stamp: 'sw 1/28/2001 20:59'! growable "Answer whether the receiver can be changed by manual additions & deletions" ^ false! ! !ChangedMessageSet methodsFor: 'reformulation' stamp: 'sw 6/26/2001 11:20'! reformulateList "Reformulate the message list of the receiver" self initializeMessageList: (changeSet changedMessageListAugmented select: [:each | each isValid]) ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChangedMessageSet class instanceVariableNames: ''! !ChangedMessageSet class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:28'! 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 := aChangeSet changedMessageListAugmented select: [ :each | each isValid]. self openMessageList: messageSet name: 'Methods in Change Set ', aChangeSet name autoSelect: nil changeSet: aChangeSet! ! !ChangedMessageSet class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:28'! 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)]! ! Magnitude subclass: #Character instanceVariableNames: 'value' classVariableNames: 'CharacterTable ClassificationTable LetterBits LowercaseBit UppercaseBit' poolDictionaries: '' category: 'Collections-Strings'! !Character commentStamp: 'ar 4/9/2005 22:35' prior: 0! I represent a character by storing its associated Unicode. The first 256 characters are created uniquely, so that all instances of latin1 characters ($R, for example) are identical. The code point is based on Unicode. Since Unicode is 21-bit wide character set, we have several bits available for other information. As the Unicode Standard states, a Unicode code point doesn't carry the language information. This is going to be a problem with the languages so called CJK (Chinese, Japanese, Korean. Or often CJKV including Vietnamese). Since the characters of those languages are unified and given the same code point, it is impossible to display a bare Unicode code point in an inspector or such tools. To utilize the extra available bits, we use them for identifying the languages. Since the old implementation uses the bits to identify the character encoding, the bits are sometimes called "encoding tag" or neutrally "leading char", but the bits rigidly denotes the concept of languages. The other languages can have the language tag if you like. This will help to break the large default font (font set) into separately loadable chunk of fonts. However, it is open to the each native speakers and writers to decide how to define the character equality, since the same Unicode code point may have different language tag thus simple #= comparison may return false. 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' stamp: 'yo 12/29/2002 10:11'! charCode ^ (value bitAnd: 16r3FFFFF). ! ! !Character methodsFor: 'accessing' stamp: 'RAH 4/25/2000 19:49'! codePoint "Return the encoding value of the receiver." #Fundmntl. ^ self asciiValue! ! !Character methodsFor: 'accessing' stamp: 'yo 12/1/2003 19:30'! 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." ^ (EncodedCharSet charsetAt: self leadingChar) digitValue: self. ! ! !Character methodsFor: 'accessing' stamp: 'yo 12/29/2002 10:14'! leadingChar ^ (value bitAnd: (16r3FC00000)) bitShift: -22. ! ! !Character methodsFor: 'comparing' stamp: 'md 8/2/2005 18:21'! sameAs: aCharacter "Answer whether the receiver is equal to aCharacter, ignoring case" ^ (self asLowercase = aCharacter asLowercase) ! ! !Character methodsFor: 'comparing'! < aCharacter "Answer true if the receiver's value < aCharacter's value." ^self asciiValue < aCharacter asciiValue! ! !Character methodsFor: 'comparing' stamp: 'ar 4/9/2005 21:48'! = 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 or:[ aCharacter isCharacter and: [self asciiValue = aCharacter asciiValue]]! ! !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: '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' stamp: 'yo 8/16/2004 11:35'! asLowercase "If the receiver is uppercase, answer its matching lowercase Character." "A tentative implementation. Eventually this should consult the Unicode table." | v | v _ self charCode. (((8r101 <= v and: [v <= 8r132]) or: [16rC0 <= v and: [v <= 16rD6]]) or: [16rD8 <= v and: [v <= 16rDE]]) 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: 'raa 5/26/2001 09:54'! asSymbol "Answer a Symbol consisting of the receiver as the only element." ^Symbol internCharacter: self! ! !Character methodsFor: 'converting' stamp: 'tk 9/4/2000 12:05'! asText ^ self asString asText! ! !Character methodsFor: 'converting' stamp: 'ar 4/9/2005 21:51'! asUnicode | table charset v | self leadingChar = 0 ifTrue: [^ value]. charset _ EncodedCharSet charsetAt: self leadingChar. charset isCharset ifFalse: [^ self charCode]. table _ charset ucsTable. table isNil ifTrue: [^ 16rFFFD]. v _ table at: self charCode + 1. v = -1 ifTrue: [^ 16rFFFD]. ^ v. ! ! !Character methodsFor: 'converting' stamp: 'ar 4/9/2005 22:25'! asUnicodeChar "@@@ FIXME: Make this use asUnicode and move it to its lonely sender @@@" | table charset v | self leadingChar = 0 ifTrue: [^ value]. charset _ EncodedCharSet charsetAt: self leadingChar. charset isCharset ifFalse: [^ self]. table _ charset ucsTable. table isNil ifTrue: [^ Character value: 16rFFFD]. v _ table at: self charCode + 1. v = -1 ifTrue: [^ Character value: 16rFFFD]. ^ Character leadingChar: charset unicodeLeadingChar code: v.! ! !Character methodsFor: 'converting' stamp: 'yo 8/16/2004 11:34'! asUppercase "If the receiver is lowercase, answer its matching uppercase Character." "A tentative implementation. Eventually this should consult the Unicode table." | v | v _ self charCode. (((8r141 <= v and: [v <= 8r172]) or: [16rE0 <= v and: [v <= 16rF6]]) or: [16rF8 <= v and: [v <= 16rFE]]) ifTrue: [^ Character value: value - 8r40] ifFalse: [^ self] ! ! !Character methodsFor: 'converting' stamp: 'yo 8/11/2003 21:18'! basicSqueakToIso | asciiValue | value < 128 ifTrue: [^ self]. value > 255 ifTrue: [^ self]. asciiValue _ #(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 134 176 162 163 167 149 182 223 174 169 153 180 168 128 198 216 129 177 138 141 165 181 142 143 144 154 157 170 186 158 230 248 191 161 172 166 131 173 178 171 187 133 160 192 195 213 140 156 150 151 147 148 145 146 247 179 253 159 185 164 139 155 188 189 135 183 130 132 137 194 202 193 203 200 205 206 207 204 211 212 190 210 218 219 217 208 136 152 175 215 221 222 184 240 254 255 256 ) at: self asciiValue - 127. ^ Character value: asciiValue. ! ! !Character methodsFor: 'converting' stamp: 'ar 4/9/2005 22:15'! isoToSqueak ^self "no longer needed"! ! !Character methodsFor: 'converting' stamp: 'ar 4/10/2005 16:05'! macToSqueak "Convert the receiver from MacRoman to Squeak encoding" | asciiValue | value < 128 ifTrue: [^ self]. value > 255 ifTrue: [^ self]. asciiValue _ #(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 134 176 162 163 167 149 182 223 174 169 153 180 168 128 198 216 129 177 138 141 165 181 142 143 144 154 157 170 186 158 230 248 191 161 172 166 131 173 178 171 187 133 160 192 195 213 140 156 150 151 147 148 145 146 247 179 255 159 185 164 139 155 188 189 135 183 130 132 137 194 202 193 203 200 205 206 207 204 211 212 190 210 218 219 217 208 136 152 175 215 221 222 184 240 253 254 ) at: self asciiValue - 127. ^ Character value: asciiValue. ! ! !Character methodsFor: 'converting' stamp: 'ar 4/9/2005 22:16'! squeakToIso ^self "no longer needed"! ! !Character methodsFor: 'converting' stamp: 'ar 4/10/2005 16:05'! squeakToMac "Convert the receiver from Squeak to MacRoman encoding." value < 128 ifTrue: [^ self]. value > 255 ifTrue: [^ self]. ^ Character value: (#( 173 176 226 196 227 201 160 224 246 228 178 220 206 179 182 183 "80-8F" 184 212 213 210 211 165 208 209 247 170 185 221 207 186 189 217 "90-9F" 202 193 162 163 219 180 195 164 172 169 187 199 194 197 168 248 "A0-AF" 161 177 198 215 171 181 166 225 252 218 188 200 222 223 240 192 "B0-BF" 203 231 229 204 128 129 174 130 233 131 230 232 237 234 235 236 "C0-CF" 245 132 241 238 239 205 133 249 175 244 242 243 134 250 251 167 "D0-DF" 136 135 137 139 138 140 190 141 143 142 144 145 147 146 148 149 "E0-EF" 253 150 152 151 153 155 154 214 191 157 156 158 159 254 255 216 "F0-FF" ) 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: '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: '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 methodsFor: 'object fileIn' stamp: 'tk 2/16/2001 14:52'! objectForDataStream: refStrm "I am being collected for inclusion in a segment. Do not include Characters!! Let them be in outPointers." refStrm insideASegment ifFalse: ["Normal use" ^ self] ifTrue: ["recording objects to go into an ImageSegment" "remove it from references. Do not trace." refStrm references removeKey: self ifAbsent: []. ^ nil] ! ! !Character methodsFor: 'printing' stamp: 'ar 4/9/2005 21:53'! hex ^value hex! ! !Character methodsFor: 'printing'! isLiteral ^true! ! !Character methodsFor: 'printing' stamp: 'lr 11/21/2005 17:40'! printOn: aStream | name | value > 32 ifTrue: [ aStream nextPut: $$; nextPut: self ] ifFalse: [ name := self class constantNameFor: self. name notNil ifTrue: [ aStream nextPutAll: self class name; space; nextPutAll: name ] ifFalse: [ aStream nextPutAll: self class name; nextPutAll: ' value: '; print: value ] ].! ! !Character methodsFor: 'printing' stamp: 'MPW 1/1/1901 22:04'! printOnStream: aStream aStream print:'$', (String with:self).! ! !Character methodsFor: 'printing' stamp: 'ar 4/9/2005 22:30'! storeBinaryOn: aStream "Store the receiver on a binary (file) stream" value < 256 ifTrue:[aStream basicNextPut: self] ifFalse:[Stream nextInt32Put: value].! ! !Character methodsFor: 'printing' stamp: 'lr 11/21/2005 17:40'! storeOn: aStream "Common character literals are preceded by '$', however special need to be encoded differently: for some this might be done by using one of the shortcut constructor methods for the rest we have to create them by ascii-value." | name | value > 32 ifTrue: [ aStream nextPut: $$; nextPut: self ] ifFalse: [ name := self class constantNameFor: self. name notNil ifTrue: [ aStream nextPutAll: self class name; space; nextPutAll: name ] ifFalse: [ aStream nextPut: $(; nextPutAll: self class name; nextPutAll: ' value: '; print: value; nextPut: $) ] ].! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:57'! canBeGlobalVarInitial ^ (EncodedCharSet charsetAt: self leadingChar) canBeGlobalVarInitial: self. ! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:58'! canBeNonGlobalVarInitial ^ (EncodedCharSet charsetAt: self leadingChar) canBeNonGlobalVarInitial: self. ! ! !Character methodsFor: 'testing'! isAlphaNumeric "Answer whether the receiver is a letter or a digit." ^self isLetter or: [self isDigit]! ! !Character methodsFor: 'testing' stamp: 'yo 8/28/2002 13:42'! isCharacter ^ true. ! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:43'! isDigit ^ (EncodedCharSet charsetAt: self leadingChar) isDigit: self. ! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:43'! isLetter ^ (EncodedCharSet charsetAt: self leadingChar) isLetter: self. ! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:43'! isLowercase ^ (EncodedCharSet charsetAt: self leadingChar) isLowercase: self. ! ! !Character methodsFor: 'testing' stamp: 'yo 8/27/2002 15:18'! isOctetCharacter ^ value < 256. ! ! !Character methodsFor: 'testing' stamp: 'yo 7/29/2005 15:21'! isSafeForHTTP "whether a character is 'safe', or needs to be escaped when used, eg, in a URL" "[GG] See http://www.faqs.org/rfcs/rfc1738.html. ~ is unsafe and has been removed" ^ self charCode < 128 and: [self isAlphaNumeric or: ['.-_' includes: (Character value: self charCode)]]! ! !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' stamp: 'ar 4/12/2005 14:09'! isTraditionalDomestic "Yoshiki's note about #isUnicode says: [This method] is for the backward compatibility when we had domestic traditional encodings for CJK languages. To support loading the projects in traditional domestic encodings (From Nihongo4), and load some changesets. Once we decided to get rid of classes like JISX0208 from the EncodedCharSet table, the need for isUnicode will not be necessary. I (Andreas) decided to change the name from isUnicode to #isTraditionalDomestic since I found isUnicode to be horribly confusing (how could the character *not* be Unicode after all?). But still, we should remove this method in due time." ^ ((EncodedCharSet charsetAt: self leadingChar) isKindOf: LanguageEnvironment class) not! ! !Character methodsFor: 'testing' stamp: 'yo 8/5/2003 16:43'! isUppercase ^ (EncodedCharSet charsetAt: self leadingChar) isUppercase: self. ! ! !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: '*packageinfo-base' stamp: 'ab 5/31/2003 17:15'! escapeEntities #($< '<' $> '>' $& '&') pairsDo: [:k :v | self = k ifTrue: [^ v]]. ^ String with: self! ! !Character methodsFor: 'private' stamp: 'ar 4/9/2005 22:18'! setValue: newValue value ifNotNil:[^self error:'Characters are immutable']. value _ newValue.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Character class instanceVariableNames: ''! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowDown ^ self value: 31! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowLeft ^ self value: 28! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowRight ^ self value: 29! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowUp ^ self value: 30! ! !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' stamp: 'NS 7/11/2000 09:19'! delete ^ self value: 127! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:21'! end ^ self value: 4! ! !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: 'NS 7/11/2000 09:21'! home ^ self value: 1! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:19'! insert ^ self value: 5! ! !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' stamp: 'NS 7/11/2000 09:20'! pageDown ^ self value: 12! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:21'! pageUp ^ self value: 11! ! !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: 'class initialization' stamp: 'yo 10/4/2003 16:03'! initialize "Create the table of unique Characters." " self initializeClassificationTable"! ! !Character class methodsFor: 'class initialization' stamp: 'dgd 8/24/2003 15:10'! initializeClassificationTable " Initialize the classification table. The classification table is a compact encoding of upper and lower cases of characters with - bits 0-7: The lower case value of this character. - bits 8-15: The upper case value of this character. - bit 16: lowercase bit (e.g., isLowercase == true) - bit 17: uppercase bit (e.g., isUppercase == true) " | ch1 ch2 | LowercaseBit := 1 bitShift: 16. UppercaseBit := 1 bitShift: 17. "Initialize the letter bits (e.g., isLetter == true)" LetterBits := LowercaseBit bitOr: UppercaseBit. ClassificationTable := Array new: 256. "Initialize the defaults (neither lower nor upper case)" 0 to: 255 do:[:i| ClassificationTable at: i+1 put: (i bitShift: 8) + i. ]. "Initialize character pairs (upper-lower case)" #( "Basic roman" ($A $a) ($B $b) ($C $c) ($D $d) ($E $e) ($F $f) ($G $g) ($H $h) ($I $i) ($J $j) ($K $k) ($L $l) ($M $m) ($N $n) ($O $o) ($P $p) ($Q $q) ($R $r) ($S $s) ($T $t) ($U $u) ($V $v) ($W $w) ($X $x) ($Y $y) ($Z $z) "International" ($Ä $ä) ($Å $å) ($Ç $ç) ($É $é) ($Ñ $ñ) ($Ö $ö) ($Ü $ü) ($À $à) ($à $ã) ($Õ $õ) ($Œ $œ) ($Æ $æ) "International - Spanish" ($Á $á) ($Í $í) ($Ó $ó) ($Ú $ú) "International - PLEASE CHECK" ($È $è) ($Ì $ì) ($Ò $ò) ($Ù $ù) ($Ë $ë) ($Ï $ï) ($ $â) ($Ê $ê) ($Î $î) ($Ô $ô) ($Û $û) ) do:[:pair| ch1 := pair first asciiValue. ch2 := pair last asciiValue. ClassificationTable at: ch1+1 put: (ch1 bitShift: 8) + ch2 + UppercaseBit. ClassificationTable at: ch2+1 put: (ch1 bitShift: 8) + ch2 + LowercaseBit. ]. "Initialize a few others for which we only have lower case versions." #($ß $Ø $ø $ÿ) do:[:char| ch1 := char asciiValue. ClassificationTable at: ch1+1 put: (ch1 bitShift: 8) + ch1 + LowercaseBit. ]. ! ! !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! ! !Character class methodsFor: 'instance creation' stamp: 'ar 4/9/2005 22:36'! allByteCharacters "Answer all the characters that can be encoded in a byte" ^ (0 to: 255) collect: [:v | Character value: v] ! ! !Character class methodsFor: 'instance creation' stamp: 'ar 4/9/2005 22:37'! allCharacters "This name is obsolete since only the characters that will fit in a byte can be queried" ^self allByteCharacters ! ! !Character class methodsFor: 'instance creation' stamp: 'RAH 4/25/2000 19:49'! codePoint: integer "Return a character whose encoding value is integer." #Fundmntl. (0 > integer or: [255 < integer]) ifTrue: [self error: 'parameter out of range 0..255']. ^ CharacterTable at: integer + 1! ! !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' stamp: 'ar 4/9/2005 22:24'! leadingChar: leadChar code: code code >= 16r400000 ifTrue: [ self error: 'code is out of range'. ]. leadChar >= 256 ifTrue: [ self error: 'lead is out of range'. ]. ^self value: (leadChar bitShift: 22) + code.! ! !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' stamp: 'ar 4/9/2005 22:19'! value: anInteger "Answer the Character whose value is anInteger." anInteger > 255 ifTrue: [^self basicNew setValue: anInteger]. ^ CharacterTable at: anInteger + 1. ! ! !Character class methodsFor: 'private' stamp: 'lr 11/21/2005 17:24'! constantNameFor: aCharacter ^ self constantNames detect: [ :each | (self perform: each) = aCharacter ] ifNone: [ nil ].! ! !Character class methodsFor: 'private' stamp: 'lr 11/21/2005 17:33'! constantNames ^ #( backspace cr delete escape lf newPage space tab ).! ! 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: 'comparing' stamp: 'th 9/17/2002 11:54'! max: aCharacterBlock aCharacterBlock ifNil:[^self]. ^aCharacterBlock > self ifTrue:[ aCharacterBlock] ifFalse:[self].! ! !CharacterBlock methodsFor: 'comparing' stamp: 'th 9/17/2002 11:54'! min: aCharacterBlock aCharacterBlock ifNil:[^self]. ^aCharacterBlock < self ifTrue:[ aCharacterBlock] ifFalse:[self].! ! !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: '' 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: '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: 'nk 11/22/2004 14:32'! characterBlockAtPoint: aPoint index: index in: textLine "This method is the Morphic characterBlock finder. It combines MVC's characterBlockAtPoint:, -ForIndex:, and buildCharcterBlock:in:" | runLength lineStop done stopCondition | line := textLine. rightMargin := line rightMargin. lastIndex := line first. self setStopConditions. "also sets font" characterIndex := index. " == nil means scanning for point" characterPoint := aPoint. (characterPoint isNil or: [characterPoint y > line bottom]) ifTrue: [characterPoint := line bottomRight]. (text isEmpty or: [(characterPoint y < line top or: [characterPoint x < line left]) or: [characterIndex notNil and: [characterIndex < line first]]]) ifTrue: [^ (CharacterBlock new stringIndex: line first text: text topLeft: line leftMargin@line top extent: 0 @ textStyle lineGrid) textLine: line]. destX := leftMargin := line leftMarginForAlignment: alignment. destY := line top. runLength := text runLengthFor: line first. characterIndex ifNotNil: [lineStop := characterIndex "scanning for index"] ifNil: [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 ifNil: [font widthOf: (text at: lastIndex)] ifNotNil: [specialWidth]). (self perform: stopCondition) ifTrue: [characterIndex ifNil: [ "Result for characterBlockAtPoint: " (stopCondition ~~ #cr and: [ lastIndex == line last and: [ aPoint x > ((characterPoint x) + (lastCharacterExtent x / 2)) ]]) ifTrue: [ "Correct for right half of last character in line" ^ (CharacterBlock new stringIndex: lastIndex + 1 text: text topLeft: characterPoint + (lastCharacterExtent x @ 0) + (font descentKern @ 0) extent: 0 @ lastCharacterExtent y) textLine: line ]. ^ (CharacterBlock new stringIndex: lastIndex text: text topLeft: characterPoint + (font descentKern @ 0) extent: lastCharacterExtent - (font baseKern @ 0)) textLine: line] ifNotNil: ["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: 'hmm 2/2/2001 15:07'! indentationLevel: anInteger super indentationLevel: anInteger. nextLeftMargin _ leftMargin. indentationLevel timesRepeat: [ nextLeftMargin _ textStyle nextTabXFrom: nextLeftMargin leftMargin: leftMargin rightMargin: rightMargin]! ! !CharacterBlockScanner methodsFor: 'scanning' stamp: 'ar 12/16/2001 19:27'! placeEmbeddedObject: anchoredMorph "Workaround: The following should really use #textAnchorType" anchoredMorph relativeTextAnchorPosition ifNotNil:[^true]. (super placeEmbeddedObject: anchoredMorph) ifFalse: [^ false]. specialWidth _ anchoredMorph width. ^ true! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'RAA 2/25/2001 14:55'! 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 _ (nextLeftMargin ifNil: [leftMargin]) @ destY. lastIndex _ lastIndex + 1. self lastCharacterExtentSetX: 0. ^ true]. lastCharacter _ CR. characterPoint _ destX @ destY. self lastCharacterExtentSetX: rightMargin - destX. ^true! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 12/15/2001 23:28'! 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 | characterIndex == nil ifFalse: [ "If the last character of the last line is a space, and it crosses the right margin, then locating the character block after it is impossible without this hack." characterIndex > text size ifTrue: [ lastIndex _ characterIndex. characterPoint _ (nextLeftMargin ifNil: [leftMargin]) @ (destY + line lineHeight). ^true]]. 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." 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]]. (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: 'yo 11/18/2002 13:16'! 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]]) or: [ ((lastIndex < line last) and: [((text at: lastIndex) leadingChar ~= (text at: lastIndex+1) leadingChar) and: [lastIndex ~= characterIndex]])]) 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: [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 10/18/2004 14:30'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. self setConditionArray: (alignment = Justified ifTrue: [#paddedSpace]). ! ! !CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'ar 12/15/2001 23:28'! tab | currentX | currentX _ (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: 'tlk 5/7/2006 16:42'! 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 alignment: (alignment ifNil:[textStyle alignment])) @ para compositionRectangle top extent: 0 @ textStyle lineGrid]. "find the line" lineIndex _ para lineIndexOfTop: characterPoint y. destY _ para topAtLineIndex: lineIndex. line _ para lines at: lineIndex. lastIndex _ line first. rightMargin _ para rightMarginForDisplay. self setStopConditions. " also loads the font and loads all emphasis attributes " (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 alignment: (alignment ifNil:[textStyle alignment])). nextLeftMargin_ para leftMarginForDisplayForLine: lineIndex+1 alignment: (alignment ifNil:[textStyle alignment]). 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. self handleIndentation. [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! ! Object subclass: #CharacterScanner instanceVariableNames: 'destX lastIndex xTable destY stopConditions text textStyle alignment leftMargin rightMargin font line runStopIndex spaceCount spaceWidth emphasisCode kern indentationLevel wantsColumnBreaks' classVariableNames: 'DefaultStopConditions NilCondition PaddedSpaceCondition SpaceCondition' 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: 'initialize' stamp: 'ls 1/14/2002 21:26'! initialize destX _ destY _ leftMargin _ 0.! ! !CharacterScanner methodsFor: 'initialize' stamp: 'ar 12/31/2001 00:52'! initializeStringMeasurer stopConditions _ Array new: 258. stopConditions at: CrossedX put: #crossedX. stopConditions at: EndOfRun put: #endOfRun. ! ! !CharacterScanner methodsFor: 'initialize' stamp: 'RAA 5/7/2001 10:11'! wantsColumnBreaks: aBoolean wantsColumnBreaks _ aBoolean! ! !CharacterScanner methodsFor: 'scanner methods' stamp: 'yo 12/18/2002 12:32'! isBreakableAtIndex: index ^ (EncodedCharSet at: ((text at: index) leadingChar + 1)) isBreakableAt: index in: text. ! ! !CharacterScanner methodsFor: 'scanner methods' stamp: 'yo 3/13/2003 11:57'! scanJapaneseCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta | ascii encoding f nextDestX maxAscii startEncoding | lastIndex _ startIndex. lastIndex > stopIndex ifTrue: [lastIndex _ stopIndex. ^ stops at: EndOfRun]. startEncoding _ (sourceString at: startIndex) leadingChar. font ifNil: [font _ (TextConstants at: #DefaultMultiStyle) fontArray at: 1]. ((font isMemberOf: StrikeFontSet) or: [font isKindOf: TTCFontSet]) ifTrue: [ maxAscii _ font maxAsciiFor: startEncoding. f _ font fontArray at: startEncoding + 1. "xTable _ f xTable. maxAscii _ xTable size - 2." spaceWidth _ f widthOf: Space. ] ifFalse: [ maxAscii _ font maxAscii. ]. [lastIndex <= stopIndex] whileTrue: [ encoding _ (sourceString at: lastIndex) leadingChar. encoding ~= startEncoding ifTrue: [lastIndex _ lastIndex - 1. ^ stops at: EndOfRun]. ascii _ (sourceString at: lastIndex) charCode. ascii > maxAscii ifTrue: [ascii _ maxAscii]. (encoding = 0 and: [(stopConditions at: ascii + 1) ~~ nil]) ifTrue: [^ stops at: ascii + 1]. nextDestX _ destX + (font widthOf: (sourceString at: lastIndex)). nextDestX > rightX ifTrue: [^ stops at: CrossedX]. destX _ nextDestX + kernDelta. "destX printString displayAt: 0@(lastIndex*20)." lastIndex _ lastIndex + 1. ]. lastIndex _ stopIndex. ^ stops at: EndOfRun! ! !CharacterScanner methodsFor: 'scanner methods' stamp: 'yo 12/27/2002 04:33'! scanMultiCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta | ascii encoding f nextDestX maxAscii startEncoding | lastIndex _ startIndex. lastIndex > stopIndex ifTrue: [lastIndex _ stopIndex. ^ stops at: EndOfRun]. startEncoding _ (sourceString at: startIndex) leadingChar. font ifNil: [font _ (TextConstants at: #DefaultMultiStyle) fontArray at: 1]. ((font isMemberOf: StrikeFontSet) or: [font isKindOf: TTCFontSet]) ifTrue: [ maxAscii _ font maxAsciiFor: startEncoding. f _ font fontArray at: startEncoding + 1. spaceWidth _ f widthOf: Space. ] ifFalse: [ maxAscii _ font maxAscii. ]. [lastIndex <= stopIndex] whileTrue: [ encoding _ (sourceString at: lastIndex) leadingChar. encoding ~= startEncoding ifTrue: [lastIndex _ lastIndex - 1. ^ stops at: EndOfRun]. ascii _ (sourceString at: lastIndex) charCode. ascii > maxAscii ifTrue: [ascii _ maxAscii]. (encoding = 0 and: [(stopConditions at: ascii + 1) ~~ nil]) ifTrue: [^ stops at: ascii + 1]. nextDestX _ destX + (font widthOf: (sourceString at: lastIndex)). nextDestX > rightX ifTrue: [^ stops at: CrossedX]. destX _ nextDestX + kernDelta. "destX printString displayAt: 0@(lastIndex*20)." lastIndex _ lastIndex + 1. ]. lastIndex _ stopIndex. ^ stops at: EndOfRun! ! !CharacterScanner methodsFor: 'scanning' stamp: 'yo 9/23/2002 16:13'! basicScanCharactersFrom: 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: 'RAA 5/4/2001 13:53'! columnBreak ^true! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 12/17/2001 01:50'! embeddedObject | savedIndex | savedIndex _ lastIndex. text attributesAt: lastIndex do:[:attr| attr anchoredMorph ifNotNil:[ "Following may look strange but logic gets reversed. If the morph fits on this line we're not done (return false for true) and if the morph won't fit we're done (return true for false)" (self placeEmbeddedObject: attr anchoredMorph) ifFalse:[^true]]]. lastIndex _ savedIndex + 1. "for multiple(!!) embedded morphs" ^false! ! !CharacterScanner methodsFor: 'scanning' stamp: 'hmm 7/15/2000 22:40'! handleIndentation self indentationLevel timesRepeat: [ self plainTab]! ! !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: 'tak 3/12/2005 00:43'! measureString: aString inFont: aFont from: startIndex to: stopIndex "WARNING: In order to use this method the receiver has to be set up using #initializeStringMeasurer" destX _ destY _ lastIndex _ 0. xTable _ aFont xTable. self scanCharactersFrom: startIndex to: stopIndex in: aString rightX: 999999 stopConditions: stopConditions kern: 0. ^destX! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 12/16/2001 19:27'! placeEmbeddedObject: anchoredMorph "Place the anchoredMorph or return false if it cannot be placed. In any event, advance destX by its width." | w | "Workaround: The following should really use #textAnchorType" anchoredMorph relativeTextAnchorPosition ifNotNil:[^true]. 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 12/15/2001 23:28'! plainTab "This is the basic method of adjusting destX for a tab." destX _ (alignment == Justified and: [self leadingTab not]) ifTrue: "embedded tabs in justified text are weird" [destX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount)) max: destX] ifFalse: [textStyle nextTabXFrom: destX leftMargin: leftMargin rightMargin: rightMargin]! ! !CharacterScanner methodsFor: 'scanning' stamp: 'ar 4/12/2005 19:53'! scanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta | startEncoding selector | (sourceString isByteString) ifTrue: [^ self basicScanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta.]. (sourceString isWideString) ifTrue: [ startIndex > stopIndex ifTrue: [lastIndex _ stopIndex. ^ stops at: EndOfRun]. startEncoding _ (sourceString at: startIndex) leadingChar. selector _ (EncodedCharSet charsetAt: startEncoding) scanSelector. ^ self perform: selector withArguments: (Array with: startIndex with: stopIndex with: sourceString with: rightX with: stopConditions with: kernDelta). ]. ^ stops at: EndOfRun ! ! !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: 'ar 12/15/2001 23:31'! setAlignment: style alignment _ style. ! ! !CharacterScanner methodsFor: 'private' stamp: 'yo 10/7/2002 14:33'! setConditionArray: aSymbol aSymbol == #paddedSpace ifTrue: [^stopConditions _ PaddedSpaceCondition copy]. aSymbol == #space ifTrue: [^stopConditions _ SpaceCondition copy]. aSymbol == nil ifTrue: [^stopConditions _ NilCondition copy]. self error: 'undefined stopcondition for space character'. ! ! !CharacterScanner methodsFor: 'private' stamp: 'tak 3/12/2005 00:43'! 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. 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 class instanceVariableNames: ''! !CharacterScanner class methodsFor: 'class initialization' stamp: 'yo 12/18/2002 14:09'! initialize " CharacterScanner initialize " | a | a _ Array new: 258. a at: 1 + 1 put: #embeddedObject. a at: Tab asciiValue + 1 put: #tab. a at: CR asciiValue + 1 put: #cr. a at: EndOfRun put: #endOfRun. a at: CrossedX put: #crossedX. NilCondition _ a copy. DefaultStopConditions _ a copy. PaddedSpaceCondition _ a copy. PaddedSpaceCondition at: Space asciiValue + 1 put: #paddedSpace. SpaceCondition _ a copy. SpaceCondition at: Space asciiValue + 1 put: #space. ! ! Collection subclass: #CharacterSet instanceVariableNames: 'map' classVariableNames: '' poolDictionaries: '' category: 'Collections-Support'! !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: 'ar 4/9/2005 22:37'! do: aBlock "evaluate aBlock with each character in the set" Character allByteCharacters 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: 'comparison' stamp: 'tk 7/5/2001 21:58'! = anObject ^self species == anObject species and: [ self byteArrayMap = anObject byteArrayMap ]! ! !CharacterSet methodsFor: 'comparison' stamp: 'ls 8/17/1998 20:46'! hash ^self byteArrayMap hash! ! !CharacterSet methodsFor: 'comparison' stamp: 'tk 7/5/2001 21:57'! species ^CharacterSet! ! !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: '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: 'nk 8/3/2004 06:54'! empty "return an empty set of characters" ^self new! ! !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! ! ClassTestCase subclass: #CharacterTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Text'! !CharacterTest commentStamp: '' prior: 0! This is the unit test for the class Character. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category! !CharacterTest methodsFor: 'testing - Class Methods' stamp: 'sd 6/5/2005 09:25'! testNew self should: [Character new] raise: Error.! ! !CharacterTest methodsFor: 'testing-printing' stamp: 'lr 11/21/2005 17:41'! testPrintString self assert: $a printString = '$a'. self assert: $5 printString = '$5'. self assert: $@ printString = '$@'. self assert: Character cr printString = 'Character cr'. self assert: Character lf printString = 'Character lf'. self assert: Character space printString = 'Character space'. self assert: (Character value: 0) printString = 'Character value: 0'. self assert: (Character value: 17) printString = 'Character value: 17'.! ! !CharacterTest methodsFor: 'testing-printing' stamp: 'lr 11/21/2005 17:41'! testPrintStringAll Character allCharacters do: [ :each | self assert: (self class compilerClass evaluate: each printString) = each ].! ! !CharacterTest methodsFor: 'testing-printing' stamp: 'lr 11/21/2005 17:22'! testStoreString self assert: $a storeString = '$a'. self assert: $5 storeString = '$5'. self assert: $@ storeString = '$@'. self assert: Character cr storeString = 'Character cr'. self assert: Character lf storeString = 'Character lf'. self assert: Character space storeString = 'Character space'. self assert: (Character value: 0) storeString = '(Character value: 0)'. self assert: (Character value: 17) storeString = '(Character value: 17)'.! ! !CharacterTest methodsFor: 'testing-printing' stamp: 'lr 11/21/2005 17:24'! testStoreStringAll Character allCharacters do: [ :each | self assert: (self class compilerClass evaluate: each storeString) = each ].! ! SimpleButtonMorph subclass: #ChatButtonMorph instanceVariableNames: 'actionDownSelector actionUpSelector labelDown labelUp' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Audio Chat'! !ChatButtonMorph methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! actionDownSelector: aSymbolOrString (nil = aSymbolOrString or: ['nil' = aSymbolOrString or: [aSymbolOrString isEmpty]]) ifTrue: [^actionDownSelector := nil]. actionDownSelector := aSymbolOrString asSymbol.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! actionUpSelector: aSymbolOrString (nil = aSymbolOrString or: ['nil' = aSymbolOrString or: [aSymbolOrString isEmpty]]) ifTrue: [^ actionUpSelector := nil]. actionUpSelector := aSymbolOrString asSymbol.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! labelDown: aString labelDown := aString.! ! !ChatButtonMorph methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! labelUp: aString labelUp := aString! ! !ChatButtonMorph methodsFor: 'event handling' stamp: 'sd 11/20/2005 21:25'! mouseDown: evt oldColor := self fillStyle. self label: labelDown. self doButtonDownAction. ! ! !ChatButtonMorph methodsFor: 'event handling' 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. ]. ! ! !ChatButtonMorph methodsFor: 'events' stamp: 'dgd 2/22/2003 18:40'! doButtonDownAction (target notNil and: [actionDownSelector notNil]) ifTrue: [Cursor normal showWhile: [target perform: actionDownSelector]]! ! !ChatButtonMorph methodsFor: 'events' stamp: 'dgd 2/22/2003 18:40'! doButtonUpAction (target notNil and: [actionUpSelector notNil]) ifTrue: [Cursor normal showWhile: [target perform: actionUpSelector]]! ! StringHolder subclass: #ChatNotes instanceVariableNames: 'name notesIndex names notes recorder player sound isPlaying isRecording isSaving nameTextMorph' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Audio Chat'! !ChatNotes methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! name ^name ifNil: [name := '']! ! !ChatNotes methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! notesListIndex ^notesIndex ifNil: [notesIndex := 0]! ! !ChatNotes methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'file i/o' stamp: 'mir 11/27/2001 12:04'! audioDirectory (FileDirectory default directoryExists: 'audio') ifFalse: [FileDirectory default createDirectory: 'audio']. ^FileDirectory default directoryNamed: 'audio'! ! !ChatNotes methodsFor: 'file i/o' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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 methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize self loadNotes. notesIndex := 0. recorder := ChatRecorder new. recorder initialize.! ! !ChatNotes methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! loadNotes "Load notes from the files" | dir | names := OrderedCollection new. notes := OrderedCollection new. (FileDirectory default directoryExists: 'audio') ifFalse: [^self]. 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: 'sd 11/20/2005 21:25'! 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: 'testing' stamp: 'sd 11/20/2005 21:25'! isPlaying ^isPlaying ifNil: [isPlaying := false]! ! !ChatNotes methodsFor: 'testing' stamp: 'sd 11/20/2005 21:25'! isPlaying: aBoolean isPlaying = aBoolean ifTrue: [^self]. isPlaying := aBoolean. self changed: #isPlaying ! ! !ChatNotes methodsFor: 'testing' stamp: 'sd 11/20/2005 21:25'! isRecording ^isRecording ifNil: [isRecording := false]! ! !ChatNotes methodsFor: 'testing' stamp: 'sd 11/20/2005 21:25'! isRecording: aBoolean isRecording = aBoolean ifTrue: [^self]. isRecording := aBoolean. self changed: #isRecording ! ! !ChatNotes methodsFor: 'testing' stamp: 'sd 11/20/2005 21:25'! isSaving ^isSaving ifNil: [isSaving := false]! ! !ChatNotes methodsFor: 'testing' stamp: 'sd 11/20/2005 21:25'! isSaving: aBoolean isSaving = aBoolean ifTrue: [^self]. isSaving := aBoolean. self changed: #isSaving! ! !ChatNotes methodsFor: 'testing' stamp: 'RAA 8/1/2000 18:05'! isStopped ^false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChatNotes class instanceVariableNames: ''! !ChatNotes class methodsFor: 'instance creation' stamp: 'RAA 8/2/2000 01:06'! openAsMorph ^self new openAsMorph! ! SoundRecorder subclass: #ChatRecorder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Audio Chat'! !ChatRecorder methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:25'! recordedSound: aSound self clearRecordedSound. recordedSound := aSound.! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! playback "Playback the sound that has been recorded." self pause. soundPlaying := self recordedSound ifNil: [^self]. soundPlaying play. ! ! !ChatRecorder methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! resumeRecording "Continue recording from the point at which it was last paused." self startRecording. paused := false. ! ! SharedPool subclass: #ChronologyConstants instanceVariableNames: 'seconds offset jdn nanos' classVariableNames: 'DayNames DaysInMonth MonthNames NanosInMillisecond NanosInSecond SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch' poolDictionaries: '' category: 'Kernel-Chronology'! !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0! ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ChronologyConstants class instanceVariableNames: ''! !ChronologyConstants class methodsFor: 'as yet unclassified' stamp: 'brp 9/25/2003 10:49'! initialize "ChronologyConstants initialize" SqueakEpoch _ 2415386. "Julian day number of 1 Jan 1901" SecondsInDay _ 86400. SecondsInHour _ 3600. SecondsInMinute _ 60. NanosInSecond _ 10 raisedTo: 9. NanosInMillisecond _ 10 raisedTo: 6. DayNames _ #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday). MonthNames _ #(January February March April May June July August September October November December). DaysInMonth _ #(31 28 31 30 31 30 31 31 30 31 30 31). ! ! 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: 'display box access'! computeBoundingBox ^center - radius + form offset extent: form extent + (radius * 2) asPoint! ! !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 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"! ! EllipseMorph subclass: #CircleMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !CircleMorph commentStamp: '' prior: 0! I am a specialization of EllipseMorph that knows enough to remain circular. ! !CircleMorph methodsFor: 'rotate scale and flex' stamp: 'nk 7/1/2002 07:04'! addFlexShellIfNecessary "When scaling or rotating from a halo, I can do this without a flex shell" ^ self ! ! !CircleMorph methodsFor: 'rotate scale and flex' stamp: 'nk 7/1/2002 16:29'! privateMoveBy: delta self setProperty: #referencePosition toValue: self referencePosition + delta. self setProperty: #originalCenter toValue: (self valueOfProperty: #originalCenter ifAbsent: [ self center ]) + delta. super privateMoveBy: delta. ! ! !CircleMorph methodsFor: 'rotate scale and flex' stamp: 'nk 7/1/2002 07:28'! rotationDegrees ^ self forwardDirection! ! !CircleMorph methodsFor: 'rotate scale and flex' stamp: 'nk 7/1/2002 15:52'! rotationDegrees: degrees | ref newPos flex origAngle origCenter | ref _ self referencePosition. origAngle _ self valueOfProperty: #originalAngle ifAbsentPut: [ self heading ]. origCenter _ self valueOfProperty: #originalCenter ifAbsentPut: [ self center ]. flex _ (MorphicTransform offset: ref negated) withAngle: (degrees - origAngle) degreesToRadians. newPos _ (flex transform: origCenter) - flex offset. self position: (self position + newPos - self center) asIntegerPoint. self setProperty: #referencePosition toValue: ref. self setProperty: #originalAngle toValue: origAngle. self setProperty: #originalCenter toValue: origCenter. self forwardDirection: degrees. self changed. ! ! !CircleMorph methodsFor: 'geometry' stamp: 'nk 7/1/2002 07:01'! bounds: aRectangle | size | size _ aRectangle width min: aRectangle height. super bounds: (Rectangle origin: aRectangle origin extent: size @ size).! ! !CircleMorph methodsFor: 'geometry' stamp: 'nk 7/1/2002 16:39'! extent: aPoint | size oldRotationCenter | oldRotationCenter _ self rotationCenter. size _ aPoint x min: aPoint y. super extent: size @ size. self rotationCenter: oldRotationCenter.! ! !CircleMorph methodsFor: 'geometry' stamp: 'nk 7/1/2002 08:49'! transformedBy: aTransform aTransform isIdentity ifTrue:[^self]. ^self center: (aTransform localPointToGlobal: self center). ! ! !CircleMorph methodsFor: 'geometry eToy' stamp: 'nk 7/1/2002 07:12'! heading: newHeading "Set the receiver's heading (in eToy terms). Note that circles never use flex shells." self rotationDegrees: newHeading.! ! !CircleMorph methodsFor: 'geometry eToy' stamp: 'nk 7/1/2002 07:31'! referencePosition "Return the current reference position of the receiver" ^ self valueOfProperty: #referencePosition ifAbsent: [ self center ] ! ! !CircleMorph methodsFor: 'geometry eToy' stamp: 'nk 7/1/2002 11:16'! rotationCenter "Return the rotation center of the receiver. The rotation center defines the relative offset inside the receiver's bounds for locating the reference position." | refPos | refPos _ self referencePosition. ^ (refPos - self bounds origin) / self bounds extent asFloatPoint! ! !CircleMorph methodsFor: 'geometry eToy' stamp: 'nk 7/1/2002 13:48'! rotationCenter: aPointOrNil "Set the new rotation center of the receiver. The rotation center defines the relative offset inside the receiver's bounds for locating the reference position." | newRef box | aPointOrNil isNil ifTrue: [self removeProperty: #referencePosition. self removeProperty: #originalCenter. self removeProperty: #originalAngle. ] ifFalse: [ box _ self bounds. newRef _ box origin + (aPointOrNil * box extent). self setRotationCenterFrom: newRef ]. ! ! !CircleMorph methodsFor: 'parts bin' stamp: 'nk 7/1/2002 16:42'! initializeToStandAlone ^super initializeToStandAlone extent: 40@40; color: Color green lighter; yourself! ! !CircleMorph methodsFor: 'menus' stamp: 'nk 7/1/2002 11:30'! setRotationCenterFrom: aPoint "Called by halo rotation code. Circles store their referencePosition." self setProperty: #referencePosition toValue: aPoint. self setProperty: #originalCenter toValue: self center. self setProperty: #originalAngle toValue: self heading.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CircleMorph class instanceVariableNames: ''! !CircleMorph class methodsFor: 'as yet unclassified' stamp: 'nk 8/23/2004 11:45'! newPin "Construct a pin for embedded attachment" "CircleMorph newPin openInHand" ^self new removeAllMorphs; extent: 18@18; hResizing: #rigid; vResizing: #rigid; layoutPolicy: nil; color: Color orange lighter; borderColor: Color orange darker; borderWidth: 2; wantsConnectionWhenEmbedded: true; name: 'Pin'! ! !CircleMorph class methodsFor: 'as yet unclassified' stamp: 'wiz 2/25/2006 16:12'! supplementaryPartsDescriptions "Extra items for parts bins" ^ {DescriptionForPartsBin formalName: 'Circle1' categoryList: #('Graphics') documentation: 'A circular shape' globalReceiverSymbol: #CircleMorph nativitySelector: #newStandAlone. "DescriptionForPartsBin formalName: 'Pin' categoryList: #('Connectors') documentation: 'An attachment point for Connectors that you can embed in another Morph.' globalReceiverSymbol: #NCPinMorph nativitySelector: #newPin." }! ! MorphTest subclass: #CircleMorphTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicTests-Basic'! !CircleMorphTest commentStamp: 'tlk 5/21/2006 14:16' prior: 0! A CircleMorphTest is a subclass of MorphTest. It was first implemented when removing some unused and broken functionality. My fixtures are morph, a CircleMorph and world. ! !CircleMorphTest methodsFor: 'initialize-release' stamp: 'tlk 5/21/2006 14:17'! setUp morph := CircleMorph new! ! ClassDescription subclass: #Class uses: TBehaviorCategorization instanceVariableNames: 'subclasses name classPool sharedPools environment category traitComposition localSelectors' 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: 'accessing' stamp: 'al 3/18/2006 13:23'! basicCategory ^category! ! !Class methodsFor: 'accessing' stamp: 'al 3/18/2006 13:23'! basicCategory: aSymbol category := aSymbol! ! !Class methodsFor: 'accessing' stamp: 'al 3/25/2006 13:16'! basicLocalSelectors "Direct accessor for the instance variable localSelectors. Since localSelectors is lazily initialized, this may return nil, which means that all selectors are local." ^ localSelectors! ! !Class methodsFor: 'accessing' stamp: 'al 3/25/2006 13:16'! basicLocalSelectors: aSetOrNil localSelectors _ aSetOrNil! ! !Class methodsFor: 'accessing'! classPool "Answer the dictionary of class variables." classPool == nil ifTrue: [^Dictionary new] ifFalse: [^classPool]! ! !Class methodsFor: 'accessing' stamp: 'BG 8/11/2002 20:53'! classPoolFrom: aClass "share the classPool with aClass." classPool := aClass classPool! ! !Class methodsFor: 'accessing' stamp: 'al 9/3/2004 13:37'! classPool: aDictionary classPool _ aDictionary! ! !Class methodsFor: 'accessing' stamp: 'al 3/25/2006 12:38'! hasTraitComposition ^traitComposition notNil! ! !Class methodsFor: 'accessing'! name "Answer the name of the receiver." name == nil ifTrue: [^super name] ifFalse: [^name]! ! !Class methodsFor: 'accessing' stamp: 'al 3/25/2006 12:35'! traitComposition traitComposition ifNil: [traitComposition _ TraitComposition new]. ^traitComposition! ! !Class methodsFor: 'accessing' stamp: 'al 3/25/2006 12:37'! traitComposition: aTraitComposition traitComposition := aTraitComposition! ! !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: '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: '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: 'class name' stamp: 'sw 12/1/2000 20:39'! externalName "Answer a name by which the receiver can be known." ^ name! ! !Class methodsFor: 'class name' stamp: 'sw 12/18/2000 15:50'! nameForViewer "Answer the name to be shown in the header of a viewer looking at the receiver" ^ self name ifNil: ['Unnamed class']! ! !Class methodsFor: 'class name' stamp: 'Noury Bouraqadi 3/24/2006 20:10'! rename: aString "The new name of the receiver is the argument, aString." | newName | (newName _ aString asSymbol) ~= self name ifFalse: [^ self]. (self environment includesKey: newName) ifTrue: [^ self error: newName , ' already exists']. (Undeclared includesKey: newName) ifTrue: [self inform: 'There are references to, ' , aString printString , ' from Undeclared. Check them after this change.']. self environment renameClass: self as: newName. name := newName. ! ! !Class methodsFor: 'class variables' stamp: 'dvf 9/27/2005 17:32'! 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 oldState | oldState _ self copy. aString first canBeGlobalVarInitial ifFalse: [^self error: aString, ' class variable name should be capitalized; proceed to include anyway.']. symbol _ aString asSymbol. self withAllSubclasses do: [:subclass | (subclass bindingOf: symbol) ifNotNil:[ ^ 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" classPool declare: symbol from: Undeclared. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: oldState to: self]! ! !Class methodsFor: 'class variables' stamp: 'al 9/3/2004 14:25'! allClassVarNames "Answer a Set of the names of the receiver's class variables, including those defined in the superclasses of the receiver." | aSet | self superclass == nil ifTrue: [^self classVarNames] "This is the keys so it is a new Set." ifFalse: [aSet _ self superclass allClassVarNames. aSet addAll: self classVarNames. ^aSet]! ! !Class methodsFor: 'class variables' stamp: 'al 9/3/2004 14:25'! 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' stamp: 'bf 1/12/2006 10:44'! 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: [ InMidstOfFileinNotification signal ifTrue: [ Transcript cr; show: self name, ' (' , aString , ' is Undeclared) '. ^Undeclared declare: aSymbol from: classPool]. (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: 'compiling' stamp: 'md 3/5/2006 23:47'! binding ^ Smalltalk associationAt: name ifAbsent: [nil -> self] ! ! !Class methodsFor: 'compiling' stamp: 'ar 5/17/2003 14:06'! bindingOf: varName "Answer the binding of some variable resolved in the scope of the receiver" | aSymbol binding | aSymbol := varName asSymbol. "First look in classVar dictionary." binding := self classPool bindingOf: aSymbol. binding ifNotNil:[^binding]. "Next look in shared pools." self sharedPools do:[:pool | binding := pool bindingOf: aSymbol. binding ifNotNil:[^binding]. ]. "Next look in declared environment." binding := self environment bindingOf: aSymbol. binding ifNotNil:[^binding]. "Finally look higher up the superclass chain and fail at the end." superclass == nil ifTrue: [^ nil] ifFalse: [^ superclass bindingOf: aSymbol]. ! ! !Class methodsFor: 'compiling' stamp: 'ar 5/17/2003 14:13'! 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." "First look in classVar dictionary." (self classPool bindingOf: varName) ifNotNil:[^true]. "Next look in shared pools." self sharedPools do:[:pool | (pool bindingOf: varName) ifNotNil:[^true]. ]. "Finally look higher up the superclass chain and fail at the end." superclass == nil ifTrue: [^ false] ifFalse: [^ (superclass bindingOf: varName) notNil]. ! ! !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' stamp: 'sd 3/28/2003 15:24'! 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: self environment continuedFrom: results ] ifFalse: [ ^ superclass possibleVariablesFor: misspelled continuedFrom: results ]! ! !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: 'fileIn/Out' stamp: 'al 9/3/2004 14:04'! 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: 'dvf 9/27/2005 17:35'! fileOutAsHtml: useHtml "File a description of the receiver onto a new file whose base name is the name of the receiver." | internalStream | internalStream _ WriteStream on: (String new: 100). internalStream header; timeStamp. self sharedPools size > 0 ifTrue: [ self shouldFileOutPools ifTrue: [self fileOutSharedPoolsOn: internalStream]]. self fileOutOn: internalStream moveSource: false toFile: 0. internalStream trailer. FileStream writeSourceCodeFrom: internalStream baseName: self name isSt: true useHtml: useHtml. ! ! !Class methodsFor: 'fileIn/Out' stamp: 'al 9/3/2004 14:00'! fileOutInitializerOn: aStream ^self class fileOutInitializerOn: aStream! ! !Class methodsFor: 'fileIn/Out' stamp: 'al 9/3/2004 14:04'! 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: 'al 9/3/2004 14:04'! 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: self 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: 'al 9/3/2004 14:04'! fileOutPool: aPool onFileStream: aFileStream | aPoolName aValue | (aPool isKindOf: SharedPool class) ifTrue:[^self notify: 'we do not fileout SharedPool type shared pools for now']. aPoolName _ self environment 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: 'al 9/3/2004 14:04'! 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: (self environment keyAtIdentityValue: aPool))]. poolsToFileOut do: [:aPool | self fileOutPool: aPool onFileStream: aFileStream]. ! ! !Class methodsFor: 'fileIn/Out' stamp: 'dvf 9/27/2005 17:36'! 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: #withClassVersion: args: {self classVersion}] 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' stamp: 'al 9/3/2004 14:05'! 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' stamp: 'al 9/3/2004 14:05'! 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:" ChangeSet current removeClassAndMetaClassChanges: self! ! !Class methodsFor: 'fileIn/Out' stamp: 'al 9/3/2004 14:05'! shouldFileOutPools "respond with true if the user wants to file out the shared pools" ^self confirm: 'FileOut selected sharedPools?'! ! !Class methodsFor: 'fileIn/Out' stamp: 'al 9/3/2004 14:05'! shouldFileOutPool: aPoolName "respond with true if the user wants to file out aPoolName" ^self confirm: ('FileOut the sharedPool ', aPoolName, '?')! ! !Class methodsFor: 'fileIn/Out' stamp: 'al 9/3/2004 14:05'! 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: 'fileIn/Out' stamp: 'ar 4/10/2005 20:27'! withClassVersion: aVersion aVersion = self classVersion ifTrue:[^self]. ^self error: 'Invalid class version'! ! !Class methodsFor: 'initialize-release' stamp: 'al 9/3/2004 13:43'! deactivate "A remnant from the 3.3a modules work, retained . Does nothing, but may be overridden in Metaclasses."! ! !Class methodsFor: 'initialize-release' stamp: 'dvf 9/27/2005 17:34'! declare: varString "Declare class variables common to all instances. Answer whether recompilation is advisable." | newVars conflicts | newVars _ (Scanner new scanFieldNames: varString) collect: [:x | x asSymbol]. newVars do: [:var | var first canBeGlobalVarInitial ifFalse: [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 bindingOf: var) notNil 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: 'al 9/3/2004 14:07'! obsolete "Change the receiver and all of its subclasses to an obsolete class." self == Object ifTrue: [^self error: 'Object is NOT obsolete']. self setName: 'AnObsolete' , self name. Object class instSize + 1 to: self class instSize do: [:i | self instVarAt: i put: nil]. "Store nil over class instVars." self classPool: nil. self sharedPools: nil. self class obsolete. super obsolete.! ! !Class methodsFor: 'initialize-release' stamp: 'al 9/3/2004 13:35'! removeFromSystem "Forget the receiver from the Smalltalk global dictionary. Any existing instances will refer to an obsolete version of the receiver." self removeFromSystem: true.! ! !Class methodsFor: 'initialize-release' stamp: 'al 9/3/2004 13:36'! 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" ^self removeFromSystem: false! ! !Class methodsFor: 'initialize-release' stamp: 'al 9/3/2004 13:43'! removeFromSystem: logged "Forget the receiver from the Smalltalk global dictionary. Any existing instances will refer to an obsolete version of the receiver." "keep the class name and category for triggering the system change message. If we wait to long, then we get obsolete information which is not what we want." "tell class to deactivate and unload itself-- two separate events in the module system" self deactivate; unload. self superclass ifNotNil: ["If we have no superclass there's nothing to be remembered" self superclass addObsoleteSubclass: self]. self environment forgetClass: self logged: logged. self obsolete.! ! !Class methodsFor: 'initialize-release' stamp: 'sd 3/28/2003 16:09'! 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: (self environment at: poolName asSymbol ifAbsent:[ (self confirm: 'The pool dictionary ', poolName,' does not exist.', '\Do you want it automatically created?' withCRs) ifTrue:[self environment 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/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: 'initialize-release' stamp: 'NS 4/8/2004 10:55'! 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. instanceVariables _ nilOrArray. classPool _ pool. sharedPools _ poolSet. self organization: org.! ! !Class methodsFor: 'initialize-release' stamp: 'al 9/3/2004 13:35'! unload "Sent when a the class is removed. Does nothing, but may be overridden by (class-side) subclasses." ! ! !Class methodsFor: 'instance variables' stamp: 'al 9/3/2004 14:25'! addInstVarName: aString "Add the argument, aString, as one of the receiver's instance variables." ^(ClassBuilder new) name: self name inEnvironment: self environment subclassOf: self superclass type: self typeOfClass instanceVariableNames: self instanceVariablesString, ' ', aString classVariableNames: self classVariablesString poolDictionaries: self sharedPoolsString category: self category ! ! !Class methodsFor: 'instance variables' stamp: 'al 9/3/2004 14:25'! 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: self superclass type: self typeOfClass instanceVariableNames: newInstVarString classVariableNames: self classVariablesString poolDictionaries: self sharedPoolsString category: self category! ! !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 methodsFor: 'pool variables' stamp: 'tpr 5/30/2003 13:04'! addSharedPool: aSharedPool "Add the argument, aSharedPool, as one of the receiver's shared pools. Create an error if the shared pool is already one of the pools. This method will work with shared pools that are plain Dictionaries or thenewer SharedPool subclasses" (self sharedPools includes: aSharedPool) ifTrue: [^self error: 'This is already in my shared pool list']. sharedPools == nil ifTrue: [sharedPools _ OrderedCollection with: aSharedPool] ifFalse: [sharedPools add: aSharedPool]! ! !Class methodsFor: 'pool variables' stamp: 'al 9/3/2004 14:25'! allSharedPools "Answer a Set of the pools the receiver shares, including those defined in the superclasses of the receiver." | aSet | ^self superclass == nil ifTrue: [self sharedPools copy] ifFalse: [aSet _ self 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: 'pool variables' stamp: 'al 9/3/2004 13:41'! sharedPools: aCollection sharedPools _ aCollection! ! !Class methodsFor: 'self evaluating' stamp: 'sd 7/31/2005 21:46'! isSelfEvaluating ^ true! ! !Class methodsFor: 'subclass creation' stamp: 'sd 3/28/2003 15:24'! newSubclass | i className | i _ 1. [className _ (self name , i printString) asSymbol. self environment 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: 'al 7/19/2004 11:18'! subclass: t uses: aTraitCompositionOrArray instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat | newClass copyOfOldClass | copyOfOldClass _ self copy. newClass _ self subclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat. newClass setTraitComposition: aTraitCompositionOrArray asTraitComposition. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^newClass! ! !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: 'al 7/19/2004 11:18'! variableByteSubclass: t uses: aTraitCompositionOrArray 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." | newClass copyOfOldClass | copyOfOldClass _ self copy. newClass _ self variableByteSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat. newClass setTraitComposition: aTraitCompositionOrArray asTraitComposition. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^newClass ! ! !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: 'al 7/19/2004 11:17'! variableSubclass: t uses: aTraitCompositionOrArray 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." | newClass copyOfOldClass | copyOfOldClass _ self copy. newClass _ self variableSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat. newClass setTraitComposition: aTraitCompositionOrArray asTraitComposition. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^newClass ! ! !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: 'al 7/19/2004 11:18'! variableWordSubclass: t uses: aTraitCompositionOrArray 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." | newClass copyOfOldClass | copyOfOldClass _ self copy. newClass _ self variableWordSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat. newClass setTraitComposition: aTraitCompositionOrArray asTraitComposition. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^newClass ! ! !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: 'subclass creation' stamp: 'al 7/19/2004 11:18'! weakSubclass: t uses: aTraitCompositionOrArray 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." | newClass copyOfOldClass | copyOfOldClass _ self copy. newClass _ self weakSubclass: t instanceVariableNames: f classVariableNames: d poolDictionaries: s category: cat. newClass setTraitComposition: aTraitCompositionOrArray asTraitComposition. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^newClass ! ! !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: 'al 6/5/2006 13:13'! 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: 'traits' stamp: 'NS 4/12/2004 16:48'! applyChangesOfNewTraitCompositionReplacing: oldComposition "See Trait>>applyChangesOfNewTraitCompositionReplacing:" | changedSelectors | changedSelectors _ super applyChangesOfNewTraitCompositionReplacing: oldComposition. self classSide noteNewBaseTraitCompositionApplied: self traitComposition. ^ changedSelectors! ! !Class methodsFor: '*eToys-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: '*monticello' stamp: 'al 3/26/2006 21:31'! asClassDefinition ^ MCClassDefinition name: self name superclassName: self superclass name traitComposition: self traitCompositionString classTraitComposition: self class traitCompositionString category: self category instVarNames: self instVarNames classVarNames: self classVarNames poolDictionaryNames: self poolDictionaryNames classInstVarNames: self class instVarNames type: self typeOfClass comment: self organization classComment asString commentStamp: self organization commentStamp ! ! !Class methodsFor: '*monticello' stamp: 'avi 3/10/2004 13:32'! classDefinitions ^ Array with: self asClassDefinition! ! !Class methodsFor: '*monticello' stamp: 'ab 4/14/2003 22:30'! poolDictionaryNames ^ self sharedPools collect: [:ea | self environment keyAtIdentityValue: ea]! ! !Class methodsFor: 'private' stamp: 'ar 7/15/1999 15:37'! setName: aSymbol "Private - set the name of the class" name _ aSymbol.! ! !Class methodsFor: 'private' stamp: 'sd 2/1/2004 15:18'! spaceUsed "Object spaceUsed" ^ super spaceUsed + self class spaceUsed! ! !Class methodsFor: '*ob-standard-converting' stamp: 'dvf 8/15/2005 17:52'! asClassSideNode ^OBMetaclassNode on: self! ! !Class methodsFor: '*ob-standard-converting' stamp: 'dvf 9/1/2005 13:51'! asNode ^OBClassNode on: self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Class class instanceVariableNames: ''! !Class class methodsFor: 'fileIn/Out' stamp: 'dvf 9/27/2005 17:30'! fileOutPool: aString "file out the global pool named aString" | internalStream | internalStream _ WriteStream on: (String new: 1000). self new fileOutPool: (self environment at: aString asSymbol) onFileStream: internalStream. FileStream writeSourceCodeFrom: internalStream baseName: aString isSt: true useHtml: false.! ! !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: 'al 1/9/2006 18:05'! 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" ^ priorClassName asString, ' subclass: #NameOfSubclass instanceVariableNames: '''' classVariableNames: '''' poolDictionaries: '''' category: ''' , systemCategoryName asString , ''''! ! !Class class methodsFor: 'inquiries' stamp: 'md 2/20/2006 22:59'! rootsOfTheWorld "return a collection of classes which have a nil superclass" ^(Smalltalk select: [:each | each isBehavior and: [each superclass isNil]]) asOrderedCollection! ! Object subclass: #ClassBuilder instanceVariableNames: 'environ classMap instVarMap progress maxClassIndex currentClassIndex' classVariableNames: 'QuietMode' poolDictionaries: '' category: 'Kernel-Classes'! !ClassBuilder commentStamp: 'ar 2/27/2003 22:55' 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. Implementation notes: ClassBuilder relies on the assumption that it can see ALL subclasses of some class. If there are any existing subclasses of some class, regardless of whether they have instances or not, regardless of whether they are considered obsolete or not, ClassBuilder MUST SEE THEM. ! !ClassBuilder methodsFor: 'class definition' stamp: 'NS 1/21/2004 09:20'! class: oldClass instanceVariableNames: instVarString unsafe: unsafe "This is the basic initialization message to change the definition of an existing Metaclass" | instVars newClass needNew copyOfOldClass | 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]]. "See if we need a new subclass or not" needNew _ self needsSubclassOf: oldClass superclass type: oldClass typeOfClass instanceVariables: instVars from: oldClass. needNew ifNil:[^nil]. "some error" needNew ifFalse:[^oldClass]. "no new class needed" "Create the new class" copyOfOldClass _ oldClass copy. newClass _ self newSubclassOf: oldClass superclass type: oldClass typeOfClass instanceVariables: instVars from: oldClass. newClass _ self recompile: false from: oldClass to: newClass mutate: false. self doneCompiling: newClass. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass. ^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: 'rw 4/3/2006 22:14'! 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 needNew oldCategory copyOfOldClass newCategory | 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:" ifTrue: [ copyOfOldClass := oldClass copy. copyOfOldClass superclass addSubclass: copyOfOldClass]. [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]]. "See if we need a new subclass" needNew := self needsSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass. needNew == nil ifTrue:[^nil]. "some error" (needNew and:[unsafe not]) ifTrue:[ "Make sure we don't redefine any dangerous classes" (self tooDangerousClasses includes: oldClass name) ifTrue:[ self error: oldClass name, ' cannot be changed'. ]. "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]]. needNew ifTrue:[ "Create the new class" newClass := self newSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass. newClass == nil ifTrue:[^nil]. "Some error" newClass setName: className. ] ifFalse:[ "Reuse the old class" newClass := oldClass. ]. "Install the class variables and pool dictionaries... " force := (newClass declare: classVarString) | (newClass sharing: poolString). "... classify ..." newCategory := category asSymbol. organization := environ ifNotNil:[environ organization]. oldClass isNil ifFalse: [oldCategory := (organization categoryOfElement: oldClass name) asSymbol]. organization classify: newClass name under: newCategory. 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] on: AttemptToWriteReadOnlyGlobal do:[:ex| ex resume: true]. Smalltalk flushClassNameCache. ]. newClass doneCompiling. "... notify interested clients ..." oldClass isNil ifTrue: [ SystemChangeNotifier uniqueInstance classAdded: newClass inCategory: newCategory. ^ newClass]. newCategory ~= oldCategory ifTrue: [SystemChangeNotifier uniqueInstance class: newClass recategorizedFrom: oldCategory to: category] ifFalse: [SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfOldClass to: newClass.]. ] ensure: [copyOfOldClass ifNotNil: [copyOfOldClass superclass removeSubclass: copyOfOldClass]. Behavior flushObsoleteSubclasses. ]. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'ar 9/22/2002 02:57'! needsSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass "Answer whether we need a new subclass to conform to the requested changes" | newFormat | "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 ifNil:[^true]. "yes, it's a new class" newSuper == oldClass superclass ifFalse:[^true]. "yes, it's a superclass change" newFormat = oldClass format ifFalse:[^true]. "yes, it's a format change" instVars = oldClass instVarNames ifFalse:[^true]. "yes, it's an iVar change" ^false ! ! !ClassBuilder methodsFor: 'class definition' stamp: 'al 7/3/2006 15:53'! newSubclassOf: newSuper type: type instanceVariables: instVars from: oldClass "Create a new subclass of the given superclass with the given specification." | newFormat newClass | "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]. (oldClass == nil or:[oldClass isMeta not]) ifTrue:[newClass := self privateNewSubclassOf: newSuper from: oldClass] ifFalse:[newClass := oldClass clone]. newClass superclass: newSuper methodDictionary: MethodDictionary new format: newFormat; setInstVarNames: instVars. oldClass ifNotNil:[ newClass organization: oldClass organization. "Recompile the new class" oldClass hasMethods ifTrue:[newClass compileAllFrom: oldClass]. oldClass hasTraitComposition ifTrue: [ newClass setTraitComposition: oldClass traitComposition copyTraitExpression ]. oldClass class hasTraitComposition ifTrue: [ newClass class setTraitComposition: oldClass class traitComposition copyTraitExpression ]. self recordClass: oldClass replacedBy: newClass. ]. (oldClass == nil or:[oldClass isObsolete not]) ifTrue:[newSuper addSubclass: newClass] ifFalse:[newSuper addObsoleteSubclass: newClass]. ^newClass! ! !ClassBuilder methodsFor: 'class definition' stamp: 'NS 1/21/2004 09:53'! recompile: force from: oldClass to: newClass 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." oldClass == nil ifTrue:[^ newClass]. (newClass == oldClass and:[force not and:[forceMutation not]]) ifTrue:[ ^newClass]. currentClassIndex _ 0. maxClassIndex _ oldClass withAllSubclasses size. (oldClass == newClass and:[forceMutation not]) ifTrue:[ "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" self informUserDuring:[ newClass isSystemDefined ifFalse:[progress _ nil]. self mutate: oldClass to: newClass. ]. ^oldClass "now mutated to newClass"! ! !ClassBuilder methodsFor: 'class definition' stamp: 'NS 1/21/2004 09:21'! silentlyMoveInstVarNamed: instVarName from: srcClass to: dstClass after: prevInstVarName "Move the instvar from srcClass to dstClass. Do not perform any checks." | srcVars dstVars dstIndex newClass copyOfSrcClass copyOfDstClass | copyOfSrcClass _ srcClass copy. copyOfDstClass _ dstClass copy. 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:[ newClass _ self reshapeClass: dstClass toSuper: dstClass superclass. self recompile: false from: dstClass to: newClass mutate: true. ] ifFalse:[ (dstClass inheritsFrom: srcClass) ifTrue:[ newClass _ self reshapeClass: srcClass toSuper: srcClass superclass. self recompile: false from: srcClass to: newClass mutate: true. ] ifFalse:[ "Disjunct hierarchies" srcClass == dstClass ifFalse:[ newClass _ self reshapeClass: dstClass toSuper: dstClass superclass. self recompile: false from: dstClass to: newClass mutate: true. ]. newClass _ self reshapeClass: srcClass toSuper: srcClass superclass. self recompile: false from: srcClass to: newClass mutate: true. ]. ]. self doneCompiling: srcClass. self doneCompiling: dstClass. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfSrcClass to: srcClass. SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: copyOfDstClass to: 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: 'class mutation' stamp: 'ar 2/27/2003 22:44'! mutate: oldClass to: newClass "Mutate the old class and subclasses into newClass and subclasses. Note: This method is slightly different from: #mutate:toSuper: since here we are at the root of reshaping and have two distinct roots." | newSubclass | self showProgressFor: oldClass. "Convert the subclasses" oldClass subclasses do:[:oldSubclass| newSubclass _ self reshapeClass: oldSubclass toSuper: newClass. self mutate: oldSubclass to: newSubclass. ]. "And any obsolete ones" oldClass obsoleteSubclasses do:[:oldSubclass| oldSubclass ifNotNil:[ newSubclass _ self reshapeClass: oldSubclass toSuper: newClass. self mutate: oldSubclass to: newSubclass. ]. ]. self update: oldClass to: newClass. ^newClass! ! !ClassBuilder methodsFor: 'class mutation' stamp: 'ar 9/22/2002 03:16'! reshapeClass: oldClass toSuper: newSuper "Reshape the given class to the new super class. Recompile all the methods in the newly created class. Answer the new class." | instVars | "ar 9/22/2002: The following is a left-over from some older code. I do *not* know why we uncompact oldClass here. If you do, then please let me know so I can put a comment here..." oldClass becomeUncompact. instVars _ instVarMap at: oldClass name ifAbsent:[oldClass instVarNames]. ^self newSubclassOf: newSuper type: oldClass typeOfClass instanceVariables: instVars from: oldClass! ! !ClassBuilder methodsFor: 'class mutation' stamp: 'ar 2/27/2003 23:42'! update: oldClass to: newClass "Convert oldClass, all its instances and possibly its meta class into newClass, instances of newClass and possibly its meta class. The process is surprisingly simple in its implementation and surprisingly complex in its nuances and potentially bad side effects. We can rely on two assumptions (which are critical): #1: The method #updateInstancesFrom: will not create any lasting pointers to 'old' instances ('old' is quote on quote since #updateInstancesFrom: will do a become of the old vs. the new instances and therefore it will not create pointers to *new* instances before the #become: which are *old* afterwards) #2: The non-preemptive execution of the critical piece of code guarantees that nobody can get a hold by 'other means' (such as process interruption and reflection) on the old instances. Given the above two, we know that after #updateInstancesFrom: there are no pointer to any old instances. After the forwarding become there will be no pointers to the old class or meta class either. Meaning that if we throw in a nice fat GC at the end of the critical block, everything will be gone (but see the comment right there). There's no need to worry. " | meta | meta _ oldClass isMeta. "Note: Everything from here on will run without the ability to get interrupted to prevent any other process to create new instances of the old class." [ "Note: The following removal may look somewhat obscure and needs an explanation. When we mutate the class hierarchy we create new classes for any existing subclass. So it may look as if we don't have to remove the old class from its superclass. However, at the top of the hierarchy (the first class we reshape) that superclass itself is not newly created so therefore it will hold both the oldClass and newClass in its (obsolete or not) subclasses. Since the #become: below will transparently replace the pointers to oldClass with newClass the superclass would have newClass in its subclasses TWICE. With rather unclear effects if we consider that we may convert the meta-class hierarchy itself (which is derived from the non-meta class hierarchy). Due to this problem ALL classes are removed from their superclass just prior to converting them. Here, breaking the superclass/subclass invariant really doesn't matter since we will effectively remove the oldClass (become+GC) just a few lines below." oldClass superclass removeSubclass: oldClass. oldClass superclass removeObsoleteSubclass: oldClass. "Convert the instances of oldClass into instances of newClass" newClass updateInstancesFrom: oldClass. meta ifTrue:[oldClass becomeForward: newClass] ifFalse:[(Array with: oldClass with: oldClass class) elementsForwardIdentityTo: (Array with: newClass with: newClass class)]. Smalltalk garbageCollect. "Warning: Read this before you even think about removing the GC. Yes, it slows us down. Quite heavily if you have a large image. However, there's no good and simple alternative here, since unfortunately, #become: does change class pointers. What happens is that after the above become all of the instances of the old class will have a class pointer identifying them as instances of newClass. If we get our hands on any of these instances we will break immediately since their expected instance layout (that of its class, e.g., newClass) will not match their actual instance layout (that of oldClass). And getting your hands on any of those instances is really simple - just reshaping one class two times in rapid succession will do it. Reflection techniques, interrupts, etc. will only add to this problem. In the case of Metaclass things get even worse since when we recompile the entire class hierarchy we will recompile both, Metaclass and its instances (and some of its instances will have the old and some the new layout). The only easy solution to this problem would be to 'fix up' the class pointers of the old instances to point to the old class (using primitiveChangeClassTo:). But this won't work either - as we do a one-way become we would have to search the entire object memory for the oldClass and couldn't even clearly identify it unless we give it some 'special token' which sounds quite error-prone. If you really need to get rid of the GC here are some alternatives: On the image level, one could create a copy of the oldClass before becoming it into the new class and, after becoming it, 'fix up' the old instances. That would certainly work but it sounds quite complex, as we need to make sure we're not breaking any of the superclass/subclass meta/non-meta class variants. Alternatively, fix up #becomeForward on the VM-level to 'dump the source objects' of #become. This would be quite doable (just 'convert' them into a well known special class such as bitmap) yet it has problems if (accidentally or not) one of the objects in #become: appears on 'both sides of the fence' (right now, this will work ... in a way ... even though the consequences are unclear). Another alternative is to provide a dedicated primitive for this (instead of using it implicitly in become) which would allow us to dump all the existing instances right here. This is equivalent to a more general primitiveChangeClassTo: and might be worthwhile but it would likely have to keep in mind the differences between bits and pointer thingies etc. Since all of the alternatives seem rather complex and magical compared to a straight-forward GC it seems best to stick with the GC solution for now. If someone has a real need to fix this problem, that person will likely be motivated enough to check out the alternatives. Personally I'd probably go for #1 (copy the old class and remap the instances to it) since it's a solution that could be easily reverted from within the image if there's any problem with it." ] valueUnpreemptively. ! ! !ClassBuilder methodsFor: 'initialize' stamp: 'ar 3/3/2001 00:29'! doneCompiling: aClass "The receiver has finished modifying the class hierarchy. Do any necessary cleanup." aClass doneCompiling. Behavior flushObsoleteSubclasses.! ! !ClassBuilder methodsFor: 'initialize' stamp: 'ar 8/29/1999 12:32'! initialize environ _ Smalltalk. instVarMap _ IdentityDictionary new.! ! !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 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: 'yo 11/11/2002 10:22'! validateClassName: aString "Validate the new class name" aString first canBeGlobalVarInitial 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: 'ajh 10/17/2002 11:10'! 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" newSuper allowsSubInstVars ifFalse: [ self error: newSuper printString, ' does not allow subclass inst vars. See allowsSubInstVars.'. ^ false]. "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: 'bkv 4/2/2003 17:13'! validateSubclass: subclass canKeepLayoutFrom: oldClass forSubclassFormat: newType "Returns whether the immediate subclasses of oldClass can keep its layout" "Note: Squeak does not appear to model classFormat relationships.. so I'm putting some logic here. bkv 4/2/2003" "isWeak implies isVariant" (oldClass isVariable and: [ subclass isWeak ]) ifFalse: [ "In general we discourage format mis-matches" (subclass typeOfClass == newType) ifFalse: [ self error: subclass name,' cannot be recompiled'. ^ false ]]. ^ true! ! !ClassBuilder methodsFor: 'validation' stamp: 'bkv 4/2/2003 17:19'! validateSubclassFormat: newType from: oldClass forSuper: newSuper extra: newInstSize "Validate the # of instVars and the format of the subclasses" | deltaSize | 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]. "If we get this far, check whether the immediate subclasses of oldClass can keep its layout." (newType ~~ #normal) ifTrue: [ self validateSubclass: sub canKeepLayoutFrom: oldClass forSubclassFormat: newType ]]. ^ 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: '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 2/27/2003 22:56'! privateNewSubclassOf: newSuper "Create a new meta and non-meta subclass of newSuper" "WARNING: This method does not preserve the superclass/subclass invariant!!" | newSuperMeta newMeta | newSuperMeta _ newSuper ifNil:[Class] ifNotNil:[newSuper class]. newMeta _ Metaclass new. newMeta superclass: newSuperMeta methodDictionary: MethodDictionary new format: newSuperMeta format. ^newMeta new ! ! !ClassBuilder methodsFor: 'private' stamp: 'ar 2/27/2003 22:56'! privateNewSubclassOf: newSuper from: oldClass "Create a new meta and non-meta subclass of newSuper using oldClass as template" "WARNING: This method does not preserve the superclass/subclass invariant!!" | newSuperMeta oldMeta newMeta | oldClass ifNil:[^self privateNewSubclassOf: newSuper]. newSuperMeta _ newSuper ifNil:[Class] ifNotNil:[newSuper class]. oldMeta _ oldClass class. newMeta _ oldMeta clone. 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]. "Record the meta class change" self recordClass: oldMeta replacedBy: newMeta. "And create a new instance" ^newMeta adoptInstance: oldClass from: oldMeta! ! !ClassBuilder methodsFor: 'private' stamp: 'NS 1/27/2004 14:21'! recordClass: oldClass replacedBy: newClass "Keep the changes up to date when we're moving instVars around" (instVarMap includesKey: oldClass name) ifTrue:[ SystemChangeNotifier uniqueInstance classDefinitionChangedFrom: oldClass to: newClass. ].! ! !ClassBuilder methodsFor: 'private' stamp: 'gk 2/28/2005 16:35'! 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: 'ar 3/5/2001 12:00'! showProgressFor: aClass "Announce that we're processing aClass" progress == nil ifTrue:[^self]. aClass isObsolete ifTrue:[^self]. currentClassIndex _ currentClassIndex + 1. (aClass hasMethods and: [aClass wantsRecompilationProgressReported]) ifTrue: [progress value: ('Recompiling ', aClass name,' (', currentClassIndex printString,'/', maxClassIndex printString,')')]! ! !ClassBuilder methodsFor: 'private' stamp: 'dvf 9/27/2005 16:49'! tooDangerousClasses "Return a list of class names which will not be modified in the public interface" ^#( "Object will break immediately" ProtoObject 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 PositionableStream UndefinedObject ) ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassBuilder class instanceVariableNames: ''! !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: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 16:04'! checkClassHierarchyConsistency "Check the consistency of the class hierarchy. The class hierarchy is consistent if the following two logical equivalences hold for classes A and B: - B is obsolete and 'B superclass' yields A <--> 'A obsoleteSubclasses' contains B - B is not obsolete and 'B superclass' yields A <--> 'A subclasses' contains B" Utilities informUserDuring:[:bar| self checkClassHierarchyConsistency: bar. ].! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 16:03'! checkClassHierarchyConsistency: informer "Check the consistency of the class hierarchy. The class hierarchy is consistent if the following two logical equivalences hold for classes A and B: - B is obsolete and 'B superclass' yields A <--> 'A obsoleteSubclasses' contains B - B is not obsolete and 'B superclass' yields A <--> 'A subclasses' contains B" | classes | Transcript cr; show: 'Start checking the class hierarchy...'. Smalltalk garbageCollect. classes := Metaclass allInstances. classes keysAndValuesDo: [:index :meta | informer value:'Validating class hierarchy ', (index * 100 // classes size) printString,'%'. meta allInstances do: [:each | self checkClassHierarchyConsistencyFor: each]. self checkClassHierarchyConsistencyFor: meta. ]. Transcript show: 'OK'.! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 2/19/2002 11:48'! checkClassHierarchyConsistencyFor: aClassDescription "Check whether aClassDescription has a consistent superclass and consistent regular and obsolete subclasses" | mySuperclass | mySuperclass _ aClassDescription superclass. (mySuperclass subclasses includes: aClassDescription) = aClassDescription isObsolete ifTrue: [self error: 'Something wrong!!']. mySuperclass ifNil: [^ self]. "Obsolete subclasses of nil cannot be stored" (mySuperclass obsoleteSubclasses includes: aClassDescription) = aClassDescription isObsolete ifFalse: [self error: 'Something wrong!!']. aClassDescription subclasses do: [:each | each isObsolete ifTrue: [self error: 'Something wrong!!']. each superclass == aClassDescription ifFalse: [self error: 'Something wrong!!'] ]. aClassDescription obsoleteSubclasses do: [:each | each isObsolete ifFalse: [self error: 'Something wrong!!']. each superclass == aClassDescription ifFalse: [self error: 'Something wrong!!'] ].! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 16:00'! cleanupAndCheckClassHierarchy "Makes the class hierarchy consistent and removes obsolete classes from the SystemDictionary. Afterwards it checks whether the hierarchy is really consistent." Utilities informUserDuring:[:bar| self cleanupAndCheckClassHierarchy: bar. ]. ! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 15:58'! cleanupAndCheckClassHierarchy: informer "Makes the class hierarchy consistent and removes obsolete classes from the SystemDictionary. Afterwards it checks whether the hierarchy is really consistent." Transcript cr; show: '*** Before cleaning up ***'. self countReallyObsoleteClassesAndMetaclasses. self cleanupClassHierarchy: informer. self checkClassHierarchyConsistency: informer. Transcript cr; cr; show: '*** After cleaning up ***'. self countReallyObsoleteClassesAndMetaclasses.! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 16:04'! cleanupClassHierarchy "Makes the class hierarchy consistent and removes obsolete classes from the SystemDictionary." Utilities informUserDuring:[:bar| self cleanupClassHierarchy: bar. ].! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'ar 4/23/2002 16:02'! cleanupClassHierarchy: informer "Makes the class hierarchy consistent and removes obsolete classes from the SystemDictionary." | classes | Transcript cr; show: 'Start fixing the class hierarchy and cleaning up...'. Smalltalk garbageCollect. classes := Metaclass allInstances. classes keysAndValuesDo: [:index :meta | informer value:'Fixing class hierarchy ', (index * 100 // classes size) printString,'%'. "Check classes before metaclasses (because Metaclass>>isObsolete checks whether the related class is obsolete)" meta allInstances do: [:each | self cleanupClassHierarchyFor: each]. self cleanupClassHierarchyFor: meta. ]. Transcript show: 'DONE'.! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 5/8/2002 10:55'! cleanupClassHierarchyFor: aClassDescription | myName mySuperclass | mySuperclass _ aClassDescription superclass. (self isReallyObsolete: aClassDescription) ifTrue: [ "Remove class >>>from SystemDictionary if it is obsolete" myName _ aClassDescription name asString. Smalltalk keys asArray do: [:each | (each asString = myName and: [(Smalltalk at: each) == aClassDescription]) ifTrue: [Smalltalk removeKey: each]]. "Make class officially obsolete if it is not" (aClassDescription name asString beginsWith: 'AnObsolete') ifFalse: [aClassDescription obsolete]. aClassDescription isObsolete ifFalse: [self error: 'Something wrong!!']. "Add class to obsoleteSubclasses of its superclass" mySuperclass ifNil: [self error: 'Obsolete subclasses of nil cannot be stored']. (mySuperclass obsoleteSubclasses includes: aClassDescription) ifFalse: [mySuperclass addObsoleteSubclass: aClassDescription]. ] ifFalse:[ "check if superclass has aClassDescription in its obsolete subclasses" mySuperclass ifNil:[mySuperclass _ Class]. "nil subclasses" mySuperclass removeObsoleteSubclass: aClassDescription. ]. "And remove its obsolete subclasses if not actual superclass" aClassDescription obsoleteSubclasses do:[:obs| obs superclass == aClassDescription ifFalse:[ aClassDescription removeObsoleteSubclass: obs]]. ! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 2/19/2002 11:49'! countReallyObsoleteClassesAndMetaclasses "Counting really obsolete classes and metaclasses" | metaSize classSize | Smalltalk garbageCollect. metaSize _ self reallyObsoleteMetaclasses size. Transcript cr; show: 'Really obsolete metaclasses: ', metaSize printString. classSize _ self reallyObsoleteClasses size. Transcript cr; show: 'Really obsolete classes: ', classSize printString; cr. "Metaclasses must correspond to classes!!" metaSize ~= classSize ifTrue: [self error: 'Serious metalevel inconsistency!!!!'].! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 2/19/2002 11:49'! isReallyObsolete: aClassDescription "Returns whether the argument class is *really* obsolete. (Due to a bug, the method isObsolete isObsolete does not always return the right answer" ^ aClassDescription isObsolete or: [(aClassDescription superclass subclasses includes: aClassDescription) not]! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 2/15/2002 16:52'! reallyObsoleteClasses | obsoleteClasses | obsoleteClasses _ OrderedCollection new. Metaclass allInstances do: [:meta | meta allInstances do: [:each | (self isReallyObsolete: each) ifTrue: [obsoleteClasses add: each]]]. ^ obsoleteClasses! ! !ClassBuilder class methodsFor: 'cleanup obsolete classes' stamp: 'NS 2/15/2002 16:52'! reallyObsoleteMetaclasses ^ Metaclass allInstances select: [:each | self isReallyObsolete: each].! ! TestCase subclass: #ClassBuilderChangeClassTypeTest instanceVariableNames: 'baseClass subClass' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !ClassBuilderChangeClassTypeTest methodsFor: 'utilities' stamp: 'BG 1/5/2004 22:49'! baseClassName ^'TestClassForClassChangeTest'! ! !ClassBuilderChangeClassTypeTest methodsFor: 'utilities' stamp: 'BG 1/5/2004 22:51'! cleanup baseClass ifNotNil:[baseClass removeFromSystem].! ! TestCase subclass: #ClassBuilderFormatTests instanceVariableNames: 'baseClass subClass' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:21'! testByteVariableSubclass "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object variableByteSubclass: self baseClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self assert: (subClass isBytes). subClass removeFromSystem. "pointer classes" self should:[self makeIVarsSubclassOf: baseClass] raise: Error. self should:[self makeVariableSubclassOf: baseClass] raise: Error. self should:[self makeWeakSubclassOf: baseClass] raise: Error. "bit classes" self shouldnt:[self makeByteVariableSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self assert: (subClass isBytes). subClass removeFromSystem. self should:[self makeWordVariableSubclassOf: baseClass] raise: Error. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:20'! testSubclass "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object subclass: self baseClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self deny: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "pointer classes" self shouldnt:[self makeIVarsSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self deny: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeVariableSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert:(subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeWeakSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert:(subClass isVariable). self assert:(subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "bit classes" self shouldnt:[self makeByteVariableSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self assert: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeWordVariableSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:21'! testSubclassWithInstanceVariables "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object subclass: self baseClassName instanceVariableNames: 'var1 var2' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self deny: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "pointer classes" self shouldnt:[self makeIVarsSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self deny: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeVariableSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeWeakSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self assert: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "bit classes" self should:[self makeByteVariableSubclassOf: baseClass] raise: Error. self should:[self makeWordVariableSubclassOf: baseClass] raise: Error. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:20'! testVariableSubclass "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object variableSubclass: self baseClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ "pointer classes" self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeIVarsSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeVariableSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeWeakSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self assert: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "bit classes" self should:[self makeByteVariableSubclassOf: baseClass] raise: Error. self should:[self makeWordVariableSubclassOf: baseClass] raise: Error. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:20'! testWeakSubclass "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object weakSubclass: self baseClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ "pointer classes" self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self assert: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeIVarsSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self assert: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeVariableSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. self shouldnt:[self makeWeakSubclassOf: baseClass] raise: Error. self assert: (subClass isPointers). self assert: (subClass isVariable). self assert: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "bit classes" self should:[self makeByteVariableSubclassOf: baseClass] raise: Error. self should:[self makeWordVariableSubclassOf: baseClass] raise: Error. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'testing' stamp: 'ar 1/4/2004 20:20'! testWordVariableSubclass "Ensure that the invariants for superclass/subclass format are preserved" baseClass := Object variableWordSubclass: self baseClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'. [ self shouldnt:[self makeNormalSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. "pointer classes" self should:[self makeIVarsSubclassOf: baseClass] raise: Error. self should:[self makeVariableSubclassOf: baseClass] raise: Error. self should:[self makeWeakSubclassOf: baseClass] raise: Error. "bit classes" self should:[self makeByteVariableSubclassOf: baseClass] raise: Error. self shouldnt:[self makeWordVariableSubclassOf: baseClass] raise: Error. self deny: (subClass isPointers). self assert: (subClass isVariable). self deny: (subClass isWeak). self deny: (subClass isBytes). subClass removeFromSystem. ] ensure:[self cleanup].! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! baseClassName ^#DummyClassBuilderFormatTestSuperClass! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! cleanup subClass ifNotNil:[subClass removeFromSystem]. baseClass ifNotNil:[baseClass removeFromSystem].! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! makeByteVariableSubclassOf: aClass subClass := aClass variableByteSubclass: self subClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! makeIVarsSubclassOf: aClass subClass := aClass subclass: self subClassName instanceVariableNames: 'var3 var4' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! makeNormalSubclassOf: aClass subClass := aClass subclass: self subClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:15'! makeVariableSubclassOf: aClass subClass := aClass variableSubclass: self subClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'.! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:16'! makeWeakSubclassOf: aClass subClass := aClass weakSubclass: self subClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:16'! makeWordVariableSubclassOf: aClass subClass := aClass variableWordSubclass: self subClassName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Tests-ClassBuilder'! ! !ClassBuilderFormatTests methodsFor: 'utilities' stamp: 'ar 1/4/2004 20:16'! subClassName ^#DummyClassBuilderFormatTestSubClass! ! 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: 'al 11/28/2005 22:10'! 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: nil]! ! !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 ! ! !ClassCategoryReader methodsFor: 'private' stamp: 'ajh 1/18/2002 01:14'! theClass ^ class! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassCategoryReader class instanceVariableNames: ''! Object subclass: #ClassChangeRecord instanceVariableNames: 'inForce revertable changeTypes thisDefinition priorDefinition thisName priorName thisOrganization priorOrganization thisComment priorComment thisMD priorMD methodChanges' classVariableNames: '' poolDictionaries: '' category: 'System-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: 'sw 4/3/2001 14:16'! 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 == #addedThenRemoved ifTrue: [^ self]. "An entire class was added but then removed" 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: '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: '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.! ! !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: '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: 'sw 8/14/2002 11:11'! removeSelector: selector "Remove all memory of changes associated with the argument, selector, in this class." selector == #Comment ifTrue: [changeTypes remove: #comment ifAbsent: []] ifFalse: [methodChanges removeKey: selector ifAbsent: []]! ! !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: '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: 'rename' stamp: 'tk 6/8/2001 09:11'! thisName ^ thisName! ! ClassCategoryReader subclass: #ClassCommentReader instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !ClassCommentReader methodsFor: 'as yet unclassified' stamp: 'sw 7/31/2002 10:40'! 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) stamp: changeStamp "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"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassCommentReader class instanceVariableNames: ''! VersionsBrowser subclass: #ClassCommentVersionsBrowser instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Changes'! !ClassCommentVersionsBrowser commentStamp: 'asm 8/13/2002 23:20' prior: 0! A class-comment-versions-browser tool! !ClassCommentVersionsBrowser methodsFor: 'basic function' stamp: 'sd 11/20/2005 21:26'! diffedVersionContents "Answer diffed version contents, maybe pretty maybe not" | change class earlier later | (listIndex = 0 or: [changeList size < listIndex]) ifTrue: [^ '']. change := changeList at: listIndex. later := change text. class := self selectedClass. (listIndex == changeList size or: [class == nil]) ifTrue: [^ later]. earlier := (changeList at: listIndex + 1) text. ^ TextDiffBuilder buildDisplayPatchFrom: earlier to: later inClass: class prettyDiffs: self showingPrettyDiffs! ! !ClassCommentVersionsBrowser methodsFor: 'basic function' stamp: 'sd 11/20/2005 21:26'! reformulateList classOfMethod organization classComment ifNil: [^ self]. self scanVersionsOf: classOfMethod. self changed: #list. "for benefit of mvc" listIndex := 1. self changed: #listIndex. self contentsChanged! ! !ClassCommentVersionsBrowser methodsFor: 'basic function' stamp: 'sd 11/20/2005 21:26'! scanVersionsOf: class "Scan for all past versions of the class comment of the given class" | oldCommentRemoteStr sourceFilesCopy position prevPos stamp preamble tokens prevFileIndex | classOfMethod := class. oldCommentRemoteStr := class organization commentRemoteStr. currentCompiledMethod := oldCommentRemoteStr. selectorOfMethod := #Comment. changeList := OrderedCollection new. list := OrderedCollection new. listIndex := 0. oldCommentRemoteStr ifNil:[^ nil] ifNotNil: [oldCommentRemoteStr sourcePointer]. sourceFilesCopy := SourceFiles collect: [:x | x isNil ifTrue: [ nil ] ifFalse: [x readOnlyCopy]]. position := oldCommentRemoteStr position. file := sourceFilesCopy at: oldCommentRemoteStr sourceFileNumber. [position notNil & file notNil] whileTrue: [file position: (0 max: position-150). " Skip back to before the preamble" [file position < (position-1)] "then pick it up from the front" whileTrue: [preamble := file nextChunk]. prevPos := nil. stamp := ''. (preamble findString: 'commentStamp:' startingAt: 1) > 0 ifTrue: [tokens := Scanner new scanTokens: preamble. (tokens at: tokens size-3) = #commentStamp: ifTrue: ["New format gives change stamp and unified prior pointer" stamp := tokens at: tokens size-2. prevPos := tokens last. prevFileIndex := sourceFilesCopy fileIndexFromSourcePointer: prevPos. prevPos := sourceFilesCopy filePositionFromSourcePointer: prevPos]] ifFalse: ["The stamp get lost, maybe after a condenseChanges" stamp := '']. self addItem: (ChangeRecord new file: file position: position type: #classComment class: class name category: nil meta: class stamp: stamp) text: stamp , ' ' , class name , ' class comment'. prevPos = 0 ifTrue:[prevPos := nil]. position := prevPos. prevPos notNil ifTrue:[file := sourceFilesCopy at: prevFileIndex]]. sourceFilesCopy do: [:x | x notNil ifTrue: [x close]]. listSelections := Array new: list size withAll: false! ! !ClassCommentVersionsBrowser methodsFor: 'basic function' stamp: 'sd 11/20/2005 21:26'! updateListsAndCodeIn: aWindow | aComment | aComment := classOfMethod organization commentRemoteStr. aComment == currentCompiledMethod ifFalse: ["Do not attempt to formulate if there is no source pointer. It probably means it has been recompiled, but the source hasn't been written (as during a display of the 'save text simply?' confirmation)." aComment last ~= 0 ifTrue: [self reformulateList]]. ^ true ! ! !ClassCommentVersionsBrowser methodsFor: 'menu' stamp: 'sd 11/20/2005 21:26'! compareToCurrentVersion "If the current selection corresponds to a method in the system, then spawn a window showing the diffs as text" | change s1 s2 | listIndex = 0 ifTrue: [^ self]. change := changeList at: listIndex. s1 := classOfMethod organization classComment. s2 := change string. s1 = s2 ifTrue: [^ self inform: 'Exact Match']. (StringHolder new textContents: (TextDiffBuilder buildDisplayPatchFrom: s1 to: s2 inClass: classOfMethod prettyDiffs: self showingPrettyDiffs)) openLabel: 'Comparison to Current Version'! ! !ClassCommentVersionsBrowser methodsFor: 'menu' stamp: 'asm 8/13/2002 21:02'! offerVersionsHelp (StringHolder new contents: self versionsHelpString) openLabel: 'Class Comment Versions Browsers'! ! !ClassCommentVersionsBrowser methodsFor: 'menu' stamp: 'sd 11/20/2005 21:26'! openSingleMessageBrowser | mr | "Create and schedule a message list browser populated only by the currently selected message" mr := MethodReference new setStandardClass: self selectedClass methodSymbol: #Comment. self systemNavigation browseMessageList: (Array with: mr) name: mr asStringOrText autoSelect: nil! ! !ClassCommentVersionsBrowser methodsFor: 'menu' stamp: 'asm 8/13/2002 21:53'! versionsMenu: aMenu "Fill aMenu with menu items appropriate to the receiver" Smalltalk isMorphic ifTrue: [aMenu title: 'versions'. aMenu addStayUpItemSpecial]. ^ aMenu addList: #( ('compare to current' compareToCurrentVersion 'compare selected version to the current version') ('revert to selected version' fileInSelections 'resubmit the selected version, so that it becomes the current version') ('remove from changes' removeMethodFromChanges 'remove this method from the current change set, if present') ('edit current method (O)' openSingleMessageBrowser 'open a single-message browser on the current version of this method') - ('toggle diffing (D)' toggleDiffing 'toggle whether or not diffs should be shown here') ('update list' reformulateList 'reformulate the list of versions, in case it somehow got out of synch with reality') - ('help...' offerVersionsHelp 'provide an explanation of the use of this tool')) ! ! !ClassCommentVersionsBrowser methodsFor: 'misc' stamp: 'sw 8/17/2002 21:57'! classCommentIndicated "Answer whether the receiver is pointed at a class comment" ^ true! ! !ClassCommentVersionsBrowser methodsFor: 'misc' stamp: 'sw 8/15/2002 22:38'! contentsSymbolQuints "Answer a list of quintuplets representing information on the alternative views available in the code pane" ^ #( (source togglePlainSource showingPlainSourceString 'source' 'the textual source code as writen') (showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version'))! ! !ClassCommentVersionsBrowser methodsFor: 'misc' stamp: 'sd 11/20/2005 21:26'! priorSourceOrNil "If the currently-selected method has a previous version, return its source, else return nil" | aClass aSelector changeRecords | (aClass := self selectedClass) ifNil: [^ nil]. (aSelector := self selectedMessageName) ifNil: [^ nil]. changeRecords := self class commentRecordsOf: self selectedClass. (changeRecords == nil or: [changeRecords size <= 1]) ifTrue: [^ nil]. ^ (changeRecords at: 2) string ! ! !ClassCommentVersionsBrowser methodsFor: 'misc' stamp: 'asm 8/13/2002 20:59'! selectedClass "Answer the class currently selected in the browser. In the case of a VersionsBrowser, the class and selector are always the same, regardless of which version is selected and indeed whether or not any entry is selected in the list pane" ^ classOfMethod! ! !ClassCommentVersionsBrowser methodsFor: 'misc' stamp: 'sw 8/15/2002 22:35'! wantsPrettyDiffOption "Answer whether pretty-diffs are meaningful for this tool" ^ false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassCommentVersionsBrowser class instanceVariableNames: ''! !ClassCommentVersionsBrowser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! browseCommentOf: class | changeList | Cursor read showWhile: [changeList := self new scanVersionsOf: class. changeList ifNil: [^ self inform: 'No versions available']. self open: changeList name: 'Recent versions of ',class name,'''s comments' multiSelect: false ] ! ! !ClassCommentVersionsBrowser class methodsFor: 'utilities' stamp: 'sd 11/20/2005 21:28'! commentRecordsOf: aClass "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." | aList | aList := self new scanVersionsOf: aClass. ^ aList ifNotNil: [aList changeList]! ! !ClassCommentVersionsBrowser class methodsFor: 'utilities' stamp: 'sd 11/20/2005 21:28'! timeStampFor: aSelector class: aClass reverseOrdinal: anInteger "Answer the time stamp corresponding to some version of the given method, nil if none. The reverseOrdinal parameter is interpreted as: 1 = current version; 2 = last-but-one version, etc." | aChangeList | aChangeList := self new scanVersionsOf: aClass. ^ aChangeList ifNil: [nil] ifNotNil: [aChangeList list size >= anInteger ifTrue: [(aChangeList changeList at: anInteger) stamp] ifFalse: [nil]]! ! !ClassCommentVersionsBrowser class methodsFor: 'window color' stamp: 'asm 8/13/2002 20:57'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Class Comment Versions Browser' brightColor: #(0.769 0.653 1.0) pastelColor: #(0.819 0.753 1.0) helpMessage: 'A tool for viewing prior versions of a class comment.'! ! Behavior subclass: #ClassDescription uses: TCopyingDescription + TBasicCategorisingDescription + TCompilingDescription + TAccessingMethodDictDescription + TTraitsCategorisingDescription + TPrintingDescription + TCommentDescription + TFileInOutDescription + TTestingDescription 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: 'accessing' stamp: 'sd 6/27/2003 23:57'! classVersion "Default. Any class may return a later version to inform readers that use ReferenceStream. 8/17/96 tk" "This method allows you to distinguish between class versions when the shape of the class hasn't changed (when there's no change in the instVar names). In the conversion methods you usually can tell by the inst var names what old version you have. In a few cases, though, the same inst var names were kept but their interpretation changed (like in the layoutFrame). By changing the class version when you keep the same instVars you can warn older and newer images that they have to convert." ^ 0! ! !ClassDescription methodsFor: 'accessing' stamp: 'NS 12/9/2003 15:12'! version "Allows polymoprhism with TraitDescription>>version" ^ self classVersion! ! !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: 'dtl 8/26/2004 11:02'! commentInventory "Answer a string with a count of the classes with and without comments for all the classes in the package of which this class is a member." "Morph commentInventory" ^ SystemOrganization commentInventory: (self category copyUpTo: $-), '*'! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'al 11/28/2005 11:51'! 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: 'al 11/28/2005 11:52'! removeUninstantiatedSubclassesSilently "Remove the classes of any subclasses that have neither instances nor subclasses. Answer the number of bytes reclaimed" "Player removeUninstantiatedSubclassesSilently" | candidatesForRemoval oldFree | oldFree _ self environment garbageCollect. candidatesForRemoval _ self subclasses select: [:c | (c instanceCount = 0) and: [c subclasses size = 0]]. candidatesForRemoval do: [:c | c removeFromSystem]. ^ self environment garbageCollect - oldFree! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'al 11/28/2005 11:52'! subclasses ^ Array new! ! !ClassDescription methodsFor: 'accessing class hierarchy' stamp: 'al 11/28/2005 11:52'! subclassesDo: aBlock "Evaluate the argument, aBlock, for each of the receiver's immediate subclasses." ^self subclasses do: aBlock! ! !ClassDescription methodsFor: 'accessing comment' stamp: 'al 10/17/2005 10:23'! classCommentBlank | existingComment stream | existingComment := self theNonMetaClass organization classComment. existingComment isEmpty ifFalse: [^existingComment]. stream := WriteStream on: (String new: 100). stream nextPutAll: 'A'; nextPutAll: (self name first isVowel ifTrue: ['n '] ifFalse: [' ']); nextPutAll: self name; nextPutAll: ' is xxxxxxxxx.'; cr; cr; nextPutAll: 'Instance Variables'. self instVarNames asSortedCollection do: [:each | stream cr; tab; nextPutAll: each; nextPut: $:; tab; tab; nextPutAll: '']. stream cr. self instVarNames asSortedCollection do: [:each | stream cr; nextPutAll: each; cr; tab; nextPutAll: '- xxxxx'; cr]. ^stream contents! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'al 7/30/2004 09:51'! addSelectorSilently: selector withMethod: compiledMethod super addSelectorSilently: selector withMethod: compiledMethod. self instanceSide noteAddedSelector: selector meta: self isMeta.! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'sw 1/5/2001 06:53'! allMethodCategoriesIntegratedThrough: mostGenericClass "Answer a list of all the method categories of the receiver and all its superclasses, up through mostGenericClass" | aColl | aColl _ OrderedCollection new. self withAllSuperclasses do: [:aClass | (aClass includesBehavior: mostGenericClass) ifTrue: [aColl addAll: aClass organization categories]]. aColl remove: 'no messages' asSymbol ifAbsent: []. ^ (aColl asSet asSortedCollection: [:a :b | a asLowercase < b asLowercase]) asArray "ColorTileMorph allMethodCategoriesIntegratedThrough: TileMorph"! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'sd 4/18/2003 10:26'! allMethodsInCategory: aName "Answer a list of all the method categories of the receiver and all its superclasses " | aColl | aColl _ OrderedCollection new. self withAllSuperclasses do: [:aClass | aColl addAll: (aName = ClassOrganizer allCategory ifTrue: [aClass organization allMethodSelectors] ifFalse: [aClass organization listAtCategoryNamed: aName])]. ^ aColl asSet asSortedArray "TileMorph allMethodsInCategory: #initialization"! ! !ClassDescription methodsFor: 'accessing 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: 'accessing method dictionary' stamp: 'sw 12/11/2000 14:00'! isUniClass "Answer whether the receiver is a uniclass." ^ self name endsWithDigit! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'di 3/7/2001 17:05'! recoverFromMDFault "This method handles methodDict faults to support, eg, discoverActiveClasses (qv)." (organization isMemberOf: Array) ifFalse: [^ self error: 'oops']. methodDict _ organization first. organization _ organization second. ! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'sd 3/28/2003 15:32'! recoverFromMDFaultWithTrace "This method handles emthodDict faults to support, eg, discoverActiveClasses (qv)." self recoverFromMDFault. self environment at: #MDFaultDict ifPresent: [:faultDict | faultDict at: self name put: (String streamContents: [:strm | (thisContext stackOfSize: 20) do: [:item | strm print: item; cr]])] "Execute the following statement to induce MD fault tracing. This means that, not only will all active classes be recorded but, after a test run, MDFaultDict will contain, for every class used, a stack trace showing how it came to be used. This statement should be executed just prior to any such text, in order to clear the traces. Smalltalk at: #MDFaultDict put: Dictionary new. "! ! !ClassDescription methodsFor: 'accessing method dictionary' stamp: 'al 5/8/2004 20:49'! removeSelector: selector "Remove the message whose selector is given from the method dictionary of the receiver, if it is there. Answer nil otherwise." | priorMethod priorProtocol | priorMethod _ self compiledMethodAt: selector ifAbsent: [^ nil]. priorProtocol _ self whichCategoryIncludesSelector: selector. super removeSelector: selector. SystemChangeNotifier uniqueInstance doSilently: [self updateOrganizationSelector: selector oldCategory: priorProtocol newCategory: nil]. SystemChangeNotifier uniqueInstance methodRemoved: priorMethod selector: selector inProtocol: priorProtocol class: self.! ! !ClassDescription methodsFor: 'accessing parallel hierarchy' stamp: 'NS 4/12/2004 15:03'! classSide ^self theMetaClass! ! !ClassDescription methodsFor: 'accessing parallel hierarchy' stamp: 'NS 4/12/2004 15:04'! instanceSide ^ self theNonMetaClass! ! !ClassDescription methodsFor: 'accessing parallel hierarchy' stamp: 'sd 6/27/2003 22:50'! theMetaClass "Sent to a class or metaclass, always return the metaclass" ^self class! ! !ClassDescription methodsFor: 'accessing parallel hierarchy'! theNonMetaClass "Sent to a class or metaclass, always return the class" ^self! ! !ClassDescription methodsFor: 'compiling' stamp: 'al 11/28/2005 11:51'! 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: 'copying' stamp: 'yo 6/2/2004 23:05'! copyAllCategoriesUnobtrusivelyFrom: 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 copyCategoryUnobtrusively: cat from: aClass]! ! !ClassDescription methodsFor: 'copying' stamp: 'yo 6/2/2004 23:04'! copyAllUnobtrusively: 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 copyUnobtrusively: s from: class classified: cat]! ! !ClassDescription methodsFor: 'copying' stamp: 'yo 6/2/2004 23:05'! copyCategoryUnobtrusively: 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 copyUnobtrusivelyCategory: cat from: class classified: cat! ! !ClassDescription methodsFor: 'copying' stamp: 'yo 6/2/2004 23:11'! copyUnobtrusivelyCategory: 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 copyAllUnobtrusively: (aClass organization listAtCategoryNamed: cat) from: aClass classified: newCat! ! !ClassDescription methodsFor: 'copying' stamp: 'KR 4/14/2006 16:10'! copyUnobtrusively: 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: []. self compileSilently: code classified: category]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'al 3/26/2006 21:49'! definitionST80 "Answer a String that defines the receiver." | aStream | aStream _ WriteStream on: (String new: 300). superclass == nil ifTrue: [aStream nextPutAll: 'ProtoObject'] ifFalse: [aStream nextPutAll: superclass name]. aStream nextPutAll: self kindOfSubclass; store: self name. (self hasTraitComposition and: [self traitComposition notEmpty]) ifTrue: [ aStream cr; tab; nextPutAll: 'uses: '; nextPutAll: self traitCompositionString]. 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. superclass ifNil: [ aStream nextPutAll: '.'; cr. aStream nextPutAll: self name. aStream space; nextPutAll: 'superclass: nil'. ]. ^ aStream contents! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'md 9/7/2006 22:22'! 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 ]. selectors _ selectors select: [:each | (self includesLocalSelector: each) or: [(self methodDict at: each) sendsToSuper]]. "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' stamp: 'bf 12/17/2005 00:04'! 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]. ((cat beginsWith: '*') and: [cat endsWith: '-override']) ifTrue: [ sels do: [:sel | self printMethodChunkHistorically: sel on: aFileStream moveSource: moveSource toFile: fileIndex]] ifFalse: [ sels do: [:sel | self printMethodChunk: sel withPreamble: true on: aFileStream moveSource: moveSource toFile: fileIndex]]]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sumim 9/2/2003 14:36'! fileOutChangedMessagesHistorically: aSet on: aFileStream moveSource: moveSource toFile: fileIndex "File all historical 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 printMethodChunkHistorically: sel on: aFileStream moveSource: moveSource toFile: fileIndex]]! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'sumim 9/2/2003 14:37'! moveChangesWithVersionsTo: 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 | changes _ self methodDict keys select: [:sel | (self methodDict at: sel) fileIndex > 1]. self fileOutChangedMessagesHistorically: changes on: newFile moveSource: true toFile: 2! ! !ClassDescription methodsFor: 'fileIn/Out' stamp: 'md 2/20/2006 15:13'! printMethodChunkHistorically: selector on: outStream moveSource: moveSource toFile: fileIndex "Copy all source codes historically for the method associated with selector onto the fileStream. If moveSource true, then also set the source code pointer of the method." | preamble method newPos sourceFile endPos category changeList prior | category _ self organization categoryOfElement: selector. preamble _ self name , ' methodsFor: ', category asString printString. method _ self methodDict at: selector. ((method fileIndex = 0 or: [(SourceFiles at: method fileIndex) == nil]) or: [method filePosition = 0]) ifTrue: [ outStream cr; nextPut: $!!; nextChunkPut: preamble; cr. outStream nextChunkPut: method decompileString. outStream nextChunkPut: ' '; cr] ifFalse: [ changeList _ ChangeSet scanVersionsOf: method class: self meta: self isMeta category: category selector: selector. newPos _ nil. sourceFile _ SourceFiles at: method fileIndex. changeList reverseDo: [ :chgRec | chgRec fileIndex = fileIndex ifTrue: [ outStream copyPreamble: preamble from: sourceFile at: chgRec position. (prior _ chgRec prior) ifNotNil: [ outStream position: outStream position - 2. outStream nextPutAll: ' prior: ', ( prior first = method fileIndex ifFalse: [prior third] ifTrue: [ SourceFiles sourcePointerFromFileIndex: method fileIndex andPosition: newPos]) printString. outStream nextPut: $!!; cr]. "Copy the method chunk" newPos _ outStream position. outStream copyMethodChunkFrom: sourceFile at: chgRec position. sourceFile skipSeparators. "The following chunk may have ]style[" sourceFile peek == $] ifTrue: [ outStream cr; copyMethodChunkFrom: sourceFile]. outStream nextChunkPut: ' '; cr]]. moveSource ifTrue: [ endPos _ outStream position. method checkOKToAdd: endPos - newPos at: newPos. method setSourcePosition: newPos inFile: fileIndex]]. ^ outStream! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'NS 4/8/2004 10:59'! forgetDoIts "get rid of old DoIt methods and bogus entries in the ClassOrganizer." SystemChangeNotifier uniqueInstance doSilently: [ self organization removeElement: #DoIt; removeElement: #DoItIn:. ]. super forgetDoIts.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'NS 4/8/2004 11:00'! obsolete "Make the receiver obsolete." superclass removeSubclass: self. self organization: nil. super obsolete.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'NS 4/8/2004 11:26'! superclass: aClass methodDictionary: mDict format: fmt "Basic initialization of the receiver" super superclass: aClass methodDictionary: mDict format: fmt. instanceVariables _ nil. self organization: nil.! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'ar 3/1/2001 23:25'! 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. Return the array of old instances (none of which should be pointed to legally by anyone but the array)." "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:[^#()]. "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. ^newInstances "which are now old"! ! !ClassDescription methodsFor: 'initialize-release' stamp: 'ar 3/1/2001 20:48'! 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. Return the array of old instances (none of which should be pointed to legally by anyone but the array)." "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 | oldInstances _ oldClass allInstances asArray. oldInstances _ 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 " | 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: 'instance variables'! addInstVarName: aString "Add the argument, aString, as one of the receiver's instance variables." self subclassResponsibility! ! !ClassDescription methodsFor: 'instance variables' stamp: 'al 11/28/2005 11:50'! 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: 'al 11/28/2005 11:50'! 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: 'al 11/28/2005 22:14'! chooseClassVarName "Present the user with a list of class variable names and answer the one selected, or nil if none" | lines labelStream vars allVars index | lines _ OrderedCollection new. allVars _ 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]. vars isEmpty ifFalse: [lines add: allVars size]]. labelStream contents isEmpty ifTrue: [^Beeper beep]. "handle nil superclass better" labelStream skip: -1 "cut last CR". index _ (UIManager default chooseFrom: (labelStream contents substrings) lines: lines). index _ (PopUpMenu labels: labelStream contents lines: lines) startUp. index = 0 ifTrue: [^ nil]. ^ allVars at: index ! ! !ClassDescription methodsFor: 'instance variables' stamp: 'al 11/28/2005 22:16'! 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 _ (UIManager default chooseFrom: allVars lines: #() title: 'Instance variables in ', self name). index = 0 ifTrue: [^ self]. aBlock value: (allVars at: index)! ! !ClassDescription methodsFor: 'instance variables' stamp: 'Noury Bouraqadi 12/13/2005 07:12'! 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 _ (UIManager default chooseFrom: (labelStream contents subStrings: {Character cr}) lines: lines title: '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: 'al 11/28/2005 11:50'! classThatDefinesClassVariable: classVarName "Answer the class that defines the given class variable" (self classPool includesKey: classVarName asSymbol) ifTrue: [^ self]. ^self superclass ifNotNil: [self superclass classThatDefinesClassVariable: classVarName]! ! !ClassDescription methodsFor: 'instance variables' stamp: 'al 11/28/2005 11:50'! classThatDefinesInstanceVariable: instVarName (self instVarNames notNil and: [self instVarNames includes: instVarName asString]) ifTrue: [^ self]. ^self superclass ifNotNil: [self 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: 'al 11/28/2005 11:52'! 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: 'al 11/28/2005 11:52'! renameSilentlyInstVar: old to: new | i oldName newName | oldName _ old asString. newName _ new asString. (i _ self instVarNames 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]]. self instVarNames 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: 'al 11/28/2005 11:52'! 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: 'organization' stamp: 'NS 4/8/2004 11:02'! organization "Answer the instance of ClassOrganizer that represents the organization of the messages of the receiver." organization ifNil: [self organization: (ClassOrganizer defaultList: self methodDict keys asSortedCollection asArray)]. (organization isMemberOf: Array) ifTrue: [self recoverFromMDFaultWithTrace]. "Making sure that subject is set correctly. It should not be necessary." organization ifNotNil: [organization setSubject: self]. ^ organization! ! !ClassDescription methodsFor: 'organization' stamp: 'NS 4/8/2004 11:04'! organization: aClassOrg "Install an instance of ClassOrganizer that represents the organization of the messages of the receiver." aClassOrg ifNotNil: [aClassOrg setSubject: self]. organization _ aClassOrg! ! !ClassDescription methodsFor: 'organization updating' stamp: 'al 5/9/2004 14:08'! applyChangesOfNewTraitCompositionReplacing: oldComposition | changedSelectors | changedSelectors _ super applyChangesOfNewTraitCompositionReplacing: oldComposition. self noteRecategorizedSelectors: changedSelectors oldComposition: oldComposition. ^ changedSelectors.! ! !ClassDescription methodsFor: 'printing' stamp: 'al 11/28/2005 11:51'! classVariablesString "Answer a string of my class variable names separated by spaces." ^String streamContents: [ :stream | self classPool keys asSortedCollection do: [ :each | stream nextPutAll: each ] separatedBy: [ stream space ] ]! ! !ClassDescription methodsFor: 'printing' stamp: 'al 11/28/2005 11:51'! instanceVariablesString "Answer a string of my instance variable names separated by spaces." ^String streamContents: [ :stream | self instVarNames do: [ :each | stream nextPutAll: each ] separatedBy: [ stream space ] ]! ! !ClassDescription methodsFor: 'printing' stamp: 'al 11/28/2005 11:52'! sharedPoolsString "Answer a string of my shared pool names separated by spaces." ^String streamContents: [ :stream | self sharedPools do: [ :each | stream nextPutAll: (self environment keyAtIdentityValue: each ifAbsent: [ 'private' ]) ] separatedBy: [ stream space ] ]! ! !ClassDescription methodsFor: '*39Deprecated' stamp: 'sd 2/1/2004 17:59'! categoryFromUserWithPrompt: aPrompt "SystemDictionary categoryFromUserWithPrompt: 'testing'" self deprecated: 'Use CodeHolder>>categoryFromUserWithPrompt: aPrompt for: aClass instead'. "this deprecation helps to remove UI dependency from the core of Squeak. Normally only CodeHolder was calling this method" CodeHolder new categoryFromUserWithPrompt: aPrompt for: self! ! !ClassDescription methodsFor: '*39Deprecated' stamp: 'sd 2/1/2004 18:01'! letUserReclassify: anElement "Put up a list of categories and solicit one from the user. Answer true if user indeed made a change, else false" self deprecated: 'Use CodeHolder>>letUserReclassify: anElement in: aClass'. CodeHolder new letUserReclassify: anElement in: self.! ! !ClassDescription methodsFor: '*39Deprecated' stamp: 'md 2/13/2006 17:44'! methods "Answer a ClassCategoryReader for compiling messages that are not classified, as in fileouts made with Smalltalk/V" self deprecated: 'Is this used?'. ^ ClassCategoryReader new setClass: self category: ClassOrganizer default! ! !ClassDescription methodsFor: '*eToys-accessing method dictionary' stamp: 'sw 3/20/2001 13:26'! namedTileScriptSelectors "Answer a list of all the selectors of named tile scripts. Initially, only Player reimplements, but if we switch to a scheme in which every class can have uniclass subclasses, this would kick in elsewhere" ^ OrderedCollection new! ! !ClassDescription methodsFor: '*system-support' stamp: 'dvf 8/23/2003 12:45'! allUnreferencedClassVariables "Answer a list of the names of all the receiver's unreferenced class vars, including those defined in superclasses" ^ self systemNavigation allUnreferencedClassVariablesOf: self! ! !ClassDescription methodsFor: 'private' stamp: 'al 11/28/2005 11:51'! 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: 'md 6/2/2006 10:33'! linesOfCode "An approximate measure of lines of code. Includes comments, but excludes blank lines." | lines | lines _ self methodDict values inject: 0 into: [:sum :each | sum + each linesOfCode]. self isMeta ifTrue: [^ lines] ifFalse: [^ lines + self class linesOfCode]! ! !ClassDescription methodsFor: 'private' stamp: 'NS 1/28/2004 14:22'! logMethodSource: aText forMethodWithNode: aCompiledMethodWithNode inCategory: category withStamp: changeStamp notifying: requestor | priorMethodOrNil newText | priorMethodOrNil := self compiledMethodAt: aCompiledMethodWithNode selector ifAbsent: []. newText _ ((requestor == nil or: [requestor isKindOf: SyntaxError]) not and: [Preferences confirmFirstUseOfStyle]) ifTrue: [aText askIfAddStyle: priorMethodOrNil req: requestor] ifFalse: [aText]. aCompiledMethodWithNode method putSource: newText fromParseNode: aCompiledMethodWithNode node class: self category: category withStamp: changeStamp inFile: 2 priorMethod: priorMethodOrNil.! ! !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 class instanceVariableNames: ''! ClassTestCase subclass: #ClassDescriptionTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !ClassDescriptionTest commentStamp: '' prior: 0! This is the unit test for the class ClassDescription. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category! !ClassDescriptionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 08:24'! testOrganization | aClassOrganizer | aClassOrganizer := ClassDescription organization. self assert: (aClassOrganizer isKindOf: ClassOrganizer).! ! TextDiffBuilder subclass: #ClassDiffBuilder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-FilePackage'! !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' stamp: 'nk 4/24/2004 08:49'! printPatchSequence: ps on: aStream | type line | ps do: [:assoc | type := assoc key. line := assoc value. aStream withAttributes: (self attributesOf: type) do: [aStream nextPutAll: line]]! ! HierarchyBrowser subclass: #ClassListBrowser instanceVariableNames: 'defaultTitle' classVariableNames: '' poolDictionaries: '' category: 'Tools-Browser'! !ClassListBrowser commentStamp: '' prior: 0! A ClassListBrowser displays the code for an arbitrary list of classes. ClassListBrowser example1. "all classes that have the string 'Pluggable' in their names" ClassListBrowser example2. "all classes whose names start with the letter S" ClassListBrowser example3. "all variable classes" ClassListBrowser example4. "all classes with more than 100 methods" ClassListBrowser example5. "all classes that lack class comments" ClassListBrowser example6. "all classes that have class instance variables" ClassListBrowser new initForClassesNamed: #(Browser Boolean) title: 'Browser and Boolean!!'. ! !ClassListBrowser methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:26'! initForClassesNamed: nameList title: aTitle "Initialize the receiver for the class-name-list and title provided" self systemOrganizer: SystemOrganization. metaClassIndicated := false. defaultTitle := aTitle. classList := nameList copy. self class openBrowserView: (self openSystemCatEditString: nil) label: aTitle "ClassListBrowser new initForClassesNamed: #(Browser CategoryViewer) title: 'Frogs'"! ! !ClassListBrowser methodsFor: 'title' stamp: 'sd 11/20/2005 21:26'! defaultTitle: aTitle "Set the browser's default title" defaultTitle := aTitle! ! !ClassListBrowser methodsFor: 'title' stamp: 'sw 7/18/2002 22:43'! labelString "Answer the label strilng to use on the browser" ^ defaultTitle ifNil: [super labelString]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassListBrowser class instanceVariableNames: ''! !ClassListBrowser class methodsFor: 'examples' stamp: 'sw 7/27/2002 16:01'! example1 "Put up a ClassListBrowser that shows all classes that have the string 'Pluggable' in their names" self browseClassesSatisfying: [:cl | cl name includesSubString: 'Pluggable'] title: 'Pluggables' "ClassListBrowser example1" ! ! !ClassListBrowser class methodsFor: 'examples' stamp: 'sd 4/17/2003 21:21'! example2 "Put up a ClassListBrowser that shows all classes whose names start with the letter S" self new initForClassesNamed: (self systemNavigation allClasses collect: [:c | c name] thenSelect: [:aName | aName first == $S]) title: 'All classes starting with S' "ClassListBrowser example2"! ! !ClassListBrowser class methodsFor: 'examples' stamp: 'sw 7/27/2002 16:03'! example3 "Put up a ClassListBrowser that shows all Variable classes" self browseClassesSatisfying: [:c | c isVariable] title: 'All Variable classes' "ClassListBrowser example3" ! ! !ClassListBrowser class methodsFor: 'examples' stamp: 'sw 7/27/2002 16:04'! example4 "Put up a ClassListBrowser that shows all classes implementing more than 100 methods" self browseClassesSatisfying: [:c | (c selectors size + c class selectors size) > 100] title: 'Classes with more than 100 methods' "ClassListBrowser example4" ! ! !ClassListBrowser class methodsFor: 'examples' stamp: 'sw 7/27/2002 14:32'! example5 "Put up a ClassListBrowser that shows all classes that lack class comments" self browseClassesSatisfying: [:c | c organization classComment isEmptyOrNil] title: 'Classes lacking class comments' "ClassListBrowser example5" ! ! !ClassListBrowser class methodsFor: 'examples' stamp: 'sw 7/27/2002 14:33'! example6 "Put up a ClassListBrowser that shows all classes that have class instance variables" self browseClassesSatisfying: [:c | c class instVarNames size > 0] title: 'Classes that define class-side instance variables' "ClassListBrowser example6"! ! !ClassListBrowser class methodsFor: 'instance creation' stamp: 'sd 4/17/2003 21:21'! browseClassesSatisfying: classBlock title: aTitle "Put up a ClassListBrowser showing all classes that satisfy the classBlock." self new initForClassesNamed: (self systemNavigation allClasses select: [:c | (classBlock value: c) == true] thenCollect: [:c | c name]) title: aTitle! ! BasicClassOrganizer subclass: #ClassOrganizer instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Classes'! !ClassOrganizer commentStamp: 'NS 4/6/2004 16:13' 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' stamp: 'NS 4/7/2004 10:37'! addCategory: catString before: nextCategory | oldCategories | oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super addCategory: catString before: nextCategory]. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/15/2004 12:28'! changeFromCategorySpecs: categorySpecs | oldDict oldCategories | oldDict _ self elementCategoryDict. oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super changeFromCategorySpecs: categorySpecs]. self notifyOfChangedSelectorsOldDict: oldDict newDict: self elementCategoryDict. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:37'! classify: element under: heading suppressIfDefault: aBoolean | oldCat newCat | oldCat _ self categoryOfElement: element. SystemChangeNotifier uniqueInstance doSilently: [ super classify: element under: heading suppressIfDefault: aBoolean]. newCat _ self categoryOfElement: element. self notifyOfChangedSelector: element from: oldCat to: newCat.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:37'! removeCategory: cat | oldCategories | oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super removeCategory: cat]. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:37'! removeElement: element | oldCat | oldCat _ self categoryOfElement: element. SystemChangeNotifier uniqueInstance doSilently: [ super removeElement: element]. self notifyOfChangedSelector: element from: oldCat to: (self categoryOfElement: element).! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:38'! removeEmptyCategories | oldCategories | oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super removeEmptyCategories]. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:38'! renameCategory: oldCatString toBe: newCatString | oldCat newCat oldElementsBefore oldElementsAfter | oldCat _ oldCatString asSymbol. newCat _ newCatString asSymbol. oldElementsBefore _ self listAtCategoryNamed: oldCat. SystemChangeNotifier uniqueInstance doSilently: [ super renameCategory: oldCatString toBe: newCatString]. oldElementsAfter _ (self listAtCategoryNamed: oldCat) asSet. oldElementsBefore do: [:each | (oldElementsAfter includes: each) ifFalse: [self notifyOfChangedSelector: each from: oldCat to: newCat]. ]. self notifyOfChangedCategoryFrom: oldCat to: newCat.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/12/2004 20:57'! setDefaultList: aSortedCollection | oldDict oldCategories | oldDict _ self elementCategoryDict. oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super setDefaultList: aSortedCollection]. self notifyOfChangedSelectorsOldDict: oldDict newDict: self elementCategoryDict. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'accessing' stamp: 'NS 4/7/2004 10:38'! sortCategories | oldCategories | oldCategories _ self categories copy. SystemChangeNotifier uniqueInstance doSilently: [ super sortCategories]. self notifyOfChangedCategoriesFrom: oldCategories to: self categories.! ! !ClassOrganizer methodsFor: 'private' stamp: 'NS 4/7/2004 10:15'! notifyOfChangedCategoriesFrom: oldCollectionOrNil to: newCollectionOrNil (self hasSubject and: [oldCollectionOrNil ~= newCollectionOrNil]) ifTrue: [SystemChangeNotifier uniqueInstance classReorganized: self subject].! ! !ClassOrganizer methodsFor: 'private' stamp: 'NS 4/7/2004 23:02'! notifyOfChangedCategoryFrom: oldNameOrNil to: newNameOrNil (self hasSubject and: [oldNameOrNil ~= newNameOrNil]) ifTrue: [SystemChangeNotifier uniqueInstance classReorganized: self subject].! ! !ClassOrganizer methodsFor: 'private' stamp: 'NS 4/16/2004 10:47'! notifyOfChangedSelector: element from: oldCategory to: newCategory (self hasSubject and: [(oldCategory ~= newCategory)]) ifTrue: [ self subject notifyOfRecategorizedSelector: element from: oldCategory to: newCategory. ].! ! !ClassOrganizer methodsFor: 'private' stamp: 'NS 4/12/2004 20:56'! notifyOfChangedSelectorsOldDict: oldDictionaryOrNil newDict: newDictionaryOrNil | newCat | (oldDictionaryOrNil isNil and: [newDictionaryOrNil isNil]) ifTrue: [^ self]. oldDictionaryOrNil isNil ifTrue: [ newDictionaryOrNil keysAndValuesDo: [:el :cat | self notifyOfChangedSelector: el from: nil to: cat]. ^ self. ]. newDictionaryOrNil isNil ifTrue: [ oldDictionaryOrNil keysAndValuesDo: [:el :cat | self notifyOfChangedSelector: el from: cat to: nil]. ^ self. ]. oldDictionaryOrNil keysAndValuesDo: [:el :cat | newCat _ newDictionaryOrNil at: el. self notifyOfChangedSelector: el from: cat to: newCat. ].! ! !ClassOrganizer methodsFor: '*ob-standard-testing' stamp: 'cwp 9/18/2004 23:59'! isClassOrganizer ^ true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassOrganizer class instanceVariableNames: ''! TestCase subclass: #ClassRenameFixTest instanceVariableNames: 'previousChangeSet testsChangeSet newClassName originalName' classVariableNames: '' poolDictionaries: '' category: 'Tests-Bugs'! !ClassRenameFixTest methodsFor: 'Running' stamp: 'md 9/6/2005 18:31'! setUp previousChangeSet := ChangeSet current. testsChangeSet := ChangeSet new. ChangeSet newChanges: testsChangeSet. SystemChangeNotifier uniqueInstance notify: self ofSystemChangesOfItem: #class change: #Renamed using: #verifyRenameEvent:. super setUp! ! !ClassRenameFixTest methodsFor: 'Running' stamp: 'ar 9/27/2005 20:06'! tearDown self removeEverythingInSetFromSystem: testsChangeSet. ChangeSet newChanges: previousChangeSet. ChangeSet removeChangeSet: testsChangeSet. previousChangeSet := nil. testsChangeSet := nil. super tearDown.! ! !ClassRenameFixTest methodsFor: 'Running' stamp: 'cmm 8/7/2005 19:04'! verifyRenameEvent: aRenamedEvent | renamedClass | self assert: aRenamedEvent isRenamed. renamedClass := aRenamedEvent item. self assert: (Smalltalk classNamed: newClassName) name = newClassName. self assert: renamedClass name = newClassName! ! !ClassRenameFixTest methodsFor: 'Tests' stamp: 'md 9/6/2005 18:31'! renameClassUsing: aBlock | createdClass foundClasses | originalName := self newUniqueClassName. createdClass := Object subclass: originalName instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ClassRenameFix-GeneradClass'. newClassName := self newUniqueClassName. aBlock value: createdClass value: newClassName. self assert: (Smalltalk classNamed: originalName) isNil. self assert: (Smalltalk classNamed: newClassName) notNil. foundClasses := Smalltalk organization listAtCategoryNamed: 'ClassRenameFix-GeneradClass'. self assert: (foundClasses notEmpty). self assert: (foundClasses includes: newClassName). self assert: (createdClass name = newClassName).! ! !ClassRenameFixTest methodsFor: 'Tests' stamp: 'md 9/6/2005 18:30'! testRenameClassUsingClass "self run: #testRenameClassUsingClass" self renameClassUsing: [:class :newName | class rename: newName].! ! !ClassRenameFixTest methodsFor: 'Private' stamp: 'md 9/6/2005 18:30'! newUniqueClassName "Return a class name that is not used in the system." "self new newClassName" | baseName newName | baseName := 'AutoGeneratedClassForTestingSystemChanges'. 1 to: 9999 do: [:number | newName := baseName , number printString. (Smalltalk hasClassNamed: newName) ifFalse: [^newName asSymbol]]. ^self error: 'Can no longer find a new and unique class name for the SystemChangeTest !!'! ! !ClassRenameFixTest methodsFor: 'Private' stamp: 'md 9/6/2005 18:30'! removeEverythingInSetFromSystem: aChangeSet aChangeSet changedMessageList do: [:methodRef | methodRef actualClass removeSelector: methodRef methodSymbol]. aChangeSet changedClasses do: [:each | each isMeta ifFalse: [each removeFromSystemUnlogged]]! ! ObjectRepresentativeMorph subclass: #ClassRepresentativeMorph instanceVariableNames: 'classRepresented' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting'! TestCase subclass: #ClassTest instanceVariableNames: 'className' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! !ClassTest methodsFor: 'setup' stamp: 'Noury Bouraqadi 3/24/2006 20:42'! deleteClass | class | class := Smalltalk at: className ifAbsent: [^self]. class removeFromChanges; removeFromSystemUnlogged ! ! !ClassTest methodsFor: 'setup' stamp: 'Noury Bouraqadi 3/24/2006 20:42'! setUp className := #TUTU. self deleteClass. Object subclass: className instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Classes'! ! !ClassTest methodsFor: 'setup' stamp: 'Noury Bouraqadi 3/24/2006 20:42'! tearDown self deleteClass! ! !ClassTest methodsFor: 'testing' stamp: 'md 1/5/2004 14:59'! testAddInstVarName "self run: #testAddInstVarName" | tutu | tutu := Smalltalk at: #TUTU. tutu addInstVarName: 'x'. self assert: (tutu instVarNames = #('x')). tutu addInstVarName: 'y'. self assert: (tutu instVarNames = #('x' 'y')) ! ! !ClassTest methodsFor: 'testing' stamp: 'Noury Bouraqadi 3/24/2006 20:38'! testRenaming | oldName newName newMetaclassName class | oldName := className. newName := #RenamedTUTU. newMetaclassName := (newName, #' class') asSymbol. class := Smalltalk at: oldName. class class compile: 'dummyMeth'. class rename: newName. className := class name. "Important for tearDown" self assert: class name = newName. self assert: (ChangeSet current changedClassNames includes: newName). self assert: (ChangeSet current changedClassNames includes: newMetaclassName). ! ! !ClassTest methodsFor: 'testing - compiling' stamp: 'sd 6/5/2005 08:25'! testCompileAll self shouldnt: [ClassTest compileAll] raise: Error.! ! !ClassTest methodsFor: 'testing - classside' stamp: 'md 2/20/2006 23:04'! testRootsOfTheWorld self assert: Class rootsOfTheWorld size = 3. self assert: (Class rootsOfTheWorld allSatisfy: [:each | each superclass = nil]). ! ! TestCase subclass: #ClassTestCase instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Utilities'! !ClassTestCase commentStamp: 'brp 7/26/2003 16:57' prior: 0! This class is intended for unit tests of individual classes and their metaclasses. It provides methods to determine the coverage of the unit tests. Subclasses are expected to re-implement #classesToBeTested and #selectorsToBeIgnored. They should also implement to confirm that all methods have been tested. #testCoverage super testCoverage. ! !ClassTestCase methodsFor: 'private' stamp: 'md 1/28/2004 11:32'! categoriesForClass: aClass ^ aClass organization allMethodSelectors collect: [:each | aClass organization categoryOfElement: each]. ! ! !ClassTestCase methodsFor: 'private' stamp: 'md 1/28/2004 11:28'! targetClass |className| className := self class name asText copyFrom: 0 to: self class name size - 4. ^ Smalltalk at: (className asString asSymbol). ! ! !ClassTestCase methodsFor: 'coverage' stamp: 'brp 7/27/2003 12:39'! classToBeTested self subclassResponsibility! ! !ClassTestCase methodsFor: 'coverage' stamp: 'brp 7/26/2003 16:35'! selectorsNotTested ^ self selectorsToBeTested difference: self selectorsTested. ! ! !ClassTestCase methodsFor: 'coverage' stamp: 'brp 7/26/2003 17:22'! selectorsToBeIgnored ^ #(#DoIt #DoItIn:)! ! !ClassTestCase methodsFor: 'coverage' stamp: 'brp 7/27/2003 12:40'! selectorsToBeTested ^ ( { self classToBeTested. self classToBeTested class } gather: [:c | c selectors]) difference: self selectorsToBeIgnored! ! !ClassTestCase methodsFor: 'tests' stamp: 'md 3/26/2003 17:39'! testClassComment self shouldnt: [self targetClass organization hasNoComment].! ! !ClassTestCase methodsFor: 'tests' stamp: 'brp 12/14/2003 15:51'! testCoverage | untested | self class mustTestCoverage ifTrue: [ untested := self selectorsNotTested. self assert: untested isEmpty description: untested size asString, ' selectors are not covered' ]! ! !ClassTestCase methodsFor: 'tests' stamp: 'md 3/25/2003 23:07'! testNew self shouldnt: [self targetClass new] raise: Error.! ! !ClassTestCase methodsFor: 'tests' stamp: 'md 3/26/2003 17:24'! testUnCategorizedMethods | categories slips | categories := self categoriesForClass: self targetClass. slips := categories select: [:each | each = #'as yet unclassified']. self should: [slips isEmpty]. ! ! !ClassTestCase methodsFor: 'Coverage' stamp: 'apb 4/15/2006 11:50'! selectorsTested | literals | literals := Set new. self class selectorsAndMethodsDo: [ :s :m | (s beginsWith: 'test') ifTrue: [ literals addAll: (m messages)] ]. ^ literals asSortedArray! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassTestCase class instanceVariableNames: ''! !ClassTestCase class methodsFor: 'Testing' stamp: 'md 2/22/2006 14:21'! isAbstract "Override to true if a TestCase subclass is Abstract and should not have TestCase instances built from it" ^self name = #ClassTestCase ! ! !ClassTestCase class methodsFor: 'Testing' stamp: 'brp 12/14/2003 15:50'! mustTestCoverage ^ false! ! TraitDescription subclass: #ClassTrait uses: TTransformationCompatibility + TApplyingOnClassSide instanceVariableNames: 'baseTrait' classVariableNames: '' poolDictionaries: '' category: 'Traits-Kernel'! !ClassTrait commentStamp: '' prior: 0! While every class has an associated metaclass, a trait can have an associated classtrait, an instance of me. To preserve metaclass compatibility, the associated classtrait (if there is one) is automatically applied to the metaclass, whenever a trait is applied to a class. Consequently, a trait with an associated classtrait can only be applied to classes, whereas a trait without a classtrait can be applied to both classes and metaclasses.! !ClassTrait methodsFor: 'accessing' stamp: 'al 4/21/2004 09:38'! name ^self baseTrait name , ' classTrait'! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:38'! baseTrait ^baseTrait! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:48'! baseTrait: aTrait self assert: aTrait isBaseTrait. baseTrait _ aTrait ! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:38'! classTrait ^self! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 4/20/2004 09:44'! classTrait: aClassTrait self error: 'Trait is already a class trait!!' ! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:41'! hasClassTrait ^false! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:48'! isBaseTrait ^false! ! !ClassTrait methodsFor: 'accessing parallel hierarchy' stamp: 'al 3/16/2004 10:48'! isClassTrait ^true! ! !ClassTrait methodsFor: 'compiling' stamp: 'al 4/7/2004 14:54'! compile: text classified: category withStamp: changeStamp notifying: requestor logSource: logSource | classSideUsersOfBaseTrait message | classSideUsersOfBaseTrait _ self baseTrait users select: [:each | each isClassSide]. classSideUsersOfBaseTrait isEmpty ifFalse: [ message _ String streamContents: [:stream | stream nextPutAll: 'The instance side of this trait is used on '; cr. classSideUsersOfBaseTrait do: [:each | stream nextPutAll: each name] separatedBy: [ stream nextPutAll: ', ']. stream cr; nextPutAll: ' You can not add methods to the class side of this trait!!']. ^TraitException signal: message]. ^super compile: text classified: category withStamp: changeStamp notifying: requestor logSource: logSource! ! !ClassTrait methodsFor: 'composition' stamp: 'al 7/18/2004 14:02'! uses: aTraitCompositionOrArray | copyOfOldTrait newComposition | copyOfOldTrait _ self copy. newComposition _ aTraitCompositionOrArray asTraitComposition. self assertConsistantCompositionsForNew: newComposition. self setTraitComposition: newComposition. SystemChangeNotifier uniqueInstance traitDefinitionChangedFrom: copyOfOldTrait to: self.! ! !ClassTrait methodsFor: 'copying' stamp: 'dvf 8/30/2005 16:51'! copy "Make a copy of the receiver. Share the reference to the base trait." ^(self class new) baseTrait: self baseTrait; initializeFrom: self; yourself! ! !ClassTrait methodsFor: 'fileIn/Out' stamp: 'al 3/26/2006 21:31'! definitionST80 ^String streamContents: [:stream | stream nextPutAll: self name; crtab; nextPutAll: 'uses: '; nextPutAll: self traitCompositionString]! ! !ClassTrait methodsFor: 'initialize' stamp: 'al 7/18/2004 12:11'! baseClass: aTrait traitComposition: aComposition methodDict: aMethodDict localSelectors: aSet organization: aClassOrganization self baseTrait: aTrait. self traitComposition: aComposition methodDict: aMethodDict localSelectors: aSet organization: aClassOrganization ! ! !ClassTrait methodsFor: 'initialize' stamp: 'dvf 8/30/2005 16:48'! initializeFrom: anotherClassTrait traitComposition _ self traitComposition copyTraitExpression. methodDict _ self methodDict copy. localSelectors _ self localSelectors copy. organization _ self organization copy.! ! !ClassTrait methodsFor: 'initialize' stamp: 'al 3/24/2004 20:37'! initializeWithBaseTrait: aTrait self baseTrait: aTrait. self noteNewBaseTraitCompositionApplied: aTrait traitComposition. aTrait users do: [:each | self addUser: each classSide]. ! ! !ClassTrait methodsFor: 'initialize' stamp: 'al 7/17/2004 22:56'! traitComposition: aComposition methodDict: aMethodDict localSelectors: aSet organization: aClassOrganization "Used by copy of Trait" localSelectors _ aSet. methodDict _ aMethodDict. traitComposition _ aComposition. self organization: aClassOrganization! ! !ClassTrait methodsFor: '*monticello' stamp: 'al 3/26/2006 21:31'! asMCDefinition ^MCClassTraitDefinition baseTraitName: self baseTrait name classTraitComposition: self traitCompositionString ! ! !ClassTrait methodsFor: '*ob-standard-converting' stamp: 'lr 3/9/2006 21:09'! asClassSideNode ^ OBMetaclassNode on: self baseTrait! ! !ClassTrait methodsFor: '*ob-standard-converting' stamp: 'lr 3/9/2006 21:02'! asNode ^ OBMetaclassNode on: self! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassTrait class uses: TTransformationCompatibility classTrait + TApplyingOnClassSide classTrait instanceVariableNames: ''! !ClassTrait class methodsFor: 'instance creation' stamp: 'al 3/23/2004 19:41'! for: aTrait ^self new initializeWithBaseTrait: aTrait; yourself! ! TraitsTestCase subclass: #ClassTraitTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Traits-Tests'! !ClassTraitTest methodsFor: 'testing' stamp: 'al 3/26/2006 12:15'! testChanges "Test the most important features to ensure that general functionality of class traits are working." "self run: #testChanges" | classTrait | classTrait := self t1 classTrait. classTrait compile: 'm1ClassSide ^17' classified: 'mycategory'. "local selectors" self assert: (classTrait includesLocalSelector: #m1ClassSide). self deny: (classTrait includesLocalSelector: #otherSelector). "propagation" self assert: (self t5 classSide methodDict includesKey: #m1ClassSide). self assert: (self c2 class methodDict includesKey: #m1ClassSide). self shouldnt: [self c2 m1ClassSide] raise: Error. self assert: self c2 m1ClassSide = 17. "category" self assert: (self c2 class organization categoryOfElement: #m1ClassSide) = 'mycategory'. "conflicts" self t2 classSide compile: 'm1ClassSide' classified: 'mycategory'. self assert: (self c2 class methodDict includesKey: #m1ClassSide). self deny: (self c2 class includesLocalSelector: #m1ClassSide). self should: [self c2 m1ClassSide] raise: Error. "conflict category" self assert: (self c2 class organization categoryOfElement: #m1ClassSide) = #mycategory! ! !ClassTraitTest methodsFor: 'testing' stamp: 'dvf 8/26/2005 14:32'! testConflictsAliasesAndExclusions "conflict" self t1 classTrait compile: 'm2ClassSide: x ^99' classified: 'mycategory'. self assert: (self t1 classTrait includesLocalSelector: #m2ClassSide:). self assert: (self t5 classTrait >> #m2ClassSide:) isConflict. self assert: (self c2 class >> #m2ClassSide:) isConflict. "exclusion and alias" self assert: self t5 classSide traitComposition asString = 'T1 classTrait + T2 classTrait'. self t5 classSide setTraitCompositionFrom: (self t1 classTrait @ { (#m2ClassSideAlias1: -> #m2ClassSide:) } + self t2 classTrait) @ { (#m2ClassSideAlias2: -> #m2ClassSide:) } - { #m2ClassSide: }. self deny: (self t5 classTrait >> #m2ClassSide:) isConflict. self deny: (self c2 class >> #m2ClassSide:) isConflict. self assert: (self c2 m2ClassSideAlias1: 13) = 99. self assert: (self c2 m2ClassSideAlias2: 13) = 13! ! !ClassTraitTest methodsFor: 'testing' stamp: 'dvf 8/30/2005 16:17'! testInitialization "self run: #testInitialization" | classTrait | classTrait := self t1 classTrait. self assert: self t1 hasClassTrait. self assert: self t1 classTrait == classTrait. self assert: classTrait isClassTrait. self assert: classTrait classSide == classTrait. self deny: classTrait isBaseTrait. self assert: classTrait baseTrait == self t1. "assert classtrait methods are propagated to users when setting traitComposition" self assert: self t4 hasClassTrait. self assert: self t5 hasClassTrait. self assert: (self t2 classSide includesLocalSelector: #m2ClassSide:). self assert: (self t4 classSide methodDict includesKey: #m2ClassSide:). self assert: (self t5 classSide methodDict includesKey: #m2ClassSide:). self assert: (self c2 m2ClassSide: 17) = 17! ! !ClassTraitTest methodsFor: 'testing' stamp: 'al 3/26/2006 12:06'! testUsers self assert: self t2 classSide users size = 3. self assert: (self t2 classSide users includesAllOf: { (self t4 classTrait). (self t5 classTrait). (self t6 classTrait) }). self assert: self t5 classSide users size = 1. self assert: self t5 classSide users anyOne = self c2 class. self c2 setTraitCompositionFrom: self t1 + self t5. self assert: self t5 classSide users size = 1. self assert: self t5 classSide users anyOne = self c2 class. self c2 setTraitComposition: self t2 asTraitComposition. self assert: self t5 classSide users isEmpty! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClassTraitTest class instanceVariableNames: ''! 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: 'stephaneducasse 2/3/2006 22:22'! accent: aString accent := aString! ! !Clause methodsFor: 'accessing' stamp: 'len 12/13/1999 02:32'! accept: anObject anObject clause: self! ! !Clause methodsFor: 'accessing' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! phrases: aCollection phrases := aCollection! ! !Clause methodsFor: 'accessing' stamp: 'len 12/12/1999 22:22'! string ^ string! ! !Clause methodsFor: 'accessing' stamp: 'stephaneducasse 2/3/2006 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: '- ']! ! RectangleMorph subclass: #ClickExerciser instanceVariableNames: 'buttons' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Demo'! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! allSelectors ^ #( #click: #doubleClick: #doubleClickTimeout: #startDrag:) ! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! alternateBorderColor "answer the alternate color/fill style for the receiver" ^ Color yellow! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/25/2005 22:38'! alternateColor "answer the alternate color/fill style for the receiver" ^ Color cyan! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! balloonText ^ 'Double-click on me to change my color; single-click on me to change border color; hold mouse down within me and then move it to grow or shrink. When I time out my border changes width. Choose which of the above will work by selecting the boxes in the middle. See the boxes balloons.' translated! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! defaultBorderColor "answer the alternate color/fill style for the receiver" ^ Color black! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! defaultColor "answer the default color/fill style for the receiver" ^ Color red! ! !ClickExerciser methodsFor: 'accessing' stamp: 'wiz 8/23/2005 19:31'! selectors ^ self allSelectors with: buttons collect: [ :s :b | b isOn ifTrue: [ s ] ifFalse: [nil ] ] .! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! click: evt self showBalloon: 'click' hand: evt hand. self borderColor: (self borderColor = self defaultBorderColor ifTrue: [self alternateBorderColor] ifFalse: [self defaultBorderColor]) ! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! doubleClickTimeout: evt self showBalloon: 'ClickTimeout' hand: evt hand. self borderWidth: self borderWidth \\ 11 + 2! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! doubleClick: evt self showBalloon: 'doubleClick' hand: evt hand. self color: ((color = self alternateColor ) ifTrue: [self defaultColor] ifFalse: [self alternateColor]) ! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! handlesMouseDown: evt ^ true! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! 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" Preferences disable: #NewClickTest . evt hand waitForClicksOrDrag: self event: evt selectors: self selectors threshold: 10 ! ! !ClickExerciser methodsFor: 'event handling' stamp: 'wiz 8/23/2005 19:31'! startDrag: evt "We'll get a mouseDown first, some mouseMoves, and a mouseUp event last" | height width both | "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]." self showBalloon: 'drag (mouse down)' hand: evt hand. width := (self defaultSide max: self extent x) min: self alternateSide. height := (self defaultSide max: self extent y) min: self alternateSide. both := self defaultSide + self alternateSide. self extent: (((color = (self defaultColor) ifTrue: [ (both - height) @ width ] ifFalse: [ height @ (both - width) ]) max: self defaultSide asPoint) min: self alternateSide asPoint ) ! ! !ClickExerciser methodsFor: 'initialization' stamp: 'wiz 8/23/2005 19:31'! alternateSide "initial extent is square. We return an alternate height/width for drag to manipulate." ^ (self defaultSide asFloat * 1.618) rounded . ! ! !ClickExerciser methodsFor: 'initialization' stamp: 'wiz 8/23/2005 19:31'! defaultSide "initial extent is square. We return the default height/width." ^ 100 . ! ! !ClickExerciser methodsFor: 'initialization' stamp: 'wiz 8/25/2005 23:05'! initButtons | aButton positions | aButton := ThreePhaseButtonMorph checkBox. positions := ((0@0) rect: aButton extent negated) corners + self center . buttons := positions collect: [ :p | ThreePhaseButtonMorph checkBox position: p; state: #on ] . buttons with: self allSelectors collect: [ :b :s | b setBalloonText: s asString maxLineLength: s size ] . self removeAllMorphs . self addAllMorphs: buttons . ! ! !ClickExerciser methodsFor: 'initialization' stamp: 'wiz 8/23/2005 19:31'! initialize super initialize. self extent: self defaultSide asPoint. self initButtons .! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClickExerciser class instanceVariableNames: ''! !ClickExerciser class methodsFor: 'parts bin' stamp: 'wiz 8/23/2005 19:31'! descriptionForPartsBin "WizClickExample descriptionForPartsBin" ^ self partName: 'ExersizeClick' categories: #('Demo') documentation: 'An exersizer for double-click in moprhic'! ! Object subclass: #Clipboard instanceVariableNames: 'contents recent interpreter' classVariableNames: 'Default' poolDictionaries: '' category: 'ST80-Kernel-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: '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: 'yo 8/11/2003 19:07'! clearInterpreter interpreter _ nil. ! ! !Clipboard methodsFor: 'accessing' stamp: 'yo 8/11/2003 19:04'! 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." | string decodedString | string _ self primitiveClipboardText. (string isEmpty or: [string = contents asString]) ifTrue: [^ contents]. decodedString _ self interpreter fromSystemClipboard: string. ^ decodedString = contents asString ifTrue: [contents] ifFalse: [decodedString asText]. ! ! !Clipboard methodsFor: 'accessing' stamp: 'yo 8/11/2003 19:12'! clipboardText: text | string | string _ text asString. self noteRecentClipping: text asText. contents _ text asText. string _ self interpreter toSystemClipboard: string. self primitiveClipboardText: string. ! ! !Clipboard methodsFor: 'accessing' stamp: 'yo 8/11/2003 18:23'! interpreter interpreter ifNil: [self setInterpreter]. ^ interpreter. ! ! !Clipboard methodsFor: 'accessing' stamp: 'mir 7/20/2004 15:44'! setInterpreter interpreter _ LanguageEnvironment defaultClipboardInterpreter. interpreter ifNil: [ "Should never be reached, but just in case." interpreter _ NoConversionClipboardInterpreter new]. ! ! !Clipboard methodsFor: 'initialize' stamp: 'ar 1/15/2001 18:34'! initialize contents _ '' asText. recent _ OrderedCollection new.! ! !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: '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.! ! !Clipboard class methodsFor: 'class initialization' stamp: 'yo 8/11/2003 22:43'! clearInterpreters self allInstances do: [:each | each clearInterpreter]. ! ! !Clipboard class methodsFor: 'class initialization' stamp: 'yo 12/29/2003 01:03'! startUp self clearInterpreters. ! ! Object subclass: #ClipboardInterpreter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !ClipboardInterpreter methodsFor: 'as yet unclassified' stamp: 'yo 8/11/2003 19:03'! fromSystemClipboard: aString self subclassResponsibility. ! ! !ClipboardInterpreter methodsFor: 'as yet unclassified' stamp: 'yo 8/11/2003 19:03'! toSystemClipboard: aString self subclassResponsibility. ! ! TextMorph subclass: #ClipboardMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Widgets'! !ClipboardMorph commentStamp: '' prior: 0! A morph that always displays the current contents of the text clipboard.! !ClipboardMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:33'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color r: 1.0 g: 0.355 b: 0.452! ! !ClipboardMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 6! ! !ClipboardMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color blue! ! !ClipboardMorph methodsFor: 'parts bin' stamp: 'dgd 2/14/2003 22:09'! initializeToStandAlone super initializeToStandAlone. "" self initialize. "" self extent: 200 @ 100. self backgroundColor: (Color r: 0.484 g: 1.0 b: 0.484). self setBalloonText: 'This shows the current contents of the text clipboard'. self newContents: Clipboard clipboardText! ! !ClipboardMorph methodsFor: 'stepping and presenter' stamp: 'sw 6/27/2001 14:15'! step self newContents: Clipboard clipboardText! ! !ClipboardMorph methodsFor: 'testing' stamp: 'sw 6/27/2001 14:18'! stepTime "Answer the interval between steps -- in this case a leisurely 1 seconds" ^ 1000! ! !ClipboardMorph methodsFor: 'testing' stamp: 'sw 6/27/2001 13:40'! wantsSteps ^ true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClipboardMorph class instanceVariableNames: ''! !ClipboardMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 01:21'! descriptionForPartsBin ^ self partName: 'Clipboard' categories: #('Useful') documentation: 'This object will always show whatever is on the text clipboard'! ! 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: 'accessing' stamp: 'ls 3/25/2000 22:56'! clipRect ^clipRect! ! !ClippingCanvas methodsFor: 'accessing' 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: 'accessing' stamp: 'ls 3/20/2000 21:17'! form ^canvas form! ! !ClippingCanvas methodsFor: 'accessing' stamp: 'ls 3/20/2000 21:15'! shadowColor ^canvas shadowColor! ! !ClippingCanvas methodsFor: 'initialization' stamp: 'ls 3/20/2000 20:44'! canvas: aCanvas clipRect: aRectangle canvas := aCanvas. clipRect := aRectangle.! ! !ClippingCanvas methodsFor: 'testing' stamp: 'ls 3/20/2000 21:17'! isBalloonCanvas ^canvas isBalloonCanvas! ! !ClippingCanvas methodsFor: 'testing' stamp: 'ls 3/20/2000 21:18'! isShadowDrawing ^canvas isShadowDrawing! ! !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 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 show24hr' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Demo'! !ClockMorph methodsFor: '24hr' stamp: 'fc 2/8/2004 11:38'! show24hr: aBoolean show24hr _ aBoolean! ! !ClockMorph methodsFor: '24hr' stamp: 'fc 2/8/2004 11:39'! toggleShowing24hr show24hr _ (show24hr == true) not ! ! !ClockMorph methodsFor: 'initialization' stamp: 'fc 2/8/2004 11:33'! initialize "initialize the state of the receiver" super initialize. "" showSeconds _ true. show24hr _ false. self step! ! !ClockMorph methodsFor: 'menu' stamp: 'fc 2/8/2004 11:57'! 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 translated target: self action: #toggleShowingSeconds. item _ show24hr == true ifTrue: ['display Am/Pm'] ifFalse: ['display 24 hour']. aCustomMenu add: item translated target: self action: #toggleShowing24hr. ! ! !ClockMorph methodsFor: 'parts bin' stamp: 'sw 7/12/2001 17:41'! initializeToStandAlone super initializeToStandAlone. showSeconds _ true. self step! ! !ClockMorph methodsFor: 'seconds' stamp: 'sw 2/17/1999 14:39'! showSeconds: aBoolean showSeconds _ aBoolean! ! !ClockMorph methodsFor: 'seconds' stamp: 'sw 2/17/1999 14:53'! toggleShowingSeconds showSeconds _ (showSeconds == true) not ! ! !ClockMorph methodsFor: 'stepping and presenter' stamp: 'fc 2/8/2004 11:40'! step | time | super step. time _ String streamContents: [:aStrm | Time now print24: (show24hr == true) showSeconds: (showSeconds == true) on: aStrm]. self contents: time ! ! !ClockMorph methodsFor: 'testing'! stepTime "Answer the desired time between steps in milliseconds." ^ 1000! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClockMorph class instanceVariableNames: ''! !ClockMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 13:00'! initialize self registerInFlapsRegistry. ! ! !ClockMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 13:02'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(ClockMorph authoringPrototype 'Clock' 'A simple digital clock') forFlapNamed: 'Supplies'. cl registerQuad: #(ClockMorph authoringPrototype 'Clock' 'A simple digital clock') forFlapNamed: 'PlugIn Supplies'.]! ! !ClockMorph class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 12:33'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !ClockMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 01:22'! descriptionForPartsBin ^ self partName: 'Clock' categories: #('Useful') documentation: 'A digital clock'! ! !ClockMorph class methodsFor: 'scripting' stamp: 'sw 10/16/1998 15:36'! authoringPrototype ^ super authoringPrototype contents: Time now printString! ! Object variableSubclass: #ClosureEnvironment instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Contexts'! !ClosureEnvironment commentStamp: 'ajh 6/24/2004 03:33' prior: 0! An environment is a collection of temporary variable values that have escaped the original method context and placed in this environment because blocks existed in the method that reference these variables (and blocks may out live their creating context). Nested blocks create nested environments when temp vars are introduced at multiple levels and referenced at lower levels. So each environment has a parent environment in its first slot. The top environment has the original receiver in it first slot (if referenced by an inner block). A block consists of its outer environment and a method to execute while the outer environment is in the receiver position. A block that remote returns from its home context holds the home environment in its outer environment. The remote return unwinds the call stack to the context that created the home context. ! !ClosureEnvironment methodsFor: 'as yet unclassified' stamp: 'ajh 6/24/2004 03:54'! = other self class == other class ifFalse: [^ false]. self size = other size ifFalse: [^ false]. 1 to: self size do: [:i | (self at: i) = (other at: i) ifFalse: [^ false]. ]. ^ true! ! !ClosureEnvironment methodsFor: 'as yet unclassified' stamp: 'ajh 6/24/2004 03:56'! 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! ! !ClosureEnvironment methodsFor: 'as yet unclassified' stamp: 'ajh 6/29/2004 14:33'! return: value "Find thisContext sender that is owner of self and return from it" | home | home _ thisContext findContextSuchThat: [:ctxt | ctxt myEnv == self]. home return: value! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ClosureEnvironment class instanceVariableNames: ''! StringHolder subclass: #CodeHolder instanceVariableNames: 'currentCompiledMethod contentsSymbol' classVariableNames: '' poolDictionaries: '' category: 'Tools-Base'! !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: 'md 2/24/2006 15:25'! 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: #annotation: readSelection: nil menu: #annotationPaneMenu:shifted:. aTextMorph askBeforeDiscardingEdits: true; acceptOnCR: true; borderWidth: 0; hideScrollBarsIndefinitely. divider := BorderedSubpaneDividerMorph forBottomEdge. divider extent: 4@4; color: Color transparent; borderColor: #raised; borderWidth: 2. delta := self defaultAnnotationPaneHeight. window addMorph: aTextMorph fullFrame: (LayoutFrame fractions: fractions offsets: (0@verticalOffset corner: 0@(verticalOffset + delta - 2))). window addMorph: divider fullFrame: (LayoutFrame fractions: fractions offsets: (0@(verticalOffset + delta - 2) corner: 0@(verticalOffset + delta))). ^ verticalOffset + delta! ! !CodeHolder methodsFor: 'annotation' stamp: 'sd 11/20/2005 21:27'! addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream "add an annotation detailing the prior versions count" | versionsCount | versionsCount := VersionsBrowser versionCountForSelector: aSelector class: aClass. aStream nextPutAll: ((versionsCount > 1 ifTrue: [versionsCount == 2 ifTrue: ['1 prior version'] ifFalse: [versionsCount printString, ' prior versions']] ifFalse: ['no prior versions']), self annotationSeparator)! ! !CodeHolder methodsFor: 'annotation' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! annotationForClassCommentFor: aClass "Provide a line of content for an annotation pane, given that the receiver is pointing at the clas comment of the given class." | aStamp nonMeta | aStamp := (nonMeta := aClass theNonMetaClass) organization commentStamp. ^ aStamp ifNil: [nonMeta name, ' has no class comment'] ifNotNil: ['class comment for ', nonMeta name, (aStamp = '' ifFalse: [' - ', aStamp] ifTrue: [''])]! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 8/26/2002 10:19'! annotationForClassDefinitionFor: aClass "Provide a line of content for an annotation pane, given that the receiver is pointing at the class definition of the given class." ^ 'Class definition for ', aClass name! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 8/26/2002 10:19'! annotationForHierarchyFor: aClass "Provide a line of content for an annotation pane, given that the receiver is pointing at the hierarchy of the given class." ^ 'Hierarchy for ', aClass name! ! !CodeHolder methodsFor: 'annotation' stamp: 'sd 11/20/2005 21:27'! annotationForSelector: aSelector ofClass: aClass "Provide a line of content for an annotation pane, representing information about the given selector and class" | stamp sendersCount implementorsCount aCategory separator aString aList aComment aStream requestList | aSelector == #Comment ifTrue: [^ self annotationForClassCommentFor: aClass]. aSelector == #Definition ifTrue: [^ self annotationForClassDefinitionFor: aClass]. aSelector == #Hierarchy ifTrue: [^ self annotationForHierarchyFor: aClass]. aStream := ReadWriteStream on: ''. requestList := self annotationRequests. separator := requestList size > 1 ifTrue: [self annotationSeparator] ifFalse: ['']. requestList do: [:aRequest | aRequest == #firstComment ifTrue: [aComment := aClass firstCommentAt: aSelector. aComment isEmptyOrNil ifFalse: [aStream nextPutAll: aComment , separator]]. aRequest == #masterComment ifTrue: [aComment := aClass supermostPrecodeCommentFor: aSelector. aComment isEmptyOrNil ifFalse: [aStream nextPutAll: aComment , separator]]. aRequest == #documentation ifTrue: [aComment := aClass precodeCommentOrInheritedCommentFor: aSelector. aComment isEmptyOrNil ifFalse: [aStream nextPutAll: aComment , separator]]. aRequest == #timeStamp ifTrue: [stamp := self timeStamp. aStream 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" aStream nextPutAll: aCategory , separator]]. aRequest == #sendersCount ifTrue: [sendersCount := (self systemNavigation allCallsOn: aSelector) size. sendersCount := sendersCount == 1 ifTrue: ['1 sender'] ifFalse: [sendersCount printString , ' senders']. aStream nextPutAll: sendersCount , separator]. aRequest == #implementorsCount ifTrue: [implementorsCount := self systemNavigation numberOfImplementorsOf: aSelector. implementorsCount := implementorsCount == 1 ifTrue: ['1 implementor'] ifFalse: [implementorsCount printString , ' implementors']. aStream nextPutAll: implementorsCount , separator]. aRequest == #priorVersionsCount ifTrue: [self addPriorVersionsCountForSelector: aSelector ofClass: aClass to: aStream]. aRequest == #priorTimeStamp ifTrue: [stamp := VersionsBrowser timeStampFor: aSelector class: aClass reverseOrdinal: 2. stamp ifNotNil: [aStream nextPutAll: 'prior time stamp: ' , stamp , separator]]. aRequest == #recentChangeSet ifTrue: [aString := ChangeSorter mostRecentChangeSetWithChangeForClass: aClass selector: aSelector. aString size > 0 ifTrue: [aStream nextPutAll: aString , separator]]. aRequest == #allChangeSets ifTrue: [aList := ChangeSorter allChangeSetsWithClass: aClass selector: aSelector. aList size > 0 ifTrue: [aList size = 1 ifTrue: [aStream nextPutAll: 'only in change set '] ifFalse: [aStream nextPutAll: 'in change sets: ']. aList do: [:aChangeSet | aStream nextPutAll: aChangeSet name , ' ']] ifFalse: [aStream nextPutAll: 'in no change set']. aStream nextPutAll: separator]]. ^ aStream contents! ! !CodeHolder methodsFor: 'annotation' stamp: 'RAA 1/13/2001 07:20'! annotationPaneMenu: aMenu shifted: shifted ^ aMenu labels: 'change pane size' lines: #() selections: #(toggleAnnotationPaneSize)! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 9/27/1999 14:13'! annotationRequests ^ Preferences defaultAnnotationRequests! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 2/22/2001 10:02'! annotationSeparator "Answer the separator to be used between annotations" ^ ' · '! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 9/28/2001 08:43'! defaultAnnotationPaneHeight "Answer the receiver's preferred default height for new annotation panes." ^ Preferences parameterAt: #defaultAnnotationPaneHeight ifAbsentPut: [25]! ! !CodeHolder methodsFor: 'annotation' stamp: 'sw 9/28/2001 08:44'! defaultButtonPaneHeight "Answer the user's preferred default height for new button panes." ^ Preferences parameterAt: #defaultButtonPaneHeight ifAbsentPut: [25]! ! !CodeHolder methodsFor: 'categories' stamp: 'sd 11/8/2005 22:06'! categoryFromUserWithPrompt: aPrompt for: aClass "self new categoryFromUserWithPrompt: 'testing' for: SystemDictionary" | labels myCategories reject lines cats newName menuIndex | labels := OrderedCollection with: 'new...'. labels addAll: (myCategories := aClass organization categories asSortedCollection: [:a :b | a asLowercase < b asLowercase]). reject := myCategories asSet. reject add: ClassOrganizer nullCategory; add: ClassOrganizer default. lines := OrderedCollection with: 1 with: (myCategories size + 1). aClass allSuperclasses do: [:cls | cats := cls organization categories reject: [:cat | reject includes: cat]. cats isEmpty ifFalse: [lines add: labels size. labels addAll: (cats asSortedCollection: [:a :b | a asLowercase < b asLowercase]). reject addAll: cats]]. newName := (labels size = 1 or: [menuIndex := (UIManager default chooseFrom: labels lines: lines title: aPrompt). menuIndex = 0 ifTrue: [^ nil]. menuIndex = 1]) ifTrue: [UIManager default request: 'Please type new category name' initialAnswer: 'category name'] ifFalse: [labels at: menuIndex]. ^ newName ifNotNil: [newName asSymbol]! ! !CodeHolder methodsFor: 'categories' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:26'! 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: [(self letUserReclassify: aSelector in: aClass) ifTrue: ["ChangeSet current reorganizeClass: aClass." "Decided on further review that the above, when present, could cause more unexpected harm than good" self methodCategoryChanged]]]! ! !CodeHolder methodsFor: 'categories' stamp: 'sd 11/20/2005 21:27'! letUserReclassify: anElement in: aClass "Put up a list of categories and solicit one from the user. Answer true if user indeed made a change, else false" | currentCat newCat | currentCat := aClass organization categoryOfElement: anElement. newCat := self categoryFromUserWithPrompt: 'choose category (currently "', currentCat, '")' for: aClass. (newCat ~~ nil and: [newCat ~= currentCat]) ifTrue: [aClass organization classify: anElement under: newCat suppressIfDefault: false. ^ true] ifFalse: [^ false]! ! !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: 'categories & search pane' stamp: 'sd 11/20/2005 21:27'! listPaneWithSelector: aSelector "If, among my window's paneMorphs, there is a list pane defined with aSelector as its retriever, answer it, else answer nil" | aWindow | ^ (aWindow := self containingWindow) ifNotNil: [aWindow paneMorphSatisfying: [:aMorph | (aMorph isKindOf: PluggableListMorph) and: [aMorph getListSelector == aSelector]]]! ! !CodeHolder methodsFor: 'categories & search pane' stamp: 'sd 11/20/2005 21:27'! newSearchPane "Answer a new search pane for the receiver" | aTextMorph | aTextMorph := PluggableTextMorph on: self text: #lastSearchString accept: #lastSearchString: readSelection: nil menu: nil. aTextMorph setProperty: #alwaysAccept toValue: true. aTextMorph askBeforeDiscardingEdits: false. aTextMorph acceptOnCR: true. aTextMorph setBalloonText: 'Type here and hit ENTER, and all methods whose selectors match what you typed will appear in the list pane below.'. ^ aTextMorph! ! !CodeHolder methodsFor: 'categories & search pane' stamp: 'sw 3/7/2001 12:22'! searchPane "Answer the search pane associated with the receiver in its window, or nil if none. Morphic only" ^ self textPaneWithSelector: #lastSearchString! ! !CodeHolder methodsFor: 'categories & search pane' stamp: 'sd 11/20/2005 21:27'! textPaneWithSelector: aSelector "If, among my window's paneMorphs, there is a text pane defined with aSelector as its retriever, answer it, else answer nil" | aWindow | ^ (aWindow := self containingWindow) ifNotNil: [aWindow paneMorphSatisfying: [:aMorph | (aMorph isKindOf: PluggableTextMorph) and: [aMorph getTextSelector == aSelector]]]! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 7/30/2001 16:31'! abbreviatedWordingFor: aButtonSelector "Answer the abbreviated form of wording, from a static table which you're welcome to edit. Answer nil if there is no entry -- in which case the long firm will be used on the corresponding browser button." #( (browseMethodFull 'browse') (browseSendersOfMessages 'senders') (browseMessages 'impl') (browseVersions 'vers') (methodHierarchy 'inher') (classHierarchy 'hier') (browseInstVarRefs 'iVar') (browseClassVarRefs 'cVar') (offerMenu 'menu')) do: [:pair | pair first == aButtonSelector ifTrue: [^ pair second]]. ^ nil! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 5/23/2003 14:35'! adoptMessageInCurrentChangeset "Add the receiver's method to the current change set if not already there" self setClassAndSelectorIn: [:cl :sel | cl ifNotNil: [ChangeSet current adoptSelector: sel forClass: cl. self changed: #annotation]] ! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 11/20/2005 21:27'! browseImplementors "Create and schedule a message set browser on all implementors of the currently selected message selector. Do nothing if no message is selected." | aMessageName | (aMessageName := self selectedMessageName) ifNotNil: [self systemNavigation browseAllImplementorsOf: aMessageName]! ! !CodeHolder methodsFor: 'commands' stamp: 'nk 6/26/2003 21:43'! browseSenders "Create and schedule a message set browser on all senders of the currently selected message selector. Of there is no message currently selected, offer a type-in" self sendQuery: #browseAllCallsOn: to: self systemNavigation! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 11/20/2005 21:27'! copyUpOrCopyDown "Used to copy down code from a superclass to a subclass or vice-versa in one easy step, if you know what you're doing. Prompt the user for which class to copy down or copy up to, then spawn a fresh browser for that class, with the existing code planted in it, and with the existing method category also established." | aClass aSelector allClasses implementors aMenu aColor | Smalltalk isMorphic ifFalse: [^ self inform: 'Sorry, for the moment you have to be in Morphic to use this feature.']. ((aClass := self selectedClassOrMetaClass) isNil or: [(aSelector := self selectedMessageName) == nil]) ifTrue: [^ Beeper beep]. allClasses := self systemNavigation hierarchyOfClassesSurrounding: aClass. implementors := self systemNavigation hierarchyOfImplementorsOf: aSelector forClass: aClass. aMenu := MenuMorph new defaultTarget: self. aMenu title: aClass name, '.', aSelector, ' Choose where to insert a copy of this method (blue = current, black = available, red = other implementors'. allClasses do: [:cl | aColor := cl == aClass ifTrue: [#blue] ifFalse: [(implementors includes: cl) ifTrue: [#red] ifFalse: [#black]]. (aColor == #red) ifFalse: [aMenu add: cl name selector: #spawnToClass: argument: cl] ifTrue: [aMenu add: cl name selector: #spawnToCollidingClass: argument: cl]. aMenu lastItem color: (Color colorFrom: aColor)]. aMenu popUpInWorld! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 5/18/2001 17:51'! offerMenu "Offer a menu to the user from the bar of tool buttons" self offerDurableMenuFrom: #messageListMenu:shifted: shifted: false! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 2/27/2001 12:14'! offerShiftedClassListMenu "Offer the shifted class-list menu." ^ self offerMenuFrom: #classListMenu:shifted: shifted: true! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 2/27/2001 12:15'! offerUnshiftedClassListMenu "Offer the shifted class-list menu." ^ self offerMenuFrom: #classListMenu:shifted: shifted: false! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 11/20/2005 21:27'! removeClass "Remove the selected class from the system, at interactive user request. Make certain the user really wants to do this, since it is not reversible. Answer true if removal actually happened." | message className classToRemove result | self okToChange ifFalse: [^ false]. classToRemove := self selectedClassOrMetaClass ifNil: [Beeper beep. ^ false]. classToRemove := classToRemove theNonMetaClass. className := classToRemove name. message := 'Are you certain that you want to REMOVE the class ', className, ' from the system?'. (result := self confirm: message) ifTrue: [classToRemove subclasses size > 0 ifTrue: [(self confirm: 'class has subclasses: ' , message) ifFalse: [^ false]]. classToRemove removeFromSystem. self changed: #classList. true]. ^ result! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 3/6/2001 15:18'! shiftedYellowButtonActivity "Offer the shifted selector-list menu" ^ self offerMenuFrom: #messageListMenu:shifted: shifted: true! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 11/20/2005 21:26'! 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. self suggestCategoryToSpawnedBrowser: newBrowser. Browser openBrowserView: (newBrowser openMessageCatEditString: aString) label: 'category "', aCategory, '" in ', newBrowser selectedClassOrMetaClassName]! ! !CodeHolder methodsFor: 'commands' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! 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: 'commands' stamp: 'sd 11/20/2005 21:27'! spawnToClass: aClass "Used to copy down code from a superclass to a subclass in one easy step, if you know what you're doing. Spawns a new message-category browser for the indicated class, populating it with the source code seen in the current tool." | aCategory newBrowser org | (aCategory := self categoryOfCurrentMethod) ifNil: [self buildClassBrowserEditString: self contents] ifNotNil: [((org := aClass organization) categories includes: aCategory) ifFalse: [org addCategory: aCategory]. newBrowser := Browser new setClass: aClass selector: nil. newBrowser selectMessageCategoryNamed: aCategory. Browser openBrowserView: (newBrowser openMessageCatEditString: self contents) label: 'category "', aCategory, '" in ', newBrowser selectedClassOrMetaClassName]! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 3/20/2001 15:11'! spawnToCollidingClass: aClass "Potentially used to copy down code from a superclass to a subclass in one easy step, in the case where the given class already has its own version of code, which would consequently be clobbered if the spawned code were accepted." self inform: 'That would be destructive of some pre-existing code already in that class for this selector. For the moment, we will not let you do this to yourself.'! ! !CodeHolder methodsFor: 'commands' stamp: 'sw 3/6/2001 15:19'! unshiftedYellowButtonActivity "Offer the unshifted shifted selector-list menu" ^ self offerMenuFrom: #messageListMenu:shifted: shifted: false! ! !CodeHolder methodsFor: 'construction' stamp: 'sd 11/20/2005 21:27'! 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: 'construction' stamp: 'sd 11/20/2005 21:27'! 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: 'construction' stamp: 'tween 8/27/2004 12:18'! buildMorphicCodePaneWith: editString "Construct the pane that shows the code. Respect the Preference for standardCodeFont." | codePane | codePane := MorphicTextEditor default on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. codePane font: Preferences standardCodeFont. editString ifNotNil: [codePane editString: editString. codePane hasUnacceptedEdits: true]. ^ codePane! ! !CodeHolder methodsFor: 'contents' stamp: 'sd 11/20/2005 21:27'! 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: 'di 10/1/2001 22:25'! contents "Answer the source code or documentation for the selected method" self showingByteCodes ifTrue: [^ self selectedBytecodes]. self showingDocumentation ifTrue: [^ self commentContents]. ^ self selectedMessage! ! !CodeHolder methodsFor: 'contents' stamp: 'rhi 12/3/2001 22:25'! contentsChanged super contentsChanged. self changed: #annotation! ! !CodeHolder methodsFor: 'contents' stamp: 'sd 11/20/2005 21:27'! 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 := Preferences browseWithPrettyPrint ifTrue: [Preferences colorWhenPrettyPrinting ifTrue: [#colorPrint] ifFalse: [#prettyPrint]] ifFalse: [#source]]! ! !CodeHolder methodsFor: 'contents' stamp: 'sd 11/20/2005 21:27'! 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: 'controls' stamp: 'gm 2/16/2003 20:37'! 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) isSystemWindow) ifFalse: [^nil]. (aPane := aWindow submorphNamed: 'buttonPane') ifNil: [^nil]. ^aPane submorphThat: [:m | (m isKindOf: PluggableButtonMorph) and: [m actionSelector == aSelector]] ifNone: [^nil]! ! !CodeHolder methodsFor: 'controls' stamp: 'sd 11/20/2005 21:27'! codePaneProvenanceButton "Answer a button that reports on, and allow the user to modify, the code-pane-provenance setting" | aButton | aButton := UpdatingSimpleButtonMorph newWithLabel: 'source'. aButton setNameTo: 'codeProvenance'. aButton useSquareCorners. aButton target: self; wordingSelector: #codePaneProvenanceString; actionSelector: #offerWhatToShowMenu. aButton setBalloonText: 'Governs what view is shown in the code pane. Click here to change the view'. aButton actWhen: #buttonDown. aButton color: Color white; borderStyle: BorderStyle thinGray; vResizing: #spaceFill. ^ aButton! ! !CodeHolder methodsFor: 'controls' stamp: 'sd 11/20/2005 21:26'! codePaneProvenanceString "Answer a string that reports on code-pane-provenance" | symsAndWordings | (symsAndWordings := self contentsSymbolQuints) do: [:aQuad | contentsSymbol == aQuad first ifTrue: [^ aQuad fourth]]. ^ symsAndWordings first fourth "default to plain source, for example if nil as initially"! ! !CodeHolder methodsFor: 'controls' stamp: 'md 8/14/2005 17:49'! contentsSymbolQuints "Answer a list of quintuplets representing information on the alternative views available in the code pane first element: the contentsSymbol used second element: the selector to call when this item is chosen. third element: the selector to call to obtain the wording of the menu item. fourth element: the wording to represent this view fifth element: balloon help A hypen indicates a need for a seperator line in a menu of such choices" ^ #( (source togglePlainSource showingPlainSourceString 'source' 'the textual source code as writen') (documentation toggleShowDocumentation showingDocumentationString 'documentation' 'the first comment in the method') - (prettyPrint togglePrettyPrint prettyPrintString 'prettyPrint' 'the method source presented in a standard text format') (colorPrint toggleColorPrint colorPrintString 'colorPrint' 'the method source in a standard text format with colors to distinguish structural parts') - (showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version') (prettyDiffs togglePrettyDiffing showingPrettyDiffsString 'prettyDiffs' 'formatted textual source diffed from formatted form of prior version') - (decompile toggleDecompile showingDecompileString 'decompile' 'source code decompiled from byteCodes') (byteCodes toggleShowingByteCodes showingByteCodesString 'byteCodes' 'the bytecodes that comprise the compiled method') - (tiles toggleShowingTiles showingTilesString 'tiles' 'universal tiles representing the method'))! ! !CodeHolder methodsFor: 'controls' stamp: 'ar 2/12/2005 14:28'! 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 changed: #inheritanceButtonColor. self decorateForInheritance ! ! !CodeHolder methodsFor: 'controls' stamp: 'sd 11/20/2005 21:27'! 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 flags | (aButton := self inheritanceButton) ifNil: [^ self]. ((currentCompiledMethod isKindOf: CompiledMethod) and: [Preferences decorateBrowserButtons]) ifFalse: [^aButton offColor: Color transparent]. "This table duplicates the old logic, but adds two new colors for the cases where there is a superclass definition, but this method doesn't call it." flags := 0. self isThisAnOverride ifTrue: [ flags := flags bitOr: 4 ]. currentCompiledMethod sendsToSuper ifTrue: [ flags := flags bitOr: 2 ]. self isThereAnOverride ifTrue: [ flags := flags bitOr: 1 ]. aColor := { Color transparent. Color tan lighter. Color green muchLighter. Color blue muchLighter. Color red muchLighter. "has super but doesn't call it" (Color r: 0.94 g: 0.823 b: 0.673). "has sub; has super but doesn't call it" Color green muchLighter. Color blue muchLighter. } at: flags + 1. aButton offColor: aColor! ! !CodeHolder methodsFor: 'controls' 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: 'controls' stamp: 'sd 11/20/2005 21:27'! optionalButtonPairs "Answer a tuple (formerly pairs) defining buttons, in the format: button label selector to send help message" | aList | aList := #( ('browse' browseMethodFull 'view this method in a browser') ('senders' browseSendersOfMessages 'browse senders of...') ('implementors' browseMessages 'browse implementors of...') ('versions' browseVersions 'browse versions')), (Preferences decorateBrowserButtons ifTrue: [{#('inheritance' methodHierarchy 'browse method inheritance green: sends to super tan: has override(s) mauve: both of the above pink: is an override but doesn''t call super pinkish tan: has override(s), also is an override but doesn''t call super' )}] ifFalse: [{#('inheritance' methodHierarchy 'browse method inheritance')}]), #( ('hierarchy' classHierarchy 'browse class hierarchy') ('inst vars' browseInstVarRefs 'inst var refs...') ('class vars' browseClassVarRefs 'class var refs...')). ^ aList! ! !CodeHolder methodsFor: 'controls' stamp: 'sd 11/20/2005 21:27'! optionalButtonRow "Answer a row of control buttons" | aRow aButton aLabel | aRow := AlignmentMorph newRow. aRow setNameTo: 'buttonPane'. aRow beSticky. aRow hResizing: #spaceFill. aRow wrapCentering: #center; cellPositioning: #leftCenter. aRow clipSubmorphs: true. aRow cellInset: 1. aRow color: Color white. Preferences menuButtonInToolPane ifTrue: [aRow addMorphFront: self menuButton]. self optionalButtonPairs do: [:tuple | aButton := PluggableButtonMorph on: self getState: nil action: tuple second. aButton hResizing: #spaceFill; vResizing: #spaceFill; onColor: Color white offColor: Color white. aLabel := Preferences abbreviatedBrowserButtons ifTrue: [self abbreviatedWordingFor: tuple second]. aButton label: (aLabel ifNil: [tuple first asString]). tuple size > 2 ifTrue: [aButton setBalloonText: tuple third]. tuple size > 3 ifTrue: [aButton triggerOnMouseDown: tuple fourth]. aRow addMorphBack: aButton]. aRow addMorphBack: self codePaneProvenanceButton. ^ aRow! ! !CodeHolder methodsFor: 'controls' stamp: 'sw 11/13/2001 09:12'! sourceAndDiffsQuintsOnly "Answer a list of quintuplets representing information on the alternative views available in the code pane for the case where the only plausible choices are showing source or either of the two kinds of diffs" ^ #( (source togglePlainSource showingPlainSourceString 'source' 'the textual source code as writen') (showDiffs toggleRegularDiffing showingRegularDiffsString 'showDiffs' 'the textual source diffed from its prior version') (prettyDiffs togglePrettyDiffing showingPrettyDiffsString 'prettyDiffs' 'formatted textual source diffed from formatted form of prior version'))! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 9/5/2001 13:36'! defaultDiffsSymbol "Answer the code symbol to use when generically switching to diffing" ^ Preferences diffsWithPrettyPrint ifTrue: [#prettyDiffs] ifFalse: [#showDiffs]! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! diffButton "Return a checkbox that lets the user decide whether diffs should be shown or not. Not sent any more but retained against the possibility of existing subclasses outside the base image using it." | 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: #toggleRegularDiffing; getSelector: #showingRegularDiffs. 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: 'sd 11/20/2005 21:27'! 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 prettyDiffs: self showingPrettyDiffs]! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! prettyDiffButton "Return a checkbox that lets the user decide whether prettyDiffs should be shown or not" | 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: #togglePrettyDiffing; getSelector: #showingPrettyDiffs. outerButton addMorphBack: (StringMorph contents: 'prettyDiffs') lock. (self isKindOf: VersionsBrowser) ifTrue: [outerButton setBalloonText: 'If checked, then pretty-printed code differences from the previous version, if any, will be shown.'] ifFalse: [outerButton setBalloonText: 'If checked, then pretty-printed code differences between the file-based method and the in-memory version, if any, will be shown.']. ^ outerButton ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! regularDiffButton "Return a checkbox that lets the user decide whether regular diffs should be shown or not" | 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: #toggleRegularDiffing; getSelector: #showingRegularDiffs. 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: 'sd 11/20/2005 21:27'! restoreTextualCodingPane "If the receiver is showing tiles, restore the textual coding pane" self showingTiles ifTrue: [contentsSymbol := #source. self installTextualCodingPane]! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 11/13/2001 07:49'! showDiffs "Answer whether the receiver is showing diffs of source code. The preferred protocol here is #showingRegularDiffs, but this message is still sent by some preexisting buttons so is retained." ^ contentsSymbol == #showDiffs ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! showDiffs: aBoolean "Set whether I'm showing diffs as indicated; use the global preference to determine which kind of diffs to institute." self showingAnyKindOfDiffs ifFalse: [aBoolean ifTrue: [contentsSymbol := self defaultDiffsSymbol]] ifTrue: [aBoolean ifFalse: [contentsSymbol := #source]]. self setContentsToForceRefetch. self contentsChanged! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:26'! showPrettyDiffs: aBoolean "Set whether I'm showing pretty diffs as indicated" self showingPrettyDiffs ifFalse: [aBoolean ifTrue: [contentsSymbol := #prettyDiffs]] ifTrue: [aBoolean ifFalse: [contentsSymbol := #source]]. self setContentsToForceRefetch. self contentsChanged! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:26'! showRegularDiffs: aBoolean "Set whether I'm showing regular diffs as indicated" self showingRegularDiffs ifFalse: [aBoolean ifTrue: [contentsSymbol := #showDiffs]] ifTrue: [aBoolean ifFalse: [contentsSymbol := #source]]. self setContentsToForceRefetch. self contentsChanged! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 9/5/2001 13:32'! showingAnyKindOfDiffs "Answer whether the receiver is currently set to show any kind of diffs" ^ #(showDiffs prettyDiffs) includes: contentsSymbol! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 11/13/2001 09:10'! showingDiffsString "Answer a string representing whether I'm showing diffs. Not sent any more but retained so that prexisting buttons that sent this will not raise errors." ^ (self showingRegularDiffs ifTrue: [''] ifFalse: ['']), 'showDiffs'! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 5/19/2001 00:07'! showingPrettyDiffs "Answer whether the receiver is showing pretty diffs of source code" ^ contentsSymbol == #prettyDiffs ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 5/22/2001 16:41'! showingPrettyDiffsString "Answer a string representing whether I'm showing pretty diffs" ^ (self showingPrettyDiffs ifTrue: [''] ifFalse: ['']), 'prettyDiffs'! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 11/13/2001 07:07'! showingRegularDiffs "Answer whether the receiver is showing regular diffs of source code" ^ contentsSymbol == #showDiffs ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 11/13/2001 07:43'! showingRegularDiffsString "Answer a string representing whether I'm showing regular diffs" ^ (self showingRegularDiffs ifTrue: [''] ifFalse: ['']), 'showDiffs'! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! toggleColorPrint "Toggle whether color-print is in effect in the code pane" self restoreTextualCodingPane. self okToChange ifTrue: [self showingColorPrint ifTrue: [contentsSymbol := #source] ifFalse: [contentsSymbol := #colorPrint]. self setContentsToForceRefetch. self contentsChanged] ! ! !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: 'sd 11/20/2005 21:27'! toggleDiffing "Toggle whether diffs should be shown in the code pane. If any kind of diffs were being shown, stop showing diffs. If no kind of diffs were being shown, start showing whatever kind of diffs are called for by default." | wasShowingDiffs | self okToChange ifTrue: [wasShowingDiffs := self showingAnyKindOfDiffs. self restoreTextualCodingPane. self showDiffs: wasShowingDiffs not. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! togglePlainSource "Toggle whether plain source shown in the code pane" | wasShowingPlainSource | self okToChange ifTrue: [wasShowingPlainSource := self showingPlainSource. self restoreTextualCodingPane. wasShowingPlainSource ifTrue: [self showDocumentation: true] ifFalse: [contentsSymbol := #source]. self setContentsToForceRefetch. self changed: #contents] ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! togglePrettyDiffing "Toggle whether pretty-diffing should be shown in the code pane" | wasShowingDiffs | self okToChange ifTrue: [wasShowingDiffs := self showingPrettyDiffs. self restoreTextualCodingPane. self showPrettyDiffs: wasShowingDiffs not. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:27'! togglePrettyPrint "Toggle whether pretty-print is in effectin the code pane" self restoreTextualCodingPane. self okToChange ifTrue: [self showingPrettyPrint ifTrue: [contentsSymbol := #source] ifFalse: [contentsSymbol := #prettyPrint]. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sd 11/20/2005 21:26'! toggleRegularDiffing "Toggle whether regular-diffing should be shown in the code pane" | wasShowingDiffs | self okToChange ifTrue: [wasShowingDiffs := self showingRegularDiffs. self restoreTextualCodingPane. self showRegularDiffs: wasShowingDiffs not. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'diffs' stamp: 'sw 11/13/2001 07:24'! wantsDiffFeedback "Answer whether the receiver is showing diffs of source code" ^ self showingAnyKindOfDiffs! ! !CodeHolder methodsFor: 'message category functions' stamp: 'sw 10/8/2001 14:19'! canShowMultipleMessageCategories "Answer whether the receiver is capable of showing multiple message categories" ^ false! ! !CodeHolder methodsFor: 'message list' stamp: 'md 2/22/2006 16:10'! decompiledSourceIntoContents "Obtain a source string by decompiling the method's code, and place that source string into my contents. Also return the string. Get temps from source file if shift key is pressed." | class | class := self selectedClassOrMetaClass. "Was method deleted while in another project?" currentCompiledMethod := (class compiledMethodAt: self selectedMessageName ifAbsent: [^ '']). contents := (Sensor leftShiftDown not) ifTrue: [currentCompiledMethod decompileWithTemps] ifFalse: [currentCompiledMethod decompile]. contents := contents decompileString asText makeSelectorBoldIn: class. ^ contents copy! ! !CodeHolder methodsFor: 'message list' stamp: 'sw 8/16/2002 23:23'! selectedBytecodes "Answer text to show in a code pane when in showing-byte-codes mode" ^ (self selectedClassOrMetaClass compiledMethodAt: self selectedMessageName ifAbsent: [^ '' asText]) symbolic asText! ! !CodeHolder methodsFor: 'message list' stamp: 'md 2/20/2006 15:02'! selectedMessage "Answer a copy of the source code for the selected message. This generic version is probably actually never reached, since every subclass probably reimplements and does not send to super. In time, ideally, most, or all, reimplementors would vanish and all would defer instead to a universal version right here. Everything in good time." | class selector method | contents ifNotNil: [^ contents copy]. self showingDecompile ifTrue:[^ self decompiledSourceIntoContents]. class := self selectedClassOrMetaClass. (class isNil or: [(selector := self selectedMessageName) isNil]) ifTrue: [^ '']. method := class compiledMethodAt: selector ifAbsent: [^ '']. "method deleted while in another project" currentCompiledMethod := method. ^ contents := (self showComment ifFalse: [self sourceStringPrettifiedAndDiffed] ifTrue: [ self commentContents]) copy asText makeSelectorBoldIn: class! ! !CodeHolder methodsFor: 'message list' stamp: 'md 8/14/2005 17:50'! sourceStringPrettifiedAndDiffed "Answer a copy of the source code for the selected message, transformed by diffing and pretty-printing exigencies" | class selector sourceString | class := self selectedClassOrMetaClass. selector := self selectedMessageName. (class isNil or: [selector isNil]) ifTrue: [^'missing']. sourceString := class ultimateSourceCodeAt: selector ifAbsent: [^'error']. self validateMessageSource: sourceString forSelector: selector. (#(#prettyPrint #colorPrint #prettyDiffs) includes: contentsSymbol) ifTrue: [sourceString := class prettyPrinterClass format: sourceString in: class notifying: nil contentsSymbol: contentsSymbol]. self showingAnyKindOfDiffs ifTrue: [sourceString := self diffFromPriorSourceFor: sourceString]. ^sourceString! ! !CodeHolder methodsFor: 'message list' stamp: 'sd 11/20/2005 21:27'! validateMessageSource: sourceString forSelector: aSelector "Check whether there is evidence that method source is invalid" | sourcesName | (self selectedClass compilerClass == Object compilerClass and: [(sourceString asString findString: aSelector keywords first ) ~= 1]) ifTrue: [sourcesName := FileDirectory localNameFor: SmalltalkImage current sourcesName. self inform: 'There may be a problem with your sources file!! The source code for every method should (usually) start with the method selector but this is not the case with this method!! You may proceed with caution but it is recommended that you get a new source file. This can happen if you download the "' , sourcesName , '" 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. (Occasionally, the source code for a method may legitimately start with a non-alphabetic character -- for example, Behavior method #formalHeaderPartsFor:. In such rare cases, you can happily disregard this warning.)'].! ! !CodeHolder methodsFor: 'message list menu' stamp: 'sd 11/20/2005 21:27'! messageListKey: aChar from: view "Respond to a Command key. I am a model with a code pane, and I also have a listView that has a list of methods. The view knows how to get the list and selection." | sel class | aChar == $D ifTrue: [^ self toggleDiffing]. sel := self selectedMessageName. aChar == $m ifTrue: "These next two put up a type in if no message selected" [^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllImplementorsOf: to: self systemNavigation]. aChar == $n ifTrue: [^ self useSelector: sel orGetSelectorAndSendQuery: #browseAllCallsOn: to: self systemNavigation]. "The following require a class selection" (class := self selectedClassOrMetaClass) ifNil: [^ self arrowKey: aChar from: view]. aChar == $b ifTrue: [^ Browser fullOnClass: class selector: sel]. aChar == $N ifTrue: [^ self browseClassRefs]. aChar == $i ifTrue: [^ self methodHierarchy]. aChar == $h ifTrue: [^ self classHierarchy]. aChar == $p ifTrue: [^ self browseFullProtocol]. "The following require a method selection" sel ifNotNil: [aChar == $o ifTrue: [^ self fileOutMessage]. aChar == $c ifTrue: [^ self copySelector]. aChar == $v ifTrue: [^ self browseVersions]. aChar == $O ifTrue: [^ self openSingleMessageBrowser]. aChar == $x ifTrue: [^ self removeMessage]. aChar == $d ifTrue: [^ self removeMessageFromBrowser]. (aChar == $C and: [self canShowMultipleMessageCategories]) ifTrue: [^ self showHomeCategory]]. ^ self arrowKey: aChar from: view! ! !CodeHolder methodsFor: 'misc' stamp: 'md 2/24/2006 15:25'! 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: Color white. divider := BorderedSubpaneDividerMorph forBottomEdge. divider extent: 4 @ 4; color: Color gray; borderColor: #simple; borderWidth: 1. 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: 'nk 4/10/2001 07:52'! getSelectorAndSendQuery: querySelector to: queryPerformer "Obtain a selector relevant to the current context, and then send the querySelector to the queryPerformer with the selector obtained as its argument. If no message is currently selected, then obtain a method name from a user type-in" self getSelectorAndSendQuery: querySelector to: queryPerformer with: { }. ! ! !CodeHolder methodsFor: 'misc' stamp: 'md 3/3/2006 09:21'! getSelectorAndSendQuery: querySelector to: queryPerformer with: queryArgs "Obtain a selector relevant to the current context, and then send the querySelector to the queryPerformer with the selector obtained and queryArgs as its arguments. If no message is currently selected, then obtain a method name from a user type-in" | strm array | strm := WriteStream on: (array := Array new: queryArgs size + 1). strm nextPut: nil. strm nextPutAll: queryArgs. self selectedMessageName ifNil: [ | selector | selector := UIManager default request: 'Type selector:' initialAnswer: 'flag:'. selector := selector copyWithout: Character space. ^ selector isEmptyOrNil ifFalse: [ (Symbol hasInterned: selector ifTrue: [ :aSymbol | array at: 1 put: aSymbol. queryPerformer perform: querySelector withArguments: array]) ifFalse: [ self inform: 'no such selector'] ] ]. self selectMessageAndEvaluate: [:selector | array at: 1 put: selector. queryPerformer perform: querySelector withArguments: array ]! ! !CodeHolder methodsFor: 'misc' stamp: 'md 2/24/2006 15:28'! isThereAnOverride "Answer whether any subclass of my selected class implements my selected selector" | aName aClass | aName := self selectedMessageName ifNil: [^ false]. aClass := self selectedClassOrMetaClass ifNil: [^ false]. aClass allSubclassesDo: [ :cls | (cls includesSelector: aName) ifTrue: [ ^true ]]. ^ false! ! !CodeHolder methodsFor: 'misc' stamp: 'md 2/24/2006 15:28'! isThisAnOverride "Answer whether any superclass of my selected class implements my selected selector" | aName aClass | aName := self selectedMessageName ifNil: [^ false]. aClass := self selectedClassOrMetaClass ifNil: [^ false]. aClass allSuperclassesDo: [ :cls | (cls includesSelector: aName) ifTrue: [ ^true ]]. ^ false! ! !CodeHolder methodsFor: 'misc' stamp: 'sd 11/20/2005 21:27'! menuButton "Answer a button that brings up a menu. Useful when adding new features, but at present is between uses" | aButton | aButton := IconicButton new target: self; borderWidth: 0; labelGraphic: (ScriptingSystem formAtKey: #TinyMenu); color: Color transparent; actWhen: #buttonDown; actionSelector: #offerMenu; yourself. aButton setBalloonText: 'click here to get a menu with further options'. ^ aButton ! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 9/27/2001 01:26'! 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. self refreshAnnotation. super modelWakeUpIn: aWindow! ! !CodeHolder methodsFor: 'misc' stamp: 'sw 11/13/2001 07:42'! okayToAccept "Answer whether it is okay to accept the receiver's input" self showingDocumentation 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 showingAnyKindOfDiffs 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: 'sd 11/20/2005 21:27'! 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: 'sw 10/28/2001 00:15'! refreshAnnotation "If the receiver has an annotation pane that does not bear unaccepted edits, refresh it" (self dependents detect: [:m | (m inheritsFromAnyIn: #('PluggableTextView' 'PluggableTextMorph')) and: [m getTextSelector == #annotation]] ifNone: [nil]) ifNotNilDo: [:aPane | aPane hasUnacceptedEdits ifFalse: [aPane update: #annotation]]! ! !CodeHolder methodsFor: 'misc' stamp: 'md 8/14/2005 17:49'! refusesToAcceptCode "Answer whether receiver, given its current contentsSymbol, could accept code happily if asked to" ^ (#(byteCodes documentation tiles) includes: self contentsSymbol)! ! !CodeHolder methodsFor: 'misc' stamp: 'sd 11/20/2005 21:27'! releaseCachedState "Can always be found again. Don't write on a file." currentCompiledMethod := nil.! ! !CodeHolder methodsFor: 'misc' stamp: 'sd 11/20/2005 21:27'! 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: 'rbb 3/1/2005 10:31'! sendQuery: querySelector to: queryPerformer "Apply a query to the primary selector associated with the current context. If no such selection exists, obtain one by user type-in. Then send querySelector to queryPerformer with the selector as its argument." | aSelector aString | aSelector := self selectedMessageName ifNil: [aString :=UIManager default request: 'Type selector:' initialAnswer: 'flag:'. ^ aString isEmptyOrNil ifFalse: [(Symbol hasInterned: aString ifTrue: [:aSymbol | queryPerformer perform: querySelector with: aSymbol]) ifFalse: [self inform: 'no such selector']]]. queryPerformer perform: querySelector with: aSelector! ! !CodeHolder methodsFor: 'misc' stamp: 'sd 11/20/2005 21:27'! 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: 'misc' stamp: 'sw 2/22/2001 06:37'! suggestCategoryToSpawnedBrowser: aBrowser "aBrowser is a message-category browser being spawned from the receiver. Tell it what it needs to know to get its category info properly set up." aBrowser setOriginalCategoryIndexForCurrentMethod! ! !CodeHolder methodsFor: 'misc' stamp: 'rbb 3/1/2005 10:31'! useSelector: incomingSelector orGetSelectorAndSendQuery: querySelector to: queryPerformer "If incomingSelector is not nil, use it, else obtain a selector from user type-in. Using the determined selector, send the query to the performer provided." | aSelector | incomingSelector ifNotNil: [queryPerformer perform: querySelector with: incomingSelector] ifNil: [aSelector :=UIManager default request: 'Type selector:' initialAnswer: 'flag:'. aSelector isEmptyOrNil ifFalse: [(Symbol hasInterned: aSelector ifTrue: [:aSymbol | queryPerformer perform: querySelector with: aSymbol]) ifFalse: [self inform: 'no such selector']]]! ! !CodeHolder methodsFor: 'self-updating' stamp: 'nk 4/29/2004 12:25'! 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]. self classCommentIndicated ifTrue: [^ currentCompiledMethod ~~ aClass organization commentRemoteStr]. ^ (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 2/14/2001 15:34'! 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: [self setContentsToForceRefetch. 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! ! !CodeHolder methodsFor: 'tiles' stamp: 'yo 2/17/2005 18:14'! addModelItemsToWindowMenu: aMenu "Add model-related item to the window menu" super addModelItemsToWindowMenu: aMenu. Smalltalk isMorphic ifTrue: [aMenu addLine. aMenu add: 'what to show...' translated target: self action: #offerWhatToShowMenu]! ! !CodeHolder methodsFor: 'tiles' stamp: 'sd 11/20/2005 21:27'! installTextualCodingPane "Install text into the code pane" | aWindow codePane aPane boundsToUse | (aWindow := self containingWindow) ifNil: [self error: 'where''s that window?']. codePane := aWindow findDeepSubmorphThat: [:m | ((m isKindOf: PluggableTextMorph) and: [m getTextSelector == #contents]) or: [m isKindOf: PluggableTileScriptorMorph]] ifAbsent: [self error: 'no code pane']. aPane := self buildMorphicCodePaneWith: nil. boundsToUse := (codePane bounds origin- (1@1)) corner: (codePane owner bounds corner " (1@1"). aWindow replacePane: codePane with: aPane. aPane vResizing: #spaceFill; hResizing: #spaceFill; borderWidth: 0. aPane bounds: boundsToUse. aPane owner clipSubmorphs: false. self contentsChanged! ! !CodeHolder methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 14:32'! buildCodeProvenanceButtonWith: builder | buttonSpec | buttonSpec := builder pluggableActionButtonSpec new. buttonSpec model: self. buttonSpec label: #codePaneProvenanceString. buttonSpec action: #offerWhatToShowMenu. buttonSpec help: 'Governs what view is shown in the code pane. Click here to change the view'. ^buttonSpec! ! !CodeHolder methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 14:32'! buildOptionalButtonsWith: builder | panelSpec buttonSpec | panelSpec := builder pluggablePanelSpec new. panelSpec children: OrderedCollection new. self optionalButtonPairs do:[:spec| buttonSpec := builder pluggableActionButtonSpec new. buttonSpec model: self. buttonSpec label: spec first. buttonSpec action: spec second. spec second == #methodHierarchy ifTrue:[ buttonSpec color: #inheritanceButtonColor. ]. spec size > 2 ifTrue:[buttonSpec help: spec third]. panelSpec children add: buttonSpec. ]. "What to show" panelSpec children add: (self buildCodeProvenanceButtonWith: builder). panelSpec layout: #horizontal. "buttons" ^panelSpec! ! !CodeHolder methodsFor: 'toolbuilder' stamp: 'sd 11/20/2005 21:27'! inheritanceButtonColor "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." | flags aColor | ((currentCompiledMethod isKindOf: CompiledMethod) and: [Preferences decorateBrowserButtons]) ifFalse: [^Color transparent]. "This table duplicates the old logic, but adds two new colors for the cases where there is a superclass definition, but this method doesn't call it." flags := 0. self isThisAnOverride ifTrue: [ flags := flags bitOr: 4 ]. currentCompiledMethod sendsToSuper ifTrue: [ flags := flags bitOr: 2 ]. self isThereAnOverride ifTrue: [ flags := flags bitOr: 1 ]. aColor := { Color transparent. Color tan lighter. Color green muchLighter. Color blue muchLighter. Color red muchLighter. "has super but doesn't call it" (Color r: 0.94 g: 0.823 b: 0.673). "has sub; has super but doesn't call it" Color green muchLighter. Color blue muchLighter. } at: flags + 1. ^aColor! ! !CodeHolder methodsFor: 'traits' stamp: 'al 4/24/2004 12:46'! makeSampleInstance | aClass nonMetaClass anInstance | ((aClass _ self selectedClassOrMetaClass) isNil or: [aClass isTrait]) ifTrue: [^ 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: 'traits' stamp: 'al 4/24/2004 12:44'! showUnreferencedClassVars "Search for all class 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 class variable in order to determine whether it is unreferenced" | cls aList aReport | ((cls _ self selectedClass) isNil or: [cls isTrait]) ifTrue: [^ self]. aList _ self systemNavigation allUnreferencedClassVariablesOf: cls. aList size == 0 ifTrue: [^ self inform: 'There are no unreferenced class variables in ' , cls name]. aReport _ String streamContents: [:aStream | aStream nextPutAll: 'Unreferenced class 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 class variables in ' , cls name! ! !CodeHolder methodsFor: 'traits' stamp: 'al 4/24/2004 12:43'! 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 selectedClassOrMetaClass) isNil or: [cls isTrait]) ifTrue: [^ 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: 'traits' stamp: 'al 4/24/2004 12:12'! spawnHierarchy "Create and schedule a new hierarchy browser on the currently selected class or meta." | newBrowser aSymbol aBehavior messageCatIndex selectedClassOrMetaClass | (selectedClassOrMetaClass _ self selectedClassOrMetaClass) ifNil: [^ self]. selectedClassOrMetaClass isTrait ifTrue: [^self]. newBrowser _ HierarchyBrowser new initHierarchyForClass: selectedClassOrMetaClass. ((aSymbol _ self selectedMessageName) notNil and: [(MessageSet isPseudoSelector: aSymbol) not]) ifTrue: [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: newBrowser labelString. Smalltalk isMorphic ifTrue: ["this workaround only needed in morphic" newBrowser assureSelectionsShow]! ! !CodeHolder methodsFor: 'what to show' stamp: 'nk 6/19/2004 16:59'! addContentsTogglesTo: aMenu "Add updating menu toggles governing contents to aMenu." self contentsSymbolQuints do: [:aQuint | aQuint == #- ifTrue: [aMenu addLine] ifFalse: [Smalltalk isMorphic ifTrue: [aMenu addUpdating: aQuint third target: self action: aQuint second. aMenu balloonTextForLastItem: aQuint fifth] ifFalse: [aMenu add: (('*' match: (self perform: aQuint third)) ifTrue: ['*'] ifFalse: ['']), aQuint fourth target: self selector: #contentsSymbol: argumentList: { aQuint first } ]]]! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/22/2001 16:36'! colorPrintString "Answer whether the receiver is showing colorPrint" ^ (self showingColorPrint ifTrue: [''] ifFalse: ['']) , 'colorPrint'! ! !CodeHolder methodsFor: 'what to show' stamp: 'yo 2/17/2005 18:09'! offerWhatToShowMenu "Offer a menu governing what to show" | aMenu | Smalltalk isMorphic ifTrue: [aMenu := MenuMorph new defaultTarget: self. aMenu addTitle: 'What to show' translated. aMenu addStayUpItem. self addContentsTogglesTo: aMenu. aMenu popUpInWorld] ifFalse: [aMenu := CustomMenu new. self addContentsTogglesTo: aMenu. aMenu title: 'What to show' translated. aMenu invokeOn: self. self changed: #contents ]! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/22/2001 16:36'! prettyPrintString "Answer whether the receiver is showing pretty-print" ^ ((contentsSymbol == #prettyPrint) ifTrue: [''] ifFalse: ['']), 'prettyPrint'! ! !CodeHolder methodsFor: 'what to show' stamp: 'sd 11/20/2005 21:27'! setContentsToForceRefetch "Set the receiver's contents such that on the next update the contents will be formulated afresh. This is a critical and obscure difference between Browsers on the one hand and MessageSets on the other, and has over the years been the source of much confusion and much difficulty. By centralizing the different handling here, we don't need so many idiosyncratic overrides in MessageSet any more" contents := nil! ! !CodeHolder methodsFor: 'what to show' stamp: 'sd 11/20/2005 21:27'! showByteCodes: aBoolean "Get into or out of bytecode-showoing mode" self okToChange ifFalse: [^ self changed: #flash]. aBoolean ifTrue: [contentsSymbol := #byteCodes] ifFalse: [contentsSymbol == #byteCodes ifTrue: [contentsSymbol := #source]]. self contentsChanged! ! !CodeHolder methodsFor: 'what to show' 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: 'what to show' stamp: 'sw 5/20/2001 09:14'! showDecompile: aBoolean "Set the decompile toggle as indicated" self contentsSymbol: (aBoolean ifFalse: [#source] ifTrue: [#decompile])! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 12/5/2000 12:25'! showDocumentation: aBoolean "Set the showDocumentation toggle as indicated" self contentsSymbol: (aBoolean ifFalse: [#source] ifTrue: [#documentation])! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/18/2001 18:05'! showingByteCodes "Answer whether the receiver is showing bytecodes" ^ contentsSymbol == #byteCodes! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/22/2001 18:28'! showingByteCodesString "Answer whether the receiver is showing bytecodes" ^ (self showingByteCodes ifTrue: [''] ifFalse: ['']), 'byteCodes'! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/18/2001 23:50'! showingColorPrint "Answer whether the receiver is showing color-pretty-print" ^ contentsSymbol == #colorPrint! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/20/2001 06:52'! showingDecompile "Answer whether the receiver should show decompile rather than, say, source code" ^ self contentsSymbol == #decompile ! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/20/2001 06:50'! showingDecompileString "Answer a string characerizing whether decompilation is showing" ^ (self showingDecompile ifTrue: [''] ifFalse: ['']), 'decompile'! ! !CodeHolder methodsFor: 'what to show' 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: 'what to show' stamp: 'sw 5/18/2001 20:05'! showingDocumentationString "Answer a string characerizing whether documentation is showing" ^ (self showingDocumentation ifTrue: [''] ifFalse: ['']), 'documentation'! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/18/2001 19:43'! showingPlainSource "Answer whether the receiver is showing plain source" ^ contentsSymbol == #source! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/22/2001 09:31'! showingPlainSourceString "Answer a string telling whether the receiver is showing plain source" ^ (self showingPlainSource ifTrue: [''] ifFalse: ['']), 'source'! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/18/2001 18:36'! showingPrettyPrint "Answer whether the receiver is showing pretty-print" ^ contentsSymbol == #prettyPrint! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 12/5/2000 11:48'! showingSource "Answer whether the receiver is currently showing source code" ^ self contentsSymbol == #source ! ! !CodeHolder methodsFor: 'what to show' stamp: 'sd 11/20/2005 21:26'! toggleDecompile "Toggle the setting of the showingDecompile flag, unless there are unsubmitted edits that the user declines to discard" | wasShowing | self okToChange ifTrue: [wasShowing := self showingDecompile. self restoreTextualCodingPane. self showDecompile: wasShowing not. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'what to show' stamp: 'sd 11/20/2005 21:27'! toggleShowDocumentation "Toggle the setting of the showingDocumentation flag, unless there are unsubmitted edits that the user declines to discard" | wasShowing | self okToChange ifTrue: [wasShowing := self showingDocumentation. self restoreTextualCodingPane. self showDocumentation: wasShowing not. self setContentsToForceRefetch. self contentsChanged] ! ! !CodeHolder methodsFor: 'what to show' stamp: 'sw 5/18/2001 20:09'! toggleShowingByteCodes "Toggle whether the receiver is showing bytecodes" self restoreTextualCodingPane. self showByteCodes: self showingByteCodes not. self setContentsToForceRefetch. self contentsChanged! ! !CodeHolder methodsFor: '*eToys-tiles' stamp: 'nk 4/28/2004 10:14'! installTilesForSelection "Install universal tiles into the code pane." | source aSelector aClass tree syn tileScriptor aWindow codePane | (aWindow _ self containingWindow) ifNil: [self error: 'hamna dirisha']. tileScriptor _ ((aSelector _ self selectedMessageName) isNil or: [(aClass _ self selectedClassOrMetaClass whichClassIncludesSelector: aSelector) isNil]) ifTrue: [PluggableTileScriptorMorph new] ifFalse: [source _ aClass sourceCodeAt: aSelector. tree _ Compiler new parse: source in: aClass notifying: nil. (syn _ tree asMorphicSyntaxUsing: SyntaxMorph) parsedInClass: aClass. syn inAPluggableScrollPane]. codePane _ aWindow findDeepSubmorphThat: [:m | (m isKindOf: PluggableTextMorph) and: [m getTextSelector == #contents]] ifAbsent: []. codePane ifNotNil: [codePane hideScrollBars]. codePane ifNil: [codePane _ aWindow findDeepSubmorphThat: [:m | m isKindOf: PluggableTileScriptorMorph] ifAbsent: [self error: 'no code pane']]. tileScriptor color: aWindow paneColorToUse; setProperty: #hideUnneededScrollbars toValue: true. aWindow replacePane: codePane with: tileScriptor. currentCompiledMethod _ aClass ifNotNil: [aClass compiledMethodAt: aSelector]. tileScriptor owner clipSubmorphs: true. tileScriptor extent: codePane extent! ! !CodeHolder methodsFor: '*eToys-tiles' stamp: 'sw 2/3/2001 00:10'! showingTiles "Answer whether the receiver is currently showing tiles" ^ contentsSymbol == #tiles ! ! !CodeHolder methodsFor: '*eToys-tiles' stamp: 'sw 5/20/2001 21:12'! showingTilesString "Answer a string characterizing whether tiles are currently showing or not" ^ (self showingTiles ifTrue: [''] ifFalse: ['']), 'tiles'! ! !CodeHolder methodsFor: '*eToys-tiles' stamp: 'rhi 1/4/2002 11:15'! showTiles: aBoolean "Set the showingTiles as indicated. The fact that there are initially no senders of this reflects that fact that initially this trait is only directly settable through the UI; later there may be senders, such as if one wanted to set a system up so that all newly-opened browsers showed tiles rather than text." aBoolean ifTrue: [contentsSymbol _ #tiles] ifFalse: [contentsSymbol == #tiles ifTrue: [contentsSymbol _ #source]]. self setContentsToForceRefetch. self changed: #contents! ! !CodeHolder methodsFor: '*eToys-tiles' stamp: 'sw 2/14/2001 15:27'! toggleShowingTiles "Toggle whether tiles should be shown in the code pane" self okToChange ifTrue: [self showingTiles ifTrue: [contentsSymbol _ #source. self setContentsToForceRefetch. self installTextualCodingPane. self contentsChanged] ifFalse: [contentsSymbol _ #tiles. self installTilesForSelection. self changed: #tiles]]! ! !CodeHolder methodsFor: '*services-base' stamp: 'rr 3/15/2004 09:21'! requestor ^ (BrowserRequestor new) browser: self; yourself! ! Object subclass: #CodeLoader instanceVariableNames: 'baseURL sourceFiles segments publicKey' classVariableNames: 'DefaultBaseURL DefaultKey' poolDictionaries: '' category: 'System-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: 'initialize-release' stamp: 'mir 1/11/2000 13:47'! initialize publicKey _ DefaultKey. baseURL _ self class defaultBaseURL! ! !CodeLoader methodsFor: 'installing' stamp: 'RAA 2/19/2001 08:23'! 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 openName: nil "<--do we want to cache this locally? Need a name if so" stream: aStream fromDirectory: nil withProjectView: nil. ! ! !CodeLoader methodsFor: 'installing' stamp: 'sd 1/30/2004 15:16'! installSegment: reqEntry "Install the previously loaded segment" | contentStream contents trusted | contentStream _ reqEntry value contentStream. contentStream ifNil:[^self error:'No content to install: ', reqEntry key printString]. trusted _ SecurityManager default positionToSecureContentsOf: contentStream. trusted ifFalse:[(SecurityManager default enterRestrictedMode) ifFalse:[ contentStream close. ^self error:'Insecure content encountered: ', reqEntry key printString]]. contents _ contentStream ascii 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: 'sd 1/30/2004 15:16'! installSourceFile: aStream "Install the previously loaded source file" | contents trusted | aStream ifNil:[^self error:'No content to install']. trusted _ SecurityManager default positionToSecureContentsOf: aStream. trusted ifFalse:[(SecurityManager default enterRestrictedMode) ifFalse:[ aStream close. ^ self error:'Insecure content encountered']]. contents _ aStream ascii 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: '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: 'private' stamp: 'mir 2/2/2001 14:44'! createRequestFor: name in: aLoader "Create a URL request for the given string, which can be cached locally." | request | request _ HTTPLoader httpRequestClass for: self baseURL , name in: aLoader. aLoader addRequest: request. "fetch from URL" ^request! ! !CodeLoader methodsFor: 'private' stamp: 'avi 4/30/2004 01:40'! httpRequestClass ^HTTPDownloadRequest! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CodeLoader class instanceVariableNames: ''! !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 2/2/2001 14:56'! loadCodeSegment: segmentName | loader | loader _ self new. loader loadSegments: (Array with: segmentName). loader installSegments.! ! !CodeLoader class methodsFor: 'utilities' stamp: 'asm 12/6/2002 08:11'! signFile: fileName renameAs: destFile key: privateKey dsa: dsa "Sign the given file using the private key." | in out | in _ FileStream readOnlyFileNamed: fileName. in binary. out _ FileStream newFileNamed: destFile. out binary. [in atEnd] whileFalse:[out nextPutAll: (in next: 4096)]. in close. out close. FileDirectory activeDirectoryClass splitName: destFile to:[:path :file| SecurityManager default signFile: file directory: (FileDirectory on: path). ]. ! ! !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: 'ads 7/31/2003 14:00'! 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 initRandomNonInteractively. '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: 'ar 2/6/2001 19:17'! verifySignedFileNamed: aFileName "CodeLoader verifySignedFileNamed: 'signed\dummy1.dsq' " | secured signedFileStream | signedFileStream _ FileStream fileNamed: aFileName. secured _ SecurityManager default positionToSecureContentsOf: signedFileStream. signedFileStream close. Transcript show: aFileName , ' verified: '; show: secured printString; cr. ! ! ModelExtension subclass: #CodeModelExtension instanceVariableNames: 'perClassCache' classVariableNames: '' poolDictionaries: '' category: 'Traits-LocalSends'! !CodeModelExtension methodsFor: 'access to cache' stamp: 'dvf 9/6/2005 15:29'! cacheFor: aClass ^perClassCache at: aClass ifAbsentPut: [self newCacheFor: aClass]! ! !CodeModelExtension methodsFor: 'access to cache' stamp: 'dvf 9/1/2005 21:17'! clearOut: aClass ^perClassCache removeKey: aClass ifAbsent: []! ! !CodeModelExtension methodsFor: 'access to cache' stamp: 'dvf 9/5/2005 14:20'! for: aClass | newSendCache | ^perClassCache at: aClass ifAbsent: [newSendCache := self newCacheFor: aClass. (self haveInterestsIn: aClass) ifTrue: [perClassCache at: aClass put: newSendCache]. newSendCache]! ! !CodeModelExtension methodsFor: 'access to cache' stamp: 'dvf 9/1/2005 21:18'! initialize super initialize. perClassCache := IdentityDictionary new.! ! !CodeModelExtension methodsFor: 'invalidation' stamp: 'dvf 1/31/2006 23:38'! classChanged: modificationEvent "We dont want to provide an out of date reply" modificationEvent itemClass ifNil: [self]. self clearOut: modificationEvent itemClass ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CodeModelExtension class instanceVariableNames: ''! !CodeModelExtension class methodsFor: 'initialize-release' stamp: 'dvf 9/2/2005 12:20'! isAbstract ^self == CodeModelExtension! ! RectangleMorph subclass: #CodecDemoMorph instanceVariableNames: 'codecClassName' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-SoundInterface'! !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: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: 'dropping/grabbing' stamp: 'jm 2/4/1999 12:19'! wantsDroppedMorph: aMorph event: evt ^ aMorph isMemberOf: SoundTile ! ! !CodecDemoMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:20'! defaultColor "answer the default color/fill style for the receiver" ^ Color r: 1.0 g: 0.806 b: 0.677! ! !CodecDemoMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:20'! initialize "initialize the state of the receiver" super initialize. "" self codecClassName: 'MuLawCodec'! ! !CodecDemoMorph methodsFor: 'layout' 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: 'menus' stamp: 'dgd 8/30/2003 21:17'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'select codec' translated action: #selectCodec. ! ! SystemWindow subclass: #CollapsedMorph instanceVariableNames: 'uncollapsedMorph' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !CollapsedMorph methodsFor: 'collapse/expand' 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: 'collapse/expand' stamp: 'sw 4/9/2001 14:23'! uncollapseToHand "Hand the uncollapsedMorph to the user, placing it in her hand, after remembering appropriate state for possible future use" | nakedMorph | nakedMorph _ uncollapsedMorph. uncollapsedMorph _ nil. nakedMorph setProperty: #collapsedPosition toValue: self position. mustNotClose _ false. "so the delete will succeed" self delete. ActiveHand attachMorph: nakedMorph! ! !CollapsedMorph methodsFor: 'menu' stamp: 'dgd 8/30/2003 16:41'! 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...' translated action: #relabel. aMenu addLine. aMenu add: 'send to back' translated action: #sendToBack. aMenu add: 'make next-to-topmost' translated action: #makeSecondTopmost. aMenu addLine. self mustNotClose ifFalse: [aMenu add: 'make unclosable' translated action: #makeUnclosable] ifTrue: [aMenu add: 'make closable' translated action: #makeClosable]. aMenu add: (self isSticky ifTrue: ['make draggable'] ifFalse: ['make undraggable']) translated action: #toggleStickiness. ^aMenu! ! !CollapsedMorph methodsFor: 'resize/collapse' 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]! ! !CollapsedMorph methodsFor: 'resize/collapse' stamp: 'sw 6/5/2001 22:55'! wantsExpandBox "Answer whether I'd like an expand box" ^ false! ! !CollapsedMorph methodsFor: '*eToys-queries' stamp: 'sw 4/9/2001 12:53'! isMyUncollapsedMorph: aMorph "Answer whether my uncollapsed morph is aMorph" ^ uncollapsedMorph == aMorph! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CollapsedMorph class instanceVariableNames: ''! !CollapsedMorph class methodsFor: '*eToys-as yet unclassified' stamp: 'sw 4/9/2001 14:19'! collapsedMorphOrNilFor: anActualMorph "If there is any instance of the receiver that represents anActualMorph, answer it, else answer nil" self allInstances do: [:cm | (cm isMyUncollapsedMorph: anActualMorph) ifTrue: [^ cm]]. ^ nil! ! Object subclass: #Collection instanceVariableNames: '' classVariableNames: 'MutexForPicking 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: 'sd 11/4/2003 22:05'! atRandom "Answer a random element of the receiver. Uses a shared random number generator owned by class Collection. If you use this a lot, define your own instance of Random and use #atRandom:. Causes an error if self has no elements." ^ self class mutexForPicking critical: [ self atRandom: self class randomForPicking ] "Examples: #('one' 'or' 'the' 'other') atRandom (1 to: 10) atRandom 'Just pick one of these letters at random' atRandom #(3 7 4 9 21) asSet atRandom (just to show it also works for Sets) "! ! !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: 'mk 10/27/2003 21:48'! adaptToComplex: 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/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: 'arithmetic' stamp: 'raok 10/22/2002 00:17'! raisedTo: arg ^ arg adaptToCollection: self andSend: #raisedTo:! ! !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: 'LC 6/18/2001 20:30'! asIdentitySkipList "Answer a IdentitySkipList whose elements are the elements of the receiver. The sort order is the default less than or equal." ^ self as: IdentitySkipList! ! !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' stamp: 'LC 6/18/2001 18:47'! asSkipList "Answer a SkipList whose elements are the elements of the receiver. The sort order is the default less than or equal." ^ self as: SkipList! ! !Collection methodsFor: 'converting' stamp: 'LC 6/18/2001 18:46'! asSkipList: aSortBlock "Answer a SkipList whose elements are the elements of the receiver. The sort order is defined by the argument, aSortBlock." | skipList | skipList _ SortedCollection new: self size. skipList sortBlock: aSortBlock. skipList addAll: self. ^ skipList! ! !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: 'converting' stamp: 'hg 12/26/2001 23:53'! topologicallySortedUsing: aSortBlock "Answer a SortedCollection whose elements are the elements of the receiver, but topologically sorted. The topological order is defined by the argument, aSortBlock." | aSortedCollection | aSortedCollection _ SortedCollection new: self size. aSortedCollection sortBlock: aSortBlock. self do: [:each | aSortedCollection addLast: each]. "avoids sorting" ^ aSortedCollection sortTopologically ! ! !Collection methodsFor: 'copying' stamp: 'al 12/12/2003 14:31'! , aCollection ^self copy addAll: aCollection; yourself! ! !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: 'ar 2/11/2001 01:55'! copyWithDependent: newElement "Answer a new collection with newElement added (as last element if sequenceable)." ^self copyWith: newElement! ! !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: 'dgd 9/13/2004 23:42'! collect: collectBlock thenDo: doBlock "Utility method to improve readability." ^ (self collect: collectBlock) do: doBlock! ! !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: 'md 7/22/2005 16:26'! do: aBlock displayingProgress: aString aString displayProgressAt: Sensor cursorPoint from: 0 to: self size during: [:bar | self inject: 1 into: [:index :each | bar value: index. aBlock value: each. index + 1]]! ! !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: 'gh 9/18/2001 15:59'! 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]]. ^ true! ! !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' stamp: 'dgd 9/13/2004 23:42'! reject: rejectBlock thenDo: doBlock "Utility method to improve readability." ^ (self reject: rejectBlock) do: doBlock! ! !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: 'dgd 9/13/2004 23:42'! select: selectBlock thenDo: doBlock "Utility method to improve readability." ^ (self select: selectBlock) do: doBlock! ! !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: 'raok 10/22/2002 00:20'! arcCos ^self collect: [:each | each arcCos]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:20'! arcSin ^self collect: [:each | each arcSin]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:20'! arcTan ^self collect: [:each | each arcTan]! ! !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: 'raok 10/22/2002 00:20'! cos ^self collect: [:each | each cos]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:20'! degreeCos ^self collect: [:each | each degreeCos]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:21'! degreeSin ^self collect: [:each | each degreeSin]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:21'! exp ^self collect: [:each | each exp]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:51'! floor ^ self collect: [:a | a floor]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:21'! ln ^self collect: [:each | each ln]! ! !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: 'nk 12/30/2003 15:47'! roundTo: quantum ^self collect: [ :ea | ea roundTo: quantum ]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:53'! rounded ^ self collect: [:a | a rounded]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:23'! sign ^self collect: [:each | each sign]! ! !Collection methodsFor: 'math functions' stamp: 'raok 10/22/2002 00:22'! sin ^self collect: [:each | each sin]! ! !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: 'raok 10/22/2002 00:22'! tan ^self collect: [:each | each tan]! ! !Collection methodsFor: 'math functions' stamp: 'TAG 11/6/1998 15:54'! truncated ^ self collect: [:a | a truncated]! ! !Collection methodsFor: 'printing' stamp: 'gk 1/14/2005 08:19'! asCommaString "Return collection printed as 'a, b, c' " ^String streamContents: [:s | self asStringOn: s delimiter: ', '] ! ! !Collection methodsFor: 'printing' stamp: 'gk 1/14/2005 08:20'! asCommaStringAnd "Return collection printed as 'a, b and c' " ^String streamContents: [:s | self asStringOn: s delimiter: ', ' last: ' and '] ! ! !Collection methodsFor: 'printing' stamp: 'gk 1/14/2005 08:27'! asStringOn: aStream delimiter: delimString "Print elements on a stream separated with a delimiter String like: 'a, b, c' Uses #asString instead of #print:." self do: [:elem | aStream nextPutAll: elem asString] separatedBy: [aStream nextPutAll: delimString]! ! !Collection methodsFor: 'printing' stamp: 'gk 1/14/2005 08:27'! asStringOn: aStream delimiter: delimString last: lastDelimString "Print elements on a stream separated with a delimiter between all the elements and with a special one before the last like: 'a, b and c'. Uses #asString instead of #print: Note: Feel free to improve the code to detect the last element." | n sz | n _ 1. sz _ self size. self do: [:elem | n _ n + 1. aStream nextPutAll: elem asString] separatedBy: [ aStream nextPutAll: (n = sz ifTrue: [lastDelimString] ifFalse: [delimString])]! ! !Collection methodsFor: 'printing' stamp: 'apb 4/21/2006 09:37'! printElementsOn: aStream "The original code used #skip:, but some streams do not support that, and we don't really need it." aStream nextPut: $(. self do: [:element | aStream print: element] separatedBy: [aStream space]. 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' stamp: 'gk 1/14/2005 08:01'! printOn: aStream delimiter: delimString "Print elements on a stream separated with a delimiter String like: 'a, b, c' " self do: [:elem | aStream print: elem] separatedBy: [aStream print: delimString] ! ! !Collection methodsFor: 'printing' stamp: 'fbs 1/14/2005 10:54'! printOn: aStream delimiter: delimString last: lastDelimString "Print elements on a stream separated with a delimiter between all the elements and with a special one before the last like: 'a, b and c' Note: Feel free to improve the code to detect the last element." | n sz | n _ 1. sz _ self size. self do: [:elem | n _ n + 1. aStream print: elem] separatedBy: [ n = sz ifTrue: [aStream print: lastDelimString] ifFalse: [aStream print: delimString]]! ! !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: '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'! contains: aBlock "VW compatibility" ^self anySatisfy: aBlock! ! !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: 'jf 12/1/2003 15:37'! ifEmpty: aBlock "Evaluate the block if I'm empty" ^ self isEmpty ifTrue: aBlock! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 14:49'! ifEmpty: emptyBlock ifNotEmpty: notEmptyBlock "Evaluate emptyBlock if I'm empty, notEmptyBlock otherwise" " If the notEmptyBlock has an argument, eval with the receiver as its argument" ^ self isEmpty ifTrue: emptyBlock ifFalse: [notEmptyBlock valueWithPossibleArgument: self]! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 15:36'! ifEmpty: emptyBlock ifNotEmptyDo: notEmptyBlock "Evaluate emptyBlock if I'm empty, notEmptyBlock otherwise" "Evaluate the notEmptyBlock with the receiver as its argument" ^ self isEmpty ifTrue: emptyBlock ifFalse: [notEmptyBlock value: self]! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 14:58'! ifNotEmpty: aBlock "Evaluate the given block unless the receiver is empty. If the block has an argument, eval with the receiver as its argument, but it might be better to use ifNotEmptyDo: to make the code easier to understand" ^self isEmpty ifFalse: [aBlock valueWithPossibleArgument: self]. ! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 14:48'! ifNotEmpty: notEmptyBlock ifEmpty: emptyBlock "Evaluate emptyBlock if I'm empty, notEmptyBlock otherwise If the notEmptyBlock has an argument, eval with the receiver as its argument" ^ self isEmpty ifFalse: [notEmptyBlock valueWithPossibleArgument: self] ifTrue: emptyBlock! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 14:28'! ifNotEmptyDo: aBlock "Evaluate the given block with the receiver as its argument." ^self isEmpty ifFalse: [aBlock value: self]. ! ! !Collection methodsFor: 'testing' stamp: 'md 10/7/2004 15:36'! ifNotEmptyDo: notEmptyBlock ifEmpty: emptyBlock "Evaluate emptyBlock if I'm empty, notEmptyBlock otherwise Evaluate the notEmptyBlock with the receiver as its argument" ^ self isEmpty ifFalse: [notEmptyBlock value: self] ifTrue: emptyBlock! ! !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: 'nk 8/30/2004 07:49'! includesSubstringAnywhere: testString "Answer whether the receiver includes, anywhere in its nested structure, a string that has testString as a substring" self do: [:element | (element isString) ifTrue: [(element includesSubString: testString) ifTrue: [^ true]]. (element isCollection) 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: 'dgd 4/4/2004 12:14'! isZero "Answer whether the receiver is zero" ^ 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 methodsFor: '*Morphic-objectMenu' stamp: 'wiz 7/20/2004 13:06'! asKnownNameMenu "Return a menu to select an element of the collection. Menu uses the knownName or class name as only description of element." | menu | menu := CustomMenu new. self do: [:m | menu add: (m knownName ifNil: [m class name asString]) action: m]. ^ menu! ! !Collection methodsFor: '*packageinfo-base' stamp: 'ab 9/30/2002 19:26'! gather: aBlock ^ Array streamContents: [:stream | self do: [:ea | stream nextPutAll: (aBlock value: ea)]]! ! !Collection methodsFor: '*services-base' stamp: 'rr 3/21/2006 11:59'! chooseOne: caption "pops up a menu asking for one of the elements in the collection. If none is chosen, raises a ServiceCancelled notification" | m | m := MenuMorph entitled: caption. self do: [:ea | m add: ea target: [:n | ^ n] selector: #value: argument: ea]. m invokeModal. ServiceCancelled signal! ! !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' stamp: 'yo 6/29/2004 13:14'! errorNotKeyed self error: ('Instances of {1} do not respond to keyed accessing messages.' translated format: {self class name}) ! ! !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 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: 'lr 11/4/2003 12:07'! 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. MutexForPicking _ Semaphore forMutualExclusion! ! !Collection class methodsFor: 'private' stamp: 'lr 11/4/2003 12:08'! mutexForPicking ^ MutexForPicking! ! !Collection class methodsFor: 'private' stamp: 'sma 5/12/2000 12:31'! randomForPicking ^ RandomForPicking! ! TestCase subclass: #CollectionTest instanceVariableNames: 'empty nonEmpty' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Abstract'! !CollectionTest commentStamp: '' prior: 0! A TestCase is a Command representing the future running of a test case. Create one with the class method #selector: aSymbol, passing the name of the method to be run when the test case runs. When you discover a new fixture, subclass TestCase, declare instance variables for the objects in the fixture, override #setUp to initialize the variables, and possibly override# tearDown to deallocate any external resources allocated in #setUp. When you are writing a test case method, send #assert: aBoolean when you want to check for an expected value. For example, you might say "self assert: socket isOpen" to test whether or not a socket is open at a point in a test.! !CollectionTest methodsFor: 'initialize-release' stamp: 'st 10/7/2004 16:23'! setUp empty := Set new. nonEmpty := OrderedCollection with: #x! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:19'! testAsCommaString {OrderedCollection new. Set new.} do: [ :coll | self assert: coll asCommaString = ''. coll add: 1. self assert: coll asCommaString = '1'. coll add: 2; add: 3. self assert: coll asCommaString = '1, 2, 3'].! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:19'! testAsCommaStringAnd {OrderedCollection new. Set new.} do: [ :coll | self assert: coll asCommaStringAnd = ''. coll add: 1. self assert: coll asCommaStringAnd = '1'. coll add: 2; add: 3. self assert: coll asCommaStringAnd = '1, 2 and 3'].! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:33'! testAsStringOnDelimiter | delim emptyStream oneItemStream multiItemStream | delim := ', '. {OrderedCollection new. Set new.} do: [ :coll | emptyStream := ReadWriteStream on: ''. coll asStringOn: emptyStream delimiter: delim. self assert: emptyStream contents = ''. coll add: 1. oneItemStream := ReadWriteStream on: ''. coll asStringOn: oneItemStream delimiter: delim. self assert: oneItemStream contents = '1'. coll add: 2; add: 3. multiItemStream := ReadWriteStream on: ''. coll asStringOn: multiItemStream delimiter: ', '. self assert: multiItemStream contents = '1, 2, 3'.]! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:34'! testAsStringOnDelimiterLast | delim emptyStream last oneItemStream multiItemStream | delim := ', '. last := ' & '. {OrderedCollection new. Set new.} do: [ :coll | emptyStream := ReadWriteStream on: ''. coll asStringOn: emptyStream delimiter: delim last: last. self assert: emptyStream contents = ''. coll add: 1. oneItemStream := ReadWriteStream on: ''. coll asStringOn: oneItemStream delimiter: delim last: last. self assert: oneItemStream contents = '1'. coll add: 2; add: 3. multiItemStream := ReadWriteStream on: ''. coll asStringOn: multiItemStream delimiter: ', ' last: last. self assert: multiItemStream contents = '1, 2 & 3'.]! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:56'! testPrintOnDelimiter | delim emptyStream oneItemStream multiItemStream | delim := ', '. {OrderedCollection new. Set new.} do: [ :coll | emptyStream := ReadWriteStream on: ''. coll printOn: emptyStream delimiter: delim. self assert: emptyStream contents = ''. coll add: 1. oneItemStream := ReadWriteStream on: ''. coll printOn: oneItemStream delimiter: delim. self assert: oneItemStream contents = '1'. coll add: 2; add: 3. multiItemStream := ReadWriteStream on: ''. coll printOn: multiItemStream delimiter: ', '. self assert: multiItemStream contents = '1'', ''2'', ''3'.]! ! !CollectionTest methodsFor: 'testing' stamp: 'fbs 1/14/2005 10:56'! testPrintOnDelimiterLast | delim emptyStream last oneItemStream multiItemStream | delim := ', '. last := ' & '. {OrderedCollection new. Set new.} do: [ :coll | emptyStream := ReadWriteStream on: ''. coll printOn: emptyStream delimiter: delim last: last. self assert: emptyStream contents = ''. coll add: 1. oneItemStream := ReadWriteStream on: ''. coll printOn: oneItemStream delimiter: delim last: last. self assert: oneItemStream contents = '1'. coll add: 2; add: 3. multiItemStream := ReadWriteStream on: ''. coll printOn: multiItemStream delimiter: ', ' last: last. self assert: multiItemStream contents = '1'', ''2'' & ''3'.]! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:12'! testIfEmptyifNotEmpty self assert: (empty ifEmpty: [true] ifNotEmpty: [false]). self assert: (nonEmpty ifEmpty: [false] ifNotEmpty: [true]). self assert: (nonEmpty ifEmpty: [false] ifNotEmpty: [:s | s first = #x])! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:12'! testIfEmptyifNotEmptyDo self assert: (empty ifEmpty: [true] ifNotEmptyDo: [:s | false]). self assert: (nonEmpty ifEmpty: [false] ifNotEmptyDo: [:s | s first = #x])! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:12'! testIfNotEmpty empty ifNotEmpty: [self assert: false]. self assert: (nonEmpty ifNotEmpty: [self]) == self. self assert: (nonEmpty ifNotEmpty: [:s | s first]) = #x ! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:13'! testIfNotEmptyDo empty ifNotEmptyDo: [:s | self assert: false]. self assert: (nonEmpty ifNotEmptyDo: [:s | s first]) = #x ! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:13'! testIfNotEmptyDoifNotEmpty self assert: (empty ifNotEmptyDo: [:s | false] ifEmpty: [true]). self assert: (nonEmpty ifNotEmptyDo: [:s | s first = #x] ifEmpty: [false])! ! !CollectionTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:13'! testIfNotEmptyifEmpty self assert: (empty ifEmpty: [true] ifNotEmpty: [false]). self assert: (nonEmpty ifEmpty: [false] ifNotEmpty: [true]). self assert: (nonEmpty ifEmpty: [false] ifNotEmpty: [:s | s first = #x])! ! 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: '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: 'st 9/27/2004 13:42'! asHTMLColor ^ '#', (self class hex: self red), (self class hex: self green), (self class hex: self blue)! ! !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 4/18/2001 16:25'! makeForegroundColor "Make a foreground color contrasting with me" ^self luminance >= 0.5 ifTrue: [Color black] ifFalse: [Color white]! ! !Color methodsFor: 'conversions' stamp: 'ar 5/15/2001 16:12'! pixelValue32 "Note: pixelWord not pixelValue so we include translucency" ^self pixelWordForDepth: 32! ! !Color methodsFor: 'conversions' stamp: 'jm 1/26/2001 15:11'! 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 | 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: [ "eight bits per component; top 8 bits set to all ones (opaque alpha)" val _ LargePositiveInteger new: 4. val at: 3 put: ((rgb bitShift: -22) bitAnd: 16rFF). val at: 2 put: ((rgb bitShift: -12) bitAnd: 16rFF). val at: 1 put: ((rgb bitShift: -2) bitAnd: 16rFF). val = 0 ifTrue: [val at: 1 put: 1]. "closest non-transparent black" val at: 4 put: 16rFF. "opaque alpha" ^ 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: '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: '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: '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: 'Morphic menu' stamp: 'dgd 10/17/2003 12:10'! addFillStyleMenuItems: aMenu hand: aHand from: aMorph "Add the items for changing the current fill style of the receiver" aMenu add: 'change color...' translated 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 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: 'ar 8/16/2001 12:47'! raisedColor ^ 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: '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: '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: 'queries' stamp: 'sw 9/27/2001 17:26'! basicType "Answer a symbol representing the inherent type of the receiver" ^ #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 4/20/2001 04:33'! isOpaque ^true! ! !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: 'self evaluating' stamp: 'sd 7/31/2005 21:46'! isSelfEvaluating ^ self class == Color! ! !Color methodsFor: 'transformations' stamp: 'fbs 2/3/2005 13:09'! * aNumberOrArray "Answer this color with its RGB multiplied by the given number, or multiply this color's RGB values by the corresponding entries in the given array." "(Color brown * 2) display" "(Color brown * #(1 0 1)) display" | multipliers | multipliers := aNumberOrArray isCollection ifTrue: [aNumberOrArray] ifFalse: [Array with: aNumberOrArray with: aNumberOrArray with: aNumberOrArray]. ^ Color basicNew setPrivateRed: (self privateRed * multipliers first) asInteger green: (self privateGreen * multipliers second) asInteger blue: (self privateBlue * multipliers third) 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: 'dew 3/19/2002 23:50'! adjustBrightness: brightness "Adjust the relative brightness of this color. (lowest value is 0.005 so that hue information is not lost)" ^ Color h: self hue s: self saturation v: (self brightness + brightness min: 1.0 max: 0.005) alpha: self alpha! ! !Color methodsFor: 'transformations' stamp: 'dew 3/19/2002 23:51'! adjustSaturation: saturation brightness: brightness "Adjust the relative saturation and brightness of this color. (lowest value is 0.005 so that hue information is not lost)" ^ Color h: self hue s: (self saturation + saturation min: 1.0 max: 0.005) v: (self brightness + brightness min: 1.0 max: 0.005) alpha: self alpha! ! !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: 'nk 3/8/2004 09:43'! atMostAsLuminentAs: aFloat | revisedColor | revisedColor _ self. [revisedColor luminance > aFloat] whileTrue: [revisedColor _ revisedColor slightlyDarker]. ^revisedColor ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/23/2002 01:38'! blacker ^ self alphaMixed: 0.8333 with: Color black ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/19/2002 23:54'! dansDarker "Return a darker shade of the same color. An attempt to do better than the current darker method. (now obsolete, since darker has been changed to do this. -dew)" ^ Color h: self hue s: self saturation v: (self brightness - 0.16 max: 0.0)! ! !Color methodsFor: 'transformations' stamp: 'dew 3/4/2002 01:40'! darker "Answer a darker shade of this color." ^ self adjustBrightness: -0.08! ! !Color methodsFor: 'transformations' stamp: 'dew 3/8/2002 00:13'! duller ^ self adjustSaturation: -0.03 brightness: -0.2! ! !Color methodsFor: 'transformations' stamp: 'dew 1/23/2002 20:19'! lighter "Answer a lighter shade of this color." ^ self adjustSaturation: -0.03 brightness: 0.08! ! !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: 'dew 1/19/2002 01:29'! muchDarker ^ self alphaMixed: 0.5 with: Color black ! ! !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: 'dew 3/4/2002 01:42'! paler "Answer a paler shade of this color." ^ self adjustSaturation: -0.09 brightness: 0.09 ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/4/2002 01:43'! slightlyDarker ^ self adjustBrightness: -0.03 ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/4/2002 01:43'! slightlyLighter ^ self adjustSaturation: -0.01 brightness: 0.03! ! !Color methodsFor: 'transformations' stamp: 'dew 1/19/2002 01:25'! slightlyWhiter ^ self alphaMixed: 0.85 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/4/2002 01:44'! twiceDarker "Answer a significantly darker shade of this color." ^ self adjustBrightness: -0.15! ! !Color methodsFor: 'transformations' stamp: 'dew 3/4/2002 01:45'! twiceLighter "Answer a significantly lighter shade of this color." ^ self adjustSaturation: -0.06 brightness: 0.15! ! !Color methodsFor: 'transformations' stamp: 'tk 7/4/2000 12:07'! veryMuchLighter ^ self alphaMixed: 0.1165 with: Color white ! ! !Color methodsFor: 'transformations' stamp: 'dew 3/23/2002 01:38'! whiter ^ self alphaMixed: 0.8333 with: Color white ! ! !Color methodsFor: '*eToys-other' stamp: 'sw 6/10/1998 17:50'! newTileMorphRepresentative ^ ColorTileMorph new colorSwatchColor: self! ! !Color methodsFor: '*MorphicExtras-*morphic-Postscript Canvases'! encodePostscriptOn: aStream aStream setrgbcolor:self. ! ! !Color methodsFor: '*nebraska-*nebraska-Morphic-Remote' 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: '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: 'html' stamp: 'ms 6/14/2006 11:18'! printHtmlString "answer a string whose characters are the html representation of the receiver" ^ (self red * 255) asInteger printStringHex , (self green * 255) asInteger printStringHex , (self blue * 255) asInteger printStringHex! ! !Color methodsFor: '*Morphic' stamp: 'ar 7/8/2006 21:00'! iconOrThumbnailOfSize: aNumberOrPoint "Answer an appropiate form to represent the receiver" | form | form := Form extent: aNumberOrPoint asPoint asPoint depth: 32. form fillColor: self. ^ form! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Color class instanceVariableNames: ''! !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: '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 ! ! !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: '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: 'instance creation' stamp: 'ar 4/10/2005 18:45'! 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 or, if it is a list, it can either be an array of three numbers, which will be interpreted as RGB values, or a list of symbols, the first of which is sent to me and then the others of which are in turn sent to the prior result, thus allowing entries of the form #(blue darker). Else just return the thing" | aColor firstParm | (parm isKindOf: Color) ifTrue: [^ parm]. (parm isSymbol) ifTrue: [^ self perform: parm]. (parm isString) ifTrue: [^ self fromString: parm]. ((parm isKindOf: SequenceableCollection) and: [parm size > 0]) ifTrue: [firstParm := parm first. (firstParm isKindOf: Number) ifTrue: [^ self fromRgbTriplet: parm]. aColor := self colorFrom: firstParm. parm doWithIndex: [:sym :ind | ind > 1 ifTrue: [aColor := aColor perform: sym]]. ^ aColor]. ^ parm " Color colorFrom: #(blue darker) Color colorFrom: Color blue darker Color colorFrom: #blue Color colorFrom: #(0.0 0.0 1.0) "! ! !Color class methodsFor: 'instance creation' stamp: 'tk 8/15/2001 11:03'! 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]) ifTrue: [ b = 0 ifTrue: [^Color transparent]. b = 1 ifTrue: [^Color black]]. ^ 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' stamp: 'dew 3/19/2002 23:49'! h: h s: s v: v alpha: alpha ^ (self h: h s: s v: v) alpha: alpha! ! !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: '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: 'other'! colorNames "Return a collection of color names." ^ ColorNames! ! !Color class methodsFor: 'other' stamp: 'BG 3/16/2005 08:18'! hex: aFloat "Return an hexadecimal two-digits string between 00 and FF for a float between 0.0 and 1.0" | str | str := ((aFloat * 255) asInteger printStringHex) asLowercase. str size = 1 ifTrue: [^'0',str] ifFalse: [^str]! ! !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! ! ArrayedCollection variableWordSubclass: #ColorArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Collections-Arrayed'! !ColorArray methodsFor: 'accessing' stamp: 'ar 3/3/2001 20:03'! at: index ^(super at: index) asColorOfDepth: 32! ! !ColorArray methodsFor: 'accessing' stamp: 'ar 3/3/2001 20:04'! at: index put: aColor ^super at: index put: (aColor pixelWordForDepth: 32).! ! !ColorArray methodsFor: 'converting' stamp: 'ar 3/3/2001 20:06'! asColorArray ^self! ! !ColorArray methodsFor: 'converting' stamp: 'RAA 3/8/2001 06:24'! bytesPerElement ^4! ! 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' stamp: 'ar 5/17/2001 15:45'! 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: self 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: '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/17/2001 15:44'! 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: self 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 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: 'color mapping' stamp: 'ar 5/17/2001 15:44'! colormapIfNeededFor: destForm | newMap color pv | (self hasNonStandardPalette or:[destForm hasNonStandardPalette]) ifFalse:[ ^self colormapIfNeededForDepth: destForm depth. ]. colors == nil ifTrue: [ "use the standard colormap" ^ super colormapIfNeededFor: destForm]. (destForm depth = cachedDepth and:[cachedColormap isColormap]) ifTrue: [^ cachedColormap]. newMap _ WordArray new: (1 bitShift: self 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 methodsFor: 'copying' stamp: 'RAA 8/14/2000 10:45'! asCursorForm ^ (self asFormOfDepth: 32) offset: offset; as: StaticForm! ! !ColorForm methodsFor: 'copying' stamp: 'ar 10/24/2005 22:25'! blankCopyOf: aRectangle scaledBy: scale ^Form extent: (aRectangle extent * scale) truncated depth: 32! ! !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: 'displaying' stamp: 'ar 5/14/2001 23:32'! 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 colormapIfNeededFor: aDisplayMedium). ! ! !ColorForm methodsFor: 'displaying' stamp: 'di 7/17/97 10:04'! displayOnPort: port at: location port copyForm: self to: location rule: Form paint! ! !ColorForm methodsFor: 'displaying' stamp: 'ar 12/14/2001 18:14'! maskingMap "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." | maskingMap | maskingMap _ Bitmap new: (1 bitShift: depth) withAll: 16rFFFFFFFF. 1 to: colors size do:[:i| (colors at: i) isTransparent ifTrue:[maskingMap at: i put: 0]. ]. colors size+1 to: maskingMap size do:[:i| maskingMap at: i put: 0]. ^maskingMap! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'ar 3/3/2001 20:07'! hibernate "Make myself take up less space. See comment in Form>hibernate." super hibernate. self clearColormapCache. colors ifNotNil:[colors _ colors asColorArray].! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'mu 8/17/2003 00:46'! readAttributesFrom: aBinaryStream super readAttributesFrom: aBinaryStream. colors _ ColorArray new: (2 raisedTo: depth). 1 to: colors size do: [:idx | colors basicAt: idx put: (aBinaryStream nextLittleEndianNumber: 4). ]. ! ! !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: 'fileIn/Out' stamp: 'ar 3/3/2001 20:07'! unhibernate colors ifNotNil:[colors _ colors asArray]. ^super unhibernate. ! ! !ColorForm methodsFor: 'fileIn/Out' stamp: 'mu 8/17/2003 00:42'! writeAttributesOn: file | colorArray | super writeAttributesOn: file. colorArray _ self colors asColorArray. 1 to: (2 raisedTo: depth) do: [:idx | file nextLittleEndianNumber: 4 put: (colorArray basicAt: idx). ] ! ! !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: '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'! 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'! 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: '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: 'testing' stamp: 'ar 5/27/2001 16:34'! isColorForm ^true! ! !ColorForm methodsFor: 'testing' stamp: 'ar 2/10/2004 17:18'! isTranslucent "Answer whether this form may be translucent" ^true! ! !ColorForm methodsFor: '*MorphicExtras-*morphic-Postscript Canvases' 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: '*MorphicExtras-*morphic-Postscript Canvases'! 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: '*nebraska-*nebraska-Morphic-Remote' stamp: 'sd 11/20/2005 21:25'! 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: '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: 'ar 5/17/2001 15:44'! ensureColorArrayExists "Return my color palette." colors ifNil: [ self depth > 8 ifTrue: [^ self error: 'ColorForms only support depths up to 8 bits']. self colors: (Color indexedColors copyFrom: 1 to: (1 bitShift: self 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 class instanceVariableNames: ''! !ColorForm class methodsFor: 'as yet unclassified' stamp: 'nk 4/17/2004 19:44'! 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 isColorForm) 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: 'comparing' stamp: 'tk 7/5/2001 21:59'! = 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 methodsFor: 'pixel mapping' stamp: 'ar 5/15/2001 16:12'! 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 ifNotNil:[pv _ colors at: pv]. "Need to check for translucency else Form>>paint goes gaga" pv = 0 ifTrue:[pixelValue = 0 ifFalse:[pv _ 1]]. ^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 5/15/2001 16:12'! pixelMap: pixelValue "Perform a reverse pixel mapping operation" | pv | colors == nil ifTrue:[pv _ pixelValue] ifFalse:[pv _ colors at: pixelValue]. (shifts == nil and:[masks == nil]) ifFalse:[pv _ (((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))]. "Need to check for translucency else Form>>paint goes gaga" pv = 0 ifTrue:[pixelValue = 0 ifFalse:[pv _ 1]]. ^pv! ! !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: '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 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 5/4/2001 15:59'! masks: maskArray shifts: shiftArray ^self shifts: shiftArray masks: maskArray colors: nil.! ! !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! ! Canvas subclass: #ColorMappingCanvas instanceVariableNames: 'myCanvas' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !ColorMappingCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 17:40'! clipRect ^myCanvas clipRect! ! !ColorMappingCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 17:39'! depth ^myCanvas depth! ! !ColorMappingCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 17:39'! extent ^myCanvas extent! ! !ColorMappingCanvas methodsFor: 'accessing' stamp: 'ar 6/24/1999 17:54'! form ^myCanvas form! ! !ColorMappingCanvas methodsFor: 'accessing' stamp: 'ar 6/22/1999 17:39'! origin ^myCanvas origin! ! !ColorMappingCanvas methodsFor: 'drawing' stamp: 'ar 6/22/1999 18:15'! line: pt1 to: pt2 width: w color: c "Draw a line using the given width and color" myCanvas line: pt1 to: pt2 width: w color: (self mapColor: c).! ! !ColorMappingCanvas methodsFor: 'drawing' stamp: 'ar 6/22/1999 18:16'! paragraph: paragraph bounds: bounds color: c "Draw the given paragraph" myCanvas paragraph: paragraph bounds: bounds color: (self mapColor: c)! ! !ColorMappingCanvas methodsFor: 'drawing-images' stamp: 'ar 6/24/1999 18:26'! stencil: aForm at: aPoint color: aColor myCanvas stencil: aForm at: aPoint color: (self mapColor: aColor)! ! !ColorMappingCanvas methodsFor: 'drawing-images' stamp: 'ar 6/24/1999 18:26'! stencil: aForm at: aPoint sourceRect: aRect color: aColor myCanvas stencil: aForm at: aPoint sourceRect: aRect color: (self mapColor: aColor)! ! !ColorMappingCanvas methodsFor: 'drawing-ovals' stamp: 'ar 6/22/1999 17:59'! fillOval: r color: c borderWidth: borderWidth borderColor: borderColor "Fill the given oval." myCanvas fillOval: r color: (self mapColor: c) borderWidth: borderWidth borderColor: (self mapColor: borderColor)! ! !ColorMappingCanvas methodsFor: 'drawing-polygons' stamp: 'mir 9/12/2001 14:24'! drawPolygon: vertices color: aColor borderWidth: bw borderColor: bc "Draw the given polygon." ^myCanvas drawPolygon: vertices color: aColor borderWidth: bw borderColor: (self mapColor: bc)! ! !ColorMappingCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/22/1999 17:59'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth borderColor: borderColor "Draw the rectangle using the given attributes" myCanvas frameAndFillRectangle: r fillColor: (self mapColor: fillColor) borderWidth: borderWidth borderColor: (self mapColor: borderColor)! ! !ColorMappingCanvas methodsFor: 'drawing-rectangles' stamp: 'ar 6/22/1999 18:01'! frameAndFillRectangle: r fillColor: fillColor borderWidth: borderWidth topLeftColor: topLeftColor bottomRightColor: bottomRightColor "Draw the rectangle using the given attributes" myCanvas frameAndFillRectangle: r fillColor: (self mapColor: fillColor) borderWidth: borderWidth topLeftColor: (self mapColor: topLeftColor) bottomRightColor: (self mapColor: bottomRightColor)! ! !ColorMappingCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 18:19'! 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" | oldCanvas | oldCanvas _ myCanvas. myCanvas clipBy: aRectangle during:[:newCanvas| myCanvas _ newCanvas. aBlock value: self]. myCanvas _ oldCanvas! ! !ColorMappingCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 18:19'! preserveStateDuring: aBlock "Preserve the full canvas state during the execution of aBlock" | oldCanvas | oldCanvas _ myCanvas. myCanvas preserveStateDuring:[:newCanvas| myCanvas _ newCanvas. aBlock value: self]. myCanvas _ oldCanvas.! ! !ColorMappingCanvas methodsFor: 'drawing-support' stamp: 'di 10/16/1999 16:01'! 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')." | oldCanvas | oldCanvas _ myCanvas. myCanvas transformBy: aDisplayTransform clippingTo: aClipRect during: [:newCanvas | myCanvas _ newCanvas. aBlock value: self] smoothing: cellSize. myCanvas _ oldCanvas.! ! !ColorMappingCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 18:22'! translateBy: delta during: aBlock "Set a translation only during the execution of aBlock." | oldCanvas | oldCanvas _ myCanvas. myCanvas translateBy: delta during:[:newCanvas| myCanvas _ newCanvas. aBlock value: self]. myCanvas _ oldCanvas.! ! !ColorMappingCanvas methodsFor: 'drawing-support' stamp: 'ar 6/22/1999 18:22'! translateTo: newOrigin clippingTo: aRectangle during: aBlock "Set a new origin and clipping rectangle only during the execution of aBlock." | oldCanvas | oldCanvas _ myCanvas. myCanvas translateTo: newOrigin clippingTo: aRectangle during:[:newCanvas| myCanvas _ newCanvas. aBlock value: self]. myCanvas _ oldCanvas.! ! !ColorMappingCanvas methodsFor: 'drawing-text' stamp: 'ar 12/31/2001 02:28'! drawString: s from: firstIndex to: lastIndex in: 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." myCanvas drawString: s from: firstIndex to: lastIndex in: boundsRect font: fontOrNil color: (self mapColor: c)! ! !ColorMappingCanvas methodsFor: 'initialization' stamp: 'ar 6/22/1999 18:24'! flush myCanvas flush.! ! !ColorMappingCanvas methodsFor: 'initialization' stamp: 'ar 8/8/2001 14:14'! on: aCanvas myCanvas _ aCanvas.! ! !ColorMappingCanvas methodsFor: 'initialization' stamp: 'ar 6/22/1999 18:23'! reset myCanvas reset.! ! !ColorMappingCanvas methodsFor: 'other' stamp: 'ar 6/22/1999 18:21'! translateBy: delta clippingTo: aRectangle during: aBlock "Set a translation and clipping rectangle only during the execution of aBlock." | oldCanvas | oldCanvas _ myCanvas. myCanvas translateBy: delta clippingTo: aRectangle during:[:newCanvas| myCanvas _ newCanvas. aBlock value: self]. myCanvas _ oldCanvas.! ! !ColorMappingCanvas methodsFor: 'testing' stamp: 'ar 8/8/2001 14:16'! isShadowDrawing ^myCanvas isShadowDrawing! ! !ColorMappingCanvas methodsFor: 'private' stamp: 'ar 8/8/2001 14:15'! image: aForm at: aPoint sourceRect: sourceRect rule: rule "Draw the given form. For the 'paint' combination rule use stenciling otherwise simply fill the source rectangle." ^myCanvas image: aForm at: aPoint sourceRect: sourceRect rule: rule.! ! !ColorMappingCanvas methodsFor: 'private' stamp: 'ar 8/8/2001 14:15'! mapColor: aColor ^aColor! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorMappingCanvas class instanceVariableNames: ''! !ColorMappingCanvas class methodsFor: 'instance creation' stamp: 'ar 6/22/1999 18:23'! on: aCanvas ^self new on: aCanvas! ! 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: '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 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: 'initialization' 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: 'other' 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: '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 commentStamp: 'kfr 10/27/2003 16:16' prior: 0! A gui for setting color and transparency. Behaviour can be changed with the Preference modalColorPickers.! !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: 'ar 8/25/2001 20:44'! locationIndicator | loc | ^self valueOfProperty: #locationIndicator ifAbsent:[ loc _ EllipseMorph new. loc color: Color transparent; borderWidth: 1; borderColor: Color red; extent: 6@6. self setProperty: #locationIndicator toValue: loc. self addMorphFront: loc. loc]! ! !ColorPickerMorph methodsFor: 'accessing' stamp: 'KR 12/9/2005 22:51'! 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 isColor) ifTrue: [colorOrSymbol] ifFalse: [Color lightGreen]. originalForm fill: RevertBox fillColor: originalColor. selectedColor := originalColor. self updateAlpha: originalColor alpha. self locationIndicator center: self topLeft + (self positionOfColor: 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: 'aoy 2/15/2003 21:24'! target: anObject target := anObject. selectedColor := (target respondsTo: #color) ifTrue: [target color] ifFalse: [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: 'drawing' 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: 'event handling' stamp: 'jm 11/4/97 07:15'! handlesMouseDown: evt ^ true ! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'RAA 2/19/2001 13:16'! inhibitDragging ^self hasProperty: #noDraggingThisPicker! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'RAA 2/19/2001 13:17'! mouseDown: evt | localPt | localPt _ evt cursorPoint - self topLeft. self deleteAllBalloons. clickedTranslucency _ TransparentBox containsPoint: localPt. self inhibitDragging ifFalse: [ (DragBox containsPoint: localPt) ifTrue: [^ evt hand grabMorph: self]. ]. (RevertBox containsPoint: localPt) ifTrue: [^ self updateColor: originalColor feedbackColor: originalColor]. self inhibitDragging ifFalse: [self comeToFront]. sourceHand _ evt hand. self startStepping. ! ! !ColorPickerMorph methodsFor: 'event handling' stamp: 'yo 9/29/2004 10:24'! mouseUp: evt | c | self stopStepping. sourceHand _ nil. deleteOnMouseUp ifTrue: [self delete]. c _ self getColorFromKedamaWorldIfPossible: evt cursorPoint. c ifNotNil: [selectedColor _ c]. self updateTargetColor. ! ! !ColorPickerMorph methodsFor: 'geometry testing' stamp: 'LC 2/2/2000 04:28'! containsPoint: aPoint ^ (super containsPoint: aPoint) or: [RevertBox containsPoint: aPoint - self topLeft]! ! !ColorPickerMorph methodsFor: 'halos and balloon help' stamp: 'sw 7/6/1999 09:07'! isLikelyRecipientForMouseOverHalos ^ false! ! !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: 'ar 9/4/2001 13:26'! 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 clipSubmorphs: true. self buildChartForm. selectedColor _ Color white. sourceHand _ nil. deleteOnMouseUp _ false. clickedTranslucency _ false. updateContinuously _ true. selector _ nil. target _ nil! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'yo 2/23/2005 17:17'! initializeForPropertiesPanel "Initialize the receiver. If beModal is true, it will be a modal color picker, else not" isModal _ false. self removeAllMorphs. self setProperty: #noDraggingThisPicker toValue: true. self addMorph: ((Morph newBounds: (RevertBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'restore original color' translated). self addMorph: ((Morph newBounds: (FeedbackBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'shows selected color' translated). self addMorph: ((Morph newBounds: (TransparentBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'adjust translucency' translated). self buildChartForm. selectedColor ifNil: [selectedColor _ Color white]. sourceHand _ nil. deleteOnMouseUp _ false. updateContinuously _ true. ! ! !ColorPickerMorph methodsFor: 'initialization' stamp: 'yo 2/23/2005 17:13'! 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' translated). 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' translated)]. self addMorph: ((Morph newBounds: (DragBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'put me somewhere' translated). self addMorph: ((Morph newBounds: (RevertBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'restore original color' translated). self addMorph: ((Morph newBounds: (FeedbackBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'shows selected color' translated). self addMorph: ((Morph newBounds: (TransparentBox translateBy: self topLeft)) color: Color transparent; setCenteredBalloonText: 'adjust translucency' translated). 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: 'menu' stamp: 'dgd 8/30/2003 21:17'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. deleteOnMouseUp ifTrue: [aCustomMenu add: 'stay up' translated action: #toggleDeleteOnMouseUp] ifFalse: [aCustomMenu add: 'do not stay up' translated action: #toggleDeleteOnMouseUp]. updateContinuously ifTrue: [aCustomMenu add: 'update only at end' translated action: #toggleUpdateContinuously] ifFalse: [aCustomMenu add: 'update continuously' translated action: #toggleUpdateContinuously]. ! ! !ColorPickerMorph methodsFor: 'menu' stamp: 'yo 9/29/2004 10:38'! pickUpColorFor: aMorph "Show the eyedropper cursor, and modally track the mouse through a mouse-down and mouse-up cycle" | aHand localPt c | 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. localPt _ Sensor cursorPoint - self topLeft. self inhibitDragging ifFalse: [ (DragBox containsPoint: localPt) 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]. c _ self getColorFromKedamaWorldIfPossible: Sensor cursorPoint. c ifNotNil: [selectedColor _ c]. 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: '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: '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 methodsFor: 'stepping and presenter' stamp: 'jm 11/4/97 07:15'! step sourceHand ifNotNil: [self pickColorAt: sourceHand position]. ! ! !ColorPickerMorph methodsFor: 'submorphs-add/remove' stamp: 'nk 4/17/2004 19:34'! 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" (selector isNil or: [ target isNil ]) ifFalse: [ self rememberCommand: (Command new cmdWording: 'color change' translated; undoTarget: target selector: selector arguments: (self argumentsWith: originalColor); redoTarget: target selector: selector arguments: (self argumentsWith: selectedColor)). ]. super delete! ! !ColorPickerMorph methodsFor: 'testing' stamp: 'jm 11/4/97 07:15'! stepTime ^ 50 ! ! !ColorPickerMorph methodsFor: '*eToys-e-toy support' stamp: 'sw 7/6/1999 09:00'! isCandidateForAutomaticViewing ^ false! ! !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: 'ar 7/19/2003 20:40'! argumentsWith: aColor "Return an argument array appropriate to this action selector" | nArgs | nArgs _ selector ifNil:[0] ifNotNil:[selector numArgs]. nArgs = 0 ifTrue:[^#()]. 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: 'dgd 2/21/2003 22:59'! modalBalloonHelpAtPoint: cursorPoint self flag: #arNote. "Throw this away. There needs to be another way." self submorphsDo: [:m | m wantsBalloon ifTrue: [(m valueOfProperty: #balloon) isNil ifTrue: [(m containsPoint: cursorPoint) ifTrue: [m showBalloon: m balloonText]] ifFalse: [(m containsPoint: cursorPoint) ifFalse: [m deleteBalloon]]]]! ! !ColorPickerMorph methodsFor: 'private' stamp: 'yo 9/29/2004 10:57'! pickColorAt: aGlobalPoint | alpha selfRelativePoint pickedColor c | 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" self locationIndicator visible: false. self refreshWorld. pickedColor _ Display colorAt: aGlobalPoint. c _ self getColorFromKedamaWorldIfPossible: aGlobalPoint. c ifNotNil: [pickedColor _ c]. self locationIndicator visible: true. self refreshWorld. self updateColor: ( (selectedColor isColor and: [selectedColor isTranslucentColor]) ifTrue: [pickedColor alpha: selectedColor alpha] ifFalse: [pickedColor] ) feedbackColor: pickedColor! ! !ColorPickerMorph methodsFor: 'private' stamp: 'ar 9/4/2001 13:27'! positionOfColor: aColor "Compute the position of the given color in the color chart form" | rgbRect x y h s v | rgbRect _ (0@0 extent: originalForm boundingBox extent) insetBy: (1@10 corner: 11@1). h _ aColor hue. s _ aColor saturation. v _ aColor brightness. h = 0.0 ifTrue:["gray" ^(rgbRect right + 6) @ (rgbRect height * (1.0 - v) + rgbRect top)]. x _ (h + 22 \\ 360 / 360.0 * rgbRect width) rounded. y _ 0.5. s < 1.0 ifTrue:[y _ y - (1.0 - s * 0.5)]. v < 1.0 ifTrue:[y _ y + (1.0 - v * 0.5)]. y _ (y * rgbRect height) rounded. ^x@y + (1@10)! ! !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: 'ar 8/25/2001 20:50'! 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]. self locationIndicator center: self topLeft + (self positionOfColor: feedbackColor).! ! !ColorPickerMorph methodsFor: 'private' stamp: 'dgd 2/22/2003 18:41'! updateTargetColor | nArgs | (target notNil and: [selector notNil]) 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: 'dgd 2/22/2003 18:41'! updateTargetColorWith: aColor "Update the target so that it reflects aColor as the color choice" (target notNil and: [selector notNil]) ifTrue: [self updateSelectorDisplay. ^target perform: selector withArguments: (self argumentsWith: aColor)]! ! !ColorPickerMorph methodsFor: 'kedama' stamp: 'yo 9/29/2004 10:43'! getColorFromKedamaWorldIfPossible: aGlobalPoint self world submorphs do: [:sub | (sub isKindOf: KedamaMorph) ifTrue: [ sub morphsAt: aGlobalPoint unlocked: false do: [:e | ^ e colorAt: (aGlobalPoint - e topLeft). ]. ]. ]. ^ nil. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorPickerMorph class instanceVariableNames: ''! !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! ! !ColorPickerMorph class methodsFor: 'class initialization' stamp: 'ar 7/8/2006 20:32'! 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 ! ! !ColorPickerMorph class methodsFor: 'class initialization' stamp: 'ar 7/8/2006 20:33'! initialize "ColorPickerMorph initialize" ColorChart _ ColorPickerMorph 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). self localeChanged.! ! !ColorPickerMorph class methodsFor: 'class initialization' stamp: 'tak 8/4/2005 14:26'! localeChanged | formTranslator | formTranslator := NaturalLanguageFormTranslator localeID: Locale current localeID. TransText := formTranslator translate: 'translucent'. TransText ifNil: [TransText := Form extent: 63 @ 8 depth: 1 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: 'class initialization' stamp: 'ar 7/8/2006 20:33'! noColorCaption | formTranslator | formTranslator := NaturalLanguageFormTranslator localeID: Locale current localeID. ^ (formTranslator translate: 'no color') ifNil: [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] ! ! ColorTileMorph subclass: #ColorSeerTile instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Tiles'! !ColorSeerTile methodsFor: 'code generation' stamp: 'dgd 2/22/2003 14:25'! storeCodeOn: aStream indent: tabCount "We have a hidden arg. Output two keywords with interspersed arguments." | parts | parts := operatorOrExpression keywords. "color:sees:" ^aStream nextPutAll: (parts first); space; nextPutAll: colorSwatch color printString; space; nextPutAll: (parts second)! ! !ColorSeerTile methodsFor: 'initialization' stamp: 'mir 7/12/2004 20:23'! initialize "initialize the state of the receiver" | m1 m2 desiredW wording | super initialize. "" self removeAllMorphs. "get rid of the parts of a regular Color tile" type _ #operator. operatorOrExpression _ #color:sees:. wording _ (Vocabulary eToyVocabulary methodInterfaceAt: operatorOrExpression ifAbsent: []) wording. m1 _ StringMorph contents: wording 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: 'initialization' stamp: 'mir 7/15/2004 15:20'! updateWordingToMatchVocabulary "The current vocabulary has changed; change the wording on my face, if appropriate" | aMethodInterface | aMethodInterface _ self currentVocabulary methodInterfaceAt: operatorOrExpression ifAbsent: [Vocabulary eToyVocabulary methodInterfaceAt: operatorOrExpression ifAbsent: [^ self]]. self labelMorph contents: aMethodInterface wording. self setBalloonText: aMethodInterface helpMessage.! ! UpdatingRectangleMorph subclass: #ColorSwatch instanceVariableNames: 'argument' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Support'! !ColorSwatch methodsFor: 'as yet unclassified' stamp: 'sw 7/13/1999 13:39'! argument: arg argument _ arg! ! !ColorSwatch methodsFor: 'setting' stamp: 'sw 3/23/2001 12:12'! setTargetColor: aColor "Set the target color as indicated" putSelector ifNotNil: [self color: aColor. contents _ aColor. target perform: self putSelector withArguments: (Array with: argument with: aColor)] ! ! !ColorSwatch methodsFor: 'target access' stamp: 'dgd 2/22/2003 13:32'! readFromTarget "Obtain a value from the target and set it into my lastValue" | v | (target isNil or: [getSelector isNil]) ifTrue: [^contents]. v := target perform: getSelector with: argument. lastValue := v. ^v! ! !ColorSwatch methodsFor: 'testing' 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] ! ! ClassTestCase subclass: #ColorTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'GraphicsTests-Primitives'! !ColorTest methodsFor: 'testing' stamp: 'fbs 2/3/2005 13:13'! testMultiplyByArray | newColor oldColor tolerance | tolerance := 0.001. oldColor := Color r: 0.75 g: 0.5 b: 0.25. newColor := oldColor * #(0.1 2 3). self assert: (0.075 - newColor red) abs < tolerance. self assert: (1 - newColor green) abs < tolerance. self assert: (0.75 - newColor blue) abs < tolerance.! ! !ColorTest methodsFor: 'testing' stamp: 'fbs 2/3/2005 12:57'! testMultiplyByArrayIdentityTransform | newColor oldColor tolerance | tolerance := 0.001. oldColor := Color r: 0.75 g: 0.5 b: 0.25. newColor := oldColor * 2. self assert: (1 - newColor red) abs < tolerance. self assert: (1 - newColor green) abs < tolerance. self assert: (0.5 - newColor blue) abs < tolerance.! ! !ColorTest methodsFor: 'testing' stamp: 'fbs 2/3/2005 12:56'! testMultiplyByNumber | newColor oldColor tolerance | tolerance := 0.001. oldColor := Color r: 0.75 g: 0.5 b: 0.25. newColor := oldColor * 2. self assert: (1 - newColor red) abs < tolerance. self assert: (1 - newColor green) abs < tolerance. self assert: (0.5 - newColor blue) abs < tolerance.! ! !ColorTest methodsFor: 'tests' stamp: 'st 9/27/2004 13:43'! testAsHTMLColor self assert: (Color white asHTMLColor = '#ffffff'). self assert: (Color black asHTMLColor = '#000000').! ! !ColorTest methodsFor: 'tests' stamp: 'st 9/27/2004 13:45'! testColorFrom self assert: ((Color colorFrom: #white) asHTMLColor = '#ffffff'). self assert: ((Color colorFrom: #(1.0 0.5 0.0)) asHTMLColor = '#ff7f00'). self assert: ((Color colorFrom: (Color white)) asHTMLColor = '#ffffff'). self assert: ((Color colorFrom: '#FF8800') asHTMLColor = '#ff8800').! ! !ColorTest methodsFor: 'tests' stamp: 'sd 6/5/2005 10:14'! testFromString self assert: ((Color fromString: '#FF8800') asHTMLColor = '#ff8800').! ! !ColorTest methodsFor: 'tests' stamp: 'sd 6/16/2006 13:12'! testPrintHtmlString "self debug: #testPrintHtmlString" self shouldnt: [Color white printHtmlString ] raise: Error. self assert: Color white printHtmlString = 'FFFFFF'. self assert: Color red printHtmlString = 'FF0000'. self assert: Color black printHtmlString = '000000'.! ! Object subclass: #ColorTheme instanceVariableNames: '' classVariableNames: 'Current' poolDictionaries: '' category: 'System-Support'! !ColorTheme methodsFor: 'applying' stamp: 'dgd 4/3/2006 12:05'! apply "apply the receiver as the current theme" BalloonMorph setBalloonColorTo: self balloonColor. Preferences setParameter: #defaultWorldColor to: self defaultWorldColor. Preferences insertionPointColor: self insertionPointColor. Preferences keyboardFocusColor: self keyboardFocusColor. Preferences textHighlightColor: self textHighlightColor. Preferences setParameter: #menuTitleColor to: self menuTitleColor. Preferences setParameter: #menuTitleBorderColor to: self menuTitleBorderColor. Preferences setParameter: #menuTitleBorderWidth to: self menuTitleBorderWidth. Preferences setParameter: #menuColor to: self menuColor. Preferences setParameter: #menuBorderColor to: self menuBorderColor. Preferences setParameter: #menuLineColor to: self menuLineColor. Preferences setParameter: #menuBorderWidth to: self menuBorderWidth. Preferences setParameter: #menuSelectionColor to: self menuSelectionColor. SystemProgressMorph reset. self class current: self. ! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 17:33'! balloonColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 21:23'! cancelColor ^ Color lightRed! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 17:33'! defaultWorldColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 1/7/2005 18:37'! helpColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 17:33'! insertionPointColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 17:33'! keyboardFocusColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 21:22'! okColor ^ Color lightGreen! ! !ColorTheme methodsFor: 'theme' stamp: 'dgd 11/2/2004 17:33'! textHighlightColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/3/2004 19:27'! dialog3DTitles ^ true! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 19:56'! dialogBorderColor ^ Color fromArray: #(0.355 0.516 1.0 )! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 19:54'! dialogBorderWidth ^ 4! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 19:59'! dialogButtonBorderWidth ^ 0! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 21:09'! dialogColor ^ Color paleYellow! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/3/2004 19:49'! dialogPaneBorderColor ^ Color black ! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/3/2004 19:44'! dialogPaneBorderWidth ^ 0! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/3/2004 19:30'! dialogPaneRampOrColor ^ {0.0 -> (Color r: 0.742 g: 0.871 b: 1.0). 1.0 -> (Color r: 0.516 g: 0.645 b: 1.0)}! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/3/2004 19:38'! dialogRampOrColor ^ {0.0 -> (Color r: 0.516 g: 0.645 b: 1.0). 1.0 -> (Color r: 0.742 g: 0.871 b: 1.0)}! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 21:18'! dialogTextBoxBorderColor ^ Color black! ! !ColorTheme methodsFor: 'theme - dialogs' stamp: 'dgd 11/2/2004 21:18'! dialogTextBoxColor ^ Color white! ! !ColorTheme methodsFor: 'theme - dockingbar' stamp: 'dgd 11/2/2004 17:54'! dockingBarAutoGradient ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - dockingbar' stamp: 'dgd 11/2/2004 17:49'! dockingBarColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - dockingbar' stamp: 'dgd 11/2/2004 17:49'! dockingBarGradientRamp ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:30'! menuBorderColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:31'! menuBorderWidth ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:31'! menuColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:32'! menuLineColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:32'! menuSelectionColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:32'! menuTitleBorderColor ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:32'! menuTitleBorderWidth ^ self subclassResponsibility! ! !ColorTheme methodsFor: 'theme - menus' stamp: 'dgd 11/2/2004 17:32'! menuTitleColor ^ self subclassResponsibility! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColorTheme class instanceVariableNames: ''! !ColorTheme class methodsFor: 'applying' stamp: 'dgd 3/31/2006 12:25'! apply ^self new apply! ! !ColorTheme class methodsFor: 'applying' stamp: 'dgd 3/12/2006 13:08'! applyTheme: aThemeClass aThemeClass new apply! ! !ColorTheme class methodsFor: 'accessing' stamp: 'dgd 3/31/2006 12:07'! current ^ Current ifNil: [self defaultTheme apply]! ! !ColorTheme class methodsFor: 'accessing' stamp: 'dgd 11/2/2004 17:52'! current: aColorTheme Current := aColorTheme! ! !ColorTheme class methodsFor: 'accessing' stamp: 'dgd 3/31/2006 13:45'! defaultTheme Smalltalk at: #YellowSmallLandColorTheme ifPresent: [:yellowSmallLandColorTheme | ^ yellowSmallLandColorTheme]. ^ (self allSubclasses select: [:each | each subclasses isEmpty]) anyOne! ! TileMorph subclass: #ColorTileMorph instanceVariableNames: 'colorSwatch' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Tiles'! !ColorTileMorph methodsFor: 'accessing' stamp: 'sw 9/27/2001 17:27'! resultType "Answer the result type of the receiver" ^ #Color! ! !ColorTileMorph methodsFor: 'code generation' stamp: 'yo 4/5/2005 11:19'! kedamaStoreCodeAsPixelValueOn: aStream indent: tabCount aStream nextPutAll: ((colorSwatch color pixelValueForDepth: 32) bitAnd: 16rFFFFFF) printString. ! ! !ColorTileMorph methodsFor: 'code generation' stamp: 'jm 5/28/1998 19:02'! storeCodeOn: aStream indent: tabCount aStream nextPutAll: colorSwatch color printString. ! ! !ColorTileMorph methodsFor: 'event handling'! handlesMouseDown: evt (colorSwatch containsPoint: evt cursorPoint) ifTrue: [^ true] ifFalse: [^ super handlesMouseDown: evt]. ! ! !ColorTileMorph methodsFor: 'event handling'! mouseDown: evt (colorSwatch containsPoint: evt cursorPoint) ifFalse: [super mouseDown: evt]. ! ! !ColorTileMorph methodsFor: 'event handling' stamp: 'yo 9/29/2004 10:50'! mouseUp: evt self changeColorTarget: self selector: #kedamaColorSwatchColor: originalColor: colorSwatch color hand: evt hand ! ! !ColorTileMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 15:44'! initialize "initialize the state of the receiver" super initialize. "" type _ #literal. self addColorSwatch! ! !ColorTileMorph methodsFor: 'initialization' stamp: 'sw 10/13/2004 23:28'! setLiteral: aLiteral "Set the receiver's literal" self colorSwatchColor: aLiteral! ! !ColorTileMorph methodsFor: 'initialization' stamp: 'yo 7/2/2004 17:33'! updateWordingToMatchVocabulary | stringMorph | stringMorph _ submorphs detect: [:morph | morph class == StringMorph] ifNone: [^ self]. stringMorph contents: 'color' translated. ! ! !ColorTileMorph methodsFor: 'other' stamp: 'yo 7/2/2004 17:33'! addColorSwatch | m1 m2 desiredW | m1 _ StringMorph contents: 'color' translated 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 + 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: 'yo 3/24/2005 12:16'! colorSwatchColor: aColor colorSwatch color: aColor. ! ! !ColorTileMorph methodsFor: 'other' stamp: 'yo 3/24/2005 12:17'! kedamaColorSwatchColor: aColor colorSwatch userSelectedColor: aColor. ! ! !ColorTileMorph methodsFor: 'player viewer' stamp: 'sw 1/6/1999 10:43'! updateLiteralLabel "Do nothing"! ! DataType subclass: #ColorType instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Type Vocabularies'! !ColorType commentStamp: 'sw 1/5/2005 22:15' prior: 0! A data type representing a Color value.! !ColorType methodsFor: 'initial value' stamp: 'sw 9/27/2001 17:28'! initialValueForASlotFor: aPlayer "Answer the value to give initially to a newly created slot of the given type in the given player" ^ Color random! ! !ColorType methodsFor: 'initialization' stamp: 'sw 9/27/2001 17:23'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" super initialize. self vocabularyName: #Color.! ! !ColorType methodsFor: 'tiles' stamp: 'yo 4/5/2005 11:36'! updatingTileForTarget: aTarget partName: partName getter: getter setter: setter "Answer, for classic tiles, an updating readout tile for a part with the receiver's type, with the given getter and setter" | readout | readout _ UpdatingRectangleMorph new. readout getSelector: getter; target: aTarget; borderWidth: 1; extent: 22@22. ((aTarget isKindOf: KedamaExamplerPlayer) and: [getter = #getColor]) ifTrue: [ readout getSelector: #getColorOpaque. ]. (setter isNil or: [#(unused none #nil) includes: setter]) ifFalse: [readout putSelector: setter]. ^ readout ! ! !ColorType methodsFor: 'tiles' stamp: 'sw 1/5/2005 19:57'! wantsArrowsOnTiles "Answer whether this data type wants up/down arrows on tiles representing its values" ^ false! ! !ColorType methodsFor: '*eToys-color' stamp: 'sw 9/27/2001 17:21'! typeColor "Answer the color for tiles to be associated with objects of this type" ^ self subduedColorFromTriplet: #(1.0 0 0.065) ! ! !ColorType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:30'! defaultArgumentTile "Answer a tile to represent the type" ^ Color blue newTileMorphRepresentative! ! TextStream subclass: #ColoredCodeStream instanceVariableNames: 'dialect colorTable' classVariableNames: 'ST80ColorTable' poolDictionaries: '' category: 'Compiler-Kernel'! !ColoredCodeStream methodsFor: 'color/style' stamp: 'md 8/14/2005 17:33'! colorTable "Answer the table to use to determine colors" ^ colorTable ifNil: [colorTable _ ST80ColorTable]! ! !ColoredCodeStream methodsFor: 'color/style' stamp: 'sw 5/20/2001 21:05'! withColor: colorSymbol emphasis: emphasisSymbol do: aBlock "Evaluate the given block with the given color and style text attribute" ^ self withAttributes: {TextColor color: (Color perform: colorSymbol). TextEmphasis perform: emphasisSymbol} do: aBlock! ! !ColoredCodeStream methodsFor: 'color/style' stamp: 'sw 5/20/2001 11:30'! withStyleFor: elementType do: aBlock "Evaluate aBlock with appropriate emphasis and color for the given elementType" | colorAndStyle | colorAndStyle _ self colorTable at: elementType. ^ self withColor: colorAndStyle first emphasis: colorAndStyle second do: aBlock! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ColoredCodeStream class instanceVariableNames: ''! !ColoredCodeStream class methodsFor: 'class initialization' stamp: 'wiz 9/12/2005 00:41'! initialize "Initialize the colors that characterize the ST80 dialect" ST80ColorTable _ IdentityDictionary new. #( (temporaryVariable blue italic) (methodArgument blue normal) (methodSelector black bold) (blockArgument red normal) (comment brown normal) (variable magenta normal) (literal orange normal) (keyword darkGray bold) (prefixKeyword veryDarkGray bold) (setOrReturn black bold)) do: [:aTriplet | ST80ColorTable at: aTriplet first put: aTriplet allButFirst] "ColoredCodeStream initialize"! ! !ColoredCodeStream class methodsFor: 'instance creation' stamp: 'md 8/15/2005 11:00'! contents: blockWithArg "Evaluate blockWithArg on a DialectStream of the given description" | stream | stream _ self on: (Text new: 400). blockWithArg value: stream. ^ stream contents! ! Object subclass: #CombinedChar instanceVariableNames: 'codes combined' classVariableNames: 'Compositions Decompositions Diacriticals' poolDictionaries: '' category: 'Multilingual-Scanning'! !CombinedChar methodsFor: 'as yet unclassified' stamp: 'yo 2/10/2004 07:08'! add: char | dict elem | codes ifNil: [codes _ Array with: char. combined _ char. ^ true]. dict _ Compositions at: combined charCode ifAbsent: [^ false]. elem _ dict at: combined charCode ifAbsent: [^ false]. codes _ codes copyWith: char. combined _ elem. ^ true. ! ! !CombinedChar methodsFor: 'as yet unclassified' stamp: 'yo 2/10/2004 07:08'! base ^ codes first. ! ! !CombinedChar methodsFor: 'as yet unclassified' stamp: 'yo 2/10/2004 07:08'! combined ^ combined. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CombinedChar class instanceVariableNames: ''! !CombinedChar class methodsFor: 'as yet unclassified' stamp: 'yo 12/31/2002 19:21'! isDiacriticals: unicode ^ Diacriticals includes: unicode. ! ! !CombinedChar class methodsFor: 'as yet unclassified' stamp: 'yo 12/31/2002 19:09'! parseCompositionMappingFrom: stream " self halt. self parseCompositionMapping " | line fieldEnd point fieldStart compositions toNumber diacritical result | toNumber _ [:quad | ('16r', quad) asNumber]. Compositions _ IdentityDictionary new: 2048. Decompositions _ IdentityDictionary new: 2048. Diacriticals _ IdentitySet new: 2048. [(line _ stream upTo: Character cr) size > 0] whileTrue: [ fieldEnd _ line indexOf: $; startingAt: 1. point _ ('16r', (line copyFrom: 1 to: fieldEnd - 1)) asNumber. 2 to: 6 do: [:i | fieldStart _ fieldEnd + 1. fieldEnd _ line indexOf: $; startingAt: fieldStart. ]. compositions _ line copyFrom: fieldStart to: fieldEnd - 1. (compositions size > 0 and: [compositions first ~= $<]) ifTrue: [ compositions _ compositions substrings collect: toNumber. compositions size > 1 ifTrue: [ diacritical _ compositions first. Diacriticals add: diacritical. result _ compositions second. (Decompositions includesKey: point) ifTrue: [ self error: 'should not happen'. ] ifFalse: [ Decompositions at: point put: (Array with: diacritical with: result). ]. (Compositions includesKey: diacritical) ifTrue: [ (Compositions at: diacritical) at: result put: point. ] ifFalse: [ Compositions at: diacritical put: (IdentityDictionary new at: result put: point; yourself). ]. ]. ]. ]. ! ! Object subclass: #Command instanceVariableNames: 'phase cmdWording undoTarget undoSelector undoArguments redoTarget redoSelector redoArguments parameters' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: '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: 'sw 2/2/2006 02:00'! stillValid "Answer whether the receiver is still valid." ^ (undoTarget isMorph and: [undoTarget isInWorld]) or: [redoTarget isMorph and: [redoTarget isInWorld]]! ! !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: 'copying' stamp: 'tk 2/25/2001 17:53'! 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])]. ! ! !Command methodsFor: 'copying' stamp: 'tk 2/25/2001 17:53'! veryDeepInner: deepCopier "ALL fields are weakly copied!! Can't duplicate an object by duplicating a Command that involves it. See DeepCopier." super veryDeepInner: deepCopier. "just keep old pointers to all fields" parameters _ parameters.! ! !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 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: 'printing' 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: '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: 'dgd 8/26/2003 21:43'! cmdWording "Answer the wording to be used to refer to the command in a menu" ^ cmdWording ifNil: ['last command' translated]! ! !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: 'MorphicExtras-Undo'! !CommandHistory methodsFor: 'called by programmer' stamp: 'sw 2/2/2006 01:48'! assureLastCommandStillValid "If the lastCommand is not valid, set it to nil; answer the lastCommand." lastCommand ifNotNil: [lastCommand stillValid ifFalse: [self cantUndo]]. ^ lastCommand! ! !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: 'aoy 2/15/2003 21:14'! purgeAllCommandsSuchThat: cmdBlock "Remove a bunch of commands, as in [:cmd | cmd undoTarget == zort]" Preferences useUndo ifFalse: [^self]. history := history reject: cmdBlock. lastCommand := history isEmpty ifTrue: [nil] ifFalse: [history last] ! ! !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: 'nb 6/17/2003 12:25'! redoNextCommand "If there is a way to 'redo' (move FORWARD) in the undo/redo history tape, do it." | anIndex | lastCommand ifNil: [^ Beeper beep]. lastCommand phase == #undone ifFalse: [anIndex _ history indexOf: lastCommand. (anIndex < history size) ifTrue: [lastCommand _ history at: anIndex + 1] ifFalse: [^ Beeper beep]]. lastCommand redoCommand. lastCommand phase: #done ! ! !CommandHistory methodsFor: 'called from the ui' stamp: 'nb 6/17/2003 12:25'! undoLastCommand "Undo the last command, i.e. move backward in the recent-commands tape, if possible." | aPhase anIndex | lastCommand ifNil: [^ Beeper 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: 'nb 6/17/2003 12:25'! 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: [^ Beeper 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: 'nb 6/17/2003 12:25'! undoTo "Not yet functional, and not yet sent. 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. " | anIndex commandList aMenu reply | (anIndex _ self historyIndexOfLastCommand) == 0 ifTrue: [^ Beeper beep]. commandList _ history copyFrom: ((anIndex - 10) max: 1) to: ((anIndex + 10) min: history size). aMenu _ SelectionMenu labels: (commandList collect: [:cmd | cmd cmdWording truncateWithElipsisTo: 20]) selections: commandList. reply _ aMenu startUpWithCaption: 'undo or redo to...'. reply ifNotNil: [self inform: #deferred] "ActiveWorld commandHistory undoTo" ! ! !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: '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: 'dgd 4/3/2006 14:26'! redoMenuWording "Answer the wording to be used in a menu offering the current Redo command" | nextCommand | ((nextCommand := self nextCommand) isNil or: [Preferences useUndo not]) ifTrue: [^ 'can''t redo' translated]. ^ String streamContents: [:aStream | aStream nextPutAll: 'redo' translated. aStream nextPutAll: ' "'. aStream nextPutAll: (nextCommand cmdWording truncateWithElipsisTo: 20). 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: 'dgd 4/3/2006 14:26'! undoMenuWording "Answer the wording to be used in an 'undo' menu item" (lastCommand isNil or: [Preferences useUndo not] or: [Preferences infiniteUndo not and: [lastCommand phase == #undone]] or: [self nextCommandToUndo isNil]) ifTrue: [^ 'can''t undo' translated]. ^ String streamContents: [:aStream | aStream nextPutAll: 'undo' translated. aStream nextPutAll: ' "'. aStream nextPutAll: (self nextCommandToUndo cmdWording truncateWithElipsisTo: 20). aStream nextPut: $". lastCommand phase == #done ifTrue: [aStream nextPutAll: ' (z)']].! ! !CommandHistory methodsFor: 'menu' stamp: 'sw 2/2/2006 01:53'! undoOrRedoMenuWording "Answer the wording to be used in a menu item offering undo/redo (i.e., the form used when the #infiniteUndo preference is false)" | pre | self assureLastCommandStillValid. lastCommand ifNil: [^ 'can''t undo' translated]. pre _ lastCommand phase == #done ifTrue: ['undo' translated] ifFalse: ['redo' translated]. ^ pre, ' "', (lastCommand cmdWording truncateWithElipsisTo: 20), '" (z)'! ! !CommandHistory methodsFor: 'undo' 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 class instanceVariableNames: ''! !CommandHistory class methodsFor: 'class initialization' stamp: 'dgd 4/3/2006 14:28'! initialize "CommandHistory initialize" Smalltalk addToStartUpList: self. Smalltalk addToShutDownList: self.! ! !CommandHistory class methodsFor: 'system startup' stamp: 'tk 5/16/2002 13:52'! forgetAllGrabCommandsFrom: starter "Forget all the commands that might be held on to in the properties dicitonary of various morphs for various reasons." | object | object _ starter. [ [0 == object] whileFalse: [ object isMorph ifTrue: [object removeProperty: #undoGrabCommand]. object _ object nextObject]. ] ifError: [:err :rcvr | "object is obsolete" self forgetAllGrabCommandsFrom: object nextObject]. "CommandHistory forgetAllGrabCommandsFrom: true someObject" ! ! !CommandHistory class methodsFor: 'system startup' stamp: 'tk 5/16/2002 13:38'! resetAllHistory "Reset all command histories, and make all morphs that might be holding on to undo-grab-commands forget them" self allInstancesDo: [:c | c resetCommandHistory]. self forgetAllGrabCommandsFrom: self someObject. "CommandHistory resetAllHistory" ! ! !CommandHistory class methodsFor: 'system startup' stamp: 'dgd 4/3/2006 14:28'! shutDown: aboutToQuit Preferences purgeUndoOnQuit ifTrue: [ aboutToQuit ifTrue: [self resetAllHistory]. ]. ! ! !CommandHistory class methodsFor: 'system startup' stamp: 'dgd 10/15/2004 12:11'! startUp: aboutToQuit Preferences purgeUndoOnQuit ifTrue: [ aboutToQuit ifTrue: [self resetAllHistory]. ]. ! ! AbstractLauncher subclass: #CommandLineLauncherExample instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-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: 'ar 9/27/2005 20:23'! startUp | className | className := self parameterAt: 'class'. ToolSet browse: (Smalltalk at: className asSymbol ifAbsent: [Object]) selector: nil! ! TileLikeMorph subclass: #CommandTilesMorph instanceVariableNames: 'morph playerScripted' classVariableNames: '' poolDictionaries: '' category: 'EToys-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! ! ParseNode subclass: #CommentNode instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Compiler-ParseNodes'! AbstractEvent subclass: #CommentedEvent instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'! !CommentedEvent methodsFor: 'printing' stamp: 'rw 7/1/2003 11:37'! printEventKindOn: aStream aStream nextPutAll: 'Commented'! ! !CommentedEvent methodsFor: 'testing' stamp: 'rw 7/1/2003 11:37'! isCommented ^true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CommentedEvent class instanceVariableNames: ''! !CommentedEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 12:08'! changeKind ^#Commented! ! !CommentedEvent class methodsFor: 'accessing' stamp: 'rw 7/10/2003 11:20'! supportedKinds ^Array with: self classKind! ! ByteArray variableByteSubclass: #CompiledMethod instanceVariableNames: '' classVariableNames: 'BlockNodeCache LargeFrame SmallFrame SpecialConstants' poolDictionaries: '' category: 'Kernel-Methods'! !CompiledMethod commentStamp: 'ls 7/5/2003 13:48' prior: 0! My instances are methods suitable for interpretation by the virtual machine. This is the only class in the system whose instances intermix both indexable pointer fields and indexable integer fields. The current format of a CompiledMethod is as follows: header (4 bytes) literals (4 bytes each) bytecodes (variable) trailer (variable) The header is a 30-bit integer with the following format: (index 0) 9 bits: main part of primitive number (#primitive) (index 9) 8 bits: number of literals (#numLiterals) (index 17) 1 bit: whether a large frame size is needed (#frameSize) (index 18) 6 bits: number of temporary variables (#numTemps) (index 24) 4 bits: number of arguments to the method (#numArgs) (index 28) 1 bit: high-bit of primitive number (#primitive) (index 29) 1 bit: flag bit, ignored by the VM (#flag) The trailer has two variant formats. In the first variant, the last byte is at least 252 and the last four bytes represent a source pointer into one of the sources files (see #sourcePointer). In the second variant, the last byte is less than 252, and the last several bytes are a compressed version of the names of the method's temporary variables. The number of bytes used for this purpose is the value of the last byte in the method. ! !CompiledMethod methodsFor: 'accessing' stamp: 'dvf 11/12/2002 00:44'! allLiterals ^ self literals.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/18/2006 13:11'! defaultSelector "Invent and answer an appropriate message selector (a Symbol) for me, that is, one that will parse with the correct number of arguments." ^#DoIt numArgs: self numArgs! ! !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: 'ls 7/5/2003 13:50'! flag "Answer the user-level flag bit" ^( (self header bitShift: -29) bitAnd: 1) = 1 ifTrue: [ true ] ifFalse: [ false ] ! ! !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: 'md 8/2/2006 18:58'! methodClass "answer the class that I am installed in" ^(self literalAt: self numLiterals) value.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/16/2006 11:30'! methodClass: aClass "set the class binding in the last literal to aClass" self literalAt: self numLiterals put: aClass binding! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/16/2006 14:00'! methodReference | class selector | class := self methodClass ifNil: [^nil]. selector := self selector ifNil: [^nil]. ^MethodReference class: class selector: selector. ! ! !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: 'lr 2/6/2006 19:46'! pragmas "Answer an array of the pragmas of the reciever." ^ self properties pragmas.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'ls 6/22/2000 14:35'! primitive "Answer the primitive index associated with the receiver. Zero indicates that this is not a primitive method. We currently allow 10 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: 16r100001FF. ^ (primBits bitAnd: 16r1FF) + (primBits bitShift: -19) ! ! !CompiledMethod methodsFor: 'accessing' stamp: 'lr 2/6/2006 19:35'! properties "Answer the method properties of the receiver." ^ self literalAt: self numLiterals - 1.! ! !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: 'md 1/20/2006 16:09'! scanner ^ InstructionStream on: self! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/15/2006 20:51'! searchForClass "search me in all classes, if found, return my class. Slow!!" self systemNavigation allBehaviorsDo: [:class | (class methodDict keyAtIdentityValue: self ifAbsent: [nil]) ifNotNil: [^class]]. ^nil.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/15/2006 20:51'! searchForSelector "search me in all classes, if found, return my selector. Slow!!" | selector | self systemNavigation allBehaviorsDo: [:class | (selector := class methodDict keyAtIdentityValue: self ifAbsent: [nil]) ifNotNil: [^selector]]. ^nil.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 8/2/2006 20:23'! selector ^self properties selector.! ! !CompiledMethod methodsFor: 'accessing' stamp: 'md 2/16/2006 17:51'! selector: aSymbol self properties selector: aSymbol! ! !CompiledMethod methodsFor: 'accessing' stamp: 'ajh 11/17/2001 14:30'! trailer | end trailer | end _ self endPC. trailer _ ByteArray new: self size - end. end + 1 to: self size do: [:i | trailer at: i - end put: (self at: i)]. ^ trailer! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:10'! allEmbeddedBlockMethods | set | set _ OrderedCollection new. 1 to: self numLiterals do: [:i | | lit | lit _ self literalAt: i. (lit isKindOf: CompiledMethod) ifTrue: [ set add: lit. set addAll: lit allEmbeddedBlockMethods. ] ifFalse: [(lit isKindOf: BlockClosure) ifTrue: [ set add: lit method. set addAll: lit method allEmbeddedBlockMethods ]]. ]. ^ set! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:01'! containsBlockClosures ^ self embeddedBlockMethods size > 0! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:10'! createBlock: env ^ BlockClosure new env: env; method: self! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:00'! embeddedBlockMethods | set | set _ OrderedCollection new. 1 to: self numLiterals do: [:i | | lit | lit _ self literalAt: i. (lit isKindOf: CompiledMethod) ifTrue: [ set add: lit. ] ifFalse: [(lit isKindOf: BlockClosure) ifTrue: [ set add: lit method. ]]. ]. ^ set! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:08'! isBlockMethod "Is this a sub-method (embedded block's method) of another method. If so the last literal points to its outer method" ^ (self header bitAnd: 1 << 29) ~= 0! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:08'! isBlockMethod: bool "Use the sign bit in the header to mark methods that are sub-methods of an outer method. The outer method will be held in my last literal." self objectAt: 1 put: (bool ifTrue: [self header bitOr: 1 << 29] ifFalse: [self header bitAnd: (1 << 29) bitInvert])! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:08'! isClosureCompiled "Return true if this method was compiled with the new closure compiler, Parser2 (compiled while Preference compileUseNewCompiler was true). Return false if it was compiled with the old compiler." ^ self header < 0! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:09'! isClosureCompiled: bool "Use the sign bit in the header to mark methods that have been compiled using the new closure compiler (Parser2)." self objectAt: 1 put: (bool ifTrue: [(self header bitOr: 1 << 30) as31BitSmallInt] ifFalse: [(self header bitAnd: (1 << 30) bitInvert) as31BitSmallInt])! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:09'! method "polymorphic with closure" ^ self! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:09'! remoteReturns "For closure methods only" ^ self messages includes: #privRemoteReturnTo:! ! !CompiledMethod methodsFor: 'closures' stamp: 'md 1/20/2006 16:56'! searchImageForHomeMethod Smalltalk allObjectsDo: [:obj | obj class == CompiledMethod ifTrue: [ (obj pointsTo: self) ifTrue: [^ obj searchImageForHomeMethod] ] ifFalse: [obj class == BlockClosure ifTrue: [ (obj method == self and: [obj size = 0]) ifTrue: [^ obj searchImageForHomeMethod] ]] ]. ^ self "must be a loner block method"! ! !CompiledMethod methodsFor: 'comparing' stamp: 'md 2/16/2006 17:07'! equivalentTo: aCompiledMethod "does not work yet with non-RB parseTrees" ^ self = aCompiledMethod or: [self class == aCompiledMethod class and: [self numArgs == aCompiledMethod numArgs and: [self decompile = aCompiledMethod decompile]]].! ! !CompiledMethod methodsFor: 'comparing' stamp: 'md 7/12/2006 16:35'! = 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 allButLast: 2) = (otherLits := method literals allButLast: 2) 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 isFloat 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: 'decompiling' stamp: 'md 3/4/2006 12:34'! compilerClass ^self methodClass ifNil: [self class compilerClass] ifNotNilDo: [:class | class compilerClass].! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 2/16/2006 17:08'! decompile "Return the decompiled parse tree that represents self" | class selector | class := self methodClass ifNil: [Object]. selector := self selector ifNil: [self defaultSelector]. ^class decompilerClass new decompile: selector in: class method: self.! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 3/4/2006 12:34'! decompilerClass ^ self isClosureCompiled ifTrue: [self compilerClass closureDecompilerClass] ifFalse: [self compilerClass decompilerClass]! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 2/16/2006 13:26'! decompileString ^self decompile decompileString! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 2/22/2006 15:59'! decompileWithTemps "Return the decompiled parse tree that represents self, but get the temp names by compiling the sourcecode..." | class selector | class := self methodClass ifNil: [Object]. selector := self selector ifNil: [self defaultSelector]. (self fileIndex > 0 and: [(SourceFiles at: self fileIndex) isNil]) ifTrue: [ "Emergency or no source file -- decompile without temp names " ^self decompile. ]. ^((self decompilerClass new withTempNames: self methodNode tempNames) decompile: selector in: class method: self)! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 3/4/2006 12:35'! methodNode "Return the parse tree that represents self" | source | ^ (source := self getSourceFromFile) ifNil: [self decompile] ifNotNil: [self parserClass new parse: source class: (self methodClass ifNil: [self sourceClass])]! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 2/19/2006 23:30'! methodNodeFormattedAndDecorated: decorate "Answer a method node made from pretty-printed (and colorized, if decorate is true) source text." | class source node | source := self getSourceFromFile. class := self methodClass ifNil: [self sourceClass]. source ifNil: [^self decompile]. source := class prettyPrinterClass format: source in: class notifying: nil decorated: decorate. node := class parserClass new parse: source class: class. node sourceText: source. ^node! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'md 3/4/2006 12:32'! parserClass ^ self isClosureCompiled ifTrue: [self compilerClass closureParserClass] ifFalse: [self compilerClass parserClass]! ! !CompiledMethod methodsFor: 'decompiling' stamp: 'ajh 7/14/2001 12:34'! primitiveNode | primNode n | primNode _ PrimitiveNode new num: (n _ self primitive). (n = 117 or: [n = 120]) ifTrue: [ primNode spec: (self literalAt: 1)]. ^ primNode! ! !CompiledMethod methodsFor: 'evaluating' stamp: 'ajh 1/28/2003 12:33'! valueWithReceiver: aReceiver arguments: anArray ^ aReceiver withArgs: anArray executeMethod: self! ! !CompiledMethod methodsFor: 'file in/out' stamp: 'md 1/21/2006 10:54'! hasReportableSlip "Answer whether the receiver contains anything that should be brought to the attention of the author when filing out. Customize the lists here to suit your preferences. If slips do not get reported in spite of your best efforts here, make certain that the Preference 'checkForSlips' is set to true." | assoc | #(#doOnlyOnce: #halt #halt: #hottest #printDirectlyToDisplay #toRemove #personal #urgent #haltOnce #haltOnce: #haltIf: ) do: [:aLit | (self hasLiteral: aLit) ifTrue: [^ true]]. #(#Transcript #AA #BB #CC #DD #EE ) do: [:aSymbol | (assoc := Smalltalk associationAt: aSymbol ifAbsent: []) ifNotNil: [(self hasLiteral: assoc) ifTrue: [^ true]]]. ^ false! ! !CompiledMethod methodsFor: 'file in/out' stamp: 'yo 10/28/2004 22:38'! objectForDataStream: refStrm self primitive = 117 ifTrue: [self literals first at: 4 put: 0]. ! ! !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: 'file in/out' stamp: 'RAA 8/21/2001 23:10'! zapSourcePointer "clobber the source pointer since it will be wrong" 0 to: 3 do: [ :i | self at: self size - i put: 0]. ! ! !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: 'initialize-release' stamp: 'lr 2/6/2006 20:52'! properties: aMethodProperties "Set the method-properties of the receiver to aMethodProperties." aMethodProperties pragmas do: [ :each | each setMethod: self ]. ^ self literalAt: self numLiterals - 1 put: aMethodProperties.! ! !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: 'md 3/3/2006 10:52'! hasLiteralSuchThat: aBlock "Answer true if aBlock returns true for any literal in this method, even if imbedded in array structure or within its pragmas." | literal | self pragmas do: [ :pragma | (pragma hasLiteralSuchThat: aBlock) ifTrue: [ ^ true ] ]. 2 to: self numLiterals + 1 do: [ :index | literal := self objectAt: index. (aBlock value: literal) ifTrue: [ ^ true ]. (literal hasLiteralSuchThat: aBlock) ifTrue: [ ^ true ] ]. ^ false.! ! !CompiledMethod methodsFor: 'literals' stamp: 'md 3/3/2006 10:51'! hasLiteralThorough: aLiteral "Answer true if any literal in this method is literal, even if embedded in array structure or within its pragmas." | literal | self pragmas do: [ :pragma | (pragma hasLiteral: aLiteral) ifTrue: [ ^ true ] ]. 2 to: self numLiterals + 1 do: [ :index | literal := self objectAt: index. literal == aLiteral ifTrue: [ ^ true ]. (literal hasLiteralThorough: aLiteral) 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' stamp: 'md 1/20/2006 16:46'! headerDescription "Answer a description containing the information about the form of the receiver and the form of the context needed to run the receiver." | s | s _ '' writeStream. self header printOn: s. s cr; nextPutAll: '"primitive: '. self primitive printOn: s. s cr; nextPutAll: ' numArgs: '. self numArgs printOn: s. s cr; nextPutAll: ' numTemps: '. self numTemps printOn: s. s cr; nextPutAll: ' numLiterals: '. self numLiterals printOn: s. s cr; nextPutAll: ' frameSize: '. self frameSize printOn: s. s cr; nextPutAll: ' isClosureCompiled: '. self isClosureCompiled printOn: s. s cr; nextPutAll: ' isBlockMethod: '. self isBlockMethod printOn: s. s nextPut: $"; cr. ^ s contents! ! !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' stamp: 'ar 4/10/2005 22:16'! literalStrings | lits litStrs | lits _ self literals. litStrs _ OrderedCollection new: lits size * 3. self literals do: [:lit | (lit isVariableBinding) ifTrue: [litStrs addLast: lit key] ifFalse: [(lit isSymbol) 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' stamp: 'dvf 11/12/2002 00:44'! literalsDo: aOneArgumentBlock ^self literals do:aOneArgumentBlock.! ! !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: 'literals' stamp: 'dvf 11/12/2002 00:44'! refersToLiteral:aLiteral ^self hasLiteral: aLiteral.! ! !CompiledMethod methodsFor: 'literals' stamp: 'dvf 11/12/2002 00:44'! sendsSelector: aSymbol ^ self messages includes: aSymbol! ! !CompiledMethod methodsFor: 'printing' stamp: 'sw 7/29/2002 02:24'! dateMethodLastSubmitted "Answer a Date object indicating when a method was last submitted. If there is no date stamp, return nil" "(CompiledMethod compiledMethodAt: #dateMethodLastSubmitted) dateMethodLastSubmitted" | aStamp tokens | aStamp _ self timeStamp. tokens _ aStamp findBetweenSubStrs: ' '. "space is expected delimiter, but cr is sometimes seen, though of mysterious provenance" ^ tokens size > 1 ifTrue: [[tokens second asDate] ifError: [nil]] ifFalse: [nil]! ! !CompiledMethod methodsFor: 'printing' stamp: 'ajh 2/9/2003 14:17'! longPrintOn: aStream "List of all the byte codes in a method with a short description of each" self longPrintOn: aStream indent: 0! ! !CompiledMethod methodsFor: 'printing' stamp: 'ar 6/28/2003 00:08'! longPrintOn: aStream indent: tabs "List of all the byte codes in a method with a short description of each" self isQuick ifTrue: [self isReturnSpecial ifTrue: [^ aStream tab: tabs; nextPutAll: 'Quick return ' , (#('self' 'true' 'false' 'nil' '-1' '0' '1' '2') at: self primitive - 255)]. ^ aStream nextPutAll: 'Quick return field ' , self returnField printString , ' (0-based)']. self primitive = 0 ifFalse: [ aStream tab: tabs. self printPrimitiveOn: aStream. ]. (InstructionPrinter on: self) indent: tabs; printInstructionsOn: aStream. ! ! !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: 'ajh 3/20/2001 11:41'! symbolic "Answer a String that contains a list of all the byte codes in a method with a short description of each." | aStream | aStream _ WriteStream on: (String new: 1000). self longPrintOn: aStream. ^aStream contents! ! !CompiledMethod methodsFor: 'printing' stamp: 'yo 3/16/2004 12:29'! timeStamp "Answer the authoring time-stamp for the given method, retrieved from the sources or changes file. Answer the empty string if no time stamp is available." "(CompiledMethod compiledMethodAt: #timeStamp) timeStamp" | file preamble stamp tokens tokenCount | self fileIndex == 0 ifTrue: [^ String new]. "no source pointer for this method" file _ SourceFiles at: self fileIndex. file ifNil: [^ String new]. "sources file not available" "file does not exist happens in secure mode" file _ [file readOnlyCopy] on: FileDoesNotExistException do:[:ex| nil]. file ifNil: [^ String new]. preamble _ self getPreambleFrom: file at: (0 max: self filePosition - 3). stamp _ String new. tokens _ (preamble findString: 'methodsFor:' startingAt: 1) > 0 ifTrue: [Scanner new scanTokens: preamble] ifFalse: [Array new "ie cant be back ref"]. (((tokenCount _ tokens size) between: 7 and: 8) and: [(tokens at: tokenCount - 5) = #methodsFor:]) ifTrue: [(tokens at: tokenCount - 3) = #stamp: ifTrue: ["New format gives change stamp and unified prior pointer" stamp _ tokens at: tokenCount - 2]]. ((tokenCount between: 5 and: 6) and: [(tokens at: tokenCount - 3) = #methodsFor:]) ifTrue: [(tokens at: tokenCount - 1) = #stamp: ifTrue: ["New format gives change stamp and unified prior pointer" stamp _ tokens at: tokenCount]]. file close. ^ stamp ! ! !CompiledMethod methodsFor: 'scanning' stamp: 'md 4/27/2006 15:12'! hasInstVarRef "Answer whether the method references an instance variable." | scanner end printer | scanner _ InstructionStream on: self. printer _ InstVarRefLocator new. end _ self endPC. [scanner pc <= end] whileTrue: [ (printer interpretNextInstructionUsing: scanner) ifTrue: [^true]. ]. ^false! ! !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' stamp: 'dvf 11/12/2002 00:44'! messagesDo: aBlock ^ self messages do:aBlock.! ! !CompiledMethod methodsFor: 'scanning' stamp: 'md 2/20/2006 21:18'! readsField: varIndex "Answer whether the receiver loads the instance variable indexed by the argument." self allEmbeddedBlockMethods do: [:meth | | scanner catcher instr scan | scanner := meth scanner. catcher := MessageCatcher new. [scanner atEnd] whileFalse: [ instr := scanner interpretNextInstructionFor: catcher. (instr selector = #send:super:numArgs: and: [instr argument = #privGetInstVar:]) ifTrue: [ scan := scanner copy. scan pc: scan previousPc. scan pc: scan previousPc. instr := scan interpretNextInstructionFor: catcher. (instr selector = #pushConstant: and: [instr argument = varIndex]) ifTrue: [^ true] ]. ]. ]. 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: 'md 2/20/2006 21:19'! writesField: field "Answer whether the receiver stores into the instance variable indexed by the argument." self allEmbeddedBlockMethods do: [:meth | | scanner catcher instr scan | scanner := meth scanner. catcher := MessageCatcher new. [scanner atEnd] whileFalse: [ instr := scanner interpretNextInstructionFor: catcher. (instr selector = #send:super:numArgs: and: [instr argument = #privStoreIn:instVar:]) ifTrue: [ scan := scanner copy. scan pc: scan previousPc. scan pc: scan previousPc. instr := scan interpretNextInstructionFor: catcher. (instr selector = #pushConstant: and: [instr argument = field]) ifTrue: [^ true] ]. ]. ]. 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' 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' stamp: 'di 1/7/2004 15:32'! copyWithTempNames: tempNames | tempStr compressed | tempStr _ String streamContents: [:strm | tempNames do: [:n | strm nextPutAll: n; space]]. compressed := self qCompress: tempStr firstTry: true. compressed ifNil: ["failure case (tempStr too big) will just decompile with tNN names" ^ self copyWithTrailerBytes: #(0 0 0 0)]. ^ self copyWithTrailerBytes: compressed! ! !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: 'yo 3/16/2004 12:23'! getPreambleFrom: aFileStream at: position | writeStream | writeStream _ String new writeStream. position to: 0 by: -1 do: [:p | | c | aFileStream position: p. c _ aFileStream basicNext. c == $!! ifTrue: [^ writeStream contents reverse] ifFalse: [writeStream nextPut: c]]! ! !CompiledMethod methodsFor: 'source code management'! getSource ^ self getSourceFor: self selector in:self methodClass.! ! !CompiledMethod methodsFor: 'source code management' stamp: 'md 2/28/2006 17:51'! getSourceFor: selector in: class "Retrieve or reconstruct the source code for this method." | source flagByte sourceSelector | 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" ^ self decompileString]. flagByte < 252 ifTrue: ["Magic sources -- decompile with temp names" ^ ((self decompilerClass new withTempNames: self tempNames) decompile: selector in: class method: self) decompileString]. "Situation normal; read the sourceCode from the file" [ source _ self getSourceFromFile ] on: Error do: [ :ex | "An error can happen here if, for example, the changes file has been truncated by an aborted download. The present solution is to ignore the error and fall back on the decompiler. A more thorough solution should probably trigger a systematic invalidation of all source pointers past the end of the changes file. Consider that, as time goes on, the changes file will eventually grow large enough to cover the lost code, and then instead of falling into this error case, random source code will get returned." source _ nil ]. source ifNotNil: [ sourceSelector _ Parser parserClass new parseSelector: source. ^sourceSelector = selector ifTrue: [source] ifFalse: [ self replace: sourceSelector with: selector in: source]]. "Something really wrong -- decompile blind (no temps)" ^ 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' stamp: 'ajh 7/21/2003 09:45'! holdsTempNames "Are tempNames stored in trailer bytes" | 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: [^ false]. "No source pointer & no temp names" flagByte < 252 ifTrue: [^ true]. "temp names compressed" ^ false "Source pointer" ! ! !CompiledMethod methodsFor: 'source code management' stamp: 'md 6/2/2006 10:34'! linesOfCode "An approximate measure of lines of code. Includes comments, but excludes blank lines." | strm line lines | lines _ 0. strm _ ReadStream on: self getSource. [strm atEnd] whileFalse: [line _ strm upTo: Character cr. line isEmpty ifFalse: [lines _ lines+1]]. ^lines ! ! !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: 'md 8/14/2005 17:47'! 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 | (SourceFiles == nil or: [(file _ SourceFiles at: fileIndex) == nil]) ifTrue: [^ self become: (self copyWithTempNames: methodNode tempNames)]. SmalltalkImage current assureStartupStampLogged. file setToEnd. preambleBlock value: file. "Write the preamble" remoteString _ RemoteString newString: sourceStr onFileNumber: fileIndex toFile: file. file nextChunkPut: ' '. InMidstOfFileinNotification signal ifFalse: [file flush]. self checkOKToAdd: sourceStr size at: remoteString position. self setSourcePosition: remoteString position inFile: fileIndex! ! !CompiledMethod methodsFor: 'source code management' stamp: 'yo 3/16/2004 12:48'! qCompress: string firstTry: firstTry "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. Normal call is with firstTry == true." | charTable odd ix oddNibble names shorterStr maybe str temps | str _ string isOctetString ifTrue: [string] ifFalse: [temps _ string findTokens: ' '. String streamContents: [:stream | 1 to: temps size do: [:index | stream nextPut: $t. stream nextPutAll: index asString. stream space]]]. charTable _ "Character encoding table must match qDecompress:" ' eatrnoislcm bdfghjkpquvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'. ^ ByteArray streamContents: [:strm | odd _ true. "Flag for odd or even nibble out" oddNibble _ nil. 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 position > 251 ifTrue: ["Only values 1...251 are available for the flag byte that signals compressed temps. See the logic in endPC." "Before giving up completely, we attempt to encode most of the temps, but with the last few shortened to tNN-style names." firstTry ifFalse: [^ nil "already tried --give up now"]. names _ str findTokens: ' '. names size < 8 ifTrue: [^ nil "weird case -- give up now"]. 4 to: names size//2 by: 4 do: [:i | shorterStr _ String streamContents: [:s | 1 to: names size - i do: [:j | s nextPutAll: (names at: j); space]. 1 to: i do: [:j | s nextPutAll: 't' , j printString; space]]. (maybe _ self qCompress: shorterStr firstTry: false) ifNotNil: [^ maybe]]. ^ nil]. 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: 'md 1/20/2006 16:36'! setMySourcePointer: 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: 'md 2/20/2006 21:19'! setSourcePointer: srcPointer self setMySourcePointer: srcPointer. self embeddedBlockMethods do: [:m | m setSourcePointer: srcPointer]. ! ! !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' stamp: 'ajh 8/13/2002 18:19'! sourceClass "Get my receiver class (method class) from the preamble of my source. Return nil if not found." ^ [(Compiler evaluate: (self sourceFileStream backChunk "blank"; backChunk "preamble")) theClass] on: Error do: [nil]! ! !CompiledMethod methodsFor: 'source code management' stamp: 'ajh 8/13/2002 18:18'! sourceFileStream "Answer the sources file stream with position set at the beginning of my source string" | pos | (pos _ self filePosition) = 0 ifTrue: [^ nil]. ^ (RemoteString newFileNumber: self fileIndex position: pos) fileStream! ! !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' stamp: 'md 8/2/2006 20:25'! sourceSelector "Answer my selector extracted from my source. If no source answer nil" | sourceString | sourceString _ self getSourceFromFile ifNil: [^ nil]. ^self methodClass parserClass new parseSelector: sourceString! ! !CompiledMethod methodsFor: 'source code management' stamp: 'ajh 7/21/2003 00:29'! tempNames | byteCount bytes | self holdsTempNames ifFalse: [ ^ (1 to: self numTemps) collect: [:i | 't', i printString] ]. 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: 'testing' stamp: 'emm 5/30/2002 09:22'! hasBreakpoint ^BreakpointManager methodHasBreakpoint: self! ! !CompiledMethod methodsFor: 'testing' stamp: 'md 2/19/2006 11:26'! hasNewPropertyFormat ^self properties isMethodProperties.! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 12/12/2003 15:18'! isAbstract | marker | marker _ self markerOrNil. ^ marker notNil and: [self class abstractMarkers includes: marker].! ! !CompiledMethod methodsFor: 'testing' stamp: 'md 11/21/2003 12:15'! isCompiledMethod ^ true! ! !CompiledMethod methodsFor: 'testing' stamp: 'al 1/23/2004 13:12'! isConflict ^ self markerOrNil == self class conflictMarker! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isDisabled ^ self isDisabled: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isDisabled: marker ^ marker == self class disabledMarker! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isExplicitlyRequired ^ self isExplicitlyRequired: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isExplicitlyRequired: marker ^ marker == self class explicitRequirementMarker! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isImplicitlyRequired ^ self isImplicitlyRequired: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:32'! isImplicitlyRequired: marker ^ marker == self class implicitRequirementMarker! ! !CompiledMethod methodsFor: 'testing' stamp: 'md 2/19/2006 11:26'! isInstalled | class selector | class := self methodClass ifNil: [^false]. selector := self selector ifNil: [^false]. ^self == (class methodDict at: selector ifAbsent: [^false]).! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:40'! isProvided ^ self isProvided: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:40'! isProvided: marker marker ifNil: [^ true]. ^ (self isRequired: marker) not and: [(self isDisabled: marker) not]! ! !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: 'NS 3/22/2005 16:38'! isRequired ^ self isRequired: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:38'! isRequired: marker marker ifNil: [^ false]. (self isImplicitlyRequired: marker) ifTrue: [^ true]. (self isExplicitlyRequired: marker) ifTrue: [^ true]. (self isSubclassResponsibility: marker) ifTrue: [^ true]. ^ false! ! !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: 'testing' stamp: 'NS 3/22/2005 16:38'! isSubclassResponsibility ^ self isSubclassResponsibility: self markerOrNil! ! !CompiledMethod methodsFor: 'testing' stamp: 'NS 3/22/2005 16:38'! isSubclassResponsibility: marker ^ marker == self class subclassResponsibilityMarker! ! !CompiledMethod methodsFor: '*Tools-Inspector' stamp: 'ar 9/27/2005 18:32'! inspectorClass "Answer the class of the inspector to be used on the receiver. Called by inspect; use basicInspect to get a normal (less useful) type of inspector." ^ CompiledMethodInspector! ! !CompiledMethod methodsFor: 'private' stamp: 'al 2/13/2006 17:44'! markerOrNil "If I am a marker method, answer the symbol used to mark me. Otherwise answer nil. What is a marker method? It is method with body like 'self subclassResponsibility' or '^ self subclassResponsibility' used to indicate ('mark') a special property. Marker methods compile to bytecode like: 9 <70> self 10 send: 11 <87> pop 12 <78> returnSelf for the first form, or 9 <70> self 10 send: 11 <7C> returnTop for the second form." | e | ((e _ self endPC) = 19 or: [e = 20]) ifFalse: [^ nil]. (self numLiterals = 3) ifFalse:[^ nil]. (self at: 17) = 16r70 ifFalse:[^ nil]. "push self" (self at: 18) = 16rD0 ifFalse:[^ nil]. "send " "If we reach this point, we have a marker method that sends self " ^ self literalAt: 1 ! ! !CompiledMethod methodsFor: 'private' stamp: 'md 8/2/2006 20:25'! replace: oldSelector with: newSelector in: aText | oldKeywords newKeywords args newSelectorWithArgs startOfSource lastSelectorToken | oldKeywords _ oldSelector keywords. newKeywords _ (newSelector ifNil: [self defaultSelector]) keywords. self assert: oldKeywords size = newKeywords size. args _ (self methodClass parserClass new parseArgsAndTemps: aText string notifying: nil) copyFrom: 1 to: self numArgs. newSelectorWithArgs _ String streamContents: [:stream | newKeywords withIndexDo: [:keyword :index | stream nextPutAll: keyword. stream space. args size >= index ifTrue: [ stream nextPutAll: (args at: index); space]]]. lastSelectorToken _ args isEmpty ifFalse: [args last] ifTrue: [oldKeywords last]. startOfSource _ (aText string indexOfSubCollection: lastSelectorToken startingAt: 1) + lastSelectorToken size. ^newSelectorWithArgs withBlanksTrimmed asText , (aText copyFrom: startOfSource to: aText size)! ! !CompiledMethod methodsFor: '*39Deprecated' stamp: 'md 2/18/2006 19:40'! decompileClass: aClass selector: selector "Return the decompiled parse tree that represents self" self deprecated: 'just call #decompile on the CompiledMethod'. ^ self decompilerClass new decompile: selector in: aClass method: self! ! !CompiledMethod methodsFor: '*39Deprecated' stamp: 'md 2/16/2006 13:27'! decompileTree self deprecated: 'just use #decompile'. ^self decompile.! ! !CompiledMethod methodsFor: '*39Deprecated' stamp: 'md 2/16/2006 15:45'! who "Answer an Array of the class in which the receiver is defined and the selector to which it corresponds." self deprecated: 'use #methodClass and #selector directly'. self isInstalled ifFalse: [^#(unknown unknown)]. ^{self methodClass . self 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: 'ajh 2/3/2003 21:16'! 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. self classPool at: #BlockNodeCache ifAbsentPut: [nil->nil].! ! !CompiledMethod class methodsFor: 'class initialization' stamp: 'ajh 7/18/2001 02:04'! smallFrameSize ^ SmallFrame! ! !CompiledMethod class methodsFor: 'constants' stamp: 'NS 12/12/2003 15:17'! abstractMarkers ^ #(subclassResponsibility shouldNotImplement)! ! !CompiledMethod class methodsFor: 'constants' stamp: 'al 1/23/2004 13:11'! conflictMarker ^ #traitConflict! ! !CompiledMethod class methodsFor: 'constants' stamp: 'al 1/23/2004 13:11'! disabledMarker ^ #shouldNotImplement! ! !CompiledMethod class methodsFor: 'constants' stamp: 'al 1/23/2004 13:11'! explicitRequirementMarker ^ #explicitRequirement! ! !CompiledMethod class methodsFor: 'constants' stamp: 'al 1/23/2004 13:11'! implicitRequirementMarker ^ #requirement! ! !CompiledMethod class methodsFor: 'constants' stamp: 'al 1/23/2004 13:11'! subclassResponsibilityMarker ^ #subclassResponsibility! ! !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: 'NS 12/12/2003 15:03'! newFrom: aCompiledMethod | inst | inst _ super basicNew: aCompiledMethod size. 1 to: aCompiledMethod size do: [:index | inst at: index put: (aCompiledMethod at: index)]. ^ inst.! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'NS 12/12/2003 15:08'! 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 | new _ self newFrom: oldInstance. 1 to: instSize do: [:offset | (map at: offset) > 0 ifTrue: [new instVarAt: offset put: (oldInstance instVarAt: (map at: offset))]]. ^new! ! !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: 'md 7/14/2006 21:21'! 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 > 63 ifTrue: [^ self error: 'Cannot compile -- too many temporary variables']. nLits > 255 ifTrue: [^ self error: 'Cannot compile -- too many literals variables']. largeBit _ (nTemps + stackSize) > SmallFrame ifTrue: [1] ifFalse: [0]. primBits _ primitiveIndex <= 16r1FF ifTrue: [primitiveIndex] ifFalse: ["For now the high bit of primitive no. is in the 29th bit of header" primitiveIndex > 16r3FF ifTrue: [self error: 'prim num too large']. (primitiveIndex bitAnd: 16r1FF) + ((primitiveIndex bitAnd: 16r200) 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' stamp: 'md 7/14/2006 21:21'! newBytes: numberOfBytes trailerBytes: trailer nArgs: nArgs nTemps: nTemps nStack: stackSize nLits: nLits primitive: primitiveIndex flag: flag "Answer an instance of me. The header is specified by the message arguments. The remaining parts are not as yet determined." | largeBit primBits method flagBit | nTemps > 63 ifTrue: [^ self error: 'Cannot compile -- too many temporary variables']. nLits > 255 ifTrue: [^ self error: 'Cannot compile -- too many literals variables']. largeBit _ (nTemps + stackSize) > SmallFrame ifTrue: [1] ifFalse: [0]. "For now the high bit of the primitive no. is in a high bit of the header" primBits _ (primitiveIndex bitAnd: 16r1FF) + ((primitiveIndex bitAnd: 16r200) bitShift: 19). flagBit := flag ifTrue: [ 1 ] ifFalse: [ 0 ]. method _ self newMethod: numberOfBytes + trailer size header: (nArgs bitShift: 24) + (nTemps bitShift: 18) + (largeBit bitShift: 17) + (nLits bitShift: 9) + primBits + (flagBit bitShift: 29). "Copy the source code trailer to the end" 1 to: trailer size do: [: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: 'md 2/20/2006 21:10'! primitive: primNum numArgs: numArgs numTemps: numTemps stackSize: stackSize literals: literals bytecodes: bytecodes trailer: trailerBytes "Create method with given attributes. numTemps includes numArgs. stackSize does not include numTemps." | compiledMethod | compiledMethod _ self newBytes: bytecodes size trailerBytes: trailerBytes nArgs: numArgs nTemps: numTemps nStack: stackSize nLits: literals size primitive: primNum. (WriteStream with: compiledMethod) position: compiledMethod initialPC - 1; nextPutAll: bytecodes. literals withIndexDo: [:obj :i | compiledMethod literalAt: i put: obj]. ^ compiledMethod! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'md 8/5/2005 17:06'! 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: 2 primitive: 256 + index ! ! !CompiledMethod class methodsFor: 'instance creation' stamp: 'md 8/5/2005 17:06'! 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: 2 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: 'md 8/5/2005 17:05'! 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: 2 primitive: 256 ! ! Inspector subclass: #CompiledMethodInspector instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Inspector'! !CompiledMethodInspector methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:27'! fieldList | keys | keys := OrderedCollection new. keys add: 'self'. keys add: 'all bytecodes'. keys add: 'header'. 1 to: object numLiterals do: [ :i | keys add: 'literal', i printString ]. object initialPC to: object size do: [ :i | keys add: i printString ]. ^ keys asArray ! ! !CompiledMethodInspector methodsFor: 'selecting' stamp: 'ajh 3/20/2003 00:17'! contentsIsString "Hacked so contents empty when deselected" ^ #(0 2 3) includes: selectionIndex! ! !CompiledMethodInspector methodsFor: 'selecting' stamp: 'sd 11/20/2005 21:27'! selection | bytecodeIndex | selectionIndex = 0 ifTrue: [^ '']. selectionIndex = 1 ifTrue: [^ object ]. selectionIndex = 2 ifTrue: [^ object symbolic]. selectionIndex = 3 ifTrue: [^ object headerDescription]. selectionIndex <= (object numLiterals + 3) ifTrue: [ ^ object objectAt: selectionIndex - 2 ]. bytecodeIndex := selectionIndex - object numLiterals - 3. ^ object at: object initialPC + bytecodeIndex - 1! ! !CompiledMethodInspector methodsFor: 'selecting' stamp: 'ajh 3/20/2001 11:56'! selectionUnmodifiable "Answer if the current selected variable is unmodifiable via acceptance in the code pane. For most inspectors, no selection and a selection of self (selectionIndex = 1) are unmodifiable" ^ true! ! ClassTestCase subclass: #CompiledMethodTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'! !CompiledMethodTest commentStamp: '' prior: 0! This is the unit test for the class CompiledMethod. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category! !CompiledMethodTest methodsFor: 'examples' stamp: 'md 2/18/2006 20:09'! returnPlusOne: anInteger ^anInteger + 1. ! ! !CompiledMethodTest methodsFor: 'examples' stamp: 'md 2/18/2006 20:09'! returnTrue ^true ! ! !CompiledMethodTest methodsFor: 'tests - evaluating' stamp: 'md 4/16/2003 15:30'! testValueWithReceiverArguments | method value | method := self class compiledMethodAt: #returnTrue. value := method valueWithReceiver: nil arguments: #(). self assert: (value = true). method := self class compiledMethodAt: #returnPlusOne:. value := method valueWithReceiver: nil arguments: #(1). self assert: (value = 2). ! ! !CompiledMethodTest methodsFor: 'tests - testing' stamp: 'md 2/19/2006 11:28'! testHasNewPropertyFormat | method | method := (self class)>>#returnTrue. self assert: method hasNewPropertyFormat. ! ! !CompiledMethodTest methodsFor: 'tests - testing' stamp: 'md 2/18/2006 20:10'! testIsInstalled | method cls | method := (self class)>>#returnTrue. self assert: method isInstalled. "now make an orphaned method by just deleting the class." Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. self deny: method isInstalled. ! ! !CompiledMethodTest methodsFor: 'tests - testing' stamp: 'md 4/16/2003 15:32'! testIsQuick | method | method := self class compiledMethodAt: #returnTrue. self assert: (method isQuick). method := self class compiledMethodAt: #returnPlusOne:. self deny: (method isQuick). ! ! !CompiledMethodTest methodsFor: 'tests - decompiling' stamp: 'md 2/16/2006 20:29'! testDecompile "self debug: #testDecompileTree" | method cls stream | Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. stream := ReadWriteStream on: String new. method decompile printOn: stream. self assert: stream contents = 'foo ^ 10' ! ! !CompiledMethodTest methodsFor: 'tests - accessing' stamp: 'md 2/18/2006 20:10'! testMethodClass | method cls | method := self class >> #returnTrue. self assert: method selector = #returnTrue. "now make an orphaned method by just deleting the class. old: #unknown new semantics: return Absolete class" Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. self assert: method methodClass = cls! ! !CompiledMethodTest methodsFor: 'tests - accessing' stamp: 'md 2/15/2006 20:54'! testSearchForClass | method cls | method := (self class)>>#returnTrue. self assert: (method searchForClass = self class). "now make an orphaned method. we want to get nil as the class" Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. self assert: method searchForClass = nil. ! ! !CompiledMethodTest methodsFor: 'tests - accessing' stamp: 'md 2/15/2006 20:55'! testSearchForSelector | method cls | method := (self class)>>#returnTrue. self assert: (method searchForSelector = #returnTrue). "now make an orphaned method. we want to get nil as the selector" Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. self assert: method searchForSelector = nil. ! ! !CompiledMethodTest methodsFor: 'tests - accessing' stamp: 'md 2/16/2006 20:28'! testSelector | method cls | method := (self class)>>#returnTrue. self assert: (method selector = #returnTrue). "now make an orphaned method. new semantics: return corrent name" Smalltalk removeClassNamed: #TUTU. cls := Object subclass: #TUTU instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Methods'. cls compile: 'foo ^ 10'. method := cls >> #foo. Smalltalk removeClassNamed: #TUTU. self assert: method selector = #foo. ! ! Object subclass: #CompiledMethodWithNode instanceVariableNames: 'node method' classVariableNames: '' poolDictionaries: '' category: 'Compiler-Support'! !CompiledMethodWithNode methodsFor: 'accessing' stamp: 'NS 1/28/2004 09:03'! method ^ method! ! !CompiledMethodWithNode methodsFor: 'accessing' stamp: 'NS 1/28/2004 09:04'! node ^ node! ! !CompiledMethodWithNode methodsFor: 'accessing' stamp: 'NS 1/28/2004 09:04'! selector ^ self node selector! ! !CompiledMethodWithNode methodsFor: 'private' stamp: 'NS 1/28/2004 09:03'! method: aCompiledMethod method _ aCompiledMethod! ! !CompiledMethodWithNode methodsFor: 'private' stamp: 'NS 1/28/2004 09:04'! node: aMethodNode node _ aMethodNode! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompiledMethodWithNode class instanceVariableNames: ''! !CompiledMethodWithNode class methodsFor: 'instance creation' stamp: 'NS 1/28/2004 09:05'! generateMethodFromNode: aMethodNode trailer: bytes ^ self method: (aMethodNode generate: bytes) node: aMethodNode.! ! !CompiledMethodWithNode class methodsFor: 'instance creation' stamp: 'NS 1/28/2004 09:05'! method: aCompiledMethod node: aMethodNode ^ self new method: aCompiledMethod; node: aMethodNode.! ! Object subclass: #Compiler instanceVariableNames: 'sourceStream requestor class category context parserClass' classVariableNames: '' poolDictionaries: '' category: 'Compiler-Kernel'! !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'! notify: aString "Refer to the comment in Object|notify:." ^self notify: aString at: sourceStream position + 1! ! !Compiler methodsFor: 'error handling' stamp: 'ar 9/27/2005 19:21'! notify: aString at: location "Refer to the comment in Object|notify:." requestor == nil ifTrue: [^SyntaxErrorNotification inClass: class category: category withCode: (sourceStream contents copyReplaceFrom: location to: location - 1 with: aString) doitFlag: false] ifFalse: [^requestor notify: aString at: location in: sourceStream]! ! !Compiler methodsFor: 'public access' stamp: 'md 2/28/2006 10:04'! compile: textOrStream in: aClass classified: aCategory 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." | methodNode | self from: textOrStream class: aClass classified: aCategory context: nil notifying: aRequestor. methodNode := self translate: sourceStream noPattern: false ifFail: failBlock. methodNode encoder requestor: requestor. ^methodNode. ! ! !Compiler methodsFor: 'public access' stamp: 'md 2/28/2006 10:45'! compile: textOrStream in: aClass notifying: aRequestor ifFail: failBlock ^self compile: textOrStream in: aClass classified: nil notifying: aRequestor ifFail: failBlock ! ! !Compiler methodsFor: 'public access' stamp: 'vb 8/13/2001 23:11'! compileNoPattern: textOrStream in: aClass context: aContext notifying: aRequestor ifFail: failBlock "Similar to #compile:in:notifying:ifFail:, but the compiled code is expected to be a do-it expression, with no message pattern." self from: textOrStream class: aClass context: aContext notifying: aRequestor. ^self translate: sourceStream noPattern: true ifFail: failBlock! ! !Compiler methodsFor: 'public access' stamp: 'sd 1/19/2004 20:58'! evaluate: aString in: aContext to: aReceiver "evaluate aString in the given context, and return the result. 2/2/96 sw" | result | result _ self evaluate: aString in: aContext to: aReceiver notifying: nil ifFail: [^ #failedDoit]. ^ result! ! !Compiler methodsFor: 'public access' stamp: 'NS 1/19/2004 09:05'! evaluate: textOrStream in: aContext to: receiver notifying: aRequestor ifFail: failBlock ^ self evaluate: textOrStream in: aContext to: receiver notifying: aRequestor ifFail: failBlock logged: false.! ! !Compiler methodsFor: 'public access' stamp: 'md 3/1/2006 12:31'! evaluate: textOrStream in: aContext to: receiver notifying: aRequestor ifFail: failBlock logged: logFlag "Compiles the sourceStream into a parse tree, then generates code into a method. 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 directly invoked without modifying the receiving-class." | methodNode method value | class := (aContext isNil 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. method selector ifNil: [method selector: #DoIt]. self interactive ifTrue: [ method := method copyWithTempNames: methodNode tempNames ]. value := receiver withArgs: (context isNil ifTrue: [ #() ] ifFalse: [ Array with: aContext ]) executeMethod: method. logFlag ifTrue: [ SystemChangeNotifier uniqueInstance evaluated: sourceStream contents context: aContext ]. ^ value.! ! !Compiler methodsFor: 'public access' stamp: 'wiz 2/26/2006 15:35'! format: textOrStream in: aClass notifying: aRequestor contentsSymbol: aSymbol "Compile a parse tree from the argument, textOrStream. Answer a string containing the original code, formatted nicely. If aSymbol is #colorPrint, then decorate the resulting text with color and hypertext actions" ^self format: textOrStream in: aClass notifying: aRequestor decorated: (aSymbol == #colorPrint)! ! !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: 'md 8/14/2005 17:58'! 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 from: textOrStream class: aClass context: nil notifying: req. ^self parserClass new parse: sourceStream class: class noPattern: false context: context notifying: requestor ifFail: []! ! !Compiler methodsFor: 'public access' stamp: 'md 2/20/2006 21:16'! translate: aStream noPattern: noPattern ifFail: failBlock parser: parser | tree | tree := parser parse: aStream class: class noPattern: noPattern context: context notifying: requestor ifFail: [^ failBlock value]. ^ tree! ! !Compiler methodsFor: 'private' stamp: 'ajh 1/21/2003 12:44'! format: aStream noPattern: noPattern ifFail: failBlock | tree | tree _ self parserClass new parse: aStream class: class noPattern: noPattern context: context notifying: requestor ifFail: [^ failBlock value]. ^ tree! ! !Compiler methodsFor: 'private' stamp: 'ar 9/27/2005 19:20'! from: textOrStream class: aClass classified: aCategory context: aContext notifying: req (textOrStream isKindOf: PositionableStream) ifTrue: [sourceStream := textOrStream] ifFalse: [sourceStream := ReadStream on: textOrStream asString]. class := aClass. context := aContext. requestor := req. category := aCategory ! ! !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: 'stephaneducassse 11/5/2005 16:39'! interactive "this version of the method is necessary to load code from MC else the interactive mode is one. This method is really bad since it links the compiler package with the Tools one. The solution would be to have a real SyntaxError exception belonging to the compiler package and not a subclass of StringHolder - sd Nov 2005" "the code submitted by PlusTools is ideally the one that should be used interactive ^requestor ~~ nil " ^ (requestor == nil or: [requestor isKindOf: SyntaxError]) not! ! !Compiler methodsFor: 'private' stamp: 'ajh 1/21/2003 12:44'! parserClass ^ parserClass! ! !Compiler methodsFor: 'private' stamp: 'md 1/20/2006 17:04'! parserClass: aParserClass parserClass _ aParserClass. ! ! !Compiler methodsFor: 'private' stamp: 'ar 9/27/2005 19:21'! translate: aStream noPattern: noPattern ifFail: failBlock | tree | tree := self parserClass new parse: aStream class: class category: category noPattern: noPattern context: context notifying: requestor ifFail: [^ failBlock value]. ^ tree ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Compiler class instanceVariableNames: ''! !Compiler class methodsFor: 'accessing' stamp: 'md 3/3/2006 10:32'! closureDecompilerClass ^self error: 'not installed'.! ! !Compiler class methodsFor: 'accessing' stamp: 'md 3/3/2006 10:32'! closureParserClass ^self error: 'not installed'.! ! !Compiler class methodsFor: 'accessing' stamp: 'nk 8/30/2004 07:56'! couldEvaluate: anObject "Answer true if anObject can be passed to my various #evaluate: methods." ^anObject isString or: [ anObject isText or: [ anObject isStream ]]! ! !Compiler class methodsFor: 'accessing' stamp: 'md 3/1/2006 21:12'! decompilerClass ^Decompiler! ! !Compiler class methodsFor: 'accessing' stamp: 'ajh 1/21/2003 12:39'! new ^ super new parserClass: self parserClass! ! !Compiler class methodsFor: 'accessing'! parserClass "Return a parser class to use for parsing method headers." ^Parser! ! !Compiler class methodsFor: 'evaluating' stamp: 'NS 1/19/2004 10:07'! 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' stamp: 'NS 1/19/2004 09:50'! 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." ^ self new evaluate: textOrString in: nil to: anObject notifying: aController ifFail: [^nil] logged: logFlag.! ! !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! ! !Compiler class methodsFor: 'evaluating' stamp: 'nk 2/23/2005 16:55'! format: textOrStream in: aClass notifying: aRequestor contentsSymbol: aSymbol ^self new format: textOrStream in: aClass notifying: aRequestor contentsSymbol: aSymbol! ! !Compiler class methodsFor: 'evaluating' stamp: 'nk 2/23/2005 16:53'! format: textOrStream in: aClass notifying: aRequestor decorated: aBoolean ^self new format: textOrStream in: aClass notifying: aRequestor decorated: aBoolean! ! !Compiler class methodsFor: 'utilities' stamp: 'al 1/13/2006 00:02'! recompileAll "Recompile all classes, starting with given name." Smalltalk forgetDoIts. Smalltalk allClassesAndTraits do: [:classOrTrait | classOrTrait compileAll] displayingProgress: 'Recompiling all classes and traits'. ! ! !Compiler class methodsFor: 'utilities' stamp: 'sd 9/25/2004 15:07'! recompileAllFrom: firstName "Recompile all classes, starting with given name." Smalltalk forgetDoIts. Smalltalk allClassesDo: [:class | class name >= firstName ifTrue: [Transcript show: class name; cr. class compileAll]] "Compiler recompileAllFrom: 'AAABodyShop'." ! ! Object subclass: #Complex instanceVariableNames: 'real imaginary' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Numbers'! !Complex commentStamp: 'mk 10/31/2003 22:19' prior: 0! I represent a complex number. real -- real part of the complex number imaginary -- imaginary part of the complex number Complex number constructors: 5 i 6 + 7 i. 5.6 - 8 i. Complex real: 10 imaginary: 5. Complex abs: 5 arg: (Float pi / 4) Arithmetic operation with other complex or non-complex numbers work. (5 - 6 i) + (-5 + 8 i). "Arithmetic between two complex numbers." 5 * (5 - 6 i). "Arithmetic between a non-complex and a complex number." It is also possible to perform arithmetic operations between a complex number and a array of (complex) numbers: 2 * {1 + 2i. 3 + 4i. 5 + 6i} 5 + 5i * {1 + 2i. 3. 5 + 6i} It behaves analogously as it is with normal numbers and an array. NOTE: Although Complex something similiar to the Smalltalk's Number class, it would not be a good idea to make a Complex to be a subclass of a Number because: - Number is subclass of Magnitude and Complex is certainly not a magnitude. Complex does not behave very well as a Magnitude. Operations such as < > <= >= do not have sense in case of complex numbers. - Methods in the following Number methods' categories do not have sense for a Complex numbers trucation and round off testing intervals comparing - However the following Number methods' categories do have sense for a Complex number arithmetic (with the exception of operation // \\ quo: rem: mathematical functions Thus Complex is somewhat similar to a Number but it is not a subclass of it. Some operations we would like to inherit (e.g. #abs, #negated, #reciprocal) but some of the Number operation do not have sens to inherit or to overload. Classes are not always neat mechanism. !!!!!! We had to COPY the implementation of the abs negated reciprocal log: isZero reciprocal ... methods from the Number class to the Complex class. Awful solution. Now I begin to appreciate the Self. Missing methods String | converting | asComplex Complex | mathematical functions | arcSin Complex | mathematical functions | arcCos Complex | mathematical functions | arcTan! !Complex methodsFor: 'accessing' stamp: 'mk 10/27/2003 17:39'! imaginary ^ imaginary! ! !Complex methodsFor: 'accessing' stamp: 'mk 10/27/2003 17:39'! real ^ real! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/21/2004 11:25'! * anObject "Answer the result of multiplying the receiver by aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ (a * c) - (b * d). newImaginary _ (a * d) + (b * c). ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #*]! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 1/18/2004 23:31'! + anObject "Answer the sum of the receiver and aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ a + c. newImaginary _ b + d. ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #+]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:45'! - anObject "Answer the difference between the receiver and aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ a - c. newImaginary _ b - d. ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #-]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:45'! / anObject "Answer the result of dividing receiver by aNumber" | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ ((a * c) + (b * d)) / ((c * c) + (d * d)). newImaginary _ ((b * c) - (a * d)) / ((c * c) + (d * d)). ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 20:48'! abs "Answer the distance of the receiver from zero (0 + 0 i)." ^ ((real * real) + (imaginary * imaginary)) sqrt! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 22:08'! arg "Answer the argument of the receiver." self isZero ifTrue: [self error: 'zero has no argument.']. 0 < real ifTrue: [^ (imaginary / real) arcTan]. 0 = real ifTrue: [0 < imaginary ifTrue: [^ Float pi / 2] ifFalse: [^ (Float pi / 2) negated]]. real < 0 ifTrue: [0 <= imaginary ifTrue: [^ (imaginary / real) arcTan + Float pi] ifFalse: [^ (imaginary / real) arcTan - Float pi]]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:48'! divideFastAndSecureBy: anObject "Answer the result of dividing receiver by aNumber" " Both operands are scaled to avoid arithmetic overflow. This algorithm works for a wide range of values, and it needs only three divisions. Note: #reciprocal uses #/ for devision " | r d newReal newImaginary | anObject isComplex ifTrue: [anObject real abs > anObject imaginary abs ifTrue: [r _ anObject imaginary / anObject real. d _ r*anObject imaginary + anObject real. newReal _ r*imaginary + real/d. newImaginary _ r negated * real + imaginary/d. ] ifFalse: [r _ anObject real / anObject imaginary. d := r*anObject real + anObject imaginary. newReal _ r*real + imaginary/d. newImaginary _ r*imaginary - real/d. ]. ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:48'! divideSecureBy: anObject "Answer the result of dividing receiver by aNumber" " Both operands are scaled to avoid arithmetic overflow. This algorithm works for a wide range of values, but it requires six divisions. #divideFastAndSecureBy: is also quite good, but it uses only 3 divisions. Note: #reciprocal uses #/ for devision" | s ars ais brs bis newReal newImaginary | anObject isComplex ifTrue: [s := anObject real abs + anObject imaginary abs. ars := self real / s. ais := self imaginary / s. brs := anObject real / s. bis := anObject imaginary / s. s := brs squared + bis squared. newReal _ ars*brs + (ais*bis) /s. newImaginary _ ais*brs - (ars*bis)/s. ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 19:33'! negated "Answer a Number that is the negation of the receiver." ^0 - self! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:47'! reciprocal "Answer 1 divided by the receiver. Create an error notification if the receiver is 0." self = 0 ifTrue: [^ (ZeroDivide dividend: self) signal] ifFalse: [^1 / self] ! ! !Complex methodsFor: 'comparing' stamp: 'mk 1/18/2004 23:37'! = anObject anObject isComplex ifTrue: [^ (real = anObject real) & (imaginary = anObject imaginary)] ifFalse: [^ anObject adaptToComplex: self andSend: #=]! ! !Complex methodsFor: 'comparing' stamp: 'mk 10/27/2003 20:35'! hash "Hash is reimplemented because = is implemented." ^ real hash bitXor: imaginary hash.! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 21:51'! adaptToCollection: rcvr andSend: selector "If I am involved in arithmetic with a Collection, return a Collection of the results of each element combined with me in that expression." ^ rcvr collect: [:element | element perform: selector with: self]! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:32'! adaptToFloat: rcvr andSend: selector "If I am involved in arithmetic with a Float, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:32'! adaptToFraction: rcvr andSend: selector "If I am involved in arithmetic with a Fraction, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:31'! adaptToInteger: rcvr andSend: selector "If I am involved in arithmetic with an Integer, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! cos "Answer receiver's cosine." | iself | iself _ 1 i * self. ^ (iself exp + iself negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 21:34'! cosh "Answer receiver's hyperbolic cosine." ^ (self exp + self negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! exp "Answer the exponential of the receiver." ^ real exp * (imaginary cos + (1 i * imaginary sin))! ! !Complex methodsFor: 'mathematical functions' stamp: 'laza 9/26/2005 10:25'! ln "Answer the natural log of the receiver." ^ self abs ln + (1 i * self arg)! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 22:05'! log: aNumber "Answer the log base aNumber of the receiver." ^self ln / aNumber ln! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! sin "Answer receiver's sine." | iself | iself _ 1 i * self. ^ (iself exp - iself negated exp) / 2 i! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 21:33'! sinh "Answer receiver's hyperbolic sine." ^ (self exp - self negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/20/2004 12:02'! squared "Answer the receiver multipled by itself." ^self * self! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 22:04'! tan "Answer receivers tangent." ^ self sin / self cos! ! !Complex methodsFor: 'printing' stamp: 'mk 10/27/2003 18:02'! printOn: aStream real printOn: aStream. aStream nextPut: Character space. 0 <= imaginary ifTrue: [aStream nextPut: $+] ifFalse: [aStream nextPut: $-]. aStream nextPut: Character space. imaginary abs printOn: aStream. aStream nextPut: Character space. aStream nextPut: $i ! ! !Complex methodsFor: 'testing' stamp: 'mk 10/27/2003 17:33'! isComplex ^ true! ! !Complex methodsFor: 'testing' stamp: 'mk 10/27/2003 20:06'! isZero ^ self = 0! ! !Complex methodsFor: 'private' stamp: 'mk 10/27/2003 17:26'! imaginary: aNumber imaginary _ aNumber.! ! !Complex methodsFor: 'private' stamp: 'mk 10/27/2003 17:26'! real: aNumber real _ aNumber.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Complex class instanceVariableNames: ''! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 21:03'! abs: aNumber1 arg: aNumber2 | real imaginary | real _ aNumber1 * aNumber2 cos. imaginary _ aNumber1 * aNumber2 sin. ^ real + imaginary i! ! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 17:28'! new ^ self real: 0 imaginary: 0! ! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 17:27'! real: aNumber1 imaginary: aNumber2 | newComplex | newComplex _ super new. newComplex real: aNumber1; imaginary: aNumber2. ^ newComplex! ! SimpleBorder subclass: #ComplexBorder instanceVariableNames: 'style colors lineStyles' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Borders'! !ComplexBorder commentStamp: 'kfr 10/27/2003 10:18' prior: 0! see BorderedMorph. poly _ polygon250 baseColor _ Color blue twiceLighter. border _ (ComplexBorder framed: 10) baseColor: poly color. border frameRectangle: ((100@100 extent: 200@200) insetBy: -5) on: Display getCanvas. baseColor _ Color red twiceLighter. border _ (ComplexBorder framed: 10) baseColor: baseColor. border drawPolygon: {100@100. 300@100. 300@300. 100@300} on: Display getCanvas. border drawPolyPatchFrom: 100@200 via: 100@100 via: 200@100 to: 200@200 on: Display getCanvas. border drawPolyPatchFrom: 100@100 via: 200@100 via: 200@200 to: 100@200 on: Display getCanvas. border drawPolyPatchFrom: 200@100 via: 200@200 via: 100@200 to: 100@100 on: Display getCanvas. border drawPolyPatchFrom: 200@200 via: 100@200 via: 100@100 to: 200@100 on: Display getCanvas. border _ (ComplexBorder raised: 10) baseColor: poly color. border drawPolygon: poly getVertices on: Display getCanvas 360 / 16.0 22.5 points _ (0 to: 15) collect:[:i| (Point r: 100 degrees: i*22.5) + 200]. Display getCanvas fillOval: (100@100 extent: 200@200) color: baseColor. border drawPolygon: points on: Display getCanvas. -1 to: points size + 1 do:[:i| border drawPolyPatchFrom: (points atWrap: i) via: (points atWrap: i+1) via: (points atWrap: i+2) to: (points atWrap: i+3) on: Display getCanvas. ]. Display getCanvas fillOval: (100@100 extent: 200@200) color: baseColor. 0 to: 36 do:[:i| border drawLineFrom: (Point r: 100 degrees: i*10) + 200 to: (Point r: 100 degrees: i+1*10) + 200 on: Display getCanvas. ]. drawPolygon: Point r: 1.0 degrees: 10 MessageTally spyOn:[ Display deferUpdates: true. t1 _ [1 to: 1000 do:[:i| border drawLineFrom: (100@100) to: (300@100) on: Display getCanvas. border drawLineFrom: (300@100) to: (300@300) on: Display getCanvas. border drawLineFrom: (300@300) to: (100@300) on: Display getCanvas. border drawLineFrom: (100@300) to: (100@100) on: Display getCanvas]] timeToRun. Display deferUpdates: false. ]. MessageTally spyOn:[ Display deferUpdates: true. t2 _ [1 to: 1000 do:[:i| border drawLine2From: (100@100) to: (300@100) on: Display getCanvas. border drawLine2From: (300@100) to: (300@300) on: Display getCanvas. border drawLine2From: (300@300) to: (100@300) on: Display getCanvas. border drawLine2From: (100@300) to: (100@100) on: Display getCanvas]] timeToRun. Display deferUpdates: false. ]. ! !ComplexBorder methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:13'! colors ^colors ifNil:[colors _ self computeColors].! ! !ComplexBorder methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:22'! style ^style! ! !ComplexBorder methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:22'! style: newStyle style == newStyle ifTrue:[^self]. style _ newStyle. self releaseCachedState.! ! !ComplexBorder methodsFor: 'accessing' stamp: 'ar 8/25/2001 16:14'! widthForRounding ^0! ! !ComplexBorder methodsFor: 'color tracking' stamp: 'ar 8/25/2001 18:17'! trackColorFrom: aMorph baseColor ifNil:[self color: aMorph raisedColor].! ! !ComplexBorder methodsFor: 'drawing' stamp: 'aoy 2/17/2003 01:08'! drawLineFrom: startPoint to: stopPoint on: aCanvas "Here we're using the balloon engine since this is much faster than BitBlt w/ brushes." | delta length dir cos sin tfm w h w1 w2 h1 h2 fill | width isPoint ifTrue: [w := width x. h := width y] ifFalse: [w := h := width]. w1 := w // 2. w2 := w - w1. h1 := h // 2. h2 := h - h1. "Compute the rotational transform from (0@0) -> (1@0) to startPoint -> stopPoint" delta := stopPoint - startPoint. length := delta r. dir := length > 1.0e-10 ifTrue: [delta / length] ifFalse: [ 1 @ 0]. cos := dir dotProduct: 1 @ 0. sin := dir crossProduct: 1 @ 0. tfm := (MatrixTransform2x3 new) a11: cos; a12: sin; a21: sin negated; a22: cos. "Install the start point offset" tfm offset: startPoint. "Now get the fill style appropriate for the given direction" fill := self fillStyleForDirection: dir. "And draw..." aCanvas asBalloonCanvas transformBy: tfm during: [:cc | cc drawPolygon: { (0 - w1) @ (0 - h1). "top left" (length + w2) @ (0 - h1). "top right" (length + w2) @ h2. "bottom right" (0 - w1) @ h2 "bottom left"} fillStyle: fill]! ! !ComplexBorder methodsFor: 'drawing' stamp: 'ar 11/26/2001 15:10'! drawPolyPatchFrom: startPoint to: stopPoint on: aCanvas usingEnds: endsArray | cos sin tfm fill dir fsOrigin fsDirection points x y | dir _ (stopPoint - startPoint) normalized. "Compute the rotational transform from (0@0) -> (1@0) to startPoint -> stopPoint" cos _ dir dotProduct: (1@0). sin _ dir crossProduct: (1@0). "Now get the fill style appropriate for the given direction" fill _ self fillStyleForDirection: dir. false ifTrue:[ "Transform the fill appropriately" fill _ fill clone. "Note: Code below is inlined from tfm transformPoint:/transformDirection:" x _ fill origin x. y _ fill origin y. fsOrigin _ ((x * cos) + (y * sin) + startPoint x) @ ((y * cos) - (x * sin) + startPoint y). x _ fill direction x. y _ fill direction y. fsDirection _ ((x * cos) + (y * sin)) @ ((y * cos) - (x * sin)). fill origin: fsOrigin; direction: fsDirection rounded; "NOTE: This is a bug in the balloon engine!!!!!!" normal: nil. aCanvas asBalloonCanvas drawPolygon: endsArray fillStyle: fill. ] ifFalse:[ "Transform the points rather than the fills" tfm _ (MatrixTransform2x3 new) a11: cos; a12: sin; a21: sin negated; a22: cos. "Install the start point offset" tfm offset: startPoint. points _ endsArray collect:[:pt| tfm invertPoint: pt]. aCanvas asBalloonCanvas transformBy: tfm during:[:cc| cc drawPolygon: points fillStyle: fill. ]. ].! ! !ComplexBorder methodsFor: 'drawing' stamp: 'ar 9/4/2001 19:51'! framePolygon2: vertices on: aCanvas | dir1 dir2 dir3 nrm1 nrm2 nrm3 point1 point2 point3 cross1 cross2 pointA pointB pointC pointD w p1 p2 p3 p4 balloon ends | balloon _ aCanvas asBalloonCanvas. balloon == aCanvas ifFalse:[balloon deferred: true]. ends _ Array new: 4. w _ width * 0.5. pointA _ nil. 1 to: vertices size do:[:i| p1 _ vertices atWrap: i. p2 _ vertices atWrap: i+1. p3 _ vertices atWrap: i+2. p4 _ vertices atWrap: i+3. dir1 _ p2 - p1. dir2 _ p3 - p2. dir3 _ p4 - p3. i = 1 ifTrue:[ "Compute the merge points of p1->p2 with p2->p3" cross1 _ dir2 crossProduct: dir1. nrm1 _ dir1 normalized. nrm1 _ (nrm1 y * w) @ (0 - nrm1 x * w). nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross1 < 0 ifTrue:[nrm1 _ nrm1 negated. nrm2 _ nrm2 negated]. point1 _ (p1 x + nrm1 x) @ (p1 y + nrm1 y). point2 _ (p2 x + nrm2 x) @ (p2 y + nrm2 y). pointA _ self intersectFrom: point1 with: dir1 to: point2 with: dir2. point1 _ (p1 x - nrm1 x) @ (p1 y - nrm1 y). point2 _ (p2 x - nrm2 x) @ (p2 y - nrm2 y). pointB _ self intersectFrom: point1 with: dir1 to: point2 with: dir2. pointB ifNotNil:[ (pointB x - p2 x) abs + (pointB y - p2 y) abs > (4*w) ifTrue:[pointA _ pointB _ nil]. ]. ]. "Compute the merge points of p2->p3 with p3->p4" cross2 _ dir3 crossProduct: dir2. nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). nrm3 _ dir3 normalized. nrm3 _ (nrm3 y * w) @ (0 - nrm3 x * w). cross2 < 0 ifTrue:[nrm2 _ nrm2 negated. nrm3 _ nrm3 negated]. point2 _ (p2 x + nrm2 x) @ (p2 y + nrm2 y). point3 _ (p3 x + nrm3 x) @ (p3 y + nrm3 y). pointC _ self intersectFrom: point2 with: dir2 to: point3 with: dir3. point2 _ (p2 x - nrm2 x) @ (p2 y - nrm2 y). point3 _ (p3 x - nrm3 x) @ (p3 y - nrm3 y). pointD _ self intersectFrom: point2 with: dir2 to: point3 with: dir3. pointD ifNotNil:[ (pointD x - p3 x) abs + (pointD y - p3 y) abs > (4*w) ifTrue:[pointC _ pointD _ nil]. ]. cross1 * cross2 < 0.0 ifTrue:[ point1 _ pointA. pointA _ pointB. pointB _ point1. cross1 _ 0.0 - cross1]. ends at: 1 put: pointA; at: 2 put: pointB; at: 3 put: pointD; at: 4 put: pointC. pointA ifNil:["degenerate and slow" nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross1 < 0 ifTrue:[nrm2 _ nrm2 negated]. point2 _ (p2 x + nrm2 x) @ (p2 y + nrm2 y). ends at: 1 put: point2]. pointB ifNil:["degenerate and slow" nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross1 < 0 ifTrue:[nrm2 _ nrm2 negated]. point2 _ (p2 x - nrm2 x) @ (p2 y - nrm2 y). ends at: 2 put: point2]. pointC ifNil:["degenerate and slow" nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross2 < 0 ifTrue:[nrm2 _ nrm2 negated]. point2 _ (p3 x + nrm2 x) @ (p3 y + nrm2 y). ends at: 4 put: point2]. pointD ifNil:["degenerate and slow" nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross2 < 0 ifTrue:[nrm2 _ nrm2 negated]. point2 _ (p3 x - nrm2 x) @ (p3 y - nrm2 y). ends at: 3 put: point2]. self drawPolyPatchFrom: p2 to: p3 on: balloon usingEnds: ends. pointA _ pointC. pointB _ pointD. cross1 _ cross2. ]. balloon == aCanvas ifFalse:[balloon flush].! ! !ComplexBorder methodsFor: 'drawing' stamp: 'ar 9/4/2001 19:50'! framePolygon: vertices on: aCanvas | dir1 dir2 dir3 nrm1 nrm2 nrm3 point1 point2 point3 cross1 cross2 pointA pointB pointC pointD w p1 p2 p3 p4 balloon ends pointE pointF | balloon _ aCanvas asBalloonCanvas. balloon == aCanvas ifFalse:[balloon deferred: true]. ends _ Array new: 6. w _ width * 0.5. pointA _ nil. 1 to: vertices size do:[:i| p1 _ vertices atWrap: i. p2 _ vertices atWrap: i+1. p3 _ vertices atWrap: i+2. p4 _ vertices atWrap: i+3. dir1 _ p2 - p1. dir2 _ p3 - p2. dir3 _ p4 - p3. (i = 1 | true) ifTrue:[ "Compute the merge points of p1->p2 with p2->p3" cross1 _ dir2 crossProduct: dir1. nrm1 _ dir1 normalized. nrm1 _ (nrm1 y * w) @ (0 - nrm1 x * w). nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). cross1 < 0 ifTrue:[nrm1 _ nrm1 negated. nrm2 _ nrm2 negated]. point1 _ (p1 x + nrm1 x) @ (p1 y + nrm1 y). point2 _ (p2 x + nrm2 x) @ (p2 y + nrm2 y). pointA _ self intersectFrom: point1 with: dir1 to: point2 with: dir2. point1 _ (p1 x - nrm1 x) @ (p1 y - nrm1 y). point2 _ (p2 x - nrm2 x) @ (p2 y - nrm2 y). pointB _ point1 + dir1 + point2 * 0.5. pointB _ p2 + ((pointB - p2) normalized * w). pointC _ point2. ]. "Compute the merge points of p2->p3 with p3->p4" cross2 _ dir3 crossProduct: dir2. nrm2 _ dir2 normalized. nrm2 _ (nrm2 y * w) @ (0 - nrm2 x * w). nrm3 _ dir3 normalized. nrm3 _ (nrm3 y * w) @ (0 - nrm3 x * w). cross2 < 0 ifTrue:[nrm2 _ nrm2 negated. nrm3 _ nrm3 negated]. point2 _ (p2 x + nrm2 x) @ (p2 y + nrm2 y). point3 _ (p3 x + nrm3 x) @ (p3 y + nrm3 y). pointD _ self intersectFrom: point2 with: dir2 to: point3 with: dir3. point2 _ (p2 x - nrm2 x) @ (p2 y - nrm2 y). point3 _ (p3 x - nrm3 x) @ (p3 y - nrm3 y). pointF _ point2 + dir2. pointE _ pointF + point3 * 0.5. pointE _ p3 + ((pointE - p3) normalized * w). cross1 * cross2 < 0.0 ifTrue:[ ends at: 1 put: pointA; at: 2 put: pointB; at: 3 put: pointC; at: 4 put: pointD; at: 5 put: pointE; at: 6 put: pointF. ] ifFalse:[ ends at: 1 put: pointA; at: 2 put: pointB; at: 3 put: pointC; at: 4 put: pointF; at: 5 put: pointE; at: 6 put: pointD. ]. self drawPolyPatchFrom: p2 to: p3 on: balloon usingEnds: ends. pointA _ pointD. pointB _ pointE. pointC _ pointF. cross1 _ cross2. ]. balloon == aCanvas ifFalse:[balloon flush].! ! !ComplexBorder methodsFor: 'drawing' stamp: 'ar 8/26/2001 19:01'! frameRectangle: aRectangle on: aCanvas "Note: This uses BitBlt since it's roughly a factor of two faster for rectangles" | w h r | self colors ifNil:[^super frameRectangle: aRectangle on: aCanvas]. w _ self width. w isPoint ifTrue:[h _ w y. w _ w x] ifFalse:[h _ w]. 1 to: h do:[:i| "top/bottom" r _ (aRectangle topLeft + (i-1)) extent: (aRectangle width - (i-1*2))@1. "top" aCanvas fillRectangle: r color: (colors at: i). r _ (aRectangle bottomLeft + (i @ (0-i))) extent: (aRectangle width - (i-1*2) - 1)@1. "bottom" aCanvas fillRectangle: r color: (colors at: colors size - i + 1). ]. 1 to: w do:[:i| "left/right" r _ (aRectangle topLeft + (i-1)) extent: 1@(aRectangle height - (i-1*2)). "left" aCanvas fillRectangle: r color: (colors at: i). r _ aRectangle topRight + ((0-i)@i) extent: 1@(aRectangle height - (i-1*2) - 1). "right" aCanvas fillRectangle: r color: (colors at: colors size - i + 1). ].! ! !ComplexBorder methodsFor: 'initialize' stamp: 'ar 11/26/2001 14:43'! releaseCachedState colors _ nil. lineStyles _ nil.! ! !ComplexBorder methodsFor: 'testing' stamp: 'ar 8/26/2001 19:30'! isComplex ^true! ! !ComplexBorder methodsFor: 'private' stamp: 'aoy 2/17/2003 01:02'! colorsForDirection: direction "Return an array of colors describing the receiver in the given direction" | colorArray dT cc | cc := self colors. direction x * direction y <= 0 ifTrue: ["within up->right or down->left transition; no color blend needed" colorArray := (direction x > 0 or: [direction y < 0]) ifTrue: ["up->right" cc copyFrom: 1 to: width] ifFalse: ["down->left" "colors are stored in reverse direction when following a line" (cc copyFrom: width + 1 to: cc size) reversed]] ifFalse: ["right->down or left->up transition; need color blend" colorArray := Array new: width. dT := direction x asFloat / (direction x + direction y). (direction x > 0 or: [direction y >= 0]) ifTrue: ["top-right" 1 to: width do: [:i | colorArray at: i put: ((cc at: i) mixed: dT with: (cc at: cc size - i + 1))]] ifFalse: ["bottom-left" 1 to: width do: [:i | colorArray at: i put: ((cc at: cc size - i + 1) mixed: dT with: (cc at: i))]]]. ^colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'ar 8/25/2001 16:16'! computeAltFramedColors | base light dark w hw colorArray param | base _ self color asColor. light _ Color white. dark _ Color black. w _ self width isPoint ifTrue:[self width x max: self width y] ifFalse:[self width]. w _ w asInteger. w = 1 ifTrue:[^{base mixed: 0.5 with: light. base mixed: 0.5 with: dark}]. colorArray _ Array new: w. hw _ w // 2. "brighten" 0 to: hw-1 do:[:i| param _ 0.5 + (i asFloat / hw * 0.5). colorArray at: i+1 put: (base mixed: param with: dark). "brighten" colorArray at: w-i put: (base mixed: param with: light). "darken" ]. w odd ifTrue:[colorArray at: hw+1 put: base]. ^colorArray, colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'aoy 2/17/2003 01:03'! computeAltInsetColors | base light dark w colorArray param hw | base := self color asColor. light := Color white. dark := Color black. w := self width isPoint ifTrue: [self width x max: self width y] ifFalse: [self width]. w := w asInteger. colorArray := Array new: w * 2. hw := 0.5 / w. 0 to: w - 1 do: [:i | param := false ifTrue: ["whats this ???!! false ifTrue:[]" 0.5 + (hw * i)] ifFalse: [0.5 + (hw * (w - i))]. colorArray at: i + 1 put: (base mixed: param with: dark). "darken" colorArray at: colorArray size - i put: (base mixed: param with: light) "brighten"]. ^colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'aoy 2/17/2003 01:05'! computeAltRaisedColors | base light dark w colorArray param hw | base := self color asColor. light := Color white. dark := Color black. w := self width isPoint ifTrue: [self width x max: self width y] ifFalse: [self width]. w := w asInteger. colorArray := Array new: w * 2. hw := 0.5 / w. 0 to: w - 1 do: [:i | "again !! false ifTrue:[] ?!!" param := false ifTrue: [0.5 + (hw * i)] ifFalse: [0.5 + (hw * (w - i))]. colorArray at: i + 1 put: (base mixed: param with: light). "brighten" colorArray at: colorArray size - i put: (base mixed: param with: dark) "darken"]. ^colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'ar 11/26/2001 15:00'! computeColors width = 0 ifTrue:[^colors _ #()]. style == #complexFramed ifTrue:[^self computeFramedColors]. style == #complexAltFramed ifTrue:[^self computeAltFramedColors]. style == #complexRaised ifTrue:[^self computeRaisedColors]. style == #complexAltRaised ifTrue:[^self computeAltRaisedColors]. style == #complexInset ifTrue:[^self computeInsetColors]. style == #complexAltInset ifTrue:[^self computeAltInsetColors]. self error:'Unknown border style: ', style printString.! ! !ComplexBorder methodsFor: 'private' stamp: 'ar 8/25/2001 16:35'! computeFramedColors | base light dark w hw colorArray param | base _ self color asColor. light _ Color white. dark _ Color black. w _ self width isPoint ifTrue:[self width x max: self width y] ifFalse:[self width]. w _ w asInteger. w = 1 ifTrue:[^{base mixed: 0.5 with: light. base mixed: 0.5 with: dark}]. colorArray _ Array new: w. hw _ w // 2. "brighten" 0 to: hw-1 do:[:i| param _ 0.5 + (i asFloat / hw * 0.5). colorArray at: i+1 put: (base mixed: param with: light). "brighten" colorArray at: w-i put: (base mixed: param with: dark). "darken" ]. w odd ifTrue:[colorArray at: hw+1 put: base]. ^colorArray, colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'aoy 2/17/2003 01:06'! computeInsetColors | base light dark w colorArray param hw | base := self color asColor. light := Color white. dark := Color black. w := self width isPoint ifTrue: [self width x max: self width y] ifFalse: [self width]. w := w asInteger. colorArray := Array new: w * 2. hw := 0.5 / w. 0 to: w - 1 do: [:i | param := true ifTrue: [ 0.5 + (hw * i)] ifFalse: [0.5 + (hw * (w - i))]. colorArray at: i + 1 put: (base mixed: param with: dark). "darken" colorArray at: colorArray size - i put: (base mixed: param with: light) "brighten"]. ^colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'aoy 2/17/2003 01:07'! computeRaisedColors | base light dark w colorArray param hw | base := self color asColor. light := Color white. dark := Color black. w := self width isPoint ifTrue: [self width x max: self width y] ifFalse: [self width]. w := w asInteger. colorArray := Array new: w * 2. hw := 0.5 / w. 0 to: w - 1 do: [:i | param := true ifTrue: [0.5 + (hw * i)] ifFalse: [0.5 + (hw * (w - i))]. colorArray at: i + 1 put: (base mixed: param with: light). "brighten" colorArray at: colorArray size - i put: (base mixed: param with: dark) "darken"]. ^colorArray! ! !ComplexBorder methodsFor: 'private' stamp: 'ar 9/4/2001 19:51'! fillStyleForDirection: direction "Fill the given form describing the receiver's look at a particular direction" | index fill dir | index _ direction degrees truncated // 10 + 1. lineStyles ifNotNil:[ fill _ lineStyles at: index. fill ifNotNil:[^fill]. ]. dir _ Point r: 1.0 degrees: index - 1 * 10 + 5. fill _ GradientFillStyle colors: (self colorsForDirection: dir). fill direction: 0 @ width asPoint y; radial: false. fill origin: ((width asPoint x // 2) @ (width asPoint y // 2)) negated. fill pixelRamp: (fill computePixelRampOfSize: 16). fill isTranslucent. "precompute" lineStyles ifNil:[lineStyles _ Array new: 37]. lineStyles at: index put: fill. ^fill! ! !ComplexBorder methodsFor: 'private' stamp: 'ar 8/26/2001 23:39'! intersectFrom: startPt with: startDir to: endPt with: endDir "Compute the intersection of two lines. Return nil if either * the intersection does not exist, or * the intersection is 'before' startPt, or * the intersection is 'after' endPt " | det deltaPt alpha beta | det _ (startDir x * endDir y) - (startDir y * endDir x). det = 0.0 ifTrue:[^nil]. "There's no solution for it" deltaPt _ endPt - startPt. alpha _ (deltaPt x * endDir y) - (deltaPt y * endDir x). beta _ (deltaPt x * startDir y) - (deltaPt y * startDir x). alpha _ alpha / det. beta _ beta / det. alpha < 0 ifTrue:[^nil]. beta > 1.0 ifTrue:[^nil]. "And compute intersection" ^(startPt x + (alpha * startDir x)) @ (startPt y + (alpha * startDir y))! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ComplexBorder class instanceVariableNames: ''! !ComplexBorder class methodsFor: 'instance creation' stamp: 'ar 8/25/2001 16:22'! style: aSymbol ^self new style: aSymbol! ! 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: 'nk 8/18/2004 16:43'! addProgressDecoration: extraParam | f m | targetMorph ifNil: [^self]. (extraParam isForm) ifTrue: [targetMorph submorphsDo: [:mm | (mm isSketchMorph) ifTrue: [mm delete]]. f := Form extent: extraParam extent depth: extraParam depth. extraParam displayOn: f. m := SketchMorph withForm: f. m align: m fullBounds leftCenter with: targetMorph fullBounds leftCenter + (2 @ 0). targetMorph addMorph: m. ^self]. (extraParam isString) ifTrue: [targetMorph submorphsDo: [:mm | (mm isKindOf: StringMorph) ifTrue: [mm delete]]. m := StringMorph contents: extraParam translated. m align: m fullBounds bottomCenter + (0 @ 8) with: targetMorph bounds bottomCenter. targetMorph addMorph: m. ^self]! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'nb 6/17/2003 12:25'! 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. Beeper beep. ]. ! ! !ComplexProgressIndicator methodsFor: 'as yet unclassified' stamp: 'ar 7/8/2001 17:05'! 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: Processor lowIOPriority.! ! !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: 'mir 3/9/2004 16:27'! withProgressDo: aBlock | safetyFactor totals trialRect delta stageCompletedString targetOwner | 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: [ targetOwner := targetMorph owner. 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. translucentMorph hide. targetOwner ifNotNil: [targetOwner hide]. 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 | translucentMorph show. targetOwner ifNotNil: [targetOwner show]. note extraParam ifNotNil:[self addProgressDecoration: note extraParam]. 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: 'nb 6/17/2003 12:25'! historyReport " ComplexProgressIndicator historyReport " | answer data | History ifNil: [^Beeper 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'! ! TestCase subclass: #ComplexTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Numbers'! !ComplexTest methodsFor: 'testing' stamp: 'nice 2/8/2006 22:09'! testEquality "self run: #testEquality" "self debug: #testEquality" self assert: 0i = 0. self assert: (2 - 5i) = ((1 -4 i) + (1 - 1i)). self assert: 0i isZero. self deny: (1 + 3 i) = 1. self deny: (1 + 3 i) = (1 + 2i). "Some more stuff" self deny: (1 i) = nil. self deny: nil = (1 i). self deny: (1 i) = #(1 2 3). self deny: #(1 2 3) = (1 i). self deny: (1 i) = 0. self deny: 0 = (1 i). self assert: (1 + 0 i) = 1. self assert: 1 = (1+ 0 i). self assert: (1 + 0 i) = 1.0. self assert: 1.0 = (1+ 0 i). self assert: (1/2 + 0 i) = (1/2). self assert: (1/2) = (1/2+ 0 i).! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:00'! testAbs "self run: #testAbs" "self debug: #testAbs" | c | c := (6 - 6 i). self assert: c abs = 72 sqrt. ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 13:59'! testAdding "self run: #testAdding" | c | c := (5 - 6 i) + (-5 + 8 i). "Complex with Complex" self assert: (c = (0 + 2 i)).! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:02'! testArg "self run: #testArg" "self debug: #testArg" | c | c := (0 + 5 i) . self assert: c arg = (Float pi/ 2). ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:13'! testComplexCollection "self run: #testComplexCollection" "self debug: #testComplexCollection" | array array2 | array := Array with: 1 + 2i with: 3 + 4i with: 5 + 6i. array2 := 2 * array. array with: array2 do: [:one :two | self assert: (2 * one) = two ] ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:16'! testConversion "self run: #testConversion" "self debug: #testConversion" self assert: ((1 + 2i) + 1) = (2 + 2 i). self assert: (1 + (1 + 2i)) = (2 + 2 i). self assert: ((1 + 2i) + 1.0) = (2.0 + 2 i). self assert: (1.0 + (1 + 2i)) = (2.0 + 2 i). self assert: ((1 + 2i) + (2/3)) = ((5/3) + 2 i ). self assert: ((2/3) + (1 + 2i)) = ((5/3) + 2 i )! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 13:59'! testCreation "self run: #testCreation" | c | c := 5 i. self assert: (c real = 0). self assert: (c imaginary = 5). c := 6 + 7 i. self assert: (c real = 6). self assert: ( c imaginary = 7). c := 5.6 - 8 i. self assert: (c real = 5.6). self assert: (c imaginary = -8). c := Complex real: 10 imaginary: 5. self assert: (c real = 10). self assert: (c imaginary = 5). c := Complex abs: 5 arg: (Float pi/2). self assert: (c real rounded = 0). self assert: (c imaginary = 5). ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 6/5/2005 08:29'! testDivision1 "self run: #testDivision1" "self debug: #testDivision1" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 / c2. self deny: (quotient - 1) isZero. "This test fails due to the wonders of floating point arithmetic. Please have a look at Complex>>divideSecureBy: and #divideFastAndSecureBy: how this can be avoided." ! ! !ComplexTest methodsFor: 'tests' stamp: 'laza 9/26/2005 10:24'! testLn self assert: (Float e + 0 i) ln = Float e ln "See Bug 1815 on Mantis"! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:03'! testNegated "self run: #testNegated" "self debug: #testNegated" | c | c := (2 + 5 i) . self assert: c negated = (-2 - 5i). ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:05'! testReciprocal "self run: #testReciprocal" "self debug: #testReciprocal" | c | c := (2 + 5 i). self assert: c reciprocal = ((2/29) - (5/29)i). ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 14:07'! testReciprocalError "self run: #testReciprocalError" "self debug: #testReciprocalError" | c | c := (0 i). self should: [c reciprocal] raise: ZeroDivide ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 6/5/2005 08:29'! testSecureDivision1 "self run: #testSecureDivision1" "self debug: #testSecureDivision1" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 divideSecureBy: c2. self assert: (quotient - 1) isZero. ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 6/5/2005 08:29'! testSecureDivision2 "self run: #testSecureDivision2" "self debug: #testSecureDivision2" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 divideFastAndSecureBy: c2. self assert: (quotient - 1) isZero. ! ! !ComplexTest methodsFor: 'tests' stamp: 'sd 7/17/2004 13:24'! testSquared "self run: #testSquared" "self debug: #testSquared" | c c2 | c := (6 - 6 i). c2 := (c squared). self assert: c2 imaginary = -72. self assert: c2 real = 0.! ! !ComplexTest methodsFor: 'testing - bugs' stamp: 'md 2/18/2006 16:53'! testBug1 self assert: (0.5 * (2+0i) ln) exp = (0.5 * 2 ln) exp.! ! Player subclass: #Component instanceVariableNames: 'model pinSpecs' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Components'! !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: '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: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: 'variables' stamp: 'gm 3/2/2003 18:35'! addVariableNamed: varName "Adjust name if necessary and add it" | otherNames i partName | otherNames := self class allInstVarNames. i := nil. [partName := i isNil ifTrue: [varName] ifFalse: [varName , i printString]. otherNames includes: partName] whileTrue: [i := i isNil ifTrue: [1] ifFalse: [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: 'viewer' stamp: 'di 5/3/1998 19:58'! externalName ^ self class name! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Component class instanceVariableNames: ''! !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: 'compiling' stamp: 'di 4/17/1998 14:02'! acceptsLoggingOfCompilation "Log everything for now" ^ true! ! !Component class methodsFor: 'compiling' 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! ! PasteUpMorph subclass: #ComponentLayout instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Components'! !ComponentLayout methodsFor: 'initialization' stamp: 'di 1/17/2000 16:36'! initialize super initialize. self createCustomModel. self extent: 384@256! ! !ComponentLayout methodsFor: 'layout' stamp: 'di 5/3/1998 10:17'! acceptDroppingMorph: aMorph event: evt "Eschew all of PasteUp's mechanism for now" self addMorph: aMorph. ! ! !ComponentLayout methodsFor: 'menus' stamp: 'dgd 8/30/2003 21:17'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. menu addLine. menu add: 'inspect model in morphic' translated action: #inspectModelInMorphic! ! !ComponentLayout methodsFor: 'model' stamp: 'dgd 2/21/2003 23:06'! 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 isNil ifFalse: [^self]. "already has a model" model := Component newSubclass new! ! !ComponentLayout methodsFor: 'submorphs-accessing' stamp: 'dgd 2/22/2003 19:06'! allKnownNames ^super allKnownNames , (self submorphs collect: [:m | m knownName] thenSelect: [:m | m notNil])! ! !ComponentLayout methodsFor: '*Tools' stamp: 'ar 9/27/2005 20:58'! 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: '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: 'di 5/2/1998 15:07'! deleteComponent model removeDependent: self. self pinsDo: [:pin | pin delete]. ^ super delete! ! !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: '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]! ! !ComponentLikeModel methodsFor: 'dropping/grabbing' 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: 'geometry' stamp: 'di 4/29/1998 09:49'! extent: newExtent super extent: newExtent. self submorphsDo: [:m | (m isKindOf: PinMorph) ifTrue: [m placeFromSpec]]! ! !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: 'naming' stamp: 'dgd 2/21/2003 23:01'! 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 isNil ifTrue: [self compilePropagationMethods] ifFalse: [self copySlotMethodsFrom: old] "old ones not erased!!"! ! !ComponentLikeModel methodsFor: 'submorphs-add/remove' stamp: 'rbb 2/18/2005 13:32'! delete "Delete the receiver. Possibly put up confirming dialog. Abort if user changes mind" (model isKindOf: Component) ifTrue: [^self deleteComponent]. (model isMorphicModel) ifFalse: [^super delete]. slotName ifNotNil: [(self 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: [(self confirm: '...but should I at least dismiss this morph? [choose no to leave everything unchanged]') ifFalse: [^self]]]. super delete! ! VoiceEvent subclass: #CompositeEvent instanceVariableNames: 'timedEvents' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !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/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 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'accessing-private' stamp: 'len 8/28/1999 22:54'! timedEvents ^ timedEvents! ! !CompositeEvent methodsFor: 'accessing-private' stamp: 'stephaneducasse 2/3/2006 22:22'! timedEvents: aCollection timedEvents := aCollection! ! !CompositeEvent methodsFor: 'converting' stamp: 'len 9/29/1999 02:52'! asArray ^ (1 to: self size) collect: [ :each | self at: each]! ! !CompositeEvent methodsFor: 'converting' stamp: 'stephaneducasse 2/3/2006 22:22'! asPHOString | stream | stream := WriteStream on: String new. self do: [ :each | stream nextPutAll: each asPHOString; nextPut: Character cr]. ^ stream contents! ! !CompositeEvent methodsFor: 'copying' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'initialization' stamp: 'stephaneducasse 2/3/2006 22:22'! initialize: anInteger timedEvents := SortedCollection new: anInteger! ! !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: 'private' stamp: 'stephaneducasse 2/3/2006 22:22'! 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! ! WidgetStub subclass: #CompositeStub instanceVariableNames: 'children' classVariableNames: '' poolDictionaries: '' category: 'ToolBuilder-SUnit'! !CompositeStub methodsFor: 'accessing' stamp: 'cwp 4/25/2005 03:50'! children ^children! ! !CompositeStub methodsFor: 'accessing' stamp: 'cwp 4/25/2005 03:50'! children: anObject children := anObject! ! !CompositeStub methodsFor: 'accessing' stamp: 'cwp 4/25/2005 03:51'! eventAccessors ^ #(children)! ! !CompositeStub methodsFor: 'accessing' stamp: 'cwp 4/25/2005 03:54'! widgetNamed: aString self name = aString ifTrue: [^ self] ifFalse: [children do: [:ea | (ea widgetNamed: aString) ifNotNilDo: [:w | ^ w]]]. ^ nil! ! 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: '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: '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: '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: '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: '*nebraska-*nebraska-Morphic-Remote' 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:44'! globalTransform: gt localTransform: lt ^self new globalTransform: gt localTransform: lt! ! !CompositeTransform class methodsFor: '*nebraska-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! ! Voice subclass: #CompositeVoice instanceVariableNames: 'voices' classVariableNames: '' poolDictionaries: '' category: 'Speech-Events'! !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: 'stephaneducasse 2/3/2006 22:22'! voices: aCollection voices := aCollection! ! !CompositeVoice methodsFor: 'enumerating' stamp: 'len 8/29/1999 02:21'! do: aBlock self voices do: aBlock! ! !CompositeVoice methodsFor: 'initialization' stamp: 'len 8/28/1999 04:00'! initialize super initialize. self voices: OrderedCollection new! ! !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: '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. ! ! !CompositionScanner methodsFor: 'scanning' stamp: 'ar 12/17/2001 02:06'! 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. spaceCount _ 0. self handleIndentation. leftMargin _ destX. line leftMargin: leftMargin. 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: 'hmm 7/20/2000 18:24'! 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" self handleIndentation. 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: 'RAA 5/4/2001 13:52'! columnBreak "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'! 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 12/17/2001 02:13'! placeEmbeddedObject: anchoredMorph | descent | "Workaround: The following should really use #textAnchorType" anchoredMorph relativeTextAnchorPosition ifNotNil:[^true]. (super placeEmbeddedObject: anchoredMorph) ifFalse: ["It doesn't fit" "But if it's the first character then leave it here" lastIndex < line first ifFalse:[ line stop: lastIndex-1. ^ false]]. descent _ lineHeight - baseline. lineHeight _ lineHeight max: anchoredMorph height. baseline _ lineHeight - descent. line stop: lastIndex. ^ true! ! !CompositionScanner methodsFor: 'stop conditions' stamp: 'RAA 5/7/2001 10:12'! setFont super setFont. stopConditions == DefaultStopConditions ifTrue:[stopConditions _ stopConditions copy]. stopConditions at: Space asciiValue + 1 put: #space. wantsColumnBreaks == true ifTrue: [ stopConditions at: TextComposer characterForColumnBreak asciiValue + 1 put: #columnBreak. ]. ! ! !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 ! ! TextConverter subclass: #CompoundTextConverter instanceVariableNames: 'state' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CompoundTextConverter commentStamp: '' prior: 0! Text converter for X Compound Text.! !CompoundTextConverter methodsFor: 'conversion' stamp: 'ar 4/9/2005 22:25'! nextFromStream: aStream | character character2 size leadingChar offset result | aStream isBinary ifTrue: [^ aStream basicNext]. character _ aStream basicNext. character ifNil: [^ nil]. character == Character escape ifTrue: [ self parseShiftSeqFromStream: aStream. character _ aStream basicNext. character ifNil: [^ nil]]. character asciiValue < 128 ifTrue: [ size _ state g0Size. leadingChar _ state g0Leading. offset _ 16r21. ] ifFalse: [ size _state g1Size. leadingChar _ state g1Leading. offset _ 16rA1. ]. size = 1 ifTrue: [ leadingChar = 0 ifTrue: [^ character] ifFalse: [^ Character leadingChar: leadingChar code: character asciiValue] ]. size = 2 ifTrue: [ character2 _ aStream basicNext. character2 ifNil: [^ nil. "self errorMalformedInput"]. character _ character asciiValue - offset. character2 _ character2 asciiValue - offset. result _ Character leadingChar: leadingChar code: character * 94 + character2. ^ result asUnicodeChar. "^ self toUnicode: result" ]. self error: 'unsupported encoding'. ! ! !CompoundTextConverter methodsFor: 'conversion' stamp: 'ar 4/12/2005 14:10'! nextPut: aCharacter toStream: aStream | ascii leadingChar class | aStream isBinary ifTrue: [^aCharacter storeBinaryOn: aStream]. aCharacter isTraditionalDomestic ifFalse: [ class _ (EncodedCharSet charsetAt: aCharacter leadingChar) traditionalCharsetClass. ascii _ (class charFromUnicode: aCharacter asUnicode) charCode. leadingChar _ class leadingChar. ] ifTrue: [ ascii _ aCharacter charCode. leadingChar _ aCharacter leadingChar. ]. self nextPutValue: ascii toStream: aStream withShiftSequenceIfNeededForLeadingChar: leadingChar. ! ! !CompoundTextConverter methodsFor: 'friend' stamp: 'yo 9/16/2002 21:41'! currentCharSize ^ state charSize. ! ! !CompoundTextConverter methodsFor: 'friend' stamp: 'yo 8/18/2003 17:50'! emitSequenceToResetStateIfNeededOn: aStream Latin1 emitSequenceToResetStateIfNeededOn: aStream forState: state. ! ! !CompoundTextConverter methodsFor: 'friend' stamp: 'yo 11/4/2002 12:33'! restoreStateOf: aStream with: aConverterState state _ aConverterState copy. aStream position: state streamPosition. ! ! !CompoundTextConverter methodsFor: 'friend' stamp: 'yo 11/4/2002 13:52'! saveStateOf: aStream | inst | inst _ state clone. inst streamPosition: aStream position. ^ inst. ! ! !CompoundTextConverter methodsFor: 'initialize-release' stamp: 'yo 8/13/2003 11:45'! initialize state _ CompoundTextConverterState g0Size: 1 g1Size: 1 g0Leading: 0 g1Leading: 0 charSize: 1 streamPosition: 0. acceptingEncodings _ #(ascii iso88591 jisx0208 gb2312 ksc5601 ksx1001 ) copy. ! ! !CompoundTextConverter methodsFor: 'query' stamp: 'yo 8/23/2002 22:39'! accepts: aSymbol ^ acceptingEncodings includes: aSymbol. ! ! !CompoundTextConverter methodsFor: 'private' stamp: 'yo 11/4/2002 14:36'! nextPutValue: ascii toStream: aStream withShiftSequenceIfNeededForLeadingChar: leadingChar | charset | charset _ EncodedCharSet charsetAt: leadingChar. charset ifNotNil: [ charset nextPutValue: ascii toStream: aStream withShiftSequenceIfNeededForTextConverterState: state. ] ifNil: [ "..." ]. ! ! !CompoundTextConverter methodsFor: 'private' stamp: 'yo 12/10/2003 15:46'! parseShiftSeqFromStream: aStream | c set target id | c _ aStream basicNext. c = $$ ifTrue: [ set _ #multibyte. c _ aStream basicNext. c = $( ifTrue: [target _ 1]. c = $) ifTrue: [target _ 2]. target ifNil: [target _ 1. id _ c] ifNotNil: [id _ aStream basicNext]. ] ifFalse: [ c = $( ifTrue: [target _ 1. set _ #nintyfour]. c = $) ifTrue: [target _ 2. set _ #nintyfour]. c = $- ifTrue: [target _ 2. set _ #nintysix]. "target = nil ifTrue: [self errorMalformedInput]." id _ aStream basicNext. ]. (set = #multibyte and: [id = $B]) ifTrue: [ state charSize: 2. target = 1 ifTrue: [ state g0Size: 2. state g0Leading: 1. ] ifFalse: [ state g1Size: 2. state g1Leading: 1. ]. ^ self ]. (set = #multibyte and: [id = $A]) ifTrue: [ state charSize: 2. target = 1 ifTrue: [ state g0Size: 2. state g0Leading: 2. ] ifFalse: [ state g1Size: 2. state g1Leading: 2. ]. ^ self ]. (set = #nintyfour and: [id = $B or: [id = $J]]) ifTrue: [ state charSize: 1. state g0Size: 1. state g0Leading: 0. ^ self ]. (set = #nintysix and: [id = $A]) ifTrue: [ state charSize: 1. state g1Size: 1. state g1Leading: 0. ^ self ]. "self errorUnsupported." ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompoundTextConverter class instanceVariableNames: ''! !CompoundTextConverter class methodsFor: 'utilities' stamp: 'yo 10/24/2002 14:16'! encodingNames ^ #('iso-2022-jp' 'x-ctext') copy ! ! Object subclass: #CompoundTextConverterState instanceVariableNames: 'g0Size g1Size g0Leading g1Leading charSize streamPosition' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !CompoundTextConverterState commentStamp: '' prior: 0! This represents the state of CompoundTextConverter.! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:30'! charSize ^ charSize ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:41'! charSize: s charSize _ s. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:29'! g0Leading ^ g0Leading ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:41'! g0Leading: l g0Leading _ l. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:29'! g0Size ^ g0Size ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:41'! g0Size: s g0Size _ s. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 14:37'! g0Size: g0 g1Size: g1 g0Leading: g0l g1Leading: g1l charSize: cSize streamPosition: pos g0Size _ g0. g1Size _ g1. g0Leading _ g0l. g1Leading _ g1l. charSize _ cSize. streamPosition _ pos. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:30'! g1Leading ^ g1Leading ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:41'! g1Leading: l g1Leading _ l. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:29'! g1Size ^ g1Size ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:41'! g1Size: s g1Size _ s. ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 11/4/2002 12:31'! printOn: aStream aStream nextPut: $(; nextPutAll: g0Size printString; space; nextPutAll: g1Size printString; space; nextPutAll: g0Leading printString; space; nextPutAll: g1Leading printString; space; nextPutAll: charSize printString; space; nextPutAll: streamPosition printString. aStream nextPut: $). ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 8/23/2002 21:30'! streamPosition ^ streamPosition ! ! !CompoundTextConverterState methodsFor: 'accessing' stamp: 'yo 9/16/2002 20:40'! streamPosition: pos streamPosition _ pos. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompoundTextConverterState class instanceVariableNames: ''! !CompoundTextConverterState class methodsFor: 'instance creation' stamp: 'yo 8/19/2002 17:04'! g0Size: g0 g1Size: g1 g0Leading: g0l g1Leading: g1l charSize: cSize streamPosition: pos ^ (self new) g0Size: g0 g1Size: g1 g0Leading: g0l g1Leading: g1l charSize: cSize streamPosition: pos ; yourself. ! ! TileLikeMorph subclass: #CompoundTileMorph instanceVariableNames: 'type testPart yesPart noPart' classVariableNames: '' poolDictionaries: '' category: 'EToys-Scripting Tiles'! !CompoundTileMorph commentStamp: '' prior: 0! A statement with other whole statements inside it. If-Then. Test.! !CompoundTileMorph methodsFor: 'access' stamp: 'sw 8/11/1998 16:42'! associatedPlayer ^ nil! ! !CompoundTileMorph methodsFor: 'access' stamp: 'nk 10/14/2004 11:37'! myMorph ^nil! ! !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: 'dropping/grabbing' stamp: 'sw 12/13/2001 16:42'! wantsDroppedMorph: aMorph event: evt "Removing this method entirely would be okay someday" ^ false " ^ (aMorph isKindOf: TileMorph) or: [(aMorph isKindOf: ScriptEditorMorph) or: [(aMorph isKindOf: CompoundTileMorph) or: [aMorph isKindOf: CommandTilesMorph]]]" ! ! !CompoundTileMorph methodsFor: 'event handling' stamp: 'tk 2/28/2001 21:22'! handlesMouseDown: evt ^true! ! !CompoundTileMorph methodsFor: 'event handling' stamp: 'di 10/17/97 21:36'! handlesMouseOver: evt ^ true ! ! !CompoundTileMorph methodsFor: 'event handling' stamp: 'di 9/14/1998 07:50'! handlesMouseOverDragging: evt ^ true ! ! !CompoundTileMorph methodsFor: 'event handling' stamp: 'tk 2/28/2001 21:25'! mouseDown: evt "Pretend we picked up the tile and then put it down for a trial positioning." "The essence of ScriptEditor mouseEnter:" | ed ss guyToTake | " self isPartsDonor ifTrue:[ dup _ self duplicate. evt hand attachMorph: dup. dup position: evt position. ^self]. submorphs isEmpty never true ifTrue: [^ self]. " (ed _ self enclosingEditor) ifNil: [^evt hand grabMorph: self]. guyToTake _ self. owner class == TilePadMorph ifTrue: ["picking me out of another phrase" (ss _ submorphs first) class == TilePadMorph ifTrue: [ss _ ss submorphs first]. guyToTake _ ss veryDeepCopy]. evt hand grabMorph: guyToTake. ed startStepping. ed mouseEnterDragging: evt. ed setProperty: #justPickedUpPhrase toValue: true. ! ! !CompoundTileMorph methodsFor: 'event handling' 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: 'event handling' stamp: 'di 9/14/1998 08:07'! mouseEnterDragging: evt "Test button state elsewhere if at all" ^ self mouseEnter: evt! ! !CompoundTileMorph methodsFor: 'event handling' 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: 'event handling' stamp: 'di 9/14/1998 08:08'! mouseLeaveDragging: evt "Test button state elsewhere if at all" ^ self mouseLeave: evt! ! !CompoundTileMorph methodsFor: 'e-toy support' stamp: 'ar 2/7/2001 17:57'! isTileEditor "Yes I am" ^true! ! !CompoundTileMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:21'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !CompoundTileMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:21'! defaultColor "answer the default color/fill style for the receiver" ^ Color orange muchLighter! ! !CompoundTileMorph methodsFor: 'initialization' stamp: 'sw 7/22/2004 00:13'! initialize "initialize the state of the receiver" | r stringMorph | super initialize. self layoutInset: 2. self listDirection: #topToBottom. self hResizing: #shrinkWrap; vResizing: #shrinkWrap; cellInset: (0 @ 1); minCellSize: (200@14). "NB: hResizing gets reset to #spaceFill below, after the standalone structure is created" r _ AlignmentMorph newRow color: color; layoutInset: 0. r setProperty: #demandsBoolean toValue: true. r addMorphBack: (Morph new color: color; extent: 2 @ 5). "spacer" stringMorph _ StringMorph new contents: 'Test' translated. stringMorph name: 'Test'. r addMorphBack: stringMorph. r addMorphBack: (Morph new color: color; extent: 5 @ 5). "spacer" r addMorphBack: (testPart _ BooleanScriptEditor new borderWidth: 0; layoutInset: 1). testPart color: Color transparent. testPart hResizing: #spaceFill. self addMorphBack: r. r _ AlignmentMorph newRow color: color; layoutInset: 0. r addMorphBack: (Morph new color: color; extent: 30 @ 5). "spacer" stringMorph _ StringMorph new contents: 'Yes' translated. stringMorph name: 'Yes'. r addMorphBack: stringMorph. r addMorphBack: (Morph new color: color; extent: 5 @ 5). "spacer" r addMorphBack: (yesPart _ ScriptEditorMorph new borderWidth: 0; layoutInset: 2). yesPart hResizing: #spaceFill. yesPart color: Color transparent. self addMorphBack: r. r _ AlignmentMorph newRow color: color; layoutInset: 0. r addMorphBack: (Morph new color: color; extent: 35 @ 5). "spacer" stringMorph _ StringMorph new contents: 'No' translated. stringMorph name: 'No'. r addMorphBack: stringMorph. r addMorphBack: (Morph new color: color; extent: 5 @ 5). "spacer" r addMorphBack: (noPart _ ScriptEditorMorph new borderWidth: 0; layoutInset: 2). noPart hResizing: #spaceFill. noPart color: Color transparent. self addMorphBack: r. self bounds: self fullBounds. self updateWordingToMatchVocabulary. self hResizing:#spaceFill ! ! !CompoundTileMorph methodsFor: 'initialization' stamp: 'nk 10/8/2004 11:56'! updateWordingToMatchVocabulary | labels | labels _ OrderedCollection new. self submorphs do: [:submorph | submorph submorphs do: [:subsubmorph | subsubmorph class == StringMorph ifTrue: [labels add: subsubmorph]]]. labels do: [:label | label knownName ifNotNilDo: [ :nm | label acceptValue: nm translated ]] ! ! !CompoundTileMorph methodsFor: 'layout'! acceptDroppingMorph: aMorph event: evt "Forward the dropped morph to the appropriate part." (self targetPartFor: aMorph) acceptDroppingMorph: aMorph event: evt. ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 10/18/97 18:03'! install "Backstop for obscure cases"! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'ar 2/6/2001 22:07'! recompileScript "Pertains only when the test is outside a script?!!" ! ! !CompoundTileMorph methodsFor: 'miscellaneous' stamp: 'sw 9/27/2001 17:27'! resultType "Answer the result type of the receiver" ^ #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: 'tk 2/15/2001 16:36'! tileRows "Answer a list of tile rows, in this case just one though it's compound" ^ Array with: (Array with: self veryDeepCopy)! ! !CompoundTileMorph methodsFor: 'miscellaneous'! type ^ #compound ! ! !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: 'testing' stamp: 'yo 11/4/2002 20:33'! isTileScriptingElement ^ true ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompoundTileMorph class instanceVariableNames: ''! !CompoundTileMorph class methodsFor: 'new-morph participation' 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: '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 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 class instanceVariableNames: ''! !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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:40'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:40'! soundClassName: aStringOrSymbol soundClassName := aStringOrSymbol asSymbol. ! ! !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: 'stephaneducasse 2/4/2006 20:40'! 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. "???"! ! !CompressedSoundData methodsFor: 'asSound' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:40'! 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! ! ReadWriteStream subclass: #CompressedSourceStream instanceVariableNames: 'segmentFile segmentSize nSegments segmentTable segmentIndex dirty endOfFile' classVariableNames: '' poolDictionaries: '' category: 'Files-System'! !CompressedSourceStream commentStamp: 'di 11/3/2003 17:58' prior: 0! I implement a file format that compresses segment by segment to allow incremental writing and browsing. Note that the file can only be written at the end. Structure: segmentFile The actual compressed file. segmentSize This is the quantum of compression. The virtual file is sliced up into segments of this size. nSegments The maximum number of segments to which this file can be grown. endOfFile The user's endOfFile pointer. segmentTable When a file is open, this table holds the physical file positions of the compressed segments. segmentIndex Index of the most recently accessed segment. Inherited from ReadWriteStream... collection The segment buffer, uncompressed position This is the position *local* to the current segment buffer readLimit ReadLimit for the current buffer writeLimit WriteLimit for the current buffer Great care must be exercised to distinguish between the position relative to the segment buffer and the full file position (and, or course, the segment file position ;-). The implementation defaults to a buffer size of 20k, and a max file size of 34MB (conveniently chosen to be greater than the current 33MB limit of source code pointers). The format of the file is as follows: segmentSize 4 bytes nSegments 4 bytes endOfFile 4 bytes segmentTable 4 bytes * (nSegments+1) beginning of first compressed segment It is possible to override the default allocation by sending the message #segmentSize:nSegments: immediately after opening a new file for writing, as follows: bigFile _ (CompressedSourceStream on: (FileStream newFileNamed: 'biggy.stc')) segmentSize: 50000 maxSize: 200000000 The difference between segment table entries reveals the size of each compressed segment. When a file is being written, it may lack the final segment, but any flush, position:, or close will force a dirty segment to be written.! !CompressedSourceStream methodsFor: 'access' stamp: 'di 11/3/2003 00:41'! atEnd position >= readLimit ifFalse: [^ false]. "more in segment" ^ self position >= endOfFile "more in file"! ! !CompressedSourceStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! contentsOfEntireFile | contents | self position: 0. contents := self next: self size. self close. ^ contents! ! !CompressedSourceStream methodsFor: 'access' stamp: 'di 11/1/2003 19:50'! flush dirty ifTrue: ["Write buffer, compressed, to file, and also write the segment offset and eof" self writeSegment].! ! !CompressedSourceStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! next position >= readLimit ifTrue: [^ (self next: 1) at: 1] ifFalse: [^ collection at: (position := position + 1)]! ! !CompressedSourceStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! next: n | str | n <= (readLimit - position) ifTrue: ["All characters are available in buffer" str := collection copyFrom: position + 1 to: position + n. position := position + n. ^ str]. "Read limit could be segment boundary or real end of file" (readLimit + self segmentOffset) = endOfFile ifTrue: ["Real end of file -- just return what's available" ^ self next: readLimit - position]. "Read rest of segment. Then (after positioning) read what remains" str := self next: readLimit - position. self position: self position. ^ str , (self next: n - str size) ! ! !CompressedSourceStream methodsFor: 'access' stamp: 'di 11/2/2003 11:27'! nextPut: char "Slow, but we don't often write, and then not a lot" self nextPutAll: char asString. ^ char! ! !CompressedSourceStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! nextPutAll: str | n nInSeg | n := str size. n <= (writeLimit - position) ifTrue: ["All characters fit in buffer" collection replaceFrom: position + 1 to: position + n with: str. dirty := true. position := position + n. readLimit := readLimit max: position. endOfFile := endOfFile max: self position. ^ str]. "Write what fits in segment. Then (after positioning) write what remains" nInSeg := writeLimit - position. nInSeg = 0 ifTrue: [self position: self position. self nextPutAll: str] ifFalse: [self nextPutAll: (str first: nInSeg). self position: self position. self nextPutAll: (str allButFirst: nInSeg)] ! ! !CompressedSourceStream methodsFor: 'access' stamp: 'di 11/2/2003 09:27'! position ^ position + self segmentOffset! ! !CompressedSourceStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! position: newPosition | compressedBuffer newSegmentIndex | newPosition > endOfFile ifTrue: [self error: 'Attempt to position beyond the end of file']. newSegmentIndex := (newPosition // segmentSize) + 1. newSegmentIndex ~= segmentIndex ifTrue: [self flush. segmentIndex := newSegmentIndex. newSegmentIndex > nSegments ifTrue: [self error: 'file size limit exceeded']. segmentFile position: (segmentTable at: segmentIndex). (segmentTable at: segmentIndex+1) = 0 ifTrue: [newPosition ~= endOfFile ifTrue: [self error: 'Internal logic error']. collection size = segmentSize ifFalse: [self error: 'Internal logic error']. "just leave garbage beyond end of file"] ifFalse: [compressedBuffer := segmentFile next: ((segmentTable at: segmentIndex+1) - (segmentTable at: segmentIndex)). collection := (GZipReadStream on: compressedBuffer) upToEnd asString]. readLimit := collection size min: endOfFile - self segmentOffset]. position := newPosition \\ segmentSize. ! ! !CompressedSourceStream methodsFor: 'access' stamp: 'di 11/1/2003 11:41'! size ^ endOfFile ifNil: [0]! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'di 11/1/2003 22:36'! binary self error: 'Compressed source files are ascii to the user (though binary underneath)'! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'di 11/1/2003 22:36'! close self flush. segmentFile close! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'stephaneducasse 2/4/2006 20:31'! openOn: aFile "Open the receiver." segmentFile := aFile. segmentFile binary. segmentFile size > 0 ifTrue: [self readHeaderInfo. "If file exists, then read the parameters"] ifFalse: [self segmentSize: 20000 maxSize: 34000000. "Otherwise write default values"]! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'di 11/3/2003 10:13'! openReadOnly segmentFile openReadOnly! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'stephaneducasse 2/4/2006 20:31'! readHeaderInfo | valid a b | segmentFile position: 0. segmentSize := segmentFile nextNumber: 4. nSegments := segmentFile nextNumber: 4. endOfFile := segmentFile nextNumber: 4. segmentFile size < (nSegments+1 + 3 * 4) ifTrue: "Check for reasonable segment info" [self error: 'This file is not in valid compressed source format']. segmentTable := (1 to: nSegments+1) collect: [:x | segmentFile nextNumber: 4]. segmentTable first ~= self firstSegmentLoc ifTrue: [self error: 'This file is not in valid compressed source format']. valid := true. 1 to: nSegments do: "Check that segment offsets are ascending" [:i | a := segmentTable at: i. b := segmentTable at: i+1. (a = 0 and: [b ~= 0]) ifTrue: [valid := false]. (a ~= 0 and: [b ~= 0]) ifTrue: [b <= a ifTrue: [valid := false]]]. valid ifFalse: [self error: 'This file is not in valid compressed source format']. dirty := false. self position: 0.! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'di 11/3/2003 10:09'! readOnlyCopy ^ self class on: segmentFile readOnlyCopy! ! !CompressedSourceStream methodsFor: 'open/close' stamp: 'di 11/2/2003 23:07'! test "FileDirectory default deleteFileNamed: 'test.stc'. (CompressedSourceStream on: (FileStream newFileNamed: 'test.stc')) fileOutChanges" "FileDirectory default deleteFileNamed: 'test2.stc'. ((CompressedSourceStream on: (FileStream newFileNamed: 'test2.stc')) segmentSize: 100 nSegments: 1000) fileOutChanges" "FileDirectory default deleteFileNamed: 'test3.st'. (FileStream newFileNamed: 'test3.st') fileOutChanges" "(CompressedSourceStream on: (FileStream oldFileNamed: 'test.stc')) contentsOfEntireFile" ! ! !CompressedSourceStream methodsFor: 'private' stamp: 'di 11/20/2003 12:45'! fileID "Only needed for OSProcess stuff" ^ segmentFile fileID ! ! !CompressedSourceStream methodsFor: 'private' stamp: 'di 11/2/2003 09:35'! firstSegmentLoc "First segment follows 3 header words and segment table" ^ (3 + nSegments+1) * 4! ! !CompressedSourceStream methodsFor: 'private' stamp: 'di 11/2/2003 09:24'! segmentOffset ^ segmentIndex - 1 * segmentSize! ! !CompressedSourceStream methodsFor: 'private' stamp: 'stephaneducasse 2/4/2006 20:31'! segmentSize: segSize maxSize: maxSize "Note that this method can be called after the initial open, provided that no writing has yet taken place. This is how to override the default segmentation." self size = 0 ifFalse: [self error: 'Cannot set parameters after the first write']. segmentFile position: 0. segmentFile nextNumber: 4 put: (segmentSize := segSize). segmentFile nextNumber: 4 put: (nSegments := maxSize // segSize + 2). segmentFile nextNumber: 4 put: (endOfFile := 0). segmentTable := Array new: nSegments+1 withAll: 0. segmentTable at: 1 put: self firstSegmentLoc. "Loc of first segment, always." segmentTable do: [:i | segmentFile nextNumber: 4 put: i]. segmentIndex := 1. collection := String new: segmentSize. writeLimit := segmentSize. readLimit := 0. position := 0. endOfFile := 0. self writeSegment. ! ! !CompressedSourceStream methodsFor: 'private' stamp: 'stephaneducasse 2/4/2006 20:31'! writeSegment "The current segment must be the last in the file." | compressedSegment | segmentFile position: (segmentTable at: segmentIndex). compressedSegment := ByteArray streamContents: [:strm | (GZipWriteStream on: strm) nextPutAll: collection asByteArray; close]. segmentFile nextPutAll: compressedSegment. segmentTable at: segmentIndex + 1 put: segmentFile position. segmentFile position: 2 * 4. segmentFile nextNumber: 4 put: endOfFile. segmentFile position: (segmentIndex + 3) * 4. segmentFile nextNumber: 4 put: (segmentTable at: segmentIndex + 1). dirty := false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CompressedSourceStream class instanceVariableNames: ''! !CompressedSourceStream class methodsFor: 'as yet unclassified' stamp: 'di 11/1/2003 22:58'! on: aFile ^ self basicNew openOn: aFile! ! NetworkError subclass: #ConnectionClosed instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Kernel'! !ConnectionClosed commentStamp: 'mir 5/12/2003 18:12' prior: 0! Signals a prematurely closed connection. ! 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: 'KLC 10/20/2005 11:24'! 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 := 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 waitForAcceptFor: 10] on: ConnectionTimedOut do: [:ex | newConnection := nil]. (newConnection notNil and: [newConnection isConnected]) ifTrue: [ accessSema critical: [connections addLast: newConnection.]. newConnection := nil. self changed]. self pruneStaleConnections]. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'mir 5/15/2003 18:28'! 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 waitForConnectionFor: 10] on: ConnectionTimedOut do: [:ex | 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 ! ! NetworkError subclass: #ConnectionRefused instanceVariableNames: 'host port' classVariableNames: '' poolDictionaries: '' category: 'Network-Kernel'! !ConnectionRefused commentStamp: 'mir 5/12/2003 18:14' prior: 0! Signals that a connection to the specified host and port was refused. host host which refused the connection port prot to which the connection was refused ! !ConnectionRefused methodsFor: 'accessing' stamp: 'len 12/14/2002 11:58'! host ^ host! ! !ConnectionRefused methodsFor: 'accessing' stamp: 'len 12/14/2002 11:39'! host: addressOrHostName port: portNumber host _ addressOrHostName. port _ portNumber! ! !ConnectionRefused methodsFor: 'accessing' stamp: 'len 12/14/2002 11:58'! port ^ port! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ConnectionRefused class instanceVariableNames: ''! !ConnectionRefused class methodsFor: 'instance creation' stamp: 'len 12/14/2002 11:39'! host: addressOrHostName port: portNumber ^ self new host: addressOrHostName port: portNumber! ! NetworkError subclass: #ConnectionTimedOut instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Kernel'! !ConnectionTimedOut commentStamp: 'mir 5/12/2003 18:14' prior: 0! Signals that a connection attempt timed out. ! InstructionStream subclass: #ContextPart instanceVariableNames: 'stackp' classVariableNames: 'PrimitiveFailToken QuickStep' 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' stamp: 'md 2/21/2006 13:52'! methodNode self method isBlockMethod ifTrue: [^ self method blockNode]. ^ self method methodNode.! ! !ContextPart methodsFor: 'accessing' stamp: 'md 2/17/2006 09:50'! methodNodeFormattedAndDecorated: decorate "Answer a method node made from pretty-printed (and colorized, if decorate is true) source text." ^ self method methodNodeFormattedAndDecorated: decorate.! ! !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: '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: 'ajh 3/25/2004 00:07'! jump "Abandon thisContext and resume self instead (using the same current process). You may want to save thisContext's sender before calling this so you can jump back to it. Self MUST BE a top context (ie. a suspended context or a abandoned context that was jumped out of). A top context already has its return value on its stack (see Interpreter>>primitiveSuspend and other suspending primitives). thisContext's sender is converted to a top context (by pushing a nil return value on its stack) so it can be jump back to." | top | "Make abandoned context a top context (has return value (nil)) so it can be jumped back to" thisContext sender push: nil. "Pop self return value then return it to self (since we jump to self by returning to it)" stackp = 0 ifTrue: [self stepToSendOrReturn]. stackp = 0 ifTrue: [self push: nil]. "must be quick return self/constant" top _ self pop. thisContext privSender: self. ^ top! ! !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' stamp: 'hmm 7/17/2001 20:57'! quickSend: selector to: receiver with: arguments super: superFlag "Send the given selector with arguments in an environment which closely resembles the non-simulating environment, with an interjected unwind-protected block to catch nonlocal returns. Attention: don't get lost!!" | oldSender contextToReturnTo result lookupClass | contextToReturnTo _ self. lookupClass _ superFlag ifTrue: [(self method literalAt: self method numLiterals) value superclass] ifFalse: [receiver class]. [oldSender _ thisContext sender swapSender: self. result _ receiver perform: selector withArguments: arguments inSuperclass: lookupClass. thisContext sender swapSender: oldSender] ifCurtailed: [ contextToReturnTo _ thisContext sender receiver. "The block context returning nonlocally" contextToReturnTo jump: -1. "skip to front of return bytecode causing this unwind" contextToReturnTo nextByte = 16r7C ifTrue: [ "If it was a returnTop, push the value to be returned. Otherwise the value is implicit in the bytecode" contextToReturnTo push: (thisContext sender tempAt: 1)]. thisContext swapSender: thisContext home sender. "Make this block return to the method's sender" contextToReturnTo]. contextToReturnTo push: result. ^contextToReturnTo! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 5/20/2004 17:32'! restart "Unwind thisContext to self and resume from beginning. Execute unwind blocks when unwinding. ASSUMES self is a sender of thisContext" | ctxt unwindBlock | self isDead ifTrue: [self cannotReturn: nil to: self]. self privRefresh. ctxt _ thisContext. [ ctxt _ ctxt findNextUnwindContextUpTo: self. ctxt isNil ] whileFalse: [ unwindBlock _ ctxt tempAt: 1. unwindBlock ifNotNil: [ ctxt tempAt: 1 put: nil. thisContext terminateTo: ctxt. unwindBlock value]. ]. thisContext terminateTo: self. self jump. ! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 6/27/2003 22:17'! resume "Roll back thisContext to self and resume. Execute unwind blocks when rolling back. ASSUMES self is a sender of thisContext" self resume: nil! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 5/20/2004 17:32'! resume: value "Unwind thisContext to self and resume with value as result of last send. Execute unwind blocks when unwinding. ASSUMES self is a sender of thisContext" | ctxt unwindBlock | self isDead ifTrue: [self cannotReturn: value to: self]. ctxt _ thisContext. [ ctxt _ ctxt findNextUnwindContextUpTo: self. ctxt isNil ] whileFalse: [ unwindBlock _ ctxt tempAt: 1. unwindBlock ifNotNil: [ ctxt tempAt: 1 put: nil. thisContext terminateTo: ctxt. unwindBlock value]. ]. thisContext terminateTo: self. ^ value ! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 1/21/2003 19:27'! return "Unwind until my sender is on top" self return: self receiver! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 5/20/2004 17:27'! return: value "Unwind thisContext to self and return value to self's sender. Execute any unwind blocks while unwinding. ASSUMES self is a sender of thisContext" sender ifNil: [self cannotReturn: value to: sender]. sender resume: value! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 1/24/2003 15:30'! return: value to: sendr "Simulate the return of value to sendr." self releaseTo: sendr. sendr ifNil: [^ nil]. ^ sendr push: value! ! !ContextPart methodsFor: 'controlling' stamp: 'ajh 5/20/2004 17:20'! runUntilErrorOrReturnFrom: aSender "ASSUMES aSender is a sender of self. Execute self's stack until aSender returns or an unhandled exception is raised. Return a pair containing the new top context and a possibly nil exception. The exception is not nil if it was raised before aSender returned and it was not handled. The exception is returned rather than openning the debugger, giving the caller the choice of how to handle it." "Self is run by jumping directly to it (the active process abandons thisContext and executes self). However, before jumping to self we insert an ensure block under aSender that jumps back to thisContext when evaluated. We also insert an exception handler under aSender that jumps back to thisContext when an unhandled exception is raised. In either case, the inserted ensure and exception handler are removed once control jumps back to thisContext." | error ctxt here topContext | here _ thisContext. "Insert ensure and exception handler contexts under aSender" error _ nil. ctxt _ aSender insertSender: (ContextPart contextOn: UnhandledError do: [:ex | error ifNil: [ error _ ex exception. topContext _ thisContext. ex resumeUnchecked: here jump] ifNotNil: [ex pass] ]). ctxt _ ctxt insertSender: (ContextPart contextEnsure: [error ifNil: [ topContext _ thisContext. here jump] ]). self jump. "Control jumps to self" "Control resumes here once above ensure block or exception handler is executed" ^ error ifNil: [ "No error was raised, remove ensure context by stepping until popped" [ctxt isDead] whileFalse: [topContext _ topContext stepToCallee]. {topContext. nil} ] ifNotNil: [ "Error was raised, remove inserted above contexts then return signaler context" aSender terminateTo: ctxt sender. "remove above ensure and handler contexts" {topContext. error} ]. ! ! !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: 'ajh 1/24/2003 00:56'! terminate "Make myself unresumable." sender _ nil. pc _ nil. ! ! !ContextPart methodsFor: 'controlling' stamp: 'ar 3/6/2001 14:26'! 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: 'debugger access' stamp: 'ajh 9/25/2001 00:12'! contextStack "Answer an Array of the contexts on the receiver's sender chain." ^self stackOfSize: 100000! ! !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: 'nk 7/29/2004 10:09'! errorReportOn: strm "Write a detailed error report on the stack (above me) on a stream. For both the error file, and emailing a bug report. Suppress any errors while getting printStrings. Limit the length." | cnt aContext startPos | strm print: Date today; space; print: Time now; cr. strm cr. strm nextPutAll: 'VM: '; nextPutAll: SmalltalkImage current platformName asString; nextPutAll: ' - '; nextPutAll: SmalltalkImage current asString; cr. strm nextPutAll: 'Image: '; nextPutAll: SystemVersion current version asString; nextPutAll: ' ['; nextPutAll: SmalltalkImage current lastUpdateString asString; nextPutAll: ']'; cr. strm cr. SecurityManager default printStateOn: strm. "Note: The following is an open-coded version of ContextPart>>stackOfSize: since this method may be called during a low space condition and we might run out of space for allocating the full stack." cnt _ 0. startPos _ strm position. aContext _ self. [aContext notNil and: [(cnt _ cnt + 1) < 5]] whileTrue: [aContext printDetails: strm. "variable values" strm cr. aContext _ aContext sender]. strm cr; nextPutAll: '--- The full stack ---'; cr. aContext _ self. cnt _ 0. [aContext == nil] whileFalse: [cnt _ cnt + 1. cnt = 5 ifTrue: [strm nextPutAll: ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'; cr]. strm print: aContext; cr. "just class>>selector" strm position > (startPos+4000) ifTrue: [strm nextPutAll: '...etc...'. ^ self]. "exit early" cnt > 60 ifTrue: [strm nextPutAll: '-- and more not shown --'. ^ self]. aContext _ aContext sender]. ! ! !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: 'md 2/17/2006 18:41'! methodClass "Answer the class in which the receiver's method was found." ^self method methodClass ifNil:[self receiver class].! ! !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' stamp: 'md 2/17/2006 18:47'! selector "Answer the selector of the method that created the receiver." ^self method selector ifNil: [self method defaultSelector].! ! !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' stamp: 'ajh 1/24/2003 00:03'! 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. pc _ nil. ! ! !ContextPart methodsFor: 'debugger access' stamp: 'md 2/22/2006 16:58'! sourceCode ^self method getSource. "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: 'md 2/20/2006 20:54'! tempNames "Answer an OrderedCollection of the names of the receiver's temporary variables, which are strings." "unused temps at end are not included in method. On the other hand, extra temps such as to:do: loop limit are added to method" | tempNames n | tempNames _ self methodNode tempNames. n _ self method numTemps. tempNames size = n ifTrue: [^ tempNames]. tempNames size > n ifTrue: [^ tempNames first: n]. tempNames size + 1 to: n do: [:i | tempNames _ tempNames copyWith: 't' , i printString]. ^ tempNames! ! !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: 'debugger access' stamp: 'tk 10/19/2001 10:20'! tempsAndValuesLimitedTo: sizeLimit indent: indent "Return a string of the temporary variabls and their current values" | aStream | aStream _ WriteStream on: (String new: 100). self tempNames doWithIndex: [:title :index | indent timesRepeat: [aStream tab]. aStream nextPutAll: title; nextPut: $:; space; tab. aStream nextPutAll: ((self tempAt: index) printStringLimitedTo: (sizeLimit -3 -title size max: 1)). aStream cr]. ^aStream contents! ! !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' stamp: 'ajh 7/6/2003 20:38'! 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." | bool | bool _ self pop. (bool == true or: [bool == false]) ifFalse: [ ^self send: #mustBeBooleanIn: to: bool with: {self} super: false]. (bool eqv: condition) ifTrue: [self jump: distance]! ! !ContextPart methodsFor: 'instruction decoding' stamp: 'ajh 1/24/2003 16:35'! 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 from: self home! ! !ContextPart methodsFor: 'instruction decoding' stamp: 'ajh 1/24/2003 16:34'! methodReturnReceiver "Simulate the action of a 'return receiver' bytecode. This corresponds to the source expression '^self'." ^ self return: self receiver from: self home! ! !ContextPart methodsFor: 'instruction decoding' stamp: 'ajh 1/24/2003 16:34'! methodReturnTop "Simulate the action of a 'return top of stack' bytecode. This corresponds to source expressions like '^something'." ^ self return: self pop from: self home! ! !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: 'ajh 3/5/2004 03:44'! return: value from: aSender "For simulation. Roll back self to aSender and return value from it. Execute any unwind blocks on the way. ASSUMES aSender is a sender of self" | newTop ctxt | aSender isDead ifTrue: [ ^ self send: #cannotReturn: to: self with: {value} super: false]. newTop _ aSender sender. ctxt _ self findNextUnwindContextUpTo: newTop. ctxt ifNotNil: [ ^ self send: #aboutToReturn:through: to: self with: {value. ctxt} super: false]. self releaseTo: newTop. newTop ifNotNil: [newTop push: value]. ^ newTop ! ! !ContextPart methodsFor: 'instruction decoding' stamp: 'hmm 7/17/2001 20:52'! 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 == #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]. QuickStep == self ifTrue: [ QuickStep _ nil. ^self quickSend: selector to: receiver with: arguments super: superFlag]. ^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: '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: 'printing' stamp: 'tk 10/19/2001 11:24'! printDetails: strm "Put my class>>selector and arguments and temporaries on the stream. Protect against errors during printing." | str | self printOn: strm. strm cr; tab; nextPutAll: 'Arguments and temporary variables: '; cr. str _ [self tempsAndValuesLimitedTo: 80 indent: 2] ifError: [:err :rcvr | '<>']. strm nextPutAll: str. strm peekLast == Character cr ifFalse: [strm cr].! ! !ContextPart methodsFor: 'printing' stamp: 'md 2/17/2006 15:41'! printOn: aStream | selector class mclass | self method == nil ifTrue: [^ super printOn: aStream]. class := self receiver class. mclass := self methodClass. selector := self selector ifNil:[self method defaultSelector]. aStream nextPutAll: class name. mclass == class ifFalse: [aStream nextPut: $(. aStream nextPutAll: mclass name. aStream nextPut: $)]. aStream nextPutAll: '>>'. aStream nextPutAll: selector. selector = #doesNotUnderstand: ifTrue: [ aStream space. (self tempAt: 1) selector printOn: aStream. ]. ! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/24/2003 12:35'! blockHome ^ self! ! !ContextPart methodsFor: 'query' stamp: 'ajh 7/21/2003 09:59'! bottomContext "Return the last context (the first context invoked) in my sender chain" ^ self findContextSuchThat: [:c | c sender isNil]! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/27/2003 18:35'! copyStack ^ self copyTo: nil! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/27/2003 21:20'! copyTo: aContext "Copy self and my sender chain down to, but not including, aContext. End of copied chain will have nil sender. BlockContexts whose home is also copied will point to the copy. However, blockContexts that are not on the stack but may be later will not have their home pointing in the new copied thread. So an error will be raised if one of these tries to return directly to its home. It is best to use BlockClosures instead. They only hold a ContextTag, which will work for all copies of the original home context." ^ self copyTo: aContext blocks: IdentityDictionary new! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/24/2003 00:12'! findContextSuchThat: testBlock "Search self and my sender chain for first one that satisfies testBlock. Return nil if none satisfy" | ctxt | ctxt _ self. [ctxt isNil] whileFalse: [ (testBlock value: ctxt) ifTrue: [^ ctxt]. ctxt _ ctxt sender. ]. ^ nil! ! !ContextPart methodsFor: 'query' stamp: 'md 1/20/2006 16:15'! findSecondToOldestSimilarSender "Search the stack for the second-to-oldest occurance of self's method. 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." | sec ctxt bot | sec _ self. ctxt _ self. [ bot _ ctxt findSimilarSender. bot isNil ] whileFalse: [ sec _ ctxt. ctxt _ bot. ]. ^ sec ! ! !ContextPart methodsFor: 'query' stamp: 'md 1/20/2006 16:14'! findSimilarSender "Return the closest sender with the same method, return nil if none found" | meth | meth _ self method. ^ self sender findContextSuchThat: [:c | c method == meth]! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/24/2003 19:42'! hasContext: aContext "Answer whether aContext is me or one of my senders" ^ (self findContextSuchThat: [:c | c == aContext]) notNil! ! !ContextPart methodsFor: 'query' stamp: 'md 1/20/2006 16:14'! isClosureContext ^ false! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/24/2003 00:04'! isDead "Has self finished" ^ pc isNil! ! !ContextPart methodsFor: 'query' stamp: 'ajh 1/24/2003 22:28'! secondFromBottom "Return the second from bottom of my sender chain" self sender ifNil: [^ nil]. ^ self findContextSuchThat: [:c | c sender sender isNil]! ! !ContextPart methodsFor: 'system simulation' stamp: 'hmm 7/30/2001 20:43'! completeCallee: aContext "Simulate the execution of bytecodes until a return to the receiver." | ctxt current ctxt1 | ctxt _ aContext. [ctxt == current or: [ctxt hasSender: self]] whileTrue: [current _ ctxt. ctxt1 _ ctxt quickStep. ctxt1 ifNil: [self halt]. ctxt _ ctxt1]. ^self stepToSendOrReturn! ! !ContextPart methodsFor: 'system simulation' stamp: 'hmm 7/15/2001 20:58'! quickStep "If the next instruction is a send, just perform it. Otherwise, do a normal step." self willReallySend ifTrue: [QuickStep _ self]. ^self step! ! !ContextPart methodsFor: 'system simulation' stamp: 'ajh 4/15/2003 10:01'! 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 isBlock and: [aBlock hasMethodReturn]) ifTrue: [self error: 'simulation of blocks with ^ can run loose']. current _ aBlock asContext. current privSender: 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: 'ajh 1/24/2003 22:54'! stepToCallee "Step to callee or sender" | ctxt | ctxt _ self. [(ctxt _ ctxt step) == self] whileTrue. ^ ctxt! ! !ContextPart methodsFor: 'system simulation' stamp: 'hmm 7/30/2001 20:48'! stepToSendOrReturn "Simulate the execution of bytecodes until either sending a message or returning a value to the receiver (that is, until switching contexts)." | ctxt | [self willReallySend | self willReturn | self willStore] whileFalse: [ ctxt _ self step. ctxt == self ifFalse: [self halt. "Caused by mustBeBoolean handling" ^ctxt]]! ! !ContextPart methodsFor: 'private' stamp: 'ajh 5/20/2004 16:27'! activateReturn: aContext value: value "Activate 'aContext return: value' in place of self, so execution will return to aContext's sender" ^ self activateMethod: ContextPart theReturnMethod withArgs: {value} receiver: aContext class: aContext class! ! !ContextPart methodsFor: 'private' stamp: 'ajh 6/29/2003 15:32'! cannotReturn: result to: homeContext "The receiver tried to return result to homeContext that no longer exists." ^ BlockCannotReturn new result: result; deadHome: homeContext; signal! ! !ContextPart methodsFor: 'private' stamp: 'ajh 1/27/2003 21:18'! copyTo: aContext blocks: dict "Copy self and my sender chain down to, but not including, aContext. End of copied chain will have nil sender. BlockContexts whose home is also copied will point to the copy. However, blockContexts that are not on the stack but may be later will not have their home pointing in the new copied thread. So an error will be raised if one of these tries to return directly to its home." | copy | self == aContext ifTrue: [^ nil]. copy _ self copy. dict at: self ifPresent: [:blocks | blocks do: [:b | b privHome: copy]]. self sender ifNotNil: [ copy privSender: (self sender copyTo: aContext blocks: dict)]. ^ copy! ! !ContextPart methodsFor: 'private' stamp: 'ajh 1/24/2003 00:50'! cut: aContext "Cut aContext and its senders from my sender chain" | ctxt callee | ctxt _ self. [ctxt == aContext] whileFalse: [ callee _ ctxt. ctxt _ ctxt sender. ctxt ifNil: [aContext ifNotNil: [self error: 'aContext not a sender']]. ]. callee privSender: nil. ! ! !ContextPart methodsFor: 'private' stamp: 'md 2/20/2006 20:59'! 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:[ ToolSet debugContext: self label:'Code simulation error' contents: nil]. (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]. primitiveIndex = 186 ifTrue: [ "closure value" | m | m _ receiver method. arguments size = m numArgs ifFalse: [^ PrimitiveFailToken]. ^ self activateMethod: m withArgs: arguments receiver: receiver class: receiver class]. primitiveIndex = 187 ifTrue: [ "closure valueWithArguments:" | m args | m _ receiver method. args _ arguments first. args size = m numArgs ifFalse: [^ PrimitiveFailToken]. ^ self activateMethod: m withArgs: args receiver: receiver class: receiver class]. primitiveIndex = 188 ifTrue: [ "object withArgs:executeMethod:" | m args | args _ arguments first. m _ arguments second. args size = m numArgs ifFalse: [^ PrimitiveFailToken]. ^ self activateMethod: m withArgs: args receiver: receiver class: receiver class]. 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: 'ajh 7/21/2003 09:59'! insertSender: aContext "Insert aContext and its sender chain between me and my sender. Return new callee of my original sender." | ctxt | ctxt _ aContext bottomContext. ctxt privSender: self sender. self privSender: aContext. ^ ctxt! ! !ContextPart methodsFor: 'private' stamp: 'ajh 1/23/2003 22:35'! privSender: aContext sender _ aContext! ! !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-debugger' stamp: 'tfei 3/19/2000 23:24'! cachesStack ^false! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'ajh 2/1/2003 01:30'! canHandleSignal: exception "Sent to handler (on:do:) contexts only. If my exception class (first arg) handles exception then return true, otherwise forward this message to the next handler context. If none left, return false (see nil>>canHandleSignal:)" ^ (((self tempAt: 1) handles: exception) and: [self tempAt: 3]) or: [self nextHandlerContext canHandleSignal: exception]. ! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'TPR 8/28/2000 19:27'! findNextHandlerContextStarting "Return the next handler marked context, returning nil if there is none. Search starts with self and proceeds up to nil." | ctx | ctx _ self. [ctx isHandlerContext ifTrue:[^ctx]. (ctx _ ctx sender) == nil ] whileFalse. ^nil! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'TPR 8/23/2000 16:37'! findNextUnwindContextUpTo: aContext "Return the next unwind marked above the receiver, returning nil if there is none. Search proceeds up to but not including aContext." | ctx | ctx _ self. [(ctx _ ctx sender) == nil or: [ctx == aContext]] whileFalse: [ ctx isUnwindContext ifTrue: [^ctx]]. ^nil! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'ajh 6/27/2003 20:47'! handleSignal: exception "Sent to handler (on:do:) contexts only. If my exception class (first arg) handles exception then execute my handle block (second arg), otherwise forward this message to the next handler context. If none left, execute exception's defaultAction (see nil>>handleSignal:)." | val | (((self tempAt: 1) handles: exception) and: [self tempAt: 3]) ifFalse: [ ^ self nextHandlerContext handleSignal: exception]. exception privHandlerContext: self contextTag. self tempAt: 3 put: false. "disable self while executing handle block" val _ [(self tempAt: 2) valueWithPossibleArgs: {exception}] ensure: [self tempAt: 3 put: true]. self return: val. "return from self if not otherwise directed in handle block" ! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'tpr 2/24/2001 21:29'! isHandlerContext ^false! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'TPR 8/28/2000 15:45'! isUnwindContext ^false! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'ajh 2/1/2003 00:20'! nextHandlerContext ^ self sender findNextHandlerContextStarting! ! !ContextPart methodsFor: 'private-exceptions' stamp: 'ajh 1/21/2003 17:59'! unwindTo: aContext | ctx unwindBlock | ctx := self. [(ctx _ ctx findNextUnwindContextUpTo: aContext) isNil] whileFalse: [ unwindBlock := ctx tempAt: 1. unwindBlock == nil ifFalse: [ ctx tempAt: 1 put: nil. unwindBlock value] ]. ! ! !ContextPart methodsFor: '*39Deprecated' stamp: 'md 2/17/2006 09:35'! mclass "Answer the class in which the receiver's method was found." self deprecated: 'use #methodClass'. ^ self methodClass! ! !ContextPart methodsFor: '*39Deprecated' stamp: 'md 2/17/2006 12:04'! methodSelector "Answer the selector of the method that created the receiver." self deprecated: 'use #selector'. ^self selector.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '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: '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: 'special context creation' stamp: 'ajh 1/24/2003 14:31'! contextEnsure: block "Create an #ensure: context that is ready to return from executing its receiver" | ctxt chain | ctxt _ thisContext. [chain _ thisContext sender cut: ctxt. ctxt jump] ensure: block. "jump above will resume here without unwinding chain" ^ chain! ! !ContextPart class methodsFor: 'special context creation' stamp: 'ajh 1/24/2003 14:31'! contextOn: exceptionClass do: block "Create an #on:do: context that is ready to return from executing its receiver" | ctxt chain | ctxt _ thisContext. [chain _ thisContext sender cut: ctxt. ctxt jump] on: exceptionClass do: block. "jump above will resume here without unwinding chain" ^ chain! ! !ContextPart class methodsFor: 'special context creation' stamp: 'ajh 5/20/2004 16:25'! theReturnMethod | meth | meth _ self lookupSelector: #return:. meth primitive = 0 ifFalse: [^ self error: 'expected #return: to not be a primitive']. ^ meth! ! !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' stamp: 'md 2/20/2006 20:33'! fieldList "Refer to the comment in Inspector|fieldList." | fields | object == nil ifTrue: [^Array with: 'thisContext']. fields _ (Array with: 'thisContext' with: 'all temp vars') , object tempNames. object myEnv ifNotNil: [ fields _ fields, object capturedTempNames]. ^ fields! ! !ContextVariablesInspector methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:27'! inspect: anObject "Initialize the receiver so that it is inspecting anObject. There is no current selection. Because no object's inspectorClass method answers this class, it is OK for this method to override Inspector >> inspect: " object := anObject. self initialize ! ! !ContextVariablesInspector methodsFor: 'code'! doItContext ^object! ! !ContextVariablesInspector methodsFor: 'code'! doItReceiver ^object receiver! ! !ContextVariablesInspector methodsFor: 'selecting' stamp: 'md 2/20/2006 20:42'! replaceSelectionValue: anObject "Refer to the comment in Inspector|replaceSelectionValue:." | numTemps | selectionIndex <= 2 ifTrue: [^ self]. numTemps _ object method numTemps. selectionIndex - 2 <= object method numTemps ifTrue: [ ^ object tempAt: selectionIndex - 2 put: anObject]. ^ object myEnv at: selectionIndex - 2 - numTemps put: anObject! ! !ContextVariablesInspector methodsFor: 'selecting' stamp: 'md 2/20/2006 20:43'! selection "Refer to the comment in Inspector|selection." | numTemps | selectionIndex = 0 ifTrue:[^'']. selectionIndex = 1 ifTrue: [^object]. selectionIndex = 2 ifTrue: [^ object tempsAndValues]. numTemps _ object method numTemps. selectionIndex - 2 <= object method numTemps ifTrue: [ ^ object tempAt: selectionIndex - 2]. ^ object myEnv at: selectionIndex - 2 - numTemps! ! 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: 'stephaneducasse 2/4/2006 20:41'! channel: midiChannel channel := midiChannel. ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'jm 9/10/1998 07:49'! control ^ control ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! control: midiControl control := midiControl. ! ! !ControlChangeEvent methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'ST80-Kernel-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: '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 5/4/2001 23:20'! controllersSatisfying: aBlock "Return a list of scheduled controllers satisfying aBlock" ^ (scheduledControllers ifNil: [^ #()]) select: [:aController | (aBlock value: aController) == true]! ! !ControlManager methodsFor: 'accessing'! includes: aController ^ scheduledControllers includes: aController! ! !ControlManager methodsFor: 'accessing'! noteNewTop newTopClicked _ true! ! !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: '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: '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: '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: 'rbb 2/18/2005 10:50'! 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 _ (UIManager default chooseFrom: (labels findTokens: Character cr) asArray). 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: 'dtl 4/4/2005 06:42'! interruptName: labelString "Create a Notifier on the active scheduling process with the given label. Make the Notifier the active controller." ^ self interruptName: labelString preemptedProcess: nil ! ! !ControlManager methodsFor: 'scheduling' stamp: 'dtl 4/6/2005 23:20'! interruptName: labelString preemptedProcess: theInterruptedProcess "Create a Notifier on the active scheduling process with the given label. Make the Notifier the active controller." | suspendingList newActiveController preemptedProcess | preemptedProcess _ theInterruptedProcess ifNil: [Processor preemptedProcess]. preemptedProcess == activeControllerProcess ifFalse: [(suspendingList _ preemptedProcess suspendingList) == nil ifTrue: [preemptedProcess suspend] ifFalse: [suspendingList remove: preemptedProcess. preemptedProcess offList]]. (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: preemptedProcess) 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' stamp: 'ajh 12/31/2001 15:15'! spawnNewProcess self activeController: self screenController! ! !ControlManager methodsFor: 'scheduling'! unschedule: aController "Remove the view, aController, from the collection of scheduled controllers." scheduledControllers remove: aController ifAbsent: []! ! !ControlManager methodsFor: 'scheduling' stamp: 'rbb 2/18/2005 10:52'! 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 _ (UIManager default chooseFrom: (labels findTokens: Character cr) asArray). ^ index > 0 ifTrue: [controllers at: index] ifFalse: [nil]! ! !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: '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: 'ST80-Kernel-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: '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 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: '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: 'view access' stamp: 'apb 7/14/2004 12:50'! inspectView view notNil ifTrue: [^ view inspect; yourself]! ! !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 class instanceVariableNames: ''! !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.! ! AbstractResizerMorph subclass: #CornerGripMorph instanceVariableNames: 'target' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Windows'! !CornerGripMorph commentStamp: 'jmv 1/29/2006 17:15' prior: 0! I am the superclass of a hierarchy of morph specialized in allowing the user to resize windows.! !CornerGripMorph methodsFor: 'as yet unclassified' stamp: 'jmv 2/19/2006 14:30'! handlesMouseDown: anEvent ^ true! ! !CornerGripMorph methodsFor: 'as yet unclassified' stamp: 'jmv 2/19/2006 14:30'! handlesMouseOver: anEvent ^true! ! !CornerGripMorph methodsFor: 'as yet unclassified' stamp: 'jmv 2/2/2006 14:24'! initialize super initialize. self extent: self defaultWidth+2 @ (self defaultHeight+2). self layoutFrame: self gripLayoutFrame! ! !CornerGripMorph methodsFor: 'as yet unclassified' stamp: 'md 2/27/2006 22:01'! mouseMove: anEvent | delta | target ifNil: [^ self]. target fastFramingOn ifTrue: [delta := target doFastWindowReframe: self ptName] ifFalse: [ delta := anEvent cursorPoint - lastMouse. lastMouse := anEvent cursorPoint. self apply: delta. self bounds: (self bounds origin + delta extent: self bounds extent)].! ! !CornerGripMorph methodsFor: 'as yet unclassified' stamp: 'bvs 3/24/2004 14:38'! target: aMorph target _ aMorph! ! !CornerGripMorph methodsFor: 'accessing' stamp: 'jmv 2/2/2006 14:24'! defaultHeight ^ 22! ! !CornerGripMorph methodsFor: 'accessing' stamp: 'jmv 2/2/2006 14:24'! defaultWidth ^ 22! ! 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 1/5/2002 17:26'! saveBitsUnderCornersOf: aMorph on: aCanvas in: bounds corners: cornerList | offset corner mask form corners rect | underBits _ Array new: 4. corners _ bounds corners. cornerList do:[:i| mask _ cornerMasks at: i. corner _ 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]. rect _ corner + offset extent: mask extent. (aCanvas isVisible: rect) ifTrue:[ form _ aCanvas contentsOfArea: rect. form copyBits: form boundingBox from: mask at: 0@0 clippingBox: form boundingBox rule: Form and fillColor: nil map: (Bitmap with: 16rFFFFFFFF with: 0). underBits at: i put: form]]. ! ! !CornerRounder methodsFor: 'all' stamp: 'kfr 8/4/2003 23:28'! tweakCornersOf: aMorph on: aCanvas in: bounds borderWidth: w corners: cornerList "This variant has a cornerList argument, to allow some corners to be rounded and others not" | offset corner saveBits fourColors mask outBits shadowColor corners | shadowColor _ aCanvas shadowColor. aCanvas shadowColor: nil. "for tweaking it's essential" w > 0 ifTrue:[ fourColors _ shadowColor ifNil:[aMorph borderStyle colorsAtCorners] ifNotNil:[Array new: 4 withAll: Color transparent]]. mask _ Form extent: cornerMasks first extent depth: aCanvas depth. corners _ bounds corners. cornerList do:[:i| corner _ corners at: i. saveBits _ underBits at: i. saveBits ifNotNil:[ 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:[ 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: 'ar 1/5/2002 17:24'! roundCornersOf: aMorph on: aCanvas in: bounds 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 in: bounds corners: aList. displayBlock value. rounder tweakCornersOf: aMorph on: aCanvas in: bounds borderWidth: w corners: aList! ! Object subclass: #CosineInterpolator instanceVariableNames: 'origin points stack' classVariableNames: '' poolDictionaries: '' category: 'Speech-Support'! !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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'initialization' stamp: 'stephaneducasse 2/3/2006 22:22'! initialize points := SortedCollection new. stack := SortedCollection new. origin := 0! ! !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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! points: aCollection points := aCollection! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CosineInterpolator class instanceVariableNames: ''! !CosineInterpolator class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/3/2006 22:23'! 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! ! StandardFileStream subclass: #CrLfFileStream instanceVariableNames: 'lineEndConvention' classVariableNames: 'Cr CrLf Lf LineEndDefault LineEndStrings LookAheadCount' poolDictionaries: '' category: 'Files-Kernel'! !CrLfFileStream commentStamp: 'ls 11/10/2002 13:32' prior: 0! I am the same as a regular file stream, except that when I am in text mode, I will automatically convert line endings between the underlying platform's convention, and Squeak's convention of carriage-return only. The goal is that Squeak text files can be treated as OS text files, and vice versa. In binary mode, I behave identically to a StandardFileStream. To enable CrLfFileStream as the default file stream class for an entire image, modify FileStream class concreteStream . There are two caveats on programming with CrLfFileStream. First, the choice of text mode versus binary mode affects which characters are visible in Squeak, and no longer just affects whether those characters are returned as Character's or as Integer's. Thus the choice of mode needs to be made very carefully, and must be based on intent instead of convenience of representation. The methods asString, asByteArray, asCharacter, and asInteger can be used to convert between character and integer representations. (Arguably, file streams should accept either strings or characters in nextPut: and nextPutAll:, but that is not the case right now.) Second, arithmetic on positions no longer works, because one character that Squeak sees (carriage return) could map to two characters in the underlying file (carriage return plus line feed, on MS Windows and MS DOS). Comparison between positions still works. (This caveat could perhaps be fixed by maintaining a map between Squeak positions and positions in the underlying file, but it is complicated. Consider, for example, updates to the middle of the file. Also, consider that text files are rarely updated in the middle of the file, and that general random access to a text file is rarely very useful. If general random access with specific file counts is desired, then the file is starting to sound like a binary file instead of a text file.) ! ]style[(448 31 1371 6 32)f1,f1LFileStream class concreteStream;,f1,f1i,f1! !CrLfFileStream methodsFor: 'access' stamp: 'ar 1/20/98 16:16'! ascii super ascii. self detectLineEndConvention! ! !CrLfFileStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! binary super binary. lineEndConvention := nil! ! !CrLfFileStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'nk 9/5/2004 12:58'! lineEndConvention ^lineEndConvention! ! !CrLfFileStream methodsFor: 'access' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'open/close' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: '*monticello' stamp: 'stephaneducasse 2/4/2006 20:47'! lineEndingConvention: aSymbol lineEndConvention := aSymbol! ! !CrLfFileStream methodsFor: 'private' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'yo 2/21/2004 04:46'! new ^ (MultiByteFileStream new) wantsLineEndConversion: true; yourself. ! ! !CrLfFileStream class methodsFor: 'class initialization' stamp: 'djp 1/28/1999 22:08'! startUp self guessDefaultLineEndConvention! ! Array variableSubclass: #Cubic instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Collections-Arrayed'! !Cubic commentStamp: 'wiz 6/17/2004 20:31' prior: 0! I am a segment between to points. In the form of a cubic polynomial that can be evaluated between 0..1 to obtain the end points and intermediate values. ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 6/17/2004 22:32'! bestSegments "Return the smallest integer number of segments that give the best curve." ^ self honeIn: self calcEnoughSegments! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 6/18/2004 23:12'! calcEnoughSegments "Find the power of two that represents a sufficient number of segments for this cubic. The measure is the sum of distances for the segments. We want this to be close enough not affect the straightness of the drawn lines. Which means within one pixel." "^ self enough: 2 withMeasure: (self measureFor: 1) withIn: self leeway This ran into a problem when the curve was an s-curve with inflections. Besides honeIn will check to see if 1 is better than two so we lose nothing by starting a little higher." ^ self enough: 4 withMeasure: (self measureFor: 2) withIn: self leeway! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 7/18/2004 22:50'! enough: nTry withMeasure: lastMeasure withIn: closeEnough "See comment in calcEnoughSegments for which I am a helper" | measure | measure := self measureFor: nTry. measure > (lastMeasure + closeEnough) ifFalse: [^ nTry // 2]. ^ self enough: 2 * nTry withMeasure: measure withIn: closeEnough! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 6/17/2004 23:51'! honeIn: enough "Find if there is a smaller n than enough that give the same measure for n." self assert: [enough isPowerOfTwo]. enough < 2 ifTrue: [^ enough]. ^ self honeIn: enough step: enough // 2 measure: (self measureFor: enough) withIn: self leeway! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 6/17/2004 23:45'! honeIn: centerN step: step measure: measure withIn: closeEnough "Pick the best n by binary search." | nTry | step < 1 ifTrue: [^ centerN]. nTry := centerN - step. ^ measure > (closeEnough + (self measureFor: nTry)) ifTrue: [self honeIn: centerN step: step // 2 measure: measure withIn: closeEnough] ifFalse: [self honeIn: nTry step: step // 2 measure: measure withIn: closeEnough]! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 6/19/2004 00:00'! leeway "How close can measure be" ^ 0.1! ! !Cubic methodsFor: 'cubic support' stamp: 'wiz 1/30/2005 20:59'! measureFor: n "Return a distance measure for cubic curve with n segments. For convienence and accuracy we use the sum of the distances. " "first point is poly of 0." | p1 p2 measure | p1 := self first. measure := 0. (1 to: n) do: [:i | p2 := self polynomialEval: i / n asFloat. measure := measure + (p2 dist: p1). p1 := p2]. ^ measure! ! Object subclass: #CurrentProjectRefactoring instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: '39Deprecated'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CurrentProjectRefactoring class instanceVariableNames: ''! !CurrentProjectRefactoring class methodsFor: 'flaps' stamp: 'dao 9/14/2005 10:46'! isFlapEnabled: aFlapTab "Answer whether the given flap tab is enabled in the current project" self deprecated: 'CurrentProjectRefactoring is deprecated'. ^ self xxxCurrent isFlapEnabled: aFlapTab! ! !CurrentProjectRefactoring class methodsFor: 'flaps' stamp: 'dao 9/14/2005 10:46'! showSharedFlaps "Answer whether shared flaps are currently showing (true) or suppressed (false). The CurrentProjectRefactoring circumlocution is in service of making it possible for shared flaps to appear on the edges of an interior subworld, I believe." self deprecated: 'CurrentProjectRefactoring is deprecated'. ^ self xxxCurrent showSharedFlaps! ! !CurrentProjectRefactoring class methodsFor: 'flaps' stamp: 'dao 9/14/2005 10:46'! suppressFlapsString "Answer a string characterizing whether flaps are suppressed at the moment or not" self deprecated: 'CurrentProjectRefactoring is deprecated'. ^ (self currentFlapsSuppressed ifTrue: [''] ifFalse: ['']), 'show shared tabs (F)' translated! ! !CurrentProjectRefactoring class methodsFor: 'miscellaneous' stamp: 'dao 9/14/2005 10:46'! exitCurrentProject " CurrentProjectRefactoring exitCurrentProject " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent exit ! ! !CurrentProjectRefactoring class methodsFor: 'miscellaneous' stamp: 'dao 9/14/2005 10:46'! newProcessIfUI: aDeadOrDyingProcess " CurrentProjectRefactoring newProcessIfUI: used ONLY for Morphic " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^Project spawnNewProcessIfThisIsUI: aDeadOrDyingProcess! ! !CurrentProjectRefactoring class methodsFor: 'miscellaneous' stamp: 'dao 9/14/2005 10:46'! projectWithNameOrCurrent: aString " CurrentProjectRefactoring projectWithNameOrCurrent: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^(Project named: aString) ifNil: [self xxxCurrent]! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:44'! currentAddGuard: anObject " CurrentProjectRefactoring currentAddGuard: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent addGuard: anObject! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:44'! currentBeIsolated " CurrentProjectRefactoring currentBeIsolated " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent beIsolated! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:44'! currentBeParentTo: anotherProject " CurrentProjectRefactoring currentBeParentTo: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^anotherProject setParent: self xxxCurrent! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:44'! currentBeParentToCurrent " CurrentProjectRefactoring currentBeParentToCurrent " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent setParent: self xxxCurrent! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentFlapsSuppressed " CurrentProjectRefactoring currentFlapsSuppressed " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent flapsSuppressed! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentFromMyServerLoad: aProjectName " CurrentProjectRefactoring currentFromMyServerLoad: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent fromMyServerLoad: aProjectName! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentInterruptName: aString " CurrentProjectRefactoring currentInterruptName: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^Project interruptName: aString! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dtl 4/3/2005 14:14'! currentInterruptName: aString preemptedProcess: theInterruptedProcess ^ Project interruptName: aString preemptedProcess: theInterruptedProcess! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentIsolationHead " CurrentProjectRefactoring currentIsolationHead " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent isolationHead! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentProjectName " CurrentProjectRefactoring currentProjectName " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent name! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentPropagateChanges " CurrentProjectRefactoring currentPropagateChanges " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent propagateChanges! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentSpawnNewProcessAndTerminateOld: aBoolean " CurrentProjectRefactoring currentSpawnNewProcessAndTerminateOld: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^Project spawnNewProcessAndTerminateOld: aBoolean ! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! currentToggleFlapsSuppressed " CurrentProjectRefactoring currentToggleFlapsSuppressed " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^self xxxCurrent flapsSuppressed: self xxxCurrent flapsSuppressed not. ! ! !CurrentProjectRefactoring class methodsFor: 'revectoring to current' stamp: 'dao 9/14/2005 10:45'! xxxCurrent self deprecated: 'CurrentProjectRefactoring is deprecated'. ^Project current! ! !CurrentProjectRefactoring class methodsFor: '*Flash' stamp: 'dao 9/14/2005 10:46'! updateProjectFillsIn: aFlashPlayerMorph " CurrentProjectRefactoring updateProjectFillsIn: " self deprecated: 'CurrentProjectRefactoring is deprecated'. ^aFlashPlayerMorph updateProjectFillsFrom: self xxxCurrent ! ! Form subclass: #Cursor instanceVariableNames: '' classVariableNames: 'BlankCursor BottomLeftCursor BottomRightCursor CornerCursor CrossHairCursor CurrentCursor DownCursor MarkerCursor MenuCursor MoveCursor NormalCursor OriginCursor ReadCursor ResizeLeftCursor ResizeTopCursor ResizeTopLeftCursor ResizeTopRightCursor RightArrowCursor SquareCursor TargetCursor TopLeftCursor TopRightCursor UpCursor WaitCursor WebLinkCursor WriteCursor XeqCursor' poolDictionaries: '' category: 'Graphics-Display Objects'! !Cursor commentStamp: '' prior: 0! I am a Form that is a possible appearance for a mouse cursor. My size is always 16x16, ever since the original implementation on the Alto. There are many examples available in the "current cursor" category of class methods. For example, "Cursor normal" and "Cursor wait". For example: Cursor wait show ! !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 methodsFor: 'displaying' stamp: 'ls 6/17/2002 11:56'! show "Make the hardware's mouse cursor look like 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: 'primitives'! 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: 'primitives' 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: 'printing'! printOn: aStream self storeOn: aStream base: 2! ! !Cursor methodsFor: 'testing' stamp: 'bf 2/2/1999 19:34'! hasMask ^false! ! !Cursor methodsFor: 'updating' stamp: 'ls 6/17/2002 12:00'! changed: aParameter "overriden to reinstall the cursor if it is the active cursor, in case the appearance has changed. (Is this used anywhere? Do cursors really change in place these days?)" self == CurrentCursor ifTrue: [self beCursor]. super changed: aParameter! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Cursor class instanceVariableNames: ''! !Cursor class methodsFor: 'class initialization' stamp: 'JMM 10/21/2003 18:57'! initBottomLeft BottomLeftCursor _ (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: 'class initialization' stamp: 'JMM 10/21/2003 18:57'! initBottomRight BottomRightCursor _ (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'! 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' stamp: 'kfr 7/12/2003 21:02'! initCrossHair CrossHairCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0111111111111100 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000100000000 2r0000000000000000 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' stamp: 'di 7/30/2001 10:32'! initMenu MenuCursor _ (Cursor extent: 16@16 fromArray: #( 2r1111111111100000 2r1000000000100000 2r1010011000100000 2r1000000000100000 2r1101001101100000 2r1111111111100000 2r1000000000100000 2r1011001010100000 2r1000000000100000 2r1010110010100000 2r1000000000100000 2r1010010100100000 2r1000000000100000 2r1111111111100000 0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization' stamp: 'kfr 7/12/2003 21:10'! initMove MoveCursor _ Cursor extent: 16@16 fromArray: #( 2r1111111111111100 2r1111111111111100 2r1100001100001100 2r1100001100001100 2r1100001100001100 2r1100001100001100 2r1111111111111100 2r1111111111111100 2r1100001100001100 2r1100001100001100 2r1100001100001100 2r1100001100001100 2r1111111111111100 2r1111111111111100 0) 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' stamp: 'kfr 7/12/2003 22:55'! initRead ReadCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000000 2r0000000000000000 2r0001000000001000 2r0010100000010100 2r0100000000100000 2r1111101111100000 2r1000010000100000 2r1000010000100000 2r1011010110100000 2r0111101111000000 2r0 2r0 2r0 2r0 2r0 2r0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization' stamp: 'jrp 8/6/2005 22:50'! initResizeLeft ResizeLeftCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000000 2r0000000000000000 2r0000000000000000 2r0000000000000000 2r0000100000010000 2r0001100000011000 2r0011100000011100 2r0111111111111110 2r0011100000011100 2r0001100000011000 2r0000100000010000 2r0000000000000000 2r0000000000000000 2r0000000000000000 2r0000000000000000 2r0000000000000000 ) offset: -7@-7 ) withMask! ! !Cursor class methodsFor: 'class initialization' stamp: 'jrp 8/6/2005 22:54'! initResizeTop "Cursor initResizeTop" ResizeTopCursor _ (Cursor extent: 16@16 fromArray: #( 2r000000100000000 2r000001110000000 2r000011111000000 2r000111111100000 2r000000100000000 2r000000100000000 2r000000100000000 2r000000100000000 2r000000100000000 2r000000100000000 2r000111111100000 2r000011111000000 2r000001110000000 2r000000100000000 2r000000000000000) offset: -7@-7) withMask! ! !Cursor class methodsFor: 'class initialization' stamp: 'jrp 8/6/2005 22:55'! initResizeTopLeft ResizeTopLeftCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000000 2r0111110000000000 2r0111100000000000 2r0111000000000000 2r0110100000000000 2r0100010000000000 2r0000001000000000 2r0000000100000000 2r0000000010000000 2r0000000001000100 2r0000000000101100 2r0000000000011100 2r0000000000111100 2r0000000001111100 2r0000000000000000 2r0000000000000000) offset: -7@-7) withMask! ! !Cursor class methodsFor: 'class initialization' stamp: 'jrp 8/7/2005 07:54'! initResizeTopRight ResizeTopRightCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000000000 2r0000000001111100 2r0000000000111100 2r0000000000011100 2r0000000000101100 2r0000000001000100 2r0000000010000000 2r0000000100000000 2r0000001000000000 2r0100010000000000 2r0110100000000000 2r0111000000000000 2r0111100000000000 2r0111110000000000 2r0000000000000000 2r0000000000000000) offset: -7@-7) withMask! ! !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' stamp: 'ar 3/1/2006 22:42'! initTarget ^TargetCursor := Cursor extent: 16 @ 16 fromArray: #(1984 6448 8456 16644 17284 33026 35106 65278 35106 33026 17284 16644 8456 6448 1984 0) offset: -7 @ -7! ! !Cursor class methodsFor: 'class initialization' stamp: 'JMM 10/21/2003 19:01'! initTopLeft TopLeftCursor _ (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' stamp: 'JMM 10/21/2003 19:02'! initTopRight TopRightCursor _ (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: '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' stamp: 'kfr 7/12/2003 21:27'! initWait WaitCursor _ (Cursor extent: 16@16 fromArray: #( 2r1111111111111100 2r1000000000000100 2r0100000000001000 2r0010000000010000 2r0001110011100000 2r0000111111000000 2r0000011110000000 2r0000011110000000 2r0000100101000000 2r0001000100100000 2r0010000110010000 2r0100001111001000 2r1000111111110100 2r1111111111111100 0) offset: 0@0). ! ! !Cursor class methodsFor: 'class initialization' stamp: 'kfr 7/12/2003 22:52'! initWrite WriteCursor _ (Cursor extent: 16@16 fromArray: #( 2r0000000000011000 2r0000000000111100 2r0000000001001000 2r0000000010010000 2r0000000100100000 2r0000001001000100 2r0000010010000100 2r0000100100001100 2r0001001000010000 2r0010010000010000 2r0111100000001000 2r0101000011111000 2r1110000110000000 2r0111111100000000 2r0 2r0) 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: 'JMM 10/21/2003 19:04'! 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 initBottomLeft. self initBottomRight. self initResizeLeft. self initResizeTop. self initResizeTopLeft. self initResizeTopRight. self initTopLeft. self initTopRight. 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: 'constants'! blank "Answer the instance of me that is all white." ^BlankCursor! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:13'! bottomLeft "Cursor bottomLeft showWhile: [Sensor waitButton]" ^BottomLeftCursor ! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:13'! bottomRight "Cursor bottomRight showWhile: [Sensor waitButton]" ^BottomRightCursor ! ! !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' stamp: 'ar 8/16/2001 14:48'! resizeBottom "Cursor resizeBottom showWhile: [Sensor waitButton]" ^self resizeTop! ! !Cursor class methodsFor: 'constants' stamp: 'ar 8/16/2001 14:46'! resizeBottomLeft "Cursor resizeBottomLeft showWhile: [Sensor waitButton]" ^self resizeTopRight! ! !Cursor class methodsFor: 'constants' stamp: 'ar 8/16/2001 14:45'! resizeBottomRight "Cursor resizeBottomRight showWhile: [Sensor waitButton]" ^self resizeTopLeft! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 18:58'! resizeLeft "Cursor resizeLeft showWhile: [Sensor waitButton]" ^ResizeLeftCursor! ! !Cursor class methodsFor: 'constants' stamp: 'ar 8/16/2001 14:45'! resizeRight "Cursor resizeRight showWhile: [Sensor waitButton]" ^self resizeLeft! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:19'! resizeTop "Cursor resizeTop showWhile: [Sensor waitButton]" ^ResizeTopCursor! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:00'! resizeTopLeft "Cursor resizeTopLeft showWhile: [Sensor waitButton]" ^ ResizeTopLeftCursor! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:00'! resizeTopRight "Cursor resizeTopRight showWhile: [Sensor waitButton]" ^ResizeTopRightCursor! ! !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' stamp: 'ar 3/1/2006 22:42'! target "Answer the instance of me that is the shape of a gunsight." "Cursor target show" ^TargetCursor ifNil:[self initTarget]! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:01'! topLeft "Cursor topLeft showWhile: [Sensor waitButton]" ^ TopLeftCursor! ! !Cursor class methodsFor: 'constants' stamp: 'JMM 10/21/2003 19:02'! topRight "Cursor topRight showWhile: [Sensor waitButton]" ^ TopRightCursor! ! !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 9/26/2001 22:37'! webLink "Return a cursor that can be used for emphasizing web links" "Cursor webLink showWhile: [Sensor waitButton]" ^WebLinkCursor ifNil:[ WebLinkCursor _ (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 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: '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: 'instance creation' stamp: 'ar 8/16/2001 15:52'! resizeForEdge: aSymbol "Cursor resizeForEdge: #top" "Cursor resizeForEdge: #bottomLeft" ^self perform: ('resize', aSymbol first asString asUppercase, (aSymbol copyFrom: 2 to: aSymbol size)) asSymbol.! ! Cursor subclass: #CursorWithMask instanceVariableNames: 'maskForm' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !CursorWithMask commentStamp: '' prior: 0! A Cursor which additionally has a 16x16 transparency bitmap called a "mask". See the comment of beCursorWithMask: for details on how the mask is treated.! ]style[(97 17 40)f3,f3LCursor beCursorWithMask:;,f3! !CursorWithMask methodsFor: 'converting' 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: 'mask' stamp: 'bf 2/2/1999 19:34'! hasMask ^true! ! !CursorWithMask methodsFor: 'mask' stamp: 'di 10/8/1998 16:46'! maskForm ^ maskForm! ! !CursorWithMask methodsFor: 'mask' stamp: 'di 10/8/1998 16:46'! setMaskForm: aForm maskForm _ aForm! ! !CursorWithMask methodsFor: 'mask' 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: 'mask' stamp: 'bf 2/2/1999 19:31'! withMask ^self! ! !CursorWithMask methodsFor: 'primitives' stamp: 'di 10/6/1998 15:16'! beCursor maskForm unhibernate. ^ self beCursorWithMask: maskForm! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '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"! ! !CurveFitter class methodsFor: 'instance creation'! new | newSelf | newSelf _ super new: 3. newSelf add: 0@0. newSelf add: 0@0. newSelf add: 0@0. ^newSelf! ! 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. ! ! !CurveMorph methodsFor: 'parts bin' stamp: 'sw 6/28/2001 11:32'! initializeToStandAlone super initializeToStandAlone. self beSmoothCurve. ! ! !CurveMorph methodsFor: 'testing' stamp: 'wiz 1/7/2005 20:02'! isCurvier "Test used by smoothing routines. If true use true closed curve splines for closed curves. If not mimic old stodgy curveMorph curves with one sharp bend. Curve overrides this test for backward compatability.." ^ (false)! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CurveMorph class instanceVariableNames: ''! !CurveMorph class methodsFor: 'instance creation' stamp: 'tk 11/14/2001 17:47'! arrowPrototype | aa | aa _ PolygonMorph vertices: (Array with: 5@40 with: 5@8 with: 35@8 with: 35@40) color: Color black borderWidth: 2 borderColor: Color black. aa beSmoothCurve; makeOpen; makeForwardArrow. "is already open" aa dashedBorder: {10. 10. Color red}. "A dash spec is a 3- or 5-element array with { length of normal border color. length of alternate border color. alternate border color}" aa computeBounds. ^ aa! ! !CurveMorph class methodsFor: 'parts bin' stamp: 'nk 8/23/2004 18:11'! descriptionForPartsBin ^ self partName: 'Curve' categories: #('Graphics' 'Basic') documentation: 'A smooth wiggly curve, or a curved solid. Shift-click to get handles and move the points.'! ! !CurveMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/11/2003 10:15'! initialize self registerInFlapsRegistry. ! ! !CurveMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/11/2003 10:16'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(CurveMorph authoringPrototype 'Curve' 'A curve') forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(CurveMorph authoringPrototype 'Curve' 'A curve') forFlapNamed: 'Supplies'.]! ! !CurveMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/11/2003 12:33'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !CurveMorph class methodsFor: '*MorphicExtras-parts bin' stamp: 'nk 8/23/2004 18:11'! supplementaryPartsDescriptions ^ {DescriptionForPartsBin formalName: 'Curvy Arrow' categoryList: #('Basic' 'Graphics') documentation: 'A curved line with an arrowhead. Shift-click to get handles and move the points.' globalReceiverSymbol: #CurveMorph nativitySelector: #arrowPrototype} ! ! PolygonMorph subclass: #CurvierMorph instanceVariableNames: '' classVariableNames: 'SlopeConstantsCache' poolDictionaries: '' category: 'Morphic-Basic-NewCurve'! !CurvierMorph commentStamp: '' prior: 0! I want to be merged into PolygonMorph. I implement Closed Cubic Curves and restructured routines to ease maintence and development. New way to calculate curves. cVariables SlopeConstantsCache anArray size 2 indexed by nVerts \\2 . Each element is an array of integers. The integers represent the constants for calculating slopes for closed cubic curves from the vertices. Class Variable SlopeConstantsCache holds a pair of arrays for even and odd number of vertices( aka knots). Each array holds a series of constants in Integer form. This allows slopes to be calculated directly from the array of knots. Wonderfully it turns out that only two arrays are needed. By matching up the knots equidistant from the point in question; Summing the weighted differences of the pairs the unscaled slope can be arrived at. The scale needed to get the slopes needed is trice the reciprical of the next integer in the series. We leave the division til last to get the full benifit of the integer arithmetic. Rounding the vertices before calculation is recommended. Instead of calculating the number of curve subsegments in lineSegDo we add a ninth array to curve state to allow the number to be precalculated. Getting better looking curves motivates finding a better way of guessing n. So this provides a framework for trying. For the first pass we just used the constant 12 for every thing. In the second pass we judge the number of segments by starting with two and doubling the number until the distance of the curve no longer increases. Then we hone in using a binary search to find the smallest number of segments with that same curve length. We have changed some assumptions. Previously curves were figured by solving for the second derivative first and using the results to determine the slope and the third derivative. So lineSegDo counted on the last second deriv being a number it could use in its calculation of the number of subsegments. Currently we just solve for slopes and the second and third derivs are derived from that. Also the derivation for the second and third derivs only assume C(1) (first derivitive continuity). The calculations for the slopes are the only calcs using C(2) continuity. Therefore the slopes can alternately be chosen to fit some other chriteria and the resulting curves will still be smooth to the first degree. A useful variant of closed slopes is to scale them by a constant. Also the last of each element of curvestate always reflects a closing segment. And we don't add an extra row for closed curves anymore. That is now lineSegDo's responsibility to be aware of as it was already doing with segmented curves. So the last n does not track its old value. Preferences: A Preference has been added to toggle between the old (ugly) closed curves based on natural cubic slopes and the new smooth algorythim. This doesn't make much difference while newcurves are a subclass of polygons but the ambition is for newcurves to supercede polygons. This will allow backwards compatibility. Shapes: With closed curves a smooth oval results from rectagular or diamond vertices. So two menuitems have been added (to PolygonMorph) that allow the vertices to be set to these shapes using the current bounds of the polygon. The former state of vertices will be lost but it seems useful to lose a complicated shape and start fresh with a simple symmetrical one. Furthur on: Modify curveState to only contain slope and higher deriv information. Let the information about the knots only be held only in the vertices of the polygon. Once that is done curvestate will not have to be recalcutaled each time the polygon is moved but only when its shape changes. There is also some possible speed up to be had by refining or experimenting with other number of segment calculating schemes but not as much as preserving curvestate over a move. Furthur furthur on: Figure out how to combine straight and curved segments into a single shape in a pleasing way. ! !CurvierMorph methodsFor: 'initialization' stamp: 'wiz 11/16/2004 21:35'! initialize "We use an oval shape because we wear it well." super initialize. self beSmoothCurve. self diamondOval! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! CurvierMorph class instanceVariableNames: ''! !CurvierMorph class methodsFor: 'parts bin' stamp: 'wiz 11/6/2004 23:24'! descriptionForPartsBin "We are very much like curve only better looking." ^ self partName: 'Curvier' categories: #('Graphics' 'Basic' ) documentation: 'A smooth wiggly curve, or a smooth curved solid without bends. Shift-click to get handles and move the points.'! ! !CurvierMorph class methodsFor: 'class initialization' stamp: 'wiz 11/6/2004 23:16'! initialize "CurvierMorph initialize" Preferences preferenceAt: #Curvier ifAbsent: [Preferences addPreference: #Curvier category: #morphic default: true balloonHelp: 'if true, closed CurvierMorphs will be smoother and more symmetrical all about. If false they will mimic the old curve shapes with the one sharp bend.']. self registerInFlapsRegistry! ! !CurvierMorph class methodsFor: 'class initialization' stamp: 'wiz 11/6/2004 23:17'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(#CurvierMorph #authoringPrototype 'Curvier' 'A curve' ) forFlapNamed: 'PlugIn Supplies'. cl registerQuad: #(#CurvierMorph #authoringPrototype 'Curvier' 'A curve' ) forFlapNamed: 'Supplies']! ! SelectionMenu subclass: #CustomMenu instanceVariableNames: 'labels dividers lastDivider title targets arguments' classVariableNames: '' poolDictionaries: '' category: 'ST80-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: 'compatibility' stamp: 'ads 2/20/2003 08:59'! add: aString subMenu: aMenu target: target selector: aSymbol argumentList: argList "Create a sub-menu with the given label. This isn't really a sub-menu the way Morphic does it; it'll just pop up another menu." self add: aString target: aMenu selector: #invokeOn: argumentList: argList asArray.! ! !CustomMenu methodsFor: 'compatibility' stamp: 'sumim 2/10/2002 01:23'! add: aString target: target selector: aSymbol argument: arg "Append a menu item with the given label. If the item is selected, it will send the given selector to the target object with the given argument." self add: aString target: target selector: aSymbol argumentList: (Array with: arg)! ! !CustomMenu methodsFor: 'compatibility' stamp: 'sumim 2/10/2002 01:18'! add: aString target: target selector: aSymbol argumentList: argList "Append a menu item with the given label. If the item is selected, it will send the given selector to the target object with the given arguments. If the selector takes one more argument than the number of arguments in the given list, then the triggering event is supplied as as the last argument." self add: aString action: aSymbol. targets addLast: target. arguments addLast: argList asArray ! ! !CustomMenu methodsFor: 'compatibility' stamp: 'nk 2/15/2004 16:19'! addService: aService for: serviceUser "Append a menu item with the given service. If the item is selected, it will perform the given service." aService addServiceFor: serviceUser toMenu: self.! ! !CustomMenu methodsFor: 'compatibility' stamp: 'nk 2/15/2004 16:02'! addServices2: services for: served extraLines: linesArray services withIndexDo: [:service :i | service addServiceFor: served toMenu: self. (linesArray includes: i) ifTrue: [self addLine] ]! ! !CustomMenu methodsFor: 'compatibility' stamp: 'sumim 2/10/2002 01:20'! addServices: services for: served extraLines: linesArray services withIndexDo: [:service :i | self addService: service for: served. (linesArray includes: i) | service useLineAfter ifTrue: [self addLine]]! ! !CustomMenu methodsFor: 'compatibility' stamp: 'sw 2/16/2002 00:57'! arguments "Answer my arguments, initializing them to an empty collection if they're found to be nil." ^ arguments ifNil: [arguments _ OrderedCollection new]! ! !CustomMenu methodsFor: 'compatibility' stamp: 'sw 2/16/2002 00:57'! targets "Answer my targets, initializing them to an empty collection if found to be nil" ^ targets ifNil: [targets _ OrderedCollection new]! ! !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: 'sw 2/27/2001 07:52'! addList: listOfTuplesAndDashes "Add a menu item to the receiver for each tuple in the given list of the form ( ). Add a line for each dash (-) in the list. The tuples may have an optional third element, providing balloon help for the item, but such an element is ignored in mvc." listOfTuplesAndDashes do: [:aTuple | aTuple == #- ifTrue: [self addLine] ifFalse: [self add: aTuple first action: aTuple second]] "CustomMenu new addList: #( ('apples' buyApples) ('oranges' buyOranges) - ('milk' buyMilk)); startUp" ! ! !CustomMenu methodsFor: 'construction' stamp: 'sw 8/12/2002 17:14'! addStayUpItem "For compatibility with MenuMorph. Here it is a no-op"! ! !CustomMenu methodsFor: 'construction' stamp: 'nk 11/25/2003 10:00'! addTranslatedList: listOfTuplesAndDashes "Add a menu item to the receiver for each tuple in the given list of the form ( ). Add a line for each dash (-) in the list. The tuples may have an optional third element, providing balloon help for the item, but such an element is ignored in mvc. The first element will be translated." listOfTuplesAndDashes do: [:aTuple | aTuple == #- ifTrue: [self addLine] ifFalse: [self add: aTuple first translated action: aTuple second]] "CustomMenu new addTranslatedList: #( ('apples' buyApples) ('oranges' buyOranges) - ('milk' buyMilk)); startUp" ! ! !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: 'yo 8/28/2002 22:34'! 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 isString 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: 'initialize-release' stamp: 'sumim 2/10/2002 01:26'! initialize labels _ OrderedCollection new. selections _ OrderedCollection new. dividers _ OrderedCollection new. lastDivider _ 0. targets _ OrderedCollection new. arguments _ OrderedCollection new ! ! !CustomMenu methodsFor: 'initialize-release' stamp: 'sw 8/18/1998 12:01'! title: aTitle title _ aTitle! ! !CustomMenu methodsFor: 'invocation' stamp: 'sw 2/17/2002 04:48'! invokeOn: targetObject "Pop up this menu and return the result of sending to the target object the selector corresponding to the menu item selected by the user. Return nil if no item is selected. If the chosen selector has arguments, obtain them from my arguments" ^ self invokeOn: targetObject orSendTo: nil! ! !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' stamp: 'sw 11/16/2002 23:45'! invokeOn: targetObject orSendTo: anObject "Pop up this menu and return the result of sending to the target object the selector corresponding to the menu item selected by the user. Return nil if no item is selected. If the chosen selector has arguments, obtain appropriately. If the recipient does not respond to the resulting message, send it to the alternate object provided" | aSelector anIndex recipient | ^ (aSelector _ self startUp) ifNotNil: [anIndex _ self selection. recipient _ ((targets _ self targets) isEmptyOrNil or: [anIndex > targets size]) ifTrue: [targetObject] ifFalse: [targets at: anIndex]. aSelector numArgs == 0 ifTrue: [recipient perform: aSelector orSendTo: anObject] ifFalse: [recipient perform: aSelector withArguments: (self arguments at: anIndex)]]! ! !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: '*Morphic-invocation' stamp: 'wiz 7/20/2004 12:20'! startUpWithCaption: caption at: aPoint "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 at: aPoint! ! !CustomMenu methodsFor: '*Morphic-invocation' stamp: 'wiz 7/20/2004 12:18'! startUp: initialSelection withCaption: caption at: aPoint "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 at: aPoint! ! !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: '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: '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: 'stephaneducasse 2/3/2006 22:22'! stream: aStream stream := aStream! ! !DECTalkReader methodsFor: 'initialization' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'reading' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: '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: 'stephaneducasse 2/3/2006 22:23'! 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: 'stephaneducasse 2/3/2006 22:23'! 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: 'stephaneducasse 2/3/2006 22:23'! 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: 'stephaneducasse 2/3/2006 22:23'! 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.'! ! !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)! ! PostscriptCanvas subclass: #DSCPostscriptCanvas instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'drawing-general' stamp: 'nk 1/2/2004 16:53'! fullDraw: aMorph (morphLevel = 0 and: [aMorph pagesHandledAutomatically not]) ifTrue: [pages _ pages + 1. target print: '%%Page: 1 1'; cr]. super fullDraw: aMorph. morphLevel = 0 ifTrue: [ self writeTrailer: pages. ]! ! !DSCPostscriptCanvas methodsFor: 'initialization' stamp: 'nk 1/2/2004 15:36'! writePSIdentifierRotated: rotateFlag | morphExtent pageExtent | target print: '%!!PS-Adobe-2.0'; cr; print: '%%Pages: (atend)'; cr; print: '%%DocumentFonts: (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 := (printSpecs isNil or: [printSpecs scaleToFitPage]) ifTrue: [pageExtent x / morphExtent x min: pageExtent y / morphExtent y] ifFalse: [1.0]. target print: '%%BoundingBox: '; write: self defaultImageableArea; cr. target print: '%%Title: '; print: self topLevelMorph externalName; cr. target print: '%%Creator: '; print: Utilities authorName; cr. target print: '%%CreationDate: '; print: Date today asString; space; print: Time now asString; cr. target print: '%%Orientation: '; print: (rotateFlag ifTrue: ['Landscape'] ifFalse: ['Portrait']); cr. target print: '%%EndComments'; cr. ! ! !DSCPostscriptCanvas methodsFor: 'morph drawing' stamp: 'nk 1/2/2004 16:53'! endGStateForMorph: aMorph "position the morph on the page " morphLevel == (topLevelMorph pagesHandledAutomatically ifTrue: [2] ifFalse: [1]) ifTrue: [ target showpage; print: 'grestore'; cr ]! ! !DSCPostscriptCanvas methodsFor: 'morph drawing' stamp: 'nk 6/10/2004 13:19'! fullDrawBookMorph: aBookMorph " draw all the pages in a book morph, but only if it is the top-level morph " morphLevel = 1 ifFalse: [^ super fullDrawBookMorph: aBookMorph]. "Unfortunately, the printable 'pages' of a StackMorph are the cards, but for a BookMorph, they are the pages. Separate the cases here." (aBookMorph isKindOf: StackMorph) ifTrue: [ aBookMorph cards do: [:aCard | aBookMorph goToCard: aCard. "cause card-specific morphs to be installed" pages _ pages + 1. target print: '%%Page: '; write: pages; space; write: pages; cr. self drawPage: aBookMorph currentPage]] ifFalse: [ aBookMorph pages do: [:aPage | pages _ pages + 1. target print: '%%Page: '; write: pages; space; write: pages; cr. self drawPage: aPage]]. morphLevel = 0 ifTrue: [ self writeTrailer: pages ]. ! ! !DSCPostscriptCanvas methodsFor: 'morph drawing' stamp: 'nk 1/1/2004 18:21'! setupGStateForMorph: aMorph "position the morph on the page " morphLevel == (topLevelMorph pagesHandledAutomatically ifTrue: [2] ifFalse: [1]) ifTrue: [ self writePageSetupFor: aMorph ]! ! !DSCPostscriptCanvas methodsFor: 'page geometry' stamp: 'mpw 9/15/1999 20:14'! defaultImageableArea ^ self defaultPageSize insetBy:self defaultMargin. ! ! !DSCPostscriptCanvas methodsFor: 'page geometry' stamp: 'di 8/3/2000 14:18'! defaultMargin "In Points" ^ (0.25 * 72) asInteger. ! ! !DSCPostscriptCanvas methodsFor: 'page geometry' 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: 'page geometry' stamp: 'nk 1/1/2004 19:56'! pageBBox | pageSize offset bbox trueExtent | trueExtent := savedMorphExtent * initialScale. "this one has been rotated" pageSize := self defaultPageSize. offset := pageSize extent - trueExtent / 2 max: 0 @ 0. bbox := offset extent: trueExtent. ^ bbox! ! !DSCPostscriptCanvas methodsFor: 'page geometry' stamp: 'nk 12/30/2003 17:22'! pageOffset ^self pageBBox origin. ! ! DSCPostscriptCanvas subclass: #DSCPostscriptCanvasToDisk instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Postscript Canvases'! !DSCPostscriptCanvasToDisk methodsFor: 'as yet unclassified' stamp: 'RAA 2/22/2001 07:41'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset ^self morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset specs: nil ! ! !DSCPostscriptCanvasToDisk methodsFor: 'as yet unclassified' stamp: 'nk 12/30/2003 17:39'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset specs: specsOrNil 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: 'nk 12/30/2003 16:58'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset specs: specsOrNil | newFileName stream | ^[ (self new morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset) close ] on: PickAFileToWriteNotification do: [ :ex | newFileName _ FillInTheBlank request: 'Name of file to write:' translated initialAnswer: 'xxx',Time millisecondClockValue printString, self defaultExtension. newFileName isEmptyOrNil ifFalse: [ stream _ FileStream fileNamed: newFileName. stream ifNotNil: [ex resume: stream]. ]. ]. ! ! !DSCPostscriptCanvasToDisk class methodsFor: 'as yet unclassified' stamp: 'RAA 2/22/2001 07:43'! morphAsPostscript: aMorph rotated: rotateFlag specs: specsOrNil ^ self morphAsPostscript: aMorph rotated: rotateFlag offsetBy: self baseOffset specs: specsOrNil ! ! !DSCPostscriptCanvasToDisk class methodsFor: 'configuring' stamp: 'RAA 9/16/2000 22:14'! defaultTarget ^PostscriptEncoderToDisk stream. ! ! !DSCPostscriptCanvasToDisk class methodsFor: 'testing' stamp: 'RAA 2/22/2001 07:41'! morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset ^self morphAsPostscript: aMorph rotated: rotateFlag offsetBy: offset specs: nil ! ! 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: 'di 11/17/2001 14:19'! recordInvalidRect: newRect "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 a | totalRepaint ifTrue: [^ self]. "planning full repaint; don't bother collecting damage" invalidRects do: [:rect | ((a _ (rect intersect: newRect) area) > 40 and: ["Avoid combining a vertical and horizontal rects. Can make a big diff and we only test when likely." a > (newRect area // 4) or: [a > (rect area // 4)]]) ifTrue: ["merge rectangle in place (see note below) if there is significant overlap" rect setOrigin: (rect origin min: newRect origin) truncated corner: (rect corner max: newRect corner) truncated. ^ self]]. invalidRects size >= 15 ifTrue: ["if there are too many separate areas, merge them all" 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: (newRect topLeft truncated corner: newRect bottomRight truncated). ! ! !DamageRecorder methodsFor: 'testing' stamp: 'dgd 2/22/2003 14:43'! updateIsNeeded "Return true if the display needs to be updated." ^totalRepaint or: [invalidRects notEmpty]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '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' stamp: 'nk 3/12/2004 21:56'! contents ^byteStream contents! ! !DataStream methodsFor: 'other' stamp: 'yo 12/3/2004 17:14'! 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 3/5/2002 09:51'! 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 | obj _ self next. self close. ^ obj! ! !DataStream methodsFor: 'other' stamp: 'ar 2/24/2001 22:45'! project ^nil! ! !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 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' stamp: 'yo 12/3/2004 17:12'! 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: 'ar 4/10/2005 20:31'! 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. internalObject == String ifTrue:[ "This is a hack to figure out if we're loading a String class that really should be a ByteString. Note that these days this will no longer be necessary since we use #withClassVersion: for constructing the global thus using a different classVersion will perfectly do the trick." ((anObject isKindOf: DiskProxy) and:[anObject globalObjectName == #String and:[anObject constructorSelector == #yourself]]) ifTrue:[ internalObject := ByteString]]. ^ 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' stamp: 'yo 12/3/2004 17:25'! 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' stamp: 'yo 12/3/2004 17:12'! 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' stamp: 'yo 12/3/2004 16:59'! 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' stamp: 'yo 12/3/2004 17:07'! 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 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: 'ar 4/10/2005 22:17'! 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: ByteString put: 5. refTypes add: 1. t at: ByteSymbol 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. self flag: #ByteArray. t at: ByteString 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, both 16-bit and 32-bit. See methods in ArrayedCollection. Overridden in SoundBuffer." t at: CompiledMethod put: 21. refTypes add: 1. "special creation method" "t at: put: 22. refTypes add: 0." ReferenceStream refTypes: refTypes. "save it" "For all classes that are like WordArrays, store them the way ColorArray is stored. As bits, and able to change endianness." Smalltalk do: [:cls | cls isInMemory ifTrue: [ cls isBehavior ifTrue: [ cls isPointers not & cls isVariable & cls isWords ifTrue: [ (t includesKey: cls) ifFalse: [t at: cls put: 20]]]]].! ! !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! ! Vocabulary subclass: #DataType instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Type Vocabularies'! !DataType commentStamp: 'sw 8/22/2002 15:01' prior: 0! A Vocabulary representing typed data.! !DataType methodsFor: 'initial value' stamp: 'sw 9/26/2001 12:00'! initialValueForASlotFor: aPlayer "Answer the value to give initially to a newly created slot of the given type in the given player" ^ 'no value'! ! !DataType methodsFor: 'queries' stamp: 'mir 7/15/2004 10:34'! representsAType "Answer whether this vocabulary represents an end-user-sensible data type" "^ (self class == DataType) not" "i.e. subclasses yes, myself no" "Assuming this is an abstract class" ^true! ! !DataType methodsFor: '*eToys-color' stamp: 'sw 8/28/2004 20:30'! subduedColorFromTriplet: anRGBTriplet "Currently: as an expedient, simply return a standard system-wide constant; this is used only for the border-color of tiles... Formerly: Answer a subdued color derived from the rgb-triplet to use as a tile color." ^ ScriptingSystem standardTileBorderColor " ^ (Color fromRgbTriplet: anRGBTriplet) mixed: ScriptingSystem colorFudge with: ScriptingSystem uniformTileInteriorColor"! ! !DataType methodsFor: '*eToys-initialization' stamp: 'sw 9/27/2001 17:32'! setFormatForDisplayer: aDisplayer "Set up the displayer to have the right format characteristics" aDisplayer useDefaultFormat. aDisplayer growable: true ! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:36'! addExtraItemsToMenu: aMenu forSlotSymbol: slotSym "If the receiver has extra menu items to add to the slot menu, here is its chance to do it"! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:38'! addUserSlotItemsTo: aMenu slotSymbol: slotSym "Optionally add items to the menu that pertain to a user-defined slot of the given symbol" ! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 1/12/2005 08:35'! addWatcherItemsToMenu: aMenu forGetter: aGetter "Add watcher items to the menu if appropriate, provided the getter is not an odd-ball one for which a watcher makes no sense" (Vocabulary gettersForbiddenFromWatchers includes: aGetter) ifFalse: [aMenu add: 'simple watcher' translated selector: #tearOffUnlabeledWatcherFor: argument: aGetter. aMenu add: 'detailed watcher' translated selector: #tearOffFancyWatcherFor: argument: aGetter. aMenu addLine]! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 02:29'! affordsCoercionToBoolean "Answer true if a tile of this data type, when dropped into a pane that demands a boolean, could plausibly be expanded into a comparison (of the form frog < toad or frog = toad) to provide a boolean expression" ^ true! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 02:53'! comparatorForSampleBoolean "Answer the comparator to use in tile coercions involving the receiver; normally, the equality comparator is used but NumberType overrides" ^ #=! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 13:15'! defaultArgumentTile "Answer a tile to represent the type" ^ 'arg' newTileMorphRepresentative typeColor: self typeColor! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:37'! newReadoutTile "Answer a tile that can serve as a readout for data of this type" ^ StringReadoutTile new typeColor: Color lightGray lighter! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 1/4/2005 00:45'! updatingTileForTarget: aTarget partName: partName getter: getter setter: setter "Answer, for classic tiles, an updating readout tile for a part with the receiver's type, with the given getter and setter" | aTile displayer actualSetter | actualSetter _ setter ifNotNil: [(#(none #nil unused) includes: setter) ifTrue: [nil] ifFalse: [setter]]. aTile _ self newReadoutTile. displayer _ UpdatingStringMorph new getSelector: getter; target: aTarget; growable: true; minimumWidth: 24; putSelector: actualSetter. "Note that when typeSymbol = #number, the #target: call above will have dealt with floatPrecision details" self setFormatForDisplayer: displayer. aTile addMorphBack: displayer. (actualSetter notNil and: [self wantsArrowsOnTiles]) ifTrue: [aTile addArrows]. getter numArgs == 0 ifTrue: [aTile setLiteralInitially: (aTarget perform: getter)]. ^ aTile ! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/27/2001 17:33'! wantsArrowsOnTiles "Answer whether this data type wants up/down arrows on tiles representing its values" ^ true! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/26/2001 03:11'! wantsAssignmentTileVariants "Answer whether an assignment tile for a variable of this type should show variants to increase-by, decrease-by, multiply-by. NumberType says yes, the rest of us say no" ^ false! ! !DataType methodsFor: '*eToys-tiles' stamp: 'sw 9/26/2001 03:18'! wantsSuffixArrow "Answer whether a tile showing data of this type would like to have a suffix arrow" ^ false! ! Timespan subclass: #Date instanceVariableNames: '' classVariableNames: '' poolDictionaries: 'ChronologyConstants' category: 'Kernel-Chronology'! !Date commentStamp: '' prior: 0! Instances of Date are Timespans with duration of 1 day. Their default creation assumes a start of midnight in the local time zone.! !Date methodsFor: 'printing' stamp: 'brp 7/27/2003 16:07'! mmddyyyy "Answer the receiver rendered in standard U.S.A format mm/dd/yyyy. Note that the name here is slightly misleading -- the month and day numbers don't show leading zeros, so that for example February 1 1996 is 2/1/96" ^ self printFormat: #(2 1 3 $/ 1 1)! ! !Date methodsFor: 'printing' stamp: 'brp 7/27/2003 16:06'! 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 3/23/2001 12:27'! printOn: aStream self printOn: aStream format: #(1 2 3 $ 3 1 )! ! !Date methodsFor: 'printing' stamp: 'brp 7/27/2003 16:05'! 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 dayMonthYearDo: [ :d :m :y | {d. m. y} ]. 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: ((Month nameOfMonth: gregorian middle) copyFrom: 1 to: 3)]. monthFormat = 3 ifTrue: [aStream nextPutAll: (Month nameOfMonth: 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: 'BP 3/23/2001 12:27'! storeOn: aStream aStream print: self printString; nextPutAll: ' asDate'! ! !Date methodsFor: 'printing' stamp: 'brp 7/27/2003 16:04'! yyyymmdd "Format the date in ISO 8601 standard like '2002-10-22'." ^ self printFormat: #(3 2 1 $- 1 1 2)! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 8/23/2003 22:09'! addDays: dayCount ^ (self asDateAndTime + (dayCount days)) asDate! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:08'! asSeconds "Answer the seconds since the Squeak epoch: 1 January 1901" ^ start asSeconds! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:08'! leap "Answer whether the receiver's year is a leap year." ^ start isLeapYear ifTrue: [1] ifFalse: [0].! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 1/16/2004 14:30'! previous: dayName "Answer the previous date whose weekday name is dayName." | days | days _ 7 + self weekdayIndex - (self class dayOfWeek: dayName) \\ 7. days = 0 ifTrue: [ days _ 7 ]. ^ self subtractDays: days ! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:09'! subtractDate: aDate "Answer the number of days between self and aDate" ^ (self start - aDate asDateAndTime) days! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 8/23/2003 22:05'! subtractDays: dayCount ^ (self asDateAndTime - (dayCount days)) asDate! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 8/24/2003 12:04'! weekday "Answer the name of the day of the week on which the receiver falls." ^ self dayOfWeekName! ! !Date methodsFor: 'smalltalk-80' stamp: 'brp 8/24/2003 12:04'! weekdayIndex "Sunday=1, ... , Saturday=7" ^ self dayOfWeek! ! !Date methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 16:10'! asDate ^ self! ! !Date methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 16:10'! dayMonthYearDo: aBlock "Supply integers for day, month and year to aBlock and return the result" ^ start dayMonthYearDo: aBlock! ! !Date methodsFor: 'squeak protocol' stamp: 'avi 2/21/2004 18:12'! month ^ self asMonth! ! !Date methodsFor: 'squeak protocol' stamp: 'avi 2/29/2004 13:10'! monthIndex ^ super month! ! !Date methodsFor: 'utils' stamp: 'tbn 7/11/2006 10:30'! addMonths: monthCount |year month maxDaysInMonth day | year := self year + (monthCount + self monthIndex - 1 // 12). month := self monthIndex + monthCount - 1 \\ 12 + 1. maxDaysInMonth := Month daysInMonth: month forYear: year. day := self dayOfMonth > maxDaysInMonth ifTrue: [maxDaysInMonth] ifFalse: [self dayOfMonth]. ^ Date newDay: day month: month year: year! ! !Date methodsFor: 'utils' stamp: 'spfa 3/8/2004 13:52'! onNextMonth ^ self addMonths: 1 ! ! !Date methodsFor: 'utils' stamp: 'spfa 3/8/2004 13:52'! onPreviousMonth ^ self addMonths: -1 ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Date class instanceVariableNames: ''! !Date class methodsFor: 'general inquiries' stamp: 'BG 3/16/2005 14:57'! easterDateFor: year " compute the easter date. source: Physikalisch-Technische Bundesanstalt Braunschweig. Lichtenberg, H.: Zur Interpretation der Gaussschen Osterformel und ihrer Ausnahmeregeln, Historia Mathematica 24 (1997), pp. 441-444 http://www.ptb.de/de/org/4/44/441/oste.htm " | k m s a d r og sz oe day | k := year // 100. m := 15 + (3*k + 3//4) - (8*k + 13//25). s := 2 - (3*k + 3// 4). a := year \\ 19. d := 19*a + m \\ 30. r := d//29 + ((d//28) - (d//29)* (a// 11)). og := 21 + d - r. sz := 7 - (year//4 + year + s\\7). oe := 7 - (og - sz\\7). day := og + oe. ^day <= 31 ifTrue: [Date newDay: day month: 3 year: year ] ifFalse: [Date newDay: day - 31 month: 4 year: year].! ! !Date class methodsFor: 'general inquiries' stamp: 'BG 3/16/2005 14:48'! orthodoxEasterDateFor: year " compute the easter date according to the rules of the orthodox calendar. source: http://www.smart.net/~mmontes/ortheast.html " | r1 r2 r3 r4 ra rb r5 rc date | r1 := year \\ 19. r2 := year \\ 4. r3 := year \\ 7. ra := 19*r1 + 16. r4 := ra \\ 30. rb := r2 + r2 + (4*r3) + (6*r4). r5 := rb \\ 7. rc := r4 + r5. date := Date newDay: 3 month: 4 year: year. ^date addDays: rc.! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 8/24/2003 00:00'! dateAndTimeNow "Answer an Array whose with Date today and Time now." ^ Time dateAndTimeNow! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:35'! dayOfWeek: dayName ^ Week indexOfDay: dayName! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:59'! daysInMonth: monthName forYear: yearInteger ^ Month daysInMonth: monthName forYear: yearInteger. ! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:53'! daysInYear: yearInteger ^ Year daysInYear: yearInteger.! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 1/16/2004 14:35'! firstWeekdayOfMonth: month year: year "Answer the weekday index of the first day in in the ." ^ (self newDay: 1 month: month year: year) weekdayIndex ! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:01'! fromDays: dayCount "Days since 1 January 1901" ^ self julianDayNumber: dayCount + SqueakEpoch! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:02'! fromSeconds: seconds "Answer an instance of me which is 'seconds' seconds after January 1, 1901." ^ self fromDays: ((Duration seconds: seconds) days)! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:39'! indexOfMonth: aMonthName ^ Month indexOfMonth: aMonthName. ! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:56'! leapYear: yearInteger ^ Year leapYear: yearInteger! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:37'! nameOfDay: dayIndex ^ Week nameOfDay: dayIndex ! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 13:40'! nameOfMonth: anIndex ^ Month nameOfMonth: anIndex. ! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:02'! newDay: day month: month year: year ^ self year: year month: month day: day! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:01'! newDay: dayCount year: yearInteger ^ self year: yearInteger day: dayCount! ! !Date class methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 16:01'! today ^ self current! ! !Date class methodsFor: 'squeak protocol' stamp: 'md 7/15/2006 18:06'! fromString: aString "Answer an instance of created from a string with format mm.dd.yyyy." ^ self readFrom: aString readStream.! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 18:25'! julianDayNumber: aJulianDayNumber ^ self starting: (DateAndTime julianDayNumber: aJulianDayNumber)! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/1/2003 09:21'! 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) (5APR82)" | day month year | 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 := Month nameOfMonth: day. day := Integer readFrom: aStream]. [aStream peek isAlphaNumeric] whileFalse: [aStream skip: 1]. year := Integer readFrom: aStream. year < 10 ifTrue: [year := 2000 + year] ifFalse: [ year < 1900 ifTrue: [ year := 1900 + year]]. ^ self year: year month: month day: day! ! !Date class methodsFor: 'squeak protocol' stamp: 'BP 3/23/2001 12:36'! starting: aDateAndTime ^super starting: (aDateAndTime midnight) duration: (Duration days: 1) ! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/1/2003 18:09'! tomorrow ^ self today next! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 22:03'! year: year day: dayOfYear ^ self starting: (DateAndTime year: year day: dayOfYear) ! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 22:02'! year: year month: month day: day ^ self starting: (DateAndTime year: year month: month day: day) ! ! !Date class methodsFor: 'squeak protocol' stamp: 'brp 7/1/2003 18:09'! yesterday ^ self today previous! ! Magnitude subclass: #DateAndTime instanceVariableNames: 'seconds offset jdn nanos' classVariableNames: 'LocalTimeZone' poolDictionaries: 'ChronologyConstants' category: 'Kernel-Chronology'! !DateAndTime commentStamp: 'brp 5/13/2003 08:07' prior: 0! I represent a point in UTC time as defined by ISO 8601. I have zero duration. My implementation uses three SmallIntegers and a Duration: jdn - julian day number. seconds - number of seconds since midnight. nanos - the number of nanoseconds since the second. offset - duration from UTC. The nanosecond attribute is almost always zero but it defined for full ISO compliance and is suitable for timestamping. ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 7/9/2005 08:45'! + operand "operand conforms to protocol Duration" | ticks | ticks _ self ticks + (operand asDuration ticks) . ^ self class basicNew ticks: ticks offset: self offset; yourself. ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 1/9/2004 05:39'! - operand "operand conforms to protocol DateAndTime or protocol Duration" ^ (operand respondsTo: #asDateAndTime) ifTrue: [ | lticks rticks | lticks _ self asLocal ticks. rticks _ operand asDateAndTime asLocal ticks. Duration seconds: (SecondsInDay *(lticks first - rticks first)) + (lticks second - rticks second) nanoSeconds: (lticks third - rticks third) ] ifFalse: [ self + (operand negated) ]. ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'nk 3/30/2004 09:09'! < comparand "comparand conforms to protocol DateAndTime, or can be converted into something that conforms." | lticks rticks comparandAsDateAndTime | comparandAsDateAndTime := comparand asDateAndTime. offset = comparandAsDateAndTime offset ifTrue: [lticks := self ticks. rticks := comparandAsDateAndTime ticks] ifFalse: [lticks := self asUTC ticks. rticks := comparandAsDateAndTime asUTC ticks]. ^ lticks first < rticks first or: [lticks first > rticks first ifTrue: [false] ifFalse: [lticks second < rticks second or: [lticks second > rticks second ifTrue: [false] ifFalse: [lticks third < rticks third]]]] ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 7/28/2004 16:14'! = comparand "comparand conforms to protocol DateAndTime, or can be converted into something that conforms." | comparandAsDateAndTime | self == comparand ifTrue: [^ true]. [comparandAsDateAndTime := comparand asDateAndTime] on: MessageNotUnderstood do: [^ false]. ^ self offset = comparandAsDateAndTime offset ifTrue: [self hasEqualTicks: comparandAsDateAndTime ] ifFalse: [self asUTC ticks = comparandAsDateAndTime asUTC ticks] ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 13:11'! asLocal ^ (self offset = self class localOffset) ifTrue: [self] ifFalse: [self utcOffset: self class localOffset] ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'ab 4/22/2006 12:30'! asUTC ^ offset isZero ifTrue: [self] ifFalse: [self utcOffset: 0]! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 21:03'! dayOfMonth "Answer which day of the month is represented by the receiver." ^ self dayMonthYearDo: [ :d :m :y | d ]! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/24/2003 12:25'! dayOfWeek "Sunday=1, ... , Saturday=7" ^ (jdn + 1 rem: 7) + 1! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 10:34'! dayOfWeekAbbreviation ^ self dayOfWeekName copyFrom: 1 to: 3! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:28'! dayOfWeekName ^ Week nameOfDay: self dayOfWeek ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 1/29/2005 10:27'! dayOfYear "This code was contributed by Dan Ingalls. It is equivalent to the terser ^ jdn - (Year year: self year) start julianDayNumber + 1 but much quicker." | monthStart | ^ self dayMonthYearDo: [ :d :m :y | monthStart _ #(1 32 60 91 121 152 182 213 244 274 305 335) at: m. (m > 2 and: [ Year isLeapYear: y ]) ifTrue: [ monthStart + d ] ifFalse: [ monthStart + d - 1 ]]! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 15:49'! hash ^ self asUTC ticks hash ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:29'! hour ^ self hour24 ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'avi 2/21/2004 18:46'! hour12 "Answer an between 1 and 12, inclusive, representing the hour of the day in the 12-hour clock of the local time of the receiver." ^ self hour24 - 1 \\ 12 + 1! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:29'! hour24 ^ (Duration seconds: seconds) hours ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:29'! isLeapYear ^ Year isLeapYear: self year. ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/24/2003 11:03'! meridianAbbreviation ^ self asTime meridianAbbreviation! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:30'! minute ^ (Duration seconds: seconds) minutes ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 21:05'! month ^ self dayMonthYearDo: [ :d :m :y | m ].! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:30'! monthAbbreviation ^ self monthName copyFrom: 1 to: 3 ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:30'! monthName ^ Month nameOfMonth: self month ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:30'! offset ^ offset ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 21:09'! offset: anOffset "Answer a equivalent to the receiver but with its local time being offset from UTC by offset." ^ self class basicNew ticks: self ticks offset: anOffset asDuration; yourself ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:31'! second ^ (Duration seconds: seconds) seconds ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 9/4/2003 06:42'! timeZoneAbbreviation ^ self class localTimeZone abbreviation ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 9/4/2003 06:42'! timeZoneName ^ self class localTimeZone name ! ! !DateAndTime methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 21:05'! year ^ self dayMonthYearDo: [ :d :m :y | y ]! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 8/23/2003 21:03'! asSeconds "Return the number of seconds since the Squeak epoch" ^ (self - (self class epoch)) asSeconds ! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 17:53'! day ^ self dayOfYear! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 5/13/2003 07:48'! daysInMonth "Answer the number of days in the month represented by the receiver." ^ self asMonth daysInMonth ! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 5/13/2003 07:48'! daysInYear "Answer the number of days in the year represented by the receiver." ^ self asYear daysInYear ! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 15:44'! daysLeftInYear "Answer the number of days in the year after the date of the receiver." ^ self daysInYear - self dayOfYear ! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 7/27/2003 15:44'! firstDayOfMonth ^ self asMonth start day! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 18:30'! hours ^ self hour! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 1/7/2004 15:45'! minutes ^ self minute! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 5/13/2003 07:50'! monthIndex ^ self month ! ! !DateAndTime methodsFor: 'smalltalk-80' stamp: 'brp 7/1/2003 18:31'! seconds ^ self second! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 23:56'! asDate ^ Date starting: self ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:46'! asDateAndTime ^ self ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:47'! asDuration "Answer the duration since midnight" ^ Duration seconds: seconds nanoSeconds: nanos ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:47'! asMonth ^ Month starting: self ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:45'! asNanoSeconds "Answer the number of nanoseconds since midnight" ^ self asDuration asNanoSeconds ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 8/24/2003 00:00'! asTime ^ Time seconds: seconds nanoSeconds: nanos! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 8/24/2003 00:02'! asTimeStamp ^ self as: TimeStamp! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:47'! asWeek ^ Week starting: self ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:47'! asYear ^ Year starting: self ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:47'! dayMonthYearDo: aBlock "Evaluation the block with three arguments: day month, year." | l n i j dd mm yyyy | l := jdn + 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. ^ aBlock value: dd value: mm value: yyyy.! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:49'! duration ^ Duration zero ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:49'! julianDayNumber ^ jdn ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:49'! middleOf: aDuration "Return a Timespan where the receiver is the middle of the Duration" | duration | duration _ aDuration asDuration. ^ Timespan starting: (self - (duration / 2)) duration: duration. ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:48'! midnight "Answer a DateAndTime starting at midnight local time" ^ self dayMonthYearDo: [ :d :m :y | self class year: y month: m day: d ]! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:50'! nanoSecond ^ nanos ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:49'! noon "Answer a DateAndTime starting at noon" ^ self dayMonthYearDo: [ :d :m :y | self class year: y month: m day: d hour: 12 minute: 0 second: 0 ]! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'nk 3/12/2004 10:03'! printHMSOn: aStream "Print just hh:mm:ss" aStream nextPutAll: (self hour asString padded: #left to: 2 with: $0); nextPut: $:; nextPutAll: (self minute asString padded: #left to: 2 with: $0); nextPut: $:; nextPutAll: (self second asString padded: #left to: 2 with: $0). ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'nk 3/12/2004 10:38'! printOn: aStream "Print as per ISO 8601 sections 5.3.3 and 5.4.1. Prints either: 'YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for positive years) or '-YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for negative years)" ^self printOn: aStream withLeadingSpace: false ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'dtl 10/31/2004 01:20'! printOn: aStream withLeadingSpace: printLeadingSpaceToo "Print as per ISO 8601 sections 5.3.3 and 5.4.1. If printLeadingSpaceToo is false, prints either: 'YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for positive years) or '-YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for negative years) If printLeadingSpaceToo is true, prints either: ' YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for positive years) or '-YYYY-MM-DDThh:mm:ss.s+ZZ:zz:z' (for negative years) " self printYMDOn: aStream withLeadingSpace: printLeadingSpaceToo. aStream nextPut: $T. self printHMSOn: aStream. self nanoSecond ~= 0 ifTrue: [ | z ps | ps := self nanoSecond printString padded: #left to: 9 with: $0. z := ps findLast: [ :c | c asciiValue > $0 asciiValue ]. (z > 0) ifTrue: [aStream nextPut: $.]. ps from: 1 to: z do: [ :c | aStream nextPut: c ] ]. aStream nextPut: (offset positive ifTrue: [$+] ifFalse: [$-]); nextPutAll: (offset hours abs asString padded: #left to: 2 with: $0); nextPut: $:; nextPutAll: (offset minutes abs asString padded: #left to: 2 with: $0). offset seconds = 0 ifFalse: [ aStream nextPut: $:; nextPutAll: (offset seconds abs truncated asString) ]. ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'nk 3/12/2004 10:29'! printYMDOn: aStream "Print just YYYY-MM-DD part. If the year is negative, prints out '-YYYY-MM-DD'." ^self printYMDOn: aStream withLeadingSpace: false. ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'nk 3/12/2004 10:29'! printYMDOn: aStream withLeadingSpace: printLeadingSpaceToo "Print just the year, month, and day on aStream. If printLeadingSpaceToo is true, then print as: ' YYYY-MM-DD' (if the year is positive) or '-YYYY-MM-DD' (if the year is negative) otherwise print as: 'YYYY-MM-DD' or '-YYYY-MM-DD' " | year month day | self dayMonthYearDo: [ :d :m :y | year := y. month := m. day := d ]. year negative ifTrue: [ aStream nextPut: $- ] ifFalse: [ printLeadingSpaceToo ifTrue: [ aStream space ]]. aStream nextPutAll: (year abs asString padded: #left to: 4 with: $0); nextPut: $-; nextPutAll: (month asString padded: #left to: 2 with: $0); nextPut: $-; nextPutAll: (day asString padded: #left to: 2 with: $0) ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:50'! to: anEnd "Answer a Timespan. anEnd conforms to protocol DateAndTime or protocol Timespan" ^ Timespan starting: self ending: (anEnd asDateAndTime). ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 15:57'! to: anEnd by: aDuration "Answer a Timespan. anEnd conforms to protocol DateAndTime or protocol Timespan" ^ (Schedule starting: self ending: (anEnd asDateAndTime)) schedule: (Array with: aDuration asDuration); yourself. ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 16:01'! to: anEnd by: aDuration do: aBlock "Answer a Timespan. anEnd conforms to protocol DateAndTime or protocol Timespan" ^ (self to: anEnd by: aDuration) scheduleDo: aBlock ! ! !DateAndTime methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:37'! utcOffset: anOffset "Answer a equivalent to the receiver but offset from UTC by anOffset" | equiv | equiv _ self + (anOffset asDuration - self offset). ^ equiv ticks: (equiv ticks) offset: anOffset asDuration; yourself ! ! !DateAndTime methodsFor: 'private' stamp: 'brp 7/28/2004 16:22'! hasEqualTicks: aDateAndTime ^ (jdn = aDateAndTime julianDayNumber) and: [ (seconds = aDateAndTime secondsSinceMidnight) and: [ nanos = aDateAndTime nanoSecond ] ] ! ! !DateAndTime methodsFor: 'private' stamp: 'brp 7/28/2004 16:20'! secondsSinceMidnight ^ seconds! ! !DateAndTime methodsFor: 'private' stamp: 'brp 8/23/2003 15:45'! ticks "Private - answer an array with our instance variables. Assumed to be UTC " ^ Array with: jdn with: seconds with: nanos .! ! !DateAndTime methodsFor: 'private' stamp: 'nk 3/30/2004 09:38'! ticks: ticks offset: utcOffset "ticks is {julianDayNumber. secondCount. nanoSeconds}" | normalize | normalize := [ :i :base | | tick div quo rem | tick := ticks at: i. div := tick digitDiv: base neg: tick negative. quo := div first normalize. rem := div second normalize. rem < 0 ifTrue: [ quo := quo - 1. rem := base + rem ]. ticks at: (i-1) put: ((ticks at: i-1) + quo). ticks at: i put: rem ]. normalize value: 3 value: NanosInSecond. normalize value: 2 value: SecondsInDay. jdn _ ticks first. seconds _ ticks second. nanos := ticks third. offset := utcOffset. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DateAndTime class instanceVariableNames: ''! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:32'! clockPrecision "One nanosecond precision" ^ Duration nanoSeconds: 1 ! ! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'avi 2/21/2004 19:03'! now ^ self basicNew ticks: (Duration days: SqueakEpoch hours: 0 minutes: 0 seconds: self totalSeconds nanoSeconds: 0) ticks offset: self localOffset; yourself ! ! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'brp 7/27/2003 15:25'! year: year day: dayOfYear hour: hour minute: minute second: second ^ self year: year day: dayOfYear hour: hour minute: minute second: second offset: self localOffset. ! ! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'brp 7/27/2003 15:28'! year: year day: dayOfYear hour: hour minute: minute second: second offset: offset "Return a DataAndTime" | y d | y _ self year: year month: 1 day: 1 hour: hour minute: minute second: second nanoSecond: 0 offset: offset. d _ Duration days: (dayOfYear - 1). ^ y + d! ! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 21:00'! year: year month: month day: day hour: hour minute: minute second: second "Return a DateAndTime" ^ self year: year month: month day: day hour: hour minute: minute second: second offset: self localOffset ! ! !DateAndTime class methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:36'! year: year month: month day: day hour: hour minute: minute second: second offset: offset ^ self year: year month: month day: day hour: hour minute: minute second: second nanoSecond: 0 offset: offset ! ! !DateAndTime class methodsFor: 'smalltalk-80' stamp: 'brp` 8/24/2003 19:09'! fromSeconds: seconds "Answer a DateAndTime since the Squeak epoch: 1 January 1901" | since | since _ Duration days: SqueakEpoch hours: 0 minutes: 0 seconds: seconds. ^ self basicNew ticks: since ticks offset: self localOffset; yourself. ! ! !DateAndTime class methodsFor: 'smalltalk-80' stamp: 'brp 8/24/2003 00:00'! millisecondClockValue ^ Time millisecondClockValue! ! !DateAndTime class methodsFor: 'smalltalk-80' stamp: 'brp 8/24/2003 00:01'! totalSeconds ^ Time totalSeconds! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:36'! current ^ self now ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 16:12'! date: aDate time: aTime ^ self year: aDate year day: aDate dayOfYear hour: aTime hour minute: aTime minute second: aTime second ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp` 8/24/2003 19:11'! epoch "Answer a DateAndTime representing the Squeak epoch: 1 January 1901" ^ self julianDayNumber: SqueakEpoch ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 07:36'! fromString: aString ^ self readFrom: (ReadStream on: aString) ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 21:08'! julianDayNumber: aJulianDayNumber ^ self basicNew ticks: aJulianDayNumber days ticks offset: self localOffset; yourself ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 9/4/2003 06:40'! localOffset "Answer the duration we are offset from UTC" ^ self localTimeZone offset ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 9/4/2003 06:39'! localTimeZone "Answer the local time zone" ^ LocalTimeZone ifNil: [ LocalTimeZone _ TimeZone default ] ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'nk 3/30/2004 09:53'! localTimeZone: aTimeZone "Set the local time zone" " DateAndTime localTimeZone: (TimeZone offset: 0 hours name: 'Universal Time' abbreviation: 'UTC'). DateAndTime localTimeZone: (TimeZone offset: -8 hours name: 'Pacific Standard Time' abbreviation: 'PST'). " LocalTimeZone := aTimeZone ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 17:09'! midnight ^ self now midnight ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:57'! new "Answer a DateAndTime representing the Squeak epoch: 1 January 1901" ^ self epoch ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 17:09'! noon ^ self now noon! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:58'! readFrom: aStream | bc year month day hour minute second nanos offset buffer ch | aStream peek = $- ifTrue: [ aStream next. bc _ -1] ifFalse: [bc _ 1]. year _ (aStream upTo: $-) asInteger * bc. month _ (aStream upTo: $-) asInteger. day _ (aStream upTo: $T) asInteger. hour _ (aStream upTo: $:) asInteger. buffer _ '00:'. ch _ nil. minute _ WriteStream on: buffer. [ aStream atEnd | (ch = $:) | (ch = $+) | (ch = $-) ] whileFalse: [ ch _ minute nextPut: aStream next. ]. (ch isNil or: [ch isDigit]) ifTrue: [ ch _ $: ]. minute _ ((ReadStream on: buffer) upTo: ch) asInteger. buffer _ '00.'. second _ WriteStream on: buffer. [ aStream atEnd | (ch = $.) | (ch = $+) | (ch = $-) ] whileFalse: [ ch _ second nextPut: aStream next. ]. (ch isNil or: [ch isDigit]) ifTrue: [ ch _ $. ]. second _ ((ReadStream on: buffer) upTo: ch) asInteger. buffer _ '00000000+'. nanos _ WriteStream on: buffer. [ aStream atEnd | (ch = $+) | (ch = $-) ] whileFalse: [ ch _ nanos nextPut: aStream next. ]. (ch isNil or: [ch isDigit]) ifTrue: [ ch _ $+ ]. nanos _ ((ReadStream on: buffer) upTo: ch) asInteger. aStream atEnd ifTrue: [ offset _ self localOffset ] ifFalse: [offset _ Duration fromString: (ch asString, '0:', aStream upToEnd). (offset = self localOffset) ifTrue: [ offset _ self localOffset ]]. ^ self year: year month: month day: day hour: hour minute: minute second: second nanoSecond: nanos offset: offset. " '-1199-01-05T20:33:14.321-05:00' asDateAndTime ' 2002-05-16T17:20:45.00000001+01:01' asDateAndTime ' 2002-05-16T17:20:45.00000001' asDateAndTime ' 2002-05-16T17:20' asDateAndTime ' 2002-05-16T17:20:45' asDateAndTime ' 2002-05-16T17:20:45+01:57' asDateAndTime ' 2002-05-16T17:20:45-02:34' asDateAndTime ' 2002-05-16T17:20:45+00:00' asDateAndTime ' 1997-04-26T01:02:03+01:02:3' asDateAndTime " ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 17:09'! today ^ self midnight ! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/24/2003 12:19'! tomorrow ^ self today asDate next asDateAndTime! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:53'! year: year day: dayOfYear "Return a DateAndTime" ^ self year: year day: dayOfYear hour: 0 minute: 0 second: 0! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:54'! year: year month: month day: day "Return a DateAndTime, midnight local time" ^ self year: year month: month day: day hour: 0 minute: 0! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 20:54'! year: year month: month day: day hour: hour minute: minute "Return a DateAndTime" ^ self year: year month: month day: day hour: hour minute: minute second: 0! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'bvs 9/29/2004 16:43'! year: year month: month day: day hour: hour minute: minute second: second nanoSecond: nanoCount offset: offset "Return a DateAndTime" | monthIndex daysInMonth p q r s julianDayNumber since | monthIndex _ month isInteger ifTrue: [month] ifFalse: [Month indexOfMonth: month]. daysInMonth _ Month daysInMonth: monthIndex forYear: year. day < 1 ifTrue: [self error: 'day may not be zero or negative']. day > daysInMonth ifTrue: [self error: 'day is after month ends']. p _ (monthIndex - 14) quo: 12. q _ year + 4800 + p. r _ monthIndex - 2 - (12 * p). s _ (year + 4900 + p) quo: 100. julianDayNumber _ ( (1461 * q) quo: 4 ) + ( (367 * r) quo: 12 ) - ( (3 * s) quo: 4 ) + ( day - 32075 ). since _ Duration days: julianDayNumber hours: hour minutes: minute seconds: second nanoSeconds: nanoCount. ^ self basicNew ticks: since ticks offset: offset; yourself.! ! !DateAndTime class methodsFor: 'squeak protocol' stamp: 'brp 8/24/2003 12:19'! yesterday ^ self today asDate previous asDateAndTime ! ! TestCase subclass: #DateAndTimeEpochTest instanceVariableNames: 'aDateAndTime aDuration aTimeZone localTimeZoneToRestore' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Chronology'! !DateAndTimeEpochTest commentStamp: 'tlk 1/6/2004 18:27' prior: 0! I represent one of several Sunit test Cases intentended to provide complete coverage for the Chronology set of classes as part of the external testing. The other Chronology sunit test cases are: DateTestCase DateAndTimeLeapTestCase, DurationTestCase, ScheduleTestCase TimeStampTestCase TimespanDoTestCase, TimespanDoSpanAYearTestCase, TimespanTestCase, YearMonthWeekTestCase. These tests attempt to exercise all public and private methods. Except, they do not explicitly depreciated methods. tlk My fixtures are: aDateAndTime = January 01, 1901 midnight (the start of the Squeak epoch) with localTimeZone = Grenwhich Meridian (local offset = 0 hours) aDuration = 1 day, 2 hours, 3, minutes, 4 seconds and 5 nano seconds. aTimeZone = 'Epoch Test Time Zone', 'ETZ' , offset: 12 hours, 15 minutes. ! !DateAndTimeEpochTest methodsFor: 'running' stamp: 'tlk 1/2/2004 10:58'! setUp localTimeZoneToRestore := DateAndTime localTimeZone. aDateAndTime := DateAndTime localTimeZone: TimeZone default; epoch. aTimeZone := TimeZone offset: (Duration minutes: 135) name: 'Epoch Test Time Zone' abbreviation: 'ETZ'. aDuration := Duration days: 1 hours: 2 minutes: 3 seconds: 4 nanoSeconds: 5 ! ! !DateAndTimeEpochTest methodsFor: 'running' stamp: 'tlk 1/2/2004 11:04'! tearDown DateAndTime localTimeZone: localTimeZoneToRestore. "wish I could remove the time zones I added earlier, tut there is no method for that" ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:01'! testAsDate self assert: aDateAndTime asDate = 'January 1, 1901' asDate. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 13:31'! testAsDateAndTime self assert: aDateAndTime asDateAndTime = aDateAndTime ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 13:34'! testAsDuration self assert: aDateAndTime asDuration = 0 asDuration ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 11:06'! testAsLocal self assert: aDateAndTime asLocal = aDateAndTime. self assert: aDateAndTime asLocal = (aDateAndTime utcOffset: aDateAndTime class localOffset) ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 09:27'! testAsMonth self assert: aDateAndTime asMonth = (Month month: 'January' year: 1901). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 13:59'! testAsNanoSeconds self assert: aDateAndTime asNanoSeconds = 0 asDuration asNanoSeconds ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 14:01'! testAsSeconds self assert: aDateAndTime asSeconds = 0 asDuration asSeconds ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 09:32'! testAsTime self assert: aDateAndTime asTime = Time midnight. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 14:51'! testAsTimeStamp self assert: aDateAndTime asTimeStamp = TimeStamp new. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 11:07'! testAsUTC self assert: aDateAndTime asUTC = aDateAndTime ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'brp 1/16/2004 13:43'! testAsWeek self assert: aDateAndTime asWeek = (Week starting: '12-31-1900' asDate). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 09:43'! testAsYear self assert: aDateAndTime asYear = (Year starting: '01-01-1901' asDate). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:28'! testCurrent self deny: aDateAndTime = (DateAndTime current). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 09:46'! testDateTime self assert: aDateAndTime = (DateAndTime date: '01-01-1901' asDate time: '00:00:00' asTime) ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:01'! testDay self assert: aDateAndTime day = DateAndTime new day ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 11:08'! testDayMonthYearDo |iterations| iterations := 0. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | iterations := iterations + 1]) = 1. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachYear]) = 1901. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachMonth]) = 1. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachDay]) = 1. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 15:45'! testDayOfMonth self assert: aDateAndTime dayOfMonth = 1. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 10:47'! testDayOfWeek self assert: aDateAndTime dayOfWeek = 3. self assert: aDateAndTime dayOfWeekAbbreviation = 'Tue'. self assert: aDateAndTime dayOfWeekName = 'Tuesday'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:01'! testDayOfYear self assert: aDateAndTime dayOfYear = 1. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:02'! testDaysInMonth self assert: aDateAndTime daysInMonth = 31. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:02'! testDaysInYear self assert: aDateAndTime daysInYear = 365. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:02'! testDaysLeftInYear self assert: aDateAndTime daysLeftInYear = 364. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 16:24'! testDuration self assert: aDateAndTime duration = 0 asDuration. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:25'! testEpoch self assert: aDateAndTime = '1901-01-01T00:00:00+00:00'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 10:44'! testFirstDayOfMonth self assert: aDateAndTime firstDayOfMonth = 1 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:25'! testFromSeconds self assert: aDateAndTime = (DateAndTime fromSeconds: 0). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:26'! testFromString self assert: aDateAndTime = (DateAndTime fromString: ' 1901-01-01T00:00:00+00:00'). self assert: aDateAndTime = (DateAndTime fromString: ' 1901-01-01T00:00:00'). self assert: aDateAndTime = (DateAndTime fromString: ' 1901-01-01T00:00'). self assert: aDateAndTime = (DateAndTime fromString: ' 1901-01-01T00:00:00+00:00'). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 4/30/2006 22:20'! testHash self assert: aDateAndTime hash = DateAndTime new hash. self assert: aDateAndTime hash = 32161486 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 16:59'! testHour self assert: aDateAndTime hour = aDateAndTime hour24. self assert: aDateAndTime hour = 0. self assert: aDateAndTime hour = aDateAndTime hours ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'brp 3/12/2004 15:21'! testHour12 self assert: aDateAndTime hour12 = DateAndTime new hour12. self assert: aDateAndTime hour12 = 12 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:02'! testIsLeapYear self deny: aDateAndTime isLeapYear ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 17:18'! testJulianDayNumber self assert: aDateAndTime = (DateAndTime julianDayNumber: 2415386). self assert: aDateAndTime julianDayNumber = 2415386.! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 13:20'! testLessThan self assert: aDateAndTime < (aDateAndTime + '1:00:00:00'). self assert: aDateAndTime + -1 < aDateAndTime. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 10:40'! testMeridianAbbreviation self assert: aDateAndTime meridianAbbreviation = 'AM'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:37'! testMiddleOf self assert: (aDateAndTime middleOf: '2:00:00:00' asDuration) = (Timespan starting: '12-31-1900' asDate duration: 2 days). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 17:39'! testMidnight self assert: aDateAndTime midnight = aDateAndTime ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:03'! testMinus self assert: aDateAndTime - aDateAndTime = '0:00:00:00' asDuration. self assert: aDateAndTime - '0:00:00:00' asDuration = aDateAndTime. self assert: aDateAndTime - aDuration = (DateAndTime year: 1900 month: 12 day: 30 hour: 21 minute: 56 second: 55 nanoSecond: 999999995 offset: 0 hours ). " I believe this Failure is a bug in the nanosecond part of (DateAndTime >> year:month:day:hour:minute:second:nanoSecond:offset:)" ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 19:35'! testMinute self assert: aDateAndTime minute = 0 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'brp 1/16/2004 13:41'! testMinutes self assert: aDateAndTime minutes = 0 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 19:46'! testMonth self assert: aDateAndTime month = 1. self assert: aDateAndTime monthAbbreviation = 'Jan'. self assert: aDateAndTime monthName = 'January'. self assert: aDateAndTime monthIndex = 1.! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 19:47'! testNanoSecond self assert: aDateAndTime nanoSecond = 0 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:27'! testNew self assert: aDateAndTime = (DateAndTime new). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 19:49'! testNoon self assert: aDateAndTime noon = '1901-01-01T12:00:00+00:00'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:28'! testNow self deny: aDateAndTime = (DateAndTime now). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:41'! testOffset self assert: aDateAndTime offset = '0:00:00:00' asDuration. self assert: (aDateAndTime offset: '0:12:00:00') = '1901-01-01T00:00:00+12:00'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 11:03'! testPlus self assert: aDateAndTime + '0:00:00:00' = aDateAndTime. self assert: aDateAndTime + 0 = aDateAndTime. self assert: aDateAndTime + aDuration = (DateAndTime year: 1901 month: 1 day: 2 hour: 2 minute: 3 second: 4 nanoSecond: 5 offset: 0 hours ) " I believe this is a bug in the nanosecond part of (DateAndTime >> year:month:day:hour:minute:second:nanoSecond:offset:)" ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'nk 3/12/2004 10:16'! testPrintOn | cs rw | cs := ReadStream on: '1901-01-01T00:00:00+00:00'. rw := ReadWriteStream on: ''. aDateAndTime printOn: rw. self assert: rw contents = cs contents. cs := ReadStream on: 'a TimeZone(ETZ)'. rw := ReadWriteStream on: ''. aTimeZone printOn: rw. self assert: rw contents = cs contents! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:22'! testSecond self assert: aDateAndTime second = 0 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:22'! testSeconds self assert: aDateAndTime seconds = 0 ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:25'! testTicks self assert: aDateAndTime ticks = (DateAndTime julianDayNumber: 2415386) ticks. self assert: aDateAndTime ticks = #(2415386 0 0)! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:31'! testTicksOffset self assert: aDateAndTime = (aDateAndTime ticks: #(2415386 0 0) offset: DateAndTime localOffset). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:42'! testTo self assert: (aDateAndTime to: aDateAndTime) = (DateAndTime new to: DateAndTime new) "MessageNotUnderstood: UndefinedObject>>starting:ending: where UndefinedObject is Timespan "! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:43'! testToBy self assert: (aDateAndTime to: aDateAndTime + 10 days by: 5 days) = (DateAndTime new to: DateAndTime new + 10 days by: 5 days ) "MessageNotUnderstood: UndefinedObject>>starting:ending: where UndefinedObject is Timespan "! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:53'! testToByDo "self assert: (aDateAndTime to: aDateAndTime + 10 days by: 5 days do: []) = " "MessageNotUnderstood: UndefinedObject>>starting:ending: where UndefinedObject is Timespan "! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 17:35'! testToday self deny: aDateAndTime = (DateAndTime today). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:45'! testTommorrow self assert: (DateAndTime today + 24 hours) = (DateAndTime tomorrow). self deny: aDateAndTime = (DateAndTime tomorrow). "MessageNotUnderstood: Date class>>starting:"! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 20:58'! testUtcOffset self assert: (aDateAndTime utcOffset: '0:12:00:00') = '1901-01-01T12:00:00+12:00'. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 21:00'! testYear self assert: aDateAndTime year = 1901. ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:30'! testYearDay self assert: aDateAndTime = (DateAndTime year: 1901 day: 1). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:31'! testYearDayHourMinuteSecond self assert: aDateAndTime = (DateAndTime year: 1901 day: 1 hour: 0 minute: 0 second: 0). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:31'! testYearMonthDay self assert: aDateAndTime = (DateAndTime year: 1901 month: 1 day: 1). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/1/2004 12:31'! testYearMonthDayHourMinuteSecond self assert: aDateAndTime = (DateAndTime year: 1901 month: 1 day: 1 hour: 0 minute: 0 second: 0). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:23'! testYearMonthDayHourMinuteSecondNanosSecondOffset self assert: aDateAndTime = (DateAndTime year: 1901 month: 1 day: 1 hour: 0 minute: 0 second: 0 nanoSecond: 0 offset:0 hours ). self assert: ((DateAndTime year: 1 month: 1 day: 1 hour: 0 minute: 0 second: 0 nanoSecond: 0 offset: 0 hours ) + (Duration days: 1 hours: 2 minutes: 3 seconds: 4 nanoSeconds: 5) ) = (DateAndTime year: 1 month: 1 day: 2 hour: 2 minute: 3 second: 4 nanoSecond: 5 offset: 0 hours ) " I believe this is a bug in the nanosecond part of (DateAndTime >> year:month:day:hour:minute:second:nanoSecond:offset:)"" I believe this is a bug in the nanosecond part of (DateAndTime >> year:month:day:hour:minute:second:nanoSecond:offset:)" ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 09:47'! testYesterday self deny: aDateAndTime = (DateAndTime yesterday). ! ! !DateAndTimeEpochTest methodsFor: 'testing' stamp: 'nk 3/12/2004 11:26'! testtimeZone self assert: aDateAndTime timeZoneName = 'Universal Time'. self assert: aDateAndTime timeZoneAbbreviation = 'UTC' ! ! TestCase subclass: #DateAndTimeLeapTest instanceVariableNames: 'aDateAndTime aDuration aTimeZone localTimeZoneToRestore' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Chronology'! !DateAndTimeLeapTest commentStamp: 'tlk 1/6/2004 17:54' prior: 0! I represent one of several Sunit test Cases intentended to provide complete coverage for the Chronology set of classes as part of the external testing. tlk. My fixtures are: aDateAndTime = February 29, 2004 1:33 PM with offset: 2 hours aDuration = 15 days, 14 hours, 13 minutes, 12 seconds and 11 nano seconds. aTimeZone = Grenwhich Meridian (local offset = 0 hours) ! !DateAndTimeLeapTest methodsFor: 'running' stamp: 'nk 3/12/2004 11:00'! setUp localTimeZoneToRestore := DateAndTime localTimeZone. DateAndTime localTimeZone: TimeZone default. aDateAndTime := (DateAndTime year: 2004 month: 2 day: 29 hour: 13 minute: 33 second: 0 offset: 2 hours). aTimeZone := TimeZone default. aDuration := Duration days: 0 hours: 13 minutes: 33 seconds: 0 nanoSeconds: 0 ! ! !DateAndTimeLeapTest methodsFor: 'running' stamp: 'tlk 1/2/2004 21:30'! tearDown DateAndTime localTimeZone: localTimeZoneToRestore. "wish I could remove the time zones I added earlier, tut there is no method for that" ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:00'! testAsDate self assert: aDateAndTime asDate = 'February 29, 2004' asDate. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:55'! testAsDuration self assert: aDateAndTime asDuration = aDuration ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 14:00'! testAsLocal self assert: aDateAndTime asLocal = aDateAndTime. self assert: aDateAndTime asLocal = (aDateAndTime utcOffset: aDateAndTime class localOffset) ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:24'! testAsMonth self assert: aDateAndTime asMonth = (Month month: 'February' year: 2004). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:59'! testAsNanoSeconds self assert: aDateAndTime asNanoSeconds = aDuration asNanoSeconds. self assert: aDateAndTime asNanoSeconds = 48780000000000 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 22:05'! testAsSeconds self assert: aDuration asSeconds = 48780. self assert: aDateAndTime asSeconds = 3255507180 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:26'! testAsTime self assert: aDateAndTime asTime = (Time hour: 13 minute: 33 second: 0) ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:31'! testAsTimeStamp self assert: aDateAndTime asTimeStamp = ((TimeStamp readFrom: '2-29-2004 1:33 pm' readStream) offset: 2 hours). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:59'! testAsUTC self assert: aDateAndTime asUTC = aDateAndTime ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:30'! testAsWeek self assert: aDateAndTime asWeek = (Week starting: '02-29-2004' asDate). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 12:36'! testAsYear self assert: aDateAndTime asYear = (Year starting: '02-29-2004' asDate). self deny: aDateAndTime asYear = (Year starting: '01-01-2004' asDate) ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:23'! testDay self assert: aDateAndTime day = 60. self deny: aDateAndTime day = 29 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 22:16'! testDayMonthYearDo self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachYear]) = 2004. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachMonth]) = 2. self assert: (aDateAndTime dayMonthYearDo: [:eachDay :eachMonth :eachYear | eachDay]) = 29. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 22:17'! testDayOfMonth self assert: aDateAndTime dayOfMonth = 29. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:34'! testDayOfWeek self assert: aDateAndTime dayOfWeek = 1. self assert: aDateAndTime dayOfWeekAbbreviation = 'Sun'. self assert: aDateAndTime dayOfWeekName = 'Sunday'. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:59'! testDayOfYear self assert: aDateAndTime dayOfYear = 60. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:58'! testDaysInMonth self assert: aDateAndTime daysInMonth = 29. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:58'! testDaysInYear self assert: aDateAndTime daysInYear = 366. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:58'! testDaysLeftInYear self assert: aDateAndTime daysLeftInYear = 306. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:38'! testFirstDayOfMonth self deny: aDateAndTime firstDayOfMonth = 1. self assert: aDateAndTime firstDayOfMonth = 32 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 10:43'! testFromString self assert: aDateAndTime = (DateAndTime fromString: ' 2004-02-29T13:33:00+02:00'). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 4/30/2006 22:17'! testHash self assert: aDateAndTime hash = 131156085 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 10:48'! testHour self assert: aDateAndTime hour = aDateAndTime hour24. self assert: aDateAndTime hour = 13. self assert: aDateAndTime hour = aDateAndTime hours ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'brp 3/12/2004 15:19'! testHour12 self assert: aDateAndTime hour12 = 1. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:35'! testIsLeapYear self assert: aDateAndTime isLeapYear ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:30'! testLessThan self assert: aDateAndTime < (aDateAndTime + '1:00:00:00'). self assert: aDateAndTime + -1 < aDateAndTime. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 10:42'! testMeridianAbbreviation self assert: aDateAndTime meridianAbbreviation = 'PM'. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:12'! testMiddleOf self assert: (aDateAndTime middleOf: aDuration) = (Timespan starting: (DateAndTime year: 2004 month: 2 day: 29 hour: 6 minute: 46 second: 30 offset: 2 hours) duration: (Duration days: 0 hours: 13 minutes: 33 seconds: 0 nanoSeconds: 0 )) ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:57'! testMidnight self assert: aDateAndTime midnight = '2004-02-29T00:00:00+00:00'. self deny: aDateAndTime midnight = '2004-02-29T00:00:00+02:00' ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:00'! testMinute self assert: aDateAndTime minute = 33 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'brp 1/16/2004 13:44'! testMinutes self assert: aDateAndTime minutes = 33 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:02'! testMonth self assert: aDateAndTime month = 2. self assert: aDateAndTime monthAbbreviation = 'Feb'. self assert: aDateAndTime monthName = 'February'. self assert: aDateAndTime monthIndex = 2.! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:30'! testNanoSecond self assert: aDateAndTime nanoSecond = 0 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:03'! testNoon self assert: aDateAndTime noon = '2004-02-29T12:00:00+00:00'. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:07'! testOffset self assert: aDateAndTime offset = '0:02:00:00' asDuration. self assert: (aDateAndTime offset: '0:12:00:00') = '2004-02-29T13:33:00+12:00'. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'nk 3/12/2004 11:27'! testPrintOn | cs rw | cs := ReadStream on: '2004-02-29T13:33:00+02:00'. rw := ReadWriteStream on: ''. aDateAndTime printOn: rw. self assert: rw contents = cs contents. cs := ReadStream on: 'a TimeZone(UTC)'. rw := ReadWriteStream on: ''. aTimeZone printOn: rw. self assert: rw contents = cs contents ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:30'! testSecond self assert: aDateAndTime second = 0 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/2/2004 21:30'! testSeconds self assert: aDateAndTime seconds = 0 ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:12'! testTicks self assert: aDateAndTime ticks = ((DateAndTime julianDayNumber: 2453065) + 48780 seconds) ticks. self assert: aDateAndTime ticks = #(2453065 48780 0)! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:52'! testTicksOffset self assert: aDateAndTime = (aDateAndTime ticks: #(2453065 48780 0) offset: DateAndTime localOffset). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/4/2004 13:51'! testUtcOffset self assert: (aDateAndTime utcOffset: '0:02:00:00') = '2004-02-29T13:33:00+02:00'. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:17'! testYear self assert: aDateAndTime year = 2004. ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:21'! testYearDayHourMinuteSecond self assert: aDateAndTime = ((DateAndTime year: 2004 day: 60 hour: 13 minute: 33 second: 0) offset: 2 hours). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'tlk 1/3/2004 11:23'! testYearMonthDayHourMinuteSecond self assert: aDateAndTime = ((DateAndTime year: 2004 month: 2 day: 29 hour: 13 minute: 33 second: 0) offset: 2 hours). ! ! !DateAndTimeLeapTest methodsFor: 'testing' stamp: 'nk 3/12/2004 11:26'! testtimeZone self assert: aDateAndTime timeZoneName = 'Universal Time'. self assert: aDateAndTime timeZoneAbbreviation = 'UTC' ! ! ClassTestCase subclass: #DateAndTimeTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Chronology'! !DateAndTimeTest methodsFor: 'Coverage' stamp: 'brp 9/25/2003 09:25'! classToBeTested ^ DateAndTime ! ! !DateAndTimeTest methodsFor: 'Coverage' stamp: 'brp 9/25/2003 09:25'! selectorsToBeIgnored | private | private := #( #printOn: ). ^ super selectorsToBeIgnored, private ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'brp 1/7/2004 17:00'! testArithmeticAcrossDateBoundary | t1 t2 | t1 _ '2004-01-07T11:55:00+00:00' asDateAndTime. t2 _ t1 - ( (42900+1) seconds). self assert: t2 = ('2004-01-06T23:59:59+00:00' asDateAndTime) ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'dtl 11/7/2004 13:00'! testDateTimeDenotation1 "DateAndTimeTest new testDateTimeDenotation1" " Detroit is 5 hours behind UTC, this offset to UTC is therefore written with a minus sign. This example tests the correct interpretation of the DateAndTime denotation. " | twoPmInLondon twoPmUTCInLocalTimeOfDetroit nineAmInDetroit | twoPmInLondon := DateAndTime year: 2004 month: 11 day: 2 hour: 14 minute: 0 second: 0 offset: 0 hours. twoPmUTCInLocalTimeOfDetroit _ twoPmInLondon utcOffset: -5 hours. nineAmInDetroit _ '2004-11-02T09:00:00-05:00' asDateAndTime. self assert: twoPmUTCInLocalTimeOfDetroit = nineAmInDetroit. ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'dtl 11/7/2004 13:01'! testDateTimeDenotation2 "DateAndTimeTest new testDateTimeDenotation2" " Moscow is 3 hours ahead UTC, this offset to UTC is therefore positive. This example tests the correct interpretation of the DateAndTime denotation. " | lateEveningInLondon lateEveningInLocalTimeOfMoscow localMoscowTimeFromDenotation | lateEveningInLondon := DateAndTime year: 2004 month: 11 day: 30 hour: 23 minute: 30 second: 0 offset: 0 hours. lateEveningInLocalTimeOfMoscow _ lateEveningInLondon utcOffset: 3 hours. localMoscowTimeFromDenotation _ '2004-12-01T02:30:00+03:00' asDateAndTime. self assert: lateEveningInLocalTimeOfMoscow = localMoscowTimeFromDenotation. ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'bvs 9/29/2004 16:22'! testErrorWhenDayIsAfterMonthEnd self should: [DateAndTime year: 2004 month: 2 day: 30] raise: Error. self shouldnt: [DateAndTime year: 2004 month: 2 day: 29] raise: Error. ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'bvs 9/29/2004 16:29'! testErrorWhenDayIsBeforeMonthStart self should: [DateAndTime year: 2004 month: 2 day: -1] raise: Error. self should: [DateAndTime year: 2004 month: 2 day: 0] raise: Error. self shouldnt: [DateAndTime year: 2004 month: 2 day: 1] raise: Error. ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'brp 1/7/2004 15:37'! testInstanceCreation | t | t _ DateAndTime year: 1 month: 1 day: 2 hour: 2 minute: 3 second: 4 nanoSecond: 5 offset: 6 hours. self assert: (t julianDayNumber = 1721427); assert: (t offset = 6 hours); assert: (t hour = 2); assert: (t minute = 3); assert: (t second = 4); assert: (t nanoSecond = 5). ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'nk 3/12/2004 11:06'! testMonotonicity | t1 t2 t3 t4 | t1 := DateAndTime now. t2 := DateAndTime now. (Delay forMilliseconds: 1000) wait. t3 := DateAndTime now. t4 := DateAndTime now. self assert: ( t1 <= t2); assert: ( t2 < t3); assert: ( t3 <= t4). ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'dtl 11/5/2004 05:45'! testPrintString "(self new setTestSelector: #testPrintString) debug" | dt | dt _DateAndTime year: 2004 month: 11 day: 2 hour: 14 minute: 3 second: 5 nanoSecond: 12345 offset: (Duration seconds: (5 * 3600)). self assert: dt printString = '2004-11-02T14:03:05.000012345+05:00' ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'brp 1/7/2004 15:43'! testSmalltalk80Accessors | t | t _ DateAndTime year: 1 month: 1 day: 2 hour: 2 minute: 3 second: 4 nanoSecond: 5 offset: 6 hours. self assert: (t hours = t hours); assert: (t minutes = t minute); assert: (t seconds = t second). ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'BG 11/7/2004 12:18'! testTimeZoneEquivalence "DateAndTimeTest new testTimeZoneEquivalence" "When the clock on the wall in Detroit says 9:00am, the clock on the wall in London says 2:00pm. The Duration difference between the corresponding DateAndTime values should be zero." " Detroit is 5 hours behind UTC, this offset to UTC is therefore written with a minus sign. This example tests both the correct interpretation of the DateAndTime denotation and correct DateAndTime arithmetics. " | twoPmInLondon nineAmInDetroit durationDifference | twoPmInLondon _ '2004-11-02T14:00:00+00:00' asDateAndTime. nineAmInDetroit _ '2004-11-02T09:00:00-05:00' asDateAndTime. durationDifference _ twoPmInLondon - nineAmInDetroit. self assert: durationDifference asSeconds = 0. self assert: twoPmInLondon = nineAmInDetroit ! ! !DateAndTimeTest methodsFor: 'Tests' stamp: 'BG 11/7/2004 12:17'! testTimeZoneEquivalence2 "DateAndTimeTest new testTimeZoneEquivalence2" "This example demonstates the fact that 2004-05-24T22:40:00 UTC is 2004-05-25T01:40:00 in Moscow (Moscow is 3 hours ahead of UTC) " | thisMoment thisMomentInMoscow | thisMoment := DateAndTime year: 2004 month: 5 day: 24 hour: 22 minute: 40. thisMomentInMoscow := thisMoment utcOffset: 3 hours. self assert: (thisMoment - thisMomentInMoscow) asSeconds = 0. self assert: thisMoment = thisMomentInMoscow ! ! ClassTestCase subclass: #DateTest instanceVariableNames: 'date aDate aTime' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Chronology'! !DateTest commentStamp: 'brp 7/26/2003 16:58' prior: 0! This is the unit test for the class Date. ! !DateTest methodsFor: 'Coverage' stamp: 'brp 7/27/2003 13:01'! classToBeTested ^ self dateClass! ! !DateTest methodsFor: 'Coverage' stamp: 'brp 1/30/2005 09:03'! selectorsToBeIgnored | deprecated private special | deprecated := #(). private := #(). special := #( #< #= #new #next #previous #printOn: #printOn:format: #storeOn: #fromString: ). ^ super selectorsToBeIgnored, deprecated, private, special! ! !DateTest methodsFor: 'Running' stamp: 'brp 1/21/2004 18:46'! setUp date := self dateClass newDay: 153 year: 1973. "2 June 1973" aDate := Date readFrom: '01-23-2004' readStream. aTime := Time readFrom: '12:34:56 pm' readStream! ! !DateTest methodsFor: 'Tests' stamp: 'brp 8/23/2003 16:07'! testAccessing self assert: date day = 153; assert: date julianDayNumber = 2441836; assert: date leap = 0; assert: date monthIndex = 6; assert: date monthName = #June; assert: date weekday = #Saturday; assert: date weekdayIndex = 7; assert: date year = 1973. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:10'! testArithmetic | d | d := date addDays: 32. "4 July 1973" self assert: d year = 1973; assert: d monthIndex = 7; assert: d dayOfMonth = 4. self assert: (d subtractDate: date) = 32; assert: (date subtractDate: d) = -32. self assert: (d subtractDays: 32) = date. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:54'! testComparing | d1 d2 d3 | d1 := self dateClass newDay: 2 month: #June year: 1973. d2 := self dateClass newDay: 97 year: 2003. "7 April 2003" d3 := self dateClass newDay: 250 year: 1865. "7 September 1865" self assert: date = d1; assert: date = date copy; assert: date hash = d1 hash. self assert: date < d2; deny: date < d3. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:15'! testConverting self assert: date asDate = date; assert: '2 June 1973' asDate = date; assert: date asSeconds = 2285280000. date dayMonthYearDo: [ :d :m :y | self assert: d = 2; assert: m = 6; assert: y = 1973 ].! ! !DateTest methodsFor: 'Tests' stamp: 'brp 1/21/2004 18:47'! testFromDays | epoch d0 d1 d2 | epoch := self dateClass newDay: 1 year: 1901. d0 := self dateClass fromDays: 0. "1 January 1901" self assert: d0 = epoch. d1 := self dateClass fromDays: 26450. "2 June 1973" self assert: d1 = date. d2 := self dateClass fromDays: -100000. "18 March 1627" self assert: d2 julianDayNumber = 2315386. self assert: aDate = (Date fromDays: 37642). self assert: aDate = (Date fromDays: 103*365 + 22 + 25 "leap days") . ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:17'! testFromSeconds | d | d := self dateClass fromSeconds: 2285280000. self assert: d = date. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 1/7/2004 16:37'! testGeneralInquiries | shuffled indices names now | shuffled := #(#January #February #March #April #May #June #July #August #September #October #November #December) shuffled. indices := shuffled collect: [ :m | self dateClass indexOfMonth: m ]. names := indices collect: [ :i | self dateClass nameOfMonth: i ]. self assert: names = shuffled. shuffled := #(#Monday #Tuesday #Wednesday #Thursday #Friday #Saturday #Sunday) shuffled. indices := shuffled collect: [ :m | self dateClass dayOfWeek: m ]. names := indices collect: [ :i | self dateClass nameOfDay: i ]. self assert: names = shuffled. now := self dateClass dateAndTimeNow. self assert: now size = 2; assert: now first = self dateClass today. self assert: (self dateClass firstWeekdayOfMonth: #June year: 1973) = 6. self assert: (self dateClass leapYear: 1973) = 0; assert: (self dateClass leapYear: 1972) = 1; assert: (self dateClass daysInYear: 1973) = 365; assert: (self dateClass daysInYear: 1972) = 366; assert: (self dateClass daysInMonth: #February forYear: 1973) = 28; assert: (self dateClass daysInMonth: #February forYear: 1972) = 29. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:17'! testInitialization self should: [ self dateClass initialize. true ]. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:18'! testInquiries self assert: date dayOfMonth = 2; assert: date dayOfYear = 153; assert: date daysInMonth = 30; assert: date daysInYear = 365; assert: date daysLeftInYear = (365 - 153); assert: date firstDayOfMonth = 152. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:05'! testNew | epoch | epoch := self dateClass newDay: 1 year: 1901. self assert: (self dateClass new = epoch).! ! !DateTest methodsFor: 'Tests' stamp: 'brp 1/9/2004 16:33'! testPreviousNext | n p pt ps | n := date next. p := date previous. self assert: n year = 1973; assert: n dayOfYear = 154; assert: p year = 1973; assert: p dayOfYear = 152. pt := date previous: #Thursday. "31 May 1973" self assert: pt year = 1973; assert: pt dayOfYear = 151. ps := date previous: #Saturday. " 26 May 1973" self assert: ps year = 1973; assert: ps dayOfYear = (153-7). ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:21'! testPrinting self assert: date mmddyyyy = '6/2/1973'; assert: date yyyymmdd = '1973-06-02'; assert: (date printFormat: #(3 1 2 $!! 2 1 1)) = '1973!!2!!Jun'. ! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:23'! testReadFrom | s1 s2 s3 s4 s5 | s1 := '2 June 1973'. s2 := '2-JUN-73'. s3 := 'June 2, 1973'. s4 := '6/2/73'. s5 := '2JUN73'. self assert: date = (self dateClass readFrom: s1 readStream); assert: date = (self dateClass readFrom: s2 readStream); assert: date = (self dateClass readFrom: s3 readStream); assert: date = (self dateClass readFrom: s4 readStream); assert: date = (self dateClass readFrom: s5 readStream).! ! !DateTest methodsFor: 'Tests' stamp: 'brp 7/27/2003 13:05'! testStoring self assert: date storeString = '''2 June 1973'' asDate'; assert: date = ('2 June 1973' asDate). ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testAddDays self assert: (aDate addDays: 00) yyyymmdd = '2004-01-23'. self assert: (aDate addDays: 30) yyyymmdd = '2004-02-22'. self assert: (aDate addDays: 60) yyyymmdd = '2004-03-23'. self assert: (aDate addDays: 90) yyyymmdd = '2004-04-22'. self assert: (aDate addDays:120) yyyymmdd = '2004-05-22'! ! !DateTest methodsFor: 'testing' stamp: 'tbn 7/11/2006 10:37'! testAddMonths self assert: (aDate addMonths: 0) yyyymmdd = '2004-01-23'. self assert: (aDate addMonths: 1) yyyymmdd = '2004-02-23'. self assert: (aDate addMonths: 2) yyyymmdd = '2004-03-23'. self assert: (aDate addMonths: 3) yyyymmdd = '2004-04-23'. self assert: (aDate addMonths: 12) yyyymmdd = '2005-01-23'. self assert: ((Date readFrom: '05-31-2017' readStream) addMonths: 1) yyyymmdd = '2017-06-30'. self assert: ((Date readFrom: '02-29-2000' readStream) addMonths: 12) yyyymmdd = '2001-02-28'! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testAsDate self assert: (aDate asDate) = aDate ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testAsSeconds self assert: (aDate asSeconds) = 3252268800. self assert: (aDate asSeconds) = ((103*365*24*60*60) + (22+25"leap days"*24*60*60)) . self assert: aDate = (Date fromSeconds: 3252268800).! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testDateAndTimeNow "Not a great test: could falsely fail if midnight come in between the two executions and doesnt catch time errors" self assert: Date dateAndTimeNow first = Date today ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testDayMonthYearDo self assert: (aDate dayMonthYearDo: [:day :month :year | day asString , month asString, year asString]) = '2312004' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testDaysInMonthForYear self assert: (Date daysInMonth: 'February' forYear: 2008) = 29. self assert: (Date daysInMonth: 'February' forYear: 2000) = 29. self assert: (Date daysInMonth: 'February' forYear: 2100) = 28. self assert: (Date daysInMonth: 'July' forYear: 2100) = 31. ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testDaysInYear self assert: (Date daysInYear: 2008) = 366. self assert: (Date daysInYear: 2000) = 366. self assert: (Date daysInYear: 2100) = 365 ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testDuration self assert: aDate duration = 24 hours! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testEqual self assert: aDate = (Date readFrom: (ReadStream on: 'January 23, 2004')).! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testFirstWeekdayOfMonthYear self assert: (Date firstWeekdayOfMonth: 'January' year: 2004) = 5. ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testIndexOfMonth self assert: (Date indexOfMonth: 'January') = 1. self assert: (Date indexOfMonth: 'December') = 12. ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testJulianDayNumber self assert: aDate = (Date julianDayNumber: ((4713+2004)*365 +1323) ). ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testLeap self assert: aDate leap = 1. ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testLeapNot self assert: (aDate addDays: 365) leap = 0 ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testLessThan self assert: aDate < (Date readFrom: (ReadStream on: '01-24-2004')).! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testMmddyyyy self assert: aDate mmddyyyy = '1/23/2004'! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testNameOfMonth self assert: (Date nameOfMonth: 5) = 'May'. self assert: (Date nameOfMonth: 8) = 'August' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testNewDayMonthYear self assert: aDate = (Date newDay: 23 month: 1 year: 2004) ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testNewDayYear self assert: aDate = (Date newDay: 23 year: 2004) ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testPreviousFriday self assert: (aDate previous: 'Friday') yyyymmdd = '2004-01-16' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testPreviousThursday self assert: (aDate previous: 'Thursday') yyyymmdd = '2004-01-22' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testPrintFormat self assert: (aDate printFormat: #(1 2 3 $? 2 2)) = '23?Jan?04'! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testPrintOn | cs rw | cs := ReadStream on: '23 January 2004'. rw := ReadWriteStream on: ''. aDate printOn: rw. self assert: rw contents = cs contents! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testPrintOnFormat | cs rw | cs := ReadStream on: '04*Jan*23'. rw := ReadWriteStream on: ''. aDate printOn: rw format: #(3 2 1 $* 2 2). self assert: rw contents = cs contents! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testStarting self assert: aDate = (Date starting: (DateAndTime fromString: '2004-01-23T12:12')). ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testStoreOn | cs rw | cs := ReadStream on: '''23 January 2004'' asDate'. rw := ReadWriteStream on: ''. aDate storeOn: rw. self assert: rw contents = cs contents! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testSubtractDate self assert: (aDate subtractDate:(aDate addDays: 30)) = -30. self assert: (aDate subtractDate:(aDate subtractDays: 00)) = 0. self assert: (aDate subtractDate:(aDate subtractDays: 30)) = 30. ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testSubtractDays self assert: (aDate subtractDays: 00) yyyymmdd = '2004-01-23'. self assert: (aDate subtractDays: 30) yyyymmdd = '2003-12-24'. self assert: (aDate subtractDays: 60) yyyymmdd = '2003-11-24' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testTomorrow "Not a great test: could falsely fail if midnight come in between the two executions and doesnt catch many errors" self assert: Date tomorrow > Date today ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testWeekday self assert: aDate weekday = 'Friday'. self assert: aDate weekdayIndex = 6. self assert: (Date dayOfWeek: aDate weekday ) =6. self assert: (Date nameOfDay: 6 ) = 'Friday' ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testYesterday "Not a great test: doesnt catch many errors" self assert: Date yesterday < Date today ! ! !DateTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:48'! testYyyymmdd self assert: aDate yyyymmdd = '2004-01-23'! ! !DateTest methodsFor: 'Private' stamp: 'brp 8/24/2003 00:10'! dateClass ^ Date! ! CodeHolder subclass: #Debugger instanceVariableNames: 'interruptedProcess interruptedController contextStack contextStackTop contextStackIndex contextStackList receiverInspector contextVariablesInspector externalInterrupt proceedValue selectingPC sourceMap tempNames savedCursor isolationHead failedProject errorWasInUIProcess labelString theMethodNode' 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: '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'! 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' stamp: 'hmm 7/16/2001 21:54'! labelString ^labelString! ! !Debugger methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:27'! labelString: aString labelString := aString. self changed: #relabel! ! !Debugger methodsFor: 'accessing'! proceedValue "Answer the value to return to the selected context when the interrupted process proceeds." ^proceedValue! ! !Debugger methodsFor: 'accessing' stamp: 'sd 11/20/2005 21:27'! 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: 'accessing' stamp: 'md 2/20/2006 18:52'! receiverInspectorObject: obj context: ctxt "set context before object so it can refer to context when building field list" receiverInspector context: ctxt. receiverInspector object: obj. ! ! !Debugger methodsFor: 'as yet unclassified' stamp: 'nk 8/6/2003 13:52'! codePaneMenu: aMenu shifted: shifted aMenu add: 'run to here' target: self selector: #runToSelection: argument: thisContext sender receiver selectionInterval. aMenu addLine. super codePaneMenu: aMenu shifted: shifted. ^aMenu.! ! !Debugger methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:27'! runToSelection: selectionInterval | currentContext | self pc first >= selectionInterval first ifTrue: [ ^self ]. currentContext := self selectedContext. [ currentContext == self selectedContext and: [ self pc first < selectionInterval first ] ] whileTrue: [ self doStep ].! ! !Debugger methodsFor: 'breakpoints' stamp: 'emm 5/30/2002 10:08'! toggleBreakOnEntry "Install or uninstall a halt-on-entry breakpoint" | selectedMethod | self selectedClassOrMetaClass isNil ifTrue:[^self]. selectedMethod := self selectedClassOrMetaClass >> self selectedMessageName. selectedMethod hasBreakpoint ifTrue: [BreakpointManager unInstall: selectedMethod] ifFalse: [BreakpointManager installInClass: self selectedClassOrMetaClass selector: self selectedMessageName].! ! !Debugger methodsFor: 'class list' stamp: 'md 2/17/2006 09:32'! selectedClass "Answer the class in which the currently selected context's method was found." ^self selectedContext methodClass! ! !Debugger methodsFor: 'class list' stamp: 'md 2/17/2006 09:29'! selectedClassOrMetaClass "Answer the class in which the currently selected context's method was found." ^self selectedClass! ! !Debugger methodsFor: 'code pane' stamp: 'tk 4/15/1998 18:31'! contentsSelection ^ self pcRange! ! !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: 'sd 11/20/2005 21:27'! pcRange "Answer the indices in the source code for the method corresponding to the selected context's program counter value." | i pc end | (selectingPC and: [contextStackIndex ~= 0]) ifFalse: [^1 to: 0]. sourceMap ifNil: [sourceMap := theMethodNode sourceMap. tempNames := theMethodNode tempNames]. (sourceMap size = 0 or: [ self selectedContext isDead ]) ifTrue: [^1 to: 0]. Smalltalk at: #RBProgramNode ifPresent:[:nodeClass| (theMethodNode isKindOf: nodeClass) ifTrue: [ pc := contextStackIndex = 1 ifTrue: [self selectedContext pc] ifFalse: [self selectedContext previousPc]. i := sourceMap findLast:[:pcRange | pcRange key <= pc]. i = 0 ifTrue:[^ 1 to: 0]. ^ (sourceMap at: i) value ]. ]. pc:= self selectedContext pc - (("externalInterrupt" true 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 menu' stamp: 'sd 11/20/2005 21:27'! 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: '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: 'rbb 3/1/2005 10:50'! askForCategoryIn: aClass default: aString | categories index category | categories := OrderedCollection with: 'new ...'. categories addAll: (aClass allMethodCategoriesIntegratedThrough: Object). index := UIManager default chooseFrom: categories title: 'Please provide a good category for the new method!!' translated. index = 0 ifTrue: [^ aString]. category := index = 1 ifTrue: [UIManager default request: 'Enter category name:'] ifFalse: [categories at: index]. ^ category isEmpty ifTrue: [^ aString] ifFalse: [category]! ! !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: 'sd 11/20/2005 21:27'! 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 theNonMetaClass meta: class 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'! 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: 'sd 11/20/2005 21:27'! 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: 'kfr 9/24/2004 21:42'! 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: [self selectedContext selector = #doesNotUnderstand: ifTrue: [aMenu add: 'implement in...' subMenu: (self populateImplementInMenu: (Smalltalk isMorphic ifTrue: [MenuMorph new defaultTarget: self] ifFalse: [CustomMenu new])) target: nil selector: nil argumentList: #(nil)]. aMenu labels: 'fullStack (f) restart (r) proceed (p) step (t) step through (T) send (e) where (w) peel to first like this return entered value toggle break on entry 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: #(8 9 13 15 18 21) selections: #(fullStack restart proceed doStep stepIntoBlock send where peelToFirst returnValue toggleBreakOnEntry 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: 'tk 4/16/1998 12:19'! debugProceedMenu: aMenu ^ aMenu labels: 'proceed debug' lines: #() selections: #(proceed debug ) ! ! !Debugger methodsFor: 'context stack menu' stamp: 'sd 11/20/2005 21:27'! doStep "Send the selected message in the accessed method, and regain control after the invoked method returns." | currentContext newContext | self okToChange ifFalse: [^ self]. self checkContextSelection. currentContext := self selectedContext. newContext := interruptedProcess completeStep: currentContext. newContext == currentContext ifTrue: [ newContext := interruptedProcess stepToSendOrReturn]. self contextStackIndex > 1 ifTrue: [self resetContext: newContext] ifFalse: [newContext == currentContext ifTrue: [self changed: #contentsSelection. self updateInspectors] ifFalse: [self resetContext: newContext]]. ! ! !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: 'md 10/6/2005 13:45'! implement: aMessage inClass: aClass | category | category := self askForCategoryIn: aClass default: 'as yet unclassified'. aClass compile: aMessage createStubMethod classified: category. self setContentsToForceRefetch. self selectedContext privRefreshWith: (aClass lookupSelector: aMessage selector). aMessage arguments doWithIndex: [:arg :i | self selectedContext at: i put: arg. ]. self resetContext: self selectedContext. self debug. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'sd 11/20/2005 21:27'! 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." | messageStrm | MailSender default ifNil: [^self]. Cursor write showWhile: ["Prepare the message" messageStrm := WriteStream on: (String new: 1500). messageStrm nextPutAll: 'From: '; nextPutAll: MailSender userName; cr; nextPutAll: 'To: squeak-dev@lists.squeakfoundation.org'; cr; nextPutAll: 'Subject: '; nextPutAll: '[BUG]'; nextPutAll: self interruptedContext printString; cr;cr; nextPutAll: 'here insert explanation of what you were doing, suspect changes you''ve made and so forth.';cr;cr. self interruptedContext errorReportOn: messageStrm. MailSender sendMessage: (MailMessage from: messageStrm contents)]. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'sw 3/16/2001 17:20'! messageListMenu: aMenu shifted: shifted "The context-stack menu takes the place of the message-list menu in the debugger, so pass it on" ^ self contextStackMenu: aMenu shifted: shifted! ! !Debugger methodsFor: 'context stack menu' stamp: 'md 2/20/2006 20:23'! 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!!" | ctxt | contextStackIndex = 0 ifTrue: [^ Beeper beep]. "self okToChange ifFalse: [^ self]." ctxt := interruptedProcess popTo: self selectedContext findSecondToOldestSimilarSender. self resetContext: ctxt. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'sd 11/20/2005 21:27'! populateImplementInMenu: aMenu | msg | msg := self selectedContext at: 1. self selectedContext receiver class withAllSuperclasses do: [:each | aMenu add: each name target: self selector: #implement:inClass: argumentList: (Array with: msg with: each)]. ^ aMenu ! ! !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: 'ajh 1/24/2003 12:29'! 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. self resumeProcess: aTopView! ! !Debugger methodsFor: 'context stack menu' stamp: 'sd 11/20/2005 21:27'! restart "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." "Closing now depends on a preference #restartAlsoProceeds - hmm 9/7/2001 16:46" | ctxt noUnwindError | self okToChange ifFalse: [^ self]. self checkContextSelection. ctxt := interruptedProcess popTo: self selectedContext. noUnwindError := false. ctxt == self selectedContext ifTrue: [ noUnwindError := true. interruptedProcess restartTop; stepToSendOrReturn]. self resetContext: ctxt. (Preferences restartAlsoProceeds and: [noUnwindError]) ifTrue: [self proceed]. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'rbb 3/1/2005 10:50'! returnValue "Force a return of a given value to the previous context!!" | previous selectedContext expression value | contextStackIndex = 0 ifTrue: [^Beeper beep]. selectedContext := self selectedContext. expression := UIManager default request: 'Enter expression for return value:'. value := Compiler new evaluate: expression in: selectedContext to: selectedContext receiver. previous := selectedContext sender. self resetContext: previous. interruptedProcess popTo: previous value: value! ! !Debugger methodsFor: 'context stack menu' stamp: 'sd 11/20/2005 21:27'! 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: 'ajh 1/24/2003 12:29'! send "Send the selected message in the accessed method, and take control in the method invoked to allow further step or send." self okToChange ifFalse: [^ self]. self checkContextSelection. interruptedProcess step: self selectedContext. self resetContext: interruptedProcess stepToSendOrReturn. ! ! !Debugger methodsFor: 'context stack menu' stamp: 'ajh 1/24/2003 12:46'! stepIntoBlock "Send messages until you return to the present method context. Used to step into a block in the method." interruptedProcess stepToHome: self selectedContext. self resetContext: interruptedProcess stepToSendOrReturn.! ! !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: 'sd 11/20/2005 21:27'! where "Select the expression whose evaluation was interrupted." selectingPC := true. self contextStackIndex: contextStackIndex oldContextWas: self selectedContext ! ! !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: 'sd 11/20/2005 21:27'! 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)' stamp: 'ajh 9/25/2001 00:14'! fullyExpandStack "Expand the stack to include all of it, rather than the first four or five contexts." self okToChange ifFalse: [^ self]. self newStack: contextStackTop contextStack. 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: 'md 2/20/2006 20:21'! selectedMessage "Answer the source code of the currently selected context." contents := theMethodNode sourceText asText. theMethodNode isDoIt ifFalse: [ contents := contents makeSelectorBold]. ^ contents! ! !Debugger methodsFor: 'context stack (message list)' stamp: 'md 2/17/2006 12:02'! 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: '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' stamp: 'hmm 7/15/2001 19:48'! updateInspectors "Update the inspectors on the receiver's variables." receiverInspector == nil ifFalse: [receiverInspector update]. contextVariablesInspector == nil ifFalse: [contextVariablesInspector update]! ! !Debugger methodsFor: 'dependents access' stamp: 'di 1/14/1999 09:25'! wantsSteps ^ true! ! !Debugger methodsFor: 'initialize' stamp: 'md 2/24/2006 15:44'! addOptionalButtonsTo: window at: fractions plus: verticalOffset "Add button panes to the window. A row of custom debugger-specific buttons (Proceed, Restart, etc.) is always added, and if optionalButtons is in force, then the standard code-tool buttons are also added. Answer the verticalOffset plus the height added." | delta buttons anOffset | anOffset := (Preferences optionalButtons and: [Preferences extraDebuggerButtons]) ifTrue: [super addOptionalButtonsTo: window at: fractions plus: verticalOffset] ifFalse: [verticalOffset]. delta := self defaultButtonPaneHeight. buttons := self customButtonRow. buttons color: Color white; borderWidth: 0. window addMorph: buttons fullFrame: (LayoutFrame fractions: fractions offsets: (0 @ anOffset corner: 0 @ (anOffset + delta - 1))). ^ anOffset + delta! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! buildMorphicNotifierLabelled: label message: messageString | notifyPane window contentTop extentToUse | self expandStack. window := (PreDebugWindow labelled: label) model: self. contentTop := 0.25. 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 | messageString notNil 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: 'initialize' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! 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 second. pair second = pairs last second 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 second = pairs first second ifTrue: [aView addSubView: aButtonView] ifFalse: [aView addSubView: aButtonView toRightOf: previousView]. previousView := aButtonView]. ^ aView! ! !Debugger methodsFor: 'initialize' stamp: 'md 2/24/2006 15:45'! buttonRowForPreDebugWindow: aDebugWindow | aRow aButton quads | aRow := AlignmentMorph newRow hResizing: #spaceFill; vResizing: #spaceFill. aRow layoutInset: 1; cellInset: 1. aRow beSticky. aRow addMorphBack: AlignmentMorph newVariableTransparentSpacer. quads := OrderedCollection withAll: self preDebugButtonQuads. (self interruptedContext selector == #doesNotUnderstand:) ifTrue: [ quads add: { 'Create'. #createMethod. #magenta. 'create the missing method' } ]. quads do: [:quad | aButton := SimpleButtonMorph new target: aDebugWindow. aButton color: Color white; borderWidth: 1. aButton actionSelector: quad second. aButton label: quad first. aButton submorphs first color: (Color colorFrom: quad third). aButton setBalloonText: quad fourth. aButton borderStyle: BorderStyle thinGray. aButton useSquareCorners. aRow addMorphBack: aButton. aRow addMorphBack: AlignmentMorph newVariableTransparentSpacer]. ^ aRow! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! customButtonRow "Answer a button pane affording the user one-touch access to certain functions; the pane is given the formal name 'customButtonPane' by which it can be retrieved by code wishing to send messages to widgets residing on the pane" | aRow aButton aLabel | aRow := AlignmentMorph newRow beSticky. aRow setNameTo: 'customButtonPane'. aRow clipSubmorphs: true. aButton := SimpleButtonMorph new target: self. aButton color: Color lightRed; borderWidth: 1; borderColor: Color red darker. aRow addTransparentSpacerOfSize: (5@0). self customButtonSpecs do: [:tuple | aButton := PluggableButtonMorph on: self getState: nil action: tuple second. aButton hResizing: #spaceFill; vResizing: #spaceFill; onColor: Color white offColor: Color white. (#(proceed restart send doStep stepIntoBlock fullStack where) includes: tuple second) ifTrue: [aButton askBeforeChanging: true]. aLabel := Preferences abbreviatedBrowserButtons ifTrue: [self abbreviatedWordingFor: tuple second] ifFalse: [nil]. aButton label: (aLabel ifNil: [tuple first asString]). tuple size > 2 ifTrue: [aButton setBalloonText: tuple third]. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0)]. ^ aRow! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! customButtonSpecs "Answer an array of elements of the form wording, selector, help-message, that characterize the custom button row of a debugger." | list | list := #(('Proceed' proceed 'close the debugger and proceed.') ('Restart' restart 'reset this context to its start.') ('Into' send 'step Into message sends') ('Over' doStep 'step Over message sends') ('Through' stepIntoBlock 'step into a block') ('Full Stack' fullStack 'show full stack') ('Where' where 'select current pc range')). Preferences restartAlsoProceeds ifTrue: [list := list collect: [:each | each second == #restart ifTrue: [each copy at: 3 put: 'proceed from the beginning of this context.'; yourself] ifFalse: [each]]]. ^ list! ! !Debugger methodsFor: 'initialize' stamp: 'kfr 10/4/2000 22:13'! debugAt: anInteger self toggleContextStackIndex: anInteger. ^ self debug.! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! errorWasInUIProcess: boolean errorWasInUIProcess := boolean! ! !Debugger methodsFor: 'initialize' stamp: 'tk 5/9/2003 11:20'! initialExtent "Make the full debugger longer!!" dependents size < 9 ifTrue: [^ super initialExtent]. "Pre debug window" RealEstateAgent standardWindowExtent y < 400 "a tiny screen" ifTrue: [^ super initialExtent]. ^ 600@700 ! ! !Debugger methodsFor: 'initialize' stamp: 'sw 12/28/1999 13:07'! notifierButtonHeight ^ 18! ! !Debugger methodsFor: 'initialize' stamp: 'wiz 2/25/2006 20:22'! openFullMorphicLabel: aLabelString "Open a full morphic debugger with the given label" | window aListMorph oldContextStackIndex | oldContextStackIndex := contextStackIndex. self expandStack. "Sets contextStackIndex to zero." window := (SystemWindow labelled: aLabelString) 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.25). self addLowerPanesTo: window at: (0@0.25 corner: 1@0.8) with: nil. window addMorph: (( PluggableListMorph new doubleClickSelector: #inspectSelection; on: self receiverInspector list: #fieldList selected: #selectionIndex changeSelected: #toggleIndex: menu: #fieldListMenu: keystroke: #inspectorKey:from:) autoDeselect: false) "For doubleClick to work best disable autoDeselect" frame: (0@0.8 corner: 0.2@1). window addMorph: (PluggableTextMorph on: self receiverInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:) frame: (0.2@0.8 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.8 corner: 0.7@1). window addMorph: (PluggableTextMorph on: self contextVariablesInspector text: #contents accept: #accept: readSelection: #contentsSelection menu: #codePaneMenu:shifted:) frame: (0.7@0.8 corner: 1@1). window openInWorld. self toggleContextStackIndex: oldContextStackIndex. ^ window ! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! 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 := Project spawnNewProcessIfThisIsUI: interruptedProcess. ^self ]. topView := self buildMVCDebuggerViewLabel: aString minSize: 300@200. topView controller openNoTerminate. ^ topView ! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! 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. (label beginsWith: 'Space is low') ifTrue: [msg := self lowSpaceChoices, (msgString ifNil: [''])] ifFalse: [msg := 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 := Project spawnNewProcessIfThisIsUI: interruptedProcess. ^self ]. Display fullScreen. topView := self buildMVCNotifierViewLabel: label message: thisContext sender sender shortStack 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: 'sw 8/23/2002 00:23'! optionalButtonPairs "Actually, return triples. In mvc (until someone deals with this) only the custom debugger-specific buttons are shown, but in morphic, the standard code-tool buttons are provided in addition to the custom buttons" ^ Smalltalk isMorphic ifFalse: [self customButtonSpecs] ifTrue: [super optionalButtonPairs]! ! !Debugger methodsFor: 'initialize' stamp: 'sd 11/20/2005 21:27'! 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 aLabel | 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: [:tuple | aButton := PluggableButtonMorph on: self getState: nil action: tuple second. aButton hResizing: #spaceFill; vResizing: #spaceFill; onColor: Color white offColor: Color white. (#(proceed restart send doStep stepIntoBlock fullStack where) includes: tuple second) ifTrue: [aButton askBeforeChanging: true]. aLabel := Preferences abbreviatedBrowserButtons ifTrue: [self abbreviatedWordingFor: tuple second] ifFalse: [nil]. aButton label: (aLabel ifNil: [tuple first asString]). tuple size > 2 ifTrue: [aButton setBalloonText: tuple third]. aRow addMorphBack: aButton. aRow addTransparentSpacerOfSize: (3 @ 0)]. ^ aRow! ! !Debugger methodsFor: 'initialize' stamp: 'yo 3/15/2005 13:18'! preDebugButtonQuads ^Preferences eToyFriendly ifTrue: [ { {'Store log' translated. #storeLog. #blue. 'write a log of the encountered problem' translated}. {'Abandon' translated. #abandon. #black. 'abandon this execution by closing this window' translated}. {'Debug' translated. #debug. #red. 'bring up a debugger' translated}}] ifFalse: [ { {'Proceed' translated. #proceed. #blue. 'continue execution' translated}. {'Abandon' translated. #abandon. #black. 'abandon this execution by closing this window' translated}. {'Debug' translated. #debug. #red. 'bring up a debugger' translated}}] ! ! !Debugger methodsFor: 'initialize' stamp: 'yo 7/2/2004 17:42'! preDebugNotifierContentsFrom: messageString ^ Preferences eToyFriendly ifFalse: [messageString] ifTrue: ['An error has occurred; you should probably just hit ''abandon''. Sorry!!' translated] ! ! !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: 'sd 11/20/2005 21:27'! 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: '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: 'notifier menu' stamp: 'sd 11/20/2005 21:27'! debug "Open a full DebuggerView." | topView | topView := self topView. topView model: nil. "so close won't release me." Smalltalk isMorphic ifTrue: [self breakDependents. topView delete. ^ self openFullMorphicLabel: topView label]. 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: 'notifier menu' stamp: 'mir 3/5/2004 19:26'! storeLog | logFileName | logFileName := Preferences debugLogTimestamp ifTrue: ['SqueakDebug-' , Time totalSeconds printString , '.log'] ifFalse: ['SqueakDebug.log']. Smalltalk logError: labelString printString inContext: contextStackTop to: logFileName ! ! !Debugger methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 18:30'! buildFullWith: builder | windowSpec listSpec textSpec panelSpec extent | windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec label: 'Debugger'. Display height < 800 "a small screen" ifTrue:[extent := RealEstateAgent standardWindowExtent] ifFalse:[extent := 600@700]. windowSpec extent: extent. windowSpec children: OrderedCollection new. listSpec := builder pluggableListSpec new. listSpec model: self; list: #contextStackList; getIndex: #contextStackIndex; setIndex: #toggleContextStackIndex:; menu: #contextStackMenu:shifted:; keyPress: #contextStackKey:from:; frame: (0@0 corner: 1@0.25). windowSpec children add: listSpec. panelSpec := self buildOptionalButtonsWith: builder. panelSpec frame: (0@0.25 corner: 1@0.3). windowSpec children add: panelSpec. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #contents; setText: #contents:notifying:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0@0.3corner: 1@0.8). windowSpec children add: textSpec. listSpec := builder pluggableListSpec new. listSpec model: self receiverInspector; list: #fieldList; getIndex: #selectionIndex; setIndex: #toggleIndex:; menu: #fieldListMenu:; keyPress: #inspectorKey:from:; frame: (0@0.8 corner: 0.2@1). windowSpec children add: listSpec. textSpec := builder pluggableTextSpec new. textSpec model: self receiverInspector; getText: #contents; setText: #accept:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0.2@0.8 corner: 0.5@1). windowSpec children add: textSpec. listSpec := builder pluggableListSpec new. listSpec model: self contextVariablesInspector; list: #fieldList; getIndex: #selectionIndex; setIndex: #toggleIndex:; menu: #fieldListMenu:; keyPress: #inspectorKey:from:; frame: (0.5@0.8 corner: 0.7@1). windowSpec children add: listSpec. textSpec := builder pluggableTextSpec new. textSpec model: self contextVariablesInspector; getText: #contents; setText: #accept:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0.7@0.8 corner: 1@1). windowSpec children add: textSpec. ^builder build: windowSpec! ! !Debugger methodsFor: 'toolbuilder' stamp: 'ar 2/12/2005 18:15'! buildNotifierWith: builder label: label message: messageString | windowSpec listSpec textSpec panelSpec buttonSpec | windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec extent: 450 @ 156. "nice and wide to show plenty of the error msg" windowSpec label: label. windowSpec children: OrderedCollection new. panelSpec := builder pluggablePanelSpec new. panelSpec children: OrderedCollection new. self preDebugButtonQuads do:[:spec| buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self. buttonSpec label: spec first. buttonSpec action: spec second. buttonSpec help: spec fourth. panelSpec children add: buttonSpec. ]. panelSpec layout: #horizontal. "buttons" panelSpec frame: (0@0 corner: 1@0.2). windowSpec children add: panelSpec. Preferences eToyFriendly | messageString notNil ifTrue:[ listSpec := builder pluggableListSpec new. listSpec model: self; list: #contextStackList; getIndex: #contextStackIndex; setIndex: #debugAt:; frame: (0@0.2 corner: 1@1). windowSpec children add: listSpec. ] ifFalse:[ textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #preDebugMessageString; setText: nil; selection: nil; menu: #debugProceedMenu:; frame: (0@0.2corner: 1@1). windowSpec children add: textSpec. ]. ^windowSpec! ! !Debugger methodsFor: 'toolbuilder' stamp: 'ar 2/11/2005 16:24'! buildWith: aBuilder ^self buildFullWith: aBuilder! ! !Debugger methodsFor: 'toolbuilder' stamp: 'ar 2/11/2005 16:25'! preDebugMessageString ^'An error has occurred; you should probably just hit ''abandon''. Sorry!!'! ! !Debugger methodsFor: 'traits' stamp: 'al 7/29/2004 14:37'! 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 h ctxt originalClassOfMethod | contextStackIndex = 0 ifTrue: [^ false]. self selectedContext isExecutingBlock ifTrue: [h := self selectedContext finalBlockHome. h ifNil: [self inform: 'Method not found for block, can''t edit'. ^ false]. (self confirm: 'I will have to revert to the method from which this block originated. Is that OK?') ifTrue: [self resetContext: h] ifFalse: [^ false]]. classOfMethod := self selectedClass. category := self selectedMessageCategoryName. selector := self selectedClass parserClass new parseSelector: aText. selector == self selectedMessageName ifFalse: [self inform: 'can''t change selector'. ^ false]. originalClassOfMethod := classOfMethod traitOrClassOfSelector: selector. selector := originalClassOfMethod compile: aText classified: category notifying: aController. selector ifNil: [^ false]. "compile cancelled" contents := aText. ctxt := interruptedProcess popTo: self selectedContext. ctxt == self selectedContext ifFalse: [ self inform: 'Method saved, but current context unchanged because of unwind error. Click OK to see error'. ] ifTrue: [ interruptedProcess restartTopWith: (classOfMethod compiledMethodAt: selector); stepToSendOrReturn. contextVariablesInspector object: nil. theMethodNode := Preferences browseWithPrettyPrint ifTrue: [ctxt methodNodeFormattedAndDecorated: Preferences colorWhenPrettyPrinting] ifFalse: [ctxt methodNode]. sourceMap := theMethodNode sourceMap. tempNames := theMethodNode tempNames. ]. self resetContext: ctxt. ^ true! ! !Debugger methodsFor: '*eToys-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: '*eToys-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: 'private' stamp: 'sd 11/20/2005 21:27'! askForSuperclassOf: aClass toImplement: aSelector ifCancel: cancelBlock | classes chosenClassIndex | classes := aClass withAllSuperclasses. chosenClassIndex := UIManager default chooseFrom: (classes collect: [:c | c name]) title: 'Define #', aSelector, ' in which class?'. chosenClassIndex = 0 ifTrue: [^ cancelBlock value]. ^ classes at: chosenClassIndex! ! !Debugger methodsFor: 'private' stamp: 'yo 8/12/2003 16:34'! checkContextSelection contextStackIndex = 0 ifTrue: [self contextStackIndex: 1 oldContextWas: nil]. ! ! !Debugger methodsFor: 'private' stamp: 'md 2/20/2006 20:30'! contextStackIndex: anInteger oldContextWas: oldContext "Change the context stack index to anInteger, perhaps in response to user selection." | newMethod c | contextStackIndex := anInteger. anInteger = 0 ifTrue: [currentCompiledMethod := theMethodNode := tempNames := sourceMap := contents := nil. self changed: #contextStackIndex. self decorateButtons. self contentsChanged. contextVariablesInspector object: nil. self receiverInspectorObject: self receiver context: nil. ^ self]. (newMethod := oldContext == nil or: [oldContext method ~~ (currentCompiledMethod := self selectedContext method)]) ifTrue: [tempNames := sourceMap := nil. theMethodNode := Preferences browseWithPrettyPrint ifTrue: [ self selectedContext methodNodeFormattedAndDecorated: Preferences colorWhenPrettyPrinting ] ifFalse: [ self selectedContext methodNode ]. contents := self selectedMessage. self contentsChanged. self pcRange "will compute tempNamesunless noFrills"]. self changed: #contextStackIndex. self decorateButtons. tempNames == nil ifTrue: [tempNames := self selectedClassOrMetaClass parserClass new parseArgsAndTemps: contents notifying: nil]. contextVariablesInspector object: (c _ self selectedContext). self receiverInspectorObject: self receiver context: c. newMethod ifFalse: [self changed: #contentsSelection]! ! !Debugger methodsFor: 'private' stamp: 'sd 11/20/2005 21:27'! createMethod "Should only be called when this Debugger was created in response to a MessageNotUnderstood exception. Create a stub for the method that was missing and proceed into it." | msg chosenClass | msg := contextStackTop tempAt: 1. chosenClass := self askForSuperclassOf: contextStackTop receiver class toImplement: msg selector ifCancel: [^self]. self implement: msg inClass: chosenClass. ! ! !Debugger methodsFor: 'private' stamp: 'sd 11/20/2005 21:27'! 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: 'yo 12/3/2004 17:14'! 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' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! process: aProcess controller: aController context: aContext isolationHead: projectOrNil super initialize. Smalltalk at: #MessageTally ifPresentAndInMemory: [: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. Smalltalk isMorphic ifTrue: [errorWasInUIProcess := false]! ! !Debugger methodsFor: 'private' stamp: 'sd 11/20/2005 21:27'! 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 contextStack. self changed: #contextStackList. self contextStackIndex: 1 oldContextWas: oldContext. self contentsChanged. ! ! !Debugger methodsFor: 'private' stamp: 'sd 11/20/2005 21:27'! resumeProcess: aTopView Smalltalk isMorphic ifFalse: [aTopView erase]. savedCursor ifNotNil: [Sensor currentCursor: savedCursor]. isolationHead ifNotNil: [failedProject enterForEmergencyRecovery. isolationHead invoke. isolationHead := nil]. interruptedProcess isTerminated ifFalse: [ 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. World displayWorld] 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: 'sd 11/20/2005 21:28'! initialize ErrorRecursion := false. ContextStackKeystrokes := Dictionary new at: $e put: #send; at: $t put: #doStep; at: $T put: #stepIntoBlock; at: $p put: #proceed; at: $r put: #restart; at: $f put: #fullStack; at: $w put: #where; yourself. "Debugger initialize"! ! !Debugger class methodsFor: 'class initialization' stamp: 'sd 11/20/2005 21:28'! openContext: aContext label: aString contents: contentsStringOrNil | 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 := Project current isolationHead) ifNil: [self primitiveError: aString] ifNotNil: [isolationHead revoke]]. ErrorRecursion := true. self informExistingDebugger: aContext label: aString. (Debugger context: aContext isolationHead: isolationHead) openNotifierContents: contentsStringOrNil label: aString. ErrorRecursion := false. Processor activeProcess suspend. ! ! !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: 'instance creation' stamp: 'sd 11/20/2005 21:28'! informExistingDebugger: aContext label: aString "Walking the context chain, we try to find out if we're in a debugger stepping situation. If we find the relevant contexts, we must rearrange them so they look just like they would if the methods were excuted outside of the debugger." | ctx quickStepMethod oldSender baseContext | ctx := thisContext. quickStepMethod := ContextPart compiledMethodAt: #quickSend:to:with:super:. [ctx sender == nil or: [ctx sender method == quickStepMethod]] whileFalse: [ctx := ctx sender]. ctx sender == nil ifTrue: [^self]. baseContext := ctx. "baseContext is now the context created by the #quickSend... method." oldSender := ctx := ctx sender home sender. "oldSender is the context which originally sent the #quickSend... method" [ctx == nil or: [ctx receiver isKindOf: self]] whileFalse: [ctx := ctx sender]. ctx == nil ifTrue: [^self]. "ctx is the context of the Debugger method #doStep" ctx receiver labelString: aString. ctx receiver externalInterrupt: false; proceedValue: aContext receiver. baseContext swapSender: baseContext sender sender sender. "remove intervening contexts" thisContext swapSender: oldSender. "make myself return to debugger" ErrorRecursion := false. ^aContext! ! !Debugger class methodsFor: 'opening' stamp: 'sd 11/20/2005 21:28'! 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']]. Preferences eToyFriendly ifTrue: [World stopRunningAll]. ^ debugger openNotifierContents: nil label: aString ! ! !Debugger class methodsFor: 'opening' stamp: 'md 3/23/2006 15:55'! openOn: process context: context label: title contents: contentsStringOrNil fullView: bool "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." | controller errorWasInUIProcess | Smalltalk isMorphic ifTrue: [errorWasInUIProcess := Project spawnNewProcessIfThisIsUI: process] ifFalse: [controller := ScheduledControllers activeControllerProcess == process ifTrue: [ScheduledControllers activeController]]. WorldState addDeferredUIMessage: [ [ | debugger | debugger := self new process: process controller: controller context: context. Smalltalk isMorphic ifTrue: ["schedule debugger in deferred UI message to address redraw problems after opening a debugger e.g. from the testrunner." "WorldState addDeferredUIMessage: ["bool ifTrue: [debugger openFullNoSuspendLabel: title] ifFalse: [debugger openNotifierContents: contentsStringOrNil label: title]] ifFalse: ["deferred UI message would require special controller in MVC" bool ifTrue: [debugger openFullNoSuspendLabel: title] ifFalse: [debugger openNotifierContents: contentsStringOrNil label: title]]. debugger errorWasInUIProcess: errorWasInUIProcess. Preferences logDebuggerStackToFile ifTrue: [ Smalltalk logError: title inContext: context to: 'SqueakDebug.log']. Smalltalk isMorphic ifFalse: [ScheduledControllers searchForActiveController "needed since openNoTerminate (see debugger #open...) does not set up activeControllerProcess if activeProcess (this fork) is not the current activeControllerProcess (see #scheduled:from:)"]. ] on: Error do: [:ex | self primitiveError: 'Orginal error: ', title asString, '. Debugger error: ', ([ex description] on: Error do: ['a ', ex class printString]), ':' ] ]. process suspend. ! ! !Debugger class methodsFor: 'window color' stamp: 'sw 2/26/2002 14:10'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Debugger' brightColor: #lightRed pastelColor: #veryPaleRed helpMessage: 'The system debugger.'! ! TestCase subclass: #DebuggerUnwindBug instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Debugger-Tests'! !DebuggerUnwindBug methodsFor: 'as yet unclassified' stamp: 'ar 3/7/2003 01:38'! testUnwindBlock "test if unwind blocks work properly" | sema process | sema := Semaphore forMutualExclusion. self assert: sema isSignaled. "deadlock on the semaphore" process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority. self deny: sema isSignaled. "terminate process" process terminate. self assert: sema isSignaled. ! ! !DebuggerUnwindBug methodsFor: 'as yet unclassified' stamp: 'ar 3/7/2003 01:41'! testUnwindDebugger "test if unwind blocks work properly when a debugger is closed" | sema process debugger top | sema := Semaphore forMutualExclusion. self assert: sema isSignaled. process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority. self deny: sema isSignaled. "everything set up here - open a debug notifier" debugger := Debugger openInterrupt: 'test' onProcess: process. "get into the debugger" debugger debug. top := debugger topView. "set top context" debugger toggleContextStackIndex: 1. "close debugger" top delete. "and see if unwind protection worked" self assert: sema isSignaled.! ! !DebuggerUnwindBug methodsFor: 'as yet unclassified' stamp: 'ar 3/7/2003 01:40'! testUnwindDebuggerWithStep "test if unwind blocks work properly when a debugger is closed" | sema process debugger top | sema := Semaphore forMutualExclusion. self assert: sema isSignaled. process := [sema critical:[sema wait]] forkAt: Processor userInterruptPriority. self deny: sema isSignaled. "everything set up here - open a debug notifier" debugger := Debugger openInterrupt: 'test' onProcess: process. "get into the debugger" debugger debug. top := debugger topView. "set top context" debugger toggleContextStackIndex: 1. "do single step" debugger doStep. "close debugger" top delete. "and see if unwind protection worked" self assert: sema isSignaled.! ! InstructionStream subclass: #Decompiler instanceVariableNames: 'constructor method instVars tempVars constTable stack statements lastPc exit caseExits lastJumpPc lastReturnPc limit hasValue blockStackBase' classVariableNames: 'ArgumentFlag CascadeFlag CaseFlag IfNilFlag' poolDictionaries: '' category: 'Compiler-Kernel'! !Decompiler commentStamp: 'ls 1/28/2004 13:31' 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) instance vars: constructor method instVars tempVars constTable stack statements lastPc exit caseExits - stack of exit addresses that have been seen in the branches of caseOf:'s lastJumpPc lastReturnPc limit hasValue blockStackBase! !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: 'ls 1/28/2004 13:29'! 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 | 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: [stack last == CaseFlag ifFalse: [ statements addLast: stack removeLast] ]. lastJumpPc = lastPc ifFalse: [exit _ pc]. caseExits add: exit. ^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: 'initialize-release' stamp: 'ajh 7/21/2003 01:14'! 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 ifNil: [method tempNames]. 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: 'instruction decoding'! blockReturnTop "No action needed"! ! !Decompiler methodsFor: 'instruction decoding' stamp: 'ls 1/28/2004 13:27'! case: dist "statements = keyStmts CascadeFlag keyValueBlock ... keyStmts" | nextCase thenJump stmtStream elements b node cases otherBlock myExits | nextCase _ pc + dist. "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" 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. "try find the end of the case" myExits := caseExits removeLast: elements size. myExits := myExits reject: [ :e | e isNil or: [ e < 0 or: [ e > method size ] ] ]. myExits isEmpty ifTrue: [ thenJump := nextCase ] ifFalse: [ thenJump := myExits min ]. otherBlock _ self blockTo: thenJump. stack addLast: (constructor codeMessage: stack removeLast selector: (constructor codeSelector: #caseOf:otherwise: code: #macro) arguments: (Array with: cases with: otherBlock)). myExits isEmpty ifTrue:[ "all branches returned; pop off the statement" statements addLast: stack removeLast. ] ].! ! !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' stamp: 'nk 2/20/2004 11:56'! pushReceiverVariable: offset | var | (var _ instVars at: offset + 1 ifAbsent: []) == nil ifTrue: ["Not set up yet" var _ constructor codeInst: offset. instVars size < (offset + 1) ifTrue: [ instVars _ (Array new: offset + 1) replaceFrom: 1 to: instVars size with: instVars; yourself ]. instVars at: offset + 1 put: var]. 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' stamp: 'md 4/24/2006 11:22'! decompileBlock: aBlock "Decompile aBlock, returning the result as a BlockNode. Show temp names from source if available." | startpc end homeClass blockNode home | (home := aBlock home) ifNil: [^ nil]. (homeClass := home methodClass) ifNil: [^ nil]. method := home method. constructor := DecompilerConstructor new. self withTempNames: method methodNode tempNames. self initSymbols: homeClass. startpc _ aBlock startpc. end _ aBlock endPC. stack _ OrderedCollection new: method frameSize. caseExits _ OrderedCollection new. statements _ OrderedCollection new: 20. super method: method pc: startpc - 5. blockNode _ self blockTo: end. stack isEmpty ifFalse: [self error: 'stack not empty']. ^ blockNode statements first "Decompiler new decompileBlock: [3 + 4]"! ! !Decompiler methodsFor: 'public access'! tempAt: offset "Needed by BraceConstructor'. 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"! ! !Decompiler class methodsFor: 'testing' stamp: 'md 2/22/2006 15:51'! recompileAllTest "[self recompileAllTest]" "decompile every method and compile it back; if the decompiler is correct then the system should keep running. :)" | decompiled ast compiled | SystemNavigation default allBehaviorsDo: [ :behavior | Utilities informUser: (behavior printString) during: [ behavior selectors do: [ :sel | decompiled := Decompiler new decompile: sel in: behavior. ast := Compiler new compile: decompiled in: behavior notifying: nil ifFail: [ self error: 'failed' ]. compiled := ast generate: (behavior compiledMethodAt: sel) trailer. behavior addSelector: sel withMethod: compiled. ] ] ]! ! ParseNode subclass: #DecompilerConstructor instanceVariableNames: 'method instVars nArgs literalValues tempVars' classVariableNames: '' poolDictionaries: '' category: 'Compiler-Support'! !DecompilerConstructor commentStamp: '' prior: 0! I construct the node tree for a Decompiler.! !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' stamp: 'nk 2/20/2004 11:51'! codeInst: index ^VariableNode new name: (instVars at: index + 1 ifAbsent: ['unknown', index asString]) 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: 'lr 2/6/2006 21:08'! codeMethod: selector block: block tempVars: vars primitive: primitive class: class | node methodTemps | node _ self codeSelector: selector code: nil. tempVars _ vars. methodTemps _ tempVars select: [:t | t scope >= 0]. ^MethodNode new selector: node arguments: (methodTemps copyFrom: 1 to: nArgs) precedence: selector precedence temporaries: (methodTemps copyFrom: nArgs + 1 to: methodTemps size) block: block encoder: (Encoder new initScopeAndLiteralTables temps: tempVars literals: literalValues class: class) primitive: primitive properties: method properties.! ! !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:! ! !DecompilerConstructor methodsFor: 'initialize-release'! method: aMethod class: aClass literals: literals method _ aMethod. instVars _ aClass allInstVarNames. nArgs _ method numArgs. literalValues _ literals! ! Object subclass: #DeepCopier instanceVariableNames: 'references uniClasses newUniClasses' classVariableNames: 'NextVariableCheckTime' poolDictionaries: '' category: 'System-Object Storage'! !DeepCopier commentStamp: 'tk 3/4/2003 19:39' prior: 0! DeepCopier does a veryDeepCopy. It is a complete tree copy using a dictionary. Any object that is in the tree twice is only copied once. All references to the object in the copy of the tree will point to the new copy. See Object|veryDeepCopy which calls (self veryDeepCopyWith: aDeepCopier). The dictionary of objects that have been seen, holds the correspondance (uniClass -> new uniClass). When a tree of morphs points at a morph outside of itself, 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 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 compute an alternate object to put in a field. (Object veryDeepCopyWith: discovers which superclasses did not define veryDeepInner:, and very deeply copies the variables defined in those classes). To decide if a class needs veryDeepInner: and veryDeepFixupWith:, ask this about an instance: If I duplicate this object, does that mean that I also want to make duplicates of the things it holds onto? If yes, (i.e. a Paragraph does want a new copy of its Text) then do nothing. If no, (i.e. an undo command does not want to copy the objects it acts upon), then define veryDeepInner: and veryDeepFixupWith:. Here is an analysis for the specific case of a morph being held by another morph. Does field X contain a morph (or a Player whose costume is a morph)? If not, no action needed. Is the morph in field X already 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 it could, you must write the methods veryDeepFixupWith: and veryDeepInner:, and in them, refrain from sending veryDeepCopyWith: to the contents of field X. newUniClasses = true in the normal case. Every duplicated Player gets a new class. When false, all duplicates will be siblings (sister instances) of existing players. ----- Things Ted is still considering ----- 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 10/4/2001 13:54'! checkBasicClasses "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 objCls morphCls playerCls | str _ '|veryDeepCopyWith: or veryDeepInner: is out of date.'. (objCls _ self objInMemory: #Object) ifNotNil: [ objCls instSize = 0 ifFalse: [self error: 'Many implementers of veryDeepCopyWith: are out of date']]. (morphCls _ self objInMemory: #Morph) ifNotNil: [ morphCls superclass == Object ifFalse: [self error: 'Morph', str]. (morphCls instVarNames copyFrom: 1 to: 6) = #('bounds' 'owner' 'submorphs' 'fullBounds' 'color' 'extension') ifFalse: [self error: 'Morph', str]]. "added ones are OK" str2 _ 'Player|copyUniClassWith: and DeepCopier|mapUniClasses are out of date'. (playerCls _ self objInMemory: #Player) ifNotNil: [ playerCls class instVarNames = #('scripts' 'slotInfo') ifFalse: [self error: str2]]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 3/7/2001 15:42'! checkClass: aClass | meth | "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." self checkBasicClasses. "Unlikely, but important to catch when it does happen." "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." (aClass includesSelector: #veryDeepInner:) ifTrue: [ ((aClass compiledMethodAt: #veryDeepInner:) writesField: aClass instSize) ifFalse: [ aClass instSize > 0 ifTrue: [ self warnIverNotCopiedIn: aClass sel: #veryDeepInner:]]]. (aClass includesSelector: #veryDeepCopyWith:) ifTrue: [ meth _ aClass compiledMethodAt: #veryDeepCopyWith:. (meth size > 20) & (meth literals includes: #veryDeepCopyWith:) not ifTrue: [ (meth writesField: aClass instSize) ifFalse: [ self warnIverNotCopiedIn: aClass sel: #veryDeepCopyWith:]]]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'dvf 8/23/2003 11:52'! 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. (Please do not remove this method.) DeepCopier new checkDeep " | mm | Transcript cr; show: 'Instance variables shared with the original object when it is copied'. (self systemNavigation 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: 'dvf 8/23/2003 11:53'! 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 " | meth | self checkBasicClasses. "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." (self systemNavigation allClassesImplementing: #veryDeepInner:) do: [:aClass | ((aClass compiledMethodAt: #veryDeepInner:) writesField: aClass instSize) ifFalse: [aClass instSize > 0 ifTrue: [self warnIverNotCopiedIn: aClass sel: #veryDeepInner:]]]. (self systemNavigation 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:]]]! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 3/11/2003 13:56'! fixDependents "They are not used much, but need to be right" | newDep newModel | DependentsFields associationsDo: [:pair | pair value do: [:dep | newDep _ references at: dep ifAbsent: [nil]. newDep ifNotNil: [ newModel _ references at: pair key ifAbsent: [pair key]. newModel addDependent: newDep]]]. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'hg 11/23/1999 13:36'! initialize self initialize: 4096. ! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 3/4/2003 19:40'! initialize: size references _ IdentityDictionary new: size. uniClasses _ IdentityDictionary new. "UniClass -> new UniClass" "self isItTimeToCheckVariables ifTrue: [self checkVariables]." "no more checking at runtime" newUniClasses _ true.! ! !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: 'tk 3/11/2003 14:14'! 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 | newUniClasses ifFalse: [^ self]. "All will be siblings. uniClasses is empty" "Uniclasses use class vars to hold onto siblings who are referred to in code" pp _ Player class superclass instSize. uniClasses do: [:playersClass | "values = new ones" playersClass classPool associationsDo: [:assoc | assoc value: (assoc value veryDeepCopyWith: self)]. playersClass scripts: (playersClass privateScripts veryDeepCopyWith: self). "pp+1" "(pp+2) slotInfo was deepCopied in copyUniClass and that's all it needs" pp+3 to: playersClass class instSize do: [:ii | playersClass instVarAt: ii put: ((playersClass instVarAt: ii) 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 isVariableBinding 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 3/11/2003 14:13'! newUniClasses "If false, all new Players are merely siblings of the old players" ^ newUniClasses! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 3/4/2003 19:44'! newUniClasses: newVal "If false, all new players are merely siblings of the old players" newUniClasses _ newVal! ! !DeepCopier methodsFor: 'like fullCopy' stamp: 'tk 3/7/2001 15:29'! objInMemory: ClassSymbol | cls | "Test if this global is in memory and return it if so." cls _ Smalltalk at: ClassSymbol ifAbsent: [^ nil]. ^ cls isInMemory ifTrue: [cls] ifFalse: [nil].! ! !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: 'ar 9/27/2005 20:27'! warnIverNotCopiedIn: aClass sel: sel "Warn the user to update veryDeepCopyWith: or veryDeepInner:" self inform: ('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. ToolSet browse: aClass selector: sel! ! Object subclass: #DefaultExternalDropHandler instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !DefaultExternalDropHandler commentStamp: 'dgd 4/5/2004 19:07' prior: 0! An alternative default handler that uses the file-list services to process files. ! !DefaultExternalDropHandler methodsFor: 'event handling' stamp: 'bf 9/21/2004 18:44'! handle: dropStream in: pasteUp dropEvent: anEvent "the file was just droped, let's do our job" | fileName services theOne | fileName := dropStream name. "" services := self servicesForFileNamed: fileName. "" "no service, default behavior" services isEmpty ifTrue: ["" dropStream edit. ^ self]. "" theOne := self chooseServiceFrom: services. theOne isNil ifFalse: [theOne performServiceFor: dropStream]! ! !DefaultExternalDropHandler methodsFor: 'private' stamp: 'dgd 4/5/2004 20:53'! chooseServiceFrom: aCollection "private - choose a service from aCollection asking the user if needed" | menu | aCollection size = 1 ifTrue: [^ aCollection anyOne]. "" menu := CustomMenu new. aCollection do: [:each | menu add: each label action: each]. ^ menu startUp! ! !DefaultExternalDropHandler methodsFor: 'private' stamp: 'dgd 4/5/2004 19:23'! servicesForFileNamed: aString "private - answer a collection of file-services for the file named aString" | allServices | allServices := FileList itemsForFile: aString. ^ allServices reject: [:svc | self unwantedSelectors includes: svc selector]! ! !DefaultExternalDropHandler methodsFor: 'private' stamp: 'dgd 4/5/2004 19:23'! unwantedSelectors "private - answer a collection well known unwanted selectors " ^ #(#removeLineFeeds: #addFileToNewZip: #compressFile: #putUpdate: )! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DefaultExternalDropHandler class instanceVariableNames: ''! !DefaultExternalDropHandler class methodsFor: 'class initialization' stamp: 'dgd 4/5/2004 19:10'! initialize "initialize the receiver" ExternalDropHandler defaultHandler: self new! ! !DefaultExternalDropHandler class methodsFor: 'class initialization' stamp: 'dgd 4/5/2004 19:09'! unload "initialize the receiver" ExternalDropHandler defaultHandler: nil! ! StandardSystemController subclass: #DeferredActionStandardSystemController instanceVariableNames: 'queue' classVariableNames: '' poolDictionaries: '' category: 'Tools-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: 'sd 11/20/2005 21:27'! initialize super initialize. queue := SharedQueue new.! ! WriteStream subclass: #DeflateStream instanceVariableNames: 'hashHead hashTail hashValue blockPosition blockStart' classVariableNames: '' poolDictionaries: 'ZipConstants' category: 'Compression-Streams'! !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 2/19/2004 00:34'! next: bytes putAll: aCollection startingAt: startPos (startPos = 1 and:[bytes = aCollection size]) ifTrue:[^self nextPutAll: aCollection]. ^self nextPutAll: (aCollection copyFrom: startPos to: startPos + bytes - 1)! ! !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: '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: '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]].! ! Object subclass: #Delay instanceVariableNames: 'delayDuration resumptionTime delaySemaphore beingWaitedOn' classVariableNames: 'AccessProtect ActiveDelay ActiveDelayStartTime SuspendedDelays TimingSemaphore' poolDictionaries: '' category: 'Kernel-Processes'! !Delay commentStamp: 'stephaneducasse 10/1/2005 21:07' prior: 0! I am the main way that a process may pause for some amount of time. The simplest usage is like this: (Delay forSeconds: 5) wait. 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. For a more complex example, see #testDelayOf:for:rect: . A word of advice: This is THE highest priority code which is run in Squeak, in other words it is time-critical. The speed of this code is critical for accurate responses, it is critical for network services, it affects every last part of the system. In short: Don't fix it if it ain't broken!! This code isn't supposed to be beautiful, it's supposed to be fast!! The reason for duplicating code is to make it fast. The reason for not using ifNil:[]ifNotNil:[] is that the compiler may not inline those. Since the effect of changes are VERY hard to predict it is best to leave things as they are for now unless there is an actual need to change anything! !Delay methodsFor: 'delaying' stamp: 'nk 3/14/2001 08:52'! isExpired ^delaySemaphore isSignaled. ! ! !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: 'public' stamp: 'brp 10/21/2004 16:05'! delaySemaphore ^ delaySemaphore! ! !Delay methodsFor: 'private' stamp: 'tpr 10/4/2005 15:14'! 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. ActiveDelayStartTime >= resumptionTime ifTrue:[ "if my time is now or in the past, trigger me and get the next in line activated" ActiveDelay signalWaitingProcess. SuspendedDelays isEmpty ifTrue:[ ActiveDelay _ nil. ActiveDelayStartTime _ nil. ] ifFalse:[SuspendedDelays removeFirst activate]. ] ifFalse:[ 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: 'tpr 10/3/2005 17:22'! beGuardianDelay "see comment for class method guardianDelay" beingWaitedOn _ false. resumptionTime _ SmallInteger maxVal. delaySemaphore _ Semaphore new! ! !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: 'laza 2/12/2005 11:45'! setDelay: milliseconds forSemaphore: aSemaphore "Private!! Initialize this delay to signal the given semaphore after the given number of milliseconds." delayDuration _ milliseconds asInteger. delayDuration < 0 ifTrue: [self error: 'delay times cannot be negative']. delayDuration > (SmallInteger maxVal // 2) ifTrue: [self error: 'delay times can''t be longer than about six days (', (SmallInteger maxVal // 2) printString , 'ms)']. 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: '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: 'instance creation' stamp: 'brp 9/25/2003 13:43'! forDuration: aDuration ^ self forMilliseconds: aDuration asMilliSeconds ! ! !Delay class methodsFor: 'instance creation' stamp: 'laza 1/30/2005 22:10'! forMilliseconds: aNumber "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." ^ self new setDelay: aNumber forSemaphore: Semaphore new ! ! !Delay class methodsFor: 'instance creation' stamp: 'laza 1/30/2005 22:11'! forSeconds: aNumber ^ self forMilliseconds: aNumber * 1000 ! ! !Delay class methodsFor: 'instance creation' stamp: 'tpr 10/3/2005 17:13'! guardianDelay "Make a Delay with a resumption time far in the future but still a SmallInteger so that it can be used as a guardian for the active delay queue. No process will be waiting on this and when triggered it will do nothing. What it allows is very long Delays where the resumption time is a large integer; should such a delay get activated it will fail the primitive and we creat one of these guardians to make sure the delay timer keeps going and triggers the resumption time recalculations in save/restoreResumptionTime" ^self new beGuardianDelay! ! !Delay class methodsFor: 'instance creation' stamp: 'laza 1/30/2005 22:13'! 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." ^ (self forMilliseconds: anInteger) 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: 'testing' stamp: 'ar 9/6/1999 17:05'! anyActive "Return true if there is any delay currently active" ^ActiveDelay notNil! ! !Delay class methodsFor: 'testing'! nextWakeUpTime ^ AccessProtect critical: [ActiveDelay isNil ifTrue: [0] ifFalse: [ActiveDelay resumptionTime]]! ! !Delay class methodsFor: 'testing' stamp: 'tpr 10/3/2005 17:26'! 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." | guardianDelay | "VM code actually only fails if the time parameter is not a SmallInteger value" aSmallInteger isInteger ifFalse:["somebody messed up badly and we can't do much about it" aSemaphore ifNotNil: [ ActiveDelay _ nil. aSemaphore signal. "Prevent an image crash"]. ^self primitiveError: 'primSignal:atMilliseconds: failed because of a non-Integer resumption time parameter. The Semaphore has been signalled as a best guess of the right thing to do']. "So now we feel fairly sure that the aSmallInteger resumption time is actually a large integer and we need to just wait some more. To make the system do that we need a fake Delay and a reasonable resumption time to feed to the VM. A decent value is SmallInteger maxVal since the VM handles correlating that sort of largish value and clock wrapping. First though we return the problem Delay to the queue" SuspendedDelays add: ActiveDelay. "Now we want a Delay set to fire and do nothing" guardianDelay _ self guardianDelay. guardianDelay activate ! ! !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: 'tpr 10/4/2005 13:51'! 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." | nowTime | [true] whileTrue: [TimingSemaphore wait. AccessProtect critical: [ActiveDelay == nil ifFalse: [ActiveDelay signalWaitingProcess. (nowTime := Time millisecondClockValue) < ActiveDelayStartTime ifTrue: ["clock wrapped so adjust the resumption times of all the suspended delays. No point adjusting the active delay since we've just triggered it" SuspendedDelays do: [:d | d adjustResumptionTimeOldBase: ActiveDelayStartTime newBase: nowTime]]]. SuspendedDelays isEmpty ifTrue: [ActiveDelay := nil. ActiveDelayStartTime := nil] ifFalse: [SuspendedDelays removeFirst activate]]]! ! TestCase subclass: #DelayTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Processes'! !DelayTest methodsFor: 'testing-limits' stamp: 'stephaneducasse 10/2/2005 17:39'! testBounds "self run: #testBounds" self should: [Delay forMilliseconds: -1] raise: Error. self should: [Delay forMilliseconds: SmallInteger maxVal // 2 + 1] raise: Error. "Not longer than a day" self shouldnt: [Delay forMilliseconds: SmallInteger maxVal // 2] raise: Error. self shouldnt: [(Delay forMilliseconds: Float pi) wait] raise: Error. "Wait 3ms" ! ! Array weakSubclass: #DependentsArray instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Objects'! !DependentsArray commentStamp: '' prior: 0! An array of (weak) dependents of some object.! !DependentsArray methodsFor: 'copying' stamp: 'bf 7/21/2006 17:04'! copyWith: newElement "Re-implemented to not copy any niled out dependents." | copy i | copy := self class new: self size + 1. i := 0. self do: [:item | copy at: (i:=i+1) put: item]. copy at: (i:=i+1) put: newElement. ^copy! ! !DependentsArray methodsFor: 'copying' stamp: 'tp 7/6/2006 11:11'! size ^self inject: 0 into: [ :count :dep | dep ifNil: [ count ] ifNotNil: [ count + 1 ]]! ! !DependentsArray methodsFor: 'enumerating' stamp: 'nk 3/11/2004 09:34'! do: aBlock "Refer to the comment in Collection|do:." | dep | 1 to: self basicSize do:[:i| (dep _ self at: i) ifNotNil:[aBlock value: dep]].! ! !DependentsArray methodsFor: 'enumerating' stamp: 'ar 2/11/2001 01:50'! select: aBlock "Refer to the comment in Collection|select:." | aStream | aStream _ WriteStream on: (self species new: self size). self do:[:obj| (aBlock value: obj) ifTrue: [aStream nextPut: obj]]. ^ aStream contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DependentsArray class instanceVariableNames: ''! Warning subclass: #Deprecation instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! !Deprecation commentStamp: 'dew 5/21/2003 17:46' prior: 0! This Warning is signalled by methods which are deprecated. The use of Object>>#deprecatedExplanation: aString and Object>>#deprecated: aBlock explanation: aString is recommended. Idiom: Imagine I want to deprecate the message #foo. foo ^ 'foo' I can replace it with: foo self deprecatedExplanation: 'The method #foo was not good. Use Bar>>newFoo instead.' ^ 'foo' Or, for certain cases such as when #foo implements a primitive, #foo can be renamed to #fooDeprecated. fooDeprecated ^ foo ^ self deprecated: [self fooDeprecated] explanation: 'The method #foo was not good. Use Bar>>newFoo instead.' ! Object subclass: #DescriptionForPartsBin instanceVariableNames: 'formalName categoryList documentation globalReceiverSymbol nativitySelector sampleImageForm' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-PartsBin'! !DescriptionForPartsBin commentStamp: '' prior: 0! An object description, for use with the ObjectsTool and other parts-bin-like repositories. formalName The formal name by which the object is to be known categoryList A list of category symbols, by way of attribute tags documentation For use in balloon help, etc. globalReceiverSymbol A symbol representing the global to whom to send nativitySelector nativitySelector The selector to send to the global receiver to obtain a new instance! !DescriptionForPartsBin methodsFor: 'access' stamp: 'sw 8/10/2001 14:38'! categories "Answer the categoryList of the receiver" ^ categoryList! ! !DescriptionForPartsBin methodsFor: 'access' stamp: 'sw 8/10/2001 14:38'! documentation "Answer the documentation of the receiver" ^ documentation! ! !DescriptionForPartsBin methodsFor: 'access' stamp: 'sw 8/10/2001 14:38'! formalName "Answer the formalName of the receiver" ^ formalName! ! !DescriptionForPartsBin methodsFor: 'access' stamp: 'sw 8/10/2001 14:38'! globalReceiverSymbol "Answer the globalReceiverSymbol of the receiver" ^ globalReceiverSymbol! ! !DescriptionForPartsBin methodsFor: 'access' stamp: 'sw 8/10/2001 14:38'! nativitySelector "Answer the nativitySelector of the receiver" ^ nativitySelector! ! !DescriptionForPartsBin methodsFor: 'access' stamp: 'dgd 9/2/2003 18:57'! translatedCategories "Answer translated the categoryList of the receiver" ^ self categories collect: [:each | each translated]! ! !DescriptionForPartsBin methodsFor: 'initialization' stamp: 'sw 8/2/2001 01:04'! formalName: aName categoryList: aList documentation: aDoc globalReceiverSymbol: aSym nativitySelector: aSel "Set all of the receiver's instance variables from the parameters provided" formalName _ aName. categoryList _ aList. documentation _ aDoc. globalReceiverSymbol _ aSym. nativitySelector _ aSel! ! !DescriptionForPartsBin methodsFor: 'initialization' stamp: 'nk 9/1/2004 16:52'! sampleImageForm "If I have a sample image form override stored, answer it, else answer one obtained by launching an actual instance" ^ sampleImageForm ifNil: [((Smalltalk at: globalReceiverSymbol) perform: nativitySelector) imageFormDepth: 32]! ! !DescriptionForPartsBin methodsFor: 'initialization' stamp: 'sw 10/24/2001 16:37'! sampleImageForm: aForm "Set the sample image form" sampleImageForm _ aForm! ! !DescriptionForPartsBin methodsFor: 'initialization' stamp: 'sw 11/27/2001 13:19'! sampleImageFormOrNil "If I have a sample image form override stored, answer it, dlse answer nil" ^ sampleImageForm ! ! !DescriptionForPartsBin methodsFor: 'printing' stamp: 'sw 8/10/2001 21:48'! printOn: aStream aStream nextPutAll: 'a DescriptionForPartsBin, with categoryList=', categoryList asString, ' docmentation=', documentation asString, ' globalReceiverSymbol=', globalReceiverSymbol asString, ' nativitySelector=', nativitySelector asString ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DescriptionForPartsBin class instanceVariableNames: ''! !DescriptionForPartsBin class methodsFor: 'instance creation' stamp: 'sw 8/10/2001 14:39'! formalName: aName categoryList: aList documentation: aDoc globalReceiverSymbol: aSym nativitySelector: aSel "Answer a new instance of the receiver with the given traits" ^ self new formalName: aName categoryList: aList documentation: aDoc globalReceiverSymbol: aSym nativitySelector: aSel! ! !DescriptionForPartsBin class methodsFor: 'instance creation' stamp: 'sw 8/10/2001 22:33'! fromQuad: aQuad categoryList: aList "Answer an instance of DescriptionForPartsBin from the part-defining quad provided" ^ self formalName: aQuad third categoryList: aList documentation: aQuad fourth globalReceiverSymbol: aQuad first nativitySelector: aQuad second! ! 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 container 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. I inherit many operations from Set.! !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' stamp: 'dvf 9/17/2003 16:03'! associations "Answer a Collection containing the receiver's associations." | out | out _ WriteStream on: (Array new: self size). self associationsDo: [:value | out nextPut: value]. ^ out contents! ! !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 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: '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: 'di 3/7/2001 15:29'! at: key ifPresentAndInMemory: 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]. v isInMemory ifFalse: [^ nil]. ^ aBlock value: v ! ! !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: 'adding' stamp: 'raok 12/17/2003 16:01'! addAll: aKeyedCollection aKeyedCollection == self ifFalse: [ aKeyedCollection keysAndValuesDo: [:key :value | self at: key put: value]]. ^aKeyedCollection! ! !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: 'comparing' stamp: 'md 10/17/2004 16:14'! = aDictionary "Two dictionaries are equal if (a) they are the same 'kind' of thing. (b) they have the same set of keys. (c) for each (common) key, they have the same value" self == aDictionary ifTrue: [ ^ true ]. (aDictionary isKindOf: Dictionary) ifFalse: [^false]. self size = aDictionary size ifFalse: [^false]. self associationsDo: [:assoc| (aDictionary at: assoc key ifAbsent: [^false]) = assoc value ifFalse: [^false]]. ^true ! ! !Dictionary methodsFor: 'enumerating'! associationsDo: aBlock "Evaluate aBlock for each of the receiver's elements (key/value associations)." super do: aBlock! ! !Dictionary methodsFor: 'enumerating' stamp: 'dtl 2/17/2003 09:40'! associationsSelect: aBlock "Evaluate aBlock with each of my associations 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) ifTrue: [newCollection add: each]]. ^newCollection! ! !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: 'dtl 2/17/2003 09:48'! valuesDo: aBlock "Evaluate aBlock for each of the receiver's values." 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: 'apb 7/14/2004 12:48'! printElementsOn: aStream aStream nextPut: $(. self size > 100 ifTrue: [aStream nextPutAll: 'size '. self size printOn: aStream] ifFalse: [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: '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'! 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'! remove: anObject self shouldNotImplement! ! !Dictionary methodsFor: 'removing'! remove: anObject ifAbsent: exceptionBlock self shouldNotImplement! ! !Dictionary methodsFor: 'removing' stamp: 'dvf 8/23/2003 11:51'! 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). (self systemNavigation allCallsOn: (self associationAt: key)) isEmpty]]! ! !Dictionary methodsFor: 'testing' stamp: 'tween 9/13/2004 10:11'! hasBindingThatBeginsWith: aString "Answer true if the receiver has a key that begins with aString, false otherwise" self keysDo:[:each | (each beginsWith: aString) ifTrue:[^true]]. ^false! ! !Dictionary methodsFor: 'testing' stamp: 'bf 8/20/1999 15:07'! hasContentsInExplorer ^self isEmpty not! ! !Dictionary methodsFor: 'testing' stamp: 'ab 9/17/2004 00:39'! includesAssociation: anAssociation ^ (self associationAt: anAssociation key ifAbsent: [ ^ false ]) value = anAssociation value ! ! !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' stamp: 'RAA 8/23/2001 12:56'! includesKey: key "Answer whether the receiver has a key equal to the argument, key." self at: key ifAbsent: [^false]. ^true! ! !Dictionary methodsFor: 'testing'! includes: anObject self do: [:each | anObject = each ifTrue: [^true]]. ^false! ! !Dictionary methodsFor: 'testing' stamp: 'md 8/11/2005 16:49'! isDictionary ^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: 'user interface' stamp: 'hg 10/3/2001 20:47'! explorerContents | contents | contents _ OrderedCollection new. self keysSortedSafely do: [:key | contents add: (ObjectExplorerWrapper with: (self at: key) name: (key printString contractTo: 32) model: self)]. ^contents ! ! !Dictionary methodsFor: '*Compiler' stamp: 'ar 5/17/2003 14:07'! bindingOf: varName ^self associationAt: varName ifAbsent:[nil]! ! !Dictionary methodsFor: '*Compiler' stamp: 'ar 5/18/2003 20:33'! bindingsDo: aBlock ^self associationsDo: aBlock! ! !Dictionary methodsFor: '*Tools-Inspector' stamp: 'ar 9/27/2005 18:32'! inspectorClass "Answer the class of the inspector to be used on the receiver. Called by inspect; use basicInspect to get a normal (less useful) type of inspector." ^ DictionaryInspector! ! !Dictionary methodsFor: 'private' stamp: 'raok 4/22/2002 12:09'! copy "Must copy the associations, or later store will affect 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' stamp: 'md 10/5/2005 15:42'! 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 | finish _ array size. start _ (anObject hash \\ finish) + 1. "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 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' stamp: 'apb 8/20/2004 23:06'! fieldList ^ self baseFieldList , (keyArray collect: [:key | key printString])! ! !DictionaryInspector methodsFor: 'initialize-release' stamp: 'PHK 7/21/2004 18:00'! initialize super initialize. self calculateKeyArray! ! !DictionaryInspector methodsFor: 'menu' stamp: 'rbb 3/1/2005 10:51'! addEntry | newKey aKey | newKey := UIManager default 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 := self numberOfFixedFields + (keyArray indexOf: aKey). self changed: #inspectObject. self changed: #selectionIndex. self changed: #fieldList. self update! ! !DictionaryInspector methodsFor: 'menu' stamp: 'apb 8/20/2004 21:19'! 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 | self selectionIndex <= self numberOfFixedFields ifTrue: [super copyName] ifFalse: [sel := String streamContents: [:strm | strm nextPutAll: '(self at: '. (keyArray at: selectionIndex - self numberOfFixedFields) storeOn: strm. strm nextPutAll: ')']. Clipboard clipboardText: sel asText "no undo allowed"]! ! !DictionaryInspector methodsFor: 'menu' stamp: 'ar 10/31/2004 17:25'! fieldListMenu: aMenu ^ aMenu labels: 'inspect copy name references objects pointing to this value senders of this key refresh view add key rename key remove basic inspect' lines: #(6 9) selections: #(inspectSelection copyName selectionReferences objectReferencesToSelection sendersOfSelectedKey refreshView addEntry renameEntry removeSelection inspectBasic) ! ! !DictionaryInspector methodsFor: 'menu' stamp: 'sd 11/20/2005 21:27'! removeSelection selectionIndex = 0 ifTrue: [^ self changed: #flash]. object removeKey: (keyArray at: selectionIndex - self numberOfFixedFields). selectionIndex := 0. contents := ''. self calculateKeyArray. self changed: #inspectObject. self changed: #selectionIndex. self changed: #fieldList. self changed: #selection.! ! !DictionaryInspector methodsFor: 'menu' stamp: 'rbb 3/1/2005 10:51'! renameEntry | newKey aKey value | value := object at: (keyArray at: selectionIndex - self numberOfFixedFields). newKey := UIManager default request: 'Enter new key, then type RETURN. (Expression will be evaluated for value.) Examples: #Fred ''a string'' 3+4' initialAnswer: (keyArray at: selectionIndex - self numberOfFixedFields) printString. aKey := Compiler evaluate: newKey. object removeKey: (keyArray at: selectionIndex - self numberOfFixedFields). object at: aKey put: value. self calculateKeyArray. selectionIndex := self numberOfFixedFields + (keyArray indexOf: aKey). self changed: #selectionIndex. self changed: #inspectObject. self changed: #fieldList. self update! ! !DictionaryInspector methodsFor: 'menu' stamp: 'ar 10/31/2004 17:26'! 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]. self systemNavigation browseAllCallsOn: (object associationAt: (keyArray at: selectionIndex - self numberOfFixedFields)). ! ! !DictionaryInspector methodsFor: 'menu' stamp: 'ar 4/10/2005 22:17'! sendersOfSelectedKey "Create a browser on all senders of the selected key" | aKey | self selectionIndex = 0 ifTrue: [^ self changed: #flash]. ((aKey := keyArray at: selectionIndex - self numberOfFixedFields) isSymbol) ifFalse: [^ self changed: #flash]. SystemNavigation default browseAllCallsOn: aKey! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'sd 11/20/2005 21:27'! addEntry: aKey object at: aKey put: nil. self calculateKeyArray. selectionIndex := self numberOfFixedFields + (keyArray indexOf: aKey). self changed: #inspectObject. self changed: #selectionIndex. self changed: #fieldList. self update! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'sd 11/20/2005 21:27'! 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' stamp: 'sd 11/20/2005 21:27'! refreshView | i | i := selectionIndex. self calculateKeyArray. selectionIndex := i. self changed: #fieldList. self changed: #contents.! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'apb 8/20/2004 22:37'! replaceSelectionValue: anObject selectionIndex <= self numberOfFixedFields ifTrue: [^ super replaceSelectionValue: anObject]. ^ object at: (keyArray at: selectionIndex - self numberOfFixedFields) put: anObject! ! !DictionaryInspector methodsFor: 'selecting' stamp: 'apb 8/20/2004 21:55'! selection selectionIndex <= (self numberOfFixedFields) ifTrue: [^ super selection]. ^ object at: (keyArray at: selectionIndex - self numberOfFixedFields) ifAbsent:[nil]! ! !DictionaryInspector methodsFor: 'private' stamp: 'apb 8/20/2004 21:15'! numberOfFixedFields ^ 2 + object class instSize! ! TestCase subclass: #DictionaryTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Unordered'! !DictionaryTest methodsFor: 'tests' stamp: 'zz 12/7/2005 19:59'! testAdd | dict | dict := Dictionary new. dict add: #a -> 1. dict add: #b -> 2. self assert: (dict at: #a) = 1. self assert: (dict at: #b) = 2! ! !DictionaryTest methodsFor: 'tests' stamp: 'zz 12/7/2005 19:55'! testAddAll | dict1 dict2 | dict1 := Dictionary new. dict1 at: #a put:1 ; at: #b put: 2. dict2 := Dictionary new. dict2 at: #a put: 3 ; at: #c put: 4. dict1 addAll: dict2. self assert: (dict1 at: #a) = 3. self assert: (dict1 at: #b) = 2. self assert: (dict1 at: #c) = 4.! ! !DictionaryTest methodsFor: 'tests' stamp: 'zz 12/7/2005 19:57'! testComma | dict1 dict2 dict3 | dict1 := Dictionary new. dict1 at: #a put:1 ; at: #b put: 2. dict2 := Dictionary new. dict2 at: #a put: 3 ; at: #c put: 4. dict3 := dict1, dict2. self assert: (dict3 at: #a) = 3. self assert: (dict3 at: #b) = 2. self assert: (dict3 at: #c) = 4.! ! !DictionaryTest methodsFor: 'tests' stamp: 'stephaneducasse 9/18/2005 10:45'! testPseudo "(self run: #testPseudo)" "true and false are valid keys" | dict1 | dict1 := Dictionary new. self shouldnt: [dict1 at: true put: #true] raise: Error. self assert: (dict1 at: true) = #true. self shouldnt: [dict1 at: false put: #false] raise: Error. self assert: (dict1 at: false) = #false.! ! !DictionaryTest methodsFor: 'association tests' stamp: 'NDCC 3/8/2006 08:14'! testAddAssociation "self run:#testAddAssociation" "self debug:#testAddAssociation" | dict | dict := Dictionary new. dict at: #a put: 1. dict at: #b put: 2. self assert: (dict at: #a) = 1. self assert: (dict at: #b) = 2. dict at: #a put: 10. dict at: #c put: 2. self assert: (dict at: #a) = 10. self assert: (dict at: #b) = 2. self assert: (dict at: #c) = 2 ! ! !DictionaryTest methodsFor: 'association tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:30'! testAssociationsSelect | answer d | d := Dictionary new. d at: (Array with: #hello with: #world) put: #fooBar. d at: Smalltalk put: #'Smalltalk is the key'. d at: #Smalltalk put: Smalltalk. answer := d associationsSelect: [:assoc | assoc key == #Smalltalk and: [assoc value == Smalltalk]]. self should: [answer isKindOf: Dictionary]. self should: [answer size == 1]. self should: [(answer at: #Smalltalk) == Smalltalk]. answer := d associationsSelect: [:assoc | assoc key == #NoSuchKey and: [assoc value == #NoSuchValue]]. self should: [answer isKindOf: Dictionary]. self should: [answer size == 0]! ! !DictionaryTest methodsFor: 'association tests' stamp: 'NDCC 3/8/2006 09:20'! testIncludesAssociation "self run:#testIncludesAssociation" | dict | dict := Dictionary new. dict at: #a put: 1. dict at: #b put: 2. self assert: (dict includesAssociation: (#a -> 1)). self assert: (dict includesAssociation: (#b -> 2)). ! ! !DictionaryTest methodsFor: 'association tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:25'! testIncludesAssociationNoValue "self run:#testIncludesAssociationNoValue" "self debug:#testIncludesAssociationNoValue" | dict a1 a3 | a1 := Association key: #Italie. a3 := Association key: #France value: 'Paris'. self assert: (a1 key = #Italie). self assert: (a1 value isNil). dict := Dictionary new. dict add: a1. dict add: a3. self assert: (dict includesKey: #France). self assert: (dict includesKey: #Italie). self assert: (dict at: #Italie) isNil. self assert: (dict at: #France) = 'Paris' ! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:26'! testAtError "self run: #testAtError" | dict | dict := Dictionary new. dict at: #a put: 666. self shouldnt: [ dict at: #a ] raise: Error. self should: [ dict at: #b ] raise: Error. ! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'NDCC 3/1/2006 14:27'! testAtIfAbsent "self run: #testAtIfAbsent" | dict | dict := Dictionary new. dict at: #a put: 666. self assert: (dict at: #a ifAbsent: [nil]) = 666. self assert: (dict at: #b ifAbsent: [nil]) isNil. ! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'NDCC 3/8/2006 09:28'! testAtPut "self run: #testAtPut" "self debug: #testAtPut" | adictionary | adictionary := Dictionary new. adictionary at: #a put: 3. self assert: (adictionary at: #a) = 3. adictionary at: #a put: 3. adictionary at: #a put: 4. self assert: (adictionary at: #a) = 4. adictionary at: nil put: 666. self assert: (adictionary at: nil) = 666! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'NDCC 3/8/2006 09:53'! testAtPutNil "self run: #testAtPut" "self debug: #testAtPut" | dict | dict := Dictionary new. dict at: nil put: 1. self assert: (dict at: nil) = 1. dict at: #a put: nil. self assert: (dict at: #a) = nil. dict at: nil put: nil. self assert: (dict at: nil) = nil. ! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:24'! testIncludesKey "self run:#testIncludesKey" "self debug:#testIncludesKey" | dict a1 a2 a3 | a1 := Association key: 'Italie'. a2 := Association new. a3 := Association key: 'France' value: 'Paris'. dict := Dictionary new. dict add: a1 . dict add: a2. dict add: a3. self assert: (dict includesKey: #France). self assert: (dict includesKey: 'France'). self assert: (dict includesKey: #Italie). self assert: (dict includesKey: nil). self assert: (dict at: 'France' ) = 'Paris'. ! ! !DictionaryTest methodsFor: 'basic tests' stamp: 'NDCC 3/8/2006 09:41'! testOccurrencesOf "self run:#testOccurrencesOf" | dict | dict := Dictionary new. dict at: #a put: 1. dict at: #b put: 2. dict at: #c put: 1. dict at: #d put: 3. dict at: nil put: nil. dict at: #z put: nil. self assert: (dict occurrencesOf: 1 ) = 2. self assert: (dict occurrencesOf: nil ) = 2. ! ! !DictionaryTest methodsFor: 'implementation tests' stamp: 'stephaneducasse 9/18/2005 10:48'! testAtNil "(self run: #testAtNil)" "nil is a valid key in squeak. In VW nil is not a valid key" "Ansi 1.9 p, 168 5.7.2.5 Message: at: key put: newElement Synopsis Store newElement at key in the receiver. Answer newElement. Definition: If lookup succeeds for key, then newElement replaces the element previously stored at key. Otherwise, the newElement is stored at the new key. In either case, subsequent successful lookups for key will answer newElement. Answer newElement. The result is undefined if the key is nil. This clearly indicates that different smalltalks where doing different assumptions." | dict1 | dict1 := Dictionary new. self shouldnt: [ dict1 at: nil put: #none] raise: Error. self assert: (dict1 at: nil) = #none. ! ! !DictionaryTest methodsFor: 'implementation tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:29'! testPseudoVariablesAreValidKeys "(self run: #testPseudoVariablesAreValidKeys)" "true and false are valid keys" | dict1 | dict1 := Dictionary new. self shouldnt: [dict1 at: true put: #true] raise: Error. self assert: (dict1 at: true) = #true. self shouldnt: [dict1 at: false put: #false] raise: Error. self assert: (dict1 at: false) = #false.! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'NDCC 3/8/2006 08:16'! testDictionaryConcatenation "self run: #testDictionaryConcatenation" | dict1 dict2 dict3 | dict1 := Dictionary new. dict1 at: #a put: 'Nicolas' ; at: #b put: 'Damien'. dict2 := Dictionary new. dict2 at: #a put: 'Christophe' ; at: #c put: 'Anthony'. dict3 := dict1, dict2. self assert: (dict3 at: #a) = 'Christophe'. self assert: (dict3 at: #b) = 'Damien'. self assert: (dict3 at: #c) = 'Anthony'. ! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:24'! testKeyAtValue "self run: #testKeyAtValue" "self debug: #testKeyAtValue" | dict | dict := Dictionary new. dict at: #a put: 1. dict at: #b put: 2. dict at: #c put: 1. self assert: (dict keyAtValue: 2) = #b. self assert: (dict keyAtValue: 1) = #c. "ugly may be a bug, why not having a set #a and #c" self should: [dict keyAtValue: 0] raise: Error ! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:23'! testKeys "self run:#testKeys " | a1 a2 dict | a1 := Association key: 'France' value: 'Paris'. a2 := Association key: 'Italie' value: 'Rome'. dict := Dictionary new. dict add: a1. dict add: a2. self assert: (dict keys size) = 2. self assert: (dict keys includes: #France) ! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'NDCC 3/8/2006 09:13'! testKeysDo "self run: #testKeysDo" "self debug: #testKeysDo" | dict res | dict := Dictionary new. dict at: #a put: 33. dict at: #b put: 66. res := OrderedCollection new. dict keysDo: [ :each | res add: each]. self assert: res asSet = #(a b) asSet. ! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'NDCC 3/8/2006 08:56'! testRemoveKey "self run:#testRemoveKey " | dict | dict := Dictionary new. dict at: #a put: 1. dict at: #b put: 2. self assert: (dict keys size) = 2. dict removeKey: #a. self assert: dict keys size = 1. self should: [dict at: #a] raise: Error. self assert: (dict at: #b) = 2 ! ! !DictionaryTest methodsFor: 'keys and value tests' stamp: 'ndCollectionsTests-Unordered 3/16/2006 10:23'! testValues "self run:#testValues " | a1 a2 a3 dict | a1 := Association key: 'France' value: 'Paris'. a2 := Association key: 'Italie' value: 'Rome'. dict := Dictionary new. dict add: a1. dict add: a2. self assert: (dict values size ) = 2. self assert: (dict values includes: 'Paris'). a3 := Association new. dict add: a3. self assert: (dict values size ) = 3. self assert: (dict values includes: nil). ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DictionaryTest class instanceVariableNames: 'testToto pt1'! StarSqueakTurtle subclass: #DiffusionTurtle instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'StarSqueak-Worlds'! !DiffusionTurtle methodsFor: 'demons' stamp: 'jm 3/3/2001 13:04'! bounce (self turtleCountHere > 1) ifTrue: [ self turnRight: 180 + (self random: 45). self turnLeft: (self random: 45)]. ! ! !DiffusionTurtle methodsFor: 'demons' stamp: 'jm 2/5/2001 19:32'! move self forward: 1. ! ! 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: 'ar 2/1/2001 20:18'! initRandomFromString: aString "Ask the user to type a long random string and use the result to seed the secure random number generator." | s k srcIndex | s _ aString. 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: 'rbb 3/1/2005 10:51'! 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 := UIManager default 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: 'gk 2/26/2004 09:52'! initRandomNonInteractively [self initRandom: (SoundService default randomBitsFromSoundInput: 512)] ifError: [self initRandomFromString: Time millisecondClockValue printString, Date today printString, SmalltalkImage current platformName printString].! ! !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: '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: 'md 7/30/2005 14:12'! signatureToString: aSignature "Answer a string representation of the given signature. This string can be parsed using the stringToSignature: method." | s | s _ WriteStream on: (String new: 2000). s nextPutAll: '[DSA digital signature '. s nextPutAll: aSignature first printStringHex. s space. s nextPutAll: aSignature second printStringHex. 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: '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: '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: 'public' stamp: 'ads 7/31/2003 14:01'! 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 initRandomNonInteractively] 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: '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: 'Files-Directories'! !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: 'multilingual system' stamp: 'stephaneducasse 2/4/2006 20:31'! convertFromSystemName name := (FilePath pathName: name isEncoded: true) asSqueakPathName! ! !DirectoryEntry methodsFor: 'private-initialization' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: #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: 'accessing' stamp: 'tk 3/10/2000 23:50'! constructorArgs ^ constructorArgs! ! !DiskProxy methodsFor: 'accessing' stamp: 'tk 11/6/2000 22:38'! constructorSelector ^ constructorSelector! ! !DiskProxy methodsFor: 'accessing' stamp: 'tk 11/6/2000 22:38'! globalObjectName ^ globalObjectName! ! !DiskProxy methodsFor: 'accessing' stamp: 'tk 11/6/2000 22:35'! preSelector ^ preSelector! ! !DiskProxy methodsFor: 'accessing' stamp: 'tk 4/8/1999 12:54'! preSelector: aSelector preSelector _ aSelector! ! !DiskProxy methodsFor: 'accessing' 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: 'exceptions' stamp: 'tk 3/14/2000 16:27'! enter "Enter the new project" self enter: false revert: false saveForRevert: false.! ! !DiskProxy methodsFor: 'exceptions' stamp: 'dao 10/1/2004 13:31'! 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: [ Project current fromMyServerLoad: 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 methodsFor: 'initialize-release' 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: 'initialize-release' 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: 'i/o' stamp: 'yo 11/14/2002 15:23'! 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: [ preSelector == nil & (constructorSelector = #yourself) ifTrue: [ Transcript cr; show: symbol, ' is undeclared.'. (Undeclared includesKey: symbol) ifTrue: [^ Undeclared at: symbol]. Undeclared at: symbol put: nil. ^ nil]. ^ self error: 'Global "', symbol, '" 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] on: Error do: [:ex | ex messageText = 'key not found' ifTrue: [^ nil]. ^ ex signal]] ]. 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]. Symbol hasInterned: constructorSelector ifTrue: [:selector | [^ globalObj perform: selector withArguments: constructorArgs] on: Error do: [:ex | ex messageText = 'key not found' ifTrue: [^ nil]. ^ ex signal] ]. "args not checked against Renamed" ^ nil "was not in proper form"! ! !DiskProxy methodsFor: 'i/o' 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: 'printing' stamp: 'ar 4/10/2005 18:46'! 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 isString ifFalse: [^ super printOn: aStream]. aStream nextPutAll: constructorArgs first, ' (on server)'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DiskProxy class instanceVariableNames: ''! !DiskProxy class methodsFor: 'instance creation'! 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: '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: '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: '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' stamp: 'hmm 9/16/2000 21:27'! deferUpdatesIn: aRectangle while: aBlock "DisplayScreen overrides with something more involved..." ^aBlock value! ! !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: '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-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: '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: '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 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: 'truncation and round off'! rounded "Convert the offset of the receiver to integer coordinates." self offset: self offset rounded! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '' 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: 'MVC-compatibility' stamp: 'BG 5/31/2003 16:08'! displayLines: linesInterval in: aParagraph clippedBy: visibleRectangle "The central display routine. 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'" morphicOffset _ 0@0. 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. bitBlt destForm deferUpdatesIn: visibleRectangle while: [ linesInterval do: [:lineIndex | line _ aParagraph lines at: lineIndex. lastIndex _ line first. self setStopConditions. " causes an assignment to inst var. alignment " leftMargin _ aParagraph leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment]). 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. self handleIndentation. [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]. fillBlt == nil ifFalse: [fillBlt destX: destX destY: lineY width: visibleRectangle right-destX height: lineHeight; copyBits]. lineY _ lineY + lineHeight]]! ! !DisplayScanner methodsFor: 'MVC-compatibility' stamp: 'yo 3/14/2005 06:48'! initializeFromParagraph: aParagraph clippedBy: clippingRectangle super initializeFromParagraph: aParagraph clippedBy: clippingRectangle. bitBlt _ BitBlt asGrafPort toForm: aParagraph destinationForm. bitBlt sourceX: 0; width: 0. "Init BitBlt so that the first call to a primitive will not fail" bitBlt combinationRule: ((Display depth = 1) ifTrue: [aParagraph rule] ifFalse: [Form paint]). bitBlt colorMap: (Bitmap with: 0 "Assumes 1-bit deep fonts" with: (bitBlt destForm pixelValueFor: aParagraph foregroundColor)). bitBlt clipRect: clippingRectangle! ! !DisplayScanner methodsFor: 'scanning' stamp: 'yo 10/7/2002 18:38'! 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. lineY _ line top + offset y. lineHeight _ line lineHeight. rightMargin _ line rightMargin + offset x. lastIndex _ line first. leftInRun <= 0 ifTrue: [self setStopConditions]. leftMargin _ (line leftMarginForAlignment: alignment) + offset x. destX _ runX _ leftMargin. fillBlt == nil ifFalse: ["Not right" fillBlt destX: line left destY: lineY width: line width left height: lineHeight; copyBits]. lastIndex _ line first. leftInRun <= 0 ifTrue: [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. lastIndex > runStopIndex ifTrue: [done _ true]. ]. ^ runStopIndex - lastIndex "Number of characters remaining in the current run"! ! !DisplayScanner methodsFor: 'scanning' stamp: 'ar 12/17/2001 13:28'! placeEmbeddedObject: anchoredMorph anchoredMorph relativeTextAnchorPosition ifNotNil:[ anchoredMorph position: anchoredMorph relativeTextAnchorPosition + (anchoredMorph owner textBounds origin x @ 0) - (0@morphicOffset y) + (0@lineY). ^true ]. (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: '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: 'hmm 7/16/2000 08:23'! plainTab | oldX | oldX _ destX. super plainTab. fillBlt == nil ifFalse: [fillBlt destX: oldX destY: destY width: destX - oldX height: font height; copyBits]! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'yo 10/4/2002 20:43'! setStopConditions "Set the font and the stop conditions for the current run." self setFont. self setConditionArray: (alignment = Justified ifTrue: [#paddedSpace]). " alignment = Justified ifTrue: [ stopConditions == DefaultStopConditions ifTrue:[stopConditions _ stopConditions copy]. stopConditions at: Space asciiValue + 1 put: #paddedSpace] "! ! !DisplayScanner methodsFor: 'stop conditions' stamp: 'hmm 7/16/2000 08:23'! tab self plainTab. lastIndex _ lastIndex + 1. ^ false! ! !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: 'hmm 9/16/2000 21:29'! setPort: aBitBlt "Install the BitBlt to use" bitBlt _ aBitBlt. bitBlt sourceX: 0; width: 0. "Init BitBlt so that the first call to a primitive will not fail" 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 class instanceVariableNames: ''! !DisplayScanner class methodsFor: 'queries' stamp: 'ar 5/17/2000 17:39'! defaultFont ^ TextStyle defaultFont! ! Form subclass: #DisplayScreen instanceVariableNames: 'clippingBox extraRegions' classVariableNames: 'DeferringUpdates DisplayChangeSignature 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: '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 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: 'displaying' stamp: 'ar 4/19/2001 05:44'! addExtraRegion: aRectangle for: regionDrawer "Register the given rectangle as a region which is drawn by the specified region drawer. The region will be excluded from any updates when #forceDamageToScreen: is called. Note that the rectangle is only valid for a single update cycle; once #forceDamageToScreen: has been called, the region drawer and its region are being removed from the list" extraRegions ifNil:[extraRegions _ #()]. extraRegions _ extraRegions copyWith: (Array with: regionDrawer with: aRectangle). ! ! !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: 'displaying' stamp: 'sw 1/1/2005 01:31'! flashAll: rectangleList andWait: msecs "Flash the areas of the screen defined by the given rectangles." rectangleList do: [:aRectangle | self reverse: aRectangle]. self forceDisplayUpdate. (Delay forMilliseconds: msecs) wait. rectangleList do: [:aRectangle | self reverse: aRectangle]. self forceDisplayUpdate. (Delay forMilliseconds: msecs) wait. ! ! !DisplayScreen methodsFor: 'displaying' stamp: 'ar 5/15/2001 20:08'! forceDamageToScreen: allDamage "Force all the damage rects to the screen." | rectList excluded remaining regions | rectList _ allDamage. "Note: Reset extra regions at the beginning to prevent repeated errors" regions _ extraRegions. extraRegions _ nil. regions ifNotNil:[ "exclude extra regions" regions do:[:drawerAndRect| excluded _ drawerAndRect at: 2. remaining _ WriteStream on: #(). rectList do:[:r| remaining nextPutAll:(r areasOutside: excluded)]. rectList _ remaining contents]. ]. rectList do:[:r| self forceToScreen: r]. regions ifNotNil:[ "Have the drawers paint what is needed" regions do:[:drawerAndRect| (drawerAndRect at: 1) forceToScreen]. ].! ! !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: 'other'! boundingBox clippingBox == nil ifTrue: [clippingBox _ super boundingBox]. ^ clippingBox! ! !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: 'hmm 6/18/2000 19:16'! deferUpdates: aBoolean | wasDeferred | "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 whether updates were deferred before if the primitive succeeds, nil if it fails." wasDeferred _ DeferringUpdates == true. DeferringUpdates _ aBoolean. ^(self primitiveDeferUpdates: aBoolean) ifNotNil: [wasDeferred]! ! !DisplayScreen methodsFor: 'other' stamp: 'hmm 2/2/2001 10:14'! deferUpdatesIn: aRectangle while: aBlock | result | (self deferUpdates: true) ifTrue: [^aBlock value]. result _ aBlock value. self deferUpdates: false. self forceToScreen: aRectangle. ^result! ! !DisplayScreen methodsFor: 'other' stamp: 'RAA 11/27/1999 15:48'! displayChangeSignature ^DisplayChangeSignature! ! !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: 'sd 6/7/2003 19:46'! fullScreenMode: aBoolean "On platforms that support it, set full-screen mode to the value of the argument. (Note: you'll need to restore the Display after calling this primitive." "Display fullScreenMode: true. Display newDepth: Display depth" self primitiveFailed ! ! !DisplayScreen methodsFor: 'other'! height ^ self boundingBox height! ! !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: 'hmm 6/18/2000 19:14'! primitiveDeferUpdates: 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'! 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: 'ar 3/17/2001 23:53'! restore Smalltalk isMorphic ifTrue: [World fullRepaintNeeded] ifFalse: [ScheduledControllers unCacheWindows; restore].! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 3/17/2001 23:53'! restoreAfter: aBlock "Evaluate the block, wait for a mouse click, and then restore the screen." aBlock value. Sensor waitButton. Smalltalk isMorphic ifTrue: [World fullRepaintNeeded] ifFalse: [(ScheduledControllers restore; activeController) view emphasize]! ! !DisplayScreen methodsFor: 'other' stamp: 'ar 5/17/2001 21:02'! supportedDisplayDepths "Return all pixel depths supported on the current host platform." ^#(1 2 4 8 16 32 -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'! width ^ self boundingBox width! ! !DisplayScreen methodsFor: 'testing' stamp: 'ar 5/25/2000 23:34'! isDisplayScreen ^true! ! !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/17/2001 21:03'! findAnyDisplayDepthIfNone: aBlock "Return any display depth that is supported on this system. If there is none, evaluate aBlock." #(1 2 4 8 16 32 -1 -2 -4 -8 -16 -32) do:[:bpp| (self supportsDisplayDepth: bpp) ifTrue:[^bpp]. ]. ^aBlock value! ! !DisplayScreen methodsFor: 'private' stamp: 'ar 5/17/2001 15:44'! newDepthNoRestore: pixelSize "Change depths. Check if there is enough space!! , di" | area need | pixelSize = depth ifTrue: [^ self "no change"]. pixelSize abs < self 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 abs - self 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: 'bf 5/16/2006 11:35'! 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:[(self supportsDisplayDepth: bitsPerPixel negated) ifTrue:[super setExtent: aPoint depth: bitsPerPixel negated] ifFalse:["Search for a suitable depth" super setExtent: aPoint depth: self findAnyDisplayDepth]]. ]. clippingBox _ super boundingBox! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'sw 10/31/2001 07:18'! checkForNewScreenSize "Check whether the screen size has changed and if so take appropriate actions" Display extent = DisplayScreen actualScreenSize ifTrue: [^ self]. DisplayScreen startUp. Smalltalk isMorphic ifTrue: [World restoreMorphicDisplay. World repositionFlapsAfterScreenSizeChange] 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' stamp: 'ar 2/5/2001 17:24'! actualScreenDepth ^ Display depth! ! !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: 'ar 5/17/2001 15:50'! startUp "DisplayScreen startUp" Display setExtent: self actualScreenSize depth: Display nativeDepth. 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: '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 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: '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: 'displaying' stamp: 'yo 6/23/2003 20:05'! displayOn: aDisplayMedium at: aDisplayPoint clippingBox: clipRectangle rule: ruleInteger fillColor: aForm "For TT font, rule 34 is used if possible." "Refer to the comment in DisplayObject|displayOn:at:clippingBox:rule:mask:." | form1 rule | form1 _ self form. rule _ (ruleInteger = Form over and: [backColor isTransparent]) ifTrue: [form1 depth = 32 ifTrue: [rule _ 34] ifFalse: [Form paint]] ifFalse: [ruleInteger]. form1 depth = 32 ifTrue: [rule _ 34]. form1 displayOn: aDisplayMedium at: aDisplayPoint + offset clippingBox: clipRectangle rule: rule 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: 'private' stamp: 'nk 6/25/2003 12:51'! composeForm "For the TT strings in MVC widgets in a Morphic world such as a progress bar, the form is created by Morphic machinery." | canvas tmpText | Smalltalk isMorphic ifTrue: [tmpText _ TextMorph new contentsAsIs: text deepCopy. foreColor ifNotNil: [tmpText text addAttribute: (TextColor color: foreColor)]. backColor ifNotNil: [tmpText backgroundColor: backColor]. tmpText setTextStyle: textStyle. canvas _ FormCanvas on: (Form extent: tmpText extent depth: 32). tmpText drawOn: canvas. form _ canvas form. ] ifFalse: [form _ self asParagraph asForm]! ! !DisplayText methodsFor: 'private'! setText: aText textStyle: aTextStyle offset: aPoint text _ aText. textStyle _ aTextStyle. offset _ aPoint. form _ nil! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DisplayText class instanceVariableNames: ''! !DisplayText class methodsFor: 'examples' stamp: 'tk 11/28/2001 16:03'! example "Continually prints two lines of text wherever you point with the cursor. 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."! ! !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! ! 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: '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: 'deEmphasizing'! deEmphasizeView "Refer to the comment in View|deEmphasizeView." (self controller isKindOf: ParagraphEditor) ifTrue: [controller deselect]! ! !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: 'initialize-release'! initialize "Refer to the comment in View|initialize." super initialize. centered _ false! ! !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 methodsFor: 'model access'! model: aDisplayText "Refer to the comment in View|model:." super model: aDisplayText. editParagraph _ model asParagraph. self centerText! ! !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: '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 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: 'accessing' stamp: 'ar 11/2/1998 19:43'! inverseTransformation "Return the inverse transformation of the receiver" ^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: '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: '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: '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: '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: 'gh 10/22/2001 13:24'! invertBoundsRect: aRectangle "Return a rectangle whose coordinates have been transformed from local back to global coordinates." ^self subclassResponsibility! ! !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: '*nebraska-*nebraska-Morphic-Remote' 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: 'ar 11/2/1998 20:55'! identity ^self new setIdentity! ! !DisplayTransform class methodsFor: '*nebraska-instance creation' stamp: 'sd 11/20/2005 21:25'! 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'! ! 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: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color blue! ! !DoCommandOnceMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 8! ! !DoCommandOnceMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:08'! initialize "initialize the state of the receiver" super initialize. "" self useRoundedCorners! ! !DoCommandOnceMorph methodsFor: 'initialization' stamp: 'RAA 7/7/2000 12:19'! openInWorld: aWorld self position: aWorld topLeft + (aWorld extent - self extent // 2). super openInWorld: aWorld! ! !DoCommandOnceMorph methodsFor: 'stepping and presenter' 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: 'testing' stamp: 'RAA 7/7/2000 11:46'! stepTime ^1 ! ! !DoCommandOnceMorph methodsFor: 'testing' stamp: 'RAA 7/7/2000 11:50'! wantsSteps ^actionBlock notNil ! ! AbstractEvent subclass: #DoItEvent instanceVariableNames: 'context' classVariableNames: '' poolDictionaries: '' category: 'System-Change Notification'! !DoItEvent methodsFor: 'accessing' stamp: 'rw 7/14/2003 11:29'! context ^context! ! !DoItEvent methodsFor: 'printing' stamp: 'rw 7/14/2003 10:15'! printEventKindOn: aStream aStream nextPutAll: 'DoIt'! ! !DoItEvent methodsFor: 'testing' stamp: 'rw 7/14/2003 10:15'! isDoIt ^true! ! !DoItEvent methodsFor: 'private-accessing' stamp: 'rw 7/14/2003 11:29'! context: aContext context := aContext! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DoItEvent class instanceVariableNames: ''! !DoItEvent class methodsFor: 'accessing' stamp: 'rw 7/14/2003 10:19'! changeKind ^#DoIt! ! !DoItEvent class methodsFor: 'accessing' stamp: 'NS 1/20/2004 12:23'! supportedKinds ^ Array with: self expressionKind! ! !DoItEvent class methodsFor: 'instance creation' stamp: 'NS 1/19/2004 09:47'! expression: stringOrStream context: aContext | instance | instance := self item: stringOrStream kind: AbstractEvent expressionKind. instance context: aContext. ^instance! ! AlignmentMorph subclass: #DockingBarMorph instanceVariableNames: 'originalColor gradientRamp fillsOwner avoidVisibleBordersAtEdge autoGradient selectedItem activeSubMenu' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Menus'! !DockingBarMorph methodsFor: 'dropping/grabbing' stamp: 'dgd 9/7/2004 14:47'! aboutToBeGrabbedBy: aHand "The morph is about to be grabbed, make it float" self beFloating. self updateBounds. self updateColor. (self bounds containsPoint: aHand position) ifFalse: [self center: aHand position]. self owner restoreFlapsDisplay! ! !DockingBarMorph methodsFor: 'dropping/grabbing' stamp: 'dgd 8/31/2004 14:37'! justDroppedInto: aMorph event: anEvent | ownerBounds leftRegion droppedPosition rightRegion topRegion bottomRegion | super justDroppedInto: aMorph event: anEvent. "" self owner isNil ifTrue: [^ self]. "" ownerBounds := aMorph bounds. topRegion := ownerBounds bottom: ownerBounds top + (ownerBounds height // 5). bottomRegion := ownerBounds top: ownerBounds bottom - (ownerBounds height // 5). "" leftRegion := ownerBounds right: ownerBounds left + (ownerBounds width // 5). leftRegion := leftRegion top: topRegion bottom. leftRegion := leftRegion bottom: bottomRegion top. "" rightRegion := ownerBounds left: ownerBounds right - (ownerBounds width // 5). rightRegion := rightRegion top: topRegion bottom. rightRegion := rightRegion bottom: bottomRegion top. "" droppedPosition := anEvent position. (topRegion containsPoint: droppedPosition) ifTrue: [ ^ self adhereToTop]. (bottomRegion containsPoint: droppedPosition) ifTrue: [ ^ self adhereToBottom]. (leftRegion containsPoint: droppedPosition) ifTrue: [ ^ self adhereToLeft]. (rightRegion containsPoint: droppedPosition) ifTrue: [ ^ self adhereToRight]. "" self beFloating! ! !DockingBarMorph methodsFor: 'events' stamp: 'dgd 9/1/2004 19:29'! activate: evt "Receiver should be activated; e.g., so that control passes correctly." evt hand newMouseFocus: self! ! !DockingBarMorph methodsFor: 'control' stamp: 'dgd 9/9/2004 21:48'! activeSubmenu: aSubmenu activeSubMenu isNil ifFalse: [activeSubMenu delete]. activeSubMenu := aSubmenu. aSubmenu isNil ifTrue: [^ self]. "" activeSubMenu selectItem: nil event: nil. MenuIcons decorateMenu: activeSubMenu. activeSubMenu activatedFromDockingBar: self. activeSubMenu borderColor: self borderColor. activeSubMenu beSticky. activeSubMenu resistsRemoval: true. activeSubMenu removeMatchString.! ! !DockingBarMorph methodsFor: 'control' stamp: 'dgd 9/1/2004 16:48'! deleteIfPopUp: evt evt ifNotNil: [evt hand releaseMouseFocus: self]! ! !DockingBarMorph methodsFor: 'control' stamp: 'dgd 9/1/2004 16:40'! selectItem: aMenuItem event: anEvent selectedItem ifNotNil: [selectedItem deselect: anEvent]. selectedItem := aMenuItem. selectedItem ifNotNil: [selectedItem select: anEvent]! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 9/13/2004 19:59'! addBlankIconsIfNecessary: anIcon "If any of my items have an icon, ensure that all do by using anIcon for those that don't" self items reject: [:each | each hasIconOrMarker] thenDo: [:each | each icon: anIcon]! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 12:01'! adhereToBottom "Instract the receiver to adhere to bottom" self adhereTo:#bottom! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 12:01'! adhereToLeft "Instract the receiver to adhere to left" self adhereTo: #left! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 12:01'! adhereToRight "Instract the receiver to adhere to right" self adhereTo: #right! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 12:01'! adhereToTop "Instract the receiver to adhere to top" self adhereTo: #top! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 14:28'! autoGradient "Answer if the receiver is in autoGradient mode" ^ autoGradient! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 14:28'! autoGradient: aBoolean "Instruct the receiver to fill the owner or not" autoGradient := aBoolean. self updateColor! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 13:38'! avoidVisibleBordersAtEdge "Answer if the receiver is in avoidVisibleBordersAtEdge mode" ^ avoidVisibleBordersAtEdge! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 13:57'! avoidVisibleBordersAtEdge: aBoolean "Instruct the receiver to avoid showing the borders at edge" avoidVisibleBordersAtEdge := aBoolean. self updateLayoutProperties.! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 12:02'! beFloating "Instract the receiver to be floating" self adhereTo: #none! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 11/2/2004 12:00'! color: aColor "Set the receiver's color." super color: aColor. originalColor := aColor asColor. "" self updateColor! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 13:35'! fillsOwner "Answer if the receiver is in fillOwner mode" ^ fillsOwner! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 13:57'! fillsOwner: aBoolean "Instruct the receiver to fill the owner or not" fillsOwner := aBoolean. self updateLayoutProperties! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:57'! isAdheringToBottom "Answer true if the receiver is adhering to bottom" ^ self edgeToAdhereTo == #bottom! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:57'! isAdheringToLeft "Answer true if the receiver is adhering to left" ^ self edgeToAdhereTo == #left! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:57'! isAdheringToRight "Answer true if the receiver is adhering to right" ^ self edgeToAdhereTo == #right! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:57'! isAdheringToTop "Answer true if the receiver is adhering to top" ^ self edgeToAdhereTo == #top! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/30/2004 23:13'! isFloating "Answer true if the receiver has a float layout" ^ self isHorizontal not and: [self isVertical not]! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:58'! isHorizontal "Answer true if the receiver has a horizontal layout" ^ self isAdheringToTop or: [self isAdheringToBottom]! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 9/1/2004 21:23'! isSticky "answer whether the receiver is Sticky" ^ Preferences noviceMode or: [super isSticky] ! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 11:58'! isVertical "Answer true if the receiver has a vertical layout" ^ self isAdheringToLeft or: [self isAdheringToRight] ! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 9/2/2004 11:01'! resistsRemoval "Answer whether the receiver is marked as resisting removal" ^ Preferences noviceMode or: [super resistsRemoval]! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 9/9/2004 19:45'! rootMenu ^ self! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 9/1/2004 16:39'! stayUp ^ false! ! !DockingBarMorph methodsFor: 'accessing' stamp: 'dgd 8/31/2004 16:33'! wantsToBeTopmost "Answer if the receiver want to be one of the topmost objects in its owner" ^ true! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:31'! addCustomMenuItems: aMenu hand: aHandMorph "Populate aMenu with appropriate menu items for a yellow-button (context menu) click." super addCustomMenuItems: aMenu hand: aHandMorph. "" aMenu addLine. aMenu addUpdating: #autoGradientString action: #toggleAutoGradient. self isFloating ifFalse: ["" aMenu addUpdating: #fillsOwnerString action: #toggleFillsOwner. aMenu addUpdating: #avoidVisibleBordersAtEdgeString action: #toggleAvoidVisibleBordersAtEdge]! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:25'! autoGradientString "Answer the string to be shown in a menu to represent the 'resistsRemoval' status" ^ (self autoGradient ifTrue: [''] ifFalse: ['']) , 'auto gradient' translated! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:25'! avoidVisibleBordersAtEdgeString "Answer the string to be shown in a menu to represent the 'resistsRemoval' status" ^ (self avoidVisibleBordersAtEdge ifTrue: [''] ifFalse: ['']) , 'avoid visible borders at edge' translated! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:23'! fillsOwnerString "Answer the string to be shown in a menu to represent the 'resistsRemoval' status" ^ (self fillsOwner ifTrue: [''] ifFalse: ['']) , 'fills owner' translated ! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:27'! toggleAutoGradient self autoGradient: self autoGradient not! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:26'! toggleAvoidVisibleBordersAtEdge self avoidVisibleBordersAtEdge: self avoidVisibleBordersAtEdge not! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/29/2004 20:24'! toggleFillsOwner self fillsOwner: self fillsOwner not! ! !DockingBarMorph methodsFor: 'menu' stamp: 'dgd 9/18/2004 18:28'! wantsYellowButtonMenu "Answer true if the receiver wants a yellow button menu" ^ Preferences noviceMode not! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 7/28/2005 12:08'! addDefaultSpace "Add a new space of the given size to the receiver." ^ self addSpace: (Preferences tinyDisplay ifFalse:[10] ifTrue:[3])! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 9/1/2004 19:10'! addLine "Append a divider line to this menu. Suppress duplicate lines." submorphs isEmpty ifTrue: [^ self]. (self lastSubmorph isKindOf: MenuLineMorph) ifFalse: [self addMorphBack: MenuLineMorph new]. ! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 8/31/2004 11:34'! addSpacer "Add a new spacer to the receiver. Spacer are objects that try to use as much space as they can" self addMorphBack: (AlignmentMorph newSpacer: Color transparent)! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 8/31/2004 11:34'! addSpace: sizePointOrNumber "Add a new space of the given size to the receiver." | space | space := RectangleMorph new. space extent: sizePointOrNumber asPoint. space color: Color transparent. space borderWidth: 0. self addMorphBack: space! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 9/10/2004 16:48'! add: wordingString icon: aForm help: helpString subMenu: aMenuMorph "Append the given submenu with the given label." | item | item := MenuItemMorph new. item contents: wordingString. item subMenu: aMenuMorph. item icon: aForm. helpString isNil ifFalse: [item setBalloonText: helpString]. self addMorphBack: item! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 9/10/2004 16:48'! add: wordingString icon: aForm subMenu: aMenuMorph "Append the given submenu with the given label." ^ self add: wordingString icon: aForm help: nil subMenu: aMenuMorph ! ! !DockingBarMorph methodsFor: 'construction' stamp: 'dgd 9/1/2004 19:08'! add: aString subMenu: aMenuMorph "Append the given submenu with the given label." self add: aString icon: nil subMenu: aMenuMorph ! ! !DockingBarMorph methodsFor: 'private - accessing' stamp: 'dgd 8/31/2004 14:35'! adhereTo: edgeSymbol "Private - Instruct the receiver to adhere to the given edge. Options: #left #top #right #bottom or #none" "" (#(#left #top #right #bottom #none ) includes: edgeSymbol) ifFalse: [^ self error: 'invalid option']. "" self setToAdhereToEdge: edgeSymbol. self updateLayoutProperties. self updateColor! ! !DockingBarMorph methodsFor: 'private - accessing' stamp: 'dgd 8/31/2004 13:56'! edgeToAdhereTo "private - answer the edge where the receiver is adhering to" ^ self valueOfProperty: #edgeToAdhereTo ifAbsent: [#none]! ! !DockingBarMorph methodsFor: 'private - accessing' stamp: 'dgd 9/1/2004 15:19'! predominantDockingBarsOfChastes: predominantChastes "Private - Answer a collection of the docking bar of my owner that are predominant to the receiver. By 'predominant' we mean docking bar that have the right to get a position before the receiver. The predominance of individual living in the same chaste is determinated by the arrival order. " | allDockingBars byChaste byArrival | (self owner isNil or: [self owner isHandMorph]) ifTrue: [^ #()]. "" allDockingBars := self owner dockingBars. "" byChaste := allDockingBars select: [:each | predominantChastes includes: each edgeToAdhereTo]. "" (predominantChastes includes: self edgeToAdhereTo) ifFalse: [^ byChaste]. "" byChaste := byChaste reject: [:each | each edgeToAdhereTo = self edgeToAdhereTo]. "" byArrival := allDockingBars select: [:each | each edgeToAdhereTo = self edgeToAdhereTo]. byArrival := byArrival copyAfter: self. "" ^ byChaste , byArrival! ! !DockingBarMorph methodsFor: 'private - accessing' stamp: 'dgd 9/1/2004 19:39'! usedHeightByPredominantDockingBarsOfChastes: predominantChastes "Private - convenience" | predominants | predominants := self predominantDockingBarsOfChastes: predominantChastes. ^ predominants isEmpty ifTrue: [0] ifFalse: [(predominants collect: [:each | each height]) sum]! ! !DockingBarMorph methodsFor: 'private - accessing' stamp: 'dgd 9/1/2004 19:38'! usedWidthByPredominantDockingBarsOfChastes: predominantChastes "Private - convenience" | predominants | predominants := self predominantDockingBarsOfChastes: predominantChastes. ^ predominants isEmpty ifTrue: [0] ifFalse: [(predominants collect: [:each | each width]) sum]! ! !DockingBarMorph methodsFor: 'meta-actions' stamp: 'dgd 9/1/2004 22:11'! blueButtonDown: anEvent "Special gestures (cmd-mouse on the Macintosh; Alt-mouse on Windows and Unix) allow a mouse-sensitive morph to be moved or bring up a halo for the morph." "In NoviceMode we don't want halos" Preferences noviceMode ifFalse: [super blueButtonDown: anEvent] ! ! !DockingBarMorph methodsFor: 'submorphs-add/remove' stamp: 'dgd 9/1/2004 19:26'! delete activeSubMenu ifNotNil: [activeSubMenu delete]. ^ super delete! ! !DockingBarMorph methodsFor: 'geometry' stamp: 'dgd 8/31/2004 14:29'! extent: aPoint "change the receiver's extent" super extent: aPoint. self updateColor! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 11/2/2004 11:59'! gradientRamp ^ gradientRamp ifNil:[{0.0 -> originalColor muchLighter. 1.0 -> originalColor twiceDarker}]! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 11/2/2004 12:00'! gradientRamp: colorRamp gradientRamp := colorRamp. "" self updateColor! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 9/1/2004 15:29'! updateBounds "private - update the receiver's bounds" self updateExtent. self isFloating ifFalse: [self updatePosition]! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 11/2/2004 11:55'! updateColor "private - update the receiver's color" | fill | self autoGradient ifFalse: [^ self]. "" fill := GradientFillStyle ramp: self gradientRamp. "" fill origin: self topLeft. self isVertical ifTrue: [fill direction: self width @ 0] ifFalse: [fill direction: 0 @ self height]. "" self fillStyle: fill! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 9/1/2004 15:20'! updateExtent "private - update the receiver's extent" | margin | self fullBounds. self fillsOwner ifFalse: [^ self]. "" margin := self avoidVisibleBordersAtEdge ifTrue: [self borderWidth * 2] ifFalse: [0]."" self isHorizontal ifTrue: [self width: self owner width + margin]."" self isVertical ifTrue: [| usedHeight | usedHeight := self usedHeightByPredominantDockingBarsOfChastes: #(#top #bottom ). self height: self owner height + margin - usedHeight]! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 8/31/2004 14:03'! updateLayoutProperties "private - update the layout properties based on adhering, fillsOwner and avoidVisibleBordersAtEdge preferencs" "" (self isHorizontal or: [self isFloating]) ifTrue: [self listDirection: #leftToRight] ifFalse: [self listDirection: #topToBottom]. "" self hResizing: #shrinkWrap. self vResizing: #shrinkWrap. self fillsOwner ifTrue: ["" self isHorizontal ifTrue: [self hResizing: #spaceFill]. self isVertical ifTrue: [self vResizing: #spaceFill]]. ! ! !DockingBarMorph methodsFor: 'private - layout' stamp: 'dgd 9/1/2004 13:06'! updatePosition "private - update the receiver's position" | edgeSymbol margin | edgeSymbol := self edgeToAdhereTo. self perform: (edgeSymbol , ':') asSymbol with: (self owner perform: edgeSymbol). "" margin := self avoidVisibleBordersAtEdge ifTrue: [self borderWidth asPoint] ifFalse: [0 asPoint]. "" self isAdheringToTop ifTrue: [| usedHeight | usedHeight := self usedHeightByPredominantDockingBarsOfChastes: #(#top ). self topLeft: self owner topLeft - margin + (0 @ usedHeight)]. self isAdheringToBottom ifTrue: [| usedHeight | usedHeight := self usedHeightByPredominantDockingBarsOfChastes: #(#bottom ). self bottomLeft: self owner bottomLeft + (-1 @ 1 * margin) - (0 @ usedHeight)]. "" self isAdheringToLeft ifTrue: [| usedHeight usedWidth | usedHeight := self usedHeightByPredominantDockingBarsOfChastes: #(#top ). usedWidth := self usedWidthByPredominantDockingBarsOfChastes: #(#left ). self topLeft: self owner topLeft - margin + (usedWidth @ usedHeight)]. self isAdheringToRight ifTrue: [| usedHeight usedWidth | usedHeight := self usedHeightByPredominantDockingBarsOfChastes: #(#top ). usedWidth := self usedWidthByPredominantDockingBarsOfChastes: #(#right ). self topRight: self owner topRight + (1 @ -1 * margin) + (usedWidth negated @ usedHeight)]! ! !DockingBarMorph methodsFor: 'events-processing' stamp: 'dgd 9/9/2004 21:43'! handleFocusEvent: evt "Handle focus events. Valid menu transitions are determined based on the menu currently holding the focus after the mouse went down on one of its children." (evt isMouse and:[ evt isMouseUp ]) ifTrue:[^ self]. self processEvent: evt. "Need to handle keyboard input if we have the focus." evt isKeyboard ifTrue: [^ self handleEvent: evt]. "We need to handle button clicks outside and transitions to local popUps so throw away everything else" (evt isMouseOver or:[evt isMouse not]) ifTrue:[^self]. "What remains are mouse buttons and moves" evt isMove ifFalse:[^self handleEvent: evt]. "handle clicks outside by regular means" "Now it's getting tricky. On #mouseMove we might transfer control to *either* the currently active submenu or the pop up owner, if any. Since the active sub menu is always displayed upfront check it first." selectedItem ifNotNil:[(selectedItem activateSubmenu: evt) ifTrue:[^self]]. ! ! !DockingBarMorph methodsFor: 'initialization' stamp: 'dgd 9/2/2004 11:03'! initialize "initialize the receiver" super initialize. "" selectedItem := nil. activeSubMenu := nil. fillsOwner := true. avoidVisibleBordersAtEdge := true. autoGradient := Preferences gradientMenu. "" self setDefaultParameters. "" self beFloating. "" self layoutInset: 0. ! ! !DockingBarMorph methodsFor: 'initialization' stamp: 'dgd 8/30/2004 22:17'! setDefaultParameters "private - set the default parameter using Preferences as the inspiration source" | colorFromMenu worldColor menuColor menuBorderColor | colorFromMenu := Preferences menuColorFromWorld and: [Display depth > 4] and: [(worldColor := self currentWorld color) isColor]. "" menuColor := colorFromMenu ifTrue: [worldColor luminance > 0.7 ifTrue: [worldColor mixed: 0.85 with: Color black] ifFalse: [worldColor mixed: 0.4 with: Color white]] ifFalse: [Preferences menuColor]. "" menuBorderColor := Preferences menuAppearance3d ifTrue: [#raised] ifFalse: [colorFromMenu ifTrue: [worldColor muchDarker] ifFalse: [Preferences menuBorderColor]]. "" self setColor: menuColor borderWidth: Preferences menuBorderWidth borderColor: menuBorderColor! ! !DockingBarMorph methodsFor: 'testing' stamp: 'dgd 8/31/2004 15:00'! isDockingBar "Return true if the receiver is a docking bar" ^ true! ! !DockingBarMorph methodsFor: 'WiW support' stamp: 'dgd 9/7/2004 19:25'! morphicLayerNumber "helpful for insuring some morphs always appear in front of or behind others. smaller numbers are in front" ^ 11! ! !DockingBarMorph methodsFor: 'submorphs-accessing' stamp: 'dgd 9/1/2004 18:41'! noteNewOwner: aMorph "I have just been added as a submorph of aMorph" super noteNewOwner: aMorph. self submorphs do: [:each | each adjustLayoutBounds]. ! ! !DockingBarMorph methodsFor: 'change reporting' stamp: 'dgd 9/1/2004 15:29'! ownerChanged "The receiver's owner has changed its layout. " self updateBounds. ^ super ownerChanged! ! !DockingBarMorph methodsFor: 'rounding' stamp: 'dgd 8/31/2004 14:16'! roundedCorners "Return a list of those corners to round" self isAdheringToTop ifTrue: [^ #(2 3 )]. self isAdheringToBottom ifTrue: [^ #(1 4 )]. self isAdheringToLeft ifTrue: [^ #(3 4 )]. self isAdheringToRight ifTrue: [^ #(1 2 )]. ^ #(1 2 3 4 )! ! !DockingBarMorph methodsFor: 'private' stamp: 'dgd 9/9/2004 21:24'! selectedItem selectedItem isNil ifTrue: [^ nil]. ^ selectedItem isSelected ifTrue: [ selectedItem] ifFalse: [ nil]! ! !DockingBarMorph methodsFor: 'menus' stamp: 'dgd 9/1/2004 15:29'! snapToEdgeIfAppropriate (self owner isNil or: [self owner isHandMorph]) ifTrue: [^ self]. "" self updateBounds! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DockingBarMorph class instanceVariableNames: ''! !DockingBarMorph class methodsFor: 'scripting' stamp: 'dgd 8/31/2004 14:26'! defaultNameStemForInstances ^ 'DockingBar'! ! !DockingBarMorph class methodsFor: 'samples' stamp: 'dgd 9/1/2004 19:52'! example1 " DockingBarMorph example1. (Color lightBlue wheel: 4) do:[:c | DockingBarMorph example1 color: c; borderColor: c twiceDarker]. World deleteDockingBars. " | instance | instance := DockingBarMorph new. "" instance addSpace: 10. instance addMorphBack: (ClockMorph new show24hr: true). instance addSpacer. instance addMorphBack: (ClockMorph new show24hr: true). instance addSpace: 10. "" instance adhereToTop. "" instance autoGradient: true. instance layoutInset: 10. "" ^ instance openInWorld! ! !DockingBarMorph class methodsFor: 'samples' stamp: 'dgd 9/22/2004 18:53'! example2 " DockingBarMorph example2. World deleteDockingBars. " | menu | menu := DockingBarMorph new. "" menu addSpace: 10. menu add: 'Squeak' icon: MenuIcons smallConfigurationIcon subMenu: self squeakMenu. menu add: 'Configuration' icon: MenuIcons smallWindowIcon subMenu: self squeakMenu. menu addSpace: 10. menu addLine. menu addSpace: 10. menu addMorphBack: (ImageMorph new image: MenuIcons smallBackIcon). menu addSpace: 10. menu addMorphBack: (ImageMorph new image: MenuIcons smallForwardIcon). menu addSpace: 10. menu addLine. menu addSpacer. "" menu addMorphBack: ProjectNavigationMorph new speakerIcon. "" menu addSpace: 10. menu addMorphBack: (ClockMorph new show24hr: true). menu addSpace: 10. "" menu adhereToTop. "" menu autoGradient: true. "" ^ menu openInWorld! ! !DockingBarMorph class methodsFor: 'samples' stamp: 'dgd 9/1/2004 19:55'! example3 " DockingBarMorph example3. World deleteDockingBars. " (Color lightBlue wheel: 4) with: #(#top #bottom #left #right ) do: [:col :edge | | instance | instance := DockingBarMorph example1. instance adhereTo: edge. instance color: col. instance borderColor: col twiceDarker]! ! !DockingBarMorph class methodsFor: 'samples' stamp: 'dgd 9/1/2004 20:12'! squeakMenu | menu | menu := MenuMorph new defaultTarget: self. menu add: 'Hello' target: self selector: #inform: argument: 'Hello World!!'. menu add: 'Long Hello' target: self selector: #inform: argument: 'Helloooo World!!'. menu add: 'A very long Hello' target: self selector: #inform: argument: 'Hellooooooooooooooo World!!'. menu add: 'An incredible long Hello' target: self selector: #inform: argument: 'Hellooooooooooooooooooooooo World!!'. ^ menu! ! FileDirectory subclass: #DosFileDirectory instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Files-Directories'! !DosFileDirectory commentStamp: '' prior: 0! I represent a DOS or Windows FileDirectory. ! !DosFileDirectory methodsFor: 'as yet unclassified' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! driveName "return a possible drive letter and colon at the start of a Path name, empty string otherwise" | firstTwoChars | ( pathName asSqueakPathName size >= 2 ) ifTrue: [ firstTwoChars := (pathName asSqueakPathName copyFrom: 1 to: 2). (self class isDrive: firstTwoChars) ifTrue: [^firstTwoChars] ]. ^''! ! !DosFileDirectory methodsFor: 'path access' stamp: 'nk 7/18/2004 17:26'! 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." fileName ifNil:[^fileName]. "Check for fully qualified names" ((fileName size >= 2 and: [fileName first isLetter and: [fileName second = $:]]) or: [(fileName beginsWith: '\\') and: [(fileName occurrencesOf: $\) >= 2]]) ifTrue:[^fileName]. ^super fullNameFor: fileName! ! !DosFileDirectory methodsFor: 'path access' stamp: 'yo 12/19/2003 21:15'! fullPathFor: path "Return the fully-qualified path name for the given file." path isEmpty ifTrue:[^pathName asSqueakPathName]. (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 asSqueakPathName, self slash, path! ! !DosFileDirectory methodsFor: 'path access' stamp: 'yo 12/19/2003 21:15'! relativeNameFor: path "Return the full name for path, assuming that path is a name relative to me." path isEmpty ifTrue:[^pathName asSqueakPathName]. (path at: 1) = $\ ifTrue:[ (path size >= 2 and:[(path at: 2) = $\]) ifTrue:[^super relativeNameFor: path allButFirst ]. "e.g., \\pipe\" ^super relativeNameFor: path "e.g., \windows\"]. (path size >= 2 and:[(path at: 2) = $: and:[path first isLetter]]) ifTrue:[^super relativeNameFor: (path copyFrom: 3 to: path size) ]. "e.g., c:" ^pathName asSqueakPathName, 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: 'ar 3/6/2004 03:46'! isDrive: fullName "Answer whether the given full name describes a 'drive', e.g., one of the root directories of a Win32 file system. We allow two forms here - the classic one where a drive is specified by a letter followed by a colon, e.g., 'C:', 'D:' etc. and the network share form starting with double-backslashes e.g., '\\server'." ^ (fullName size = 2 and: [fullName first isLetter and: [fullName last = $:]]) or: [(fullName beginsWith: '\\') and: [(fullName occurrencesOf: $\) = 2]]! ! !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: 'ar 3/6/2004 04:14'! 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. IMPORTANT NOTE: For 'drives', e.g., roots of the file system on Windows we treat the full name of that 'drive' as the local name rather than the path. This is because conceptually, all of these 'drives' hang off the virtual root of the entire Squeak file system, specified by FileDirectory root. In order to be consistent with, e.g., DosFileDirectory localNameFor: 'C:\Windows' -> 'Windows' DosFileDirectory dirPathFor: 'C:\Windows' -> 'C:' we expect the following to be true: DosFileDirectory localNameFor: 'C:' -> 'C:' DosFileDirectory dirPathFor: 'C:'. -> '' DosFileDirectory localNameFor: '\\server' -> '\\server'. DosFileDirectory dirPathFor: '\\server' -> ''. so that in turn the following relations hold: | fd | fd := DosFileDirectory on: 'C:\Windows'. fd containingDirectory includes: fd localName. fd := DosFileDirectory on: 'C:'. fd containingDirectory includes: fd localName. fd := DosFileDirectory on: '\\server'. fd containingDirectory includes: fd localName. " (self isDrive: fullName) ifTrue: [^ pathAndNameBlock value:'' value: fullName]. ^ super splitName: fullName to: pathAndNameBlock! ! !DosFileDirectory class methodsFor: '*network-uri' stamp: 'mir 6/20/2005 18:53'! privateFullPathForURI: aURI | path | path := aURI path. "Check for drive notation (a: etc)" path size > 1 ifTrue: [ ((path at: 3) = $:) ifTrue: [path := path copyFrom: 2 to: path size] ifFalse: [ "All other cases should be network path names (\\xxx\sdsd etc)" path := '/' , path]]. ^(path copyReplaceAll: '/' with: self slash) unescapePercents! ! TestCase subclass: #DosFileDirectoryTests instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Files-Tests'! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:03'! testFileDirectoryContainingDirectory "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory on: 'C:'. self assert: fd containingDirectory pathName = ''. ! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:05'! testFileDirectoryContainingDirectoryExistence "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory on: 'C:'. self assert: (fd containingDirectory fileOrDirectoryExists: 'C:').! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:04'! testFileDirectoryContainingEntry "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory on: 'C:'. self assert: (fd containingDirectory entryAt: fd localName) notNil. ! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:04'! testFileDirectoryDirectoryEntry "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory on: 'C:'. self assert: fd directoryEntry notNil.! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 4/27/2004 23:28'! testFileDirectoryEntryFor "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory root directoryEntryFor: 'C:'. self assert: (fd name sameAs: 'C:').! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 4/27/2004 23:21'! testFileDirectoryExists "Hoping that you have 'C:' of course..." FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. self assert: (FileDirectory root directoryExists: 'C:').! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:04'! testFileDirectoryLocalName "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory on: 'C:'. self assert: fd localName = 'C:'. ! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 4/27/2004 23:19'! testFileDirectoryNamed "Hoping that you have 'C:' of course..." | fd | FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. fd := FileDirectory root directoryNamed: 'C:'. self assert: fd pathName = 'C:'.! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'DF 5/26/2006 11:57'! testFileDirectoryNonExistence | inexistentFileName | "Hoping that you have 'C:' of course..." FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. inexistentFileName := DosFileDirectory default nextNameFor: 'DosFileDirectoryTest' extension: 'temp'. "This test can fail if another process creates a file with the same name as inexistentFileName (the probability of that is very very remote)" self deny: (DosFileDirectory default fileOrDirectoryExists: inexistentFileName)! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:13'! testFileDirectoryRootExistence "Hoping that you have 'C:' of course..." FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. self assert: (FileDirectory root fileOrDirectoryExists: 'C:').! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 4/27/2004 23:28'! testFullNameFor "Hoping that you have 'C:' of course..." FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self]. self assert: (FileDirectory default fullNameFor: 'C:') = 'C:'. self assert: (FileDirectory default fullNameFor: 'C:\test') = 'C:\test'. self assert: (FileDirectory default fullNameFor: '\\share') = '\\share'. self assert: (FileDirectory default fullNameFor: '\\share\test') = '\\share\test'. self assert: (FileDirectory default fullNameFor: '\test') = (FileDirectory default pathParts first, '\test'). ! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:17'! testIsDriveForDrive self assert: (DosFileDirectory isDrive: 'C:'). self deny: (DosFileDirectory isDrive: 'C:\'). self deny: (DosFileDirectory isDrive: 'C:\foo'). self deny: (DosFileDirectory isDrive: 'C:foo').! ! !DosFileDirectoryTests methodsFor: 'as yet unclassified' stamp: 'ar 3/6/2004 04:17'! testIsDriveForShare self assert: (DosFileDirectory isDrive: '\\server'). self deny: (DosFileDirectory isDrive: '\\server\'). self deny: (DosFileDirectory isDrive: '\\server\foo'). ! ! RectangleMorph subclass: #DoubleClickExample instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'accessing' stamp: 'nk 7/26/2004 10:38'! balloonText ^ 'Double-click on me to change my color; single-click on me to change border color; hold mouse down within me and then move it to grow (if I''m red) or shrink (if I''m blue).' translated ! ! !DoubleClickExample methodsFor: 'event handling' 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: 'event handling' 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: 'event handling' stamp: 'sw 9/14/1999 16:05'! handlesMouseDown: evt ^ true! ! !DoubleClickExample methodsFor: 'event handling' 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: 'event handling' 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! ! !DoubleClickExample methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:22'! defaultColor "answer the default color/fill style for the receiver" ^ Color red! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DoubleClickExample class instanceVariableNames: ''! !DoubleClickExample class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 12:46'! descriptionForPartsBin ^ self partName: 'DoubleClick' categories: #('Demo') documentation: 'An example of how to use double-click in moprhic'! ! Morph subclass: #DrawErrorMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'MorphicExtras-Widgets'! !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: '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 12/31/2001 02:35'! drawOn: aCanvas aCanvas drawString: contents in: (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 12/31/2001 02:51'! maxExtent: listOfStrings | h w maxW f | maxW _ 0. listOfStrings do: [:str | f _ self fontToUse. w _ f widthOfString: str. h _ f height. maxW _ maxW max: w]. self extent: (maxW + 4 + h) @ (h + 4). self changed! ! !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 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 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: 'dispatching' stamp: 'ar 1/10/2001 21:24'! sentTo: anObject "Dispatch the receiver into anObject" self type == #dropEvent ifTrue:[^anObject handleDropMorph: self].! ! !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 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: 'testing' stamp: 'ar 9/13/2000 18:33'! isDropEvent ^true! ! !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: 'private' stamp: 'ar 9/13/2000 19:23'! setPosition: pos contents: aMorph hand: aHand position _ pos. contents _ aMorph. source _ aHand. 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].! ! 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: 'initialization' stamp: 'sd 11/20/2005 21:27'! morphicWindow | window | leftCngSorter := ChangeSorter new myChangeSet: ChangeSet current. 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: 'initialization' stamp: 'di 5/20/1998 21:44'! okToChange ^ leftCngSorter okToChange & rightCngSorter okToChange! ! !DualChangeSorter methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:27'! open | topView | Smalltalk isMorphic | Sensor leftShiftDown ifTrue: [^ self openAsMorph]. leftCngSorter := ChangeSorter new myChangeSet: ChangeSet current. 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: 'initialization' stamp: 'sw 3/6/1999 09:34'! openAsMorph ^ self morphicWindow openInWorld ! ! !DualChangeSorter methodsFor: 'initialization'! release leftCngSorter release. rightCngSorter release.! ! !DualChangeSorter methodsFor: 'other'! isLeftSide: theOne "Which side am I?" ^ theOne == leftCngSorter! ! !DualChangeSorter methodsFor: 'other' stamp: 'sd 11/20/2005 21:27'! labelString "The window label" | leftName rightName changesName | leftName := leftCngSorter changeSetCategory categoryName. rightName := rightCngSorter changeSetCategory categoryName. changesName := 'Changes go to "', ChangeSet current name, '"'. ^ ((leftName ~~ #All) or: [rightName ~~ #All]) ifTrue: ['(', leftName, ') - ', changesName, ' - (', rightName, ')'] ifFalse: [changesName]! ! !DualChangeSorter methodsFor: 'other' 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: 'other'! other: theOne "Return the other side's ChangeSorter" ^ theOne == leftCngSorter ifTrue: [rightCngSorter] ifFalse: [leftCngSorter]! ! !DualChangeSorter methodsFor: 'toolbuilder' stamp: 'sd 11/20/2005 21:27'! buildWith: builder | windowSpec | leftCngSorter := ChangeSorter new myChangeSet: ChangeSet current. leftCngSorter parent: self. rightCngSorter := ChangeSorter new myChangeSet: ChangeSorter secondaryChangeSet. rightCngSorter parent: self. windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec label: 'Change Sorter'. windowSpec children: OrderedCollection new. leftCngSorter buildWith: builder in: windowSpec rect: (0@0 extent: 0.5@1). rightCngSorter buildWith: builder in: windowSpec rect: (0.5@0 extent: 0.5@1). ^builder build: windowSpec ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DualChangeSorter class instanceVariableNames: ''! !DualChangeSorter class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:44'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(DualChangeSorter prototypicalToolWindow 'Change Sorter' 'Shows two change sets side by side') forFlapNamed: 'Tools']! ! !DualChangeSorter class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 12:33'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !DualChangeSorter class methodsFor: 'opening' stamp: 'sw 3/24/1999 17:50'! open "Open a new instance of the receiver's class" self new open! ! !DualChangeSorter class methodsFor: 'opening' stamp: 'sw 6/11/2001 17:38'! prototypicalToolWindow "Answer an example of myself seen in a tool window, for the benefit of parts-launching tools" ^ self new morphicWindow applyModelExtent! ! !DualChangeSorter class methodsFor: 'window color' stamp: 'sw 2/26/2002 14:12'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'Dual Change Sorter' brightColor: #lightBlue pastelColor: #paleBlue helpMessage: 'Lets you view and manipulate two change sets concurrently.'! ! MenuMorph subclass: #DumberMenuMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Explorer'! !DumberMenuMorph commentStamp: '' prior: 0! Contributed by Bob Arning as part of the ObjectExplorer package. ! !DumberMenuMorph methodsFor: 'menu' stamp: 'RAA 6/21/1999 15:40'! setInvokingView: invokingView "I'd rather not, if that's OK"! ! AbstractSoundSystem subclass: #DummySoundSystem instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !DummySoundSystem commentStamp: 'gk 2/24/2004 23:14' prior: 0! This is a dummy sound system registered in SoundService to absorb all sound playing and to use the primitive beep instead of sampled sounds when playing a beep.! !DummySoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 20:48'! randomBitsFromSoundInput: bitCount "I'm not sure what the right thing to do here is." self error: 'Can not provide random data.'! ! !DummySoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:54'! sampledSoundChoices "No choices other than this." ^ #('silence')! ! !DummySoundSystem methodsFor: 'misc' stamp: 'gk 2/23/2004 19:55'! soundNamed: soundName "There are no sounds to look up." ^ nil! ! !DummySoundSystem methodsFor: 'playing' stamp: 'gk 2/24/2004 23:53'! beep "Make a primitive beep." Beeper beepPrimitive! ! !DummySoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:53'! playSampledSound: samples rate: rate "Do nothing." ! ! !DummySoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:54'! playSoundNamed: soundName "Do nothing."! ! !DummySoundSystem methodsFor: 'playing' stamp: 'gk 2/23/2004 19:54'! playSoundNamed: soundName ifAbsentReadFrom: aifFileName "Do nothing."! ! !DummySoundSystem methodsFor: 'playing' stamp: 'gk 4/8/2005 14:15'! playSoundNamedOrBeep: soundName "There is no sound support, so we make the beep." self beep! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DummySoundSystem class instanceVariableNames: ''! !DummySoundSystem class methodsFor: 'class initialization' stamp: 'gk 2/23/2004 21:08'! initialize SoundService register: self new.! ! !DummySoundSystem class methodsFor: 'class initialization' stamp: 'gk 2/23/2004 21:08'! unload SoundService registeredClasses do: [:ss | (ss isKindOf: self) ifTrue: [SoundService unregister: ss]].! ! 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: 'accessing'! nextInt32Put: arg "do nothing"! ! !DummyStream methodsFor: 'accessing'! nextNumber: cnt put: num "do nothing"! ! !DummyStream methodsFor: 'accessing' stamp: 'tk 6/8/1998 21:06'! nextPutAll: aByteArray "do nothing"! ! !DummyStream methodsFor: 'accessing' stamp: 'tk 6/8/1998 21:07'! nextPut: aByte "do nothing"! ! !DummyStream methodsFor: 'accessing'! nextStringPut: aString "do nothing"! ! !DummyStream methodsFor: 'accessing' stamp: 'tk 3/6/2000 11:10'! originalContents ^ ''! ! !DummyStream methodsFor: 'accessing'! position "Return any random number. Here is where the real lying begins. We are a DummyStream afterall. 8/17/96 tk" ^ 47 ! ! !DummyStream methodsFor: 'accessing' stamp: 'tk 7/12/1998 12:51'! position: anOffset "Pretend to position wherever the caller says!!" ! ! !DummyStream methodsFor: 'error handling'! 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 methodsFor: 'open/close' stamp: 'tk 10/31/97 11:43'! close "do nothing"! ! !DummyStream methodsFor: 'positioning' stamp: '6/10/97 17:14 tk'! skip: aNumber "Do nothing."! ! !DummyStream methodsFor: 'protocol'! binary "do nothing"! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DummyStream class instanceVariableNames: ''! !DummyStream class methodsFor: 'instance creation' stamp: 'jm 12/3/97 20:25'! on: aFile "Return a new DummyStream instance, ignoring the argument." ^ self basicNew ! ! Object subclass: #DummyToolWorkingWithFileList instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-FileList-Tests'! !DummyToolWorkingWithFileList commentStamp: '' prior: 0! I'm a dummy class for testing that the registration of the tool to the FileList of actually happens. In the future the tests should cover that the class register when loaded in memory and unregister when unloaded.! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! DummyToolWorkingWithFileList class instanceVariableNames: ''! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'sd 2/6/2002 21:29'! fileReaderServicesForFile: fullName suffix: suffix ^ (suffix = 'kkk') ifTrue: [ self services] ifFalse: [#()] ! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'sd 2/6/2002 21:46'! initialize "self initialize" FileList registerFileReader: self ! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'SD 11/14/2001 22:12'! loadAFileForTheDummyTool: aFileListOrAPath "attention. if the file list selects a file the argument will be a fullpath of the selected file else it will pass the filelist itself"! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'sw 2/17/2002 02:36'! serviceLoadAFilForDummyTool "Answer a service for opening the Dummy tool" ^ SimpleServiceEntry provider: self label: 'menu label' selector: #loadAFileForTheDummyTool: description: 'Menu label for dummy tool' buttonLabel: 'test'! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'sd 2/1/2002 22:32'! services ^ Array with: self serviceLoadAFilForDummyTool ! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !DummyToolWorkingWithFileList class methodsFor: 'class initialization' stamp: 'SD 11/10/2001 21:49'! unregister FileList unregisterFileReader: self. ! ! Magnitude subclass: #Duration instanceVariableNames: 'nanos seconds' classVariableNames: '' poolDictionaries: 'ChronologyConstants' category: 'Kernel-Chronology'! !Duration commentStamp: '' prior: 0! I represent a duration of time. I have nanosecond precision ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:59'! * operand "operand is a Number" ^ self class nanoSeconds: ( (self asNanoSeconds * operand) asInteger). ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:59'! + operand "operand is a Duration" ^ self class nanoSeconds: (self asNanoSeconds + operand asNanoSeconds) ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:59'! - operand "operand is a Duration" ^ self + operand negated ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:00'! / operand "operand is a Duration or a Number" ^ operand isNumber ifTrue: [ self class nanoSeconds: (self asNanoSeconds / operand) asInteger ] ifFalse: [ self asNanoSeconds / operand asDuration asNanoSeconds ] . ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:00'! < comparand ^ self asNanoSeconds < comparand asNanoSeconds ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 1/9/2004 06:25'! = comparand "Answer whether the argument is a representing the same period of time as the receiver." ^ self == comparand ifTrue: [true] ifFalse: [self species = comparand species ifTrue: [self asNanoSeconds = comparand asNanoSeconds] ifFalse: [false] ]! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! abs ^ self class seconds: seconds abs nanoSeconds: nanos abs ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! asDuration ^ self ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! asSeconds ^ seconds ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 1/7/2004 16:20'! days "Answer the number of days the receiver represents." ^ seconds quo: SecondsInDay ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! hash ^seconds bitXor: nanos ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! hours "Answer the number of hours the receiver represents." ^ (seconds rem: SecondsInDay) quo: SecondsInHour ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:01'! minutes "Answer the number of minutes the receiver represents." ^ (seconds rem: SecondsInHour) quo: SecondsInMinute ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:02'! negated ^ self class seconds: seconds negated nanoSeconds: nanos negated ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:02'! negative ^ self positive not ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 08:02'! positive ^ seconds = 0 ifTrue: [ nanos positive ] ifFalse: [ seconds positive ] ! ! !Duration methodsFor: 'ansi protocol' stamp: 'brp 8/23/2003 10:03'! seconds "Answer the number of seconds the receiver represents." ^ (seconds rem: SecondsInMinute) + (nanos / NanosInSecond)! ! !Duration methodsFor: 'initialize-release' stamp: 'nk 3/30/2004 10:01'! initialize self seconds: 0 nanoSeconds: 0. ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 14:29'! // operand "operand is a Duration or a Number" ^ operand isNumber ifTrue: [ self class nanoSeconds: (self asNanoSeconds // operand) asInteger ] ifFalse: [ self asNanoSeconds // operand asDuration asNanoSeconds ] ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 15:07'! \\ operand "modulo. Remainder defined in terms of //. Answer a Duration with the same sign as aDuration. operand is a Duration or a Number." ^ operand isNumber ifTrue: [ self class nanoSeconds: (self asNanoSeconds \\ operand) ] ifFalse: [ self - (operand * (self // operand)) ] ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 13:42'! asDelay ^ Delay forDuration: self! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 08:03'! asMilliSeconds ^ ((seconds * NanosInSecond) + nanos) // (10 raisedToInteger: 6) ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 08:03'! asNanoSeconds ^ (seconds * NanosInSecond) + nanos ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 5/13/2003 08:03'! nanoSeconds ^ nanos ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 13:22'! printOn: aStream "Format as per ANSI 5.8.2.16: [-]D:HH:MM:SS[.S]" | d h m s n | d _ self days abs. h _ self hours abs. m _ self minutes abs. s _ self seconds abs truncated. n _ self nanoSeconds abs. self negative ifTrue: [ aStream nextPut: $- ]. d printOn: aStream. aStream nextPut: $:. h < 10 ifTrue: [ aStream nextPut: $0. ]. h printOn: aStream. aStream nextPut: $:. m < 10 ifTrue: [ aStream nextPut: $0. ]. m printOn: aStream. aStream nextPut: $:. s < 10 ifTrue: [ aStream nextPut: $0. ]. s printOn: aStream. n = 0 ifFalse: [ | z ps | aStream nextPut: $.. ps _ n printString padded: #left to: 9 with: $0. z _ ps findLast: [ :c | c asciiValue > $0 asciiValue ]. ps from: 1 to: z do: [ :c | aStream nextPut: c ] ]. ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 15:42'! roundTo: aDuration "e.g. if the receiver is 5 minutes, 37 seconds, and aDuration is 2 minutes, answer 6 minutes." ^ self class nanoSeconds: (self asNanoSeconds roundTo: aDuration asNanoSeconds) ! ! !Duration methodsFor: 'squeak protocol' stamp: 'brp 9/25/2003 15:38'! truncateTo: aDuration "e.g. if the receiver is 5 minutes, 37 seconds, and aDuration is 2 minutes, answer 4 minutes." ^ self class nanoSeconds: (self asNanoSeconds truncateTo: aDuration asNanoSeconds) ! ! !Duration methodsFor: 'private' stamp: 'brp 7/27/2003 15:08'! seconds: secondCount nanoSeconds: nanoCount "Private - only used by Duration class" seconds _ secondCount. nanos _ nanoCount! ! !Duration methodsFor: 'private' stamp: 'brp 9/25/2003 14:42'! storeOn: aStream aStream nextPut: $(; nextPutAll: self className; nextPutAll: ' seconds: '; print: seconds; nextPutAll: ' nanoSeconds: '; print: nanos; nextPut: $). ! ! !Duration methodsFor: 'private' stamp: 'brp 8/23/2003 20:31'! ticks "Answer an array {days. seconds. nanoSeconds}. Used by DateAndTime and Time" ^ Array with: self days with: (self hours * 3600) + (self minutes * 60 ) + (self seconds truncated) with: self nanoSeconds! ! !Duration methodsFor: 'testing' stamp: 'tlk 4/30/2006 22:02'! isZero ^seconds isZero and:[nanos isZero]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Duration class instanceVariableNames: ''! !Duration class methodsFor: 'ansi protocol' stamp: 'brp 5/13/2003 07:55'! days: days hours: hours minutes: minutes seconds: seconds ^ self days: days hours: hours minutes: minutes seconds: seconds nanoSeconds: 0.! ! !Duration class methodsFor: 'ansi protocol' stamp: 'nk 3/30/2004 10:05'! seconds: aNumber ^ (self basicNew) seconds: aNumber nanoSeconds: 0; yourself. ! ! !Duration class methodsFor: 'ansi protocol' stamp: 'nk 3/30/2004 10:06'! zero ^ (self basicNew) seconds: 0 nanoSeconds: 0; yourself. ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:00'! days: aNumber ^ self days: aNumber hours: 0 minutes: 0 seconds: 0 nanoSeconds: 0! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 1/7/2004 15:38'! days: days hours: hours minutes: minutes seconds: seconds nanoSeconds: nanos ^ self nanoSeconds: ( ( (days * SecondsInDay) + (hours * SecondsInHour) + (minutes * SecondsInMinute) + seconds ) * NanosInSecond ) + nanos. ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 5/16/2003 11:29'! fromString: aString ^ self readFrom: (ReadStream on: aString) ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:00'! hours: aNumber ^ self days: 0 hours: aNumber minutes: 0 seconds: 0 nanoSeconds: 0! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:04'! milliSeconds: milliCount ^ self days: 0 hours: 0 minutes: 0 seconds: 0 nanoSeconds: (milliCount * (10 raisedToInteger: 6)) ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:01'! minutes: aNumber ^ self days: 0 hours: 0 minutes: aNumber seconds: 0 nanoSeconds: 0! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 1/9/2004 17:20'! month: aMonth "aMonth is an Integer or a String" ^ (Month month: aMonth year: Year current year) duration ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 5/21/2003 08:27'! nanoSeconds: nanos ^ self new seconds: (nanos quo: NanosInSecond) nanoSeconds: (nanos rem: NanosInSecond) rounded; yourself. ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 8/23/2003 12:47'! readFrom: aStream "Formatted as per ANSI 5.8.2.16: [-]D:HH:MM:SS[.S] To assiste DateAndTime>>#readFrom: SS may be unpadded or absent." | sign days hours minutes seconds nanos ws ch | sign _ (aStream peekFor: $-) ifTrue: [-1] ifFalse: [1]. days _ (aStream upTo: $:) asInteger sign: sign. hours _ (aStream upTo: $:) asInteger sign: sign. minutes _ (aStream upTo: $:) asInteger sign: sign. aStream atEnd ifTrue: [seconds _ 0. nanos _ 0] ifFalse: [ ws _ String new writeStream. [ch _ aStream next. (ch isNil) | (ch = $.)] whileFalse: [ ws nextPut: ch ]. seconds _ ws contents asInteger sign: sign. ws reset. 9 timesRepeat: [ ch _ aStream next. ws nextPut: (ch ifNil: [$0] ifNotNil: [ch]) ]. nanos _ ws contents asInteger sign: sign]. ^ self days: days hours: hours minutes: minutes seconds: seconds nanoSeconds: nanos. " '0:00:00:00' asDuration '0:00:00:00.000000001' asDuration '0:00:00:00.999999999' asDuration '0:00:00:00.100000000' asDuration '0:00:00:00.10' asDuration '0:00:00:00.1' asDuration '0:00:00:01' asDuration '0:12:45:45' asDuration '1:00:00:00' asDuration '365:00:00:00' asDuration '-7:09:12:06.10' asDuration '+0:01:02' asDuration '+0:01:02:3' asDuration " ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 7/27/2003 15:01'! seconds: seconds nanoSeconds: nanos ^ self days: 0 hours: 0 minutes: 0 seconds: seconds nanoSeconds: nanos ! ! !Duration class methodsFor: 'squeak protocol' stamp: 'brp 8/6/2003 18:54'! weeks: aNumber ^ self days: (aNumber * 7) hours: 0 minutes: 0 seconds: 0 nanoSeconds: 0 ! ! ClassTestCase subclass: #DurationTest instanceVariableNames: 'aDuration' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Chronology'! !DurationTest methodsFor: 'Coverage' stamp: 'brp 9/25/2003 14:30'! classToBeTested ^ Duration ! ! !DurationTest methodsFor: 'Coverage' stamp: 'brp 9/25/2003 14:30'! selectorsToBeIgnored | private | private := #( #printOn: ). ^ super selectorsToBeIgnored, private ! ! !DurationTest methodsFor: 'running' stamp: 'brp 1/21/2004 18:36'! setUp aDuration := Duration days: 1 hours: 2 minutes: 3 seconds: 4 nanoSeconds: 5 ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/9/2004 06:32'! testComparing | d1 d2 d3 | d1 _ Duration seconds: 10 nanoSeconds: 1. d2 _ Duration seconds: 10 nanoSeconds: 1. d3 _ Duration seconds: 10 nanoSeconds: 2. self assert: (d1 = d1); assert: (d1 = d2); deny: (d1 = d3); assert: (d1 < d3) ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/21/2004 18:36'! testModulo | d1 d2 d3 | d1 _ 11.5 seconds. d2 _ d1 \\ 3. self assert: d2 = (Duration nanoSeconds: 1). d3 _ d1 \\ (3 seconds). self assert: d3 = (Duration seconds: 2 nanoSeconds: 500000000). self assert: aDuration \\ aDuration = (Duration days: 0 hours: 0 minutes: 0 seconds: 0 nanoSeconds: 0). self assert: aDuration \\ 2 = (Duration days: 0 hours: 0 minutes: 0 seconds: 0 nanoSeconds: 1). ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/16/2004 14:17'! testMonthDurations | jan feb dec | jan _ Duration month: #January. feb _ Duration month: #February. dec _ Duration month: #December. self assert: jan = (Year current months first duration); assert: feb = (Year current months second duration); assert: dec = (Year current months last duration) ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/9/2004 06:28'! testNumberConvenienceMethods self assert: 1 week = (Duration days: 7); assert: -1 week = (Duration days: -7); assert: 1 day = (Duration days: 1); assert: -1 day = (Duration days: -1); assert: 1 hours = (Duration hours: 1); assert: -1 hour = (Duration hours: -1); assert: 1 minute = (Duration seconds: 60); assert: -1 minute = (Duration seconds: -60); assert: 1 second = (Duration seconds: 1); assert: -1 second = (Duration seconds: -1); assert: 1 milliSecond = (Duration milliSeconds: 1); assert: -1 milliSecond = (Duration milliSeconds: -1); assert: 1 nanoSecond = (Duration nanoSeconds: 1); assert: -1 nanoSecond = (Duration nanoSeconds: -1) ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 9/25/2003 14:57'! testQuotient | d1 d2 q | d1 _ 11.5 seconds. d2 _ d1 // 3. self assert: d2 = (Duration seconds: 3 nanoSeconds: 833333333). q _ d1 // (3 seconds). self assert: q = 3. ! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/21/2004 18:38'! testRoundTo self assert: ((5 minutes + 37 seconds) roundTo: (2 minutes)) = (6 minutes). self assert: (aDuration roundTo: (Duration days: 1)) = (Duration days: 1 hours: 0 minutes: 0 seconds: 0 nanoSeconds: 0). self assert: (aDuration roundTo: (Duration hours: 1)) = (Duration days: 1 hours: 2 minutes: 0 seconds: 0 nanoSeconds: 0). self assert: (aDuration roundTo: (Duration minutes: 1)) = (Duration days: 1 hours: 2 minutes: 3 seconds: 0 nanoSeconds: 0).! ! !DurationTest methodsFor: 'Tests' stamp: 'brp 1/21/2004 18:37'! testTruncateTo self assert: ((5 minutes + 37 seconds) truncateTo: (2 minutes)) = (4 minutes). self assert: (aDuration truncateTo: (Duration days: 1)) = (Duration days: 1 hours: 0 minutes: 0 seconds: 0 nanoSeconds: 0). self assert: (aDuration truncateTo: (Duration hours: 1)) = (Duration days: 1 hours: 2 minutes: 0 seconds: 0 nanoSeconds: 0). self assert: (aDuration truncateTo: (Duration minutes: 1)) = (Duration days: 1 hours: 2 minutes: 3 seconds: 0 nanoSeconds: 0).! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAbs self assert: aDuration abs = aDuration. self assert: (Duration nanoSeconds: -5) abs = (Duration nanoSeconds: 5). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAsDelay self deny: aDuration asDelay = aDuration. "want to come up with a more meaningful test" ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAsDuration self assert: aDuration asDuration = aDuration ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAsMilliSeconds self assert: (Duration nanoSeconds: 1000000) asMilliSeconds = 1. self assert: (Duration seconds: 1) asMilliSeconds = 1000. self assert: (Duration nanoSeconds: 1000000) asMilliSeconds = 1. self assert: (Duration nanoSeconds: 1000000) asMilliSeconds = 1. self assert: aDuration asMilliSeconds = 93784000.! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAsNanoSeconds self assert: (Duration nanoSeconds: 1) asNanoSeconds = 1. self assert: (Duration seconds: 1) asNanoSeconds = 1000000000. self assert: aDuration asNanoSeconds = 93784000000005.! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testAsSeconds self assert: (Duration nanoSeconds: 1000000000) asSeconds = 1. self assert: (Duration seconds: 1) asSeconds = 1. self assert: aDuration asSeconds = 93784.! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testDays self assert: aDuration days = 1. self assert: (Duration days: 1) days= 1. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testDivide self assert: aDuration / aDuration = 1. self assert: aDuration / 2 = (Duration days: 0 hours: 13 minutes: 1 seconds: 32 nanoSeconds: 2). self assert: aDuration / (1/2) = (Duration days: 2 hours: 4 minutes: 6 seconds: 8 nanoSeconds: 10). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testFromString self assert: aDuration = (Duration fromString: '1:02:03:04.000000005'). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testHash self assert: aDuration hash = (Duration days: 1 hours: 2 minutes: 3 seconds: 4 nanoSeconds: 5) hash. self assert: aDuration hash = 93789 "must be a more meaningful test?"! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testHours self assert: aDuration hours = 2. self assert: (Duration hours: 2) hours = 2. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testIntegerDivision self assert: aDuration // aDuration = 1. self assert: aDuration // 2 = (aDuration / 2). "is there ever a case where this is not true, since precision is always to the nano second?"! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testLessThan self assert: aDuration < (aDuration + 1 day ). self deny: aDuration < aDuration. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testMilliSeconds self assert: (Duration milliSeconds: 5) nanoSeconds = 5000000. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testMinus self assert: aDuration - aDuration = (Duration seconds: 0). self assert: aDuration - (Duration days: -1 hours: -2 minutes: -3 seconds: -4 nanoSeconds: -5) = (Duration days: 2 hours: 4 minutes: 6 seconds: 8 nanoSeconds: 10). self assert: aDuration - (Duration days: 0 hours: 1 minutes: 2 seconds: 3 nanoSeconds: 4) = (Duration days: 1 hours: 1 minutes: 1 seconds: 1 nanoSeconds: 1). self assert: aDuration - (Duration days: 0 hours: 3 minutes: 0 seconds: 5 nanoSeconds: 0) = (Duration days: 0 hours: 23 minutes: 2 seconds: 59 nanoSeconds: 5). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testMinutes self assert: aDuration minutes = 3. self assert: (Duration minutes: 3) minutes = 3. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testMultiply self assert: aDuration * 2 = (Duration days: 2 hours: 4 minutes: 6 seconds: 8 nanoSeconds: 10). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testNanoSeconds self assert: aDuration nanoSeconds = 5. self assert: (Duration nanoSeconds: 5) nanoSeconds = 5. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testNegated self assert: aDuration + aDuration negated = (Duration seconds: 0). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testNegative self deny: aDuration negative. self assert: aDuration negated negative ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testNew "self assert: Duration new = (Duration seconds: 0)." "new is not valid as a creation method: MessageNotUnderstood: UndefinedObject>>quo:, where Duration seconds is nil"! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testPlus self assert: (aDuration + 0 hours) = aDuration. self assert: (aDuration + aDuration) = (Duration days: 2 hours: 4 minutes: 6 seconds: 8 nanoSeconds: 10). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testPositive self assert: (Duration nanoSeconds: 0) positive. self assert: aDuration positive. self deny: aDuration negated positive ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testPrintOn |cs rw | cs _ ReadStream on: '1:02:03:04.000000005'. rw _ ReadWriteStream on: ''. aDuration printOn: rw. self assert: rw contents = cs contents.! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testReadFrom self assert: aDuration = (Duration readFrom: (ReadStream on: '1:02:03:04.000000005')) ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testSeconds self assert: aDuration seconds = (800000001/200000000). self assert: (Duration nanoSeconds: 2) seconds = (2/1000000000). self assert: (Duration seconds: 2) seconds = 2. self assert: (Duration days: 1 hours: 2 minutes: 3 seconds:4) seconds = (4). self deny: (Duration days: 1 hours: 2 minutes: 3 seconds:4) seconds = (1*24*60*60+(2*60*60)+(3*60)+4). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testSecondsNanoSeconds self assert: (Duration seconds: 0 nanoSeconds: 5) = (Duration nanoSeconds: 5). "not sure I should include in sunit since its Private " self assert: (aDuration seconds: 0 nanoSeconds: 1) = (Duration nanoSeconds: 1). ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testStoreOn self assert: (aDuration storeOn: (WriteStream on:'')) asString ='1:02:03:04.000000005'. "storeOn: returns a duration (self) not a stream"! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testTicks self assert: aDuration ticks = #(1 7384 5)! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testWeeks self assert: (Duration weeks: 1) days= 7. ! ! !DurationTest methodsFor: 'testing' stamp: 'brp 1/21/2004 18:38'! testZero self assert: (Duration zero) = (Duration seconds: 0). ! ! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! lowers: aDictionary lowers := aDictionary! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'len 12/8/1999 18:28'! speed ^ speed! ! !DurationsVisitor methodsFor: 'accessing' stamp: 'stephaneducasse 2/3/2006 22:22'! speed: aNumber speed := aNumber! ! !DurationsVisitor methodsFor: 'rules' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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 methodsFor: 'visiting' stamp: 'stephaneducasse 2/3/2006 22: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 class instanceVariableNames: ''! !DurationsVisitor class methodsFor: 'examples' stamp: 'stephaneducasse 2/3/2006 22:23'! 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! ! !DurationsVisitor class methodsFor: 'instance creation' stamp: 'len 12/8/1999 16:40'! inherents: aDictionary lowers: anotherDictionary ^ self new inherents: aDictionary; lowers: anotherDictionary! ! BDFFontReader subclass: #EFontBDFFontReader instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-Display'! !EFontBDFFontReader methodsFor: 'as yet unclassified' stamp: 'yo 12/28/2002 22:03'! readCharactersInRangeFrom: start to: stop totalNums: upToNum storeInto: chars | array form code | 1 to: upToNum do: [:i | array _ self readOneCharacter. code _ array at: 2. code > stop ifTrue: [^ self]. (code between: start and: stop) ifTrue: [ form _ array at: 1. form ifNotNil: [ chars add: array. ]. ]. ]. ! ! !EFontBDFFontReader methodsFor: 'as yet unclassified' stamp: 'yo 1/18/2005 15:29'! readFrom: start to: end | xTable strikeWidth glyphs ascent descent minAscii maxAscii maxWidth chars charsNum height form encoding bbx width blt lastAscii pointSize ret lastValue | form _ encoding _ bbx _ nil. self initialize. self readAttributes. height _ Integer readFromString: ((properties at: #FONTBOUNDINGBOX) at: 2). ascent _ Integer readFromString: (properties at: 'FONT_ASCENT' asSymbol) first. descent _ Integer readFromString: (properties at: 'FONT_DESCENT' asSymbol) first. (properties includesKey: 'POINT_SIZE' asSymbol) ifTrue: [ pointSize _ (Integer readFromString: (properties at: 'POINT_SIZE' asSymbol) first) // 10. ] ifFalse: [ pointSize _ (ascent + descent) * 72 // 96. ]. maxWidth _ 0. minAscii _ 16r200000. strikeWidth _ 0. maxAscii _ 0. charsNum _ Integer readFromString: (properties at: #CHARS) first. chars _ Set new: charsNum. self readCharactersInRangeFrom: start to: end totalNums: charsNum storeInto: chars. chars _ chars asSortedCollection: [:x :y | (x at: 2) <= (y at: 2)]. charsNum _ chars size. "undefined encodings make this different" chars do: [:array | encoding _ array at: 2. bbx _ array at: 3.. width _ bbx at: 1. maxWidth _ maxWidth max: width. minAscii _ minAscii min: encoding. maxAscii _ maxAscii max: encoding. strikeWidth _ strikeWidth + width. ]. glyphs _ Form extent: strikeWidth@height. blt _ BitBlt toForm: glyphs. "xTable _ XTableForUnicodeFont new ranges: (Array with: (Array with: start with: end))." xTable _ SparseLargeTable new: end + 3 chunkSize: 32 arrayClass: Array base: start + 1 defaultValue: -1. lastAscii _ start. 1 to: charsNum do: [:i | form _ (chars at: i) first. encoding _ (chars at: i) second. bbx _ (chars at: i) third. "lastAscii+1 to: encoding-1 do: [:a | xTable at: a+2 put: (xTable at: a+1)]." lastValue _ xTable at: lastAscii + 1 + 1. xTable at: encoding + 1 put: lastValue. blt copy: (((xTable at: encoding+1)@(ascent - (bbx at: 2) - (bbx at: 4))) extent: (bbx at: 1)@(bbx at: 2)) from: 0@0 in: form. xTable at: encoding+2 put: (xTable at: encoding+1)+(bbx at: 1). lastAscii _ encoding. ]. xTable zapDefaultOnlyEntries. ret _ Array new: 8. ret at: 1 put: xTable. ret at: 2 put: glyphs. ret at: 3 put: minAscii. ret at: 4 put: maxAscii. ret at: 5 put: maxWidth. ret at: 6 put: ascent. ret at: 7 put: descent. ret at: 8 put: pointSize. ^ret. " ^{xTable. glyphs. minAscii. maxAscii. maxWidth. ascent. descent. pointSize}" ! ! EFontBDFFontReader subclass: #EFontBDFFontReaderForRanges instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-Display'! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 11/30/2003 16:55'! additionalRangesForJapanese | basics | basics _ { Array with: 16r5C with: 16rFF3C. Array with: 16r3013 with: 16rFFFD. }. ^ basics ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 1/15/2004 16:46'! additionalRangesForKorean | basics | basics _ { Array with: 16rA1 with: 16rFFE6C. Array with: 16r3000 with: 16rFFFD. }. ^ basics ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'BG 3/16/2005 08:22'! override: chars with: otherFileName ranges: pairArray transcodingTable: table additionalRange: additionalRange | other rangeStream currentRange newChars code form u newArray j | other _ BDFFontReader readOnlyFileNamed: otherFileName. rangeStream _ ReadStream on: pairArray. currentRange _ rangeStream next. newChars _ PluggableSet new. newChars hashBlock: [:elem | (elem at: 2) hash]. newChars equalBlock: [:a :b | (a at: 2) = (b at: 2)]. other readChars do: [:array | code _ array at: 2. "code printStringHex printString displayAt: 0@0." code > currentRange last ifTrue: [ [rangeStream atEnd not and: [currentRange _ rangeStream next. currentRange last < code]] whileTrue. rangeStream atEnd ifTrue: [ newChars addAll: chars. ^ newChars. ]. ]. (code between: currentRange first and: currentRange last) ifTrue: [ form _ array at: 1. form ifNotNil: [ j _ array at: 2. u _ table at: (((j // 256) - 33 * 94 + ((j \\ 256) - 33)) + 1). u ~= -1 ifTrue: [ array at: 2 put: u. newChars add: array. additionalRange do: [:e | e first = (array at: 2) ifTrue: [ newArray _ array clone. newArray at: 2 put: e second. newChars add: newArray ]. ] ]. ]. ]. ]. self error: 'should not reach here'. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 2/14/2004 02:46'! rangesForGreek ^ { Array with: 16r1 with: 16rFF. Array with: 16r370 with: 16r3FF. Array with: 16r1F00 with: 16r1FFF. Array with: 16r2000 with: 16r206F. Array with: 16r20A0 with: 16r20AF }. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 3/1/2004 23:12'! rangesForJapanese | basics etc | basics _ { Array with: 16r5C with: 16r5C. Array with: 16rA2 with: 16rA3. Array with: 16rA7 with: 16rA8. Array with: 16rAC with: 16rAC. Array with: 16rB0 with: 16rB1. Array with: 16rB4 with: 16rB4. Array with: 16rB6 with: 16rB6. Array with: 16rD7 with: 16rD7. Array with: 16rF7 with: 16rF7 }. etc _ { Array with: 16r370 with: 16r3FF. "greek" Array with: 16r400 with: 16r52F. "cyrillic" Array with: 16r1D00 with: 16r1D7F. "phonetic" Array with: 16r1E00 with: 16r1EFF. "latin extended additional" Array with: 16r2000 with: 16r206F. "general punctuation" Array with: 16r20A0 with: 16r20CF. "currency symbols" Array with: 16r2100 with: 16r214F. "letterlike" Array with: 16r2150 with: 16r218F. "number form" Array with: 16r2190 with: 16r21FF. "arrows" Array with: 16r2200 with: 16r22FF. "math operators" Array with: 16r2300 with: 16r23FF. "misc tech" Array with: 16r2460 with: 16r24FF. "enclosed alnum" Array with: 16r2500 with: 16r257F. "box drawing" Array with: 16r2580 with: 16r259F. "box elem" Array with: 16r25A0 with: 16r25FF. "geometric shapes" Array with: 16r2600 with: 16r26FF. "misc symbols" Array with: 16r2700 with: 16r27BF. "dingbats" Array with: 16r27C0 with: 16r27EF. "misc math A" Array with: 16r27F0 with: 16r27FF. "supplimental arrow A" Array with: 16r2900 with: 16r297F. "supplimental arrow B" Array with: 16r2980 with: 16r29FF. "misc math B" Array with: 16r2A00 with: 16r2AFF. "supplimental math op" Array with: 16r2900 with: 16r297F. "supplimental arrow B" Array with: 16r2E80 with: 16r2EFF. "cjk radicals suppliment" Array with: 16r2F00 with: 16r2FDF. "kangxi radicals" Array with: 16r3000 with: 16r303F. "cjk symbols" Array with: 16r3040 with: 16r309F. "hiragana" Array with: 16r30A0 with: 16r30FF. "katakana" Array with: 16r3190 with: 16r319F. "kanbun" Array with: 16r31F0 with: 16r31FF. "katakana extension" Array with: 16r3200 with: 16r32FF. "enclosed CJK" Array with: 16r3300 with: 16r33FF. "CJK compatibility" Array with: 16r3400 with: 16r4DBF. "CJK unified extension A" Array with: 16r4E00 with: 16r9FAF. "CJK ideograph" Array with: 16rF900 with: 16rFAFF. "CJK compatiblity ideograph" Array with: 16rFE30 with: 16rFE4F. "CJK compatiblity forms" Array with: 16rFF00 with: 16rFFEF. "half and full" Array with: 16rFFFF with: 16rFFFF. "sentinel" }. ^ basics, etc. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 1/15/2004 16:53'! rangesForKorean | basics etc | basics _ { Array with: 16rA1 with: 16rFF }. etc _ { Array with: 16r100 with: 16r17F. "extended latin" Array with: 16r370 with: 16r3FF. "greek" Array with: 16r400 with: 16r52F. "cyrillic" Array with: 16r2000 with: 16r206F. "general punctuation" Array with: 16r2100 with: 16r214F. "letterlike" Array with: 16r2150 with: 16r218F. "number form" Array with: 16r2190 with: 16r21FF. "arrows" Array with: 16r2200 with: 16r22FF. "math operators" Array with: 16r2300 with: 16r23FF. "misc tech" Array with: 16r2460 with: 16r24FF. "enclosed alnum" Array with: 16r2500 with: 16r257F. "box drawing" Array with: 16r2580 with: 16r259F. "box elem" Array with: 16r25A0 with: 16r25FF. "geometric shapes" Array with: 16r2600 with: 16r26FF. "misc symbols" Array with: 16r3000 with: 16r303F. "cjk symbols" Array with: 16r3040 with: 16r309F. "hiragana" Array with: 16r30A0 with: 16r30FF. "katakana" Array with: 16r3190 with: 16r319F. "kanbun" Array with: 16r31F0 with: 16r31FF. "katakana extension" Array with: 16r3200 with: 16r32FF. "enclosed CJK" Array with: 16r3300 with: 16r33FF. "CJK compatibility" Array with: 16r4E00 with: 16r9FAF. "CJK ideograph" Array with: 16rAC00 with: 16rD7AF. "Hangul Syllables" Array with: 16rF900 with: 16rFAFF. "CJK compatiblity ideograph" Array with: 16rFF00 with: 16rFFEF. "half and full" }. ^ basics, etc. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 1/19/2005 11:20'! rangesForLatin2 ^ { Array with: 0 with: 16r17F. Array with: 16r2B0 with: 16r2FF. Array with: 16r2000 with: 16r206F. Array with: 16r2122 with: 16r2122. Array with: 16rFFFF with: 16rFFFF. "sentinel" }. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 3/1/2004 23:20'! readCharactersInRanges: ranges storeInto: chars | array form code rangeStream currentRange | rangeStream _ ReadStream on: ranges. currentRange _ rangeStream next. [true] whileTrue: [ array _ self readOneCharacter. array second ifNil: [^ self]. code _ array at: 2. code > currentRange last ifTrue: [ [rangeStream atEnd not and: [currentRange _ rangeStream next. currentRange last < code]] whileTrue. rangeStream atEnd ifTrue: [^ self]. ]. (code between: currentRange first and: currentRange last) ifTrue: [ form _ array at: 1. form ifNotNil: [ chars add: array. ]. ]. ]. ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 1/19/2005 11:26'! readRanges: ranges | xTable strikeWidth glyphs ascent descent minAscii maxAscii maxWidth chars charsNum height form encoding bbx width blt lastAscii pointSize ret lastValue start end | form _ encoding _ bbx _ nil. self initialize. self readAttributes. height _ Integer readFromString: ((properties at: #FONTBOUNDINGBOX) at: 2). ascent _ Integer readFromString: (properties at: 'FONT_ASCENT' asSymbol) first. descent _ Integer readFromString: (properties at: 'FONT_DESCENT' asSymbol) first. (properties includesKey: 'POINT_SIZE' asSymbol) ifTrue: [ pointSize _ (Integer readFromString: (properties at: 'POINT_SIZE' asSymbol) first) // 10. ] ifFalse: [ pointSize _ (ascent + descent) * 72 // 96. ]. maxWidth _ 0. minAscii _ 16r200000. strikeWidth _ 0. maxAscii _ 0. charsNum _ Integer readFromString: (properties at: #CHARS) first. chars _ Set new: charsNum. self readCharactersInRanges: ranges storeInto: chars. chars _ chars asSortedCollection: [:x :y | (x at: 2) <= (y at: 2)]. charsNum _ chars size. "undefined encodings make this different" chars do: [:array | encoding _ array at: 2. bbx _ array at: 3.. width _ bbx at: 1. maxWidth _ maxWidth max: width. minAscii _ minAscii min: encoding. maxAscii _ maxAscii max: encoding. strikeWidth _ strikeWidth + width. ]. glyphs _ Form extent: strikeWidth@height. blt _ BitBlt toForm: glyphs. start _ (ranges collect: [:r | r first]) min. end _ (ranges collect: [:r | r second]) max + 3. xTable _ SparseLargeTable new: end chunkSize: 64 arrayClass: Array base: start +1 defaultValue: -1. lastAscii _ start. xTable at: lastAscii + 2 put: 0. 1 to: charsNum do: [:i | form _ (chars at: i) first. encoding _ (chars at: i) second. bbx _ (chars at: i) third. "lastAscii+1 to: encoding-1 do: [:a | xTable at: a+2 put: (xTable at: a+1)]." lastValue _ xTable at: lastAscii + 1 + 1. xTable at: encoding + 1 put: lastValue. blt copy: (((xTable at: encoding+1)@(ascent - (bbx at: 2) - (bbx at: 4))) extent: (bbx at: 1)@(bbx at: 2)) from: 0@0 in: form. xTable at: encoding+2 put: (xTable at: encoding+1)+(bbx at: 1). lastAscii _ encoding. ]. xTable at: xTable size put: (xTable at: xTable size - 1). xTable zapDefaultOnlyEntries. ret _ Array new: 8. ret at: 1 put: xTable. ret at: 2 put: glyphs. ret at: 3 put: minAscii. ret at: 4 put: maxAscii. ret at: 5 put: maxWidth. ret at: 6 put: ascent. ret at: 7 put: descent. ret at: 8 put: pointSize. ^ret. " ^{xTable. glyphs. minAscii. maxAscii. maxWidth. ascent. descent. pointSize}" ! ! !EFontBDFFontReaderForRanges methodsFor: 'as yet unclassified' stamp: 'yo 5/26/2004 14:43'! readRanges: ranges overrideWith: otherFileName otherRanges: otherRanges additionalOverrideRange: additionalRange | xTable strikeWidth glyphs ascent descent minAscii maxAscii maxWidth chars charsNum height form encoding bbx width blt lastAscii pointSize ret lastValue start end | form _ encoding _ bbx _ nil. self initialize. self readAttributes. height _ Integer readFromString: ((properties at: #FONTBOUNDINGBOX) at: 2). ascent _ Integer readFromString: (properties at: 'FONT_ASCENT' asSymbol) first. descent _ Integer readFromString: (properties at: 'FONT_DESCENT' asSymbol) first. (properties includesKey: 'POINT_SIZE' asSymbol) ifTrue: [ pointSize _ (Integer readFromString: (properties at: 'POINT_SIZE' asSymbol) first) // 10. ] ifFalse: [ pointSize _ (ascent + descent) * 72 // 96. ]. maxWidth _ 0. minAscii _ 16r200000. strikeWidth _ 0. maxAscii _ 0. charsNum _ Integer readFromString: (properties at: #CHARS) first. chars _ Set new: charsNum. self readCharactersInRanges: ranges storeInto: chars. chars _ self override: chars with: otherFileName ranges: otherRanges transcodingTable: (UCSTable jisx0208Table) additionalRange: additionalRange. chars _ chars asSortedCollection: [:x :y | (x at: 2) <= (y at: 2)]. charsNum _ chars size. "undefined encodings make this different" chars do: [:array | encoding _ array at: 2. bbx _ array at: 3.. width _ bbx at: 1. maxWidth _ maxWidth max: width. minAscii _ minAscii min: encoding. maxAscii _ maxAscii max: encoding. strikeWidth _ strikeWidth + width. ]. glyphs _ Form extent: strikeWidth@height. blt _ BitBlt toForm: glyphs. start _ ((ranges collect: [:r | r first]), (additionalRange collect: [:r2 | r2 first])) min. end _ ((ranges collect: [:r | r second]), (additionalRange collect: [:r2 | r2 second])) max + 3. "xRange _ Array with: (Array with: ((ranges collect: [:r | r first]), (additionalRange collect: [:r2 | r2 first])) min with: (((ranges collect: [:r | r second]), (additionalRange collect: [:r2 | r2 second])) max + 2))." "xTable _ XTableForUnicodeFont new ranges: xRange." xTable _ SparseLargeTable new: end chunkSize: 64 arrayClass: Array base: start defaultValue: -1. lastAscii _ start. xTable at: lastAscii + 2 put: 0. 1 to: charsNum do: [:i | form _ (chars at: i) first. encoding _ (chars at: i) second. bbx _ (chars at: i) third. "lastAscii+1 to: encoding-1 do: [:a | xTable at: a+2 put: (xTable at: a+1)]." lastValue _ xTable at: lastAscii + 1 + 1. xTable at: encoding + 1 put: lastValue. blt copy: (((xTable at: encoding+1)@(ascent - (bbx at: 2) - (bbx at: 4))) extent: (bbx at: 1)@(bbx at: 2)) from: 0@0 in: form. xTable at: encoding+2 put: (xTable at: encoding+1)+(bbx at: 1). lastAscii _ encoding. ]. xTable at: xTable size put: (xTable at: xTable size - 1). xTable zapDefaultOnlyEntries. ret _ Array new: 8. ret at: 1 put: xTable. ret at: 2 put: glyphs. ret at: 3 put: minAscii. ret at: 4 put: maxAscii. ret at: 5 put: maxWidth. ret at: 6 put: ascent. ret at: 7 put: descent. ret at: 8 put: pointSize. ^ret. " ^{xTable. glyphs. minAscii. maxAscii. maxWidth. ascent. descent. pointSize}" ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EFontBDFFontReaderForRanges class instanceVariableNames: ''! !EFontBDFFontReaderForRanges class methodsFor: 'as yet unclassified' stamp: 'yo 1/19/2005 11:24'! rangesForGreek ^ { Array with: 16r1 with: 16rFF. Array with: 16r370 with: 16r3FF. Array with: 16r1F00 with: 16r1FFF. Array with: 16r2000 with: 16r206F. Array with: 16r20A0 with: 16r20AF }. ! ! !EFontBDFFontReaderForRanges class methodsFor: 'as yet unclassified' stamp: 'yo 1/19/2005 11:24'! rangesForLatin2 ^ { Array with: 0 with: 16r17F. Array with: 16r2B0 with: 16r2FF. Array with: 16r2000 with: 16r206F. Array with: 16r2122 with: 16r2122. Array with: 16rFFFF with: 16rFFFF. "sentinel" }. ! ! PostscriptCanvas subclass: #EPSCanvas instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-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: 'drawing-general' stamp: 'nk 1/2/2004 16:53'! fullDraw: aMorph super fullDraw: aMorph. morphLevel = 0 ifTrue: [ self writeTrailer: 1. ]! ! !EPSCanvas methodsFor: 'page geometry' stamp: 'nk 1/1/2004 18:29'! pageBBox ^psBounds! ! !EPSCanvas methodsFor: 'page geometry' stamp: 'nk 1/1/2004 20:22'! pageOffset ^0@0! ! !EPSCanvas methodsFor: 'private' stamp: 'nk 1/1/2004 12:48'! writeEPSPreviewImageFor: aMorph | form stream string lines newExtent | newExtent _ (aMorph width roundUpTo: 8) @ aMorph height. form _ aMorph imageForm: 1 forRectangle: (aMorph bounds origin extent: newExtent). stream _ RWBinaryOrTextStream on: (String new: (form bits byteSize * 2.04) asInteger). form storePostscriptHexOn: stream. string _ stream contents. lines _ string occurrencesOf: Character cr. "%%BeginPreview: 80 24 1 24" "width height depth " target print: '%%BeginPreview: '; write: newExtent; space; write: form depth; space; write: lines; cr. stream position: 0. [ stream atEnd ] whileFalse: [ target nextPut: $%; nextPutAll: (stream upTo: Character cr); cr. lines _ lines - 1. ]. target print: '%%EndPreview'; cr. ! ! !EPSCanvas methodsFor: 'private' stamp: 'nk 1/2/2004 16:31'! writePSIdentifierRotated: rotateFlag target print: '%!!PS-Adobe-2.0 EPSF-2.0'; cr. rotateFlag ifTrue: [target print: '%%BoundingBox: '; write: (0 @ 0 corner: psBounds corner transposed) rounded; cr] ifFalse: [target print: '%%BoundingBox: '; write: psBounds rounded; cr]. target print: '%%Title: '; print: self topLevelMorph externalName; cr. target print: '%%Creator: '; print: Utilities authorName; cr. target print: '%%CreationDate: '; print: Date today asString; space; print: Time now asString; cr. "is this relevant?" target print: '%%Orientation: '; print: (rotateFlag ifTrue: [ 'Landscape' ] ifFalse: [ 'Portrait' ]); cr. target print: '%%DocumentFonts: (atend)'; cr. target print: '%%EndComments'; cr " self writeEPSPreviewImageFor: topLevelMorph." " target print: '%%EndProlog'; cr."! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EPSCanvas class instanceVariableNames: ''! !EPSCanvas class methodsFor: 'configuring' stamp: 'nk 1/1/2004 20:22'! baseOffset ^0@0.! ! !EPSCanvas class methodsFor: 'configuring' stamp: 'nk 12/29/2003 13:19'! defaultExtension ^'.eps'! ! EToyChatOrBadgeMorph subclass: #EToyChatMorph instanceVariableNames: 'listener receivingPane myForm recipientForm acceptOnCR sendingPane' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Morphic-Collaborative'! !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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! chatFrom: ipAddress name: senderName text: text | initialText attrib | recipientForm ifNil: [ initialText := senderName asText allBold. ] ifNotNil: [ attrib := TextAnchor new anchoredMorph: recipientForm "asMorph". initialText := (String value: 1) 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: 'sd 11/20/2005 21:25'! 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: '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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! startOfMessageFromMe myForm ifNil: [ myForm := EToySenderMorph pictureForIPAddress: NetNameResolver localAddressString. myForm ifNotNil: [ myForm := myForm scaledToSize: 20@20 ]. ]. myForm ifNil: [ ^(Preferences defaultAuthorName asText allBold addAttribute: TextColor blue) ]. ^(String value: 1) asText addAttribute: (TextAnchor new anchoredMorph: myForm); yourself ! ! !EToyChatMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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 methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ self standardBorderColor! ! !EToyChatMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:36'! defaultBorderWidth "answer the default border width for the receiver" ^ 8! ! !EToyChatMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 15:07'! defaultBounds "answer the default bounds for the receiver" ^ 400 @ 100 extent: 200 @ 150! ! !EToyChatMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color paleYellow! ! !EToyChatMorph methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize "initialize the state of the receiver" super initialize. "" acceptOnCR := true. self listDirection: #topToBottom; layoutInset: 0; hResizing: #shrinkWrap; vResizing: #shrinkWrap; rubberBandCells: false; minWidth: 200; minHeight: 200; rebuild ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyChatMorph class instanceVariableNames: ''! !EToyChatMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21: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] ! ! !EToyChatMorph class methodsFor: 'parts bin' stamp: 'md 8/10/2006 11:53'! descriptionForPartsBin ^ self partName: 'Text chat' categories: #('Collaborative') documentation: 'A tool for sending messages to other Squeak users'! ! EToyCommunicatorMorph subclass: #EToyChatOrBadgeMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Morphic-Experimental'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyChatOrBadgeMorph class instanceVariableNames: ''! !EToyChatOrBadgeMorph class methodsFor: 'new-morph participation' 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: 'EToys-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] ]. ! !EToyCommunicatorMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:26'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString; color: aColor; borderColor: aColor muchDarker; 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 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: '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/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: '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: 'initialization' stamp: 'dgd 2/14/2003 19:32'! initialize "initialize the state of the receiver" super initialize. "" self vResizing: #shrinkWrap; hResizing: #shrinkWrap. resultQueue _ SharedQueue new. fields _ Dictionary new. self useRoundedCorners! ! !EToyCommunicatorMorph methodsFor: 'stepping and presenter' 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: 'submorphs-add/remove' stamp: 'RAA 7/8/2000 17:45'! delete super delete. self breakDependents! ! !EToyCommunicatorMorph methodsFor: 'testing' stamp: 'RAA 7/10/2000 10:27'! stepTime (self valueOfProperty: #flashingState ifAbsent: [0]) > 0 ifTrue: [ ^200 ] ifFalse: [ ^1000 ].! ! !EToyCommunicatorMorph methodsFor: 'testing' stamp: 'RAA 7/9/2000 06:25'! wantsSteps ^true! ! !EToyCommunicatorMorph methodsFor: '*nebraska-*nebraska-Morphic-Collaborative' stamp: 'sd 11/20/2005 21:25'! 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: '*nebraska-*nebraska-Morphic-Collaborative' stamp: 'mir 10/12/2000 14:55'! transmitStreamedObject: outData as: objectCategory to: anIPAddress EToyPeerToPeer transmitStreamedObject: outData as: objectCategory to: anIPAddress for: self! ! !EToyCommunicatorMorph methodsFor: '*nebraska-*nebraska-Morphic-Collaborative' stamp: 'mir 10/10/2000 12:47'! transmitStreamedObject: outData to: anIPAddress self transmitStreamedObject: outData as: self transmittedObjectCategory to: anIPAddress ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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 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: 'gk 2/23/2004 21:07'! playArrivalSound "Make a sound that something has arrived." SoundService default playSoundNamedOrBeep: 'chirp'! ! !EToyCommunicatorMorph class methodsFor: 'new-morph participation' stamp: 'RAA 8/3/2000 07:48'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ self ~~ EToyCommunicatorMorph! ! EToyCommunicatorMorph subclass: #EToyFridgeMorph instanceVariableNames: 'recipients incomingRow recipientRow updateCounter groupMode' classVariableNames: 'FridgeRecipients NewItems TheFridgeForm UpdateCounter' poolDictionaries: '' category: 'Nebraska-Morphic-Collaborative'! !EToyFridgeMorph commentStamp: '' prior: 0! EToyFridgeMorph new openInWorld! ]style[(31)f4cblue;! !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/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'! mouseLeaveEither: evt self deleteAnyMouseActionIndicators. ! ! !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: 'sd 11/20/2005 21:25'! rebuild | row filler fudge people maxPerRow insetY | 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. insetY := self layoutInset. insetY isPoint ifTrue: [insetY := insetY y]. filler extent: 4 @ (self height - filler height * 0.37 - insetY - borderWidth - fudge) truncated. "self fixLayout. htsAfter := submorphs collect: [ :each | each height]. {htsBefore. htsAfter} explore." ! ! !EToyFridgeMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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])). Beeper beep. "self world displayWorldSafely."! ! !EToyFridgeMorph methodsFor: 'drawing' stamp: 'sd 11/20/2005 21:25'! 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: 'dropping/grabbing' stamp: 'RAA 7/12/2000 15:53'! wantsDroppedMorph: aMorph event: evt ^true! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'sd 11/20/2005 21:25'! 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: 'event handling' stamp: 'RAA 7/17/2000 09:47'! handlesMouseOver: globalEvt ^true! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:51'! handlesMouseOverDragging: globalEvt ^true! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:55'! mouseDown: localEvt self addMouseActionIndicatorsWidth: 15 color: (Color blue alpha: 0.7). ! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:51'! mouseEnter: evt ^self mouseEnterEither: evt ! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:51'! mouseEnterDragging: evt ^self mouseEnterEither: evt ! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:53'! mouseLeave: evt ^self mouseLeaveEither: evt ! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:54'! mouseLeaveDragging: evt ^self mouseLeaveEither: evt ! ! !EToyFridgeMorph methodsFor: 'event handling' stamp: 'RAA 7/17/2000 09:55'! mouseUp: localEvt (self containsPoint: localEvt cursorPoint) ifFalse: [^self]. Project enterIfThereOrFind: 'Fridge'! ! !EToyFridgeMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ #raised! ! !EToyFridgeMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !EToyFridgeMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color paleRed! ! !EToyFridgeMorph methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize "initialize the state of the receiver" super initialize. "" groupMode := true. self listDirection: #topToBottom; layoutInset: 10; hResizing: #shrinkWrap; vResizing: #shrinkWrap; setProperty: #normalBorderColor toValue: self borderColor; setProperty: #flashingColors toValue: {Color red. Color yellow}; rebuild! ! !EToyFridgeMorph methodsFor: 'layout' stamp: 'sd 11/20/2005 21:25'! 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. "7 mar 2001 - remove #veryDeepCopy" outData := morphToDrop eToyStreamedRepresentationNotifying: self. self resetIndicator: #working. self class fridgeRecipients do: [ :each | self transmitStreamedObject: outData to: each ipAddress ]. ! ! !EToyFridgeMorph methodsFor: 'stepping and presenter' stamp: 'RAA 7/13/2000 12:31'! step super step. updateCounter = self class updateCounter ifFalse: [self rebuild]. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyFridgeMorph class instanceVariableNames: ''! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! fridgeRecipients ^FridgeRecipients ifNil: [FridgeRecipients := OrderedCollection new]! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! newItems ^NewItems ifNil: [NewItems := OrderedCollection new]! ! !EToyFridgeMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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]! ! !EToyFridgeMorph class methodsFor: 'parts bin' stamp: 'md 8/10/2006 11:53'! descriptionForPartsBin ^ self partName: 'Fridge' categories: #('Collaborative') documentation: 'A tool for sending objects to other Squeak users'! ! MorphicModel subclass: #EToyGateKeeperEntry instanceVariableNames: 'ipAddress accessAttempts lastTimes acceptableTypes latestUserName attempsDenied lastRequests' classVariableNames: 'KnownIPAddresses' poolDictionaries: '' category: 'Nebraska-Morphic-Experimental'! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'RAA 7/31/2000 12:42'! acceptableTypes ^acceptableTypes! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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 7/11/2000 09:33'! ipAddress ^ipAddress! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! latestUserName: aString latestUserName := aString! ! !EToyGateKeeperEntry methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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 methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! 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 class instanceVariableNames: ''! !EToyGateKeeperEntry class methodsFor: 'new-morph participation' 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: 'Nebraska-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: '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: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ #raised! ! !EToyGateKeeperMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !EToyGateKeeperMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightGray! ! !EToyGateKeeperMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:42'! initialize "initialize the state of the receiver" super initialize. "" self listDirection: #topToBottom; layoutInset: 4; hResizing: #spaceFill; vResizing: #spaceFill; useRoundedCorners; rebuild ! ! !EToyGateKeeperMorph methodsFor: 'stepping and presenter' 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! knownIPAddresses ^KnownIPAddresses ifNil: [KnownIPAddresses := Dictionary new]! ! !EToyGateKeeperMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! updateCounter ^UpdateCounter ifNil: [UpdateCounter := 0]! ! !EToyGateKeeperMorph class methodsFor: 'new-morph participation' stamp: 'RAA 8/3/2000 07:48'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! AlignmentMorphBob1 subclass: #EToyGenericDialogMorph instanceVariableNames: 'namedFields' classVariableNames: '' poolDictionaries: '' category: 'EToys-Experimental'! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 7/12/2003 12:33'! genericTextFieldNamed: aString | newField | newField := ShowEmptyTextMorph new beAllFont: self myFont; extent: 400 @ 20; contentsWrapped: ''. namedFields at: aString put: newField. ^ newField! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:18'! inAColumnForText: someMorphs ^ (self inAColumn: someMorphs) hResizing: #shrinkWrap; color: ColorTheme current dialogTextBoxColor; borderColor: ColorTheme current dialogTextBoxBorderColor; borderWidth: ColorTheme current dialogButtonBorderWidth; useRoundedCorners! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:13'! lockedString: aString ^ self lockedString: aString font: self myFont! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:13'! lockedString: aString font: aFont ^ self inAColumn: {(StringMorph contents: aString font: aFont) lock}! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 7/12/2003 12:29'! myFont ^ Preferences standardEToysFont! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:53'! rightLockedString: aString ^ self rightLockedString: aString font: self myFont! ! !EToyGenericDialogMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:54'! rightLockedString: aString font: aFont ^ self inARightColumn: {(StringMorph contents: aString font: aFont) lock}! ! !EToyGenericDialogMorph methodsFor: 'initialization' stamp: 'dgd 1/7/2005 19:18'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ ColorTheme current dialogBorderColor! ! !EToyGenericDialogMorph methodsFor: 'initialization' stamp: 'dgd 1/7/2005 19:19'! defaultBorderWidth "answer the default border width for the receiver" ^ ColorTheme current dialogBorderWidth! ! !EToyGenericDialogMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:54'! initialize "initialize the state of the receiver" super initialize. "" namedFields _ Dictionary new. self rebuild! ! !EToyGenericDialogMorph methodsFor: 'initialization' stamp: 'jam 3/9/2003 18:05'! rebuild "rebuilds the receiver" ^ self! ! AbstractHierarchicalList subclass: #EToyHierarchicalTextGizmo instanceVariableNames: 'topNode' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'EToys-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/29/2000 22:20'! indentingItemClass ^IndentingListParagraphMorph! ! !EToyHierarchicalTextMorph methodsFor: 'event handling' stamp: 'RAA 7/30/2000 15:05'! keyStroke: evt selectedMorph ifNil: [^self]. selectedMorph keyStroke: evt ! ! !EToyHierarchicalTextMorph methodsFor: 'geometry' stamp: 'RAA 7/30/2000 01:50'! extent: aPoint | wasDifferent | wasDifferent _ self extent ~= aPoint. super extent: aPoint. wasDifferent ifTrue: [self adjustSubmorphPositions].! ! !EToyHierarchicalTextMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color white! ! !EToyHierarchicalTextMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 18:27'! initialize "initialize the state of the receiver" super initialize. self useRoundedCorners! ! !EToyHierarchicalTextMorph methodsFor: 'selection' 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: 'instance creation' 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: 'instance creation' stamp: 'md 7/13/2005 16:33'! 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." ^ self basicNew initialize on: anObject list: getListSel selected: getSelectionSel changeSelected: setSelectionSel menu: getMenuSel keystroke: keyActionSel ! ! Object subclass: #EToyIncomingMessage instanceVariableNames: '' classVariableNames: 'MessageHandlers MessageTypes' poolDictionaries: '' category: 'Nebraska-Morphic-Experimental'! !EToyIncomingMessage methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:26'! messageHandlers ^MessageHandlers ifNil: [MessageHandlers := Dictionary new].! ! !EToyIncomingMessage class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21: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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! unregisterType: aMessageType MessageTypes := self allTypes copyWithout: aMessageType! ! EToyCommunicatorMorph subclass: #EToyListenerMorph instanceVariableNames: 'listener updateCounter' classVariableNames: 'GlobalIncomingQueue GlobalListener QueueSemaphore UpdateCounter WasListeningAtShutdown' poolDictionaries: '' category: 'Nebraska-Morphic-Collaborative'! !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: 'sd 11/20/2005 21:25'! 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. } }. true ifTrue: [ "simpler protocol" row on: #mouseUp send: #mouseUpEvent:for: to: self. ] ifFalse: [ row on: #mouseDown send: #mouseDownEvent:for: to: self. ]. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'dgd 2/22/2003 18:59'! 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 second) firstSubmorph contents. selection ifNil: [^self]. selection value! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! mouseUpEvent: event for: aMorph | depictedObject | depictedObject := aMorph firstSubmorph valueOfProperty: #depictedObject. event hand attachMorph: depictedObject. self class removeFromGlobalIncomingQueue: depictedObject. self rebuild. ! ! !EToyListenerMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! rebuild | earMorph | updateCounter := UpdateCounter. self removeAllMorphs. self addGateKeeperMorphs. GlobalListener ifNil: [ earMorph := (self class makeListeningToggleNew: false) asMorph. earMorph setBalloonText: 'Click to START listening for messages'. earMorph on: #mouseUp send: #startListening to: self. ] ifNotNil: [ earMorph := (self class makeListeningToggleNew: 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/15/2000 18:23'! stopListening self class stopListening! ! !EToyListenerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color blue! ! !EToyListenerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !EToyListenerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightBlue! ! !EToyListenerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:44'! initialize "initialize the state of the receiver" super initialize. "" self listDirection: #topToBottom; layoutInset: 4; rebuild ! ! !EToyListenerMorph methodsFor: 'stepping and presenter' stamp: 'sd 11/20/2005 21:25'! 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: 'submorphs-add/remove' stamp: 'sd 11/20/2005 21:25'! delete listener ifNotNil: [listener stopListening. listener := nil]. "for old instances that were locally listening" super delete.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! critical: aBlock QueueSemaphore ifNil: [QueueSemaphore := Semaphore forMutualExclusion]. ^QueueSemaphore critical: aBlock ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! 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 8/9/2000 17:56'! isListening ^GlobalListener notNil ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! makeListeningToggleNew: activeMode | background c baseExtent bgExtent botCent factor len endPts base | factor := 2. bgExtent := (50@25) * factor. baseExtent := (15@15) * factor. background := Form extent: bgExtent depth: 8. botCent := background boundingBox bottomCenter. c := background getCanvas. "c fillColor: Color white." base := (botCent - (baseExtent // 2)) extent: baseExtent. c fillOval: base color: Color black borderWidth: 0 borderColor: Color black. activeMode ifTrue: [ len := background boundingBox height - 15. endPts := {botCent - (len@len). botCent - (len negated@len)}. endPts do: [ :each | c line: botCent to: each width: 2 color: Color black. ]. endPts do: [ :each | #(4 8 12) do: [ :offset | c frameOval: (each - offset corner: each + offset) color: Color red ]. ]. ]. "background asMorph openInWorld." ^background ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! startListening self stopListening. GlobalListener := EToyPeerToPeer new awaitDataFor: self. self bumpUpdateCounter. ! ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:26'! stopListening GlobalListener ifNotNil: [GlobalListener stopListening. GlobalListener := nil. self bumpUpdateCounter] "EToyListenerMorph stopListening"! ! !EToyListenerMorph class methodsFor: 'class initialization' stamp: 'RAA 7/25/2000 16:28'! initialize " EToyListenerMorph initialize " Smalltalk addToStartUpList: self. Smalltalk addToShutDownList: self. ! ! !EToyListenerMorph class methodsFor: 'class initialization' stamp: 'ads 7/18/2003 09:07'! unload Smalltalk removeFromStartUpList: self. Smalltalk removeFromShutDownList: self. ! ! !EToyListenerMorph class methodsFor: 'parts bin' stamp: 'md 8/10/2006 11:54'! descriptionForPartsBin ^ self partName: 'Listener' categories: #('Collaborative') documentation: 'A tool for receiving things from other Squeak users'! ! !EToyListenerMorph class methodsFor: 'system startup' stamp: 'sd 11/20/2005 21:26'! shutDown: quitting WasListeningAtShutdown := GlobalListener notNil. self stopListening. ! ! !EToyListenerMorph class methodsFor: 'system startup' stamp: 'RAA 7/25/2000 16:27'! startUp: resuming WasListeningAtShutdown == true ifTrue: [ self startListening. ]. ! ! EToyCommunicatorMorph subclass: #EToyMorphsWelcomeMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Morphic-Collaborative'! !EToyMorphsWelcomeMorph commentStamp: '' prior: 0! EToyMorphsWelcomeMorph new openInWorld! !EToyMorphsWelcomeMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color yellow! ! !EToyMorphsWelcomeMorph methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize "initialize the state of the receiver" | earMorph | super initialize. "" self layoutInset: 8 @ 8. "earMorph := (EToyListenerMorph makeListeningToggle: true) asMorph." earMorph := TextMorph new contents: 'Morphs welcome here'; fontName: Preferences standardEToysFont familyName size: 18; centered; lock. self addARow: {earMorph}. self setBalloonText: 'My presence in this world means received morphs may appear automatically'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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]! ! !EToyMorphsWelcomeMorph class methodsFor: 'parts bin' stamp: 'RAA 8/20/2001 12:52'! descriptionForPartsBin ^ self partName: 'Welcome' categories: #('Collaborative') documentation: 'A sign that you accept morphs dropped directly into your world'! ! EToyChatMorph subclass: #EToyMultiChatMorph instanceVariableNames: 'targetIPAddresses' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-Morphic-Collaborative'! !EToyMultiChatMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! updateIPAddressField: newAddresses targetIPAddresses := ( newAddresses copyWithout: NetNameResolver localAddressString ) asSet asSortedCollection asArray. (fields at: #ipAddress) contents: targetIPAddresses size printString,' people'.! ! !EToyMultiChatMorph methodsFor: 'dropping/grabbing' stamp: 'RAA 8/17/2000 09:04'! wantsDroppedMorph: aMorph event: evt (aMorph isKindOf: EToySenderMorph) ifFalse: [^false]. (bounds containsPoint: evt cursorPoint) ifFalse: [^false]. ^true.! ! !EToyMultiChatMorph methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:25'! initialize targetIPAddresses := OrderedCollection new. super initialize. bounds := 0@0 extent: 350@350.! ! !EToyMultiChatMorph methodsFor: 'layout' 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 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 ]. ! ! !EToyMultiChatMorph class methodsFor: 'parts bin' stamp: 'RAA 1/28/2002 15:32'! descriptionForPartsBin ^ self partName: 'Text chat+' categories: #('Collaborative') documentation: 'A tool for sending messages to several Squeak users at once' sampleImageForm: (Form extent: 25@25 depth: 16 fromArray: #( 1177640695 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593270007 1593245696 1593263665 1593270007 1593270007 1593270007 1177634353 1177628012 1177628012 1177640695 1593270007 1593270007 1593278463 2147450879 1316159488 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593274233 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1731723264 1593257324 762064236 762064236 762064236 762064236 762057894 762057894 762064236 762064236 762064236 762064236 762064236 1177616384 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593278459 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 2147188731 1870200832 1593274233 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1870229369 1731723264) offset: 0@0)! ! Object subclass: #EToyPeerToPeer instanceVariableNames: 'socket communicatorMorph process ipAddress connectionQueue dataQueue remoteSocketAddress leftOverData' classVariableNames: 'DEBUG PREVTICK' poolDictionaries: '' category: 'Nebraska-Network-EToy Communications'! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! doAwaitData [true] whileTrue: [ socket := connectionQueue getConnectionOrNilLenient. socket ifNil: [ (Delay forMilliseconds: 50) wait ] ifNotNil: [ self class new receiveDataOn: socket for: communicatorMorph ] ]. ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! doReceiveOneMessage | awaitingLength i length answer | awaitingLength := true. answer := WriteStream on: String new. [awaitingLength] whileTrue: [ leftOverData := leftOverData , socket receiveData. (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 receiveData. communicatorMorph commResult: {#commFlash -> true}. ]. answer := answer contents. answer size > length ifTrue: [ leftOverData := answer allButFirst: length. answer := answer first: length ]. ^answer ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! stopListening process ifNotNil: [process terminate. process := nil]. connectionQueue ifNotNil: [connectionQueue destroy. connectionQueue := nil]. ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'sd 11/20/2005 21:25'! doConnectForSend | addr | addr := NetNameResolver addressForName: ipAddress. addr ifNil: [ communicatorMorph commResult: {#message -> ('could not find ',ipAddress)}. ^false ]. socket connectNonBlockingTo: addr port: self class eToyCommunicationsPort. [socket waitForConnectionFor: 15] on: ConnectionTimedOut do: [:ex | communicatorMorph commResult: {#message -> ('no connection to ',ipAddress,' (', (NetNameResolver stringFromAddress: addr),')')}. ^false]. ^true ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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 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: 'sd 11/20/2005 21:26'! 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: 'EToys-Experimental'! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'ar 2/23/2001 20:56'! 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 withBlanksTrimmed. ]. ^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: 'yo 2/23/2005 17:24'! expandButton ^self buttonNamed: 'More' translated action: #doExpand color: self buttonColor help: 'Show more info on this project.' translated. ! ! !EToyProjectDetailsMorph methodsFor: 'as yet unclassified' stamp: 'dgd 3/16/2004 12:10'! expandedFormat ^ Preferences expandedPublishing or: [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: 'initialization' stamp: 'dgd 11/3/2004 17:55'! rebuild | bottomButtons | self removeAllMorphs. self addARow: { self lockedString: 'Please describe this project' translated font: Preferences standardEToysTitleFont. }. self addARow: {self space }. self addARow: { self rightLockedString: 'Name:' translated. self inAColumnForText: {self fieldForProjectName} }. self expandedFormat ifTrue: [ self fieldToDetailsMappings do: [ :each | self addARow: { self rightLockedString: each third translated. self inAColumnForText: {(self genericTextFieldNamed: each first) height: each fourth} }. ]. ]. self addARow: {self space }. bottomButtons _ self expandedFormat ifTrue: [ { self okButton. self cancelButton } ] ifFalse: [ { self okButton. self expandButton. self cancelButton } ]. self addARow: bottomButtons. self fillInDetails.! ! !EToyProjectDetailsMorph methodsFor: 'initialization' stamp: 'dgd 11/3/2004 17:29'! space ^ RectangleMorph new extent: 5 @ 5; color: Color transparent; borderWidth: 0 ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectDetailsMorph class instanceVariableNames: ''! !EToyProjectDetailsMorph class methodsFor: 'as yet unclassified' stamp: 'tak 3/15/2005 15:52'! 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 | aProject renameTo: newName. ]. me delete. aBlock value. ]; initialize; becomeModal; openCenteredInWorld! ! !EToyProjectDetailsMorph class methodsFor: 'as yet unclassified' stamp: 'mir 6/19/2001 10:17'! test1: aProject "EToyProjectDetailsMorph test1: Project current" (self basicNew) project: aProject actionBlock: [ :x | aProject world setProperty: #ProjectDetails toValue: x. x at: 'projectname' ifPresent: [ :newName | aProject renameTo: newName. ] ]; initialize; openCenteredInWorld! ! EToyCommunicatorMorph subclass: #EToyProjectHistoryMorph instanceVariableNames: 'changeCounter' classVariableNames: '' poolDictionaries: '' category: 'EToys-Experimental'! !EToyProjectHistoryMorph commentStamp: '' prior: 0! EToyProjectHistoryMorph new openInWorld EToyProjectHistoryMorph provides a quick reference of the most recent projects. Click on one to go there.! !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 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: 'nb 6/17/2003 12:25'! mouseUp: evt in: aMorph | tuple project url | (aMorph boundsInWorld containsPoint: evt cursorPoint) ifFalse: [^self]. tuple _ aMorph valueOfProperty: #projectParametersTuple ifAbsent: [^Beeper beep]. project _ tuple fourth first. (project notNil and: [project world notNil]) ifTrue: [self closeMyFlapIfAny. ^project enter]. url _ tuple third. url isEmptyOrNil ifTrue: [^Beeper beep]. self closeMyFlapIfAny. ProjectLoading thumbnailFromUrl: url. "--- newTuple _ { aProject name. aProject thumbnail. aProject url. WeakArray with: aProject. }. ---"! ! !EToyProjectHistoryMorph methodsFor: 'as yet unclassified' stamp: 'dgd 9/20/2003 18:52'! rebuild | history r1 | history _ ProjectHistory currentHistory mostRecentCopy. changeCounter _ ProjectHistory changeCounter. self removeAllMorphs. self rubberBandCells: false. "enable growing" r1 _ self addARow: { self inAColumn: { StringMorph new contents: 'Jump...' translated; 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: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ #raised! ! !EToyProjectHistoryMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !EToyProjectHistoryMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:25'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightBrown! ! !EToyProjectHistoryMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:46'! initialize "initialize the state of the receiver" super initialize. "" self listDirection: #topToBottom; layoutInset: 4; hResizing: #shrinkWrap; vResizing: #shrinkWrap; useRoundedCorners; rebuild ! ! !EToyProjectHistoryMorph methodsFor: 'stepping and presenter' stamp: 'RAA 7/10/2000 23:07'! step changeCounter = ProjectHistory changeCounter ifTrue: [^self]. self rebuild. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectHistoryMorph class instanceVariableNames: ''! !EToyProjectHistoryMorph class methodsFor: 'parts bin' stamp: 'sw 8/19/2001 21:15'! descriptionForPartsBin ^ self partName: 'ProjectHistory' categories: #('Navigation') documentation: 'A tool that lets you navigate back to recently-visited projects'! ! EToyProjectDetailsMorph subclass: #EToyProjectQueryMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'initialization' stamp: 'dgd 2/14/2003 22:26'! defaultColor "answer the default color/fill style for the receiver" ^ Color r: 0.545 g: 0.47 b: 0.621! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyProjectQueryMorph class instanceVariableNames: ''! !EToyProjectQueryMorph class methodsFor: 'as yet unclassified' stamp: 'tak 3/15/2005 16:37'! onServer: aProjectServer "EToyProjectQueryMorph onServer: SuperSwikiServer testOnlySuperSwiki" | criteria clean | (self basicNew) project: nil actionBlock: [ :x | criteria _ OrderedCollection new. x keysAndValuesDo: [ :k :v | (clean _ v withBlanksTrimmed) isEmpty ifFalse: [criteria add: k,': *',clean,'*']]. aProjectServer queryProjectsAndShow: criteria]; initialize; becomeModal; openCenteredInWorld! ! !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: 'EToys-Experimental'! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'RAA 9/21/2000 15:01'! buttonColor ^color darker! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:25'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString translated font: self myFont; color: aColor; borderColor: aColor muchDarker; actionSelector: aSymbol; setBalloonText: helpString translated. col _ (self inAColumn: {f}) hResizing: #spaceFill. ^col! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:24'! cancelButton ^ self buttonNamed: 'Cancel' action: #doCancel color: ColorTheme current cancelColor 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: 'ar 2/23/2001 20:55'! doOK self validateTheProjectName ifFalse: [^self]. self delete. actionBlock value: (namedFields at: 'projectname') contents string withBlanksTrimmed.! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'yo 2/23/2005 17:25'! fieldForProjectName | tm | tm _ self genericTextFieldNamed: 'projectname'. tm crAction: (MessageSend receiver: self selector: #doOK). tm setBalloonText: 'Pick a name 24 characters or less and avoid the following characters: : < > | / \ ? * " .' translated. ^tm ! ! !EToyProjectRenamerMorph methodsFor: 'as yet unclassified' stamp: 'dgd 11/2/2004 21:23'! okButton ^ self buttonNamed: 'OK' action: #doOK color: ColorTheme current okColor 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: 'dgd 10/8/2003 18:53'! validateTheProjectName | proposed | proposed _ (namedFields at: 'projectname') contents string withBlanksTrimmed. proposed isEmpty ifTrue: [ self inform: 'I do need a name for the project' translated. ^false ]. proposed size > 24 ifTrue: [ self inform: 'Please make the name 24 characters or less' translated. ^false ]. (Project isBadNameForStoring: proposed) ifTrue: [ self inform: 'Please remove any funny characters from the name' translated. ^false ]. proposed = theProject name ifTrue: [^true]. (ChangeSorter changeSetNamed: proposed) ifNotNil: [ Utilities inform: 'Sorry that name is already used' translated. ^false ]. ^true! ! !EToyProjectRenamerMorph methodsFor: 'initialization' stamp: 'dgd 11/2/2004 21:09'! defaultColor "answer the default color/fill style for the receiver" ^ ColorTheme current dialogColor! ! !EToyProjectRenamerMorph methodsFor: 'initialization' stamp: 'dgd 1/7/2005 19:21'! initialize "initialize the state of the receiver" super initialize. "" self vResizing: #shrinkWrap; hResizing: #shrinkWrap; layoutInset: 4; useRoundedCorners; rebuild! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'Nebraska-Morphic-Collaborative'! !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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! fixOldVersion | uName uForm uEmail uIP | uName := self userName. uForm := userPicture ifNil: [ (self findDeepSubmorphThat: [ :x | (x isKindOf: ImageMorph) or: [x isSketchMorph]] 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: '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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! mouseEnteredDZ | dz | dz := self valueOfProperty: #specialDropZone ifAbsent: [^self]. dz color: Color blue.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! mouseLeftDZ | dz | dz := self valueOfProperty: #specialDropZone ifAbsent: [^self]. dz color: Color transparent.! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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: 'aoy 2/15/2003 20:59'! startAudioChat: toggleMode | chat r | (self valueOfProperty: #embeddedAudioChatHolder) ifNotNil: [toggleMode ifFalse: [^self]. ^self killExistingChat]. chat := AudioChatGUI new ipAddress: self ipAddress. (self ownerThatIsA: EToyFridgeMorph) isNil ifTrue: [chat removeConnectButton; vResizing: #shrinkWrap; hResizing: #shrinkWrap; borderWidth: 2. "we already know the connectee" r := (self addARow: { chat}) vResizing: #shrinkWrap. self world startSteppingSubmorphsOf: chat. self setProperty: #embeddedAudioChatHolder toValue: r. self hResizing: #shrinkWrap; vResizing: #shrinkWrap] ifFalse: [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: 'sd 11/20/2005 21:25'! 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: 'sd 11/20/2005 21:25'! 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 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 8/20/2001 13:03'! userName ^ (self findDeepSubmorphThat: [ :x | x isKindOf: StringMorph] ifAbsent: [^nil]) contents ! ! !EToySenderMorph methodsFor: 'as yet unclassified' stamp: 'sd 11/20/2005 21:25'! 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: 'debug and other' 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: 'dropping/grabbing' stamp: 'sd 11/20/2005 21:25'! aboutToBeGrabbedBy: aHand | aFridge | super aboutToBeGrabbedBy: aHand. aFridge := self ownerThatIsA: EToyFridgeMorph. aFridge ifNil: [^self]. aFridge noteRemovalOf: self.! ! !EToySenderMorph methodsFor: 'dropping/grabbing' stamp: 'sd 11/20/2005 21:25'! wantsDroppedMorph: aMorph event: evt | dz | dz := self valueOfProperty: #specialDropZone ifAbsent: [^false]. (dz bounds containsPoint: (evt cursorPoint)) ifFalse: [^false]. ^true.! ! !EToySenderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color magenta! ! !EToySenderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 4! ! !EToySenderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:26'! defaultColor "answer the default color/fill style for the receiver" ^ Color lightMagenta! ! !EToySenderMorph methodsFor: 'initialization' stamp: 'dgd 2/17/2003 19:58'! initialize "initialize the state of the receiver" 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; layoutInset: 4; setProperty: #normalBorderColor toValue: self borderColor; setProperty: #flashingColors toValue: {Color red. Color yellow}! ! !EToySenderMorph methodsFor: 'layout' stamp: 'sd 11/20/2005 21:25'! 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" "7 mar 2001 - remove #veryDeepCopy" myCopy := morphToDrop. "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: 'parts bin' stamp: 'RAA 8/20/2001 13:08'! initializeToStandAlone super initializeToStandAlone. self installModelIn: ActiveWorld. ! ! !EToySenderMorph methodsFor: 'stepping and presenter' 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 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: 'sd 11/20/2005 21:26'! 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: 'sd 11/20/2005 21:26'! pictureForIPAddress: ipString | senderMorphs | senderMorphs := EToySenderMorph allInstances select: [ :x | x userPicture notNil and: [x ipAddress = ipString] ]. senderMorphs isEmpty ifTrue: [^nil]. ^senderMorphs first userPicture ! ! !EToySenderMorph class methodsFor: 'parts bin' stamp: 'RAA 12/18/2001 10:05'! descriptionForPartsBin ^ self partName: 'Badge' categories: #('Collaborative') documentation: 'A tool for collaborating with other Squeak users' sampleImageForm: (Form extent: 66@72 depth: 16 fromArray: #( 7175 1545042975 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082429975 470220800 470252575 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082413575 1545042975 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082429975 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 1582767304 1032871511 2134867775 2134842568 2134867775 2134867775 2134867775 1032879935 2134867775 2134867775 2134867775 2134867775 1582792511 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134842568 1032871511 1032863120 1582775696 1032871511 2134867775 2134867775 1032871511 2134842568 1032863120 482885008 1032879935 482901823 482885008 1032879935 1032863120 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 2134850960 1032879935 1032863120 2134850960 2134867775 2134859351 482876616 2134850960 2134867775 1032879935 1032879935 1032879935 1032879935 1032879935 1032863120 1582792511 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 1582767304 1032871511 1032863120 1582775696 1032871511 2134867775 2134842568 1582767304 1582767304 1582792511 482893399 482893399 482893399 482893399 482893399 1032863120 1582792511 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 1032863120 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 65537 65537 65537 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 65537 1032863120 1032863120 98111 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 81296 2134867775 2134867775 1032847361 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 1032879935 2134867775 2134867775 2134835201 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 65537 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 81296 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 65537 98111 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 65537 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 65537 81296 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 65537 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 81296 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134835201 98111 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134850960 1032879935 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 2147450879 2147450879 1039171583 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 1039171583 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141142512 1039154672 1593270007 1039163127 1593278463 1593261552 2147442423 1039154672 2147433968 1039154672 1593270007 1039163127 1593278463 1593261552 2147442423 1593270007 1593261552 2147450879 2147442423 1593270007 2147442423 1039171583 484990711 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141150967 1039163127 1039171583 1039163127 1039171583 1039171583 1039154672 1039154672 1039163127 1039163127 1039171583 1039163127 1039171583 484982256 1039146216 1593270007 484982256 1039171583 2147425512 1593261552 2147425512 1039154672 1039171583 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 1039154672 1593278463 1039171583 1039171583 1039171583 1039154672 1039146216 1593278463 1039154672 1593278463 1039171583 1039171583 1039171583 1593261552 2147450879 1039171583 1593270007 2147433968 2147433968 2147433968 2147442423 1039171583 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 1593270007 2147450879 1039163127 1039163127 1593261552 2147442423 1039163127 2147450879 1593270007 2147450879 1039163127 1039163127 1593261552 2147433968 1593278463 1593261552 2147442423 2147433968 1593261552 1593270007 1039171583 2147442423 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2141159231 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450783 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141150967 1039171583 1593261552 2147442423 1039171583 2147450879 2147442423 2147450879 1593278463 1593261552 2147450879 2147442423 1039171583 2147442423 2147450879 2147442423 1039171583 2147442423 2147450783 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141150967 2147433968 1039171583 1039154672 2147433968 2147450879 1593261552 2147442423 1039171583 1593278463 1039171583 2147433968 2147433968 1593261552 2147450879 2147442423 2147433968 1593261552 2147450783 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 1039171583 1039171583 1039154672 1039154672 2147450879 2147433968 2147425512 484990711 2147433968 1593278463 2147433968 1039154672 2147433968 2147450879 2147450879 1039163127 484973800 1593278367 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141142512 2147442423 1039171583 1039171583 1593270007 1593278463 2147433968 2147450879 1039171583 2147450879 1039163127 2147450879 1593270007 2147433968 2147442423 2147450879 2147433968 2147433968 2147450783 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141142512 1039163127 1593261552 2147442423 1593278463 1593278463 1593261552 2147450879 1039163127 1593261552 2147442423 2147442423 1593278463 1593261552 2147442423 2147442423 1039171583 2147433968 1593278367 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2141159423 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450879 2147450783 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134861595 1391951679 2134867775 2134867775 2134856439 1729855295 2134867775 2134867775 1729849115 2134867775 2134867775 2134861595 1729855295 2134867775 2134867775 1729843959 1391951679 2134867775 2134867775 2134856439 1729855295 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 1326930843 1879001943 1729855295 2134861595 1398112251 1738035990 2134867775 2134855446 1536646039 1326874431 2134867775 1387357800 1718112945 2134867775 2134856463 1736736736 2145407816 1729855295 2134861595 1398243327 1738232599 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 1032863120 1032879935 2134867775 1032863120 1032879935 2134856439 1879011327 1879011327 1264025407 2134856533 2147188731 2147188731 1391951679 1391947770 1878683642 1878676215 2134856439 2120646246 2120646246 1391951679 1391951840 2145419232 2145419232 1397260095 2134856535 2147450879 2147450879 1391951679 2134867775 2082438175 2082438175 2134867775 2134842568 1507359 1514696 2134842568 48235488 48241864 2134854487 1391932912 904949759 1879003895 1391951867 484908263 1039040507 1398112063 1263890426 904753146 1878674261 2134856331 2120629539 1025736294 1384873791 1397260256 2145402336 2145419232 2145407735 1391951871 1039154672 1039171583 1398243135 2134867775 2082438175 2082438175 2134867775 2134835216 2031647 2031632 2134835680 65012704 65012192 2134854487 904949759 1879011327 1879003895 1391951867 2147171822 2147188731 1398112063 1263890426 904753146 1878674261 2134856331 2120646246 1025736294 1384873791 1397260256 1591754208 2145419232 2145407735 1391951871 1039163127 2147450879 1398243135 2134867775 2082438175 2082438175 2134867775 2134835216 2031647 2031632 2134835680 65012704 65012192 2134854487 904949759 1879011327 1879003895 1391951867 2147171822 2147188731 1398112063 1263890426 904753146 1878674261 2134856331 2120629539 2120646246 1384873791 1397260256 484449504 1591771104 2145407735 1391951871 2147442423 1593278463 1398243135 2134867775 2082438175 2082438175 2134867775 2134842568 1507359 1514696 2134842568 48235488 48241864 2134854487 1391932912 904949759 1879003895 1391951867 1593056487 2147188731 1398112063 1263890426 1391685626 1878674261 2134856331 2120637892 2120646246 1384873791 1397251808 484466400 484474848 2145407735 1391951871 484982256 1593278463 1398243135 2134867775 2082438175 2082438175 2134867775 2134867775 1032863120 1032879935 2134867775 1032863120 1032879935 2134855447 1879011327 1879011327 1536911131 1729849240 2147188731 2147188731 1393983295 1326870522 1878683642 1878675222 2134856369 2120646246 2120646246 1387364159 1393524704 2145419232 2145419232 1736730395 1729849243 2147450879 2147450879 1394048831 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134861595 1536913407 1879011327 1326939967 2134856470 2147188731 2147182488 1729855295 1729846167 1878683642 1536648987 2134861595 1718124134 2120640104 1729855295 1729849220 2145419232 2145419232 1393524543 2134856471 2147450879 2147444635 1729855295 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 1729842967 1264014071 2134867775 2134867775 1391940437 1393977115 2134867775 2134861595 1326862102 1729855295 2134867775 1729843889 1387357979 2134867775 2134861595 1393513288 1397248759 2134867775 2134867775 1391940439 1394042651 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 2082438175 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082438175 1545042975 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2134867775 2082429975 470252575 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082413575 7175 1545042975 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082438175 2082429975 470220800) offset: 0@0)! ! StandardScriptingSystem subclass: #EToySystem instanceVariableNames: '' classVariableNames: 'EToyVersion EToyVersionDate' poolDictionaries: '' category: 'EToys-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: 'sd 5/11/2003 22:13'! loadJanForms "EToySystem loadJanForms" | aReferenceStream newFormDict | aReferenceStream _ ReferenceStream fileNamed: 'JanForms'. newFormDict _ aReferenceStream next. aReferenceStream close. newFormDict associationsDo: [:assoc | Imports default importImage: assoc value named: assoc key]! ! !EToySystem class methodsFor: 'development support' stamp: 'sd 1/16/2004 20:55'! stripMethodsForExternalRelease "EToySystem stripMethodsForExternalRelease" SmalltalkImage current stripMethods: self methodsToStripForExternalRelease messageCode: '2.3External'! ! !EToySystem class methodsFor: 'external release' stamp: 'tk 4/10/2001 13:08'! methodsToStripForExternalRelease "Answer a list of triplets #(className, class/instance, methodName) of methods to be stripped in an external release." ^ #( (EToySystem class prepareRelease) (EToySystem class previewEToysOn:) )! ! !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: '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: 'EToys-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 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 methodsFor: 'event handling' stamp: 'RAA 7/30/2000 15:08'! keyStroke: evt (owner notNil and: [owner keyStroke: evt]) ifTrue: [^self]. ^super keyStroke: evt.! ! !EToyTextNode methodsFor: 'event handling' stamp: 'RAA 7/30/2000 10:07'! keyboardFocusChange: aBoolean super keyboardFocusChange: aBoolean. aBoolean ifTrue: [owner takeFocus]. ! ! !EToyTextNode methodsFor: 'initialization' 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 class instanceVariableNames: ''! !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) )! ! !EToyTextNode class methodsFor: 'new-morph participation' stamp: 'RAA 8/8/2000 14:36'! includeInNewMorphMenu ^ false! ! ListItemWrapper subclass: #EToyTextNodeWrapper instanceVariableNames: 'parentWrapper' classVariableNames: '' poolDictionaries: '' category: 'EToys-Outliner'! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/30/2000 17:25'! addNewChildAfter: anotherOrNil item addNewChildAfter: anotherOrNil. ! ! !EToyTextNodeWrapper methodsFor: 'as yet unclassified' stamp: 'nb 6/17/2003 12:25'! addSibling parentWrapper ifNil: [^Beeper beep]. parentWrapper addNewChildAfter: item.! ! !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: 'nb 6/17/2003 12:25'! delete parentWrapper ifNil: [^Beeper 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 methodsFor: 'converting' stamp: 'RAA 7/30/2000 00:56'! asString ^item contents! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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! ! EToyVocabulary subclass: #EToyVectorVocabulary instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-Protocols'! !EToyVectorVocabulary commentStamp: '' prior: 0! An extension of the etoy vocabulary in support of an experiment Alan Kay requested in summer 2001 for allowing any morph/player to be thought of as a vector. In effect, adds a category #vector to the viewer for such all morphs. Consult Ted Kaehler and Alan Kay for more information on this track.! !EToyVectorVocabulary methodsFor: 'initialization' stamp: 'sw 9/10/2001 14:44'! addCustomCategoriesTo: categoryList "Add any further categories to the default list of viewer categories for an object" categoryList add: #vector! ! !EToyVectorVocabulary methodsFor: 'initialization' stamp: 'sw 9/26/2001 03:56'! eToyVectorTable "Answer a table of specifications to send to #addFromTable: which add the 'players are vectors' extension to the etoy vocabulary." "(selector setterOrNil ((arg name arg type)...) resultType (category ...) 'help msg' 'wording' autoUpdate)" ^ #( (+ nil ((aVector Player)) Player (geometry) 'Adds two players together, treating each as a vector from the origin.') (- nil ((aVector Player)) Player (geometry) 'Subtracts one player from another, treating each as a vector from the origin.') (* nil ((aVector Number)) Player (geometry) 'Multiply a player by a number, treating the Player as a vector from the origin.') (/ nil ((aVector Number)) Player (geometry) 'Divide a player by a Number, treating the Player as a vector from the origin.') (incr: nil ((aVector Player)) unknown (geometry) 'Each Player is a vector from the origin. Increase one by the amount of the other.' 'increase by') (decr: nil ((aVector Player)) unknown (geometry) 'Each Player is a vector from the origin. Decrease one by the amount of the other.' 'decrease by') (multBy: nil ((factor Number)) unknown (geometry) 'A Player is a vector from the origin. Multiply its length by the factor.' 'multiplied by') (dividedBy: nil ((factor Number)) unknown (geometry) 'A Player is a vector from the origin. Divide its length by the factor.' 'divided by') "distance and theta are already in Player. See additionsToViewerCategoryGeometry" ).! ! !EToyVectorVocabulary methodsFor: 'initialization' stamp: 'mir 7/15/2004 19:29'! initialize "Initialize the vocabulary" super initialize. self addFromTable: self eToyVectorTable. self vocabularyName: #Vector. self documentation: 'This vocabulary adds to the basic etoy experience an interpretation of "players are vectors", requested by Alan Kay and implemented by Ted Kaehler in summer 2001'. ! ! !EToyVectorVocabulary methodsFor: 'method list' stamp: 'sw 7/9/2005 12:02'! allMethodsInCategory: aCategorySymbol 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" ^ ((anObject isKindOf: Player) and: [aCategorySymbol == #vector]) ifTrue: [anObject costume class vectorAdditions collect: [:anAddition | (self methodInterfaceFrom: anAddition) selector]] ifFalse: [super allMethodsInCategory: aCategorySymbol forInstance: anObject ofClass: aClass]! ! Vocabulary subclass: #EToyVocabulary instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'EToys-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: 'category list' stamp: 'nk 8/29/2004 17:17'! categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass "Answer the category list for the given object, considering only code implemented in aClass and lower" ^ (anObject isPlayerLike) ifTrue: [self flag: #deferred. "The bit commented out on next line is desirable but not yet workable, because it delivers categories that are not relevant to the costume in question" "#(scripts #'instance variables'), (super categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass)]" self translatedWordingsFor: ((mostGenericClass == aClass) ifFalse: [anObject categoriesForVocabulary: self] ifTrue: [{ScriptingSystem nameForScriptsCategory. ScriptingSystem nameForInstanceVariablesCategory}])] ifFalse: [super categoryListForInstance: anObject ofClass: aClass limitClass: mostGenericClass]! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'sw 9/13/2001 16:36'! addCustomCategoriesTo: categoryList "Add any further categories to the categoryList -- for benefit of subclasses wishing to override."! ! !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 9/13/2001 16:39'! methodInterfaceFrom: elementTuple "Tedious revectoring: The argument is a tuple of the sort that #additionsToViewerCategory: answers a list of; answer a MethodInterface" ^ elementTuple first == #command ifTrue: [MethodInterface new initializeFromEToyCommandSpec: elementTuple category: nil] ifFalse: "#slot format" [MethodInterface new initializeFromEToySlotSpec: elementTuple]! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'RAA 6/4/2001 19:12'! objectForDataStream: refStrm "I am about to be written on an object file. Write a path to me in the other system instead." vocabularyName == #eToy ifFalse: [^ self]. ^ DiskProxy global: #Vocabulary selector: #vocabularyNamed: args: (Array with: vocabularyName) ! ! !EToyVocabulary methodsFor: 'initialization' stamp: 'nk 10/6/2004 11:56'! setCategoryDocumentationStrings "Initialize the documentation strings associated with the old etoy categories, in English" self setCategoryStrings: #( (basic 'basic' 'a few important things') (#'book navigation' 'book navigation' 'relating to book, stacks, etc') (button 'button' 'for thinking of this object as a push-button control') (collections 'collections' 'for thinking of this object as a collection') (fog 'fog' '3D fog') (geometry 'geometry' 'measurements and coordinates') (#'color & border' 'color & border' 'matters concerning the colors and borders of objects') (graphics 'graphics' 'for thinking of this object as a picture') (variables 'variables' 'variables added by this object') (joystick 'joystick' 'the object as a Joystick') (miscellaneous 'miscellaneous' 'various commands') (motion 'motion' 'matters relating to moving and turning') (paintbox 'paintbox' 'the painting palette') (#'pen trails' 'pen trails' 'relating to trails put down by pens') (#'pen use' 'pen use' 'use of an object''s "pen"') (playfield 'playfield' 'the object as a container for other visible objects') (sampling 'sampling' 'sampling') (scripting 'scripting' 'commands to start and stop scripts') (scripts 'scripts' 'methods added by this object') (slider 'slider' 'functions useful to sliders') (speaker 'speaker' 'the object as an audio Speaker') (#'stack navigation' 'stack navigation' 'navigation within a stck') (storyboard 'storyboard' 'storyboard') (tests 'tests' 'yes/no tests, to use in "Test" panes of scripts') (text 'text' 'The object as text') (vector 'vector' 'The object as a vector') (viewing 'viewing' 'matters relating to viewing') ) ! ! !EToyVocabulary methodsFor: 'method list' stamp: 'sw 10/7/2004 17:09'! allMethodsInCategory: aCategoryName 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 | aCategoryName ifNil: [^ OrderedCollection new]. aClass isUniClass ifTrue: [aCategoryName = ScriptingSystem nameForScriptsCategory ifTrue: [^ aClass namedTileScriptSelectors]. aCategoryName = ScriptingSystem nameForInstanceVariablesCategory ifTrue: [^ aClass slotInfo keys asSortedArray collect: [:anInstVarName | Utilities getterSelectorFor: anInstVarName]]]. unfiltered _ (isAll _ aCategoryName = self allCategoryName) ifTrue: [methodInterfaces collect: [:anInterface | anInterface selector]] ifFalse: [aCategory _ categories detect: [:cat | cat categoryName = aCategoryName] 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! ! !EToyVocabulary methodsFor: 'method list' stamp: 'sw 8/3/2005 14:03'! masterOrderingOfPhraseSymbols "Answer a dictatorially-imposed presentation list of phrase-symbols. This governs the order in which suitable phrases are presented in etoy viewers using the etoy vocabulary. For any given category, the default implementation is that any items that are in this list will occur first, in the order specified here; after that, all other items will come, in alphabetic order by formal selector." ^ #(beep: forward: turn: getX getY getLocationRounded getHeading getScaleFactor getLeft getRight getTop getBottom getLength getWidth getTheta getDistance getHeadingTheta getUnitVector startScript: pauseScript: stopScript: startAll: pauseAll: stopAll: tellAllSiblings: doScript: getColor getUseGradientFill getSecondColor getRadialGradientFill getBorderWidth getBorderColor getBorderStyle getRoundedCorners getDropShadow getShadowColor getVolume play playUntilPosition: stop rewind getIsRunning getRepeat getPosition getTotalFrames getTotalSeconds getFrameGraphic getVideoFileName getSubtitlesFileName getGraphic getBaseGraphic getAllowEtoyUserCustomEvents #getAutoExpansion #getAutoLineLayout #getBatchPenTrails getDropProducesWatcher #getFenceEnabled #getIndicateCursor #getIsOpenForDragNDrop #getIsPartsBin #getMouseOverHalos #getOriginAtCenter #getShowThumbnail getFenceEnabled getKeepTickingWhilePainting getOliveHandleForScriptedObjects getUseVectorVocabulary )! ! !EToyVocabulary methodsFor: 'method list' stamp: 'sw 7/14/2004 18:24'! phraseSymbolsToSuppress "Answer a dictatorially-imposed list of phrase-symbols that are to be suppressed from viewers when the eToyFriendly preference is set to true. This list at the moment corresponds to the wishes of Alan and Kim and the LA teachers using Squeak in school-year 2001-2" ^ Preferences eToyFriendly ifTrue: [#(moveToward: followPath goToRightOf: getViewingByIcon initiatePainting append: prepend: getClipSubmorphs touchesA:)] ifFalse: [#()]! ! !EToyVocabulary methodsFor: '*flexibleVocabularies-flexibleVocabularies-testing' stamp: 'nk 8/29/2004 17:20'! isEToyVocabulary ^true! ! !EToyVocabulary methodsFor: '*flexibleVocabularies-flexiblevocabularies-initialization' stamp: 'stephaneducasse 2/4/2006 20:33'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" | classes aMethodCategory selector selectors categorySymbols aMethodInterface | 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 := self class morphClassesDeclaringViewerAdditions. classes do: [:aMorphClass | categorySymbols addAll: aMorphClass unfilteredCategoriesForViewer]. self addCustomCategoriesTo: categorySymbols. "For benefit, e.g., of EToyVectorVocabulary" categorySymbols asOrderedCollection do: [:aCategorySymbol | aMethodCategory := ElementCategory new categoryName: aCategorySymbol. selectors := Set new. classes do: [:aMorphClass | (aMorphClass additionsToViewerCategory: aCategorySymbol) do: [:anElement | aMethodInterface := self methodInterfaceFrom: anElement. selectors add: (selector := aMethodInterface selector). (methodInterfaces includesKey: selector) ifFalse: [methodInterfaces at: selector put: aMethodInterface]. self flag: #deferred. "NB at present, the *setter* does not get its own method interface. Need to revisit"]. (selectors copyWithout: #unused) asSortedArray do: [:aSelector | aMethodCategory elementAt: aSelector put: (methodInterfaces at: aSelector)]]. self addCategory: aMethodCategory]. self addCategoryNamed: ScriptingSystem nameForInstanceVariablesCategory. self addCategoryNamed: ScriptingSystem nameForScriptsCategory. self setCategoryDocumentationStrings. (self respondsTo: #applyMasterOrdering) ifTrue: [ self applyMasterOrdering ].! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EToyVocabulary class instanceVariableNames: ''! !EToyVocabulary class methodsFor: '*flexibleVocabularies-flexiblevocabularies-scripting' stamp: 'nk 10/8/2004 16:21'! masterOrderingOfCategorySymbols "Answer a dictatorially-imposed presentation list of category symbols. This governs the order in which available vocabulary categories are presented in etoy viewers using the etoy vocabulary. The default implementation is that any items that are in this list will occur first, in the order specified here; after that, all other items will come, in alphabetic order by their translated wording." ^#(basic #'color & border' geometry motion #'pen use' tests layout #'drag & drop' scripting observation button search miscellaneous)! ! !EToyVocabulary class methodsFor: '*flexibleVocabularies-flexiblevocabularies-scripting' stamp: 'nk 7/3/2003 20:07'! morphClassesDeclaringViewerAdditions "Answer a list of actual morph classes that either implement #additionsToViewerCategories, or that have methods that match #additionToViewerCategory* ." ^(Morph class allSubInstances select: [ :ea | ea hasAdditionsToViewerCategories ]) ! ! !EToyVocabulary class methodsFor: '*flexibleVocabularies-flexiblevocabularies-scripting' stamp: 'nk 9/11/2004 18:00'! vocabularySummary "Answer a string describing all the vocabulary defined anywhere in the system." " (StringHolder new contents: EToyVocabulary vocabularySummary) openLabel: 'EToy Vocabulary' translated " | etoyVocab rt interfaces allAdditions | etoyVocab := Vocabulary eToyVocabulary. etoyVocab initialize. "just to make sure that it's unfiltered." ^ String streamContents: [:s | self morphClassesDeclaringViewerAdditions do: [:cl | s nextPutAll: cl name; cr. allAdditions := cl allAdditionsToViewerCategories. cl unfilteredCategoriesForViewer do: [ :cat | allAdditions at: cat ifPresent: [ :additions | interfaces := ((etoyVocab categoryAt: cat) ifNil: [ ElementCategory new ]) elementsInOrder. interfaces := interfaces select: [:ea | additions anySatisfy: [:tuple | (tuple first = #slot ifTrue: [tuple at: 7] ifFalse: [tuple at: 2]) = ea selector]]. s tab; nextPutAll: cat translated; cr. interfaces do: [:if | s tab: 2. rt := if resultType. rt = #unknown ifTrue: [s nextPutAll: 'command' translated] ifFalse: [s nextPutAll: 'property' translated; nextPut: $(; nextPutAll: (if companionSetterSelector ifNil: ['RO'] ifNotNil: ['RW']) translated; space; nextPutAll: rt translated; nextPutAll: ') ']. s tab; print: if wording; space. if argumentVariables do: [:av | s nextPutAll: av variableName; nextPut: $(; nextPutAll: av variableType asString; nextPut: $)] separatedBy: [s space]. s tab; nextPutAll: if helpMessage; cr]]]]]! ! EUCTextConverter subclass: #EUCJPTextConverter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !EUCJPTextConverter commentStamp: '' prior: 0! Text converter for Japanese variation of EUC.! !EUCJPTextConverter methodsFor: 'private' stamp: 'yo 3/17/2004 00:41'! languageEnvironment ^ JapaneseEnvironment. ! ! !EUCJPTextConverter methodsFor: 'private' stamp: 'yo 10/23/2002 10:09'! leadingChar ^ JISX0208 leadingChar ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EUCJPTextConverter class instanceVariableNames: ''! !EUCJPTextConverter class methodsFor: 'utilities' stamp: 'yo 12/19/2003 22:00'! encodingNames ^ #('euc-jp' 'eucjp') copy ! ! EUCTextConverter subclass: #EUCKRTextConverter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !EUCKRTextConverter commentStamp: '' prior: 0! Text converter for Korean variation of EUC.! !EUCKRTextConverter methodsFor: 'private' stamp: 'yo 3/17/2004 00:41'! languageEnvironment ^ KoreanEnvironment. ! ! !EUCKRTextConverter methodsFor: 'private' stamp: 'yo 10/23/2002 15:19'! leadingChar ^ KSX1001 leadingChar ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EUCKRTextConverter class instanceVariableNames: ''! !EUCKRTextConverter class methodsFor: 'utilities' stamp: 'yo 2/17/2004 18:45'! encodingNames ^ #('euc-kr' 'ks-c-5601-1987' 'euckr') copy ! ! TextConverter subclass: #EUCTextConverter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-TextConversion'! !EUCTextConverter commentStamp: '' prior: 0! Text converter for Extended Unix Character. This is an abstract class. The CJK variations are implemented as subclasses.! !EUCTextConverter methodsFor: 'conversion' stamp: 'ar 4/9/2005 22:25'! nextFromStream: aStream | character1 character2 offset value1 value2 nonUnicodeChar | aStream isBinary ifTrue: [^ aStream basicNext]. character1 _ aStream basicNext. character1 isNil ifTrue: [^ nil]. character1 asciiValue <= 127 ifTrue: [^ character1]. character2 _ aStream basicNext. character2 = nil ifTrue: [^ nil]. offset _ 16rA1. value1 _ character1 asciiValue - offset. value2 _ character2 asciiValue - offset. (value1 < 0 or: [value1 > 93]) ifTrue: [^ nil]. (value2 < 0 or: [value2 > 93]) ifTrue: [^ nil]. nonUnicodeChar _ Character leadingChar: self leadingChar code: value1 * 94 + value2. ^ Character leadingChar: self languageEnvironment leadingChar code: nonUnicodeChar asUnicode. ! ! !EUCTextConverter methodsFor: 'conversion' stamp: 'ar 4/12/2005 14:10'! nextPut: aCharacter toStream: aStream | value leadingChar nonUnicodeChar value1 value2 | aStream isBinary ifTrue: [^aCharacter storeBinaryOn: aStream]. value _ aCharacter charCode. leadingChar _ aCharacter leadingChar. (leadingChar = 0 and: [value < 128]) ifTrue: [ aStream basicNextPut: (Character value: value). ^ aStream ]. (128 <= value and: [value < 256]) ifTrue: [^ aStream]. aCharacter isTraditionalDomestic ifFalse: [ nonUnicodeChar _ self nonUnicodeClass charFromUnicode: value. ] ifTrue: [ nonUnicodeChar _(Character value: value) ]. nonUnicodeChar ifNotNil: [ value _ nonUnicodeChar charCode. value1 _ value // 94 + 161. value2 _ value \\ 94 + 161. aStream basicNextPut: (Character value: value1). aStream basicNextPut: (Character value: value2). ^ aStream ] ! ! !EUCTextConverter methodsFor: 'friend' stamp: 'yo 1/18/2004 15:10'! restoreStateOf: aStream with: aConverterState aStream position: aConverterState. ! ! !EUCTextConverter methodsFor: 'friend' stamp: 'yo 1/18/2004 15:10'! saveStateOf: aStream ^ aStream position. ! ! !EUCTextConverter methodsFor: 'private' stamp: 'yo 3/17/2004 00:40'! languageEnvironment self subclassResponsibility ! ! !EUCTextConverter methodsFor: 'private' stamp: 'yo 10/23/2002 10:09'! leadingChar ^ self subclassResponsibility ! ! !EUCTextConverter methodsFor: 'private' stamp: 'yo 10/4/2003 15:48'! nonUnicodeClass ^ (EncodedCharSet charsetAt: self leadingChar). ! ! Object subclass: #EditCommand instanceVariableNames: 'textMorph phase replacedText replacedTextInterval newText newTextInterval lastSelectionInterval' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Text Support'! !EditCommand commentStamp: '' prior: 0! This class handles all paragraph surgery in VI. In general, subclasses of EditCommand should be able to rely on the super class' undo/redo machinery -- only the repeat command needs to be overridden in most cases. This assumes, of course, that the newText, replacedText, newTextInterval, and replacedTextInterval have been set correctly. When setting the interval, use normal mode style selections, not insert mode selections (see class comment of VIMorphEditor). 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.! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/11/2002 17:12'! iEditCommand ^true! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 14:05'! lastSelectionInterval ^lastSelectionInterval! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:36'! newText ^newText! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/4/2002 22:37'! newTextInterval ^newTextInterval! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:36'! newTextInterval: anInterval ^newText _ anInterval! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:36'! newText: aText ^newText _ aText! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 17:01'! pEditor ^textMorph editor ! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 13:40'! phase ^phase ! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 13:40'! phase: aSymbol ^phase _ aSymbol ! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/11/2002 20:58'! printOn: aStream | | aStream nextPutAll: self class name; nextPut: $[; nextPutAll: ('new: ', newTextInterval asString,' -> "', newText, '", rText: ', replacedTextInterval asString,' -> "', replacedText, '"'); nextPut: $].! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:37'! replacedText ^replacedText! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/4/2002 22:30'! replacedTextInterval ^replacedTextInterval! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:36'! replacedTextInterval: anInterval ^replacedTextInterval _ anInterval! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 1/5/2002 16:37'! replacedText: aText ^replacedText _ aText! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 17:04'! textMorphEditor ^textMorph editor ! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 17:05'! textMorphString ^textMorph text string ! ! !EditCommand methodsFor: 'accessors' stamp: 'sps 7/24/2003 17:02'! textMorphStringSize ^textMorph text string size ! ! !EditCommand methodsFor: 'command execution' stamp: 'sps 1/7/2002 21:37'! doCommand ^self redoCommand ! ! !EditCommand methodsFor: 'command execution' stamp: 'sps 7/24/2003 17:04'! redoCommand | | "Debug dShow: ('rInterval: ', replacedTextInterval asString, '. rText: ', replacedText string, ' nInterval: ', newTextInterval asString, ' nText: ', newText string)." self textMorphEditor noUndoReplace: replacedTextInterval with: newText. "Debug dShow: ('lastSelInt: ', lastSelectionInterval asString)." ! ! !EditCommand methodsFor: 'command execution' stamp: 'sps 7/24/2003 17:04'! undoCommand "Debug dShow: ('new Interval: ', newTextInterval asString, '. rText: ', replacedText string)." self textMorphEditor noUndoReplace: newTextInterval with: replacedText. ! ! !EditCommand methodsFor: 'initialization' stamp: 'sps 7/24/2003 17:01'! textMorph: tm replacedText: rText replacedTextInterval: rInterval newText: nText newTextInterval: nInterval textMorph _ tm. replacedText _ rText. replacedTextInterval _ rInterval. newText _ nText. newTextInterval _ nInterval. ! ! !EditCommand methodsFor: 'selection' stamp: 'sps 1/7/2002 19:54'! doSelectionInterval ^self redoSelectionInterval! ! !EditCommand methodsFor: 'selection' stamp: 'sps 7/24/2003 17:34'! redoSelectionInterval "Return an interval to be displayed as a subtle selection after undo, or nil" ^newTextInterval ! ! !EditCommand methodsFor: 'selection' stamp: 'sps 7/24/2003 17:36'! undoSelection "Return an interval to be displayed as a selection after undo, or nil" ^replacedTextInterval first to: (replacedTextInterval first + replacedText size - 1) ! ! !EditCommand methodsFor: 'selection' stamp: 'sps 7/24/2003 17:03'! undoSelectionInterval "Return an interval to be displayed as a selection after undo, or nil" | i | i _ (replacedTextInterval first min: self textMorphStringSize). ^i to: i - 1 ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EditCommand class instanceVariableNames: ''! !EditCommand class methodsFor: 'instance creation' stamp: 'sps 7/24/2003 17:08'! textMorph: tm replacedText: replacedText replacedTextInterval: replacedTextInterval newText: newText newTextInterval: newTextInterval ^(self new) textMorph: tm replacedText: replacedText replacedTextInterval: replacedTextInterval newText: newText newTextInterval: newTextInterval; yourself ! ! ObjectWithDocumentation subclass: #ElementCategory instanceVariableNames: 'categoryName keysInOrder elementDictionary' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Kernel'! !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: 'category name' stamp: 'sw 1/26/2001 22:45'! categoryName "Answer the formal name of the category" ^ categoryName! ! !ElementCategory methodsFor: 'category name' stamp: 'stephaneducasse 2/4/2006 20:39'! categoryName: aName "Set the category name" categoryName := aName! ! !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: 'stephaneducasse 2/4/2006 20:39'! 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: '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 9/12/2001 22:59'! elementSymbol "Answer the element symbol for the receiver. Here, the categoryName dominates" ^ categoryName! ! !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 4/3/2001 11:06'! fasterElementAt: sym put: element "Add symbol at the end of my sorted list and put the element in the dictionary. This variant adds the key at the end of the keys list without checking whether it already exists." keysInOrder add: sym. ^ elementDictionary at: sym put: element! ! !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: 'elements' stamp: 'sw 4/11/2001 20:08'! removeElementAt: aKey "Remove the element at the given key" elementDictionary removeKey: aKey ifAbsent: [^ self]. keysInOrder remove: aKey ifAbsent: []! ! !ElementCategory methodsFor: 'initialization' stamp: 'sw 3/30/2001 00:12'! addCategoryItem: anItem "Add the item at the end, obtaining its key from itself (it must respond to #categoryName)" self elementAt: anItem categoryName put: anItem! ! !ElementCategory methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:39'! clear "Clear the receiber's keysInOrder and elementDictionary" keysInOrder := OrderedCollection new. elementDictionary := IdentityDictionary new! ! !ElementCategory methodsFor: 'initialization' stamp: 'sw 3/28/2001 19:47'! initialize "Initialize the receiver (automatically called when instances are created via 'new')" super initialize. self clear! ! !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: '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]! ! !ElementCategory methodsFor: 'translation' stamp: 'dgd 12/4/2003 20:22'! translated "answer the receiver translated to the current language" ^ self class new categoryName: categoryName asString translated asSymbol! ! !ElementCategory methodsFor: 'private' stamp: 'sw 8/6/2004 10:34'! initWordingAndDocumentation "Initialize wording and documentation (helpMessage) for getters and setters" self wording: self categoryName! ! Object subclass: #ElementTranslation instanceVariableNames: 'wording helpMessage naturalLanguageSymbol' classVariableNames: '' poolDictionaries: '' category: 'Protocols-Kernel'! !ElementTranslation methodsFor: 'access' stamp: 'sw 8/18/2004 22:12'! helpMessage "Answer the helpMessage" ^ helpMessage! ! Object subclass: #EllipseMidpointTracer instanceVariableNames: 'rect x y a b aSquared bSquared d1 d2 inFirstRegion' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !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! ! !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.! ! BorderedMorph subclass: #EllipseMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Basic'! !EllipseMorph commentStamp: 'kfr 10/27/2003 10:32' prior: 0! A round BorderedMorph. Supports borderWidth and borderColor. Only simple borderStyle is implemented. EllipseMorph new borderWidth:10; borderColor: Color green; openInWorld. EllipseMorph new borderStyle:(SimpleBorder width: 5 color: Color blue); openInWorld.! !EllipseMorph methodsFor: 'accessing' stamp: 'sw 11/24/1999 14:59'! couldHaveRoundedCorners ^ false! ! !EllipseMorph methodsFor: 'accessing' stamp: 'di 6/20/97 11:29'! doesBevels ^ false! ! !EllipseMorph methodsFor: 'drawing' stamp: 'di 6/24/1998 14:27'! areasRemainingToFill: aRectangle "Could be improved by quick check of inner rectangle" ^ Array with: aRectangle! ! !EllipseMorph methodsFor: 'drawing' stamp: 'di 5/25/2001 01:37'! drawOn: aCanvas aCanvas isShadowDrawing ifTrue: [^ aCanvas fillOval: bounds fillStyle: self fillStyle borderWidth: 0 borderColor: nil]. aCanvas fillOval: bounds fillStyle: self fillStyle borderWidth: borderWidth borderColor: borderColor. ! ! !EllipseMorph methodsFor: 'geometry testing' 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: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !EllipseMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:26'! defaultColor "answer the default color/fill style for the receiver" ^ Color yellow! ! !EllipseMorph methodsFor: 'testing' stamp: 'ar 8/25/2001 19:08'! canDrawBorder: aBorderStyle ^aBorderStyle style == #simple! ! !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: 'geometry' stamp: 'nk 8/31/2004 14:25'! bottomLeftCorner ^self intersectionWithLineSegmentFromCenterTo: bounds bottomLeft ! ! !EllipseMorph methodsFor: 'geometry' stamp: 'nk 8/31/2004 14:23'! bottomRightCorner ^self intersectionWithLineSegmentFromCenterTo: bounds bottomRight ! ! !EllipseMorph methodsFor: 'geometry' stamp: 'nk 2/15/2001 16:08'! closestPointTo: aPoint ^self intersectionWithLineSegmentFromCenterTo: aPoint! ! !EllipseMorph methodsFor: 'geometry' stamp: 'nk 2/13/2001 18:16'! intersectionWithLineSegmentFromCenterTo: aPoint | dx aSquared bSquared m mSquared xSquared x y dy | (self containsPoint: aPoint) ifTrue: [ ^aPoint ]. dx _ aPoint x - self center x. dy _ aPoint y - self center y. dx = 0 ifTrue: [ ^self bounds pointNearestTo: aPoint ]. m _ dy / dx. mSquared _ m squared. aSquared _ (self bounds width / 2) squared. bSquared _ (self bounds height / 2) squared. xSquared _ 1 / ((1 / aSquared) + (mSquared / bSquared)). x _ xSquared sqrt. dx < 0 ifTrue: [ x _ x negated ]. y _ m * x. ^ self center + (x @ y) asIntegerPoint. ! ! !EllipseMorph methodsFor: 'geometry' stamp: 'nk 8/31/2004 14:23'! topLeftCorner ^self intersectionWithLineSegmentFromCenterTo: bounds topLeft ! ! !EllipseMorph methodsFor: 'geometry' stamp: 'nk 8/31/2004 14:26'! topRightCorner ^self intersectionWithLineSegmentFromCenterTo: bounds topRight ! ! !EllipseMorph methodsFor: 'rounding' stamp: 'ka 12/4/2005 00:52'! cornerStyle: aSymbol "Set the receiver's corner style. But, in this case, do *not*" self removeProperty: #cornerStyle. self changed! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EllipseMorph class instanceVariableNames: ''! !EllipseMorph class methodsFor: 'parts bin' stamp: 'nk 8/23/2004 18:11'! descriptionForPartsBin ^ self partName: 'Ellipse' categories: #('Graphics' 'Basic') documentation: 'An elliptical or circular shape'! ! !EllipseMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/10/2003 13:03'! initialize self registerInFlapsRegistry. ! ! !EllipseMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/10/2003 13:05'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') forFlapNamed: 'Supplies'. cl registerQuad: #(EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') forFlapNamed: 'PlugIn Supplies'.]! ! !EllipseMorph class methodsFor: '*MorphicExtras-class initialization' stamp: 'asm 4/11/2003 12:33'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! AlignmentMorph subclass: #EmbeddedWorldBorderMorph instanceVariableNames: 'heights minWidth minHeight' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-AdditionalSupport'! !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 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: '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 ! ! !EmbeddedWorldBorderMorph methodsFor: 'drawing' 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: 'event handling' stamp: 'RAA 7/13/2000 10:10'! handlesMouseDown: evt self boxesAndColorsAndSelectors do: [ :each | (each first containsPoint: evt cursorPoint) ifTrue: [^true] ]. ^false ! ! !EmbeddedWorldBorderMorph methodsFor: 'event handling' 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: 'geometry' stamp: 'RAA 6/26/2000 19:10'! extent: aPoint bounds extent = aPoint ifFalse: [ self changed. bounds _ bounds topLeft extent: aPoint. self myWorldChanged. ]. ! ! !EmbeddedWorldBorderMorph methodsFor: 'initialization' stamp: 'em 3/24/2005 15:07'! 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' translated! ! !EmbeddedWorldBorderMorph methodsFor: 'layout' stamp: 'RAA 6/26/2000 18:45'! minHeight: anInteger minHeight _ anInteger! ! !EmbeddedWorldBorderMorph methodsFor: 'layout' stamp: 'RAA 6/26/2000 18:46'! minWidth: anInteger minWidth _ anInteger! ! !EmbeddedWorldBorderMorph methodsFor: 'menus' stamp: 'RAA 7/13/2000 10:16'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. self worldIEnclose addScalingMenuItems: menu hand: aHandMorph ! ! !EmbeddedWorldBorderMorph methodsFor: 'WiW support' 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"! ! SelectionMenu subclass: #EmphasizedMenu instanceVariableNames: 'emphases' classVariableNames: '' poolDictionaries: '' category: 'ST80-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: 'fc 2/19/2004 22:07'! onlyBoldItem: itemNumber "Set up emphasis such that all items are plain except for the given item number. " emphases _ (Array new: selections size) atAllPut: #normal. emphases at: itemNumber put: #bold! ! !EmphasizedMenu methodsFor: 'private' stamp: 'fc 2/20/2004 11:01'! 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 ~~ #normal]) ifTrue: [labelString addAttribute: (TextEmphasis perform: currEmphasis) from: selStart to: selEnd]. selStart _ selEnd + 2]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EmphasizedMenu class instanceVariableNames: ''! !EmphasizedMenu class methodsFor: 'examples' stamp: 'fc 2/19/2004 22:06'! example1 "EmphasizedMenu example1" ^ (self selections: #('how' 'well' 'does' 'this' 'work?' ) emphases: #(#bold #normal #italic #struckOut #normal )) 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: 'fc 2/19/2004 22:08'! example3 "EmphasizedMenu example3" ^ (self selectionAndEmphasisPairs: #('how' #bold 'well' #normal 'does' #italic 'this' #struckOut 'work' #normal)) startUpWithCaption: 'A Menu with Emphases'! ! !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"! ! Object subclass: #EncodedCharSet instanceVariableNames: '' classVariableNames: 'EncodedCharSets' poolDictionaries: '' category: 'Multilingual-Encodings'! !EncodedCharSet commentStamp: 'yo 10/19/2004 19:08' prior: 0! An abstract superclasss of the classes that represent encoded character sets. In the old implementation, the charsets had more important role. However, in the current implementation, the subclasses are used only for keeping the backward compatibility. The other confusion comes from the name of "Latin1" class. It used to mean the Latin-1 (ISO-8859-1) character set, but now it primarily means that the "Western European languages that are covered by the characters in Latin-1 character set. ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EncodedCharSet class instanceVariableNames: 'CompoundTextSequence'! !EncodedCharSet class methodsFor: 'accessing - displaying' stamp: 'yo 12/18/2002 12:34'! isBreakableAt: index in: text self subclassResponsibility. ! ! !EncodedCharSet class methodsFor: 'accessing - displaying' stamp: 'yo 9/4/2002 22:51'! printingDirection self subclassResponsibility. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 16:55'! canBeGlobalVarInitial: char | leadingChar | leadingChar _ char leadingChar. leadingChar = 0 ifTrue: [^ self isUppercase: char]. ^ self isLetter: char. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 17:18'! canBeNonGlobalVarInitial: char | leadingChar | leadingChar _ char leadingChar. leadingChar = 0 ifTrue: [^ self isLowercase: char]. ^ self isLetter: char. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 16:44'! isDigit: char "Answer whether the receiver is a digit." | value | value _ char asciiValue. ^ value >= 48 and: [value <= 57]. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 16:40'! isLetter: char "Answer whether the receiver is a letter." | value | value _ char asciiValue. ^ (8r141 <= value and: [value <= 8r172]) or: [8r101 <= value and: [value <= 8r132]]. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 16:40'! isLowercase: char "Answer whether the receiver is a lowercase letter. (The old implementation answered whether the receiver is not an uppercase letter.)" | value | value _ char asciiValue. ^ 8r141 <= value and: [value <= 8r172]. ! ! !EncodedCharSet class methodsFor: 'character classification' stamp: 'yo 8/5/2003 16:44'! isUppercase: char "Answer whether the receiver is an uppercase letter. (The old implementation answered whether the receiver is not a lowercase letter.)" | value | value _ char asciiValue. ^ 8r101 <= value and: [value <= 8r132]. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'tak 11/5/2005 18:14'! charFromUnicode: unicode | table index | unicode < 128 ifTrue: [^ Character value: unicode]. table _ self ucsTable. index _ table indexOf: unicode. index = 0 ifTrue: [ ^ nil. ]. ^ Character leadingChar: self leadingChar code: index - 1. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 9/4/2002 22:57'! charsetAt: encoding ^ EncodedCharSets at: encoding + 1 ifAbsent: [EncodedCharSets at: 1]. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 12/1/2003 19:29'! digitValue: char "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 | value _ char charCode. value <= $9 asciiValue ifTrue: [^value - $0 asciiValue]. value >= $A asciiValue ifTrue: [value <= $Z asciiValue ifTrue: [^value - $A asciiValue + 10]]. ^ -1 ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 1/19/2005 11:33'! initialize " self initialize " self allSubclassesDo: [:each | each initialize]. EncodedCharSets _ Array new: 256. EncodedCharSets at: 0+1 put: Latin1Environment. EncodedCharSets at: 1+1 put: JISX0208. EncodedCharSets at: 2+1 put: GB2312. EncodedCharSets at: 3+1 put: KSX1001. EncodedCharSets at: 4+1 put: JISX0208. EncodedCharSets at: 5+1 put: JapaneseEnvironment. EncodedCharSets at: 6+1 put: SimplifiedChineseEnvironment. EncodedCharSets at: 7+1 put: KoreanEnvironment. EncodedCharSets at: 8+1 put: GB2312. "EncodedCharSets at: 9+1 put: UnicodeTraditionalChinese." "EncodedCharSets at: 10+1 put: UnicodeVietnamese." EncodedCharSets at: 12+1 put: KSX1001. EncodedCharSets at: 13+1 put: GreekEnvironment. EncodedCharSets at: 14+1 put: Latin2Environment. EncodedCharSets at: 256 put: Unicode. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 12/2/2004 16:13'! isCharset ^ true. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 9/2/2002 16:32'! leadingChar self subclassResponsibility. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 11/4/2002 14:43'! nextPutValue: ascii toStream: aStream withShiftSequenceIfNeededForTextConverterState: state self subclassResponsibility. ! ! !EncodedCharSet class methodsFor: 'class methods' stamp: 'yo 10/14/2003 10:19'! ucsTable ^ UCSTable latin1Table. ! ! ParseNode subclass: #Encoder instanceVariableNames: 'scopeTable nTemps supered requestor class literalStream selectorSet litIndSet litSet sourceRanges globalSourceRanges' classVariableNames: '' poolDictionaries: '' category: 'Compiler-Kernel'! !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: '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: 'yo 11/11/2002 10:22'! 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 canBeGlobalVarInitial ifTrue: [globalSourceRanges addLast: { name. range. false }]. ]. (varNode isTemp and: [varNode scope < 0]) ifTrue: [ OutOfScopeNotification signal ifFalse: [ ^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: '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: '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' stamp: 'ajh 1/24/2003 18:46'! nTemps: n literals: lits class: cl "Decompile." supered _ false. class _ cl. nTemps _ n. literalStream _ ReadStream on: lits. literalStream position: lits size. sourceRanges _ Dictionary new: 32. globalSourceRanges _ OrderedCollection new: 32. ! ! !Encoder methodsFor: 'initialize-release'! noteSuper supered _ true! ! !Encoder methodsFor: 'initialize-release'! release requestor _ nil! ! !Encoder methodsFor: 'initialize-release' stamp: 'ajh 7/21/2003 00:53'! temps: tempVars literals: lits class: cl "Decompile." supered _ false. class _ cl. nTemps _ tempVars size. tempVars do: [:node | scopeTable at: node name put: node]. literalStream _ ReadStream on: lits. literalStream position: lits size. sourceRanges _ Dictionary new: 32. globalSourceRanges _ OrderedCollection new: 32. ! ! !Encoder methodsFor: 'results' stamp: 'md 3/1/2006 10:47'! allLiterals (literalStream isKindOf: WriteStream) ifTrue: [ self litIndex: nil. self litIndex: class binding . ]. ^ literalStream contents! ! !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: '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: '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: 'mir 1/17/2004 12:22'! 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 in "' , class printString, '")']]. ^self reallyBind: name! ! !Encoder methodsFor: 'temps' stamp: 'mir 1/17/2004 12:31'! bindTemp: name in: methodSelector "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 in "' , class printString , '>>' , methodSelector printString , '")']]. ^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: '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' stamp: 'ar 8/14/2001 23:12'! global: ref name: name ^self name: name key: ref class: LiteralVariableNode type: LdLitIndType set: litIndSet! ! !Encoder methodsFor: 'private' stamp: 'ar 5/17/2003 14:16'! lookupInPools: varName ifFound: assocBlock Symbol hasInterned: varName ifTrue:[:sym| (class bindingOf: sym) ifNotNilDo:[:assoc| assocBlock value: assoc. ^true]. (Preferences valueOfFlag: #lenientScopeForGlobals) "**Temporary**" ifTrue: [^ Smalltalk lenientScopeHas: sym ifTrue: assocBlock] ifFalse: [^ false]]. (class bindingOf: varName) ifNotNilDo:[:assoc| assocBlock value: assoc. ^true]. ^false! ! !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' stamp: 'ar 1/2/2002 14:53'! possibleNamesFor: proposedName | results | results _ class possibleVariablesFor: proposedName continuedFrom: nil. ^ proposedName correctAgainst: nil continuedFrom: results. ! ! !Encoder methodsFor: 'private' stamp: 'yo 11/11/2002 10:23'! possibleVariablesFor: proposedVariable | results | results _ proposedVariable correctAgainstDictionary: scopeTable continuedFrom: nil. proposedVariable first canBeGlobalVarInitial 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: '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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! scale: aNumber scale := aNumber asFloat. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/26/97 09:25'! target ^ target ! ! !Envelope methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! target: anObject target := anObject. ! ! !Envelope methodsFor: 'accessing' stamp: 'jm 11/24/97 14:34'! updateSelector ^ updateSelector ! ! !Envelope methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! reset "Reset the state for this envelope." lastValue := -100000.0. "impossible value" nextRecomputeTime := 0. self updateTargetAt: 0. ! ! !Envelope methodsFor: 'applying' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! example "Envelope example" | 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: 'stephaneducasse 2/4/2006 20:41'! exponentialDecay: multiplier "(Envelope exponentialDecay: 0.95) " | 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: 'MorphicExtras-SoundInterface'! !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: 'ar 3/17/2001 14:24'! 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: #clickOn:evt:from: to: self withValue: env. self addMorph: aLine]]]. self addMorph: line "add the active one last (in front)"! ! !EnvelopeEditorMorph methodsFor: 'construction' stamp: 'ar 3/17/2001 14:25'! 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 veryDeepCopy with: handle veryDeepCopy. 1 to: limitHandles size do: [:i | handle _ limitHandles at: i. handle on: #mouseDown send: #limitHandleMove:event:from: to: self withValue: i. handle on: #mouseMove send: #limitHandleMove:event:from: 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: 'editing' stamp: 'dgd 2/22/2003 14:23'! 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 first loopEnd: (limits second). ^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: 'ar 3/17/2001 14:24'! clickOn: env evt: anEvent from: aLine self editEnvelope: env! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'ar 3/18/2001 17:27'! clickOnLine: arg1 evt: arg2 envelope: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self clickOn: arg1 evt: arg2 from: arg3! ! !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: 'dgd 2/22/2003 14:24'! 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 first) loopEnd: (limits second). ^true! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'dgd 2/22/2003 14:24'! 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 first) loopEnd: (limits second). ^true! ! !EnvelopeEditorMorph methodsFor: 'editing' stamp: 'ar 3/17/2001 14:24'! limitHandleMove: index event: evt from: handle "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: 'editing' stamp: 'ar 3/18/2001 17:27'! limitHandleMoveEvent: arg1 from: arg2 index: arg3 "Reorder the arguments for existing event handlers" (arg3 isMorph and:[arg3 eventHandler notNil]) ifTrue:[arg3 eventHandler fixReversedValueMessages]. ^self limitHandleMove: arg1 event: arg2 from: arg3! ! !EnvelopeEditorMorph methodsFor: 'geometry' 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: '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: 'menu' stamp: 'dgd 8/30/2003 21:19'! addCustomMenuItems: menu hand: aHandMorph super addCustomMenuItems: menu hand: aHandMorph. menu addLine. envelope updateSelector = #ratio: ifTrue: [menu add: 'choose denominator...' translated action: #chooseDenominator:]. menu add: 'adjust scale...' translated action: #adjustScale:. SoundPlayer isReverbOn ifTrue: [menu add: 'turn reverb off' translated target: SoundPlayer selector: #stopReverb] ifFalse: [menu add: 'turn reverb on' translated target: SoundPlayer selector: #startReverb]. menu addLine. menu add: 'get sound from lib' translated action: #chooseSound:. menu add: 'put sound in lib' translated action: #saveSound:. menu add: 'read sound from disk...' translated action: #readFromDisk:. menu add: 'save sound on disk...' translated action: #saveToDisk:. menu add: 'save library on disk...' translated action: #saveLibToDisk:. ! ! !EnvelopeEditorMorph methodsFor: 'menu' stamp: 'dgd 2/22/2003 14:23'! 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 first) loopEnd: (limits second). 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: 'sw 5/23/2001 14:26'! saveLibToDisk: evt "Save the library to disk" | 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: [self inform: 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: 'rounding' stamp: 'di 7/14/2000 11:13'! wantsRoundedCorners ^ Preferences roundedWindowCorners or: [super wantsRoundedCorners]! ! !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: 'stepping and presenter' 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: 'testing' 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: 'MorphicExtras-SoundInterface'! !EnvelopeLineMorph methodsFor: 'as yet unclassified' stamp: 'dgd 2/14/2003 20:17'! vertices: verts borderWidth: bw borderColor: bc super initialize. vertices _ verts. borderWidth _ bw. borderColor _ bc. closed _ false. arrows _ #none. self computeBounds! ! !EnvelopeLineMorph methodsFor: 'editing' stamp: 'ar 3/17/2001 14:38'! dragVertex: ix event: evt fromHandle: handle | p | super dragVertex: ix event: evt fromHandle: handle. p _ owner acceptGraphPoint: evt cursorPoint at: ix. self verticesAt: ix put: p. ! ! !EnvelopeLineMorph methodsFor: 'editing' stamp: 'ar 3/17/2001 14:31'! dropVertex: ix event: evt fromHandle: handle | oldVerts | oldVerts _ vertices. super dropVertex: ix event: evt fromHandle: handle. vertices = oldVerts ifFalse: [owner deletePoint: ix "deleted a vertex"]! ! !EnvelopeLineMorph methodsFor: 'editing' stamp: 'ar 3/17/2001 14:39'! newVertex: ix event: evt fromHandle: handle "Install a new vertex if there is room." (owner insertPointAfter: ix) ifFalse: [^ self "not enough room"]. super newVertex: ix event: evt fromHandle: handle. self verticesAt: ix+1 put: (owner acceptGraphPoint: evt cursorPoint at: ix+1). ! ! !EnvelopeLineMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color transparent! ! PrototypeTester subclass: #EqualityTester instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Utilities'! !EqualityTester commentStamp: 'mjr 8/20/2003 13:04' prior: 0! I provide a simple way to test the equality properties of any object.! !EqualityTester methodsFor: 'as yet unclassified' stamp: 'mjr 8/20/2003 18:56'! resultFor: runs "Test that equality is the same over runs and answer the result" 1 to: runs do: [:i | self prototype = self prototype ifFalse: [^ false]]. ^ true! ! Exception subclass: #Error instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: '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: 'exceptionDescription' stamp: 'ajh 9/4/2002 19:24'! defaultAction "No one has handled this error, but now give them a chance to decide how to debug it. If none handle this either then open debugger (see UnhandedError-defaultAction)" UnhandledError signalForException: self! ! !Error methodsFor: 'private' stamp: 'ajh 2/1/2003 00:54'! isResumable "Determine whether an exception is resumable." ^ false! ! AlignmentMorphBob1 subclass: #EtoyLoginMorph instanceVariableNames: 'theName theNameMorph actionBlock cancelBlock' classVariableNames: '' poolDictionaries: '' category: 'EToys-Experimental'! !EtoyLoginMorph methodsFor: 'actions' stamp: 'ar 9/24/2000 00:08'! doCancel self delete. cancelBlock ifNotNil:[cancelBlock value].! ! !EtoyLoginMorph methodsFor: 'actions' stamp: 'dgd 10/8/2003 18:58'! doOK | proposed | proposed _ theNameMorph contents string. proposed isEmpty ifTrue: [^self inform: 'Please enter your login name' translated]. proposed size > 24 ifTrue: [^self inform: 'Please make the name 24 characters or less' translated]. (Project isBadNameForStoring: proposed) ifTrue: [ ^self inform: 'Please remove any funny characters' translated ]. (actionBlock value: proposed) ifTrue:[self delete].! ! !EtoyLoginMorph methodsFor: 'building' stamp: 'gm 3/11/2003 21:51'! buttonColor ^ Color paleYellow darker! ]style[(11 4 23)f2b,f2,f1cred;! ! !EtoyLoginMorph methodsFor: 'building' stamp: 'dgd 11/2/2004 21:26'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString font: self myFont; color: aColor; borderColor: aColor muchDarker; actionSelector: aSymbol; setBalloonText: helpString. col _ (self inAColumn: {f}) hResizing: #spaceFill. ^col! ! !EtoyLoginMorph methodsFor: 'building' stamp: 'dgd 11/2/2004 21:24'! cancelButton ^ self buttonNamed: 'Cancel' action: #doCancel color: ColorTheme current cancelColor help: 'Cancel this login operation.'! ! !EtoyLoginMorph methodsFor: 'building' stamp: 'nk 7/12/2003 08:40'! myFont ^ Preferences standardEToysFont! ! !EtoyLoginMorph methodsFor: 'building' stamp: 'dgd 11/2/2004 21:23'! okButton ^ self buttonNamed: 'OK' action: #doOK color:ColorTheme current okColor help: 'Login into Squeak'! ! !EtoyLoginMorph methodsFor: 'initialization' stamp: 'gm 3/11/2003 21:53'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ Color paleYellow darker! ]style[(18 2 61 27)f2b,f2,f2c141039000,f2! ! !EtoyLoginMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 8! ! !EtoyLoginMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 16:01'! defaultColor "answer the default color/fill style for the receiver" | result | result _ GradientFillStyle ramp: {0.0 -> (Color r: 0.5 g: 0.5 b: 1.0). 1.0 -> (Color r: 0.8 g: 0.8 b: 1.0)}. result origin: self bounds origin. result direction: 0 @ self bounds height. ^ result! ]style[(12 2 54 3 7 4 6 3 17 8 3 10 5 11 3 11 3 11 3 3 3 10 5 11 3 11 3 11 3 5 6 9 4 17 6 12 1 3 4 19 6)f2b,f2,f2c146044000,f2,f2cblue;i,f2,f2cblue;i,f2,f2cmagenta;,f2,f2c196196120,f2,f2cmagenta;,f2,f2c196196120,f2,f2c196196120,f2,f2c196196120,f2,f2c196196120,f2,f2cmagenta;,f2,f2c196196120,f2,f2c196196120,f2,f2c196196120,f2,f2cblue;i,f2,f2cmagenta;,f2,f2cblue;i,f2,f2c196196120,f2,f2cmagenta;,f2,f2cblue;i! ! !EtoyLoginMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 15:28'! initialize "initialize the state of the receiver" super initialize. "" self vResizing: #shrinkWrap; hResizing: #shrinkWrap; layoutInset: 4; beSticky; useRoundedCorners; rebuild. ! ! !EtoyLoginMorph methodsFor: 'initialization' stamp: 'ar 9/23/2000 14:13'! openInWorld: aWorld super openInWorld: aWorld. aWorld primaryHand newKeyboardFocus: theNameMorph.! ! !EtoyLoginMorph methodsFor: 'initialize' stamp: 'ar 9/24/2000 00:09'! name: aString actionBlock: aBlock cancelBlock: altBlock theName _ aString. actionBlock _ aBlock. cancelBlock _ altBlock. theNameMorph contentsWrapped: theName. theNameMorph editor selectAll.! ! !EtoyLoginMorph methodsFor: 'initialize' stamp: 'ar 9/23/2000 23:52'! rebuild self removeAllMorphs. self addARow: { (StringMorph contents:'') lock }. self addARow: { (StringMorph contents: 'Please enter your Squeak login name' font: self myFont) lock. }. (self addARow: { (theNameMorph _ TextMorph new beAllFont: self myFont; crAction: (MessageSend receiver: self selector: #doOK); extent: 300@20; contentsWrapped: 'the old name'; setBalloonText: 'Enter your name and avoid the following characters: : < > | / \ ? * "' ). }) color: Color white; borderColor: Color black; borderWidth: 1. self addARow: { self okButton. self cancelButton. }. self addARow: { (StringMorph contents:'') lock }. ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EtoyLoginMorph class instanceVariableNames: ''! !EtoyLoginMorph class methodsFor: 'instance creation' stamp: 'ar 8/23/2001 21:37'! loginAndDo: aBlock ifCanceled: cancelBlock "EtoyLoginMorph loginAndDo:[:n| true] ifCanceled:[]" | me | (me _ self new) name: 'your name' actionBlock: aBlock cancelBlock: cancelBlock; fullBounds; position: Display extent - me extent // 2; openInWorld. me position: me position + (0@40).! ! UpdatingThreePhaseButtonMorph subclass: #EtoyUpdatingThreePhaseButtonMorph instanceVariableNames: '' classVariableNames: 'CheckedForm MouseDownForm UncheckedForm' poolDictionaries: '' category: 'EToys-Widgets'! !EtoyUpdatingThreePhaseButtonMorph commentStamp: '' prior: 0! A slight variation wherein the actionSelector and getSelector both take argument(s).! !EtoyUpdatingThreePhaseButtonMorph methodsFor: 'stepping and presenter' 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'! 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)! ! !EtoyUpdatingThreePhaseButtonMorph class methodsFor: 'instance creation' 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 ! ! 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 doubleClickTimeoutSelector doubleClickTimeoutRecipient 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: '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'! messageList "Return a list of 'Class selector' for each message I can send. tk 9/13/97" | list | self flag: #mref. "is this still needed? I replaced the one use that I could spot with #methodRefList " list _ SortedCollection new. mouseDownRecipient ifNotNil: [list add: (mouseDownRecipient class whichClassIncludesSelector: mouseDownSelector) name , ' ' , mouseDownSelector]. mouseMoveRecipient ifNotNil: [list add: (mouseMoveRecipient class whichClassIncludesSelector: mouseMoveSelector) name , ' ' , mouseMoveSelector]. mouseStillDownRecipient ifNotNil: [list add: (mouseStillDownRecipient class whichClassIncludesSelector: mouseStillDownSelector) name , ' ' , mouseStillDownSelector]. mouseUpRecipient ifNotNil: [list add: (mouseUpRecipient class whichClassIncludesSelector: mouseUpSelector) name , ' ' , mouseUpSelector]. mouseEnterRecipient ifNotNil: [list add: (mouseEnterRecipient class whichClassIncludesSelector: mouseEnterSelector) name , ' ' , mouseEnterSelector]. mouseLeaveRecipient ifNotNil: [list add: (mouseLeaveRecipient class whichClassIncludesSelector: mouseLeaveSelector) name , ' ' , mouseLeaveSelector]. mouseEnterDraggingRecipient ifNotNil: [list add: (mouseEnterDraggingRecipient class whichClassIncludesSelector: mouseEnterDraggingSelector) name , ' ' , mouseEnterDraggingSelector]. mouseLeaveDraggingRecipient ifNotNil: [list add: (mouseLeaveDraggingRecipient class whichClassIncludesSelector: mouseLeaveDraggingSelector) name , ' ' , mouseLeaveDraggingSelector]. doubleClickRecipient ifNotNil: [list add: (doubleClickRecipient class whichClassIncludesSelector: doubleClickSelector) name , ' ' , doubleClickSelector]. keyStrokeRecipient ifNotNil: [list add: (keyStrokeRecipient class whichClassIncludesSelector: keyStrokeSelector) name , ' ' , keyStrokeSelector]. ^ list! ! !EventHandler methodsFor: 'access'! methodRefList "Return a MethodReference for each message I can send. tk 9/13/97, raa 5/29/01 " | list adder | list _ SortedCollection new. adder _ [:recip :sel | recip ifNotNil: [list add: (MethodReference new setStandardClass: (recip class whichClassIncludesSelector: sel) methodSymbol: sel)]]. adder value: mouseDownRecipient value: mouseDownSelector. adder value: mouseMoveRecipient value: mouseMoveSelector. adder value: mouseStillDownRecipient value: mouseStillDownSelector. adder value: mouseUpRecipient value: mouseUpSelector. adder value: mouseEnterRecipient value: mouseEnterSelector. adder value: mouseLeaveRecipient value: mouseLeaveSelector. adder value: mouseEnterDraggingRecipient value: mouseEnterDraggingSelector. adder value: mouseLeaveDraggingRecipient value: mouseLeaveDraggingSelector. adder value: doubleClickRecipient value: doubleClickSelector. adder value: keyStrokeRecipient value: 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: '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: 'nk 2/14/2004 18:24'! 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" ! ]style[(25 108 10 78)f1b,f1,f1LDeepCopier Comment;,f1! ! !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' stamp: 'jcg 9/21/2001 13:06'! doubleClickTimeout: event fromMorph: sourceMorph ^ self send: doubleClickTimeoutSelector to: doubleClickTimeoutRecipient 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' stamp: 'ar 3/17/2001 14:34'! 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: valueParameter with: event with: sourceMorph]. 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: 'fixups' stamp: 'sw 3/28/2001 14:22'! fixReversedValueMessages "ar 3/18/2001: Due to the change in the ordering of the value parameter old event handlers may have messages that need to be fixed up. Do this here." self replaceSendsIn: #( renameCharAction:sourceMorph:requestor: makeGetter:from:forPart: makeSetter:from:forPart: newMakeGetter:from:forPart: newMakeSetter:from:forPart: clickOnLine:evt:envelope: limitHandleMoveEvent:from:index: mouseUpEvent:linkMorph:formData: mouseUpEvent:linkMorph:browserAndUrl: mouseDownEvent:noteMorph:pitch: mouseMoveEvent:noteMorph:pitch: mouseUpEvent:noteMorph:pitch: dragVertex:fromHandle:vertIndex: dropVertex:fromHandle:vertIndex: newVertex:fromHandle:afterVert: prefMenu:rcvr:pref: event:arrow:upDown: newMakeGetter:from:forMethodInterface:) with: #( renameCharAction:event:sourceMorph: makeGetter:event:from: makeSetter:event:from: newMakeGetter:event:from: newMakeSetter:event:from: clickOn:evt:from: limitHandleMove:event:from: mouseUpFormData:event:linkMorph: mouseUpBrowserAndUrl:event:linkMorph: mouseDownPitch:event:noteMorph: mouseMovePitch:event:noteMorph: mouseUpPitch:event:noteMorph: dragVertex:event:fromHandle: dropVertex:event:fromHandle: newVertex:event:fromHandle: prefMenu:event:rcvr: upDown:event:arrow: makeUniversalTilesGetter:event:from:). "sw 3/28/2001 extended Andreas's original lists by one item"! ! !EventHandler methodsFor: 'fixups' stamp: 'ar 3/18/2001 17:18'! replaceSendsIn: array1 with: array2 "Replace all the sends that occur in array1 with those in array2. Used for fixing old event handlers in files." | old index | 1 to: self class instSize do:[:i| old _ self instVarAt: i. index _ array1 identityIndexOf: old. index > 0 ifTrue:[self instVarAt: i put: (array2 at: index)]].! ! !EventHandler methodsFor: 'initialization' stamp: 'nk 2/15/2004 08:16'! 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 == #doubleClickTimeout ifTrue: [doubleClickTimeoutRecipient _ recipient. doubleClickTimeoutSelector _ selector. ^ self]. eventName == #startDrag ifTrue: [startDragRecipient _ recipient. startDragSelector _ selector. ^ self]. eventName == #keyStroke ifTrue: [keyStrokeRecipient _ recipient. keyStrokeSelector _ selector. ^ self]. eventName == #gesture ifTrue: [ ^self onGestureSend: selector to: recipient ]. self error: 'Event name, ' , eventName , ' is not recognizable.' ! ! !EventHandler methodsFor: 'initialization' stamp: 'wiz 8/21/2005 01:44'! on: eventName send: selector to: recipient withValue: value selector numArgs = 3 ifFalse: [self halt: 'Warning: value parameters are passed as first of 3 arguments']. self on: eventName send: selector to: recipient. valueParameter := value ! ! !EventHandler methodsFor: 'initialization' stamp: 'nk 2/15/2004 08:59'! onGestureSend: selector to: recipient! ! !EventHandler methodsFor: 'objects from disk' 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: 'printing' stamp: 'dgd 2/22/2003 18:40'! printOn: aStream | aVal recipients | super printOn: aStream. #('mouseDownSelector' 'mouseStillDownSelector' 'mouseUpSelector' 'mouseEnterSelector' 'mouseLeaveSelector' 'mouseEnterDraggingSelector' 'mouseLeaveDraggingSelector' 'doubleClickSelector' 'keyStrokeSelector') do: [:aName | (aVal := self instVarNamed: aName) notNil ifTrue: [aStream nextPutAll: '; ' , aName , '=' , aVal]]. (recipients := self allRecipients) notEmpty ifTrue: [aStream nextPutAll: ' recipients: '. recipients printOn: aStream]! ! !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: 'nk 2/15/2004 08:57'! handlesGestureStart: evt "Does the associated morph want to handle gestures?" ^false! ! !EventHandler methodsFor: 'testing' stamp: 'ar 10/28/2000 22:17'! handlesKeyboard: evt keyStrokeRecipient ifNotNil: [^ true]. ^ false! ! !EventHandler methodsFor: 'testing' stamp: 'nk 2/15/2004 08:13'! handlesMouseDown: evt mouseDownRecipient ifNotNil: [^ true]. mouseStillDownRecipient ifNotNil: [^ true]. mouseUpRecipient ifNotNil: [^ true]. (self handlesClickOrDrag: evt) ifTrue:[^true]. ^self handlesGestureStart: evt! ! !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: '*eToys-initialization' stamp: 'jcg 9/21/2001 12:57'! 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]. doubleClickTimeoutSelector == aSelector ifTrue: [doubleClickTimeoutRecipient _ doubleClickTimeoutSelector _ nil]. keyStrokeSelector == aSelector ifTrue: [keyStrokeRecipient _ keyStrokeSelector _ nil].! ! !EventHandler methodsFor: '*MorphicExtras-initialization' stamp: 'ar 3/17/2001 20:12'! 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 _ value adaptedToWorld: aWorld. (newValue notNil and: [newValue ~~ value]) ifTrue: [self instVarNamed: aName asString put: newValue]]]! ! Object subclass: #EventManager instanceVariableNames: 'actionMap' classVariableNames: 'ActionMaps' poolDictionaries: '' category: 'System-Object Events'! !EventManager commentStamp: 'tlk 5/7/2006 20:01' prior: 0! An EventManager is used to registers a 'observer' object's interest in in changes to an 'observed' object. Then when the observered object is changed, EventManager broadcasts the an update message to all objects with a registered interest. Finally, the Event manager can be used to remove an object from the list of observer object. An interested object is said to be a dependant on the target object. Registering an interest in an event is called adding a dependant. Deregistering is called removing a dependant. The EventManager's action map is a WeakIdentityDictionary that maps events (selectors) to dependants (objects & selectors) in a way that ensures the mapping is to specific objects (hence identity) and in a way that allows the object to be garbage collected if not other used (hence weak.) EventManager class has ActionMaps which has one actionMap for each object. Classic uses of an EventManager are to implement the Observer Pattern, see ChangeNotification or the MorphicModle as examples.! !EventManager methodsFor: 'accessing' stamp: 'reThink 2/18/2001 15:37'! actionMap ^actionMap == nil ifTrue: [self createActionMap] ifFalse: [actionMap]! ! !EventManager methodsFor: 'accessing' stamp: 'reThink 3/3/2001 10:07'! changedEventSelector ^#changed:! ! !EventManager methodsFor: 'accessing' stamp: 'reThink 2/18/2001 15:39'! releaseActionMap actionMap := nil! ! !EventManager methodsFor: 'accessing' stamp: 'reThink 3/3/2001 10:07'! updateEventSelector ^#update:! ! !EventManager methodsFor: 'accessing' stamp: 'reThink 2/18/2001 15:38'! updateableActionMap actionMap == nil ifTrue: [actionMap := self createActionMap]. ^actionMap! ! !EventManager methodsFor: 'copying' stamp: 'reThink 3/3/2001 10:22'! copy | answer | answer := super copy. answer release. ^answer! ! !EventManager methodsFor: 'dependents access' stamp: 'reThink 3/3/2001 10:07'! addDependent: anObject "Make the given object one of the receiver's dependents." self when: self changedEventSelector send: self updateEventSelector to: anObject. ^anObject! ! !EventManager methodsFor: 'dependents access' stamp: 'reThink 3/3/2001 10:07'! breakDependents "Remove all of the receiver's dependents." self removeActionsForEvent: self changedEventSelector! ! !EventManager methodsFor: 'dependents access' stamp: 'reThink 3/3/2001 10:18'! dependents ^(self actionSequenceForEvent: self changedEventSelector) asSet collect: [:each | each receiver]! ! !EventManager methodsFor: 'dependents access' stamp: 'reThink 3/3/2001 10:07'! removeDependent: anObject "Remove the given object as one of the receiver's dependents." self removeActionsWithReceiver: anObject forEvent: self changedEventSelector. ^ anObject! ! !EventManager methodsFor: 'updating' stamp: 'reThink 3/3/2001 10:20'! changed: aParameter "Receiver changed. The change is denoted by the argument aParameter. Usually the argument is a Symbol that is part of the dependent's change protocol. Inform all of the dependents." self triggerEvent: self changedEventSelector with: aParameter! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EventManager class instanceVariableNames: ''! !EventManager class methodsFor: 'accessing' stamp: 'reThink 2/18/2001 14:42'! actionMapFor: anObject ^self actionMaps at: anObject ifAbsent: [self createActionMap]! ! !EventManager class methodsFor: 'accessing' stamp: 'rww 10/2/2001 07:20'! actionMaps ActionMaps == nil ifTrue: [ActionMaps := WeakIdentityKeyDictionary new]. ^ActionMaps! ! !EventManager class methodsFor: 'accessing' stamp: 'reThink 2/25/2001 08:52'! updateableActionMapFor: anObject ^self actionMaps at: anObject ifAbsentPut: [self createActionMap]! ! !EventManager class methodsFor: 'initialize-release' stamp: 'rw 2/10/2002 13:09'! flushEvents "Object flushEvents" | msgSet | self actionMaps keysAndValuesDo:[:rcvr :evtDict| rcvr ifNotNil:[ "make sure we don't modify evtDict while enumerating" evtDict keys do:[:evtName| msgSet _ evtDict at: evtName ifAbsent:[nil]. (msgSet == nil) ifTrue:[rcvr removeActionsForEvent: evtName]]]]. EventManager actionMaps finalizeValues. ! ! !EventManager class methodsFor: 'releasing' stamp: 'reThink 2/18/2001 15:34'! releaseActionMapFor: anObject self actionMaps removeKey: anObject ifAbsent: []! ! ClassTestCase subclass: #EventManagerTest instanceVariableNames: 'eventSource eventListener succeeded' classVariableNames: '' poolDictionaries: '' category: 'System-Object Events-Tests'! !EventManagerTest methodsFor: 'running' stamp: 'JWS 9/7/2000 17:19'! setUp super setUp. eventSource := EventManager new. eventListener := Bag new. succeeded := false! ! !EventManagerTest methodsFor: 'running' stamp: 'jws 11/28/2000 16:25'! tearDown eventSource releaseActionMap. eventSource := nil. eventListener := nil. super tearDown. ! ! !EventManagerTest methodsFor: 'running-broadcast query' stamp: 'JWS 9/7/2000 17:21'! testMultipleValueSuppliers eventSource when: #needsValue send: #getFalse to: self. eventSource when: #needsValue send: #getTrue to: self. succeeded := eventSource triggerEvent: #needsValue. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-broadcast query' stamp: 'JWS 9/7/2000 17:21'! testMultipleValueSuppliersEventHasArguments eventSource when: #needsValue: send: #getFalse: to: self. eventSource when: #needsValue: send: #getTrue: to: self. succeeded := eventSource triggerEvent: #needsValue: with: 'kolme'. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-broadcast query' stamp: 'JWS 9/7/2000 17:22'! testNoValueSupplier succeeded := eventSource triggerEvent: #needsValue ifNotHandled: [true]. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-broadcast query' stamp: 'JWS 9/7/2000 17:22'! testNoValueSupplierHasArguments succeeded := eventSource triggerEvent: #needsValue: with: 'nelja' ifNotHandled: [true]. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-broadcast query' stamp: 'jws 11/28/2000 15:52'! testSingleValueSupplier eventSource when: #needsValue send: #getTrue to: self. succeeded := eventSource triggerEvent: #needsValue. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-copying' stamp: 'SqR 11/12/2000 19:38'! testCopy "Ensure that the actionMap is zapped when you make a copy of anEventManager" eventSource when: #blah send: #yourself to: eventListener. self assert: eventSource actionMap keys isEmpty not. self assert: eventSource copy actionMap keys isEmpty! ! !EventManagerTest methodsFor: 'running-dependent action' stamp: 'jws 9/7/2000 16:39'! testNoArgumentEvent eventSource when: #anEvent send: #heardEvent to: self. eventSource triggerEvent: #anEvent. self should: [succeeded]! ! !EventManagerTest methodsFor: 'running-dependent action' stamp: 'JWS 9/7/2000 17:20'! testOneArgumentEvent eventSource when: #anEvent: send: #add: to: eventListener. eventSource triggerEvent: #anEvent: with: 9. self should: [eventListener includes: 9]! ! !EventManagerTest methodsFor: 'running-dependent action' stamp: 'JWS 9/7/2000 17:20'! testTwoArgumentEvent eventSource when: #anEvent:info: send: #addArg1:addArg2: to: self. eventSource triggerEvent: #anEvent:info: withArguments: #( 9 42 ). self should: [(eventListener includes: 9) and: [eventListener includes: 42]]! ! !EventManagerTest methodsFor: 'running-dependent action supplied arguments' stamp: 'JWS 9/7/2000 17:20'! testNoArgumentEventDependentSuppliedArgument eventSource when: #anEvent send: #add: to: eventListener with: 'boundValue'. eventSource triggerEvent: #anEvent. self should: [eventListener includes: 'boundValue']! ! !EventManagerTest methodsFor: 'running-dependent action supplied arguments' stamp: 'JWS 9/7/2000 17:21'! testNoArgumentEventDependentSuppliedArguments eventSource when: #anEvent send: #addArg1:addArg2: to: self withArguments: #('hello' 'world'). eventSource triggerEvent: #anEvent. self should: [(eventListener includes: 'hello') and: [eventListener includes: 'world']]! ! !EventManagerTest methodsFor: 'running-dependent value' stamp: 'JWS 9/7/2000 17:21'! testReturnValueWithManyListeners | value newListener | newListener := 'busybody'. eventSource when: #needsValue send: #yourself to: eventListener. eventSource when: #needsValue send: #yourself to: newListener. value := eventSource triggerEvent: #needsValue. self should: [value == newListener]! ! !EventManagerTest methodsFor: 'running-dependent value' stamp: 'JWS 9/7/2000 17:21'! testReturnValueWithNoListeners | value | value := eventSource triggerEvent: #needsValue. self should: [value == nil]! ! !EventManagerTest methodsFor: 'running-dependent value' stamp: 'JWS 9/7/2000 17:21'! testReturnValueWithOneListener | value | eventSource when: #needsValue send: #yourself to: eventListener. value := eventSource triggerEvent: #needsValue. self should: [value == eventListener]! ! !EventManagerTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:01'! testRemoveActionsForEvent eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsForEvent: #anEvent. self shouldnt: [eventSource hasActionForEvent: #anEvent]! ! !EventManagerTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:01'! testRemoveActionsTwiceForEvent eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsForEvent: #anEvent. self assert: (eventSource hasActionForEvent: #anEvent) not. eventSource removeActionsForEvent: #anEvent. self assert: (eventSource hasActionForEvent: #anEvent) not.! ! !EventManagerTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:10'! testRemoveActionsWithReceiver | action | eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsWithReceiver: self. action := eventSource actionForEvent: #anEvent. self assert: (action respondsTo: #receiver). self assert: ((action receiver == self) not)! ! !EventManagerTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:37'! addArg1: arg1 addArg2: arg2 eventListener add: arg1; add: arg2! ! !EventManagerTest methodsFor: 'private' stamp: 'JWS 9/7/2000 17:19'! getFalse ^false! ! !EventManagerTest methodsFor: 'private' stamp: 'JWS 9/7/2000 17:19'! getFalse: anArg ^false! ! !EventManagerTest methodsFor: 'private' stamp: 'JWS 9/7/2000 17:19'! getTrue ^true! ! !EventManagerTest methodsFor: 'private' stamp: 'JWS 9/7/2000 17:19'! getTrue: anArg ^true! ! !EventManagerTest methodsFor: 'private' stamp: 'JWS 9/7/2000 17:20'! heardEvent succeeded := true! ! AlignmentMorph subclass: #EventRecorderMorph instanceVariableNames: 'tape state time deltaTime recHand playHand lastEvent lastDelta tapeStream saved statusLight voiceRecorder startSoundEvent recordMeter caption journalFile' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-AdditionalSupport'! !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: 'accessing' stamp: 'LC 12/23/1998 12:48'! button: label ^ self allMorphs detect: [:one | (one isKindOf: SimpleButtonMorph) and: [one label = label]] ifNone: []! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'yo 2/11/2005 09:59'! button "Make a simple button interface for replay only" | butnCaption erm | butnCaption _ FillInTheBlank request: 'Caption for this butn?' translated initialAnswer: 'play' translated. butnCaption isEmpty ifTrue: [^ self]. erm _ (EventRecorderMorph basicNew caption: butnCaption voiceRecorder: voiceRecorder copy tape: tape) initialize. self world primaryHand attachMorph: erm! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'dgd 2/22/2003 19:01'! condense "Shorten the tape by deleting mouseMove events that can just as well be interpolated later at playback time." "e1, e2, and e3 are three consecutive events on the tape. t1, t2, and t3 are the associated time steps for each of them." | e1 e2 t1 t2 e3 t3 | 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 notNil 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: 'RAA 6/14/2001 16:42'! play self isInWorld ifFalse: [^ self]. self stop. tape ifNil: [^ self]. tapeStream _ ReadStream on: tape. self resumePlayIn: self world. self setStatusLight: #nowPlaying. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 6/14/2001 16:42'! record self isInWorld ifFalse: [^ self]. self stop. self writeCheck. self addJournalFile. tapeStream _ WriteStream on: (Array new: 10000). self resumeRecordIn: self world. self setStatusLight: #nowRecording. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'RAA 6/14/2001 16:43'! setStatusLight: aSymbol aSymbol == #ready ifTrue: [ statusLight color: Color green. tape ifNil: [ statusLight setBalloonText: 'Ready to record'. ] ifNotNil: [ statusLight setBalloonText: 'Ready to record or play'. ]. ^self ]. aSymbol == #nowRecording ifTrue: [ statusLight color: Color red; setBalloonText: 'Recording is active'. ^self ]. aSymbol == #nowPlaying ifTrue: [ statusLight color: Color yellow; setBalloonText: 'Now playing'. ^self ]. ! ! !EventRecorderMorph methodsFor: 'commands' stamp: 'dgd 9/21/2003 17:54'! 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]. self inform: ('{1} events reduced to {2}' translated format:{oldSize. tape size}). voiceRecorder ifNotNil: [voiceRecorder suppressSilence]. saved _ false. ! ! !EventRecorderMorph methodsFor: 'event handling' stamp: 'wiz 7/24/2005 21:41'! 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 isNil or:[tapeStream atEnd]) ifTrue:[^MorphicUnknownEvent new setType: #EOF argument: nil]. now := Time millisecondClockValue. nextEvent := tapeStream next. "nextEvent isKeyboard ifTrue: [ nextEvent setPosition: self position ]." 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: 'events-processing' 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: 'fileIn/Out' stamp: 'dgd 2/21/2003 23:15'! checkTape "See if this tape was already converted to the new format" tape ifNil: [^self]. tape isEmpty 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: 'md 7/15/2004 17:22'! readTape ^ self readTape: (FillInTheBlank request: 'Tape to read' initialAnswer: 'tapeName.tape').! ! !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: 'md 7/15/2004 17:23'! writeTape | args b | args := (b := self button: 'writeTape') isNil ifTrue: [#()] ifFalse: [b arguments]. (args notEmpty and: [args first notEmpty]) ifTrue: [args first. self writeTape: args first] ifFalse: [^self writeTape: (FillInTheBlank request: 'Tape to write' initialAnswer: 'tapeName.tape')].! ! !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: 'initialization' stamp: 'RAA 6/14/2001 16:52'! addButtons | r b | caption ifNotNil: ["Special setup for play-only interface" (r _ self makeARowForButtons) addMorphBack: (SimpleButtonMorph new target: self; label: caption; actionSelector: #play); addMorphBack: self makeASpacer; addMorphBack: self makeStatusLight; addMorphBack: self makeASpacer. ^ self addMorphBack: r ]. (r _ self makeARowForButtons) addMorphBack: (b _ self buttonFor: {#record. nil. 'Begin recording'}); addMorphBack: self makeASpacer; addMorphBack: (self buttonFor: {#stop. b width. 'Stop recording - you can also use the ESC key to stop it'}); addMorphBack: self makeASpacer; addMorphBack: (self buttonFor: {#play. b width. 'Play current recording'}). self addMorphBack: r. (r _ self makeARowForButtons) addMorphBack: (b _ self buttonFor: {#writeTape. nil. 'Save current recording on disk'}); addMorphBack: self makeASpacer; addMorphBack: (self buttonFor: {#readTape. b width. 'Get a new recording from disk'}). self addMorphBack: r. (r _ self makeARowForButtons) addMorphBack: (b _ self buttonFor: {#shrink. nil. 'Make recording shorter by removing unneeded events'}); addMorphBack: self makeASpacer; addMorphBack: self makeStatusLight; addMorphBack: self makeASpacer; addMorphBack: (self buttonFor: {#button. b width. 'Make a simple button to play this recording'}). self addMorph: r. self setStatusLight: #ready.! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'dgd 8/30/2003 21:19'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'add voice controls' translated action: #addVoiceControls. aCustomMenu add: 'add journal file' translated 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: 'yo 2/11/2005 09:56'! buttonFor: data | b | b _ SimpleButtonMorph new target: self; label: data first asString translated; actionSelector: data first. data second ifNotNil: [b width < data second ifTrue: [b width: data second]]. data third ifNotNil: [b setBalloonText: data third translated]. ^b! ! !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: 'dgd 2/14/2003 22:34'! defaultBorderColor "answer the default border color/fill style for the receiver" ^ #raised! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:37'! defaultBorderWidth "answer the default border width for the receiver" ^ 2! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color red! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:21'! initialize "initialize the state of the receiver" super initialize. "" saved _ true. self listDirection: #topToBottom; wrapCentering: #center; cellPositioning: #topCenter; hResizing: #shrinkWrap; vResizing: #shrinkWrap; layoutInset: 2; minCellSize: 4; addButtons! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'RAA 6/14/2001 16:19'! makeARowForButtons ^AlignmentMorph newRow vResizing: #shrinkWrap; wrapCentering: #center; cellPositioning: #leftCenter; minCellSize: 4; color: Color blue! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'RAA 6/14/2001 16:14'! makeASpacer ^AlignmentMorph newSpacer: Color transparent! ! !EventRecorderMorph methodsFor: 'initialization' stamp: 'RAA 6/14/2001 16:13'! makeStatusLight ^statusLight _ EllipseMorph new extent: 11 @ 11; color: Color green; borderWidth: 0! ! !EventRecorderMorph methodsFor: 'pause/resume' stamp: 'RAA 6/14/2001 16:50'! 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." self setStatusLight: #ready. 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: 'stepping and presenter' stamp: 'RAA 1/2/2001 15:45'! step (state == #record and: [voiceRecorder notNil]) ifTrue: [ recordMeter width: (voiceRecorder meterLevel + 1). ]. ! ! !EventRecorderMorph methodsFor: 'stepping and presenter' stamp: 'RAA 6/14/2001 16:43'! stop state = #record ifTrue: [tape _ tapeStream contents. saved _ false]. journalFile ifNotNil: [journalFile close]. self pauseIn: self world. tapeStream _ nil. state _ nil. self setStatusLight: #ready. recordMeter ifNotNil: [recordMeter width: 1]. self checkTape.! ! !EventRecorderMorph methodsFor: 'testing' stamp: 'RAA 1/2/2001 10:28'! stepTime ^500 ! ! !EventRecorderMorph methodsFor: 'testing' stamp: 'RAA 1/2/2001 10:25'! wantsSteps ^true ! ! !EventRecorderMorph methodsFor: '*sound' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: '*sound-piano rolls' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: '*sound-piano rolls' stamp: 'RAA 12/13/2000 12:40'! encounteredAtTime: ticks inScorePlayer: scorePlayer atIndex: index inEventTrack: track secsPerTick: secsPerTick self play.! ! !EventRecorderMorph methodsFor: '*sound-piano rolls' stamp: 'stephaneducasse 2/4/2006 20:40'! 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: '*sound-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: 'class initialization' stamp: 'hg 8/3/2000 17:25'! initialize FileList registerFileReader: self! ! !EventRecorderMorph class methodsFor: 'fileIn/Out' stamp: 'sd 2/6/2002 21:31'! fileReaderServicesForFile: fullName suffix: suffix ^(suffix = 'tape') | (suffix = '*') ifTrue: [ self services] ifFalse: [#()] ! ! !EventRecorderMorph class methodsFor: 'fileIn/Out' stamp: 'sd 2/6/2002 21:31'! services ^{SimpleServiceEntry provider: self label: 'open for playback' selector: #openTapeFromFile:.} ! ! !EventRecorderMorph class methodsFor: 'initialize-release' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !EventRecorderMorph class methodsFor: 'instance creation' stamp: 'edc 5/29/2006 08:35'! fromFileNamed: aFileName | file answer | file _ FileStream readOnlyFileNamed: aFileName. answer _ self readFrom: file setConverterForCode. file close. ^ answer! ! !EventRecorderMorph class methodsFor: 'instance creation' stamp: 'los 2/26/2004 11:46'! openTapeFromFile: fullName "Open an eventRecorder tape for playback." (self new) readTape: fullName; openInWorld! ! !EventRecorderMorph class methodsFor: 'instance creation' stamp: 'LC 12/23/1998 11:14'! readFrom: aStream ^ self new readFrom: aStream! ! !EventRecorderMorph class methodsFor: 'parts bin' stamp: 'sw 11/21/2001 16:06'! descriptionForPartsBin "Answer a description for use in a parts bin" ^ self partName: 'Event Recorder' categories: #(Presentation Tools) documentation: 'Lets you record and play back interactions'! ! InputSensor subclass: #EventSensor instanceVariableNames: 'mouseButtons mousePosition keyboardBuffer interruptKey interruptSemaphore eventQueue inputSemaphore lastEventPoll hasInputSemaphore' classVariableNames: 'EventPollPeriod EventTicklerProcess' poolDictionaries: 'EventSensorConstants' category: 'Kernel-Processes'! !EventSensor commentStamp: 'nk 4/13/2004 11:18' 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 will be signaled to indicate pending events. On platforms that do not support asynchronous notifications about events, the UI will have to poll EventSensor periodically to read events from the VM. 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 inputSemaphore - the semaphore signaled by the VM if asynchronous event notification is supported lastEventPoll - the last millisecondClockValue at which we called fetchMoreEvents hasInputSemaphore - true if my inputSemaphore has actually been signaled at least once. Class variables: EventPollPeriod - the number of milliseconds to wait between polling for more events in the userInterruptHandler. EventTicklerProcess - the process that makes sure that events are polled for often enough (at least every EventPollPeriod milliseconds). 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: 'accessing' stamp: 'ar 7/23/2000 14:37'! eventQueue "Return the current event queue" ^eventQueue! ! !EventSensor methodsFor: 'accessing' stamp: 'nk 4/12/2004 19:36'! eventTicklerProcess "Answer my event tickler process, if any" ^EventTicklerProcess! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 2/6/2004 14:48'! flushAllButDandDEvents | newQueue oldQueue | newQueue _ SharedQueue new. self eventQueue ifNil: [eventQueue := newQueue. ^self]. oldQueue _ self eventQueue. [oldQueue size > 0] whileTrue: [| item type | item _ oldQueue next. type _ item at: 1. type = EventTypeDragDropFiles ifTrue: [ newQueue nextPut: item]]. eventQueue := newQueue. ! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 2/7/2001 17:13'! flushEvents eventQueue ifNotNil:[eventQueue flush].! ! !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: 'JMM 11/7/2005 14:38'! peekButtons self wait2ms. self fetchMoreEvents. ^mouseButtons! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 2/6/2004 14:51'! peekEvent "Look ahead at the next event." eventQueue ifNil:[^nil]. self fetchMoreEvents. ^eventQueue peek! ! !EventSensor methodsFor: 'accessing' stamp: 'tpr 1/5/2005 17:34'! peekKeyboardEvent "Return the next keyboard char event from the receiver or nil if none available" ^eventQueue nextOrNilSuchThat: [:buf | buf first = EventTypeKeyboard and: [(buf fourth) = EventKeyChar]]! ! !EventSensor methodsFor: 'accessing' stamp: 'ar 2/8/2001 21:45'! peekMousePt ^mousePosition! ! !EventSensor methodsFor: 'accessing' stamp: 'JMM 11/7/2005 14:38'! peekPosition self wait2ms. self fetchMoreEvents. ^mousePosition! ! !EventSensor methodsFor: 'initialize' stamp: 'nk 4/12/2004 19:21'! initialize "Initialize the receiver" mouseButtons := 0. mousePosition := 0 @ 0. keyboardBuffer := SharedQueue new. self setInterruptKey: (interruptKey ifNil: [$. asciiValue bitOr: 16r0800 ]). "cmd-." interruptSemaphore := (Smalltalk specialObjectsArray at: 31) ifNil: [Semaphore new]. self flushAllButDandDEvents. inputSemaphore := Semaphore new. hasInputSemaphore := false.! ! !EventSensor methodsFor: 'initialize' stamp: 'nk 4/12/2004 20:13'! shutDown super shutDown. EventTicklerProcess ifNotNil: [ EventTicklerProcess terminate. EventTicklerProcess _ nil. ]. inputSemaphore ifNotNil:[Smalltalk unregisterExternalObject: inputSemaphore]. ! ! !EventSensor methodsFor: 'initialize' stamp: 'nk 6/21/2004 10:42'! startUp "Run the I/O process" self initialize. self primSetInputSemaphore: (Smalltalk registerExternalObject: inputSemaphore). super startUp. self installEventTickler. Smalltalk isMorphic ifTrue:[self flushAllButDandDEvents]. "Attempt to discover whether the input semaphore is actually being signaled." hasInputSemaphore := false. inputSemaphore initSignals. ! ! !EventSensor methodsFor: 'mouse' stamp: 'ar 5/18/2003 18:27'! createMouseEvent "create and return a new mouse event from the current mouse position; this is useful for restarting normal event queue processing after manual polling" | buttons modifiers pos mapped eventBuffer | eventBuffer _ Array new: 8. buttons _ self primMouseButtons. pos _ self primMousePt. modifiers _ buttons bitShift: -3. buttons _ buttons bitAnd: 7. mapped _ self mapButtons: buttons modifiers: modifiers. eventBuffer 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. ^ eventBuffer! ! !EventSensor methodsFor: 'private' stamp: 'nk 4/12/2004 20:16'! eventTickler "Poll infrequently to make sure that the UI process is not been stuck. If it has been stuck, then spin the event loop so that I can detect the interrupt key." | delay | delay := Delay forMilliseconds: self class eventPollPeriod. self lastEventPoll. "ensure not nil." [| delta | [ delay wait. delta := Time millisecondClockValue - lastEventPoll. (delta < 0 or: [delta > self class eventPollPeriod]) ifTrue: ["force check on rollover" self fetchMoreEvents]] on: Error do: [:ex | ]. true ] whileTrue.! ! !EventSensor methodsFor: 'private' stamp: 'di 10/1/2001 20:52'! flushNonKbdEvents eventQueue ifNil: [^ self]. eventQueue flushAllSuchThat: [:buf | (self isKbdEvent: buf) not] ! ! !EventSensor methodsFor: 'private' stamp: 'nk 6/21/2004 10:40'! installEventTickler "Initialize the interrupt watcher process. Terminate the old process if any." "Sensor installEventTickler" EventTicklerProcess ifNotNil: [EventTicklerProcess terminate]. EventTicklerProcess _ [self eventTickler] forkAt: Processor lowIOPriority. ! ! !EventSensor methodsFor: 'private' stamp: 'di 10/1/2001 20:51'! isKbdEvent: buf ^ (buf at: 1) = EventTypeKeyboard and: [(buf at: 4) = EventKeyChar]! ! !EventSensor methodsFor: 'private' stamp: 'nk 3/18/2004 13:21'! lastEventPoll "Answer the last clock value at which fetchMoreEvents was called." ^lastEventPoll ifNil: [ lastEventPoll _ Time millisecondClockValue ]! ! !EventSensor methodsFor: 'private' stamp: 'ar 2/6/2004 14:42'! nextEventFromQueue "Return the next event from the receiver." eventQueue isEmpty ifTrue:[self fetchMoreEvents]. eventQueue isEmpty ifTrue:[^nil] ifFalse:[^eventQueue next]! ! !EventSensor methodsFor: 'private' stamp: 'nk 3/17/2004 07:09'! 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 ifNotNil: ["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" pos _ self primMousePt. buttons _ mouseButtons. 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: '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: 'JMM 11/7/2005 14:38'! primKbdNext "Allows for use of old Sensor protocol to get at the keyboard, as when running kbdTest or the InterpreterSimulator in Morphic" | evtBuf | self wait2ms. self fetchMoreEvents. keyboardBuffer isEmpty ifFalse:[^ keyboardBuffer next]. eventQueue ifNotNil: [evtBuf _ eventQueue nextOrNilSuchThat: [:buf | self isKbdEvent: buf]. self flushNonKbdEvents]. ^ evtBuf ifNotNil: [evtBuf at: 3] ! ! !EventSensor methodsFor: 'private' stamp: 'JMM 11/7/2005 14:39'! primKbdPeek "Allows for use of old Sensor protocol to get at the keyboard, as when running kbdTest or the InterpreterSimulator in Morphic" | char | self wait2ms. self fetchMoreEvents. keyboardBuffer isEmpty ifFalse: [^ keyboardBuffer peek]. char _ nil. eventQueue ifNotNil: [eventQueue nextOrNilSuchThat: "NOTE: must not return out of this block, so loop to end" [:buf | (self isKbdEvent: buf) ifTrue: [char ifNil: [char _ buf at: 3]]. false "NOTE: block value must be false so Queue won't advance"]]. ^ char! ! !EventSensor methodsFor: 'private' stamp: 'JMM 11/7/2005 14:39'! primMouseButtons self wait2ms. self fetchMoreEvents. self flushNonKbdEvents. ^ mouseButtons! ! !EventSensor methodsFor: 'private' stamp: 'JMM 11/7/2005 14:39'! primMousePt self wait2ms. self fetchMoreEvents. self flushNonKbdEvents. ^ 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: 'private' stamp: 'JMM 11/7/2005 14:37'! wait2ms (Delay forMilliseconds: 2) wait.! ! !EventSensor methodsFor: 'private-I/O' stamp: 'nk 4/12/2004 20:01'! fetchMoreEvents "Fetch more events from the VM" | eventBuffer type | "Reset input semaphore so clients can wait for the next events after this one." inputSemaphore isSignaled ifTrue: [ hasInputSemaphore _ true. inputSemaphore initSignals ]. "Remember the last time that I checked for events." lastEventPoll := Time millisecondClockValue. eventBuffer := Array new: 8. [self primGetNextEvent: eventBuffer. type := eventBuffer at: 1. type = EventTypeNone] whileFalse: [self processEvent: eventBuffer]. ! ! !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: 'wiz 11/11/2005 02:37'! processEvent: evt "Process a single event. This method is run at high priority." | type | type := evt at: 1. "Tackle mouse events first" type = EventTypeMouse ifTrue: [evt at: 5 put: (ButtonDecodeTable at: (evt at: 5) + 1). self queueEvent: evt. self processMouseEvent: evt . ^self]. "Store the event in the queue if there's any" type = EventTypeKeyboard ifTrue: [ "Check if the event is a user interrupt" ((evt at: 4) = 0 and: [((evt at: 3) bitOr: (((evt at: 5) bitAnd: 8) bitShift: 8)) = interruptKey]) ifTrue: ["interrupt key is meta - not reported as event" ^ interruptSemaphore signal]. "Else swap ctrl/alt keys if neeeded.wi" KeyDecodeTable at: {evt at: 3. evt at: 5} ifPresent: [:a | evt at: 3 put: a first; at: 5 put: a second]. self queueEvent: evt. self processKeyboardEvent: evt . ^self ]. "Handle all events other than Keyborad or Mouse." self queueEvent: evt. ! ! !EventSensor methodsFor: 'private-I/O' stamp: 'nk 4/11/2001 18:28'! processKeyboardEvent: evt "process a keyboard event, updating InputSensor state" | charCode pressCode | "Never update keyboardBuffer if we have an eventQueue active" mouseButtons _ (mouseButtons bitAnd: 7) bitOr: ((evt at: 5) bitShift: 3). 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). 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 class instanceVariableNames: ''! !EventSensor class methodsFor: 'class initialization' stamp: 'nk 4/12/2004 18:55'! eventPollPeriod ^EventPollPeriod ifNil: [ EventPollPeriod _ 500 ].! ! !EventSensor class methodsFor: 'class initialization' stamp: 'nk 4/12/2004 18:55'! eventPollPeriod: msec "Set the number of milliseconds between checking for events to msec." EventPollPeriod _ msec max: 10.! ! !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"! ! SharedPool subclass: #EventSensorConstants instanceVariableNames: '' classVariableNames: 'BlueButtonBit CommandKeyBit CtrlKeyBit EventKeyChar EventKeyDown EventKeyUp EventTypeDragDropFiles EventTypeKeyboard EventTypeMouse EventTypeNone OptionKeyBit RedButtonBit ShiftKeyBit YellowButtonBit' poolDictionaries: '' category: 'Kernel-Processes'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! EventSensorConstants class instanceVariableNames: ''! !EventSensorConstants class methodsFor: 'pool initialization' stamp: 'ar 5/18/2003 18:26'! initialize "EventSensorConstants initialize" 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. ! ! TestCase subclass: #EventTest instanceVariableNames: 'eventSource eventListener succeeded' classVariableNames: '' poolDictionaries: '' category: 'System-Object Events-Tests'! !EventTest methodsFor: 'running' stamp: 'jws 9/7/2000 16:37'! setUp super setUp. eventSource := Object new. eventListener := Bag new. succeeded := false! ! !EventTest methodsFor: 'running' stamp: 'jws 11/28/2000 16:25'! tearDown eventSource releaseActionMap. eventSource := nil. eventListener := nil. super tearDown. ! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'jws 9/7/2000 16:41'! testMultipleValueSuppliers eventSource when: #needsValue send: #getFalse to: self. eventSource when: #needsValue send: #getTrue to: self. succeeded := eventSource triggerEvent: #needsValue. self should: [succeeded]! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'jws 9/7/2000 16:41'! testMultipleValueSuppliersEventHasArguments eventSource when: #needsValue: send: #getFalse: to: self. eventSource when: #needsValue: send: #getTrue: to: self. succeeded := eventSource triggerEvent: #needsValue: with: 'kolme'. self should: [succeeded]! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'rw 4/27/2002 09:12'! testMultipleValueSuppliersEventHasArgumentsWithGC eventSource when: #needsValue: send: #getFalse: to: self with: Object new. eventSource when: #needsValue: send: #getTrue: to: self with: Object new. Smalltalk garbageCollectMost. succeeded := eventSource triggerEvent: #needsValue: with: 'kolme'. self should: [succeeded = nil] ! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'jws 9/7/2000 16:41'! testNoValueSupplier succeeded := eventSource triggerEvent: #needsValue ifNotHandled: [true]. self should: [succeeded]! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'jws 9/7/2000 16:41'! testNoValueSupplierHasArguments succeeded := eventSource triggerEvent: #needsValue: with: 'nelja' ifNotHandled: [true]. self should: [succeeded]! ! !EventTest methodsFor: 'running-broadcast query' stamp: 'jws 9/7/2000 16:42'! testSingleValueSupplier eventSource when: #needsValue send: #getTrue to: self. succeeded := eventSource triggerEvent: #needsValue. self should: [succeeded]! ! !EventTest methodsFor: 'running-dependent action' stamp: 'jws 9/7/2000 16:39'! testNoArgumentEvent eventSource when: #anEvent send: #heardEvent to: self. eventSource triggerEvent: #anEvent. self should: [succeeded]! ! !EventTest methodsFor: 'running-dependent action' stamp: 'jws 9/7/2000 16:39'! testOneArgumentEvent eventSource when: #anEvent: send: #add: to: eventListener. eventSource triggerEvent: #anEvent: with: 9. self should: [eventListener includes: 9]! ! !EventTest methodsFor: 'running-dependent action' stamp: 'jws 9/7/2000 16:39'! testTwoArgumentEvent eventSource when: #anEvent:info: send: #addArg1:addArg2: to: self. eventSource triggerEvent: #anEvent:info: withArguments: #( 9 42 ). self should: [(eventListener includes: 9) and: [eventListener includes: 42]]! ! !EventTest methodsFor: 'running-dependent action supplied arguments' stamp: 'jws 9/7/2000 16:39'! testNoArgumentEventDependentSuppliedArgument eventSource when: #anEvent send: #add: to: eventListener with: 'boundValue'. eventSource triggerEvent: #anEvent. self should: [eventListener includes: 'boundValue']! ! !EventTest methodsFor: 'running-dependent action supplied arguments' stamp: 'jws 9/7/2000 16:40'! testNoArgumentEventDependentSuppliedArguments eventSource when: #anEvent send: #addArg1:addArg2: to: self withArguments: #('hello' 'world'). eventSource triggerEvent: #anEvent. self should: [(eventListener includes: 'hello') and: [eventListener includes: 'world']]! ! !EventTest methodsFor: 'running-dependent value' stamp: 'jws 9/7/2000 16:40'! testReturnValueWithManyListeners | value newListener | newListener := 'busybody'. eventSource when: #needsValue send: #yourself to: eventListener. eventSource when: #needsValue send: #yourself to: newListener. value := eventSource triggerEvent: #needsValue. self should: [value == newListener]! ! !EventTest methodsFor: 'running-dependent value' stamp: 'jws 9/7/2000 16:40'! testReturnValueWithNoListeners | value | value := eventSource triggerEvent: #needsValue. self should: [value == nil]! ! !EventTest methodsFor: 'running-dependent value' stamp: 'jws 9/7/2000 16:40'! testReturnValueWithOneListener | value | eventSource when: #needsValue send: #yourself to: eventListener. value := eventSource triggerEvent: #needsValue. self should: [value == eventListener]! ! !EventTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:04'! testRemoveActionsForEvent eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsForEvent: #anEvent. self shouldnt: [eventSource hasActionForEvent: #anEvent]! ! !EventTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:05'! testRemoveActionsTwiceForEvent eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsForEvent: #anEvent. self assert: (eventSource hasActionForEvent: #anEvent) not. eventSource removeActionsForEvent: #anEvent. self assert: (eventSource hasActionForEvent: #anEvent) not.! ! !EventTest methodsFor: 'running-remove actions' stamp: 'SqR 2/19/2001 14:05'! testRemoveActionsWithReceiver | action | eventSource when: #anEvent send: #size to: eventListener; when: #anEvent send: #getTrue to: self; when: #anEvent: send: #fizzbin to: self. eventSource removeActionsWithReceiver: self. action := eventSource actionForEvent: #anEvent. self assert: (action respondsTo: #receiver). self assert: ((action receiver == self) not)! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:37'! addArg1: arg1 addArg2: arg2 eventListener add: arg1; add: arg2! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:37'! getFalse ^false! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:37'! getFalse: anArg ^false! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:38'! getTrue ^true! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:38'! getTrue: anArg ^true! ! !EventTest methodsFor: 'private' stamp: 'jws 9/7/2000 16:38'! heardEvent succeeded := true! ! Object subclass: #Exception instanceVariableNames: 'messageText tag signalContext handlerContext outerContext' classVariableNames: '' poolDictionaries: '' category: '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: 'exceptionBuilder' stamp: 'pnm 8/16/2000 15:23'! tag: t "This message is not specified in the ANSI protocol, but that looks like an oversight because #tag is specified, and the spec states that the signaler may store the tag value." tag := t! ! !Exception methodsFor: 'exceptionDescription' stamp: 'pnm 8/16/2000 14:54'! tag "Return an exception's tag value." ^tag == nil ifTrue: [self messageText] ifFalse: [tag]! ! !Exception methodsFor: 'handling' stamp: 'ajh 2/1/2003 01:32'! isNested "Determine whether the current exception handler is within the scope of another handler for the same exception." ^ handlerContext nextHandlerContext canHandleSignal: self! ! !Exception methodsFor: 'handling' stamp: 'ajh 6/27/2003 22:13'! outer "Evaluate the enclosing exception action and return to here instead of signal if it resumes (see #resumeUnchecked:)." | prevOuterContext | self isResumable ifTrue: [ prevOuterContext _ outerContext. outerContext _ thisContext contextTag. ]. self pass. ! ! !Exception methodsFor: 'handling' stamp: 'ajh 2/1/2003 01:33'! pass "Yield control to the enclosing exception action for the receiver." handlerContext nextHandlerContext handleSignal: self! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/22/2003 23:04'! resignalAs: replacementException "Signal an alternative exception in place of the receiver." self resumeUnchecked: replacementException signal! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/13/2002 15:09'! resume "Return from the message that signaled the receiver." self resume: nil! ! !Exception methodsFor: 'handling' stamp: 'ajh 6/27/2003 22:30'! resumeUnchecked: resumptionValue "Return resumptionValue as the value of #signal, unless this was called after an #outer message, then return resumptionValue as the value of #outer." | ctxt | outerContext ifNil: [ signalContext return: resumptionValue ] ifNotNil: [ ctxt _ outerContext. outerContext _ ctxt tempAt: 1. "prevOuterContext in #outer" ctxt return: resumptionValue ]. ! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/13/2002 15:14'! resume: resumptionValue "Return resumptionValue as the value of the signal message." self isResumable ifFalse: [IllegalResumeAttempt signal]. self resumeUnchecked: resumptionValue! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/29/2003 13:36'! retry "Abort an exception handler and re-evaluate its protected block." handlerContext restart! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/29/2003 13:37'! retryUsing: alternativeBlock "Abort an exception handler and evaluate a new block in place of the handler's protected block." handlerContext restartWithNewReceiver: alternativeBlock ! ! !Exception methodsFor: 'handling' stamp: 'ajh 9/30/2001 15:33'! return "Return nil as the value of the block protected by the active exception handler." self return: nil! ! !Exception methodsFor: 'handling' stamp: 'ajh 1/29/2003 13:37'! return: returnValue "Return the argument as the value of the block protected by the active exception handler." handlerContext return: returnValue! ! !Exception methodsFor: 'handling' stamp: 'ajh 2/16/2003 17:37'! searchFrom: aContext " Set the context where the handler search will start. " signalContext := aContext contextTag! ! !Exception methodsFor: 'printing' stamp: 'pnm 8/16/2000 14:53'! 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: 'printing' stamp: 'ajh 9/30/2001 15:33'! messageText "Return an exception's message text." ^messageText! ! !Exception methodsFor: 'printing' stamp: 'ajh 9/30/2001 15:33'! printOn: stream stream nextPutAll: self description! ! !Exception methodsFor: 'printing' stamp: 'ajh 10/22/2001 14:24'! receiver ^ self signalerContext receiver! ! !Exception methodsFor: 'printing' stamp: 'ar 6/28/2003 00:13'! signalerContext "Find the first sender of signal(:)" ^ signalContext findContextSuchThat: [:ctxt | (ctxt receiver == self or: [ctxt receiver == self class]) not]! ! !Exception methodsFor: 'priv handling' stamp: 'ajh 9/30/2001 15:33'! defaultAction "The default action taken if the exception is signaled." self subclassResponsibility! ! !Exception methodsFor: 'priv handling' stamp: 'ajh 2/1/2003 00:58'! isResumable "Determine whether an exception is resumable." ^ true! ! !Exception methodsFor: 'priv handling' stamp: 'ajh 1/29/2003 13:44'! privHandlerContext: aContextTag handlerContext _ aContextTag! ! !Exception methodsFor: 'signaling' stamp: 'ajh 9/30/2001 15:33'! messageText: signalerText "Set an exception's message text." messageText := signalerText! ! !Exception methodsFor: 'signaling' stamp: 'ajh 2/1/2003 01:33'! signal "Ask ContextHandlers in the sender chain to handle this signal. The default is to execute and return my defaultAction." signalContext _ thisContext contextTag. ^ thisContext nextHandlerContext handleSignal: self! ! !Exception methodsFor: 'signaling' stamp: 'ajh 9/30/2001 20:13'! signal: signalerText "Signal the occurrence of an exceptional condition with a specified textual description." self messageText: signalerText. ^ self signal! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Exception class instanceVariableNames: ''! !Exception class methodsFor: 'exceptionInstantiator' stamp: 'ajh 9/30/2001 21:54'! signal "Signal the occurrence of an exceptional condition." ^ self new signal! ! !Exception class methodsFor: 'exceptionInstantiator' stamp: 'ajh 9/30/2001 21:54'! signal: signalerText "Signal the occurrence of an exceptional condition with a specified textual description." ^ self new signal: signalerText! ! !Exception class methodsFor: 'exceptionSelector' stamp: 'ajh 9/30/2001 15:33'! , anotherException "Create an exception set." ^ExceptionSet new add: self; add: anotherException; yourself! ! !Exception class methodsFor: 'exceptionSelector' stamp: 'ajh 8/5/2003 11:33'! handles: exception "Determine whether an exception handler will accept a signaled exception." ^ exception isKindOf: self! ! Notification subclass: #ExceptionAboutToReturn instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: '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: '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: '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: 'pnm 8/16/2000 15:15'! handles: anException "Determine whether an exception handler will accept a signaled exception." exceptions do: [:ex | (ex handles: anException) ifTrue: [^true]]. ^false! ! !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! ! Object subclass: #ExceptionTester instanceVariableNames: 'log suiteLog iterationsBeforeTimeout' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Tests'! !ExceptionTester methodsFor: 'accessing' stamp: 'dtl 6/1/2004 21:53'! basicANSISignaledExceptionTestSelectors ^#( simpleIsNestedTest simpleOuterTest doubleOuterTest doubleOuterPassTest doublePassOuterTest simplePassTest simpleResignalAsTest simpleResumeTest simpleRetryTest simpleRetryUsingTest simpleReturnTest)! ! !ExceptionTester methodsFor: 'accessing' stamp: 'brp 10/21/2004 17:54'! basicTestSelectors ^ #(#simpleEnsureTest #simpleEnsureTestWithNotification #simpleEnsureTestWithUparrow #simpleEnsureTestWithError #signalFromHandlerActionTest #resumableFallOffTheEndHandler #nonResumableFallOffTheEndHandler #doubleResumeTest #simpleTimeoutWithZeroDurationTest #simpleTimeoutTest simpleNoTimeoutTest)! ! !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: 'brp 10/21/2004 17:15'! iterationsBeforeTimeout ^ iterationsBeforeTimeout! ! !ExceptionTester methodsFor: 'accessing' stamp: 'brp 10/21/2004 17:16'! iterationsBeforeTimeout: anInteger iterationsBeforeTimeout := anInteger! ! !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: '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: '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: '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: 'results' stamp: 'brp 10/21/2004 16:54'! simpleNoTimeoutTestResults ^OrderedCollection new add: self doSomethingString; yourself! ! !ExceptionTester methodsFor: 'results' stamp: 'brp 10/21/2004 17:44'! simpleTimeoutTestResults | things | things := OrderedCollection new: self iterationsBeforeTimeout. self iterationsBeforeTimeout timesRepeat: [ things add: self doSomethingString ]. things add: self doSomethingElseString. ^ things! ! !ExceptionTester methodsFor: 'results' stamp: 'brp 10/21/2004 16:52'! simpleTimeoutWithZeroDurationTestResults ^OrderedCollection new add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'dtl 6/1/2004 21:56'! doubleOuterPassTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingElseString; yourself! ! !ExceptionTester methodsFor: 'signaledException results' stamp: 'dtl 6/1/2004 21:56'! doublePassOuterTestResults ^OrderedCollection new add: self doSomethingString; add: self doYetAnotherThingString; add: self doSomethingElseString; yourself! ! !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! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'dtl 6/1/2004 21:51'! doubleOuterPassTest "uses #resume" [[[self doSomething. MyTestNotification signal. self doSomethingExceptional] on: MyTestNotification do: [:ex | ex outer. self doSomethingElse]] on: MyTestNotification do: [:ex | ex pass. self doSomethingExceptional]] on: MyTestNotification do: [:ex | self doYetAnotherThing. ex resume]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'dtl 6/1/2004 21:49'! doubleOuterTest "uses #resume" [[[self doSomething. MyTestNotification signal. self doSomethingExceptional] on: MyTestNotification do: [:ex | ex outer. self doSomethingExceptional]] on: MyTestNotification do: [:ex | ex outer. self doSomethingElse]] on: MyTestNotification do: [:ex | self doYetAnotherThing. ex resume]! ! !ExceptionTester methodsFor: 'signaledException tests' stamp: 'dtl 6/1/2004 21:52'! doublePassOuterTest "uses #resume" [[[self doSomething. MyTestNotification signal. self doSomethingExceptional] on: MyTestNotification do: [:ex | ex pass. self doSomethingExceptional]] on: MyTestNotification do: [:ex | ex outer. self doSomethingElse]] on: MyTestNotification do: [:ex | self doYetAnotherThing. ex resume]! ! !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: 'tpr 5/27/2004 21:50'! simpleOuterTest "uses #resume" [[self doSomething. MyTestNotification signal. "self doSomethingElse" self doSomethingExceptional] on: MyTestNotification do: [:ex | ex outer. self doSomethingElse]] 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: '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: 'brp 10/21/2004 17:40'! runTest: aSelector | actualResult expectedResult | [ self logTest: aSelector; clearLog; perform: aSelector ] on: MyTestError do: [ :ex | self log: 'Unhandled Exception'. ex return: nil ]. actualResult := self log. expectedResult := self perform: (aSelector, #Results) asSymbol. actualResult = 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: 'brp 10/22/2004 12:00'! simpleNoTimeoutTest [ self doSomething ] valueWithin: 1 day onTimeout: [ self doSomethingElse ]. ! ! !ExceptionTester methodsFor: 'tests' stamp: 'brp 10/22/2004 12:00'! simpleTimeoutTest | n | [1 to: 1000000 do: [ :i | n := i. self doSomething ] ] valueWithin: 50 milliSeconds onTimeout: [ self iterationsBeforeTimeout: n. self doSomethingElse ]! ! !ExceptionTester methodsFor: 'tests' stamp: 'brp 10/22/2004 12:00'! simpleTimeoutWithZeroDurationTest [ self doSomething ] valueWithin: 0 seconds onTimeout: [ self doSomethingElse ]. ! ! !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.'! ! TestCase subclass: #ExceptionTests instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Tests'! !ExceptionTests methodsFor: 'testing' stamp: 'brp 10/21/2004 16:42'! testNoTimeout self assertSuccess: (ExceptionTester new runTest: #simpleNoTimeoutTest ) ! ! !ExceptionTests methodsFor: 'testing' stamp: 'brp 10/21/2004 16:41'! testTimeoutWithZeroDuration self assertSuccess: (ExceptionTester new runTest: #simpleTimeoutWithZeroDurationTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'dtl 6/1/2004 21:54'! testDoubleOuterPass self assertSuccess: (ExceptionTester new runTest: #doubleOuterPassTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'dtl 6/1/2004 21:54'! testDoublePassOuter self assertSuccess: (ExceptionTester new runTest: #doublePassOuterTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:43'! testDoubleResume self assertSuccess: (ExceptionTester new runTest: #doubleResumeTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:44'! testNonResumableFallOffTheEndHandler self assertSuccess: (ExceptionTester new runTest: #nonResumableFallOffTheEndHandler ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:44'! testResumableFallOffTheEndHandler self assertSuccess: (ExceptionTester new runTest: #resumableFallOffTheEndHandler ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:44'! testSignalFromHandlerActionTest self assertSuccess: (ExceptionTester new runTest: #signalFromHandlerActionTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:48'! testSimpleEnsure self assertSuccess: (ExceptionTester new runTest: #simpleEnsureTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:45'! testSimpleEnsureTestWithError self assertSuccess: (ExceptionTester new runTest: #simpleEnsureTestWithError ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:46'! testSimpleEnsureTestWithNotification self assertSuccess: (ExceptionTester new runTest: #simpleEnsureTestWithNotification ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:45'! testSimpleEnsureTestWithUparrow self assertSuccess: (ExceptionTester new runTest: #simpleEnsureTestWithUparrow ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:46'! testSimpleIsNested self assertSuccess: (ExceptionTester new runTest: #simpleIsNestedTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:41'! testSimpleOuter self assertSuccess: (ExceptionTester new runTest: #simpleOuterTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:42'! testSimplePass self assertSuccess: (ExceptionTester new runTest: #simplePassTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:43'! testSimpleResignalAs self assertSuccess: (ExceptionTester new runTest: #simpleResignalAsTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:48'! testSimpleResume self assertSuccess: (ExceptionTester new runTest: #simpleResumeTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:48'! testSimpleRetry self assertSuccess: (ExceptionTester new runTest: #simpleRetryTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:47'! testSimpleRetryUsing self assertSuccess: (ExceptionTester new runTest: #simpleRetryUsingTest ) ! ! !ExceptionTests methodsFor: 'testing-ExceptionTester' stamp: 'md 3/25/2003 23:48'! testSimpleReturn self assertSuccess: (ExceptionTester new runTest: #simpleReturnTest ) ! ! !ExceptionTests methodsFor: 'testing-outer' stamp: 'dtl 6/1/2004 21:59'! testNonResumableOuter self should: [ [Error signal. 4] on: Error do: [:ex | ex outer. ex return: 5] ] raise: Error ! ! !ExceptionTests methodsFor: 'testing-outer' stamp: 'dtl 6/1/2004 22:00'! testNonResumablePass self should: [ [Error signal. 4] on: Error do: [:ex | ex pass. ex return: 5] ] raise: Error ! ! !ExceptionTests methodsFor: 'testing-outer' stamp: 'dtl 6/1/2004 22:00'! testResumableOuter | result | result _ [Notification signal. 4] on: Notification do: [:ex | ex outer. ex return: 5]. self assert: result == 5 ! ! !ExceptionTests methodsFor: 'testing-outer' stamp: 'dtl 6/1/2004 22:00'! testResumablePass | result | result _ [Notification signal. 4] on: Notification do: [:ex | ex pass. ex return: 5]. self assert: result == 4 ! ! !ExceptionTests methodsFor: 'private' stamp: 'md 3/25/2003 23:40'! assertSuccess: anExceptionTester self should: [ ( anExceptionTester suiteLog first) endsWith: 'succeeded'].! ! Object subclass: #ExternalDropHandler instanceVariableNames: 'action type extension' classVariableNames: 'DefaultHandler RegisteredHandlers' poolDictionaries: '' category: 'System-Support'! !ExternalDropHandler methodsFor: 'accessing' stamp: 'mir 1/10/2002 15:54'! extension ^extension! ! !ExternalDropHandler methodsFor: 'accessing' stamp: 'mir 1/10/2002 17:29'! handle: dropStream in: pasteUp dropEvent: anEvent | numArgs | numArgs _ action numArgs. numArgs == 1 ifTrue: [^action value: dropStream]. numArgs == 2 ifTrue: [^action value: dropStream value: pasteUp]. numArgs == 3 ifTrue: [^action value: dropStream value: pasteUp value: anEvent]. self error: 'Wrong number of args for dop action.'! ! !ExternalDropHandler methodsFor: 'accessing' stamp: 'mir 1/10/2002 15:54'! type ^type! ! !ExternalDropHandler methodsFor: 'initialize' stamp: 'mir 1/10/2002 17:17'! type: aType extension: anExtension action: anAction action _ anAction. type _ aType. extension _ anExtension! ! !ExternalDropHandler methodsFor: 'testing' stamp: 'spfa 5/25/2004 13:38'! matchesExtension: aExtension (self extension isNil or: [aExtension isNil]) ifTrue: [^false]. FileDirectory activeDirectoryClass isCaseSensitive ifTrue: [^extension = aExtension] ifFalse: [^extension sameAs: aExtension]! ! !ExternalDropHandler methodsFor: 'testing' stamp: 'mir 1/10/2002 16:35'! matchesTypes: types (self type isNil or: [types isNil]) ifTrue: [^false]. ^types anySatisfy: [:mimeType | mimeType beginsWith: self type]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalDropHandler class instanceVariableNames: ''! !ExternalDropHandler class methodsFor: 'accessing' stamp: 'mir 1/10/2002 17:17'! defaultHandler DefaultHandler ifNil: [DefaultHandler _ ExternalDropHandler type: nil extension: nil action: [:dropStream | dropStream edit]]. ^DefaultHandler! ! !ExternalDropHandler class methodsFor: 'accessing' stamp: 'mir 1/10/2002 16:54'! defaultHandler: externalDropHandler DefaultHandler _ externalDropHandler! ! !ExternalDropHandler class methodsFor: 'accessing' stamp: 'mir 8/24/2004 15:37'! lookupExternalDropHandler: stream | types extension serviceHandler | types _ stream mimeTypes. types ifNotNil: [ self registeredHandlers do: [:handler | (handler matchesTypes: types) ifTrue: [^handler]]]. extension _ FileDirectory extensionFor: stream name. self registeredHandlers do: [:handler | (handler matchesExtension: extension) ifTrue: [^handler]]. serviceHandler := self lookupServiceBasedHandler: stream. ^serviceHandler ifNil: [self defaultHandler]! ! !ExternalDropHandler class methodsFor: 'accessing' stamp: 'mir 8/24/2004 17:15'! lookupServiceBasedHandler: dropStream "the file was just droped, let's do our job" | fileName services theOne | fileName := dropStream name. services := (FileList itemsForFile: fileName) reject: [:svc | self unwantedSelectors includes: svc selector]. "no service, default behavior" services isEmpty ifTrue: [^nil]. theOne := self chooseServiceFrom: services. ^theOne ifNotNil: [ExternalDropHandler type: nil extension: nil action: [:stream | theOne performServiceFor: stream]]! ! !ExternalDropHandler class methodsFor: 'accessing' stamp: 'mir 1/10/2002 17:19'! registerHandler: aHandler self registeredHandlers add: aHandler! ! !ExternalDropHandler class methodsFor: 'class initialization' stamp: 'mir 1/10/2002 17:37'! initialize "ExternalDropHandler initialize" self resetRegisteredHandlers. self registerHandler: self defaultImageHandler; registerHandler: self defaultGZipHandler; registerHandler: self defaultProjectHandler! ! !ExternalDropHandler class methodsFor: 'class initialization' stamp: 'nk 6/12/2004 16:15'! registerStandardExternalDropHandlers "ExternalDropHandler registerStandardExternalDropHandlers" self registeredHandlers add: ( ExternalDropHandler type: 'image/' extension: nil action: [:stream :pasteUp :event | pasteUp addMorph: (World drawingClass withForm: (Form fromBinaryStream: stream binary)) centeredNear: event position])! ! !ExternalDropHandler class methodsFor: 'instance creation' stamp: 'mir 1/10/2002 17:16'! type: aType extension: anExtension action: anAction ^self new type: aType extension: anExtension action: anAction ! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 8/24/2004 15:28'! chooseServiceFrom: aCollection "private - choose a service from aCollection asking the user if needed" | menu | aCollection size = 1 ifTrue: [^ aCollection anyOne]. "" menu := CustomMenu new. aCollection do: [:each | menu add: each label action: each]. ^ menu startUp! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 1/10/2002 17:23'! defaultGZipHandler ^ExternalDropHandler type: nil extension: 'gz' action: [:stream :pasteUp :event | stream viewGZipContents]! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'tak 3/13/2005 21:19'! defaultImageHandler | image sketch | ^ExternalDropHandler type: 'image/' extension: nil action: [:stream :pasteUp :event | stream binary. image _ Form fromBinaryStream: ((RWBinaryOrTextStream with: stream contents) reset). Project current resourceManager addResource: image url: (FileDirectory urlForFileNamed: stream name) asString. sketch _ World drawingClass withForm: image. pasteUp addMorph: sketch centeredNear: event position. image _ sketch _ nil] fixTemps! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 1/10/2002 17:38'! defaultProjectHandler ^ExternalDropHandler type: nil extension: 'pr' action: [:stream | ProjectLoading openName: nil stream: stream fromDirectory: nil withProjectView: nil] ! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 1/10/2002 15:57'! registeredHandlers RegisteredHandlers ifNil: [RegisteredHandlers _ OrderedCollection new]. ^RegisteredHandlers! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 1/10/2002 15:57'! resetRegisteredHandlers RegisteredHandlers _ nil! ! !ExternalDropHandler class methodsFor: 'private' stamp: 'mir 8/24/2004 15:28'! unwantedSelectors "private - answer a collection well known unwanted selectors " ^ #(#removeLineFeeds: #addFileToNewZip: #compressFile: #putUpdate: )! ! 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! ! Object subclass: #ExternalSettings instanceVariableNames: '' classVariableNames: 'RegisteredClients' poolDictionaries: '' category: 'System-Support'! !ExternalSettings commentStamp: '' prior: 0! ExternalSettings manages settings kept externally, e.g. files. Objects can register themselves as clients to be notified at startup time to read their settings. Eventually all the preferences should be managed through this mechanism. ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! ExternalSettings class instanceVariableNames: ''! !ExternalSettings class methodsFor: 'accessing' stamp: 'sw 1/25/2002 12:39'! assuredPreferenceDirectory "Answer the preference directory, creating it if necessary" | prefDir | prefDir _ self preferenceDirectory. prefDir ifNil: [prefDir _ FileDirectory default directoryNamed: self preferenceDirectoryName. prefDir assureExistence]. ^ prefDir! ! !ExternalSettings class methodsFor: 'accessing' stamp: 'mir 8/23/2002 14:22'! parseServerEntryArgsFrom: stream "Args are in the form : delimited by end of line. It's not a very robust format and should be replaced by something like XML later. But it avoids evaluating the entries for security reasons." | entries lineStream entryName entryValue | entries _ Dictionary new. stream skipSeparators. [stream atEnd] whileFalse: [ lineStream _ ReadStream on: stream nextLine. entryName _ lineStream upTo: $:. lineStream skipSeparators. entryValue _ lineStream upToEnd. (entryName isEmptyOrNil or: [entryValue isEmptyOrNil]) ifFalse: [entries at: entryName put: entryValue withoutTrailingBlanks]. stream skipSeparators]. ^entries! ! !ExternalSettings class methodsFor: 'accessing' stamp: 'sd 9/30/2003 14:01'! preferenceDirectory | prefDirName path | prefDirName := self preferenceDirectoryName. path := SmalltalkImage current vmPath. ^(FileDirectory default directoryExists: prefDirName) ifTrue: [FileDirectory default directoryNamed: prefDirName] ifFalse: [ ((FileDirectory on: path) directoryExists: prefDirName) ifTrue: [(FileDirectory on: path) directoryNamed: prefDirName] ifFalse: [nil]] ! ! !ExternalSettings class methodsFor: 'accessing' stamp: 'mir 11/16/2001 13:33'! preferenceDirectoryName ^'prefs'! ! !ExternalSettings class methodsFor: 'accessing' stamp: 'mir 6/25/2001 18:45'! registerClient: anObject "Register anObject as a settings client to be notified on startup." self registeredClients add: anObject! ! !ExternalSettings class methodsFor: 'class initialization' stamp: 'ar 8/23/2001 22:56'! initialize "ExternalSettings initialize" "Order: ExternalSettings, SecurityManager, AutoStart" Smalltalk addToStartUpList: self. Smalltalk addToShutDownList: self! ! !ExternalSettings class methodsFor: 'class initialization' stamp: 'mir 8/22/2001 15:17'! shutDown "Look for external defs and load them." "ExternalSettings shutDown" self registeredClients do: [:client | client releaseExternalSettings]! ! !ExternalSettings class methodsFor: 'class initialization' stamp: 'mir 11/16/2001 13:29'! startUp "Look for external defs and load them." "ExternalSettings startUp" | prefDir | prefDir _ self preferenceDirectory. prefDir ifNil: [^self]. self registeredClients do: [:client | client fetchExternalSettingsIn: prefDir]! ! !ExternalSettings class methodsFor: 'private' stamp: 'mir 6/25/2001 18:46'! registeredClients RegisteredClients ifNil: [RegisteredClients _ Set new]. ^RegisteredClients! ! Inspector subclass: #ExternalStructureInspector instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-Inspector'! !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: 'sd 11/20/2005 21:27'! recordFieldList | fields | fields := object class fields. (fields first isKindOf: Array) ifFalse: [fields := Array with: fields]. ^fields collect: [ :field | field first ] thenSelect: [:name | name notNil]! ! !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)]! ! EllipseMorph subclass: #EyeMorph instanceVariableNames: 'iris' classVariableNames: '' poolDictionaries: '' category: 'Speech-Gestures'! !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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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]! ! !EyeMorph methodsFor: 'initialization' stamp: 'dgd 3/7/2003 14:24'! defaultColor "answer the default color/fill style for the receiver" ^ Color r: 1.0 g: 0.968 b: 0.935! ! !EyeMorph methodsFor: 'initialization' stamp: 'stephaneducasse 2/3/2006 22:22'! initialize "initialize the state of the receiver" super initialize. "" self extent: 30 @ 37. self addMorphFront: (iris := EllipseMorph new extent: 6 @ 6; borderWidth: 0; color: Color black). 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: 'stephaneducasse 2/3/2006 22:22'! timeForEvent: aVoiceEvent | time | time := 0. clause eventsDo: [ :each | aVoiceEvent == each ifTrue: [^ time] ifFalse: [time := time + each duration]]! ! !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: 'stephaneducasse 2/3/2006 22:22'! boundaryStopTime | lastEvent | lastEvent := (phrase ifNil: [clause phrases last]) lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'stephaneducasse 2/3/2006 22:22'! initialStopTime | lastEvent | lastEvent := clause phrases first words first lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-boundary tones' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'rendering-phrase accents' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! phraseAccentStopTime | lastEvent | lastEvent := (phrase ifNil: [clause phrases last]) lastSyllable events last. ^ (self timeForEvent: lastEvent) + lastEvent duration! ! !F0RenderingVisitor methodsFor: 'rendering-phrase accents' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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-pitch accents' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'visiting' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'private' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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: 'stephaneducasse 2/3/2006 22:22'! 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! ! 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: 'bulk processing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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 methodsFor: 'initialization' stamp: 'jm 8/25/1999 21:59'! n ^ n ! ! !FFT methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! realData: real realData := real. imagData := real collect: [:i | 0.0] "imaginary component all zero"! ! !FFT methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! realData: real imagData: imag realData := real. imagData := imag! ! !FFT methodsFor: 'plugin-testing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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/13/2001 21:10'! pluginTransformData: forward "Plugin testing -- if the primitive is not implemented or cannot be found run the simulation. See also: FFTPlugin" ^(Smalltalk at: #FFTPlugin ifAbsent:[^self primitiveFailed]) doPrimitive: 'primitiveFFTTransformData'.! ! !FFT methodsFor: 'testing' stamp: 'jm 8/1/1998 13:08'! imagData ^ imagData ! ! !FFT methodsFor: 'testing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'transforming' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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 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 ! ! FMSound subclass: #FMBassoonSound instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Sound-Synthesis'! !FMBassoonSound methodsFor: 'as yet unclassified' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'accessing' stamp: 'jm 3/26/98 10:45'! duration ^ initialCount asFloat / self samplingRate asFloat ! ! !FMSound methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! duration: seconds super duration: seconds. count := initialCount := (seconds * self samplingRate) rounded. ! ! !FMSound methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20: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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! initialize super initialize. waveTable := SineTable. scaledWaveTableSize := waveTable size * ScaleFactor. self setPitch: 440.0 dur: 1.0 loudness: 0.2. ! ! !FMSound methodsFor: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'sound generation' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! stopAfterMSecs: mSecs "Terminate this sound this note after the given number of milliseconds." count := (mSecs * self samplingRate) // 1000. ! ! !FMSound methodsFor: 'storing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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 ! ! TelnetProtocolClient subclass: #FTPClient instanceVariableNames: 'dataSocket' classVariableNames: '' poolDictionaries: '' category: 'Network-Protocols'! !FTPClient commentStamp: 'mir 5/12/2003 17:55' 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.! !FTPClient methodsFor: 'protocol' stamp: 'mir 2/13/2002 18:05'! abortDataConnection self sendCommand: 'ABOR'. self closeDataSocket! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 3/7/2002 13:36'! ascii self sendCommand: 'TYPE A'. self lookForCode: 200! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 3/7/2002 13:36'! binary self sendCommand: 'TYPE I'. self lookForCode: 200! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/13/2002 17:52'! changeDirectoryTo: newDirName self sendCommand: 'CWD ' , newDirName. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 17:11'! deleteDirectory: dirName self sendCommand: 'RMD ' , dirName. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 17:12'! deleteFileNamed: fileName self sendCommand: 'DELE ' , fileName. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 2/20/2002 13:53'! getDirectory | dirList | self openPassiveDataConnection. self sendCommand: 'LIST'. dirList _ self getData. self checkResponse. self checkResponse. ^dirList ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 16:50'! getFileList | dirList | self openPassiveDataConnection. self sendCommand: 'NLST'. dirList _ self getData. self checkResponse. self checkResponse. ^dirList ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 19:23'! getFileNamed: remoteFileName | data | self openPassiveDataConnection. self sendCommand: 'RETR ', remoteFileName. [self checkResponse] on: TelnetProtocolError do: [:ex | self closeDataSocket. ex pass]. data _ self getData. self checkResponse. ^data ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 5/9/2003 15:50'! getFileNamed: remoteFileName into: dataStream self openPassiveDataConnection. self sendCommand: 'RETR ', remoteFileName. [self checkResponse] on: TelnetProtocolError do: [:ex | self closeDataSocket. ex pass]. self getDataInto: dataStream. self closeDataSocket. self checkResponse! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 10/31/2000 19:03'! getPartial: limit fileNamed: remoteFileName into: dataStream | data | self openPassiveDataConnection. self sendCommand: 'RETR ', remoteFileName. [self checkResponse] on: TelnetProtocolError do: [:ex | self closeDataSocket. ex pass]. data _ self get: limit dataInto: dataStream. self abortDataConnection. ^data ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/12/2002 18:39'! loginUser: userName password: passwdString self user: userName. self password: passwdString. self login! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 17:10'! makeDirectory: newDirName self sendCommand: 'MKD ' , newDirName. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/14/2002 17:51'! openDataSocket: remoteHostAddress port: dataPort dataSocket _ Socket new. dataSocket connectTo: remoteHostAddress port: dataPort! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/14/2002 16:55'! passive self sendCommand: 'PASV'. self lookForCode: 227! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/19/2002 16:54'! putFileNamed: filePath as: fileNameOnServer "FTP a file to the server." | fileStream | fileStream _ FileStream readOnlyFileNamed: filePath. fileStream ifNil: [(FileDoesNotExistException fileName: filePath) signal]. self putFileStreamContents: fileStream as: fileNameOnServer ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 12/8/2003 16:54'! putFileStreamContents: fileStream as: fileNameOnServer "FTP a file to the server." self openPassiveDataConnection. self sendCommand: 'STOR ', fileNameOnServer. fileStream reset. [self sendStreamContents: fileStream] ensure: [self closeDataSocket]. self checkResponse. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/14/2002 16:43'! pwd | result | self sendCommand: 'PWD'. self lookForCode: 257. result := self lastResponse. ^result copyFrom: (result indexOf: $")+1 to: (result lastIndexOf: $")-1! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 10/31/2000 13:12'! quit self sendCommand: 'QUIT'. self close! ! !FTPClient methodsFor: 'protocol' stamp: 'mir 11/13/2002 17:50'! removeFileNamed: remoteFileName self sendCommand: 'DELE ', remoteFileName. self checkResponse. ! ! !FTPClient methodsFor: 'protocol' stamp: 'nk 1/26/2005 16:40'! renameFileNamed: oldFileName to: newFileName self sendCommand: 'RNFR ' , oldFileName. self lookForCode: 350. self sendCommand: 'RNTO ' , newFileName. self lookForCode: 250! ! !FTPClient methodsFor: 'private' stamp: 'mir 2/19/2002 18:27'! closeDataSocket self dataSocket ifNotNil: [ self dataSocket closeAndDestroy. self dataSocket: nil] ! ! !FTPClient methodsFor: 'private' stamp: 'mir 10/31/2000 16:24'! dataSocket ^dataSocket! ! !FTPClient methodsFor: 'private' stamp: 'mir 10/31/2000 18:23'! dataSocket: aSocket dataSocket _ aSocket! ! !FTPClient methodsFor: 'private' stamp: 'mir 4/7/2003 17:20'! login self user ifNil: [^self]. ["repeat both USER and PASS since some servers require it" self sendCommand: 'USER ', self user. "331 Password required" self lookForCode: 331. "will ask user, if needed" self sendCommand: 'PASS ', self password. "230 User logged in" ([self lookForCode: 230.] on: TelnetProtocolError do: [false]) == false ] whileTrue: [ (LoginFailedException protocolInstance: self) signal: self lastResponse] ! ! !FTPClient methodsFor: 'private' stamp: 'mir 11/14/2002 18:14'! sendStreamContents: aStream self dataSocket sendStreamContents: aStream checkBlock: [self checkForPendingError. true]! ! !FTPClient methodsFor: 'private protocol' stamp: 'svp 10/28/2003 11:06'! get: limit dataInto: dataStream "Reel in data until the server closes the connection or the limit is reached. At the same time, watch for errors on otherSocket." | buf bytesRead currentlyRead | currentlyRead _ 0. buf _ String new: 4000. [currentlyRead < limit and: [self dataSocket isConnected or: [self dataSocket dataAvailable]]] whileTrue: [ self checkForPendingError. bytesRead _ self dataSocket receiveDataWithTimeoutInto: buf. 1 to: (bytesRead min: (limit - currentlyRead)) do: [:ii | dataStream nextPut: (buf at: ii)]. currentlyRead _ currentlyRead + bytesRead]. dataStream reset. "position: 0." ^ dataStream! ! !FTPClient methodsFor: 'private protocol' stamp: 'mir 2/13/2002 18:06'! getData | dataStream | dataStream _ RWBinaryOrTextStream on: (String new: 4000). self getDataInto: dataStream. self closeDataSocket. ^dataStream contents ! ! !FTPClient methodsFor: 'private protocol' stamp: 'svp 10/28/2003 11:04'! getDataInto: dataStream "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 dataSocket isConnected or: [self dataSocket dataAvailable]] whileTrue: [ self checkForPendingError. bytesRead _ self dataSocket receiveDataWithTimeoutInto: buf. 1 to: bytesRead do: [:ii | dataStream nextPut: (buf at: ii)]]. dataStream reset. "position: 0." ^ dataStream! ! !FTPClient methodsFor: 'private protocol' stamp: 'gk 9/9/2005 09:31'! lookForCode: code ifDifferent: handleBlock "We are expecting a certain numeric code next. However, in the FTP protocol, multiple lines are allowed. If the response is multi-line, the fourth character of the first line is a $- and the last line repeats the numeric code but the code is followed by a space. So it's possible that there are more lines left of the last response that we need to throw away. We use peekForAll: so that we don't discard the next response that is not a continuation line." | headToDiscard | "check for multi-line response" (self lastResponse size > 3 and: [(self lastResponse at: 4) = $-]) ifTrue: ["Discard continuation lines." [headToDiscard _ self lastResponse first: 4. [[self stream peekForAll: headToDiscard] whileTrue: [self stream nextLine]] on: Exception do: [:ex | ^handleBlock value: nil]]]. ^ super lookForCode: code ifDifferent: handleBlock! ! !FTPClient methodsFor: 'private protocol' stamp: 'mir 4/7/2003 16:59'! openPassiveDataConnection | portInfo list dataPort remoteHostAddress | self sendCommand: 'PASV'. self lookForCode: 227 ifDifferent: [:response | (TelnetProtocolError protocolInstance: self) signal: 'Could not enter passive mode: ' , response]. portInfo _ (self lastResponse findTokens: '()') at: 2. list _ portInfo findTokens: ','. remoteHostAddress _ ByteArray with: (list at: 1) asNumber with: (list at: 2) asNumber with: (list at: 3) asNumber with: (list at: 4) asNumber. dataPort _ (list at: 5) asNumber * 256 + (list at: 6) asNumber. self openDataSocket: remoteHostAddress port: dataPort ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FTPClient class instanceVariableNames: ''! !FTPClient class methodsFor: 'accessing' stamp: 'mir 10/30/2000 20:10'! defaultPortNumber ^21! ! !FTPClient class methodsFor: 'accessing' stamp: 'mir 2/25/2002 19:08'! logFlag ^#ftp! ! !FTPClient class methodsFor: 'accessing' stamp: 'mir 2/13/2002 17:50'! rawResponseCodes #(200 'Command okay.' 500 'Syntax error, command unrecognized. This may include errors such as command line too long.' 501 'Syntax error in parameters or arguments.' 202 'Command not implemented, superfluous at this site.' 502 'Command not implemented.' 503 'Bad sequence of commands.' 504 'Command not implemented for that parameter.' 110 'Restart marker reply. In this case, the text is exact and not left to the particular implementation; it must read: MARK yyyy = mmmm Where yyyy is User-process data stream marker, and mmmm server''s equivalent marker (note the spaces between markers and "=").' 211 'System status, or system help reply.' 212 'Directory status.' 213 'File status.' 214 'Help message. On how to use the server or the meaning of a particular non-standard command. This reply is useful only to the human user.' 215 'NAME system type. Where NAME is an official system name from the list in the Assigned Numbers document.' 120 'Service ready in nnn minutes.' 220 'Service ready for new user.' 221 'Service closing control connection. Logged out if appropriate.' 421 'Service not available, closing control connection. This may be a reply to any command if the service knows it must shut down.' 125 'Data connection already open; transfer starting.' 225 'Data connection open; no transfer in progress.' 425 'Can''t open data connection.' 226 'Closing data connection. Requested file action successful (for example, file transfer or file abort).' 426 'Connection closed; transfer aborted.' 227 'Entering Passive Mode (h1,h2,h3,h4,p1,p2).' 230 'User logged in, proceed.' 530 'Not logged in.' 331 'User name okay, need password.' 332 'Need account for login.' 532 'Need account for storing files.' 150 'File status okay; about to open data connection.' 250 'Requested file action okay, completed.' 257 '"PATHNAME" created.' 350 'Requested file action pending further information.' 450 'Requested file action not taken. File unavailable (e.g., file busy).' 550 'Requested action not taken. File unavailable (e.g., file not found, no access).' 451 'Requested action aborted. Local error in processing.' 551 'Requested action aborted. Page type unknown.' 452 'Requested action not taken. Insufficient storage space in system.' 552 'Requested file action aborted. Exceeded storage allocation (for current directory or dataset).' 553 'Requested action not taken. File name not allowed.') ! ! Error subclass: #FTPConnectionException instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! !FTPConnectionException methodsFor: 'as yet unclassified' stamp: 'RAA 3/9/2001 07:47'! defaultAction self resume! ! !FTPConnectionException methodsFor: 'as yet unclassified' stamp: 'RAA 3/14/2001 15:57'! isResumable ^true! ! 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: 'access' stamp: 'stephaneducasse 2/4/2006 20:41'! coeffs "Return all coefficients needed 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'initialization' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'testing' stamp: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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: 'stephaneducasse 2/4/2006 20:41'! 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! ! Morph subclass: #FaceMorph instanceVariableNames: 'leftEye leftEyebrow rightEye rightEyebrow lips' classVariableNames: '' poolDictionaries: '' category: 'Speech-Gestures'! !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: 'stephaneducasse 2/3/2006 22:22'! 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: 'drawing' stamp: 'stephaneducasse 2/3/2006 22:22'! 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: '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: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color transparent! ! !FaceMorph methodsFor: 'initialization' stamp: 'stephaneducasse 2/3/2006 22:22'! initialize "initialize the state of the receiver" super initialize. "" self addMorph: (leftEye := EyeMorph new); addMorph: (rightEye := EyeMorph new); 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: 'stepping and presenter' stamp: 'stephaneducasse 2/3/2006 22: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: 'testing' stamp: 'len 9/13/1999 00:18'! stepTime ^ 1000! ! Object subclass: #FakeClassPool instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Tools'! !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: 'initialize' stamp: 'dvf 9/27/2005 19:05'! adopt: classOrNil "Temporarily use the classPool and sharedPools of another class" classOrNil isBehavior ifFalse: [classPool _ nil. sharedPools _ nil] ifTrue: [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: '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: 'logical operations' stamp: 'md 7/30/2005 18:05'! & aBoolean "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: 'printing' stamp: 'ajh 7/1/2004 10:36'! asBit ^ 0! ! !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! ! ClassTestCase subclass: #FalseTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Objects'! !FalseTest commentStamp: '' prior: 0! This is the unit test for the class False. Unit tests are a good way to exercise the functionality of your system in a repeatable and automatic manner. They are therefore recommended if you plan to release anything. For more information, see: - http://www.c2.com/cgi/wiki?UnitTest - http://minnow.cc.gatech.edu/squeak/1547 - the sunit class category ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:02'! testAND self assert: (false & true) = false. self assert: (false & false) = false.! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:02'! testAnd self assert: (false and: ['alternativeBlock']) = false.! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testIfFalse self assert: ((false ifFalse: ['alternativeBlock']) = 'alternativeBlock'). ! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testIfFalseIfTrue self assert: (false ifFalse: ['falseAlternativeBlock'] ifTrue: ['trueAlternativeBlock']) = 'falseAlternativeBlock'. ! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testIfTrue self assert: (false ifTrue: ['alternativeBlock']) = nil. ! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testIfTrueIfFalse self assert: (false ifTrue: ['trueAlternativeBlock'] ifFalse: ['falseAlternativeBlock']) = 'falseAlternativeBlock'. ! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testNew self should: [False new] raise: TestResult error. ! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:03'! testNot self assert: (false not = true).! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:04'! testOR self assert: (false | true) = true. self assert: (false | false) = false.! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:04'! testOr self assert: (false or: ['alternativeBlock']) = 'alternativeBlock'.! ! !FalseTest methodsFor: 'tests' stamp: 'sd 6/5/2005 09:04'! testPrintOn self assert: (String streamContents: [:stream | false printOn: stream]) = 'false'. ! ! MailComposition subclass: #FancyMailComposition instanceVariableNames: 'theLinkToInclude to subject textFields' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-EToy-Download'! !FancyMailComposition methodsFor: 'access' stamp: 'RAA 5/19/2000 18:48'! subject ^subject ! ! !FancyMailComposition methodsFor: 'access' stamp: 'RAA 5/19/2000 19:02'! subject: x subject _ x. self changed: #subject. ^true! ! !FancyMailComposition methodsFor: 'access' stamp: 'RAA 5/19/2000 18:47'! to ^to! ! !FancyMailComposition methodsFor: 'access' stamp: 'RAA 5/19/2000 19:02'! to: x to _ x. self changed: #to. ^true ! ! !FancyMailComposition methodsFor: 'actions' stamp: 'dvf 6/15/2002 19:09'! 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: ', MailSender userName; cr; nextPutAll: 'To: ',to; cr; nextPutAll: 'Subject: ',subject; cr; cr; nextPutAll: '
'; nextPutAll: messageText asString asHtml; nextPutAll: '

',theLinkToInclude,'
'. ^strm contents ! ! !FancyMailComposition methodsFor: 'actions' stamp: 'RAA 5/19/2000 12:53'! sendNow self submit: true ! ! !FancyMailComposition methodsFor: 'actions' stamp: 'RAA 5/19/2000 12:53'! submit self submit: false! ! !FancyMailComposition methodsFor: 'actions' stamp: 'mir 5/13/2003 10:58'! submit: sendNow | message | messageText _ self breakLines: self completeTheMessage atWidth: 999. message _ MailMessage from: messageText. SMTPClient deliverMailFrom: message from to: (Array with: message to) text: message text usingServer: self smtpServer. self forgetIt. ! ! !FancyMailComposition methodsFor: 'initialization' stamp: 'nk 7/3/2003 09:41'! celeste: aCeleste to: argTo subject: argSubject initialText: aText theLinkToInclude: linkText "self new celeste: Celeste current to: 'danielv@netvision.net.il' subject: 'Mysubj' initialText: 'atext' theLinkToInclude: 'linkText'" to _ argTo. subject _ argSubject. messageText _ aText. theLinkToInclude _ linkText. textFields _ #(). ! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'RAA 7/7/2000 17:51'! borderAndButtonColor ^Color r: 0.729 g: 0.365 b: 0.729! ! !FancyMailComposition methodsFor: 'morphic gui' 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 ) ! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'RAA 7/7/2000 17:35'! forgetIt morphicWindow ifNotNil: [ morphicWindow delete ]. mvcWindow ifNotNil: [ mvcWindow controller close ]. ! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'RAA 7/7/2000 17:42'! newColumn ^AlignmentMorph newColumn color: self staticBackgroundColor! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'RAA 7/7/2000 17:41'! newRow ^AlignmentMorph newRow color: self staticBackgroundColor! ! !FancyMailComposition methodsFor: 'morphic gui' 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.! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'ar 11/9/2000 20:39'! simpleString: aString ^self newRow layoutInset: 2; addMorphBack: (StringMorph contents: aString) lock! ! !FancyMailComposition methodsFor: 'morphic gui' stamp: 'RAA 7/7/2000 17:38'! staticBackgroundColor ^Color veryLightGray! ! InflateStream subclass: #FastInflateStream instanceVariableNames: '' classVariableNames: 'DistanceMap FixedDistTable FixedLitTable LiteralLengthMap' poolDictionaries: '' category: 'Compression-Streams'! !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: 'bit access' stamp: 'ar 12/4/1998 02:02'! nextSingleBits: n "Fetch the bits all at once" ^self nextBits: n.! ! !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: '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 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: 'MorphicExtras-AdditionalWidgets'! !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: 'event handling' stamp: 'jm 11/4/97 07:15'! handlesMouseDown: evt ^ true ! ! !FatBitsPaint methodsFor: 'event handling' stamp: 'RAA 9/25/1999 15:24'! mouseDown: evt ^ self perform: (currentTools at: #mouseDown: ifAbsent: [^nil]) with: evt! ! !FatBitsPaint methodsFor: 'event handling' stamp: 'RAA 9/25/1999 15:24'! mouseMove: evt ^ self perform: (currentTools at: #mouseMove: ifAbsent: [^nil]) with: evt! ! !FatBitsPaint methodsFor: 'events' stamp: 'sw 3/30/2002 16:47'! mouseDownDefault: evt lastMouse _ nil. formToEdit depth = 1 ifTrue: [self brushColor: (originalForm colorAt: (self pointGriddedFromEvent: evt)) negated]! ! !FatBitsPaint methodsFor: 'events' stamp: 'sw 3/30/2002 16:47'! mouseDownSelection: evt lastMouse _ nil. currentSelectionMorph ifNotNil: [currentSelectionMorph delete. currentSelectionMorph _ nil]. selectionAnchor _ self pointGriddedFromEvent: evt! ! !FatBitsPaint methodsFor: 'events' stamp: 'sw 3/30/2002 16:47'! mouseMovePaintBrushMode: evt | p p2 | p _ self pointGriddedFromEvent: 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: 'sw 3/30/2002 16:47'! pointGriddedFromEvent: evt | relativePt | relativePt _ evt cursorPoint - self position. ^ (relativePt x truncateTo: magnification)@(relativePt y truncateTo: magnification) ! ! !FatBitsPaint methodsFor: 'events' stamp: 'nk 4/18/2004 19:04'! 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: evt. menu popUpEvent: evt in: self world! ! !FatBitsPaint methodsFor: 'geometry testing' stamp: 'RAA 9/25/1999 21:14'! containsPoint: aPoint ^ self bounds containsPoint: aPoint "even if we are transparent" ! ! !FatBitsPaint methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color veryVeryLightGray! ! !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: 'dgd 2/14/2003 21:54'! initialize "initialize the state of the receiver" super initialize. "" self setCurrentToolTo: self toolsForPaintBrush. formToEdit _ Form extent: 50 @ 40 depth: 8. formToEdit fill: formToEdit boundingBox fillColor: Color veryVeryLightGray. brushSize _ magnification _ 4. 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: 'dgd 10/8/2003 18:59'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'background color' translated action: #setBackgroundColor:; add: 'pen color' translated action: #setPenColor:; add: 'pen size' translated action: #setPenSize:; add: 'fill' translated action: #fill; add: 'magnification' translated action: #setMagnification:; add: 'accept' translated action: #accept; add: 'revert' translated action: #revert; add: 'inspect' translated action: #inspectForm; add: 'file out' translated action: #fileOut; add: 'selection...' translated action: #selectionMenu:; add: 'tools...' translated action: #toolMenu:! ! !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: 'nb 6/17/2003 12:25'! fileOut | fileName result | result _ StandardFileMenu newFile ifNil: [^Beeper 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: 'sw 3/30/2002 16:48'! mouseMoveSelectionMode: evt | p | p _ self pointGriddedFromEvent: 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: 'nk 4/18/2004 19:04'! 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: evt. 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! ! FileList2 subclass: #FileChooser instanceVariableNames: 'view caption captionMorph captionBox cancelButton okButton buttonPane captionPane directoryPane filePane showShortFileNames' classVariableNames: '' poolDictionaries: '' category: 'Tools-FileList'! !FileChooser commentStamp: 'miki 8/15/2005 12:07' prior: 0! This class provides a simple "modal" dialog box to choose a file, with a directory tree, a file list, and open and cancel buttons. It is meant to be an improvement of FileList2 modalFileSelector. An applictaion can customize the user interface in a straightforward way. Creation of the file choser is done in several steps by calling various helper methods.. The order of the method calls when creating a customized file chooser are important. The UI must be created before methods that change the attributes of the UI can be called. You can either start by creating the default UI, and them modify the morphs in the file chooser (there are methods to access the buttons and the panes). You can also build a completely custom UI, by writing your own methods for creating the layout etc. One way to do this is to subclass FileChooser and override the methods you want to change, andother way is to supply your own morphic view to the file chooser. This must be an instance of MorphicModel or a subclass of it, because the file chooser uses the model functionality. There are two varieties of the UI, one that is supposed to be like a dialog box (uses colors from the menu preferences in class Preference), and one is using a system window. The way a system window works turns out to be somehat different from how a plain Morphic Model works, and this is why there are separate methods for creating the dialog box UI and the system window UI. On the class side, there are examples that shows differents ways to use this class. On the to do list is adding support for a file save dialog box, with a directory tree and a text input field for typing a file name. (Mikael Kindborg, 050815) ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/15/2005 11:26'! buttonPane ^buttonPane! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/14/2005 22:48'! cancelButton ^cancelButton! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/13/2005 17:52'! caption ^caption! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/15/2005 11:26'! captionPane ^captionPane! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/14/2005 22:55'! directory ^super directory! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/15/2005 11:27'! directoryPane ^directoryPane! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/15/2005 11:27'! filePane ^filePane! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/13/2005 17:57'! morphicView ^view! ! !FileChooser methodsFor: 'accessing' stamp: 'miki 8/14/2005 22:48'! okButton ^okButton! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/15/2005 11:45'! initalizeAsDialogBox self initalizeBasicParameters. self createDialogBoxUI. self morphicView useRoundedCorners; color: Preferences menuColor; adoptPaneColor: Preferences menuLineColor. self setCaptionColor: Preferences menuTitleColor; setButtonColor: Preferences menuColor.! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/15/2005 11:45'! initalizeAsSystemWindow self initalizeBasicParameters. self createSystemWindowUI.! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/15/2005 11:45'! initalizeAsSystemWindowWithCaptionPane self initalizeBasicParameters. self createSystemWindowUIWithCaptionPane.! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/15/2005 12:09'! initalizeBasicParameters self showShortFileNames: true. self setDirectory: FileDirectory default.! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/14/2005 23:12'! setDirectory: aDir ^super directory: aDir! ! !FileChooser methodsFor: 'initialization' stamp: 'sd 11/20/2005 21:27'! setMorphicView: aMorphicModel view := aMorphicModel. self modalView: view. view model: self.! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/14/2005 23:12'! setSuffixes: aList self fileSelectionBlock: [:entry :myPattern | entry isDirectory ifTrue: [false] ifFalse: [aList includes: (FileDirectory extensionFor: entry name asLowercase)]] fixTemps! ! !FileChooser methodsFor: 'initialization' stamp: 'miki 8/15/2005 11:32'! showShortFileNames: aBoolean showShortFileNames := aBoolean! ! !FileChooser methodsFor: 'open' stamp: 'sd 11/20/2005 21:27'! open | model | self postOpen. "Funny name in this context, should be renamed, but whatever..." self morphicView openInWorld. model := self morphicView model. FileChooser modalLoopOn: self morphicView. ^ model getSelectedFile. ! ! !FileChooser methodsFor: 'ui creation' stamp: 'sd 11/20/2005 21:27'! addFullPanesTo: aMorph from: aCollection | frame | aCollection do: [ :each | frame := LayoutFrame fractions: each second offsets: each third. aMorph addMorph: each first fullFrame: frame. ]! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 18:54'! centerMorphicView self morphicView fullBounds; position: Display extent - self morphicView extent // 2. ! ! !FileChooser methodsFor: 'ui creation' stamp: 'sd 11/20/2005 21:27'! createCancelButton cancelButton := SimpleButtonMorph new. cancelButton label: 'Cancel' translated; color: Color transparent; borderColor: Color black; borderWidth: 1. cancelButton on: #mouseUp send: #cancelHit to: self. ^cancelButton ! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 15:05'! createDialogBoxButtonPane "Create buttons suitable for a MorphicModel file chooser." buttonPane := AlignmentMorph new. buttonPane layoutPolicy: ProportionalLayout new; color: Color transparent; borderWidth: 0. self createOkButton. self createCancelButton. buttonPane addMorph: self cancelButton fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 0.49 @ 1.0) offsets: (0 @ 0 corner: 0 @ 0)). buttonPane addMorph: self okButton fullFrame: (LayoutFrame fractions: (0.51 @ 0 corner: 1.0 @ 1.0) offsets: (0 @ 0 corner: 0 @ 0)). ^buttonPane! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 18:45'! createDialogBoxCaptionPane "Create a morph to hold the caption string. The caption is created in setCaption:" | icon frame | captionPane := AlignmentMorph new. captionPane color: Color transparent; layoutPolicy: ProportionalLayout new. "You can change the caption later by calling setCaption:" self setCaption: 'Please select a file' translated. self setCaptionFont: Preferences windowTitleFont. icon := SketchMorph new. icon form: MenuIcons openIcon. captionPane addMorph: icon. frame := LayoutFrame new. frame leftFraction: 0; topFraction: 0.5; leftOffset: icon form width // 2; topOffset: (icon form width // 2) negated. icon layoutFrame: frame. ^captionPane! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 12:13'! createDialogBoxLayout "Create a layout suitable for a MorphicModel file chooser." | inset insetNeg captionTop captionBottom buttonsBottom buttonsTop contentTop contentBottom | inset := 6. insetNeg := inset negated. captionTop := 0. captionBottom := 33. contentTop := captionBottom + inset. contentBottom := -30 - inset - inset. buttonsTop := contentBottom + inset. buttonsBottom := insetNeg. self addFullPanesTo: self morphicView from: { { (self captionPane). (0 @ 0 corner: 1 @ 0). (0 @ captionTop corner: 0 @ captionBottom) }. { (self buttonPane). (0 @ 1 corner: 1 @ 1). (inset @ buttonsTop corner: insetNeg @ buttonsBottom) }. { (self directoryPane). (0 @ 0 corner: 0.5 @ 1). (inset @ contentTop corner: insetNeg @ contentBottom) }. { (self filePane). (0.5 @ 0 corner: 1 @ 1). (inset @ contentTop corner: insetNeg @ contentBottom) } }! ! !FileChooser methodsFor: 'ui creation' stamp: 'sd 11/20/2005 21:27'! createDialogBoxMorphicView | m | m := MorphicModel new layoutPolicy: ProportionalLayout new; color: Preferences menuColor; borderColor: Preferences menuBorderColor; borderWidth: Preferences menuBorderWidth; layoutInset: 0; extent: 600@400. self setMorphicView: m. ^m! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 19:02'! createDialogBoxUI "This method creates UI components and a layout that are suitable for a MorphicModel. Also centers the morphic view in the world. Note that the order of the method calls are important if you modify this." self createDialogBoxMorphicView; createDialogBoxCaptionPane; createDialogBoxButtonPane; createDirectoryPane; createFilePane; createDialogBoxLayout; centerMorphicView. ^self morphicView! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:27'! createDirectoryPane directoryPane := self morphicDirectoryTreePane. directoryPane borderWidth: 0. ^directoryPane! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:27'! createFilePane filePane := self morphicFileListPane. filePane borderWidth: 0. ^filePane! ! !FileChooser methodsFor: 'ui creation' stamp: 'sd 11/20/2005 21:27'! createOkButton okButton := SimpleButtonMorph new. okButton label: 'Open' translated; color: Color transparent; borderColor: Color black; borderWidth: 1. okButton on: #mouseUp send: #okHit to: self. ^okButton! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:26'! createSystemWindowButtonPane "Create buttons suitable for a SystemWindow file chooser." self optionalButtonSpecs: self okayAndCancelServices. buttonPane := self optionalButtonRow. okButton := buttonPane firstSubmorph. cancelButton := buttonPane firstSubmorph. ^buttonPane! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:26'! createSystemWindowCaptionPane "Create a morph to hold the caption string. The caption is created in setCaption:" captionPane := AlignmentMorph new. captionPane color: Color transparent; layoutPolicy: ProportionalLayout new. "You can change the caption later by calling setCaption:" self setCaption: 'Please select a file' translated. ^captionPane! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 12:12'! createSystemWindowLayout "Create a layout suitable for a SystemWindow file chooser." | buttonsHeight | buttonsHeight := 33. self addFullPanesTo: self morphicView from: { { (self buttonPane). (0 @ 0 corner: 1 @ 0). (0 @ 0 corner: 0 @ buttonsHeight) }. { (self directoryPane). (0 @ 0 corner: 0.5 @ 1). (0 @ buttonsHeight corner: 0 @ 0) }. { (self filePane). (0.5 @ 0 corner: 1 @ 1). (0 @ buttonsHeight corner: 0 @ 0) } }! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 12:18'! createSystemWindowLayoutWithCaptionPane | buttonsHeight captionHeight | buttonsHeight := 33. captionHeight := 28. self addFullPanesTo: self morphicView from: { { (self captionPane). (0 @ 0 corner: 1 @ 0). (0 @ 0 corner: 0 @ captionHeight) }. { (self buttonPane). (0 @ 0 corner: 1 @ 0). (0 @ captionHeight corner: 0 @ (captionHeight + buttonsHeight)) }. { (self directoryPane). (0 @ 0 corner: 0.5 @ 1). (0 @ (captionHeight + buttonsHeight) corner: 0 @ 0) }. { (self filePane). (0.5 @ 0 corner: 1 @ 1). (0 @ (captionHeight + buttonsHeight) corner: 0 @ 0) } }! ! !FileChooser methodsFor: 'ui creation' stamp: 'sd 11/20/2005 21:27'! createSystemWindowMorphicView | m | m := SystemWindow labelled: 'Please select a file' translated. "self directory pathName." "m deleteCloseBox." self setMorphicView: m.! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:40'! createSystemWindowUI "This method creates UI components and a layout that are suitable for a SystemWindow. Note that the order of the method calls are important." self createSystemWindowMorphicView; createSystemWindowButtonPane; createDirectoryPane; createFilePane; createSystemWindowLayout. ^self morphicView! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:40'! createSystemWindowUIWithCaptionPane self createSystemWindowMorphicView; createSystemWindowCaptionPane; createSystemWindowButtonPane; createDirectoryPane; createFilePane; createSystemWindowLayoutWithCaptionPane. ^self morphicView! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 01:12'! setButtonColor: aColor self okButton color: aColor. self cancelButton color: aColor. ! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:23'! setCaptionColor: aColor self captionPane color: aColor! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 14:55'! setCaptionFont: aFont self caption font: aFont. self setCaption: self caption contents asString. ! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 11:23'! setCaption: aString | frame | caption ifNil: [caption := StringMorph new. self captionPane addMorph: caption]. caption contents: aString. frame := LayoutFrame new. frame leftFraction: 0.5; topFraction: 0.5; leftOffset: caption width negated // 2; topOffset: caption height negated // 2. caption layoutFrame: frame! ! !FileChooser methodsFor: 'ui creation' stamp: 'miki 8/15/2005 00:41'! setPaneColor: aColor self morphicView color: aColor; adoptPaneColor: aColor. ! ! !FileChooser methodsFor: 'updating' stamp: 'miki 8/15/2005 11:32'! fileNameFormattedFrom: entry sizePad: sizePad "entry is a 5-element array of the form: (name creationTime modificationTime dirFlag fileSize)" "If the short file list flag is false, we send this on to the superclass." | nameStr | showShortFileNames ifFalse: [^super fileNameFormattedFrom: entry sizePad: sizePad]. "Otherwise, just show the name of the file in the file list." nameStr := (entry at: 4) ifTrue: [entry first , self folderString] ifFalse: [entry first]. ^nameStr! ! !FileChooser methodsFor: 'updating' stamp: 'miki 8/14/2005 23:04'! updateButtonRow "Prevent updating of the the button row."! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileChooser class instanceVariableNames: ''! !FileChooser class methodsFor: 'examples' stamp: 'sd 11/20/2005 21:28'! example1 "Open file chooser with the standard dialog box UI." "FileChooser example1" | fc stream | fc := FileChooser new. fc initalizeAsDialogBox. stream := fc open. stream inspect.! ! !FileChooser class methodsFor: 'examples' stamp: 'sd 11/20/2005 21:28'! example2 "Open file chooser with a system window UI." "FileChooser example2" | fc stream | fc := FileChooser new. fc initalizeAsSystemWindow. stream := fc open. stream inspect.! ! !FileChooser class methodsFor: 'examples' stamp: 'sd 11/20/2005 21:28'! example3 "Open file chooser with a system window UI that has a caption pane and shows only picture files." "FileChooser example3" | fc stream | fc := FileChooser new. fc initalizeAsSystemWindowWithCaptionPane. fc setCaption: 'Select a picture file' translated. fc setSuffixes: {'png' . 'gif' . 'bmp' . 'jpg' . 'jpeg' }. stream := fc open. stream ifNotNil: [(Form fromBinaryStream: stream) asMorph openInHand].! ! !FileChooser class methodsFor: 'examples' stamp: 'sd 11/20/2005 21:28'! example4 "Open file chooser with a customized dialog box UI. The order of the messages is important. In general, call the initialize method first, then modify things, and finally call open." "FileChooser example4" | fc stream | fc := FileChooser new. fc initalizeAsDialogBox. fc setDirectory: FileDirectory root. fc setSuffixes: {'png' . 'gif' . 'bmp' . 'jpg' . 'jpeg' }. fc setCaption: 'Select a picture file' translated. fc morphicView borderColor: Color black; borderWidth: 2; color: Color white. fc setPaneColor: Color gray muchLighter. fc captionPane color: Color orange muchLighter. fc okButton color: Color green muchLighter. fc cancelButton color: Color blue muchLighter. fc morphicView position: 20@20. stream := fc open. stream ifNotNil: [(Form fromBinaryStream: stream) asMorph openInHand].! ! Browser subclass: #FileContentsBrowser instanceVariableNames: 'packages infoString' classVariableNames: '' poolDictionaries: '' category: 'Tools-File Contents Browser'! !FileContentsBrowser commentStamp: '' prior: 0! I am a class browser view on a fileout (either a source file (.st) or change set (.cs)). I do not actually load the code into to the system, nor do I alter the classes in the image. Use me to vet code in a comfortable way before loading it into your image. From a FileList, I can be invoked by selecting a source file and selecting the "browse code" menu item from the yellow button menu. I use PseudoClass, PseudoClassOrganizers, and PseudoMetaclass to model the class structure of the source file.! !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' stamp: 'sd 11/20/2005 21:27'! 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: [self inform: '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: 'class list' stamp: 'ar 9/27/2005 20:27'! browseMethodFull | myClass | (myClass := self selectedClassOrMetaClass) ifNotNil: [ToolSet browse: 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' stamp: 'rbb 3/1/2005 10:52'! findClass | pattern foundClass classNames index foundPackage | self okToChange ifFalse: [^ self classNotFound]. pattern := (UIManager default 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: [(UIManager default chooseFrom: classNames lines: #())]. 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: 'sd 11/20/2005 21:27'! 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: 'creation' stamp: 'md 2/24/2006 15:46'! addLowerPanesTo: window at: nominalFractions with: editString | verticalOffset row codePane infoPane infoHeight divider | row := AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 1; borderColor: Color black; layoutPolicy: ProportionalLayout new. codePane := MorphicTextEditor default on: self text: #contents accept: #contents:notifying: readSelection: #contentsSelection menu: #codePaneMenu:shifted:. infoPane := PluggableTextMorph on: self text: #infoViewContents accept: nil readSelection: nil menu: nil. infoPane askBeforeDiscardingEdits: false. verticalOffset := 0. ">>not with this browser--- at least not yet --- innerFractions := 0@0 corner: 1@0. 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) ). divider := BorderedSubpaneDividerMorph forTopEdge. divider extent: 4@4; color: Color transparent; borderColor: #raised; borderWidth: 2. row addMorph: divider fullFrame: ( LayoutFrame fractions: (0@1 corner: 1@1) offsets: (0@infoHeight negated corner: 0@(1-infoHeight)) ). row addMorph: (infoPane borderWidth: 0; hideScrollBarsIndefinitely) 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: 'sd 11/20/2005 21:27'! 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 | contentsSymbol := self defaultDiffsSymbol. "#showDiffs or #prettyDiffs" 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 := MvcTextEditor default 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: 'sd 11/20/2005 21:27'! openAsMorph "Create a pluggable version of all the views for a Browser, including views and controllers." | window aListExtent next mySingletonList | window := (SystemWindow labelled: 'later') model: self. self packages size = 1 ifTrue: [ aListExtent := 0.333333 @ 0.34. self systemCategoryListIndex: 1. mySingletonList := PluggableListMorph on: self list: #systemCategorySingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #packageListMenu: keystroke: #packageListKey:from:. mySingletonList hideScrollBarsIndefinitely. window addMorph: mySingletonList 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: 'diffs' stamp: 'sd 11/20/2005 21:27'! methodDiffFor: aString class: aPseudoClass selector: selector meta: meta "Answer the diff between the current copy of the given class/selector/meta for the string provided" | 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 prettyDiffs: self showingPrettyDiffs]! ! !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: 'edit pane' stamp: 'md 2/13/2006 14:36'! selectedBytecodes "Compile the source code for the selected message selector and extract and return the bytecode listing." | class selector | class := self selectedClassOrMetaClass. selector := self selectedMessageName. contents := class sourceCodeAt: selector. contents := Compiler new parse: contents in: class notifying: nil. contents := contents generate. ^ contents symbolic asText! ! !FileContentsBrowser methodsFor: 'edit pane' stamp: 'nk 2/23/2005 18:00'! 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 := class prettyPrinterClass format: contents in: class notifying: nil decorated: Preferences colorWhenPrettyPrinting]. self showingAnyKindOfDiffs ifTrue: [contents := self methodDiffFor: contents class: self selectedClass selector: self selectedMessageName meta: self metaClassIndicated]. ^contents asText makeSelectorBoldIn: class! ! !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: 'ar 9/27/2005 20:07'! fileIntoNewChangeSet | p ff | (p := self selectedPackage) ifNil: [^ Beeper beep]. ff := FileStream readOnlyFileNamed: p fullPackageName. ChangeSet 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: 'sd 11/20/2005 21:27'! infoViewContents "Answer the string to show in the info view" | theClass stamp exists | 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' translated] ifFalse: ['New class' translated]]. editSelection == #editMessage ifFalse: [^ '']. (theClass notNil and: [self metaClassIndicated]) ifTrue: [theClass := theClass class]. stamp := self selectedClassOrMetaClass stampAt: self selectedMessageName. exists := theClass notNil and: [theClass includesSelector: self selectedMessageName]. ^ stamp = 'methodWasRemoved' ifTrue: [exists ifTrue: ['Existing method removed by this change-set' translated] ifFalse: ['Removal request for a method that is not present in this image' translated]] ifFalse: [stamp, ' · ', (exists ifTrue: ['Method already exists' translated , self extraInfo] ifFalse: ['New method' translated])]! ! !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: 'initialize-release' stamp: 'dew 9/15/2001 16:19'! defaultBrowserTitle ^ 'File Contents Browser'! ! !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 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: 'tpr 3/11/2001 21:26'! classListMenu: aMenu shifted: ignored "Answer the class list menu, ignoring the state of the shift key in this case" ^ self classListMenu: aMenu! ! !FileContentsBrowser methodsFor: 'menus' stamp: 'sw 11/13/2001 09:12'! contentsSymbolQuints "Answer a list of quintuplets representing information on the alternative views available in the code pane. For the file-contents browser, the choices are restricted to source and the two diffing options" ^ self sourceAndDiffsQuintsOnly! ! !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: 'metaclass' stamp: 'sd 11/20/2005 21:27'! selectedClassOrMetaClass "Answer the selected class or metaclass." | cls | self metaClassIndicated ifTrue: [^ (cls := self selectedClass) ifNotNil: [cls metaClass]] ifFalse: [^ self selectedClass]! ! !FileContentsBrowser methodsFor: 'metaclass' stamp: 'sd 11/20/2005 21:27'! 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: 'bkv 8/13/2003 23:59'! 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: [self systemNavigation browseAllCallsOn: self selectedMessageName]! ! !FileContentsBrowser methodsFor: 'other' stamp: 'sd 11/20/2005 21:27'! 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 theNonMetaClass 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: 'sd 11/20/2005 21:27'! didCodeChangeElsewhere "Determine whether the code for the currently selected method and class has been changed somewhere else." | aClass | (aClass := self selectedClassOrMetaClass) ifNil: [^ false]. (aClass isKindOf: PseudoClass) ifTrue: [^ false]. "class not installed" ^super didCodeChangeElsewhere! ! !FileContentsBrowser methodsFor: 'other' stamp: 'sw 10/1/2001 11:16'! labelString "Answer the string for the window title" ^ 'File Contents Browser ', (self selectedSystemCategoryName ifNil: [''])! ! !FileContentsBrowser methodsFor: 'other' stamp: 'sma 2/6/2000 12:27'! methodHierarchy (self selectedClassOrMetaClass isNil or: [self selectedClassOrMetaClass hasDefinition]) ifFalse: [super methodHierarchy]! ! !FileContentsBrowser methodsFor: 'removing' stamp: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! removeMessage | messageName | messageListIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. messageName := self selectedMessageName. (self selectedClass 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: 'sd 11/20/2005 21:27'! 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: 'sd 11/20/2005 21:27'! 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: 'toolbuilder' stamp: 'ar 2/12/2005 14:20'! buildWith: builder "Create the ui for the browser" | windowSpec listSpec textSpec buttonSpec panelSpec max | windowSpec := builder pluggableWindowSpec new. windowSpec model: self. windowSpec label: 'System Browser'. windowSpec children: OrderedCollection new. max := self wantsOptionalButtons ifTrue:[0.43] ifFalse:[0.5]. listSpec := builder pluggableListSpec new. listSpec model: self; list: #systemCategoryList; getIndex: #systemCategoryListIndex; setIndex: #systemCategoryListIndex:; menu: #packageListMenu:; keyPress: #packageListKey:from:; frame: (0@0 corner: 0.25@max). windowSpec children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #classList; getIndex: #classListIndex; setIndex: #classListIndex:; menu: #classListMenu:; keyPress: #classListKey:from:; frame: (0.25@0 corner: 0.5@(max-0.1)). windowSpec children add: listSpec. panelSpec := builder pluggablePanelSpec new. panelSpec frame: (0.25@(max-0.1) corner: 0.5@max). panelSpec children: OrderedCollection new. windowSpec children addLast: panelSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: 'instance'; state: #instanceMessagesIndicated; action: #indicateInstanceMessages; frame: (0@0 corner: 0.4@1). panelSpec children addLast: buttonSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: '?'; state: #classCommentIndicated; action: #plusButtonHit; frame: (0.4@0 corner: 0.6@1). panelSpec children addLast: buttonSpec. buttonSpec := builder pluggableButtonSpec new. buttonSpec model: self; label: 'class'; state: #classMessagesIndicated; action: #indicateClassMessages; frame: (0.6@0 corner: 1@1). panelSpec children addLast: buttonSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #messageCategoryList; getIndex: #messageCategoryListIndex; setIndex: #messageCategoryListIndex:; menu: #messageCategoryMenu:; keyPress: #arrowKey:from:; frame: (0.5@0 corner: 0.75@max). windowSpec children add: listSpec. listSpec := builder pluggableListSpec new. listSpec model: self; list: #messageList; getIndex: #messageListIndex; setIndex: #messageListIndex:; menu: #messageListMenu:shifted:; keyPress: #messageListKey:from:; frame: (0.75@0 corner: 1@max). windowSpec children add: listSpec. self wantsOptionalButtons ifTrue:[ panelSpec := self buildOptionalButtonsWith: builder. panelSpec frame: (0@0.43 corner: 1@0.5). windowSpec children add: panelSpec. ]. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #contents; setText: #contents:notifying:; selection: #contentsSelection; menu: #codePaneMenu:shifted:; frame: (0@0.5corner: 1@0.92). windowSpec children add: textSpec. textSpec := builder pluggableInputFieldSpec new. textSpec model: self; getText: #infoViewContents; frame: (0@0.92corner: 1@1). windowSpec children add: textSpec. ^builder build: windowSpec! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileContentsBrowser class instanceVariableNames: ''! !FileContentsBrowser class methodsFor: 'class initialization' stamp: 'hg 8/3/2000 18:17'! initialize FileList registerFileReader: self! ! !FileContentsBrowser class methodsFor: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'nk 6/12/2004 11:41'! fileReaderServicesForDirectory: aDirectory ^{ self serviceBrowseCodeFiles }! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'nk 2/17/2004 19:18'! fileReaderServicesForFile: fullName suffix: suffix ((FileStream isSourceFileSuffix: suffix) or: [ suffix = '*' ]) ifTrue: [ ^Array with: self serviceBrowseCode]. ^(fullName endsWith: 'cs.gz') ifTrue: [ Array with: self serviceBrowseCompressedCode ] ifFalse: [#()] ! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'rbb 3/1/2005 10:53'! selectAndBrowseFile: aFileList "When no file are selected you can ask to browse several of them" | selectionPattern files | selectionPattern := UIManager default request:'What files?' initialAnswer: '*.cs;*.st'. files := (aFileList directory fileNamesMatching: selectionPattern) collect: [:each | aFileList directory fullNameFor: each]. self browseFiles: files. ! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'nk 4/29/2004 10:35'! serviceBrowseCode "Answer the service of opening a file-contents browser" ^ (SimpleServiceEntry provider: self label: 'code-file browser' selector: #browseStream: description: 'open a "file-contents browser" on this file, allowing you to view and selectively load its code' buttonLabel: 'code') argumentGetter: [ :fileList | fileList readOnlyStream ]! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'nk 6/12/2004 11:35'! serviceBrowseCodeFiles ^ (SimpleServiceEntry provider: self label: 'browse code files' selector: #selectAndBrowseFile:) argumentGetter: [ :fileList | fileList ]; yourself! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'nk 4/29/2004 10:35'! serviceBrowseCompressedCode "Answer a service for opening a changelist browser on a file" ^ (SimpleServiceEntry provider: self label: 'code-file browser' selector: #browseCompressedCodeStream: description: 'open a "file-contents browser" on this file, allowing you to view and selectively load its code' buttonLabel: 'code') argumentGetter: [ :fileList | fileList readOnlyStream ]! ! !FileContentsBrowser class methodsFor: 'file list services' stamp: 'md 11/23/2004 13:34'! services "Answer potential file services associated with this class" ^ {self serviceBrowseCode}.! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'tak 3/16/2005 11:37'! browseCompressedCodeStream: aStandardFileStream "Browse the selected file in fileIn format." | zipped unzipped | [zipped := GZipReadStream on: aStandardFileStream. unzipped := MultiByteBinaryOrTextStream with: zipped contents asString] ensure: [aStandardFileStream close]. unzipped reset. self browseStream: unzipped named: aStandardFileStream name! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! 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 systemOrganizer: organizer) packages: packageDict]. self openBrowserView: browser createViews label: 'File Contents Browser'. ! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'nb 6/17/2003 12:25'! browseFile: aFilename "Open a file contents browser on a file of the given name" aFilename ifNil: [^ Beeper beep]. self browseFiles: (Array with: aFilename)! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'edc 5/12/2006 07:09'! browseStream: aStream aStream setConverterForCode. self browseStream: aStream named: aStream name! ! !FileContentsBrowser class methodsFor: 'instance creation' stamp: 'sd 11/20/2005 21:28'! browseStream: aStream named: aString | package organizer packageDict browser | Cursor wait showWhile: [ packageDict := Dictionary new. browser := self new. organizer := SystemOrganizer defaultList: Array new. package := (FilePackage new fullName: aString; fileInFrom: aStream). packageDict at: package packageName put: package. organizer classifyAll: package classes keys under: package packageName. (browser := self systemOrganizer: organizer) packages: packageDict]. self openBrowserView: browser createViews label: 'File Contents Browser'. ! ! !FileContentsBrowser class methodsFor: 'window color' stamp: 'sw 2/26/2002 14:25'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'File Contents Browser' brightColor: #tan pastelColor: #paleTan helpMessage: 'Lets you view the contents of a file as code, in a browser-like tool.'! ! Object subclass: #FileDirectory instanceVariableNames: 'pathName' classVariableNames: 'DefaultDirectory DirectoryClass StandardMIMEMappings' poolDictionaries: '' category: 'Files-Directories'! !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: 'enumeration' stamp: 'yo 12/19/2003 21:15'! containingDirectory "Return the directory containing this directory." ^ FileDirectory on: (FileDirectory dirPathFor: pathName asSqueakPathName) ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'nk 2/23/2001 11:35'! directoryEntry ^self containingDirectory entryAt: self localName! ! !FileDirectory methodsFor: 'enumeration' stamp: 'stephaneducasse 2/4/2006 20:31'! directoryEntryFor: filenameOrPath "Answer the directory entry for the given file or path. Sorta like a poor man's stat()." | fName dir | DirectoryClass splitName: filenameOrPath to:[:filePath :name | fName := name. filePath isEmpty ifTrue: [dir := self] ifFalse: [dir := FileDirectory on: filePath]]. self isCaseSensitive ifTrue:[^dir entries detect:[:entry| entry name = fName] ifNone:[nil]] ifFalse:[^dir entries detect:[:entry| entry name sameAs: fName] ifNone:[nil]]! ! !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: 'yo 12/19/2003 21:15'! fullName "Return the full name of this directory." ^pathName asSqueakPathName ! ! !FileDirectory methodsFor: 'enumeration' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'yo 12/19/2003 21:15'! localName "Return the local name of this directory." ^FileDirectory localNameFor: pathName asSqueakPathName! ! !FileDirectory methodsFor: 'enumeration' stamp: 'mir 8/24/2001 12:01'! matchingEntries: criteria "Ignore the filter criteria for now" ^self entries! ! !FileDirectory methodsFor: 'enumeration' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'enumeration' stamp: 'stephaneducasse 2/4/2006 20:31'! withAllSubdirectoriesCollect: aBlock "Evaluate aBlock with each of the directories in the subtree of the file system whose root is this directory. Answer the results of these evaluations." | result todo dir | result := OrderedCollection new: 100. todo := OrderedCollection with: self. [todo size > 0] whileTrue: [ dir := todo removeFirst. result add: (aBlock value: dir). dir directoryNames do: [:n | todo add: (dir directoryNamed: n)]]. ^ result ! ! !FileDirectory methodsFor: 'file directory' stamp: 'hg 2/2/2002 16:37'! assureExistence "Make sure the current directory exists. If necessary, create all parts in between" self containingDirectory assureExistenceOfPath: self localName! ! !FileDirectory methodsFor: 'file directory' stamp: 'stephaneducasse 2/4/2006 20:31'! assureExistenceOfPath: lPath "Make sure the local directory exists. If necessary, create all parts in between" | localPath | localPath := lPath. localPath isEmpty ifTrue: [ ^self ]. "Assumed to exist" (self directoryExists: localPath) ifTrue: [^ self]. "exists" "otherwise check parent first and then create local dir" self containingDirectory assureExistenceOfPath: self localName. self createDirectory: localPath! ! !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: 'file name utilities' stamp: 'gk 2/10/2004 13:22'! asUrl "Convert my path into a file:// type url - a FileUrl." ^FileUrl pathParts: (self pathParts copyWith: '')! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! fileNamesMatching: pat " FileDirectory default fileNamesMatching: '*' FileDirectory default fileNamesMatching: '*.image;*.changes' " | files | files := OrderedCollection new. (pat findTokens: ';', String crlf) do: [ :tok | files addAll: (self fileNames select: [:name | tok match: name]) ]. ^files ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! 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. Also note that this method is tolerent of a nil argument -- is simply returns nil in this case." | correctedLocalName prefix | fileName ifNil: [^ nil]. DirectoryClass 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: 'ar 2/27/2001 22:23'! isTypeFile ^true! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! lastNameFor: 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 | 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: [nil] ifFalse: [(baseFileName, '.', (splits last at: 2) asString, self class dot, extension) asFileName]! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! realUrl "Senders expect url without trailing slash - #url returns slash" | url | url := self url. url last = $/ ifTrue:[^url copyFrom: 1 to: url size-1]. ^url! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'yo 12/19/2003 21:15'! relativeNameFor: aFileName "Return the full name for aFileName, assuming that aFileName is a name relative to me." aFileName isEmpty ifTrue: [ ^pathName asSqueakPathName]. ^aFileName first = self pathNameDelimiter ifTrue: [ pathName asSqueakPathName, aFileName ] ifFalse: [ pathName asSqueakPathName, self slash, aFileName ] ! ! !FileDirectory methodsFor: 'file name utilities' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 operations' stamp: 'stephaneducasse 2/4/2006 20: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: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! copyFile: fileStream1 toFile: fileStream2 | buffer | buffer := String new: 50000. [fileStream1 atEnd] whileFalse: [fileStream2 nextPutAll: (fileStream1 nextInto: buffer)]. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'yo 2/24/2005 18:33'! 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) asVmPathName ! ! !FileDirectory methodsFor: 'file operations' stamp: 'yo 2/24/2005 18:33'! 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) asVmPathName. ! ! !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: 'stephaneducasse 2/4/2006 20:31'! 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" | fullName | fullName := self fullNameFor: localFileName. (StandardFileStream retryWithGC:[self primDeleteFileNamed: (self fullNameFor: localFileName) asVmPathName] until:[:result| result notNil] forFileNamed: fullName) == nil ifTrue: [^failBlock value]. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'tpr 3/26/2002 16:48'! deleteLocalFiles "Delete the local files in this directory." self fileNames do:[:fn| self deleteFileNamed: fn ifAbsent: [(CannotDeleteFileException new messageText: 'Could not delete the old version of file ' , (self fullNameFor: fn)) signal]] ! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 | DirectoryClass splitName: filenameOrPath to: [:filePath :name | fName := name. filePath isEmpty ifTrue: [dir := self] ifFalse: [dir := FileDirectory on: filePath]]. ^ (dir includesKey: fName) or: [ fName = '' and:[ dir entries size > 1]]! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20:31'! 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) asVmPathName type: typeString creator: creatorString.] ensure: [typeString := typeString asString. creatorString := creatorString asString]. results := Array with: typeString convertFromSystemString with: creatorString convertFromSystemString. ^results ! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 := fileName findLast: [:ch| ch = dot]. idx = 0 ifFalse: [ext := fileName copyFrom: idx+1 to: fileName size]. ^ StandardMIMEMappings at: ext asLowercase ifAbsent: [nil]! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20: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: 'tpr 3/26/2002 18:09'! recursiveDelete "Delete the this directory, recursing down its tree." self directoryNames do: [:dn | (self directoryNamed: dn) recursiveDelete]. self deleteLocalFiles. "should really be some exception handling for directory deletion, but no support for it yet" self containingDirectory deleteDirectory: self localName! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20:31'! rename: oldFileName toBe: newFileName | selection oldName newName | "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" oldName := self fullNameFor: oldFileName. newName := self fullNameFor: newFileName. (StandardFileStream retryWithGC:[self primRename: oldName asVmPathName to: newName asVmPathName] until:[:result| result notNil] forFileNamed: oldName) ~~ 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: 'yo 2/24/2005 18:34'! 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) asVmPathName type: typeString convertToSystemString creator: creatorString convertToSystemString. ! ! !FileDirectory methodsFor: 'file operations' stamp: 'stephaneducasse 2/4/2006 20:31'! upLoadProject: projectFile named: destinationFileName resourceUrl: resUrl 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." | result | result := self putFile: projectFile named: destinationFileName. [self setMacFileNamed: destinationFileName type: 'SOBJ' creator: 'FAST'] on: Error do: [ "ignore" ]. ^result! ! !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: 'stephaneducasse 2/4/2006 20:31'! 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 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: 'rop 3/14/2004 13:52'! oldFileOrNoneNamed: localFileName "If the file exists, answer a read-only FileStream on it. If it doesn't, answer nil." ^ FileStream concreteStream oldFileOrNoneNamed: (self fullNameFor: localFileName) ! ! !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: 'path access' stamp: 'yo 12/19/2003 21:15'! fullPathFor: path ^path isEmpty ifTrue:[pathName asSqueakPathName] 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: 'stephaneducasse 2/4/2006 20:31'! pathFromUrl: aFileUrl | first | ^String streamContents: [ :s | first := false. aFileUrl path do: [ :p | first ifTrue: [ s nextPut: self pathNameDelimiter ]. first := true. s nextPutAll: p ] ].! ! !FileDirectory methodsFor: 'path access' stamp: 'yo 12/19/2003 21:15'! pathName "Return the path from the root of the file system to this directory." ^ pathName asSqueakPathName. ! ! !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: 'yo 12/19/2003 21:15'! pathParts "Return the path from the root of the file system to this directory as an array of directory names." ^ pathName asSqueakPathName findTokens: self pathNameDelimiter asString! ! !FileDirectory methodsFor: 'path access' stamp: 'ar 12/18/1999 00:36'! slash ^self class slash! ! !FileDirectory methodsFor: 'printing' stamp: 'yo 12/19/2003 21:15'! printOn: aStream "Refer to the comment in Object|printOn:." aStream nextPutAll: self class name. aStream nextPutAll: ' on '. pathName asSqueakPathName printOn: aStream. ! ! !FileDirectory methodsFor: 'school support' stamp: 'ar 9/5/2001 16:09'! eToyBaseFolderSpec ^ServerDirectory eToyBaseFolderSpecForFileDirectory: self! ! !FileDirectory methodsFor: 'school support' stamp: 'ar 9/5/2001 16:09'! eToyBaseFolderSpec: aString ^ServerDirectory eToyBaseFolderSpecForFileDirectory: self put: aString! ! !FileDirectory methodsFor: 'school support' stamp: 'stephaneducasse 2/4/2006 20:31'! eToyUserList | spec index fd list match | spec := self eToyBaseFolderSpec. "something like 'U:\Squeak\users\*-Squeak'." spec ifNil:[^ServerDirectory eToyUserListForFileDirectory: self]. "Compute list of users based on base folder spec" index := spec indexOf: $*. "we really need one" index = 0 ifTrue:[^ServerDirectory eToyUserListForFileDirectory: self]. fd := FileDirectory on: (FileDirectory dirPathFor: (spec copyFrom: 1 to: index)). "reject all non-directories" list := fd entries select:[:each| each isDirectory]. "reject all non-matching entries" match := spec copyFrom: fd pathName size + 2 to: spec size. list := list collect:[:each| each name]. list := list select:[:each| match match: each]. "extract the names (e.g., those positions that match '*')" index := match indexOf: $*. list := list collect:[:each| each copyFrom: index to: each size - (match size - index)]. ^list! ! !FileDirectory methodsFor: 'school support' stamp: 'ar 9/5/2001 15:41'! eToyUserListUrl ^ServerDirectory eToyUserListUrlForFileDirectory: self! ! !FileDirectory methodsFor: 'school support' stamp: 'ar 9/5/2001 15:48'! eToyUserListUrl: urlString ^ServerDirectory eToyUserListUrlForFileDirectory: self put: urlString.! ! !FileDirectory methodsFor: 'school support' stamp: 'stephaneducasse 2/4/2006 20:31'! eToyUserName: aString "Set the default directory from the given user name" | dirName | dirName := self eToyBaseFolderSpec. "something like 'U:\Squeak\users\*-Squeak'" dirName ifNil:[^self]. dirName := dirName copyReplaceAll:'*' with: aString. " dirName last = self class pathNameDelimiter ifFalse:[dirName := dirName, self slash]. FileDirectory setDefaultDirectoryFrom: dirName. dirName := dirName copyFrom: 1 to: dirName size - 1. " pathName := FilePath pathName: dirName! ! !FileDirectory methodsFor: 'school support' stamp: 'ar 9/5/2001 16:13'! hasEToyUserList ^self eToyUserListUrl notNil or:[self eToyBaseFolderSpec notNil]! ! !FileDirectory methodsFor: 'searching' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'stephaneducasse 2/4/2006 20:31'! 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: 'mir 6/17/2001 23:42'! downloadUrl ^''! ! !FileDirectory methodsFor: 'squeaklets' stamp: 'RAA 10/12/2000 17:18'! updateProjectInfoFor: aProject "only swiki servers for now"! ! !FileDirectory methodsFor: 'squeaklets' stamp: 'dgd 12/23/2003 16:21'! writeProject: aProject inFileNamed: fileNameString fromDirectory: localDirectory "write aProject (a file version can be found in the file named fileNameString in localDirectory)" aProject writeFileNamed: fileNameString fromDirectory: localDirectory toServer: self! ! !FileDirectory methodsFor: 'testing' stamp: 'mir 6/25/2001 13:08'! acceptsUploads ^true! ! !FileDirectory methodsFor: 'testing' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 | DirectoryClass splitName: filenameOrPath to: [:filePath :name | fName := name. filePath isEmpty ifTrue: [dir := self] ifFalse: [dir := self directoryNamed: filePath]]. ^dir exists and: [ self isCaseSensitive ifTrue:[dir directoryNames includes: fName] ifFalse:[dir directoryNames anySatisfy: [:name| name sameAs: fName]]]. ! ! !FileDirectory methodsFor: 'testing' stamp: 'stephaneducasse 2/4/2006 20:31'! exists "Answer whether the directory exists" | result | result := self primLookupEntryIn: pathName asVmPathName index: 1. ^ result ~= #badDirectoryPath ! ! !FileDirectory methodsFor: 'testing' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 | DirectoryClass 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: '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/30/2001 21:42'! isAFileNamed: fName ^FileStream isAFileNamed: (self fullNameFor: fName)! ! !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: 'testing' stamp: 'dgd 12/27/2003 10:46'! isRemoteDirectory "answer whatever the receiver is a remote directory" ^ false! ! !FileDirectory methodsFor: '*MorphicExtras-file name utilities' stamp: 'fbs 2/2/2005 13:22'! url "Convert my path into a file:// type url String." ^self asUrl asString.! ! !FileDirectory methodsFor: '*network-uri' stamp: 'mir 10/20/2003 16:03'! uri "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: $/]) asURI! ! !FileDirectory methodsFor: 'private' stamp: 'stephaneducasse 2/4/2006 20:31'! 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 f | entries := OrderedCollection new: 200. index := 1. done := false. f := fullPath asVmPathName. [done] whileFalse: [ entryArray := self primLookupEntryIn: f index: index. #badDirectoryPath = entryArray ifTrue: [ ^(InvalidDirectoryError pathName: pathName asSqueakPathName) signal]. entryArray == nil ifTrue: [done := true] ifFalse: [entries addLast: (DirectoryEntry fromArray: entryArray)]. index := index + 1]. ^ entries asArray collect: [:s | s convertFromSystemName]. ! ! !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: 'stephaneducasse 2/4/2006 20:31'! setPathName: pathString pathName := FilePath pathName: pathString. ! ! !FileDirectory methodsFor: 'private' stamp: 'mir 6/25/2001 18:05'! storeServerEntryOn: stream stream nextPutAll: 'name:'; tab; nextPutAll: self localName; cr; nextPutAll: 'directory:'; tab; nextPutAll: self pathName; cr; nextPutAll: 'type:'; tab; nextPutAll: 'file'; cr! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileDirectory class instanceVariableNames: ''! !FileDirectory class methodsFor: 'class initialization' stamp: 'stephaneducasse 2/4/2006 20:32'! initializeStandardMIMETypes "FileDirectory initializeStandardMIMETypes" StandardMIMEMappings := Dictionary new. #( (gif ('image/gif')) (pdf ('application/pdf')) (aiff ('audio/aiff')) (bmp ('image/bmp')) (png ('image/png')) (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. ].! ! !FileDirectory class methodsFor: 'create/delete file' stamp: 'stephaneducasse 2/4/2006 20:32'! deleteFilePath: fullPathToAFile "Delete the file after finding its directory" | dir | dir := self on: (self dirPathFor: fullPathToAFile). dir deleteFileNamed: (self localNameFor: fullPathToAFile). ! ! !FileDirectory class methodsFor: 'create/delete file' stamp: 'stephaneducasse 2/4/2006 20:32'! lookInUsualPlaces: fileName "Check the default directory, the imagePath, and the vmPath (and the vmPath's owner) for this file." | vmp | (FileDirectory default fileExists: fileName) ifTrue: [^ FileDirectory default fileNamed: fileName]. ((vmp := FileDirectory on: SmalltalkImage current imagePath) fileExists: fileName) ifTrue: [^ vmp fileNamed: fileName]. ((vmp := FileDirectory on: SmalltalkImage current vmPath) fileExists: fileName) ifTrue: [^ vmp fileNamed: fileName]. ((vmp := vmp containingDirectory) fileExists: fileName) ifTrue: [^ vmp fileNamed: fileName]. ^ nil! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'jm 12/4/97 19:24'! default "Answer the default directory." ^ DefaultDirectory ! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:32'! forFileName: aString | path | path := self dirPathFor: aString. path isEmpty ifTrue: [^ self default]. ^ self on: path ! ! !FileDirectory class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:32'! on: pathString "Return a new file directory for the given path, of the appropriate FileDirectory subclass for the current OS platform." | pathName | DirectoryClass ifNil: [self setDefaultDirectoryClass]. "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: 'stephaneducasse 2/4/2006 20:32'! baseNameFor: fileName "Return the given file name without its extension, if any. We have to remember that many (most?) OSs allow extension separators within directory names and so the leaf filename needs to be extracted, trimmed and rejoined. Yuck" "The test is FileDirectory baseNameFor: ((FileDirectory default directoryNamed: 'foo.bar') fullNameFor:'blim.blam') should end 'foo.bar/blim' (or as appropriate for your platform AND FileDirectory baseNameFor: ((FileDirectory default directoryNamed: 'foo.bar') fullNameFor:'blim') should be the same and NOT 'foo' Oh, and FileDirectory baseNameFor: 'foo.bar' should be 'foo' not '/foo' " | delim i leaf | self splitName: fileName to: [:path : fn| delim := DirectoryClass extensionDelimiter. i := fn findLast: [:c | c = delim]. leaf := i = 0 ifTrue: [fn] ifFalse: [fn copyFrom: 1 to: i - 1]. path isEmpty ifTrue:[^leaf]. ^path, self slash, leaf] ! ! !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: 'jf 2/7/2004 17:22'! checkName: fileName fixErrors: flag "Check a string fileName for validity as a file name on the current default file system. 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: fileName fixErrors: flag ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'tpr 10/13/2003 10:59'! dirPathFor: fullName "Return the directory part the given name." DirectoryClass splitName: fullName to: [:dirPath :localName | ^ dirPath]! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'ar 4/7/2002 15:47'! directoryEntryFor: filenameOrPath ^self default directoryEntryFor: filenameOrPath! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'stephaneducasse 2/4/2006 20:32'! 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: 'stephaneducasse 2/4/2006 20:32'! 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: 'tpr 10/13/2003 10:59'! localNameFor: fullName "Return the local part the given name." DirectoryClass splitName: fullName to: [:dirPath :localName | ^ localName]! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'stephaneducasse 2/4/2006 20:32'! 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: 'stephaneducasse 2/4/2006 20:32'! 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: 'name utilities' stamp: 'tpr 12/15/2003 12:03'! startUp "Establish the platform-specific FileDirectory subclass. Do any platform-specific startup." self setDefaultDirectoryClass. self setDefaultDirectory: (self dirPathFor: SmalltalkImage current imageName). Preferences startInUntrustedDirectory ifTrue:[ "The SecurityManager may override the default directory to prevent unwanted write access etc." self setDefaultDirectory: SecurityManager default untrustedUserDirectory. "Make sure we have a place to go to" DefaultDirectory assureExistence]. SmalltalkImage current openSourceFiles. ! ! !FileDirectory class methodsFor: 'name utilities' stamp: 'stephaneducasse 2/4/2006 20:32'! urlForFileNamed: aFilename "Create a URL for the given fully qualified file name" "FileDirectory urlForFileNamed: 'C:\Home\andreasr\Squeak\DSqueak3\DSqueak3:=1.1\DSqueak3.1.image' " | path localName | DirectoryClass splitName: aFilename to: [:p :n | path := p. localName := n]. ^ localName asUrlRelativeTo: (self on: path) url asUrl! ! !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: 'nk 3/13/2003 10:58'! makeAbsolute: path "Ensure that path looks like an absolute path" ^path first = self pathNameDelimiter ifTrue: [ path ] ifFalse: [ self slash, path ]! ! !FileDirectory class methodsFor: 'platform specific' stamp: 'nk 3/13/2003 10:59'! makeRelative: path "Ensure that path looks like an relative path" ^path first = self pathNameDelimiter ifTrue: [ path copyWithoutFirst ] ifFalse: [ path ]! ! !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: 'system start up' stamp: 'stephaneducasse 2/4/2006 20:32'! openChanges: changesName forImage: imageName "find the changes file by looking in a) the directory derived from the image name b) the DefaultDirectory (which will normally be the directory derived from the image name or the SecurityManager's choice) If an old file is not found in either place, check for a read-only file in the same places. If that fails, return nil" | changes fd | "look for the changes file or an alias to it in the image directory" fd := FileDirectory on: (FileDirectory dirPathFor: imageName). (fd fileExists: changesName) ifTrue: [changes := fd oldFileNamed: changesName]. changes ifNotNil:[^changes]. "look for the changes in the default directory" fd := DefaultDirectory. (fd fileExists: changesName) ifTrue: [changes := fd oldFileNamed: changesName]. changes ifNotNil:[^changes]. "look for read-only changes in the image directory" fd := FileDirectory on: (FileDirectory dirPathFor: imageName). (fd fileExists: changesName) ifTrue: [changes := fd readOnlyFileNamed: changesName]. changes ifNotNil:[^changes]. "look for read-only changes in the default directory" fd := DefaultDirectory. (fd fileExists: changesName) ifTrue: [changes := fd readOnlyFileNamed: changesName]. "this may be nil if the last try above failed to open a file" ^changes ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'stephaneducasse 2/4/2006 20:32'! openSources: sourcesName andChanges: changesName forImage: imageName "Open the changes and sources files and install them in SourceFiles. Inform the user of problems regarding write permissions or CR/CRLF mixups." "Note: SourcesName and imageName are full paths; changesName is a local name." | sources changes msg wmsg | msg := 'Squeak cannot locate &fileRef. Please check that the file is named properly and is in the same directory as this image.'. 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.'. sources := self openSources: sourcesName forImage: imageName. changes := self openChanges: changesName forImage: imageName. ((sources == nil or: [sources atEnd]) and: [Preferences valueOfFlag: #warnIfNoSourcesFile]) ifTrue: [SmalltalkImage current platformName = 'Mac OS' ifTrue: [msg := msg , ' Make sure the sources file is not an Alias.']. self inform: (msg copyReplaceAll: '&fileRef' with: 'the sources file named ' , sourcesName)]. (changes == nil and: [Preferences valueOfFlag: #warnIfNoChangesFile]) ifTrue: [self inform: (msg copyReplaceAll: '&fileRef' with: 'the changes file named ' , changesName)]. ((Preferences valueOfFlag: #warnIfNoChangesFile) and: [changes notNil]) ifTrue: [changes isReadOnly ifTrue: [self inform: (wmsg copyReplaceAll: '&fileRef' with: 'the changes file named ' , changesName)]. ((changes next: 200) includesSubString: String crlf) ifTrue: [self inform: '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: 'stephaneducasse 2/4/2006 20:32'! openSources: fullSourcesName forImage: imageName "We first do a check to see if a compressed version ofthe sources file is present. Open the .sources file read-only after searching in: a) the directory where the VM lives b) the directory where the image came from c) the DefaultDirectory (which is likely the same as b unless the SecurityManager has changed it). " | sources fd sourcesName | (fullSourcesName endsWith: 'sources') ifTrue: ["Look first for a sources file in compressed format." sources := self openSources: (fullSourcesName allButLast: 7) , 'stc' forImage: imageName. sources ifNotNil: [^ CompressedSourceStream on: sources]]. sourcesName := FileDirectory localNameFor: fullSourcesName. "look for the sources file or an alias to it in the VM's directory" fd := FileDirectory on: SmalltalkImage current vmPath. (fd fileExists: sourcesName) ifTrue: [sources := fd readOnlyFileNamed: sourcesName]. sources ifNotNil: [^ sources]. "look for the sources file or an alias to it in the image directory" fd := FileDirectory on: (FileDirectory dirPathFor: imageName). (fd fileExists: sourcesName) ifTrue: [sources := fd readOnlyFileNamed: sourcesName]. sources ifNotNil: [^ sources]. "look for the sources in the current directory" fd := DefaultDirectory. (fd fileExists: sourcesName) ifTrue: [sources := fd readOnlyFileNamed: sourcesName]. "sources may still be nil here" ^sources ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'stephaneducasse 2/4/2006 20:32'! setDefaultDirectory: directoryName "Initialize the default directory to the directory supplied. This method is called when the image starts up." | dirName | DirectoryClass := self activeDirectoryClass. dirName := (FilePath pathName: directoryName) asSqueakPathName. [dirName endsWith: self slash] whileTrue:[ dirName := dirName copyFrom: 1 to: dirName size - self slash size. ]. DefaultDirectory := self on: dirName.! ! !FileDirectory class methodsFor: 'system start up' stamp: 'stephaneducasse 2/4/2006 20:32'! setDefaultDirectoryClass "Initialize the default directory class to suit this platform. This method is called when the image starts up - it needs to be right at the front of the list of the startup sequence" DirectoryClass := self activeDirectoryClass ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'stephaneducasse 2/4/2006 20:32'! 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: (FilePath pathName: (self dirPathFor: imageName) isEncoded: true) asSqueakPathName. ! ! !FileDirectory class methodsFor: 'system start up' stamp: 'sd 11/16/2003 13:13'! shutDown SmalltalkImage current closeSourceFiles. ! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'mir 3/24/2005 19:06'! contentStreamForURI: aURI | fullPath fileDir | fullPath := self fullPathForURI: aURI. fileDir := self forFileName: fullPath. " ^fileDir readOnlyFileNamed: (self localNameFor: fullPath)" ^StandardFileStream readOnlyFileNamed: fullPath ! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'mir 3/24/2005 17:04'! directoryEntryForURI: aURI ^ self directoryEntryFor: (self fullPathForURI: aURI)! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'mir 6/20/2005 18:53'! fullPathForURI: aURI ^self activeDirectoryClass privateFullPathForURI: (FileDirectory default uri resolveRelativeURI: aURI)! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'mir 6/20/2005 18:53'! privateFullPathForURI: aURI ^(aURI path copyReplaceAll: '/' with: self slash) unescapePercents! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'tb 5/24/2006 22:01'! retrieveMIMEDocument: uri | file | file _ [self contentStreamForURI: uri] on: FileDoesNotExistException do:[:ex| ex return: nil]. file ifNotNil: [^MIMEDocument contentType: (MIMEDocument guessTypeFromName: uri) content: file contents url: uri]. ^nil! ! !FileDirectory class methodsFor: '*network-uri' stamp: 'mir 6/20/2005 18:34'! uri: aURI ^self on: (FileDirectory fullPathForURI: aURI)! ! !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 ! ! ClassTestCase subclass: #FileDirectoryTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Files-Tests'! !FileDirectoryTest methodsFor: 'create/delete tests' stamp: 'nk 11/13/2002 19:39'! deleteDirectory (self myDirectory exists) ifTrue: [self myDirectory containingDirectory deleteDirectory: self myLocalDirectoryName]! ! !FileDirectoryTest methodsFor: 'create/delete tests' stamp: 'stephaneducasse 2/4/2006 20:31'! testDeleteDirectory "Test deletion of a directory" | aContainingDirectory preTestItems | aContainingDirectory := self myDirectory containingDirectory. preTestItems := aContainingDirectory fileAndDirectoryNames. self assert: self myAssuredDirectory exists. aContainingDirectory deleteDirectory: self myLocalDirectoryName. self shouldnt: [aContainingDirectory directoryNames includes: self myLocalDirectoryName ] description: 'Should successfully delete directory.'. self should: [preTestItems = aContainingDirectory fileAndDirectoryNames] description: 'Should only delete the indicated directory.'. ! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'aka 5/20/2003 16:43'! testAttemptExistenceCheckWhenFile "How should a FileDirectory instance respond with an existent file name?" | directory | FileDirectory default forceNewFileNamed: 'aTestFile'. directory := FileDirectory default directoryNamed: 'aTestFile'. self shouldnt: [directory exists] description: 'Files are not directories.'.! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'aka 5/20/2003 23:33'! testDirectoryExists self assert: self myAssuredDirectory exists. self should: [self myDirectory containingDirectory directoryExists: self myLocalDirectoryName]. self myDirectory containingDirectory deleteDirectory: self myLocalDirectoryName. self shouldnt: [self myDirectory containingDirectory directoryExists: self myLocalDirectoryName]! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'svp 5/20/2003 17:14'! testDirectoryExistsWhenLikeNamedFileExists | testFileName | [testFileName := self myAssuredDirectory fullNameFor: 'zDirExistsTest.testing'. (FileStream newFileNamed: testFileName) close. self should: [FileStream isAFileNamed: testFileName]. self shouldnt: [(FileDirectory on: testFileName) exists]] ensure: [self myAssuredDirectory deleteFileNamed: 'zDirExistsTest.testing'] ! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'hg 2/2/2002 16:44'! testDirectoryNamed self should: [(self myDirectory containingDirectory directoryNamed: self myLocalDirectoryName) pathName = self myDirectory pathName]! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'tpr 8/15/2003 16:30'! testExists self should: [FileDirectory default exists] description: 'Should know default directory exists.'. self should: [self myAssuredDirectory exists] description: 'Should know created directory exists.'. self myDirectory containingDirectory deleteDirectory: self myLocalDirectoryName. self shouldnt: [(self myDirectory containingDirectory directoryNamed: self myLocalDirectoryName) exists] description: 'Should know that recently deleted directory no longer exists.'.! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'stephaneducasse 2/4/2006 20:31'! testNonExistentDirectory | directory parentDirectory | directory :=FileDirectory default directoryNamed: 'nonExistentFolder'. self shouldnt: [directory exists] description: 'A FileDirectory instance should know if it points to a non-existent directory.'. parentDirectory :=FileDirectory default. self shouldnt: [parentDirectory directoryExists: 'nonExistentFolder'] description: 'A FileDirectory instance should know when a directory of the given name doesn''t exist'. ! ! !FileDirectoryTest methodsFor: 'existence tests' stamp: 'CdG 10/19/2005 22:41'! testOldFileOrNoneNamed | file | file := self myAssuredDirectory oldFileOrNoneNamed: 'test.txt'. [self assert: file isNil. "Reproduction of Mantis #1049" (self myAssuredDirectory fileNamed: 'test.txt') nextPutAll: 'foo'; close. file := self myAssuredDirectory oldFileOrNoneNamed: 'test.txt'. self assert: file notNil] ensure: [ file ifNotNil: [file close]. self myAssuredDirectory deleteFileNamed: 'test.txt' ifAbsent: nil] ! ! !FileDirectoryTest methodsFor: 'resources' stamp: 'hg 2/2/2002 16:44'! myAssuredDirectory ^self myDirectory assureExistence! ! !FileDirectoryTest methodsFor: 'resources' stamp: 'hg 2/2/2002 16:42'! myDirectory ^FileDirectory default directoryNamed: self myLocalDirectoryName! ! !FileDirectoryTest methodsFor: 'resources' stamp: 'hg 2/2/2002 16:42'! myLocalDirectoryName ^'zTestDir'! ! !FileDirectoryTest methodsFor: 'resources' stamp: 'nk 11/13/2002 19:56'! tearDown [ self deleteDirectory ] on: Error do: [ :ex | ]! ! ListItemWrapper subclass: #FileDirectoryWrapper instanceVariableNames: 'itemName balloonText hasContents' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Explorer'! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 7/21/2000 11:01'! balloonText: aStringOrNil balloonText _ aStringOrNil! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'ar 2/12/2001 16:22'! directoryNamesFor: anItem ^model directoryNamesFor: anItem! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'sps 12/5/2002 16:59'! setItem: anObject name: aString model: aModel item _ anObject. model _ aModel. itemName _ aString. hasContents _ nil. ! ! !FileDirectoryWrapper methodsFor: 'as yet unclassified' stamp: 'RAA 6/16/2000 18:30'! settingSelector ^#setSelectedDirectoryTo:! ! !FileDirectoryWrapper methodsFor: 'converting' stamp: 'dgd 8/27/2004 18:45'! asString ^itemName translatedIfCorresponds! ! !FileDirectoryWrapper methodsFor: 'accessing' stamp: 'RAA 7/21/2000 11:00'! balloonText ^balloonText! ! !FileDirectoryWrapper methodsFor: 'accessing' stamp: 'ar 2/12/2001 16:20'! contents ^((model directoryNamesFor: item) sortBy: [ :a :b | a caseInsensitiveLessOrEqual: b]) collect: [ :n | FileDirectoryWrapper with: (item directoryNamed: n) name: n model: self ] ! ! !FileDirectoryWrapper methodsFor: 'accessing' stamp: 'tpr 11/28/2003 14:02'! hasContents "Return whether this directory has subfolders. The value is cached to avoid a performance penalty. Also for performance reasons, the code below will just assume that the directory does indeed have contents in a few of cases: 1. If the item is not a FileDirectory (thus avoiding the cost of refreshing directories that are not local) 2. If it's the root directory of a given volume 3. If there is an error computing the FileDirectory's contents " hasContents ifNil: [hasContents := true. "default" ["Best test I could think of for determining if this is a local directory " ((item isKindOf: FileDirectory) and: ["test to see that it's not the root directory" "there has to be a better way of doing this test -tpr" item pathParts size > 1]) ifTrue: [hasContents := self contents notEmpty]] on: Error do: [hasContents := true]]. ^ hasContents! ! !FileDirectoryWrapper methodsFor: 'accessing' stamp: 'dgd 9/26/2004 18:22'! icon "Answer a form to be used as icon" ^ item isRemoteDirectory ifTrue: [MenuIcons smallRemoteOpenIcon] ifFalse: [MenuIcons smallOpenIcon]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '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: 'fileClass' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! !FileExistsException methodsFor: 'accessing' stamp: 'LC 10/24/2001 21:49'! fileClass ^ fileClass ifNil: [StandardFileStream]! ! !FileExistsException methodsFor: 'accessing' stamp: 'LC 10/24/2001 21:42'! fileClass: aClass fileClass _ aClass! ! !FileExistsException methodsFor: 'exceptionDescription' stamp: 'LC 10/24/2001 21:50'! defaultAction "The default action taken if the exception is signaled." ^ self fileClass fileExistsUserHandling: self fileName ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileExistsException class instanceVariableNames: ''! !FileExistsException class methodsFor: 'exceptionInstantiator' stamp: 'LC 10/24/2001 21:50'! fileName: aFileName fileClass: aClass ^ self new fileName: aFileName; fileClass: aClass! ! StringHolder subclass: #FileList instanceVariableNames: 'fileName directory volList volListIndex list listIndex pattern sortMode brevityState' classVariableNames: 'FileReaderRegistry RecentDirs' poolDictionaries: '' category: 'Morphic-FileList'! !FileList commentStamp: 'nk 11/26/2002 11:52' 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. The FileList now provides a registration mechanism to which any tools the filelist uses ***MUST*** register. This way it is possible to dynamically load or unload a new tool and have the FileList automatically updated. This change supports a decomposition of Squeak and removes a problem with dead reference to classes after a major shrink. Tools should implement the following methods (look for implementors in the image): #fileReaderServicesForFile:suffix: (appropriate services for given file, takes a file name and a lowercased suffix) #services (all provided services, to be displayed in full list) These methods both return a collection of SimpleServiceEntry instances. These contain a class, a menu label and a method selector having one argument. They may also provide separate button labels and description. The argument to the specified method will be a string representing the full name of a file when one is selected or the file list itself when there is no selected file. Tools must register with the FileList calling the class method #registerFileReader: when they load. They also must call #unregisterFileReader: when they unload. There is a testSuite called FileListTest that presents some examples. Stef (I do not like really this distinction passing always a file list could be better) Old Comments: 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: 'drag''n''drop' stamp: 'nk 6/19/2003 10:08'! acceptDroppingMorph: aTransferMorph event: evt inMorph: dest | oldName oldEntry destDirectory newName newEntry baseName response | destDirectory _ self dropDestinationDirectory: dest event: evt. oldName _ aTransferMorph passenger. baseName _ FileDirectory localNameFor: oldName. newName _ destDirectory fullNameFor: baseName. newName = oldName ifTrue: [ "Transcript nextPutAll: 'same as old name'; cr." ^ true ]. oldEntry _ FileDirectory directoryEntryFor: oldName. newEntry _ FileDirectory directoryEntryFor: newName. newEntry ifNotNil: [ | msg | msg _ String streamContents: [ :s | s nextPutAll: 'destination file '; nextPutAll: newName; nextPutAll: ' exists already,'; cr; nextPutAll: 'and is '; nextPutAll: (oldEntry modificationTime < newEntry modificationTime ifTrue: [ 'newer' ] ifFalse: [ 'not newer' ]); nextPutAll: ' than source file '; nextPutAll: oldName; nextPut: $.; cr; nextPutAll: 'Overwrite file '; nextPutAll: newName; nextPut: $? ]. response _ self confirm: msg. response ifFalse: [ ^false ]. ]. aTransferMorph shouldCopy ifTrue: [ self primitiveCopyFileNamed: oldName to: newName ] ifFalse: [ directory rename: oldName toBe: newName ]. self updateFileList; fileListIndex: 0. aTransferMorph source model ~= self ifTrue: [ aTransferMorph source model updateFileList; fileListIndex: 0 ]. "Transcript nextPutAll: 'copied'; cr." ^true! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/14/2003 12:58'! dragPassengerFor: item inMorph: dragSource ^self directory fullNameFor: ((self fileNameFromFormattedItem: item contents copy) copyReplaceAll: self folderString with: ''). ! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/14/2003 11:16'! dragTransferTypeForMorph: aMorph ^#file! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/15/2003 13:07'! dropDestinationDirectory: dest event: evt "Answer a FileDirectory representing the drop destination in the volume list morph dest" | index dir delim path | index _ volList indexOf: (dest itemFromPoint: evt position) contents. index = 1 ifTrue: [dir _ FileDirectory on: ''] ifFalse: [delim _ directory pathNameDelimiter. path _ String streamContents: [:str | 2 to: index do: [:d | str nextPutAll: (volList at: d) withBlanksTrimmed. d < index ifTrue: [str nextPut: delim]]. nil]. dir _ directory on: path]. ^ dir! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/15/2003 21:58'! isDirectoryList: aMorph ^aMorph getListSelector == #volumeList! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/12/2004 16:17'! primitiveCopyFileNamed: srcName to: dstName "Copied from VMMaker code. This really ought to be a facility in file system. The major annoyance here is that file types and permissions are not handled by current Squeak code. NOTE that this will clobber the destination file!!" | buffer src dst | "primitiveExternalCall" "If the plugin doesn't do it, go the slow way and lose the filetype info" "This method may signal FileDoesNotExistException if either the source or dest files cannnot be opened; possibly permissions or bad name problems" [[src _ FileStream readOnlyFileNamed: srcName] on: FileDoesNotExistException do: [^ self error: ('could not open file ', srcName)]. [dst _ FileStream forceNewFileNamed: dstName] on: FileDoesNotExistException do: [^ self error: ('could not open file ', dstName)]. buffer _ String new: 50000. [src atEnd] whileFalse: [dst nextPutAll: (src nextInto: buffer)]] ensure: [src ifNotNil: [src close]. dst ifNotNil: [dst close]]! ! !FileList methodsFor: 'drag''n''drop' stamp: 'nk 6/19/2003 10:08'! wantsDroppedMorph: aTransferMorph event: evt inMorph: dest | retval | retval _ (aTransferMorph isKindOf: TransferMorph) and: [ aTransferMorph dragTransferType == #file ] and: [ self isDirectoryList: dest ]. "retval ifFalse: [ Transcript nextPutAll: 'drop not wanted'; cr ]." ^retval! ! !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: 'sw 2/17/2002 02:32'! 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. self updateButtonRow! ! !FileList methodsFor: 'file list' stamp: 'sd 2/14/2002 16:58'! fileName ^ fileName! ! !FileList methodsFor: 'file list' stamp: 'nk 4/29/2004 10:34'! readOnlyStream "Answer a read-only stream on the selected file. For the various stream-reading services." ^self directory ifNotNilDo: [ :dir | dir readOnlyFileNamed: self fileName ]! ! !FileList methodsFor: 'file list menu' stamp: 'RAA 2/2/2002 08:18'! dirAndFileName ^{directory. fileName}! ! !FileList methodsFor: 'file list menu' stamp: 'dgd 10/1/2004 13:55'! fileContentsMenu: aMenu shifted: shifted "Construct aMenu to have items appropriate for the file browser's code pane, given the shift state provided" | shiftMenu services maybeLine extraLines | shifted ifTrue: [shiftMenu _ ParagraphEditor shiftedYellowButtonMenu. ^ aMenu addAllFrom: shiftMenu]. fileName ifNotNil: [services _ OrderedCollection new. (#(briefHex briefFile needToGetBriefHex needToGetBrief) includes: brevityState) ifTrue: [services add: self serviceGet]. (#(fullHex briefHex needToGetFullHex needToGetBriefHex) includes: brevityState) ifFalse: [services add: self serviceGetHex]. (#(needToGetShiftJIS needToGetEUCJP needToGetCNGB needToGetEUCKR needToGetUTF8) includes: brevityState) ifFalse: [services add: self serviceGetEncodedText]. maybeLine _ services size. (FileStream sourceFileSuffixes includes: self suffixOfSelectedFile) ifTrue: [services addAll: (self servicesFromSelectorSpecs: #(fileIntoNewChangeSet: fileIn: browseChangesFile: browseFile:))]. extraLines _ OrderedCollection new. maybeLine > 0 ifTrue: [extraLines add: maybeLine]. services size > maybeLine ifTrue: [extraLines add: services size]. aMenu addServices: services for: self fullName extraLines: extraLines]. aMenu addList: { {'find...(f)' translated. #find}. {'find again (g)' translated. #findAgain}. {'set search string (h)' translated. #setSearchString}. #-. {'do again (j)' translated. #again}. {'undo (z)' translated. #undo}. #-. {'copy (c)' translated. #copySelection}. {'cut (x)' translated. #cut}. {'paste (v)' translated. #paste}. {'paste...' translated. #pasteRecent}. #-. {'do it (d)' translated. #doIt}. {'print it (p)' translated. #printIt}. {'inspect it (i)' translated. #inspectIt}. {'fileIn selection (G)' translated. #fileItIn}. #-. {'accept (s)' translated. #accept}. {'cancel (l)' translated. #cancel}. #-. {'more...' translated. #shiftedYellowButtonActivity}}. ^ aMenu ! ! !FileList methodsFor: 'file list menu' stamp: 'LEG 10/24/2001 15:37'! fileListMenu: aMenu fileName ifNil: [^ self noFileSelectedMenu: aMenu] ifNotNil: [^ self fileSelectedMenu: aMenu]. ! ! !FileList methodsFor: 'file list menu' stamp: 'nk 11/16/2002 13:00'! fileSelectedMenu: aMenu | firstItems secondItems thirdItems n1 n2 n3 services | firstItems _ self itemsForFile: self fullName. secondItems _ self itemsForAnyFile. thirdItems _ self itemsForNoFile. n1 _ firstItems size. n2 _ n1 + secondItems size. n3 _ n2 + thirdItems size. services _ firstItems, secondItems, thirdItems, self serviceAllFileOptions. services do: [ :svc | svc addDependent: self ]. ^ aMenu addServices2: services for: self extraLines: (Array with: n1 with: n2 with: n3) ! ! !FileList methodsFor: 'file list menu' stamp: 'em 3/29/2005 12:25'! fullFileListMenu: aMenu shifted: aBoolean "Fill the menu with all possible items for the file list pane, regardless of selection." | lastProvider | aMenu title: 'all possible file operations' translated. Smalltalk isMorphic ifTrue: [aMenu addStayUpItemSpecial]. lastProvider _ nil. (self itemsForFile: 'a.*') do: [ :svc | (lastProvider notNil and: [svc provider ~~ lastProvider]) ifTrue: [ aMenu addLine ]. svc addServiceFor: self toMenu: aMenu. Smalltalk isMorphic ifTrue: [aMenu submorphs last setBalloonText: svc description]. lastProvider _ svc provider. svc addDependent: self. ]. ^aMenu! ! !FileList methodsFor: 'file list menu' stamp: 'sw 11/8/2003 13:32'! itemsForAnyFile "Answer a list of universal services that could apply to any file" | services | services := OrderedCollection new: 4. services add: self serviceCopyName. services add: self serviceRenameFile. services add: self serviceDeleteFile. services add: self serviceViewContentsInWorkspace. ^ services! ! !FileList methodsFor: 'file list menu' stamp: 'nk 6/12/2004 12:05'! itemsForDirectory: dir | services | services := OrderedCollection new. dir ifNotNil: [ services addAll: (self class itemsForDirectory: dir). services last useLineAfter: true. ]. services add: self serviceAddNewFile. services add: self serviceAddNewDirectory. ^ services! ! !FileList methodsFor: 'file list menu' stamp: 'nk 12/7/2002 12:56'! itemsForFile: fullName "Answer a list of services appropriate for a file of the given full name" | suffix | suffix _ self class suffixOf: fullName. ^ (self class itemsForFile: fullName) , (self myServicesForFile: fullName suffix: suffix)! ! !FileList methodsFor: 'file list menu' stamp: 'nk 6/12/2004 12:06'! itemsForNoFile | services | services := OrderedCollection new. services add: self serviceSortByName. services add: self serviceSortBySize. services add: (self serviceSortByDate useLineAfter: true). services addAll: (self itemsForDirectory: (self isFileSelected ifFalse: [ self directory ] ifTrue: [])). ^ services ! ! !FileList methodsFor: 'file list menu' stamp: 'sd 2/6/2002 21:25'! myServicesForFile: fullName suffix: suffix ^(FileStream isSourceFileSuffix: suffix) ifTrue: [ {self serviceBroadcastUpdate} ] ifFalse: [ #() ]! ! !FileList methodsFor: 'file list menu' stamp: 'SD 11/8/2001 20:34'! noFileSelectedMenu: aMenu ^ aMenu addServices: self itemsForNoFile for: self extraLines: #() ! ! !FileList methodsFor: 'file list menu' stamp: 'sw 2/27/2001 13:52'! offerAllFileOptions "Put up a menu offering all possible file options, whatever the suffix of the current selection may be. Specially useful if you're wanting to keep the menu up" self offerMenuFrom: #fullFileListMenu:shifted: shifted: true! ! !FileList methodsFor: 'file list menu' stamp: 'yo 11/14/2002 15:04'! 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 _ (MultiByteBinaryOrTextStream with: ((FileStream readOnlyFileNamed: self fullName) binary contentsOfEntireFile)) binary reset. morphOrList _ aFileStream fileInObjectAndCode. (morphOrList isKindOf: SqueakPage) ifTrue: [morphOrList _ morphOrList contentsMorph]. Smalltalk isMorphic ifTrue: [ActiveWorld addMorphsAndModel: morphOrList] ifFalse: [morphOrList isMorph ifFalse: [^ self errorMustBeMorph]. morphOrList openInWorld]! ! !FileList methodsFor: 'file list menu' stamp: 'nk 12/7/2002 12:57'! suffixOfSelectedFile "Answer the file extension of the receiver's selected file" ^ self class suffixOf: self fullName.! ! !FileList methodsFor: 'file menu action' stamp: 'rbb 3/1/2005 10:53'! 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 := UIManager default request: ('New {1} Name?' translated format: {aString translated}) initialAnswer: ('{1}Name' translated format: {aString translated})) 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 menu action' stamp: 'sge 11/28/1999 09:04'! addNewDirectory self addNew: 'Directory' byEvaluating: [:newName | directory createDirectory: newName] ! ! !FileList methodsFor: 'file menu action' stamp: 'sge 11/28/1999 09:04'! addNewFile self addNew: 'File' byEvaluating: [:newName | (directory newFileNamed: newName) close] ! ! !FileList methodsFor: 'file menu action' stamp: 'ka 8/3/2001 21:12'! compressFile "Compress the currently selected file" | f | f _ StandardFileStream readOnlyFileNamed: (directory fullNameFor: self fullName). f compressFile. self updateFileList! ! !FileList methodsFor: 'file menu action' stamp: 'dgd 9/21/2003 17:37'! deleteFile "Delete the currently selected file" listIndex = 0 ifTrue: [^ self]. (self confirm: ('Really delete {1}?' translated format:{fileName})) ifFalse: [^ self]. directory deleteFileNamed: fileName. self updateFileList. brevityState _ #FileList. self get! ! !FileList methodsFor: 'file menu action' 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 menu action' stamp: 'yo 3/31/2003 11:25'! getEncodedText Cursor read showWhile: [ self selectEncoding. self changed: #contents]. ! ! !FileList methodsFor: 'file menu action' 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 menu action' stamp: 'rbb 3/1/2005 10:53'! renameFile "Rename the currently selected file" | newName response | listIndex = 0 ifTrue: [^ self]. self okToChange ifFalse: [^ self]. (response := UIManager default request: 'NewFileName?' translated 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 menu action' stamp: 'di 4/15/98 12:36'! sortByDate self resort: #date! ! !FileList methodsFor: 'file menu action' stamp: 'di 4/15/98 12:37'! sortByName self resort: #name! ! !FileList methodsFor: 'file menu action' stamp: 'di 4/15/98 12:36'! sortBySize self resort: #size! ! !FileList methodsFor: 'file menu action' stamp: 'sd 2/1/2002 20:02'! spawn: code "Open a simple Edit window" listIndex = 0 ifTrue: [^ self]. self class openEditorOn: (directory readOnlyFileNamed: fileName) "read only just for initial look" editString: code! ! !FileList methodsFor: 'initialization' stamp: 'sw 11/30/2002 00:05'! buttonSelectorsToSuppress "Answer a list of action selectors whose corresponding services we would prefer *not* to have appear in the filelist's button pane; this can be hand-jimmied to suit personal taste." ^ #(removeLineFeeds: addFileToNewZip: compressFile: putUpdate:)! ! !FileList methodsFor: 'initialization' stamp: 'BG 12/13/2002 15:32'! 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]. volListIndex := volList size. self changed: #relabel. self changed: #volumeList. self pattern: pattern! ! !FileList methodsFor: 'initialization' stamp: 'sw 2/26/2002 00:37'! dynamicButtonServices "Answer services for buttons that may come and go in the button pane, depending on selection" ^ fileName isEmptyOrNil ifTrue: [#()] ifFalse: [ | toReject | toReject _ self buttonSelectorsToSuppress. (self itemsForFile: self fullName) reject: [:svc | toReject includes: svc selector]]! ! !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: 'nk 1/19/2005 13:25'! modelWakeUp "User has entered or expanded the window -- reopen any remote connection." (directory notNil and:[directory isRemoteDirectory]) ifTrue: [[directory wakeUp] on: TelnetProtocolError do: [ :ex | self inform: ex printString ]] "It would be good to implement a null method wakeUp on the root of directory"! ! !FileList methodsFor: 'initialization' stamp: 'sbw 12/30/1999 15:53'! optionalButtonHeight ^ 15! ! !FileList methodsFor: 'initialization' stamp: 'sw 11/30/2002 14:36'! optionalButtonRow "Answer the button row associated with a file list" | aRow | aRow _ AlignmentMorph newRow beSticky. aRow color: Color transparent. aRow clipSubmorphs: true. aRow layoutInset: 5@1; cellInset: 6. self universalButtonServices do: "just the three sort-by items" [:service | aRow addMorphBack: (service buttonToTriggerIn: self). (service selector == #sortBySize) ifTrue: [aRow addTransparentSpacerOfSize: (4@0)]]. aRow setNameTo: 'buttons'. aRow setProperty: #buttonRow toValue: true. "Used for dynamic retrieval later on" ^ aRow! ! !FileList methodsFor: 'initialization' stamp: 'sw 2/17/2002 00:07'! optionalButtonSpecs "Answer a list of services underlying the optional buttons in their initial inception." ^ {self serviceSortByName. self serviceSortByDate. self serviceSortBySize}! ! !FileList methodsFor: 'initialization' stamp: 'sw 2/17/2002 05:39'! optionalButtonView "Answer a view of optional buttons" | aView bHeight windowWidth offset previousView aButtonView wid services sel allServices | aView _ View new model: self. bHeight _ self optionalButtonHeight. windowWidth _ 120. aView window: (0 @ 0 extent: windowWidth @ bHeight). offset _ 0. allServices _ self universalButtonServices. services _ allServices copyFrom: 1 to: (allServices size min: 5). previousView _ nil. services do: [:service | sel _ service selector. aButtonView _ sel asString numArgs = 0 ifTrue: [PluggableButtonView on: service provider getState: (service extraSelector == #none ifFalse: [service extraSelector]) action: sel] ifFalse: [PluggableButtonView on: service provider getState: (service extraSelector == #none ifFalse: [service extraSelector]) action: sel getArguments: #fullName from: self]. service selector = services last selector ifTrue: [wid _ windowWidth - offset] ifFalse: [aButtonView borderWidthLeft: 0 right: 1 top: 0 bottom: 0. wid _ windowWidth // services size - 2]. aButtonView label: service buttonLabel asParagraph; window: (offset @ 0 extent: wid @ bHeight). offset _ offset + wid. service selector = services first selector 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: 'initialization' stamp: 'sw 2/17/2002 05:38'! universalButtonServices "Answer a list of services underlying the universal buttons in their initial inception. For the moment, only the sorting buttons are shown." ^ {self serviceSortByName. self serviceSortByDate. self serviceSortBySize}! ! !FileList methodsFor: 'initialization' stamp: 'gm 2/16/2003 20:38'! updateButtonRow "Dynamically update the contents of the button row, if any." | aWindow aRow | Smalltalk isMorphic ifFalse: [^self]. aWindow := self dependents detect: [:m | (m isSystemWindow) and: [m model == self]] ifNone: [^self]. aRow := aWindow findDeepSubmorphThat: [:m | m hasProperty: #buttonRow] ifAbsent: [^self]. aRow submorphs size - 4 timesRepeat: [aRow submorphs last delete]. self dynamicButtonServices do: [:service | aRow addMorphBack: (service buttonToTriggerIn: self). service addDependent: self]! ! !FileList methodsFor: 'menu messages' stamp: 'ar 1/15/2001 18:38'! copyName listIndex = 0 ifTrue: [^ self]. Clipboard clipboardText: self fullName asText. ! ! !FileList methodsFor: 'menu messages' stamp: 'sw 11/30/2002 15:38'! perform: selector orSendTo: otherTarget "Selector was just chosen from a menu by a user. If it's one of the three sort-by items, handle it specially. If I can respond myself, then perform it on myself. If not, send it to otherTarget, presumably the editPane from which the menu was invoked." ^ (#(sortByDate sortBySize sortByName) includes: selector) ifTrue: [self resort: selector] ifFalse: [(#(get getHex copyName openImageInWindow importImage renameFile deleteFile addNewFile) includes: selector) ifTrue: [self perform: selector] ifFalse: [super perform: selector orSendTo: otherTarget]]! ! !FileList methodsFor: 'own services' stamp: 'sw 2/15/2002 19:07'! serviceAddNewDirectory "Answer a service entry characterizing the 'add new directory' command" ^ SimpleServiceEntry provider: self label: 'add new directory' selector: #addNewDirectory description: 'adds a new, empty directory (folder)' ! ! !FileList methodsFor: 'own services' stamp: 'sw 2/11/2002 23:36'! serviceAddNewFile "Answer a service entry characterizing the 'add new file' command" ^ SimpleServiceEntry provider: self label: 'add new file' selector: #addNewFile description: 'create a new,. empty file, and add it to the current directory.'! ! !FileList methodsFor: 'own services' stamp: 'sd 1/31/2002 22:12'! serviceAllFileOptions ^ {SimpleServiceEntry provider: self label: 'more...' selector: #offerAllFileOptions description: 'show all the options available'}! ! !FileList methodsFor: 'own services' stamp: 'sw 2/17/2002 01:36'! serviceBroadcastUpdate "Answer a service for broadcasting a file as an update" ^ SimpleServiceEntry provider: self label: 'broadcast as update' selector: #putUpdate: description: 'broadcast file as update' buttonLabel: 'broadcast'! ! !FileList methodsFor: 'own services' stamp: 'sw 2/17/2002 02:36'! serviceCompressFile "Answer a service for compressing a file" ^ SimpleServiceEntry provider: self label: 'compress' selector: #compressFile description: 'compress file' buttonLabel: 'compress'! ! !FileList methodsFor: 'own services' stamp: 'sd 1/31/2002 22:16'! serviceCopyName ^ (SimpleServiceEntry provider: self label: 'copy name to clipboard' selector: #copyName description:'copy name to clipboard' )! ! !FileList methodsFor: 'own services' stamp: 'sd 1/31/2002 21:17'! serviceDeleteFile ^ (SimpleServiceEntry provider: self label: 'delete' selector: #deleteFile) description: 'delete the seleted item'! ! !FileList methodsFor: 'own services' stamp: 'sw 2/16/2002 01:39'! serviceGet "Answer a service for getting the entire file" ^ (SimpleServiceEntry provider: self label: 'get entire file' selector: #get description: 'if the file has only been partially read in, because it is very large, read the entire file in at this time.')! ! !FileList methodsFor: 'own services' stamp: 'yo 3/31/2003 11:24'! serviceGetEncodedText ^ (SimpleServiceEntry provider: self label: 'view as encoded text' selector: #getEncodedText description: 'view as encoded text') ! ! !FileList methodsFor: 'own services' stamp: 'sd 2/1/2002 20:50'! serviceGetHex ^ (SimpleServiceEntry provider: self label: 'view as hex' selector: #getHex description: 'view as hex') ! ! !FileList methodsFor: 'own services' stamp: 'sd 1/31/2002 22:15'! serviceRenameFile ^ (SimpleServiceEntry provider: self label: 'rename' selector: #renameFile description: 'rename file')! ! !FileList methodsFor: 'own services' stamp: 'sw 2/16/2002 01:39'! serviceSortByDate "Answer a service for sorting by date" ^ (SimpleServiceEntry new provider: self label: 'by date' selector: #sortByDate description: 'sort entries by date') extraSelector: #sortingByDate; buttonLabel: 'date'! ! !FileList methodsFor: 'own services' stamp: 'sw 2/16/2002 01:39'! serviceSortByName "Answer a service for soring by name" ^ (SimpleServiceEntry new provider: self label: 'by name' selector: #sortByName description: 'sort entries by name') extraSelector: #sortingByName; buttonLabel: 'name'! ! !FileList methodsFor: 'own services' stamp: 'sw 2/16/2002 01:40'! serviceSortBySize "Answer a service for sorting by size" ^ (SimpleServiceEntry provider: self label: 'by size' selector: #sortBySize description: 'sort entries by size') extraSelector: #sortingBySize; buttonLabel: 'size'! ! !FileList methodsFor: 'own services' stamp: 'sw 11/8/2003 13:34'! serviceViewContentsInWorkspace "Answer a service for viewing the contents of a file in a workspace" ^ (SimpleServiceEntry provider: self label: 'workspace with contents' selector: #viewContentsInWorkspace) description: 'open a new Workspace whose contents are set to the contents of this file'! ! !FileList methodsFor: 'own services' stamp: 'sw 2/15/2002 20:19'! servicesFromSelectorSpecs: symbolArray "Answer an array of services represented by the incoming symbols, eliminating any that do not have a currently-registered service. Pass the symbol #- along unchanged to serve as a separator between services" "FileList new servicesFromSelectorSpecs: #(fileIn: fileIntoNewChangeSet: browseChangesFile:)" | res services col | col := OrderedCollection new. services := self class allRegisteredServices, (self myServicesForFile: #dummy suffix: '*'). symbolArray do: [:sel | sel == #- ifTrue: [col add: sel] ifFalse: [res := services detect: [:each | each selector = sel] ifNone: [nil]. res notNil ifTrue: [col add: res]]]. ^ col! ! !FileList methodsFor: 'own services' stamp: 'edc 5/31/2006 07:37'! viewContentsInWorkspace "View the contents of my selected file in a new workspace" | aString aFileStream aName | aString := (aFileStream := directory readOnlyFileNamed: self fullName) setConverterForCode contentsOfEntireFile. aName := aFileStream localName. aFileStream close. UIManager default edit: aString label: 'Workspace from ', aName! ! !FileList methodsFor: 'server list' stamp: 'SD 11/10/2001 17:49'! 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 | self flag: #ViolateNonReferenceToOtherClasses. 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: 'server list' stamp: 'di 1/29/2002 21:45'! putUpdate: fullFileName "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 serverInGroupNamed: choice) putUpdate: (directory oldFileNamed: fullFileName). self volumeListIndex: volListIndex. ! ! !FileList methodsFor: 'server list' stamp: 'SD 11/10/2001 17:49'! removeServer | choice names | self flag: #ViolateNonReferenceToOtherClasses. names := ServerDirectory serverNames asSortedArray. choice := (SelectionMenu labelList: names selections: names) startUp. choice == nil ifTrue: [^ self]. ServerDirectory removeServerNamed: choice! ! !FileList methodsFor: 'updating' stamp: 'sw 11/30/2002 16:49'! update: aParameter "Receive a change notice from an object of whom the receiver is a dependent" (aParameter == #fileListChanged) ifTrue: [self updateFileList]. super update: aParameter! ! !FileList methodsFor: 'volume list and pattern' stamp: 'tpr 11/28/2003 11:44'! deleteDirectory "Remove the currently selected directory" | localDirName | directory entries size = 0 ifFalse:[^self inform:'Directory must be empty']. localDirName _ directory localName. (self confirm: 'Really delete ' , localDirName , '?') ifFalse: [^ self]. self volumeListIndex: self volumeListIndex-1. directory deleteDirectory: localDirName. self updateFileList.! ! !FileList methodsFor: 'volume list and pattern' stamp: 'SD 11/11/2001 13:59'! directory ^ directory! ! !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: '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: 'sw 2/21/2002 02:01'! 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. self updateButtonRow! ! !FileList methodsFor: 'volume menu' stamp: 'nk 6/12/2004 12:07'! volumeMenu: aMenu aMenu addList: { {'recent...' translated. #recentDirs}. #-. {'add server...' translated. #askServerInfo}. {'remove server...' translated. #removeServer}. #-. {'delete directory...' translated. #deleteDirectory}. #-}. aMenu addServices: (self itemsForDirectory: self directory) for: self extraLines: #(). ^aMenu.! ! !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: 'yo 7/5/2004 19:41'! 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" (TextConverter allEncodingNames includes: brevityState) ifTrue: [ ^self readContentsAsEncoding: brevityState]. self halt: 'unknown state ' , brevityState printString! ! !FileList methodsFor: 'private' stamp: 'dgd 12/27/2003 12:22'! defaultContents contents _ list == nil ifTrue: [String new] ifFalse: [String streamContents: [:s | s nextPutAll: 'NO FILE SELECTED' translated; cr. s nextPutAll: ' -- Folder Summary --' translated; cr. list do: [:item | s nextPutAll: item; cr]]]. brevityState _ #FileList. ^ contents! ! !FileList methodsFor: 'private' stamp: 'yo 7/6/2004 20:52'! defaultEncoderFor: aFileName "This method just illustrates the stupidest possible implementation of encoder selection." | l | l _ aFileName asLowercase. " ((l endsWith: FileStream multiCs) or: [ l endsWith: FileStream multiSt]) ifTrue: [ ^ UTF8TextConverter new. ]. " ((l endsWith: FileStream cs) or: [ l endsWith: FileStream st]) ifTrue: [ ^ MacRomanTextConverter new. ]. ^ Latin1TextConverter new. ! ! !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: 'rhi 9/8/2001 02:17'! fileNameFromFormattedItem: item "Extract fileName and folderString from a formatted fileList item string" | from to | self sortingByName ifTrue: [ from _ item lastIndexOf: $( ifAbsent: [0]. to _ item lastIndexOf: $) ifAbsent: [0]] ifFalse: [ from _ item indexOf: $( ifAbsent: [0]. to _ item indexOf: $) ifAbsent: [0]]. ^ (from * to = 0 ifTrue: [item] ifFalse: [item copyReplaceFrom: from to: to with: '']) withBlanksTrimmed! ! !FileList methodsFor: 'private'! folderString ^ ' [...]'! ! !FileList methodsFor: 'private' stamp: 'sw 1/7/2003 17:08'! fullName "Answer the full name for the currently selected file; answer nil if no file is selected." ^ fileName ifNotNil: [directory ifNil: [FileDirectory default fullNameFor: fileName] ifNotNil: [directory fullNameFor: fileName]] ! ! !FileList methodsFor: 'private' stamp: 'SD 11/14/2001 21:59'! isFileSelected "return if a file is currently selected" ^ fileName isNil not! ! !FileList methodsFor: 'private' stamp: 'nk 2/20/2001 12:36'! listForPatterns: anArray "Make the list be those file names which match the pattern." | sizePad newList | newList _ Set new. anArray do: [ :pat | newList addAll: (self entriesMatching: pat) ]. newList _ (SortedCollection sortBlock: self sortBlock) addAll: newList; yourself. 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: 'private' stamp: 'dgd 12/27/2003 12:24'! put: aText "Private - put the supplied text onto the file" | 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: [self inform: 'No fileName is selected' translated. ^ false "failed"]. type _ 'These'. brevityState = #briefFile ifTrue: [type _ 'Abbreviated']. brevityState = #briefHex ifTrue: [type _ 'Abbreviated']. brevityState = #fullHex ifTrue: [type _ 'Hexadecimal']. brevityState = #FileList ifTrue: [type _ 'Directory']. self inform: ('{1} contents cannot meaningfully be saved at present.' translated format:{type translated}). ^ false "failed" ! ! !FileList methodsFor: 'private' stamp: 'yo 3/14/2005 13:55'! readContentsAsEncoding: encodingName | f writeStream converter c | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. converter _ TextConverter defaultConverterClassForEncoding: encodingName. converter ifNil: [^ 'This encoding is not supported']. f converter: converter new. f wantsLineEndConversion: true. [f atEnd or: [(c _ f next) isNil]] whileFalse: [writeStream nextPut: c]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'tlk 11/13/2004 19:01'! 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 isRemoteDirectory ifTrue: [^ self readServerBrief]]. f := directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read' translated]. f converter: (self defaultEncoderFor: self fullName). (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 ''{1}'' is {2} bytes long. You may use the ''get'' command to read the entire file. Here are the first 5000 characters... ------------------------------------------ {3} ------------------------------------------ ... end of the first 5000 characters.' translated format: {fileName. fileSize. first5000}. brevityState := #briefFile. "don't change till actually read" ^ contents. ! ! !FileList methodsFor: 'private' stamp: 'ka 8/24/2000 18:55'! readContentsCNGB | f writeStream | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. f converter: CNGBTextConverter new. [f atEnd] whileFalse: [writeStream nextPut: f next]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'ka 8/24/2000 18:31'! readContentsEUCJP | f writeStream | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. f converter: EUCJPTextConverter new. [f atEnd] whileFalse: [writeStream nextPut: f next]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'ka 8/24/2000 18:56'! readContentsEUCKR | f writeStream | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. f converter: EUCKRTextConverter new. [f atEnd] whileFalse: [writeStream nextPut: f next]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'BG 3/16/2005 08:22'! 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' translated]. f binary. ((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 printStringHex; space; nextPut: $(; print: loc; nextPut: $); space; tab. loc+1 to: (loc+16 min: data size) do: [:i | s nextPutAll: (data at: i) printStringHex; space]. s cr]. hexData := s contents. ^ contents := ((size > 5000) & brevity ifTrue: ['File ''{1}'' is {2} bytes long. You may use the ''get'' command to read the entire file. Here are the first 5000 characters... ------------------------------------------ {3} ------------------------------------------ ... end of the first 5000 characters.' translated format: {fileName. size. hexData}] ifFalse: [hexData]). ! ! !FileList methodsFor: 'private' stamp: 'ka 8/26/2000 18:48'! readContentsShiftJIS | f writeStream | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. f converter: ShiftJISTextConverter new. [f atEnd] whileFalse: [writeStream nextPut: f next]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'ka 6/23/2002 15:55'! readContentsUTF8 | f writeStream | f _ directory oldFileOrNoneNamed: self fullName. f ifNil: [^ 'For some reason, this file cannot be read']. writeStream _ WriteStream on: String new. f converter: UTF8TextConverter new. [f atEnd] whileFalse: [writeStream nextPut: f next]. f close. ^ writeStream contents! ! !FileList methodsFor: 'private' stamp: 'dgd 12/27/2003 12:09'! 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' translated]. 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 ''{1}'' is {2} bytes long. You may use the ''get'' command to read the entire file. Here are the first 3500 characters... ------------------------------------------ {3} ------------------------------------------ ... end of the first 3500 characters.' translated format: {fileName. sizeStr. first5000}. 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: 'SD 11/8/2001 21:11'! registeredFileReaderClasses "return the list of classes that provide file reader services" ^ self class registeredFileReaderClasses! ! !FileList methodsFor: 'private' stamp: 'sw 11/30/2002 16:34'! 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]. self updateButtonRow ! ! !FileList methodsFor: 'private' stamp: 'mu 8/22/2003 01:46'! selectEncoding | aMenu encodingItems | aMenu _ CustomMenu new. encodingItems _ OrderedCollection new. TextConverter allSubclasses do: [:each | | names | names _ each encodingNames. names notEmpty ifTrue: [ | label | label _ '' writeStream. names do: [:eachName | label nextPutAll: eachName ] separatedBy: [ label nextPutAll: ', ']. encodingItems add: {label contents. names first asSymbol}. ]. ]. aMenu addList: encodingItems. brevityState _ aMenu startUp. brevityState ifNil: [brevityState _ #needToGetBrief]. ! ! !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: 'nk 12/10/2002 07:57'! updateFileList "Update my files list with file names in the current directory that match the pattern. The pattern string may have embedded newlines or semicolons; these separate different patterns." | patterns | patterns _ OrderedCollection new. Cursor wait showWhile: [ (pattern findTokens: (String with: Character cr with: Character lf with: $;)) do: [ :each | (each includes: $*) | (each includes: $#) ifTrue: [ patterns add: each] ifFalse: [each isEmpty ifTrue: [ patterns add: '*'] ifFalse: [ patterns add: '*' , each , '*']]]. list _ self listForPatterns: patterns. listIndex _ 0. volListIndex _ volList size. fileName _ nil. contents _ ''. self changed: #volumeListIndex. self changed: #fileList. self updateButtonRow]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileList class instanceVariableNames: ''! !FileList class methodsFor: 'class initialization' stamp: 'dvf 8/23/2003 12:17'! initialize "FileList initialize" RecentDirs := OrderedCollection new. (self systemNavigation allClassesImplementing: #fileReaderServicesForFile:suffix:) do: [:providerMetaclass | self registerFileReader: providerMetaclass soleInstance]! ! !FileList class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 12:47'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(FileList prototypicalToolWindow 'File List' 'A File List is a tool for browsing folders and files on disks and on ftp types.') forFlapNamed: 'Tools']! ! !FileList class methodsFor: 'class initialization' stamp: 'ar 9/27/2005 21:48'! removeObsolete "FileList removeObsolete" self registeredFileReaderClasses copy do:[:cls| cls isObsolete ifTrue:[self unregisterFileReader: cls]]! ! !FileList class methodsFor: 'class initialization' stamp: 'asm 4/08/2003 12:15'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !FileList class methodsFor: 'file reader registration' stamp: 'sd 2/1/2002 21:30'! allRegisteredServices "self allRegisteredServices" | col | col := OrderedCollection new. self registeredFileReaderClasses do: [:each | col addAll: (each services)]. ^ col! ! !FileList class methodsFor: 'file reader registration' stamp: 'sd 1/31/2002 21:42'! detectService: aBlock ifNone: anotherBlock "self detectService: [:each | each selector = #fileIn:] ifNone: [nil]" ^ self allRegisteredServices detect: aBlock ifNone: anotherBlock! ! !FileList class methodsFor: 'file reader registration' stamp: 'SD 11/11/2001 13:53'! isReaderNamedRegistered: aSymbol "return if a given reader class has been registered. Note that this is on purpose that the argument is a symbol and not a class" ^ (self registeredFileReaderClasses collect: [:each | each name]) includes: aSymbol ! ! !FileList class methodsFor: 'file reader registration' stamp: 'nk 6/12/2004 11:42'! itemsForDirectory: aFileDirectory "Answer a list of services appropriate when no file is selected." | services | services _ OrderedCollection new. self registeredFileReaderClasses do: [:reader | reader ifNotNil: [services addAll: (reader fileReaderServicesForDirectory: aFileDirectory) ]]. ^ services! ! !FileList class methodsFor: 'file reader registration' stamp: 'nk 12/7/2002 12:53'! itemsForFile: fullName "Answer a list of services appropriate for a file of the given full name" | services suffix | suffix _ self suffixOf: fullName. services _ OrderedCollection new. self registeredFileReaderClasses do: [:reader | reader ifNotNil: [services addAll: (reader fileReaderServicesForFile: fullName suffix: suffix)]]. ^ services! ! !FileList class methodsFor: 'file reader registration' stamp: 'SD 11/8/2001 21:17'! registerFileReader: aProviderClass "register the given class as providing services for reading files" | registeredReaders | registeredReaders := self registeredFileReaderClasses. (registeredReaders includes: aProviderClass) ifFalse: [ registeredReaders addLast: aProviderClass ]! ! !FileList class methodsFor: 'file reader registration' stamp: 'ar 9/29/2005 12:27'! registeredFileReaderClasses FileReaderRegistry := nil. "wipe it out" ^FileServices registeredFileReaderClasses ! ! !FileList class methodsFor: 'file reader registration' stamp: 'nk 12/7/2002 12:52'! suffixOf: aName "Answer the file extension of the given file" ^ aName ifNil: [''] ifNotNil: [(FileDirectory extensionFor: aName) asLowercase]! ! !FileList class methodsFor: 'file reader registration' stamp: 'SD 11/8/2001 21:18'! unregisterFileReader: aProviderClass "unregister the given class as providing services for reading files" self registeredFileReaderClasses remove: aProviderClass ifAbsent: [nil]! ! !FileList class methodsFor: 'instance creation' stamp: 'md 2/24/2006 16:07'! addButtonsAndFileListPanesTo: window at: upperFraction plus: offset forFileList: aFileList | fileListMorph row buttonHeight fileListTop divider dividerDelta buttons | fileListMorph _ PluggableListMorph on: aFileList list: #fileList selected: #fileListIndex changeSelected: #fileListIndex: menu: #fileListMenu:. fileListMorph enableDrag: true; enableDrop: false. aFileList wantsOptionalButtons ifTrue: [buttons _ aFileList optionalButtonRow. divider _ BorderedSubpaneDividerMorph forBottomEdge. dividerDelta _ 0. buttons color: Color transparent. buttons submorphsDo: [:m | m borderWidth: 2; borderColor: #raised]. divider extent: 4 @ 4; color: Color transparent; borderColor: #raised; borderWidth: 2. fileListMorph borderColor: Color transparent. dividerDelta _ 3. row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 2; layoutPolicy: ProportionalLayout new. buttonHeight _ self defaultButtonPaneHeight. row addMorph: buttons fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 0) offsets: (0 @ 0 corner: 0 @ buttonHeight)). row addMorph: divider fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 0) offsets: (0 @ buttonHeight corner: 0 @ buttonHeight + dividerDelta)). row addMorph: fileListMorph fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 1) offsets: (0 @ buttonHeight + dividerDelta corner: 0 @ 0)). window addMorph: row fullFrame: (LayoutFrame fractions: upperFraction offsets: (0 @ offset corner: 0 @ 0)). row borderWidth: 2] ifFalse: [fileListTop _ 0. window addMorph: fileListMorph frame: (0.3 @ fileListTop corner: 1 @ 0.3)].! ! !FileList class methodsFor: 'instance creation' stamp: 'md 2/24/2006 15:59'! addVolumesAndPatternPanesTo: window at: upperFraction plus: offset forFileList: aFileList | row patternHeight volumeListMorph patternMorph divider dividerDelta | row _ AlignmentMorph newColumn hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 0; borderWidth: 0; layoutPolicy: ProportionalLayout new. patternHeight _ 25. volumeListMorph _ (PluggableListMorph on: aFileList list: #volumeList selected: #volumeListIndex changeSelected: #volumeListIndex: menu: #volumeMenu:) autoDeselect: false. volumeListMorph enableDrag: false; enableDrop: true. patternMorph _ PluggableTextMorph on: aFileList text: #pattern accept: #pattern:. patternMorph acceptOnCR: true. patternMorph hideScrollBarsIndefinitely. divider _ BorderedSubpaneDividerMorph horizontal. dividerDelta _ 0. divider extent: 4 @ 4; color: Color transparent; borderColor: #raised; borderWidth: 2. volumeListMorph borderColor: Color transparent. patternMorph borderColor: Color transparent. dividerDelta _ 3. row addMorph: (volumeListMorph autoDeselect: false) fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 1) offsets: (0 @ 0 corner: 0 @ patternHeight negated - dividerDelta)). row addMorph: divider fullFrame: (LayoutFrame fractions: (0 @ 1 corner: 1 @ 1) offsets: (0 @ patternHeight negated - dividerDelta corner: 0 @ patternHeight negated)). row addMorph: patternMorph fullFrame: (LayoutFrame fractions: (0 @ 1 corner: 1 @ 1) offsets: (0 @ patternHeight negated corner: 0 @ 0)). window addMorph: row fullFrame: (LayoutFrame fractions: upperFraction offsets: (0 @ offset corner: 0 @ 0)). row borderWidth: 2! ! !FileList class methodsFor: 'instance creation' stamp: 'sw 9/28/2001 09:21'! defaultButtonPaneHeight "Answer the user's preferred default height for new button panes." ^ Preferences parameterAt: #defaultButtonPaneHeight ifAbsentPut: [25]! ! !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: 'sbw 8/29/2001 19:37'! openAsMorph "Open a morphic view of a FileList on the default directory." | dir aFileList window upperFraction offset | dir _ FileDirectory default. aFileList _ self new directory: dir. window _ (SystemWindow labelled: dir pathName) model: aFileList. upperFraction _ 0.3. offset _ 0. self addVolumesAndPatternPanesTo: window at: (0 @ 0 corner: 0.3 @ upperFraction) plus: offset forFileList: aFileList. self addButtonsAndFileListPanesTo: window at: (0.3 @ 0 corner: 1.0 @ upperFraction) plus: offset forFileList: aFileList. 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: 'SD 11/8/2001 21:21'! 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: 'SD 11/8/2001 21:20'! 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: 'instance creation' stamp: 'sw 6/11/2001 17:38'! prototypicalToolWindow "Answer an example of myself seen in a tool window, for the benefit of parts-launching tools" ^ self openAsMorph applyModelExtent! ! !FileList class methodsFor: 'window color' stamp: 'sw 2/26/2002 14:04'! windowColorSpecification "Answer a WindowColorSpec object that declares my preference" ^ WindowColorSpec classSymbol: self name wording: 'File List' brightColor: #lightMagenta pastelColor: #paleMagenta helpMessage: 'A tool for looking at files'! ! FileList subclass: #FileList2 instanceVariableNames: 'showDirsInFileList currentDirectorySelected fileSelectionBlock dirSelectionBlock optionalButtonSpecs modalView directoryChangeBlock ok' classVariableNames: '' poolDictionaries: '' category: 'Morphic-FileList'! !FileList2 commentStamp: 'BJP 11/19/2003 21:13' 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 modalFolderSelector "allows the user to select a folder" ! !FileList2 methodsFor: 'as yet unclassified' stamp: 'BG 2/29/2004 23:40'! specsForImageViewer ^{self serviceSortByName. self serviceSortByDate. self serviceSortBySize }! ! !FileList2 methodsFor: 'drag''n''drop' stamp: 'nk 6/15/2003 13:07'! dropDestinationDirectory: dest event: evt "Answer a FileDirectory representing the drop destination in the directory hierarchy morph dest" ^ (dest itemFromPoint: evt position) withoutListWrapper! ! !FileList2 methodsFor: 'drag''n''drop' stamp: 'nk 6/15/2003 22:00'! isDirectoryList: aMorph ^aMorph isKindOf: SimpleHierarchicalListMorph! ! !FileList2 methodsFor: 'initialization' stamp: 'ar 10/10/2000 16:00'! dirSelectionBlock: aBlock dirSelectionBlock _ aBlock! ! !FileList2 methodsFor: 'initialization' stamp: 'tpr 12/1/2003 17:14'! 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: 'initialization' stamp: 'RAA 8/17/2000 13:22'! directoryChangeBlock: aBlockOrNil directoryChangeBlock _ aBlockOrNil.! ! !FileList2 methodsFor: 'initialization' stamp: 'RAA 6/16/2000 13:08'! fileSelectionBlock: aBlock fileSelectionBlock _ aBlock! ! !FileList2 methodsFor: 'initialization' stamp: 'ar 2/12/2001 16:12'! initialDirectoryList | dir nameToShow dirList | dirList _ (FileDirectory on: '') directoryNames collect: [ :each | FileDirectoryWrapper with: (FileDirectory on: each) name: each model: self]. dirList isEmpty ifTrue:[ dirList _ Array with: (FileDirectoryWrapper with: FileDirectory default name: FileDirectory default localName model: self)]. dirList _ dirList,( ServerDirectory serverNames collect: [ :n | dir _ ServerDirectory serverNamed: n. nameToShow _ n. (dir directoryWrapperClass with: dir name: nameToShow model: self) balloonText: dir realUrl ] ). ^dirList! ! !FileList2 methodsFor: 'initialization' stamp: 'RAA 6/16/2000 10:40'! labelString ^ (directory ifNil: [^'[]']) pathName contractTo: 50! ! !FileList2 methodsFor: 'initialization' stamp: 'mir 2/6/2004 17:25'! limitedSuperSwikiDirectoryList | dir nameToShow dirList localDirName localDir | dirList _ OrderedCollection new. ServerDirectory serverNames do: [ :n | dir _ ServerDirectory serverNamed: n. dir isProjectSwiki ifTrue: [ nameToShow _ n. dirList add: ((dir directoryWrapperClass with: dir name: nameToShow model: self) balloonText: dir realUrl) ]. ]. ServerDirectory localProjectDirectories do: [ :each | dirList add: (FileDirectoryWrapper with: each name: each localName model: self) ]. "Make sure the following are always shown, but not twice" localDirName := SecurityManager default untrustedUserDirectory. localDir := FileDirectory on: localDirName. ((ServerDirectory localProjectDirectories collect: [:each | each pathName]) includes: localDirName) ifFalse: [dirList add: (FileDirectoryWrapper with: localDir name: localDir localName model: self)]. FileDirectory default pathName = localDirName ifFalse: [dirList add: (FileDirectoryWrapper with: FileDirectory default name: FileDirectory default localName model: self)]. (dirList anySatisfy: [:each | each withoutListWrapper acceptsUploads]) ifFalse: [dirList add: (FileDirectoryWrapper with: FileDirectory default name: FileDirectory default localName model: self)]. ^dirList! ! !FileList2 methodsFor: 'initialization' stamp: 'mir 2/6/2004 17:25'! limitedSuperSwikiPublishDirectoryList | dirList localDirName localDir | dirList _ self publishingServers. ServerDirectory localProjectDirectories do: [ :each | dirList add: (FileDirectoryWrapper with: each name: each localName model: self)]. "Make sure the following are always shown, but not twice" localDirName := SecurityManager default untrustedUserDirectory. localDir := FileDirectory on: localDirName. ((ServerDirectory localProjectDirectories collect: [:each | each pathName]) includes: localDirName) ifFalse: [dirList add: (FileDirectoryWrapper with: localDir name: localDir localName model: self)]. FileDirectory default pathName = localDirName ifFalse: [dirList add: (FileDirectoryWrapper with: FileDirectory default name: FileDirectory default localName model: self)]. ^dirList! ! !FileList2 methodsFor: 'initialization' stamp: 'RAA 6/16/2000 13:00'! optionalButtonSpecs ^optionalButtonSpecs ifNil: [super optionalButtonSpecs]! ! !FileList2 methodsFor: 'initialization' stamp: 'RAA 6/16/2000 13:01'! optionalButtonSpecs: anArray optionalButtonSpecs _ anArray! ! !FileList2 methodsFor: 'initialization' stamp: 'mir 11/15/2001 18:16'! publishingServers | dir nameToShow dirList | dirList _ OrderedCollection new. ServerDirectory serverNames do: [ :n | dir _ ServerDirectory serverNamed: n. (dir isProjectSwiki and: [dir acceptsUploads]) ifTrue: [ nameToShow _ n. dirList add: ((dir directoryWrapperClass with: dir name: nameToShow model: self) balloonText: dir realUrl)]]. ^dirList! ! !FileList2 methodsFor: 'initialization' stamp: 'sw 2/22/2002 02:34'! universalButtonServices "Answer the services to be reflected in the receiver's buttons" ^ self optionalButtonSpecs! ! !FileList2 methodsFor: 'initialization' stamp: 'nk 6/14/2004 09:39'! updateDirectory "directory has been changed externally, by calling directory:. Now change the view to reflect the change." self changed: #currentDirectorySelected. self postOpen.! ! !FileList2 methodsFor: 'initialize-release' 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: 'own services' stamp: 'nk 6/14/2004 09:43'! addNewDirectory super addNewDirectory. self updateDirectory.! ! !FileList2 methodsFor: 'own services' stamp: 'nk 6/14/2004 09:42'! deleteDirectory super deleteDirectory. self updateDirectory.! ! !FileList2 methodsFor: 'own services' stamp: 'sd 5/11/2003 22:15'! importImage "Import the given image file and store the resulting Form in the default Imports" | fname image | fname _ fileName sansPeriodSuffix. image _ Form fromFileNamed: self fullName. Imports default importImage: image named: fname. ! ! !FileList2 methodsFor: 'own services' stamp: 'sw 2/22/2002 02:35'! okayAndCancelServices "Answer ok and cancel services" ^ {self serviceOkay. self serviceCancel}! ! !FileList2 methodsFor: 'own services' stamp: 'nk 1/6/2004 12:36'! 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: [(World drawingClass withForm: image) openInWorld] ifFalse: [FormView open: image named: fileName]! ! !FileList2 methodsFor: 'own services' stamp: 'hg 8/3/2000 16:55'! 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." Project canWeLoadAProjectNow ifFalse: [^ self]. ProjectViewMorph openFromDirectory: directory andFileName: fileName ! ! !FileList2 methodsFor: 'own services' stamp: 'yo 7/31/2004 18:08'! removeLinefeeds "Remove any line feeds by converting to CRs instead. This is a temporary implementation for 3.6 only... should be removed during 3.7alpha." | fileContents | fileContents _ ((FileStream readOnlyFileNamed: self fullName) wantsLineEndConversion: true) contentsOfEntireFile. (FileStream newFileNamed: self fullName) nextPutAll: fileContents; close.! ! !FileList2 methodsFor: 'own services' stamp: 'nk 6/8/2004 17:09'! serviceCancel "Answer a service for hitting the cancel button" ^ (SimpleServiceEntry new provider: self label: 'cancel' selector: #cancelHit description: 'hit here to cancel ') buttonLabel: 'cancel'! ! !FileList2 methodsFor: 'own services' stamp: 'nk 6/8/2004 17:09'! serviceOkay "Answer a service for hitting the okay button" ^ (SimpleServiceEntry new provider: self label: 'okay' selector: #okHit description: 'hit here to accept the current selection') buttonLabel: 'ok'! ! !FileList2 methodsFor: 'own services' stamp: 'sw 2/22/2002 02:07'! serviceOpenProjectFromFile "Answer a service for opening a .pr project file" ^ SimpleServiceEntry provider: self label: 'load as project' selector: #openProjectFromFile description: 'open project from file' buttonLabel: 'load'! ! !FileList2 methodsFor: 'own services' stamp: 'sw 2/22/2002 02:36'! servicesForFolderSelector "Answer the ok and cancel servies for the folder selector" ^ self okayAndCancelServices! ! !FileList2 methodsFor: 'own services' stamp: 'sw 2/22/2002 02:36'! servicesForProjectLoader "Answer the services to show in the button pane for the project loader" ^ {self serviceSortByName. self serviceSortByDate. self serviceSortBySize. self serviceOpenProjectFromFile}! ! !FileList2 methodsFor: 'user interface' stamp: 'dgd 4/3/2006 13:59'! blueButtonForService: aService textColor: textColor inWindow: window | block result | block := [self fullName isNil ifTrue: [self inform: 'Please select a file' translated] ifFalse: [aService performServiceFor: self]] copy fixTemps. result := window fancyText: aService buttonLabel capitalized translated font: Preferences standardEToysFont color: textColor. result setProperty: #buttonText toValue: aService buttonLabel capitalized; hResizing: #rigid; extent: 100 @ 20; layoutInset: 4; borderWidth: ColorTheme current dialogButtonBorderWidth; useRoundedCorners; setBalloonText: aService label. result on: #mouseUp send: #value to: block. ^ result! ! !FileList2 methodsFor: 'user interface' stamp: 'RAA 2/17/2001 12:18'! morphicDirectoryTreePane ^self morphicDirectoryTreePaneFiltered: #initialDirectoryList ! ! !FileList2 methodsFor: 'user interface' stamp: 'rww 12/13/2003 13:07'! morphicDirectoryTreePaneFiltered: aSymbol ^(SimpleHierarchicalListMorph on: self list: aSymbol selected: #currentDirectorySelected changeSelected: #setSelectedDirectoryTo: menu: #volumeMenu: keystroke: nil) autoDeselect: false; enableDrag: false; enableDrop: true; yourself ! ! !FileList2 methodsFor: 'user interface' stamp: 'RAA 6/16/2000 10:53'! morphicFileContentsPane ^PluggableTextMorph on: self text: #contents accept: #put: readSelection: #contentsSelection menu: #fileContentsMenu:shifted: ! ! !FileList2 methodsFor: 'user interface' stamp: 'nk 6/15/2003 13:05'! morphicFileListPane ^(PluggableListMorph on: self list: #fileList selected: #fileListIndex changeSelected: #fileListIndex: menu: #fileListMenu:) enableDrag: true; enableDrop: false; yourself ! ! !FileList2 methodsFor: 'user interface' stamp: 'BG 2/28/2004 22:58'! morphicPatternPane | pane | pane := PluggableTextMorph on: self text: #pattern accept: #pattern:. pane acceptOnCR: true. ^pane ! ! !FileList2 methodsFor: 'volume list and pattern' stamp: 'nk 6/14/2004 09:45'! changeDirectoryTo: aFileDirectory "Change directory as requested." self directory: aFileDirectory. self updateDirectory! ! !FileList2 methodsFor: 'volume list and pattern' stamp: 'RAA 8/17/2000 13:59'! directory ^directory! ! !FileList2 methodsFor: 'volume list and pattern' stamp: 'mir 8/24/2001 12:03'! listForPattern: pat "Make the list be those file names which match the pattern." | sizePad newList entries | directory ifNil: [^#()]. entries _ (Preferences eToyLoginEnabled and: [Utilities authorNamePerSe notNil]) ifTrue: [directory matchingEntries: {'submittedBy: ' , Utilities authorName.} ] ifFalse: [directory entries]. (fileSelectionBlock isKindOf: MessageSend) ifTrue: [ fileSelectionBlock arguments: {entries}. newList _ fileSelectionBlock value. fileSelectionBlock arguments: #(). ] ifFalse: [ newList _ 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: 'volume list and pattern' stamp: 'nk 2/20/2001 12:09'! listForPatterns: anArray "Make the list be those file names which match the patterns." | sizePad newList | directory ifNil: [^#()]. (fileSelectionBlock isKindOf: MessageSend) ifTrue: [ fileSelectionBlock arguments: {directory entries}. newList _ fileSelectionBlock value. fileSelectionBlock arguments: #(). ] ifFalse: [ newList _ Set new. anArray do: [ :pat | newList addAll: (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: 'private' stamp: 'RAA 4/6/2001 12:45'! cancelHit modalView delete. directory _ fileName _ currentDirectorySelected _ nil.! ! !FileList2 methodsFor: 'private' stamp: 'LC 1/6/2002 06:50'! currentDirectorySelected ^ currentDirectorySelected ! ! !FileList2 methodsFor: 'private' stamp: 'ar 2/12/2001 16:20'! directoryNamesFor: item "item may be file directory or server directory" | entries | entries _ item directoryNames. dirSelectionBlock ifNotNil:[entries _ entries select: dirSelectionBlock]. ^entries! ! !FileList2 methodsFor: 'private' stamp: 'LC 1/6/2002 06:51'! getSelectedDirectory ok == true ifFalse: [^ nil]. ^ currentDirectorySelected ! ! !FileList2 methodsFor: 'private' stamp: 'sw 9/12/2002 00:43'! getSelectedFile "Answer a filestream on the selected file. If it cannot be opened for read/write, try read-only before giving up; answer nil if unsuccessful" ok == true ifFalse: [^ nil]. directory ifNil: [^ nil]. fileName ifNil: [^ nil]. ^ (directory oldFileNamed: fileName) ifNil: [directory readOnlyFileNamed: fileName]! ! !FileList2 methodsFor: 'private' stamp: 'RAA 6/21/2000 12:06'! modalView: aSystemWindowOrSuch modalView _ aSystemWindowOrSuch! ! !FileList2 methodsFor: 'private' stamp: 'md 10/22/2003 15:27'! okHit ok _ true. currentDirectorySelected ifNil: [Beeper beep] ifNotNil: [modalView delete]! ! !FileList2 methodsFor: 'private' stamp: 'TN 4/13/2005 18:32'! okHitForProjectLoader | areaOfProgress | fileName ifNil: [^ self]. ok _ true. 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: 'private' stamp: 'RAA 6/16/2000 10:48'! postOpen directory ifNotNil: [ self changed: #(openPath) , directory pathParts. ]. ! ! !FileList2 methodsFor: 'private' stamp: 'LC 1/6/2002 07:12'! saveLocalOnlyHit ok _ true. modalView delete. directory _ fileName _ nil. currentDirectorySelected _ #localOnly.! ! !FileList2 methodsFor: 'private' stamp: 'LC 1/6/2002 09:03'! setSelectedDirectoryTo: aFileDirectoryWrapper currentDirectorySelected _ aFileDirectoryWrapper. self directory: aFileDirectoryWrapper withoutListWrapper. brevityState := #FileList. "self addPath: path." self changed: #fileList. self changed: #contents. self changed: #currentDirectorySelected.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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 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 2/19/2001 06:57'! projectOnlySelectionMethod: incomingEntries | versionsAccepted basicInfoTuple basicName basicVersion | "this shows only the latest version of each project" versionsAccepted _ Dictionary new. incomingEntries do: [ :entry | entry isDirectory ifFalse: [ (#('*.pr' '*.pr.gz' '*.project') anySatisfy: [ :each | each match: entry name]) ifTrue: [ basicInfoTuple _ Project parseProjectFileName: entry name. basicName _ basicInfoTuple first. basicVersion _ basicInfoTuple second. ((versionsAccepted includesKey: basicName) and: [(versionsAccepted at: basicName) first > basicVersion]) ifFalse: [ versionsAccepted at: basicName put: {basicVersion. entry} ]. ] ] ]. ^versionsAccepted asArray collect: [ :each | each second]! ! !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: 'dgd 11/3/2004 20:09'! blueButtonText: aString textColor: textColor color: aColor inWindow: window | result | result := window fancyText: aString translated font: Preferences standardEToysFont color: textColor. result setProperty: #buttonText toValue: aString; hResizing: #rigid; extent: 100 @ 20; layoutInset: 4; borderWidth: ColorTheme current dialogButtonBorderWidth; useRoundedCorners. aColor isNil ifFalse: [""result color: aColor. result borderColor: aColor muchDarker]. ^ result! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 11/3/2004 20:13'! blueButtonText: aString textColor: textColor color: aColor inWindow: window balloonText: balloonText selector: sel recipient: recip | result | result := window fancyText: aString translated font: Preferences standardEToysFont color: textColor. result setProperty: #buttonText toValue: aString; hResizing: #rigid; extent: 100 @ 20; layoutInset: 4; borderWidth: ColorTheme current dialogButtonBorderWidth; useRoundedCorners; setBalloonText: balloonText. result on: #mouseUp send: sel to: recip. aColor isNil ifFalse: ["" result color: aColor. result borderColor: aColor muchDarker]. ^ result! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 11/2/2004 21:43'! blueButtonText: aString textColor: textColor inWindow: window ^ self blueButtonText: aString textColor: textColor color: nil inWindow: window! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 11/3/2004 20:04'! blueButtonText: aString textColor: textColor inWindow: window balloonText: balloonText selector: sel recipient: recip ^ self blueButtonText: aString textColor: textColor color: nil inWindow: window balloonText: balloonText selector: sel recipient: recip ! ! !FileList2 class methodsFor: 'blue ui' stamp: 'nk 7/16/2003 17:13'! enableTypeButtons: typeButtons info: fileTypeInfo forDir: aDirectory | foundSuffixes fileSuffixes firstEnabled enableIt | firstEnabled _ nil. foundSuffixes _ (aDirectory ifNil: [ #()] ifNotNil: [ 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: 'dgd 4/3/2006 14:02'! endingSpecs "Answer a collection of specs to build the selective 'find anything' tool called by the Navigator. This version uses the services registry to do so." "FileList2 morphicViewGeneralLoaderInWorld: World" | categories services specs rejects | rejects _ #(addFileToNewZip: compressFile: openInZipViewer: extractAllFrom: openOn:). categories _ #( ('Art' ('bmp' 'gif' 'jpg' 'jpeg' 'form' 'png' 'pcx' 'xbm' 'xpm' 'ppm' 'pbm')) ('Morphs' ('morph' 'morphs' 'sp')) ('Projects' ('extseg' 'project' 'pr')) ('MIDI' ('mid' 'midi')) ('Music' ('mp3')) ('Movies' ('movie' 'mpg' 'mpeg' 'qt' 'mov')) ('Flash' ('swf')) ). "('Books' ('bo'))" "('Code' ('st' 'cs'))" "('TrueType' ('ttf'))" "('3ds' ('3ds'))" "('Tape' ('tape'))" "('Wonderland' ('wrl'))" "('HTML' ('htm' 'html'))" categories first at: 2 put: ImageReadWriter allTypicalFileExtensions. specs _ OrderedCollection new. categories do: [ :cat | | catSpecs catServices okExtensions | services _ Dictionary new. catSpecs _ Array new: 3. catServices _ OrderedCollection new. okExtensions _ Set new. cat second do: [ :ext | (FileList itemsForFile: 'fred.',ext) do: [ :i | (rejects includes: i selector) ifFalse: [ okExtensions add: ext. services at: i label put: i ]]]. services do: [ :svc | catServices add: svc ]. services isEmpty ifFalse: [ catSpecs at: 1 put: cat first; at: 2 put: okExtensions; at: 3 put: catServices. specs add: catSpecs ] ]. ^specs ! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 4/3/2006 14:00'! 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: ColorTheme current dialogBorderWidth; borderColor: ColorTheme current dialogBorderColor; useRoundedCorners. fileTypeButtons _ fileTypeInfo collect: [ :each | (self blueButtonText: each first textColor: Color gray inWindow: window) setProperty: #enabled toValue: true; hResizing: #shrinkWrap; useSquareCorners ]. buttons _ {{'OK'. ColorTheme current okColor}. {'Cancel'. ColorTheme current cancelColor}} collect: [ :each | self blueButtonText: each first textColor: textColor1 color: each second 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...' translated font: Preferences standardEToysTitleFont color: textColor1}. fileTypeRow _ window addARowCentered: fileTypeButtons cellInset: 2. actionRow _ window addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second } cellInset: 2. window addARow: { (window inAColumn: {(pane2a _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 0; borderWidth: ColorTheme current dialogPaneBorderWidth; borderColor: ColorTheme current dialogPaneBorderColor }) layoutInset: 10. (window inAColumn: {(pane2b _ window inARow: {window inAColumn: {fileListPane}}) useRoundedCorners; layoutInset: 0; borderWidth: ColorTheme current dialogPaneBorderWidth; borderColor: ColorTheme current dialogPaneBorderColor }) layoutInset: 10. }. window fullBounds. window fillWithRamp: ColorTheme current dialogRampOrColor oriented: 0.65. pane2a fillWithRamp: ColorTheme current dialogPaneRampOrColor oriented: (0.7 @ 0.35). pane2b fillWithRamp: ColorTheme current dialogPaneRampOrColor oriented: (0.7 @ 0.35). " buttons do: [ :each | each fillWithRamp: ColorTheme current dialogButtonsRampOrColor 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 update: actionRow in: window fileTypeRow: fileTypeRow morphUp: nil. self enableTypeButtons: fileTypeButtons info: fileTypeInfo forDir: newDir. ] fixTemps. aFileList directory: aFileList directory. window adoptPaneColor: (Color r: 0.548 g: 0.677 b: 1.0). window becomeModal. ^ 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: 'RAA 2/19/2001 10:14'! morphicViewProjectLoader2InWorld: aWorld reallyLoad: aBoolean ^self morphicViewProjectLoader2InWorld: aWorld reallyLoad: aBoolean dirFilterType: #initialDirectoryList ! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 4/3/2006 14:03'! morphicViewProjectLoader2InWorld: aWorld reallyLoad: aBoolean dirFilterType: aSymbol | window aFileList buttons treePane textColor1 fileListPane pane2a pane2b treeExtent filesExtent | 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: aFileList servicesForProjectLoader; fileSelectionBlock: ( aSymbol == #limitedSuperSwikiDirectoryList ifTrue: [ MessageSend receiver: self selector: #projectOnlySelectionMethod: ] ifFalse: [ self projectOnlySelectionBlock ] ); "dirSelectionBlock: self hideSqueakletDirectoryBlock;" modalView: window. window setProperty: #FileList toValue: aFileList; wrapCentering: #center; cellPositioning: #topCenter; borderWidth: ColorTheme current dialogBorderWidth; borderColor: ColorTheme current dialogBorderColor; useRoundedCorners. buttons _ {{'OK'. ColorTheme current okColor}. {'Cancel'. ColorTheme current cancelColor}} collect: [ :each | self blueButtonText: each first textColor: textColor1 color: each second inWindow: window ]. aWorld width < 800 ifTrue: [ treeExtent _ 150@300. filesExtent _ 350@300. ] ifFalse: [ treeExtent _ 250@300. filesExtent _ 350@300. ]. (treePane _ aFileList morphicDirectoryTreePaneFiltered: aSymbol) extent: treeExtent; retractable: false; borderWidth: 0. fileListPane _ aFileList morphicFileListPane extent: filesExtent; retractable: false; borderWidth: 0. window addARow: { window fancyText: 'Load A Project' translated font: Preferences standardEToysTitleFont color: textColor1 }; addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second }; addARow: { window fancyText: 'Please select a project' translated font: Preferences standardEToysFont color: textColor1 }; addARow: { (window inAColumn: {(pane2a _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 0; borderWidth: ColorTheme current dialogPaneBorderWidth; borderColor: ColorTheme current dialogPaneBorderColor }) layoutInset: 10. (window inAColumn: {(pane2b _ window inARow: {window inAColumn: {fileListPane}}) useRoundedCorners; layoutInset: 0; borderWidth: ColorTheme current dialogPaneBorderWidth; borderColor: ColorTheme current dialogPaneBorderColor }) layoutInset: 10. }. window fullBounds. window fillWithRamp: ColorTheme current dialogRampOrColor oriented: 0.65. pane2a fillWithRamp: ColorTheme current dialogPaneRampOrColor oriented: (0.7 @ 0.35). pane2b fillWithRamp: ColorTheme current dialogPaneRampOrColor oriented: (0.7 @ 0.35). " buttons do: [ :each | each fillWithRamp: ColorTheme current dialogButtonsRampOrColor 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 adoptPaneColor: (Color r: 0.548 g: 0.677 b: 1.0). window becomeModal. ^ window openInWorld: aWorld.! ! !FileList2 class methodsFor: 'blue ui' stamp: 'dgd 11/3/2004 19:56'! morphicViewProjectSaverFor: aProject " (FileList2 morphicViewProjectSaverFor: Project current) openInWorld " | window aFileList buttons treePane pane2 textColor1 option treeExtent buttonData buttonRow | textColor1 _ Color r: 0.742 g: 0.839 b: 1.0. aFileList _ self new directory: ServerDirectory projectDefaultDirectory. 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: ColorTheme current dialogBorderWidth; borderColor: ColorTheme current dialogBorderColor; useRoundedCorners. buttonData := Preferences enableLocalSave ifTrue: [{ {'Save'. #okHit. 'Save in the place specified below, and in the Squeaklets folder on your local disk'. ColorTheme current okColor}. {'Save on local disk only'. #saveLocalOnlyHit. 'saves in the Squeaklets folder'. ColorTheme current okColor}. {'Cancel'. #cancelHit. 'return without saving'. ColorTheme current cancelColor} }] ifFalse: [{ {'Save'. #okHit. 'Save in the place specified below, and in the Squeaklets folder on your local disk'. ColorTheme current okColor}. {'Cancel'. #cancelHit. 'return without saving'. ColorTheme current cancelColor} }]. buttons _ buttonData collect: [ :each | (self blueButtonText: each first textColor: textColor1 color: each fourth inWindow: window) setBalloonText: each third translated; hResizing: #shrinkWrap; on: #mouseUp send: each second to: aFileList ]. option _ aProject world valueOfProperty: #SuperSwikiPublishOptions ifAbsent: [#initialDirectoryList]. aProject world removeProperty: #SuperSwikiPublishOptions. treeExtent _ World height < 500 ifTrue: [ 350@150 ] ifFalse: [ 350@300 ]. (treePane _ aFileList morphicDirectoryTreePaneFiltered: option) extent: treeExtent; retractable: false; borderWidth: 0. window addARowCentered: { window fancyText: 'Publish This Project' translated font: Preferences standardEToysTitleFont color: textColor1 }. buttonRow _ OrderedCollection new. buttons do: [:button | buttonRow add: button] separatedBy: [buttonRow add: ((Morph new extent: 30@5) color: Color transparent)]. " addARowCentered: { buttons first. (Morph new extent: 30@5) color: Color transparent. buttons second. (Morph new extent: 30@5) color: Color transparent. buttons third };" window addARowCentered: buttonRow; addARowCentered: { (window inAColumn: {(ProjectViewMorph on: aProject) lock}) layoutInset: 4}; addARowCentered: { window fancyText: 'Please select a folder' translated font: Preferences standardEToysFont color: textColor1 }; addARow: { ( window inAColumn: { (pane2 _ window inARow: {window inAColumn: {treePane}}) useRoundedCorners; layoutInset: 0; borderWidth: ColorTheme current dialogPaneBorderWidth; borderColor: ColorTheme current dialogPaneBorderColor } ) layoutInset: 10 }. window fullBounds. window fillWithRamp: ColorTheme current dialogRampOrColor oriented: 0.65. pane2 fillWithRamp: ColorTheme current dialogPaneRampOrColor oriented: (0.7 @ 0.35). " buttons do: [ :each | each fillWithRamp: ColorTheme current dialogButtonsRampOrColor oriented: (0.75 @ 0). ]. " window setProperty: #morphicLayerNumber toValue: 11. aFileList postOpen. window adoptPaneColor: (Color r: 0.548 g: 0.677 b: 1.0). ^ window ! ! !FileList2 class methodsFor: 'instance creation' stamp: 'nk 7/12/2000 11:03'! openMorphicViewInWorld "FileList2 openMorphicViewInWorld" ^self morphicView openInWorld! ! !FileList2 class methodsFor: 'instance creation' stamp: 'nk 6/14/2004 08:41'! prototypicalToolWindow "Answer an example of myself seen in a tool window, for the benefit of parts-launching tools" ^ self morphicView applyModelExtent! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:35'! modalFileSelector | window | window _ self morphicViewFileSelector. window openCenteredInWorld. self modalLoopOn: window. ^(window valueOfProperty: #fileListModel) getSelectedFile! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:34'! modalFileSelectorForSuffixes: aList | window aFileList | window _ self morphicViewFileSelectorForSuffixes: aList. aFileList _ window valueOfProperty: #fileListModel. window openCenteredInWorld. self modalLoopOn: window. ^aFileList getSelectedFile! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:34'! modalFileSelectorForSuffixes: aList directory: aDirectory | window aFileList | window _ self morphicViewFileSelectorForSuffixes: aList directory: aDirectory. aFileList _ window valueOfProperty: #fileListModel. window openCenteredInWorld. self modalLoopOn: window. ^aFileList getSelectedFile! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'gh 9/16/2002 10:33'! modalFolderSelector ^self modalFolderSelector: FileDirectory default! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:34'! 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. self modalLoopOn: window. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:34'! 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). w addMorphInLayer: window. w startSteppingSubmorphsOf: window. self modalLoopOn: window. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'modal dialogs' stamp: 'miki 8/15/2005 18:33'! modalFolderSelector: aDir | window fileModel | window _ self morphicViewFolderSelector: aDir. fileModel _ window model. window openInWorld: self currentWorld extent: 300@400. self modalLoopOn: window. ^fileModel getSelectedDirectory withoutListWrapper! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'RAA 3/6/2001 12:47'! morphicViewFileSelector ^self morphicViewFileSelectorForSuffixes: nil ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'miki 8/14/2005 21:21'! morphicViewFileSelectorForSuffixes: aList "Answer a morphic file-selector tool for the given suffix list." ^self morphicViewFileSelectorForSuffixes: aList directory: FileDirectory default.! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'miki 8/14/2005 21:21'! morphicViewFileSelectorForSuffixes: aList directory: dir "Answer a morphic file-selector tool for the given suffix list and the given directory." | aFileList window fixedSize midLine gap | aFileList _ self new directory: dir. aFileList optionalButtonSpecs: aFileList okayAndCancelServices. aList ifNotNil: [aFileList fileSelectionBlock: [:entry :myPattern | entry isDirectory ifTrue: [false] ifFalse: [aList includes: (FileDirectory extensionFor: entry name asLowercase)]] fixTemps]. window _ BorderedMorph new layoutPolicy: ProportionalLayout new; color: Color lightBlue; borderColor: Color blue; borderWidth: 4; layoutInset: 4; extent: 600@400; useRoundedCorners. window setProperty: #fileListModel toValue: aFileList. aFileList modalView: window. midLine _ 0.4. fixedSize _ 25. gap _ 5. self addFullPanesTo: window from: { {self textRow: 'Please select a file'. 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: midLine@1. gap @(fixedSize * 2) corner: gap negated@0}. {aFileList morphicFileListPane. midLine @ 0 corner: 1@1. gap@(fixedSize * 2) corner: gap negated@0}. }. aFileList postOpen. ^ window ! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'gh 9/16/2002 10:30'! morphicViewFolderSelector ^self morphicViewFolderSelector: FileDirectory default! ! !FileList2 class methodsFor: 'morphic ui' stamp: 'bkv 11/12/2002 16:55'! morphicViewFolderSelector: aDir "Answer a tool that allows the user to select a folder" | aFileList window fixedSize | aFileList _ self new directory: aDir. aFileList optionalButtonSpecs: aFileList servicesForFolderSelector. window _ (SystemWindow labelled: aDir 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: 'BG 2/29/2004 23:26'! morphicViewImageViewer | dir aFileList window midLine fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. aFileList optionalButtonSpecs: aFileList specsForImageViewer. aFileList fileSelectionBlock: [ :entry :myPattern | entry isDirectory ifTrue: [ false ] ifFalse: [ #('bmp' 'gif' 'jpg' 'form' 'png') includes: (FileDirectory extensionFor: 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: 'sw 2/22/2002 02:02'! morphicViewProjectLoader | dir aFileList window midLine fixedSize | dir _ FileDirectory default. aFileList _ self new directory: dir. aFileList optionalButtonSpecs: aFileList servicesForProjectLoader. 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: 'dgd 4/3/2006 14:04'! update: actionRow in: window fileTypeRow: fileTypeRow morphUp: morph | fileTypeInfo info2 buttons textColor1 fileSuffixes fileActions aFileList fileTypeString | (morph notNil and:[(morph valueOfProperty: #enabled) not]) ifTrue: [^self]. fileTypeRow submorphsDo: [ :sub | sub color: ( sub == morph ifTrue: [Color white] ifFalse: [(sub valueOfProperty: #enabled) ifTrue: [Color transparent] ifFalse: [Color gray]] ). ]. fileTypeString _ morph isNil ifTrue:['xxxx'] ifFalse:[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: [ nil ]. info2 isNil ifTrue:[ buttons := OrderedCollection new ] ifFalse:[ fileSuffixes _ info2 second. fileActions _ info2 third. buttons _ fileActions collect: [ :each | aFileList blueButtonForService: each textColor: textColor1 inWindow: window ]. buttons do: [ :each | each fillWithRamp: ColorTheme current okColor oriented: (0.75 @ 0). ]. ]. buttons addLast: (self blueButtonText: 'Cancel' textColor: textColor1 color: ColorTheme current cancelColor inWindow: window balloonText: 'Cancel this search' selector: #cancelHit recipient: aFileList). buttons do: [ :each | actionRow addMorphBack: each]. window fullBounds. fileSuffixes isNil ifFalse:[ aFileList fileSelectionBlock: ( self selectionBlockForSuffixes: (fileSuffixes collect: [ :each | '*.',each]) ). ]. aFileList updateFileList.! ! !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: 'miki 8/15/2005 18:34'! modalLoopOn: aMorph [aMorph world notNil] whileTrue: [ aMorph outermostWorldMorph doOneCycle. ].! ! !FileList2 class methodsFor: 'utility' stamp: 'RAA 3/6/2001 12:39'! textRow: aString ^AlignmentMorph newRow wrapCentering: #center; cellPositioning: #leftCenter; color: Color transparent; 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: '*smloader-extension' stamp: 'gk 5/5/2006 02:05'! morphicViewOnDirectory: aFileDirectory | aFileList window fileListBottom midLine fileListTopOffset buttonPane | aFileList := self new directory: aFileDirectory. window := (SystemWindow labelled: aFileDirectory pathName) model: aFileList. fileListTopOffset := (TextStyle defaultFont pointSize * 2) + 14. fileListBottom := 0.4. midLine := 0.4. buttonPane := aFileList optionalButtonRow addMorph: (aFileList morphicPatternPane vResizing: #spaceFill; yourself). self addFullPanesTo: window from: { {buttonPane. 0@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: '*smloader-override' stamp: 'btr 1/30/2004 00:56'! morphicView ^ self morphicViewOnDirectory: FileDirectory default! ! TestCase subclass: #FileList2ModalDialogsTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-FileList-Tests'! !FileList2ModalDialogsTest commentStamp: '' prior: 0! TestRunner open! !FileList2ModalDialogsTest methodsFor: 'running' stamp: 'sd 11/20/2005 21:27'! testModalFileSelector | window fileList2 | window := FileList2 morphicViewFileSelector. window openCenteredInWorld. fileList2 := window valueOfProperty: #fileListModel. fileList2 fileListIndex: 1. window delete. self assert: fileList2 getSelectedFile isNil. fileList2 okHit. self deny: fileList2 getSelectedFile isNil ! ! !FileList2ModalDialogsTest methodsFor: 'running' stamp: 'sd 11/20/2005 21:27'! testModalFileSelectorForSuffixes | window fileList2 | window := FileList2 morphicViewFileSelectorForSuffixes: nil. window openCenteredInWorld. fileList2 := window valueOfProperty: #fileListModel. fileList2 fileListIndex: 1. window delete. self assert: fileList2 getSelectedFile isNil. fileList2 okHit. self deny: fileList2 getSelectedFile isNil ! ! !FileList2ModalDialogsTest methodsFor: 'running' stamp: 'sd 11/20/2005 21:27'! testModalFolderSelector | window fileList2 | window := FileList2 morphicViewFolderSelector. fileList2 := window model. window openInWorld: self currentWorld extent: 300@400. fileList2 fileListIndex: 1. window delete. self assert: fileList2 getSelectedDirectory withoutListWrapper isNil. fileList2 okHit. self deny: fileList2 getSelectedDirectory withoutListWrapper isNil ! ! !FileList2ModalDialogsTest methodsFor: 'running' stamp: 'sd 11/20/2005 21:27'! testModalFolderSelectorForProjectLoad | window fileList2 w | window := FileList2 morphicViewProjectLoader2InWorld: self currentWorld reallyLoad: false. fileList2 := window valueOfProperty: #FileList. w := self currentWorld. window position: w topLeft + (w extent - window extent // 2). window openInWorld: w. window delete. self assert: fileList2 getSelectedDirectory withoutListWrapper isNil. fileList2 okHit. self deny: fileList2 getSelectedDirectory withoutListWrapper isNil ! ! ClassTestCase subclass: #FileListTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tools-FileList-Tests'! !FileListTest methodsFor: 'initialize' stamp: 'SD 11/10/2001 21:48'! setUp DummyToolWorkingWithFileList initialize.! ! !FileListTest methodsFor: 'initialize' stamp: 'SD 11/10/2001 21:49'! tearDown DummyToolWorkingWithFileList unregister.! ! !FileListTest methodsFor: 'test' stamp: 'stephaneducasse 10/9/2005 20:52'! testAllRegisteredServices "(self selector: #testAllRegisteredServices) debug" self shouldnt: [FileList allRegisteredServices] raise: Error! ! !FileListTest methodsFor: 'test' stamp: 'SD 11/10/2001 21:53'! testMenuReturned "(self selector: #testToolRegistered) debug" self assert: (FileList registeredFileReaderClasses includes: DummyToolWorkingWithFileList)! ! !FileListTest methodsFor: 'test' stamp: 'stephaneducasse 10/9/2005 20:50'! testService "a stupid test to check that the class returns a service" "(self selector: #testService) debug" | service | service := (DummyToolWorkingWithFileList fileReaderServicesForFile: 'abab.kkk' suffix: 'kkk') first. self assert: (self checkIsServiceIsFromDummyTool: service). service := (DummyToolWorkingWithFileList fileReaderServicesForFile: 'zkk.gz' suffix: 'gz'). self assert: service isEmpty! ! !FileListTest methodsFor: 'test' stamp: 'nk 11/30/2002 14:55'! testServicesForFileEnding "(self selector: #testServicesForFileEnding) debug" self assert: (((FileList new directory: FileDirectory default; yourself) itemsForFile: 'aaa.kkk') anySatisfy: [ :ea | self checkIsServiceIsFromDummyTool: ea ]). ! ! !FileListTest methodsFor: 'test' stamp: 'SD 11/10/2001 21:52'! testToolRegistered "(self selector: #testToolRegistered) debug" self assert: (FileList registeredFileReaderClasses includes: DummyToolWorkingWithFileList)! ! !FileListTest methodsFor: 'test' stamp: 'SD 11/11/2001 13:54'! testToolRegisteredUsingInterface "(self selector: #testToolRegisteredUsingInterface) debug" self assert: (FileList isReaderNamedRegistered: #DummyToolWorkingWithFileList)! ! !FileListTest methodsFor: 'private' stamp: 'sd 2/1/2002 23:04'! checkIsServiceIsFromDummyTool: service ^ (service instVarNamed: #provider) = DummyToolWorkingWithFileList & service label = 'menu label' & (service instVarNamed: #selector) = #loadAFileForTheDummyTool:! ! SimpleServiceEntry subclass: #FileModifyingSimpleServiceEntry instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-FileRegistry'! !FileModifyingSimpleServiceEntry commentStamp: 'nk 11/26/2002 12:03' prior: 0! I represent a service that may change the contents of a directory. Such changes include: * file creation * file deletion * file modification! !FileModifyingSimpleServiceEntry methodsFor: 'as yet unclassified' stamp: 'nk 11/26/2002 12:08'! performServiceFor: anObject | retval | retval _ super performServiceFor: anObject. self changed: #fileListChanged. ^retval "is this used anywhere?"! ! Object subclass: #FilePackage instanceVariableNames: 'fullName sourceSystem classes doIts classOrder' classVariableNames: 'LogFileStream' poolDictionaries: '' category: 'System-FilePackage'! !FilePackage methodsFor: 'accessing'! classAt: className ^self classes at: className! ! !FilePackage methodsFor: 'accessing'! classes ^classes! ! !FilePackage methodsFor: 'accessing' stamp: 'nk 2/18/2004 18:31'! fixClassOrder "Essentially bubble sort the classOrder so that superclasses appear before subclasses" | superClass index subClass superIndex | index := 0. [index < classOrder size] whileTrue:[ subClass := classOrder at: (index := index + 1). superClass := nil. subClass isMeta ifTrue:[ "Treat non-meta as superclass" superClass := self classes at: subClass name ifAbsent:[nil]. ] ifFalse:[ subClass hasDefinition ifTrue:[ superClass := self classes at: (Scanner new scanTokens: subClass definition) first ifAbsent:[nil]. superClass ifNotNil:[superClass hasDefinition ifFalse:[superClass := nil]]. ]. ]. superClass ifNotNil:[ superIndex := classOrder indexOf: superClass ifAbsent:[self error:'Where is the class?']. superIndex > index ifTrue:[ "Move superClass before index" classOrder remove: superClass. classOrder add: superClass before: subClass. "Rewind index - we need to check superClass itself" index := index - 1. ]. ]. ]. ! ! !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' stamp: 'ar 2/5/2004 15:11'! removeDoIts doIts := OrderedCollection new.! ! !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: '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: 'conflict checker' stamp: 'dew 2/14/2004 00:12'! checkForMoreRecentUpdateThanChangeSet: updateNumberChangeSet pseudoClass: pseudoClass selector: selector "Returns the source code for a conflict if a conflict is found, otherwise returns nil." | classOrMeta allChangeSets moreRecentChangeSets conflictingChangeSets changeRecordSource classAndMethodPrintString | classAndMethodPrintString _ pseudoClass name, (pseudoClass hasMetaclass ifTrue: [' class'] ifFalse: ['']), '>>', selector asString. changeRecordSource _ pseudoClass sourceCode at: selector. changeRecordSource isText ifTrue: [changeRecordSource _ Text fromString: 'method: ', classAndMethodPrintString, ' was removed'] ifFalse: [changeRecordSource stamp isEmptyOrNil ifTrue: [self notify: 'Warning: ', classAndMethodPrintString, ' in ', self packageName, ' has no timestamp/initials!!']]. pseudoClass exists ifFalse: [(self classes at: pseudoClass name) hasDefinition ifTrue: [^ nil "a method was added for a newly defined class; not a conflict"] ifFalse: [self class logCr; log: 'CONFLICT found for ', classAndMethodPrintString, '... class ', pseudoClass name asString, ' does not exist in the image and is not defined in the file'. ^ changeRecordSource]]. classOrMeta _ pseudoClass realClass. "Only printout the replacing methods here, but we still check for removed methods too in the rest of this method." (self class verboseConflicts and: [classOrMeta includesSelector: selector]) ifTrue: [self class logCr; log: '...checking ', classOrMeta asString, '>>', selector asString]. allChangeSets _ ChangeSorter allChangeSets. moreRecentChangeSets _ allChangeSets copyFrom: (allChangeSets indexOf: updateNumberChangeSet) to: (allChangeSets size). conflictingChangeSets _ (moreRecentChangeSets select: [:cs | (cs atSelector: selector class: classOrMeta) ~~ #none]). conflictingChangeSets isEmpty ifTrue: [^ nil]. self class logCr; log: 'CONFLICT found for ', classAndMethodPrintString, (' with newer changeset' asPluralBasedOn: conflictingChangeSets). conflictingChangeSets do: [:cs | self class log: ' ', cs name]. ^ changeRecordSource ! ! !FilePackage methodsFor: 'conflict checker' stamp: 'dew 3/30/2004 23:16'! conflictsWithUpdatedMethods "Check this package for conflicts with methods in the image which are in newer updates." | localFileName stream updateNumberString updateNumber imageUpdateNumber updateNumberChangeSet conflicts fileStream | localFileName _ FileDirectory localNameFor: fullName. stream _ ReadStream on: sourceSystem. stream upToAll: 'latest update: #'. updateNumberString _ stream upTo: $]. stream close. fileStream := FileStream readOnlyFileNamed: fullName. (fileStream contentsOfEntireFile includes: Character linefeed) ifTrue: [self notifyWithLabel: 'The changeset file ', localFileName, ' contains linefeeds. Proceed if... you know that this is okay (e.g. the file contains raw binary data).']. fileStream close. updateNumberString isEmpty ifFalse: "remove prepended junk, if any" [updateNumberString _ (updateNumberString findTokens: Character space) last]. updateNumberString asInteger ifNil: [(self confirm: 'Error: ', localFileName, ' has no valid Latest Update number in its header. Do you want to enter an update number for this file?') ifFalse: [^ self] ifTrue: [updateNumberString _ FillInTheBlank request: 'Please enter the estimated update number (e.g. 4332).']]. updateNumberString asInteger ifNil: [self inform: 'Conflict check cancelled.'. ^ self]. updateNumber _ updateNumberString asInteger. imageUpdateNumber _ SystemVersion current highestUpdate. updateNumber > imageUpdateNumber ifTrue: [(self confirm: 'Warning: The update number for this file (#', updateNumberString, ') is greater than the highest update number for this image (#', imageUpdateNumber asString, '). This probably means you need to update your image. Should we proceed anyway as if the file update number is #', imageUpdateNumber asString, '?') ifTrue: [updateNumber _ imageUpdateNumber. updateNumberString _ imageUpdateNumber asString] ifFalse: [^ self]]. updateNumberChangeSet _ self findUpdateChangeSetMatching: updateNumber. updateNumberChangeSet ifNil: [^ self]. Smalltalk isMorphic ifTrue: [self currentWorld findATranscript: self currentEvent]. self class logCr; logCr; log: 'Checking ', localFileName, ' (#', updateNumberString, ') for method conflicts with changesets after ', updateNumberChangeSet name, ' ...'. conflicts _ OrderedCollection new. self classes values do: [:pseudoClass | (Array with: pseudoClass with: pseudoClass metaClass) do: [:classOrMeta | classOrMeta selectors do: [:selector | | conflict | conflict _ self checkForMoreRecentUpdateThanChangeSet: updateNumberChangeSet pseudoClass: classOrMeta selector: selector. conflict ifNotNil: [conflicts add: conflict]. ]. ]. ]. self class logCr; log: conflicts size asString, (' conflict' asPluralBasedOn: conflicts), ' found.'; logCr. self class closeLog. ^ conflicts! ! !FilePackage methodsFor: 'conflict checker' stamp: 'dew 10/19/2003 21:29'! findUpdateChangeSetMatching: updateNumber "Find update-changeset beginning with updateNumber, or reasonably close." "This is to account for the fact that many changeset files are output from final releases, but may be tested for conflicts in a following alpha image, which will often not include that particular update-changeset from the final release but will contain ones near it. For example, if the file updateNumber is 5180 (from 3.5 final), but the image has no update-changeset beginning with 5180 because it's a 3.6alpha image (which starts at 5181), it will try up to 5190 and down to 5170 for a close match." | updateNumberChangeSet updateNumberToTry | updateNumberToTry _ updateNumber. updateNumberChangeSet _ nil. [updateNumberChangeSet isNil and: [updateNumberToTry notNil]] whileTrue: [updateNumberChangeSet _ ChangeSorter allChangeSets detect: [:cs | (cs name beginsWith: updateNumberToTry asString) and: [(cs name at: (updateNumberToTry asString size + 1)) isDigit not]] ifNone: [nil]. updateNumberToTry >= updateNumber ifTrue: [updateNumberToTry < (updateNumber + 10) ifTrue: [updateNumberToTry _ updateNumberToTry + 1] ifFalse: [updateNumberToTry _ updateNumber]]. updateNumberToTry <= updateNumber ifTrue: [updateNumberToTry > (updateNumber - 10) ifTrue: [updateNumberToTry _ updateNumberToTry - 1] ifFalse: [updateNumberToTry _ nil "we're done trying"]]. ]. updateNumberChangeSet ifNil: [(self confirm: 'Warning: No changeset beginning with ', updateNumber asString, ' (within +/- 10) was found in the image. You must have changesets going back this far in your image in order to accurately check for conflicts. Proceed anyway?') ifTrue: [updateNumberChangeSet _ ChangeSorter allChangeSets first]]. ^ updateNumberChangeSet! ! !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: 'ar 7/17/2005 03:36'! fileOut | fileName stream | fileName := UIManager default 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: 'initialize' stamp: 'yo 8/17/2004 09:53'! fromFileNamed: aName | stream | fullName := aName. stream := FileStream readOnlyFileNamed: aName. stream setConverterForCode. [self fileInFrom: stream] ensure:[stream close].! ! !FilePackage methodsFor: 'initialize' stamp: 'yo 8/17/2004 09:54'! fromFileNamed: aName encoding: encodingName | stream | fullName := aName. stream := FileStream readOnlyFileNamed: aName. stream converter: (TextConverter newForEncoding: encodingName). 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: 'reading' stamp: 'ar 7/16/2005 15:05'! fileInFrom: aStream | chgRec changes | changes := ChangeSet scanFile: aStream from: 0 to: aStream size. 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 methodsFor: '*monticello' stamp: 'al 12/2/2005 13:58'! classDefinition: string with: chgRec | tokens theClass | self flag: #traits. 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: '*monticello' stamp: 'avi 1/19/2004 23:47'! doIts ^ doIts! ! !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' stamp: 'ar 4/10/2005 18:46'! msgClassComment: string with: chgRec | tokens theClass | tokens := Scanner new scanTokens: string. (tokens size = 3 and:[(tokens at: 3) isString]) ifTrue:[ theClass := self getClass: tokens first. ^theClass commentString: tokens last]. (tokens size = 4 and:[(tokens at: 3) asString = 'class' and:[(tokens at: 4) isString]]) ifTrue:[ theClass := self getClass: tokens first. theClass metaClass commentString: tokens last]. ! ! !FilePackage methodsFor: 'private' stamp: 'ar 4/10/2005 18:46'! possibleSystemSource: chgRec | tokens | sourceSystem isEmpty ifTrue:[ tokens := Scanner new scanTokens: chgRec string. (tokens size = 1 and:[tokens first isString]) 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 class instanceVariableNames: ''! !FilePackage class methodsFor: 'conflict checker logging' stamp: 'dew 2/13/2004 23:33'! closeLog self logFileStream close. LogFileStream _ nil.! ! !FilePackage class methodsFor: 'conflict checker logging' stamp: 'dew 2/14/2004 23:03'! logCr Transcript cr. self logFileStream nextPut: Character cr. ! ! !FilePackage class methodsFor: 'conflict checker logging' stamp: 'stephaneducasse 11/1/2005 15:39'! logFileStream LogFileStream ifNil: [LogFileStream _ FileStream fileNamed: 'ConflictChecker.log'. LogFileStream setToEnd]. ^ LogFileStream! ! !FilePackage class methodsFor: 'conflict checker logging' stamp: 'dew 2/14/2004 23:03'! log: aString Transcript show: aString. self logFileStream nextPutAll: aString. ! ! !FilePackage class methodsFor: 'conflict checker preferences' stamp: 'dew 12/22/2002 00:19'! showIdenticalConflicts "(Not implemented yet. Need to implement versionFromChangeSet first for this to work for the 'false' case.)" "Set this to true if we want to show conflicts with methods which have an identical timestamp to the one being checked. This type of conflict usually just proves that you've already loaded the changeset (or some part of it) in your image." ^ true ! ! !FilePackage class methodsFor: 'conflict checker preferences' stamp: 'dew 2/13/2004 23:32'! verboseConflicts "Set this to true if we want to list each replacing (potentially conflicting) method being checked." ^ true! ! !FilePackage class methodsFor: 'instance creation' stamp: 'dew 10/26/2003 22:08'! conflictsWithUpdatedMethods: fullName | conflicts changeList | conflicts := (self fromFileNamed: fullName) conflictsWithUpdatedMethods. conflicts isEmpty ifTrue: [^ self]. changeList := ChangeList new. changeList changes: conflicts file: (FileDirectory default readOnlyFileNamed: fullName) close; openAsMorphName: 'Conflicts for ', (FileDirectory localNameFor: fullName) multiSelect: true ! ! !FilePackage class methodsFor: 'instance creation'! fromFileNamed: aName ^self new fromFileNamed: aName! ! !FilePackage class methodsFor: 'reader service' stamp: 'dew 12/16/2002 18:29'! fileReaderServicesForFile: fullName suffix: suffix ^(suffix = 'st') | (suffix = 'cs') | (suffix = '*') ifTrue: [self services] ifFalse: [#()]! ! !FilePackage class methodsFor: 'reader service' stamp: 'dew 12/16/2002 18:29'! serviceConflictsWithUpdatedMethods ^ SimpleServiceEntry provider: self label: 'conflicts with updated methods' selector: #conflictsWithUpdatedMethods: description: 'check for conflicts with more recently updated methods in the image, showing the conflicts in a transcript window' buttonLabel: 'conflicts'! ! !FilePackage class methodsFor: 'reader service' stamp: 'dew 12/16/2002 18:28'! services ^ Array with: self serviceConflictsWithUpdatedMethods! ! Object subclass: #FilePath instanceVariableNames: 'squeakPathName vmPathName converter' classVariableNames: '' poolDictionaries: '' category: 'Files-Directories'! !FilePath commentStamp: 'yo 10/19/2004 21:36' prior: 0! This class absorb the difference of internal and external representation of the file path. The idea is to keep the internal one as much as possible, and only when it goes to a primitive, the encoded file path, i.e. the native platform representation is passsed to the primitive. The converter used is obtained by "LanguageEnvironment defaultFileNameConverter". ! !FilePath methodsFor: 'conversion' stamp: 'yo 12/19/2003 21:10'! asSqueakPathName ^ self pathName. ! ! !FilePath methodsFor: 'conversion' stamp: 'ar 1/31/2005 11:16'! asString ^self asSqueakPathName! ! !FilePath methodsFor: 'conversion' stamp: 'yo 2/24/2005 18:45'! asVmPathName ^ vmPathName. ! ! !FilePath methodsFor: 'conversion' stamp: 'stephaneducasse 2/4/2006 20:31'! coverter: aTextConverter converter class ~= aTextConverter class ifTrue: [ converter := aTextConverter. vmPathName := squeakPathName convertToWithConverter: converter ]. ! ! !FilePath methodsFor: 'conversion' stamp: 'yo 12/19/2003 21:07'! pathName ^ squeakPathName. ! ! !FilePath methodsFor: 'conversion' stamp: 'stephaneducasse 2/4/2006 20:31'! pathName: p isEncoded: isEncoded converter := LanguageEnvironment defaultFileNameConverter. isEncoded ifTrue: [ squeakPathName := p convertFromWithConverter: converter. vmPathName := p. ] ifFalse: [ squeakPathName := p isOctetString ifTrue: [p asOctetString] ifFalse: [p]. vmPathName := squeakPathName convertToWithConverter: converter. ]. ! ! !FilePath methodsFor: 'conversion' stamp: 'yo 12/19/2003 21:07'! printOn: aStream aStream nextPutAll: 'FilePath('''. aStream nextPutAll: squeakPathName. aStream nextPutAll: ''')'. ! ! !FilePath methodsFor: 'file in/out' stamp: 'stephaneducasse 2/4/2006 20:31'! convertToCurrentVersion: varDict refStream: smartRefStrm "If we're reading in an old version with a system path instance variable, convert it to a vm path." varDict at: 'systemPathName' ifPresent: [ :x | vmPathName := x. ]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !FilePath methodsFor: 'file in/out' stamp: 'stephaneducasse 2/4/2006 20:31'! copySystemToVm (self class instVarNames includes: 'systemPathName') ifTrue: [ vmPathName := self instVarNamed: 'systemPathName'. ]. ! ! !FilePath methodsFor: 'testing' stamp: 'tpr 11/5/2004 11:39'! isNullPath "an empty path is used to represent the root path(s) when calling the primitive to list directory entries. Some users need to check for this and this is cleaner than grabbing the pathname and assuming it is a plain String" ^self pathName isEmpty! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FilePath class instanceVariableNames: ''! !FilePath class methodsFor: 'as yet unclassified' stamp: 'yo 2/24/2005 18:38'! classVersion ^ 1. ! ! !FilePath class methodsFor: 'instance creation' stamp: 'yo 12/19/2003 16:30'! pathName: pathName ^ self pathName: pathName isEncoded: false. ! ! !FilePath class methodsFor: 'instance creation' stamp: 'yo 12/19/2003 16:30'! pathName: pathName isEncoded: aBoolean ^ (self new) pathName: pathName isEncoded: aBoolean; yourself. ! ! Object subclass: #FileServices instanceVariableNames: '' classVariableNames: 'FileReaderRegistry' poolDictionaries: '' category: 'System-FileRegistry'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileServices class instanceVariableNames: ''! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 16:59'! allRegisteredServices "self allRegisteredServices" | col | col := OrderedCollection new. self registeredFileReaderClasses do: [:each | col addAll: (each services)]. ^ col! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 9/29/2005 12:30'! initialize "FileServices initialize" Smalltalk allClassesDo:[:aClass| (aClass class includesSelector: #fileReaderServicesForFile:suffix:) ifTrue:[self registerFileReader: aClass]].! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 17:00'! isReaderNamedRegistered: aSymbol "return if a given reader class has been registered. Note that this is on purpose that the argument is a symbol and not a class" ^ (self registeredFileReaderClasses collect: [:each | each name]) includes: aSymbol ! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 17:00'! itemsForDirectory: aFileDirectory "Answer a list of services appropriate when no file is selected." | services | services _ OrderedCollection new. self registeredFileReaderClasses do: [:reader | reader ifNotNil: [services addAll: (reader fileReaderServicesForDirectory: aFileDirectory) ]]. ^ services! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 17:00'! itemsForFile: fullName "Answer a list of services appropriate for a file of the given full name" | services suffix | suffix _ self suffixOf: fullName. services _ OrderedCollection new. self registeredFileReaderClasses do: [:reader | reader ifNotNil: [services addAll: (reader fileReaderServicesForFile: fullName suffix: suffix)]]. ^ services! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 16:59'! registeredFileReaderClasses FileReaderRegistry ifNil: [FileReaderRegistry _ OrderedCollection new]. ^ FileReaderRegistry! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 17:00'! registerFileReader: aProviderClass "register the given class as providing services for reading files" | registeredReaders | registeredReaders := self registeredFileReaderClasses. (registeredReaders includes: aProviderClass) ifFalse: [ registeredReaders addLast: aProviderClass ]! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/17/2005 02:36'! removeObsolete "FileServices removeObsolete" self registeredFileReaderClasses copy do:[:cls| cls isObsolete ifTrue:[self unregisterFileReader: cls]]! ! !FileServices class methodsFor: 'accessing' stamp: 'ar 7/16/2005 17:00'! unregisterFileReader: aProviderClass "unregister the given class as providing services for reading files" self registeredFileReaderClasses remove: aProviderClass ifAbsent: [nil]! ! ReadWriteStream subclass: #FileStream instanceVariableNames: 'rwmode' classVariableNames: '' poolDictionaries: '' category: 'Files-Kernel'! !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 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' stamp: 'stephaneducasse 2/4/2006 20:31'! contents "Return the contents of the receiver. Do not close or otherwise touch the receiver. Return data in whatever mode the receiver is in (e.g., binary or text)." | s savePos | savePos := self position. self position: 0. s := self next: self size. self position: savePos. ^s! ! !FileStream methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'nk 2/22/2001 17:07'! directoryEntry ^self directory entryAt: self localName! ! !FileStream methodsFor: 'accessing' stamp: 'ar 1/25/2001 19:33'! mimeTypes ^FileDirectory default mimeTypesFor: self name.! ! !FileStream methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:31'! next (position >= readLimit and: [self atEnd]) ifTrue: [^nil] ifFalse: [^collection at: (position := position + 1)]! ! !FileStream methodsFor: 'accessing' stamp: 'stephaneducasse 2/4/2006 20:31'! 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: 'converting' stamp: 'tk 2/4/2000 09:16'! asBinaryOrTextStream "I can switch between binary and text data" ^ self! ! !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: 'editing' stamp: 'stephaneducasse 2/4/2006 20:31'! viewGZipContents "View the contents of a gzipped file" | stringContents | self binary. stringContents := self contentsOfEntireFile. Cursor wait showWhile: [stringContents := (GZipReadStream on: stringContents) upToEnd]. stringContents := stringContents asString withSqueakLineEndings. Workspace new contents: stringContents; openLabel: 'Decompressed contents of: ', self localName! ! !FileStream methodsFor: 'file accessing' stamp: 'gk 2/10/2004 13:21'! asUrl "Convert my path into a file:// type url - a FileUrl." ^FileUrl pathParts: (self directory pathParts copyWith: self localName)! ! !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 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: 'mir 8/24/2004 17:58'! readOnlyStream ^self readOnly! ! !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 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: '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: 'fileIn/Out' stamp: 'di 10/31/2001 12:07'! fileIntoNewChangeSet "File all of my contents into a new change set." self readOnly. ChangeSorter newChangesFromStream: self named: (self localName) ! ! !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: 'positioning' stamp: 'JMM 5/24/2001 22:58'! truncate: pos "Truncate file to pos" self subclassResponsibility! ! !FileStream methodsFor: 'printing' stamp: 'tk 12/5/2001 09:12'! longPrintOn: aStream "Do nothing, so it will print short. Called to print the error file. If the error was in a file operation, we can't read the contents of that file. Just print its name instead." ! ! !FileStream methodsFor: 'printing' stamp: 'tk 12/5/2001 09:32'! longPrintOn: aStream limitedTo: sizeLimit indent: indent "Do nothing, so it will print short. Called to print the error file. If the error was in a file operation, we can't read the contents of that file. Just print its name instead." aStream cr! ! !FileStream methodsFor: 'printing'! printOn: aStream super printOn: aStream. aStream nextPutAll: ' on '. self file printOn: aStream! ! !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 methodsFor: 'testing'! atEnd "Answer true if the current position is >= the end of file position. 1/31/96 sw: subclassResponsibility" self subclassResponsibility! ! !FileStream methodsFor: '*MorphicExtras-file accessing' stamp: 'fbs 2/2/2005 13:23'! url "Convert my path into a file:// type url String." ^self asUrl asString! ! !FileStream methodsFor: '*network-uri' stamp: 'mir 3/24/2005 18:44'! uri ^self directory uri resolveRelativeURI: self localName! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileStream class instanceVariableNames: ''! !FileStream class methodsFor: 'browser requests' stamp: 'stephaneducasse 2/4/2006 20:32'! httpPostDocument: url args: argsDict | argString | argString := argsDict ifNotNil: [argString := HTTPSocket argString: argsDict] ifNil: ['']. ^self post: argString url: url , argString ifError: [self halt]! ! !FileStream class methodsFor: 'browser requests' stamp: 'stephaneducasse 2/4/2006 20:32'! httpPostMultipart: url args: argsDict | mimeBorder argsStream crLf fieldValue resultStream result | " do multipart/form-data encoding rather than x-www-urlencoded " crLf := String crlf. mimeBorder := '----squeak-', Time millisecondClockValue printString, '-stuff-----'. "encode the arguments dictionary" argsStream := WriteStream on: String new. argsDict associationsDo: [:assoc | assoc value do: [ :value | "print the boundary" argsStream nextPutAll: '--', mimeBorder, crLf. " check if it's a non-text field " argsStream nextPutAll: 'Content-disposition: form-data; name="', assoc key, '"'. (value isKindOf: MIMEDocument) ifFalse: [fieldValue := value] ifTrue: [argsStream nextPutAll: ' filename="', value url pathForFile, '"', crLf, 'Content-Type: ', value contentType. fieldValue := (value content ifNil: [(FileStream fileNamed: value url pathForFile) contentsOfEntireFile] ifNotNil: [value content]) asString]. " Transcript show: 'field=', key, '; value=', fieldValue; cr. " argsStream nextPutAll: crLf, crLf, fieldValue, crLf. ]]. argsStream nextPutAll: '--', mimeBorder, '--'. resultStream := self post: ('Content-type: multipart/form-data; boundary=', mimeBorder, crLf, 'Content-length: ', argsStream contents size printString, crLf, crLf, argsStream contents) url: url ifError: [^'Error in post ' url asString]. "get the header of the reply" result := resultStream upToEnd. ^MIMEDocument content: result! ! !FileStream class methodsFor: 'browser requests' stamp: 'mir 2/2/2001 14:23'! post: data target: target url: url ifError: errorBlock ^self concreteStream new post: data target: target url: url ifError: errorBlock! ! !FileStream class methodsFor: 'browser requests' stamp: 'mir 2/2/2001 14:23'! post: data url: url ifError: errorBlock ^self post: data target: nil url: url ifError: errorBlock! ! !FileStream class methodsFor: 'browser requests' stamp: 'stephaneducasse 2/4/2006 20:32'! requestURL: url target: target "FileStream requestURL:'http://isgwww.cs.uni-magdeburg.de/~raab' target: ':=blank' " ^self concreteStream new requestURL: url target: target! ! !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: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !FileStream class methodsFor: 'concrete classes' stamp: 'yo 7/5/2004 20:18'! concreteStream "Who should we really direct class queries to? " ^ MultiByteFileStream. ! ! !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.! ! !FileStream class methodsFor: 'file reader services' stamp: 'yo 7/5/2004 21:01'! cs ^ 'cs' clone. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'stephaneducasse 2/4/2006 20:32'! fileIn: fullName "File in the entire contents of the file specified by the name provided" | ff | fullName ifNil: [^ Beeper beep]. ff := self readOnlyFileNamed: (GZipReadStream uncompressedFileName: fullName). ff fileIn. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'nk 7/16/2003 15:49'! fileReaderServicesForFile: fullName suffix: suffix "Answer services for the given file" ^ ((self isSourceFileSuffix: suffix) or: [ suffix = '*' ]) ifTrue: [{self serviceRemoveLineFeeds. self serviceFileIn}] ifFalse: [#()]! ! !FileStream class methodsFor: 'file reader services' stamp: 'tpr 9/15/2005 15:06'! isSourceFileSuffix: suffix ^ FileStream sourceFileSuffixes includes: suffix ! ! !FileStream class methodsFor: 'file reader services' stamp: 'yo 7/5/2004 21:00'! multiCs ^ 'mcs' clone. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'yo 7/5/2004 21:01'! multiSt ^ 'mst' clone. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'stephaneducasse 2/4/2006 20:32'! removeLineFeeds: fullName | fileContents | fileContents := ((FileStream readOnlyFileNamed: fullName) wantsLineEndConversion: true) contentsOfEntireFile. (FileStream newFileNamed: fullName) nextPutAll: fileContents; close.! ! !FileStream class methodsFor: 'file reader services' stamp: 'sw 2/17/2002 01:38'! serviceFileIn "Answer a service for filing in an entire file" ^ SimpleServiceEntry provider: self label: 'fileIn entire file' selector: #fileIn: description: 'file in the entire contents of the file, which is expected to contain Smalltalk code in fileout ("chunk") format' buttonLabel: 'filein'! ! !FileStream class methodsFor: 'file reader services' stamp: 'nk 11/26/2002 12:49'! serviceRemoveLineFeeds "Answer a service for removing linefeeds from a file" ^ FileModifyingSimpleServiceEntry provider: self label: 'remove line feeds' selector: #removeLineFeeds: description: 'remove line feeds in file' buttonLabel: 'remove lfs'! ! !FileStream class methodsFor: 'file reader services' stamp: 'sd 2/1/2002 22:28'! services ^ Array with: self serviceRemoveLineFeeds with: self serviceFileIn ! ! !FileStream class methodsFor: 'file reader services' stamp: 'yo 7/7/2004 09:43'! sourceFileSuffixes ^ {FileStream st. FileStream cs. FileStream multiSt. FileStream multiCs} asSet asArray. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'yo 7/5/2004 21:01'! st ^ 'st' clone. ! ! !FileStream class methodsFor: 'file reader services' stamp: 'stephaneducasse 2/4/2006 20:32'! writeSourceCodeFrom: aStream baseName: baseName isSt: stOrCsFlag useHtml: useHtml | extension converter f fileName | aStream contents isAsciiString ifTrue: [ stOrCsFlag ifTrue: [ extension := (FileDirectory dot, FileStream st). ] ifFalse: [ extension := (FileDirectory dot, FileStream cs). ]. converter := MacRomanTextConverter new. ] ifFalse: [ stOrCsFlag ifTrue: [ extension := (FileDirectory dot, FileStream st "multiSt"). ] ifFalse: [ extension := (FileDirectory dot, FileStream cs "multiCs"). ]. converter := UTF8TextConverter new. ]. fileName := useHtml ifTrue: [baseName, '.html'] ifFalse: [baseName, extension]. f := FileStream newFileNamed: fileName. f ifNil: [^ self error: 'Cannot open file']. (converter isMemberOf: UTF8TextConverter) ifTrue: [f binary. UTF8TextConverter writeBOMOn: f]. f text. f converter: converter. f nextPutAll: aStream contents. f close. ! ! !FileStream class methodsFor: 'initialize-release' stamp: 'hg 8/3/2000 18:00'! initialize FileList registerFileReader: self! ! !FileStream class methodsFor: 'instance creation' stamp: 'CdG 10/19/2005 23:21'! detectFile: aBlock do: anotherBlock | file | file := aBlock value. ^ file ifNil: [ nil ] ifNotNil: [ [anotherBlock value: file] ensure: [file close]]! ! !FileStream class methodsFor: 'instance creation'! fileNamed: fileName ^ self concreteStream fileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'instance creation' stamp: 'bkv 1/22/2004 17:28'! fileNamed: fileName do: aBlock "Avi Bryant says, ''This idiom is quite common in other languages that make heavy use of closures (i.e. Lisp (with-file 'foo' (f) ...) and Ruby (File.open('foo'){|f|...})). It's time Squeak had it, too.'' Returns the result of aBlock." ^ self detectFile: [ self fileNamed: fileName ] do: aBlock! ! !FileStream class methodsFor: 'instance creation' stamp: 'tpr 10/16/2001 12:49'! forceNewFileNamed: fileName "Create a new file with the given name, and answer a stream opened for writing on that file. If the file already exists, delete it without asking before creating the new file." ^self concreteStream forceNewFileNamed: fileName! ! !FileStream class methodsFor: 'instance creation' stamp: 'bkv 1/22/2004 17:29'! forceNewFileNamed: fileName do: aBlock "Avi Bryant says, ''This idiom is quite common in other languages that make heavy use of closures (i.e. Lisp (with-file 'foo' (f) ...) and Ruby (File.open('foo'){|f|...})). It's time Squeak had it, too.'' Returns the result of aBlock." ^ self detectFile: [ self forceNewFileNamed: fileName ] do: aBlock! ! !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' stamp: 'bkv 1/22/2004 17:28'! newFileNamed: fileName do: aBlock "Avi Bryant says, ''This idiom is quite common in other languages that make heavy use of closures (i.e. Lisp (with-file 'foo' (f) ...) and Ruby (File.open('foo'){|f|...})). It's time Squeak had it, too.'' Returns the result of aBlock." ^ self detectFile: [ self newFileNamed: fileName ] do: aBlock! ! !FileStream class methodsFor: 'instance creation'! oldFileNamed: fileName ^ self concreteStream oldFileNamed: (self fullName: fileName)! ! !FileStream class methodsFor: 'instance creation' stamp: 'bkv 1/22/2004 17:29'! oldFileNamed: fileName do: aBlock "Avi Bryant says, ''This idiom is quite common in other languages that make heavy use of closures (i.e. Lisp (with-file 'foo' (f) ...) and Ruby (File.open('foo'){|f|...})). It's time Squeak had it, too.'' Returns the result of aBlock." ^ self detectFile: [ self oldFileNamed: fileName ] do: aBlock! ! !FileStream class methodsFor: 'instance creation' stamp: 'stephaneducasse 2/4/2006 20:32'! 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: 'instance creation' stamp: 'bkv 1/22/2004 17:29'! readOnlyFileNamed: fileName do: aBlock "Avi Bryant says, ''This idiom is quite common in other languages that make heavy use of closures (i.e. Lisp (with-file 'foo' (f) ...) and Ruby (File.open('foo'){|f|...})). It's time Squeak had it, too.'' Returns the result of aBlock." ^ self detectFile: [ self readOnlyFileNamed: fileName ] do: aBlock! ! Error subclass: #FileStreamException instanceVariableNames: 'fileName' classVariableNames: '' poolDictionaries: '' category: '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! ! ClassTestCase subclass: #FileStreamTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Files-Tests'! !FileStreamTest methodsFor: 'as yet unclassified' stamp: 'CdG 10/19/2005 23:20'! testDetectFileDo "Mantis #1838" [(FileDirectory default forceNewFileNamed: 'filestream.tst') nextPutAll: '42'; close. FileStream detectFile: [FileDirectory default oldFileNamed: 'filestream.tst'] do: [:file | self assert: file notNil. self deny: file closed. self assert: file contentsOfEntireFile = '42']] ensure: [FileDirectory default deleteFileNamed: 'filestream.txt' ifAbsent: nil]! ! Url subclass: #FileUrl instanceVariableNames: 'host path isAbsolute' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !FileUrl commentStamp: 'gk 10/21/2005 10:58' prior: 0! This class models a file URL according to (somewhat) RFC1738, see http://www.w3.org/Addressing/rfc1738.txt Here is the relevant part of the RFC: 3.10 FILES The file URL scheme is used to designate files accessible on a particular host computer. This scheme, unlike most other URL schemes, does not designate a resource that is universally accessible over the Internet. A file URL takes the form: file:/// where is the fully qualified domain name of the system on which the is accessible, and is a hierarchical directory path of the form //.../. For example, a VMS file DISK$USER:[MY.NOTES]NOTE123456.TXT might become As a special case, can be the string "localhost" or the empty string; this is interpreted as `the machine from which the URL is being interpreted'. The file URL scheme is unusual in that it does not specify an Internet protocol or access method for such files; as such, its utility in network protocols between hosts is limited. From the above we can conclude that the RFC says that the part never starts or ends with a slash and is always absolute. If the last name can be a directory instead of a file is not specified clearly. The path is stored as a SequenceableCollection of path parts. Notes regarding non RFC features in this class: - If the last path part is the empty string, then the FileUrl is referring to a directory. This is also shown with a trailing slash when converted to a String. - The FileUrl has an attribute isAbsolute which signals if the path should be considered absolute or relative to the current directory. This distinction is not visible in the String representation of FileUrl, since the RFC does not have that. - Fragment is supported (kept for historical reasons) ! !FileUrl methodsFor: 'access' stamp: 'gk 10/21/2005 10:21'! directoryUrl "The path always has at least one element so this works." ^self copy path: (path copyFrom: 1 to: path size - 1)! ! !FileUrl methodsFor: 'access' stamp: 'gk 10/21/2005 11:14'! fileName "Return the last part of the path, most often a filename but can also be a directory." ^self path last! ! !FileUrl methodsFor: 'access' stamp: 'ar 10/13/2004 17:54'! pathForFile "Path using local file system's delimiter. $\ or $:" ^FileDirectory default pathFromUrl: self! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 2/10/2004 10:16'! host "Return the host name, either 'localhost', '', or a fully qualified domain name." ^host ifNil: ['']! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 2/12/2004 16:22'! host: hostName "Set the host name, either 'localhost', '', or a fully qualified domain name." host _ hostName! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 10/21/2005 11:12'! isAbsolute "Should the path be considered absolute to the filesystem instead of relative to the default directory?" ^isAbsolute! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 10/21/2005 11:13'! isAbsolute: aBoolean "Set if the path should be considered absolute to the filesystem instead of relative to the default directory." isAbsolute _ aBoolean! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 2/10/2004 00:15'! path "Return an ordered collection of the path elements." ^path! ! !FileUrl methodsFor: 'accessing' stamp: 'gk 10/21/2005 11:11'! path: aCollection "Set the collection of path elements." path _ aCollection! ! !FileUrl methodsFor: 'classification' stamp: 'gk 2/10/2004 10:34'! scheme ^self class schemeName! ! !FileUrl methodsFor: 'classification' stamp: 'gk 2/10/2004 10:34'! schemeName ^self class schemeName! ! !FileUrl methodsFor: 'copying' stamp: 'gk 10/21/2005 11:15'! copy "Be sure not to share the path with the copy." ^self clone path: path copy! ! !FileUrl methodsFor: 'downloading' stamp: 'gk 2/10/2004 13:06'! default "Use the default local Squeak file directory." | local | local _ self class pathParts: (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: 'ASF 4/30/2005 16:37'! retrieveContents | file pathString s type entries | pathString _ self pathForFile. file _ [FileStream readOnlyFileNamed: pathString] on: FileDoesNotExistException do:[:ex| ex return: nil]. file ifNotNil: [ type _ file mimeTypes. type ifNotNil:[type _ type first]. type ifNil:[type _ MIMEDocument guessTypeFromName: self path last]. ^MIMELocalFileDocument contentType: type contentStream: file]. "see if it's a directory..." entries _ [(FileDirectory on: pathString) entries] on: InvalidDirectoryError do:[:ex| ex return: nil]. entries ifNil:[^nil]. s _ WriteStream on: String new. (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: 'paths' stamp: 'gk 2/10/2004 00:19'! pathDirString "Path to directory as url, using slash as delimiter. Filename is left out." ^String streamContents: [ :s | isAbsolute ifTrue: [ s nextPut: $/ ]. 1 to: self path size - 1 do: [ :ii | s nextPutAll: (path at: ii); nextPut: $/]]! ! !FileUrl methodsFor: 'paths' stamp: 'gk 10/21/2005 10:01'! pathForDirectory "Path using local file system's pathname delimiter. DOS paths with drive letters should not be prepended with a delimiter even though they are absolute. Filename is left out." | delimiter | delimiter _ FileDirectory default pathNameDelimiter. ^String streamContents: [ :s | (self isAbsolute and: [self firstPartIsDriveLetter not]) ifTrue: [ s nextPut: delimiter ]. 1 to: self path size - 1 do: [ :ii | s nextPutAll: (path at: ii); nextPut: delimiter]]! ! !FileUrl methodsFor: 'paths' stamp: 'gk 2/10/2004 10:22'! 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: 'printing' stamp: 'fbs 2/2/2005 13:09'! printOn: aStream "Return the FileUrl according to RFC1738 plus supporting fragments: 'file:///#' Note that being '' is equivalent to 'localhost'. Note: The pathString can not start with a leading $/ to indicate an 'absolute' file path. This is not according to RFC1738 where the path should have no leading or trailing slashes, and always be considered absolute relative to the filesystem." aStream nextPutAll: self schemeName, '://'. host ifNotNil: [aStream nextPutAll: host]. aStream nextPut: $/; nextPutAll: self pathString. fragment ifNotNil: [aStream nextPut: $#; nextPutAll: fragment encodeForHTTP].! ! !FileUrl methodsFor: 'testing' stamp: 'gk 2/9/2004 20:32'! firstPartIsDriveLetter "Return true if the first part of the path is a letter followed by a $: like 'C:' " | firstPart | path isEmpty ifTrue: [^false]. firstPart _ path first. ^firstPart size = 2 and: [ firstPart first isLetter and: [firstPart last = $:]]! ! !FileUrl methodsFor: 'private-initialization' stamp: 'gk 2/10/2004 13:05'! host: aHostString pathParts: aCollection isAbsolute: aBoolean host _ aHostString. path _ aCollection. isAbsolute _ aBoolean! ! !FileUrl methodsFor: 'private-initialization' stamp: 'gk 2/12/2004 16:01'! initializeFromPathString: aPathString " is a file path as a String. We construct a path collection using various heuristics." | pathString hasDriveLetter | pathString _ aPathString. pathString isEmpty ifTrue: [pathString _ '/']. path _ (pathString findTokens: '/') collect: [:token | token unescapePercents]. "A path like 'C:' refers in practice to 'c:/'" ((pathString endsWith: '/') or: [(hasDriveLetter _ self firstPartIsDriveLetter) and: [path size = 1]]) ifTrue: [path add: '']. "Decide if we are absolute by checking for leading $/ or beginning with drive letter. Smarts for other OSes?" self isAbsolute: ((pathString beginsWith: '/') or: [hasDriveLetter ifNil: [self firstPartIsDriveLetter]])! ! !FileUrl methodsFor: 'private-initialization' stamp: 'gk 2/10/2004 13:04'! pathParts: aCollection isAbsolute: aBoolean ^self host: nil pathParts: aCollection isAbsolute: aBoolean! ! !FileUrl methodsFor: 'private-initialization' stamp: 'gk 2/12/2004 16:11'! privateInitializeFromText: aString "Calculate host and path from a file URL in String format. Some malformed formats are allowed and interpreted by guessing." | schemeName pathString bare hasDriveLetter stream char i | bare _ aString withBlanksTrimmed. schemeName _ Url schemeNameForString: bare. (schemeName isNil or: [schemeName ~= self schemeName]) ifTrue: [ host _ ''. pathString _ bare] ifFalse: [ "First remove schemeName and colon" bare _ bare copyFrom: (schemeName size + 2) to: bare size. "A proper file URL then has two slashes before host, A malformed URL is interpreted as using syntax file:." (bare beginsWith: '//') ifTrue: [i _ bare indexOf: $/ startingAt: 3. i=0 ifTrue: [ host _ bare copyFrom: 3 to: bare size. pathString _ ''] ifFalse: [ host _ bare copyFrom: 3 to: i-1. pathString _ bare copyFrom: host size + 3 to: bare size]] ifFalse: [host _ ''. pathString _ bare]]. self initializeFromPathString: pathString ! ! !FileUrl methodsFor: 'private-initialization' stamp: 'gk 2/12/2004 16:29'! privateInitializeFromText: pathString relativeTo: aUrl " should be a filesystem path. This url is adjusted to be aUrl + the path." | bare newPath | self host: aUrl host. self initializeFromPathString: pathString. self isAbsolute: aUrl isAbsolute. newPath _ aUrl path copy. newPath removeLast. "empty string that says its a directory" path do: [ :token | ((token ~= '..') and: [token ~= '.']) ifTrue: [ newPath addLast: token unescapePercents ]. token = '..' ifTrue: [ newPath isEmpty ifFalse: [ newPath last = '..' ifFalse: [ newPath removeLast ] ] ]. "token = '.' do nothing" ]. path _ newPath ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FileUrl class instanceVariableNames: ''! !FileUrl class methodsFor: 'constants' stamp: 'gk 2/10/2004 10:33'! schemeName ^'file'! ! !FileUrl class methodsFor: 'instance creation' stamp: 'gk 2/10/2004 12:16'! absoluteFromText: aString "Method that can be called explicitly to create a FileUrl." ^self new privateInitializeFromText: aString! ! !FileUrl class methodsFor: 'instance creation' stamp: 'gk 2/10/2004 13:04'! host: aHost pathParts: aCollectionOfPathParts isAbsolute: aBoolean "Create a FileUrl." ^self new host: aHost pathParts: aCollectionOfPathParts isAbsolute: aBoolean! ! !FileUrl class methodsFor: 'instance creation' stamp: 'gk 2/10/2004 13:10'! pathParts: aCollectionOfPathParts "Create a FileUrl." ^self host: nil pathParts: aCollectionOfPathParts isAbsolute: true! ! !FileUrl class methodsFor: 'instance creation' stamp: 'gk 2/10/2004 13:06'! pathParts: aCollectionOfPathParts isAbsolute: aBoolean "Create a FileUrl." ^self host: nil pathParts: aCollectionOfPathParts isAbsolute: aBoolean! ! ClassTestCase subclass: #FileUrlTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'NetworkTests-Url'! !FileUrlTest methodsFor: 'testing' stamp: 'fbs 2/2/2005 12:43'! testAsString | target url | target := 'file://localhost/etc/rc.conf'. url := target asUrl. self assert: url asString = target. ! ! StringHolder subclass: #FillInTheBlank instanceVariableNames: 'acceptOnCR done responseUponCancel' classVariableNames: '' poolDictionaries: '' category: 'ST80-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: '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: 'initialize-release' stamp: 'sw 1/31/2000 14:42'! initialize super initialize. acceptOnCR _ false. done _ false. 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: 'nk 7/30/2004 21:50'! 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. 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: 'ar 3/18/2001 00:53'! 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: (ActiveHand ifNil:[Sensor]) cursorPoint! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'ar 3/18/2001 00:54'! 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: (ActiveHand ifNil:[Sensor]) cursorPoint! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'nk 7/30/2004 21:50'! 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. model contents: defaultAnswer. fillInView := self fillInTheBlankViewClass on: model message: queryString centerAt: aPoint. ^model show: fillInView! ! !FillInTheBlank class methodsFor: 'instance creation' stamp: 'nk 7/30/2004 21:50'! 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. 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: 'th 9/17/2002 16:46'! controlInitialize model acceptOnCR ifFalse: [^ super controlInitialize]. self setMark: self markBlock stringIndex. self setPoint: self pointBlock 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: '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: 'event handling' stamp: 'ar 10/7/2000 15:47'! handlesMouseDown: evt ^true! ! !FillInTheBlankMorph methodsFor: 'event handling' stamp: 'md 10/22/2003 16:20'! mouseDown: evt (self containsPoint: evt position) ifFalse:[^ Beeper beep]. "sent in response to outside modal click" evt hand grabMorph: self. "allow repositioning"! ! !FillInTheBlankMorph methodsFor: 'geometry' stamp: 'jrp 7/6/2005 21:42'! extent: aPoint "change the receiver's extent" super extent: aPoint . self setDefaultParameters. self updateColor! ! !FillInTheBlankMorph methodsFor: 'grabbing/dropping' stamp: 'ar 10/7/2000 15:50'! undoGrabCommand ^nil! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'dgd 4/3/2006 13:34'! createAcceptButton "create the [accept] button" | result frame | result := SimpleButtonMorph new target: self; color: ColorTheme current okColor. result borderColor: (Preferences menuAppearance3d ifTrue: [#raised] ifFalse: [result color twiceDarker]). result label: 'Accept(s)' translated; actionSelector: #accept. result setNameTo: 'accept'. frame := LayoutFrame new. frame rightFraction: 0.5; rightOffset: -10; bottomFraction: 1.0; bottomOffset: -2. result layoutFrame: frame. self addMorph: result. self updateColor: result color: result color intensity: 2. ^ result! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'dgd 4/3/2006 13:34'! createCancelButton "create the [cancel] button" | result frame | result := SimpleButtonMorph new target: self; color: ColorTheme current cancelColor. result borderColor: (Preferences menuAppearance3d ifTrue: [#raised] ifFalse: [result color twiceDarker]). result label: 'Cancel(l)' translated; actionSelector: #cancel. result setNameTo: 'cancel'. frame := LayoutFrame new. frame leftFraction: 0.5; leftOffset: 10; bottomFraction: 1.0; bottomOffset: -2. result layoutFrame: frame. self addMorph: result. self updateColor: result color: result color intensity: 2. ^ result! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'yo 7/2/2004 17:52'! createQueryTextMorph: queryString "create the queryTextMorph" | result frame | result := TextMorph new contents: queryString. result setNameTo: 'query' translated. result lock. frame := LayoutFrame new. frame topFraction: 0.0; topOffset: 2. frame leftFraction: 0.5; leftOffset: (result width // 2) negated. result layoutFrame: frame. self addMorph: result. ^ result! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sd 11/8/2003 15:56'! createTextPaneExtent: answerExtent acceptBoolean: acceptBoolean topOffset: topOffset buttonAreaHeight: buttonAreaHeight "create the textPane" | result frame | result := PluggableTextMorph on: self text: #response accept: #response: readSelection: #selectionInterval menu: #codePaneMenu:shifted:. result extent: answerExtent. result hResizing: #spaceFill; vResizing: #spaceFill. result borderWidth: 1. result hasUnacceptedEdits: true. result acceptOnCR: acceptBoolean. result setNameTo: 'textPane'. frame := LayoutFrame new. frame leftFraction: 0.0; rightFraction: 1.0; topFraction: 0.0; topOffset: topOffset; bottomFraction: 1.0; bottomOffset: buttonAreaHeight negated. result layoutFrame: frame. self addMorph: result. ^ result! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color white! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'ar 10/10/2000 22:35'! delete self breakDependents. super delete.! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sd 11/8/2003 15:57'! initialize super initialize. self setDefaultParameters. self extent: 400 @ 150. responseUponCancel := ''. Preferences roundedMenuCorners ifTrue: [self useRoundedCorners]. ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sw 1/31/2000 11:01'! responseUponCancel: anObject responseUponCancel _ anObject ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'dgd 4/3/2006 10:54'! setDefaultParameters "change the receiver's appareance parameters" | colorFromMenu worldColor menuColor | colorFromMenu := Preferences menuColorFromWorld and: [Display depth > 4] and: [(worldColor := self currentWorld color) isColor]. menuColor := colorFromMenu ifTrue: [worldColor luminance > 0.7 ifTrue: [worldColor mixed: 0.85 with: Color black] ifFalse: [worldColor mixed: 0.4 with: Color white]] ifFalse: [Preferences menuColor]. self color: menuColor. self borderWidth: Preferences menuBorderWidth. Preferences menuAppearance3d ifTrue: [ self borderStyle: BorderStyle thinGray. self addDropShadow; shadowColor: (TranslucentColor r: 0.0 g: 0.0 b: 0.0 alpha: 0.666); shadowOffset: 1 @ 1 ] ifFalse: [ | menuBorderColor | menuBorderColor := colorFromMenu ifTrue: [worldColor muchDarker] ifFalse: [Preferences menuBorderColor]. self borderColor: menuBorderColor. ]. self layoutInset: 3. ! ! !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: 'sd 11/8/2003 15:58'! setQuery: queryString initialAnswer: initialAnswer answerExtent: answerExtent acceptOnCR: acceptBoolean | query topOffset accept cancel buttonAreaHeight | response := initialAnswer. done := false. self removeAllMorphs. self layoutPolicy: ProportionalLayout new. query := self createQueryTextMorph: queryString. topOffset := query height + 4. accept := self createAcceptButton. cancel := self createCancelButton. buttonAreaHeight := (accept height max: cancel height) + 4. textPane := self createTextPaneExtent: answerExtent acceptBoolean: acceptBoolean topOffset: topOffset buttonAreaHeight: buttonAreaHeight. self extent: (query extent x max: answerExtent x) + 4 @ (topOffset + answerExtent y + 4 + buttonAreaHeight). ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'NS 8/1/2000 11:44'! setQuery: queryString initialAnswer: initialAnswer answerHeight: answerHeight acceptOnCR: acceptBoolean self setQuery: queryString initialAnswer: initialAnswer answerExtent: (self class defaultAnswerExtent x @ answerHeight) acceptOnCR: acceptBoolean ! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'sd 11/8/2003 15:58'! updateColor "update the recevier's fillStyle" | textPaneBorderColor | self updateColor: self color: self color intensity: 1. textPane isNil ifTrue: [^ self]. textPaneBorderColor := self borderColor == #raised ifTrue: [#inset] ifFalse: [self borderColor]. textPane borderColor: textPaneBorderColor! ! !FillInTheBlankMorph methodsFor: 'initialization' stamp: 'jrp 7/6/2005 21:44'! updateColor: aMorph color: aColor intensity: anInteger "update the apareance of aMorph" | fill | Preferences gradientMenu ifFalse: [^ self]. fill _ GradientFillStyle ramp: {0.0 -> Color white. 1 -> aColor}. fill radial: false; origin: aMorph topLeft; direction: 0 @ aMorph height. aMorph fillStyle: fill! ! !FillInTheBlankMorph methodsFor: 'invoking' stamp: 'jrp 10/4/2004 16:06'! 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]. (ProvideAnswerNotification signal: (self submorphOfClass: TextMorph) userString) ifNotNilDo: [:answer | self delete. w doOneCycle. ^ response _ (answer == #default) ifTrue: [response] ifFalse: [answer]]. done _ false. w activeHand newKeyboardFocus: textPane. [done] whileFalse: [w doOneCycle]. self delete. w doOneCycle. ^ response ! ! !FillInTheBlankMorph methodsFor: 'invoking' stamp: 'RAA 7/19/2000 20:40'! morphicLayerNumber ^10.6! ! !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: '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: '*services-base' stamp: 'rr 1/9/2006 11:52'! selection "answers what is actually selected in the morph" ^ textPane selectionInterval! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FillInTheBlankMorph class instanceVariableNames: ''! !FillInTheBlankMorph class methodsFor: 'default constants' stamp: 'dgd 4/27/2003 17:10'! defaultAnswerExtent ^ (200@60 * (Preferences standardMenuFont height / 12)) rounded! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'ar 3/18/2001 00:54'! 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: (ActiveHand ifNil:[Sensor]) cursorPoint! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'ar 3/18/2001 00:54'! 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: ActiveHand cursorPoint! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'ar 3/17/2001 23:43'! 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: ActiveWorld ! ! !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: 'NS 8/1/2000 11:44'! 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." ^ self request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean answerExtent: self defaultAnswerExtent! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'NS 8/1/2000 11:39'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean answerExtent: answerExtent "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 answerExtent: answerExtent acceptOnCR: acceptBoolean. aFillInTheBlankMorph responseUponCancel: returnOnCancel. aWorld addMorph: aFillInTheBlankMorph centeredNear: aPoint. ^ aFillInTheBlankMorph getUserResponse ! ! !FillInTheBlankMorph class methodsFor: 'instance creation' stamp: 'NS 8/1/2000 11:43'! request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean answerHeight: answerHeight "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." ^ self request: queryString initialAnswer: defaultAnswer centerAt: aPoint inWorld: aWorld onCancelReturn: returnOnCancel acceptOnCR: acceptBoolean answerExtent: self defaultAnswerExtent x @ answerHeight! ! !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 commentStamp: '' prior: 0! FillStyle is an abstract base class for fills in the BalloonEngine.! !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: 'converting' stamp: 'ar 11/9/1998 13:53'! asColor ^self subclassResponsibility! ! !FillStyle methodsFor: 'converting' stamp: 'ar 6/4/2001 00:41'! mixed: fraction with: aColor ^self asColor mixed: fraction with: aColor! ! !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! ! MagnifierMorph subclass: #FishEyeMorph instanceVariableNames: 'gridNum d clipRects toRects quads savedExtent' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Demo'! !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! ! !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: 'dgd 2/14/2003 21:39'! initialize "initialize the state of the receiver" super initialize. "" "magnification should be always 1" magnification _ 1. d _ 1.3. self extent: 130 @ 130! ! !FishEyeMorph methodsFor: 'initialization' stamp: 'md 11/14/2003 16: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: 'dgd 2/21/2003 23:04'! transformY: aFloatArray | focus subArray dMaxY | focus := srcExtent y asFloat / 2. dMaxY := (aFloatArray first) <= focus ifTrue: [0.0 - focus] ifFalse: [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: 'magnifying' 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: 'menu' stamp: 'yo 12/17/1999 12:03'! chooseMagnification: evt ! ! !FishEyeMorph methodsFor: 'menus' stamp: 'dgd 9/21/2003 17:55'! chooseMagnification self inform: 'Magnification is fixed, sorry.' translated! ! !FishEyeMorph methodsFor: 'parts bin' stamp: 'sw 6/28/2001 11:32'! initializeToStandAlone super initializeToStandAlone. "magnification should be always 1" magnification _ 1. d _ 1.3. self extent: 130@130. ! ! !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 class instanceVariableNames: ''! !FishEyeMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 12:47'! descriptionForPartsBin ^ self partName: 'FishEye' categories: #('Useful') documentation: 'An extreme-wide-angle lens'! ! Object subclass: #FixUnderscores instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FixUnderscores'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FixUnderscores class instanceVariableNames: ''! !FixUnderscores class methodsFor: 'class initialization' stamp: 'bf 10/18/2005 12:13'! initialize "self initialize" self fixFonts. self inform: 'Fonts were _fixed_.\The arrow glyph is now Character value ' withCRs, self arrowChar asInteger hex, ' ($', self arrowChar asString, ')'. ! ! !FixUnderscores class methodsFor: 'fixing' stamp: 'bf 4/7/2006 14:47'! arrowChar "FIXME: this should rather be (Character leftArrow)" ^Character value: 16r8F! ! !FixUnderscores class methodsFor: 'fixing' stamp: 'bf 11/26/2004 21:52'! fixFonts "self fixFonts" StrikeFont allInstances do: [:fnt | self fixFont: fnt] displayingProgress: 'Fixing Bitmap Fonts'.! ! !FixUnderscores class methodsFor: 'fixing' stamp: 'bf 10/18/2005 19:05'! fixFont: aFont | glyph underline | glyph := aFont characterFormAt: $_. "save arrow glyph to arrowChar codepoint" ((glyph copy: (0@aFont ascent corner: glyph extent)) isAllWhite and: [(aFont characterFormAt: self arrowChar) isAllWhite]) ifTrue: [aFont characterFormAt: self arrowChar put: glyph]. "make underscore glyph" glyph fillWhite. underline := aFont ascent + 1. glyph fillBlack: (1@underline extent: glyph width-1@1). aFont characterFormAt: $_ put: glyph.! ! !FixUnderscores class methodsFor: 'fixing' stamp: 'bf 4/7/2006 15:23'! fixPackages: packageNames "FixUnderscores fixPackages: #('FixUnderscores' 'Bert')" | failed | failed := OrderedCollection new. packageNames do: [:pkgName | (PackageInfo named: pkgName) methods do: [:mRef | mRef fixUnderscores ifFalse: [failed add: mRef]] displayingProgress: pkgName] displayingProgress: 'Fixing ...'. failed isEmpty ifFalse: [ MessageSet openMessageList: failed name: 'These methods with literal underscores were not fixed' autoSelect: '_'].! ! !FixUnderscores class methodsFor: 'fixing' stamp: 'bf 4/7/2006 15:11'! fixPackage: aPackageName ^self fixPackages: {aPackageName} ! ! AbstractFont subclass: #FixedFaceFont instanceVariableNames: 'baseFont substitutionCharacter displaySelector' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-Display'! !FixedFaceFont commentStamp: 'tak 12/22/2004 01:45' prior: 0! I am a font for special purpose like password or fallback. I can show same form whenever someone requests any character. Variable displaySelector is future use to show a form dynamically. (Although it would be unnecessary...)! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:58'! ascent ^baseFont ascent! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/20/2004 10:51'! ascentOf: aCharacter ^ self ascent! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:57'! baseFont ^baseFont! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:57'! baseFont: aFont baseFont := aFont! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:59'! baseKern ^baseFont baseKern! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/22/2004 02:01'! characterFormAt: character ^ baseFont characterFormAt: substitutionCharacter! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 17:00'! descent ^baseFont descent! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:58'! descentKern ^baseFont descentKern! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/20/2004 10:51'! descentOf: aCharacter ^ self descent! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/20/2004 10:10'! emphasized: emph ^self class new baseFont: (baseFont emphasized: emph)! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:25'! familyName ^baseFont familyName, '-pw'! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/20/2004 10:19'! fontSize: aNumber self baseFont: (StrikeFont familyName: baseFont familyName size: aNumber) copy! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:57'! height ^baseFont height! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:26'! lineGrid ^baseFont lineGrid! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'tak 12/20/2004 10:51'! maxAscii ^ SmallInteger maxVal! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'ar 1/5/2003 16:59'! passwordCharacter ^$*! ! !FixedFaceFont methodsFor: 'accessing' stamp: 'nk 8/31/2004 09:28'! pointSize ^baseFont pointSize! ! !FixedFaceFont methodsFor: 'caching' stamp: 'nk 3/15/2004 18:48'! releaseCachedState baseFont releaseCachedState.! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'tak 12/20/2004 18:06'! displayErrorOn: aCanvas length: length at: aPoint kern: kernDelta | maskedString | maskedString := String new: length. maskedString atAllPut: substitutionCharacter. ^ baseFont displayString: maskedString on: aCanvas from: 1 to: length at: aPoint kern: kernDelta! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'yo 1/7/2005 11:49'! displayErrorOn: aCanvas length: length at: aPoint kern: kernDelta baselineY: baselineY | maskedString | maskedString := String new: length. maskedString atAllPut: substitutionCharacter. ^ baseFont displayString: maskedString on: aCanvas from: 1 to: length at: aPoint kern: kernDelta baselineY: baselineY! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'tak 12/20/2004 18:06'! displayPasswordOn: aCanvas length: length at: aPoint kern: kernDelta | maskedString | maskedString := String new: length. maskedString atAllPut: substitutionCharacter. ^ baseFont displayString: maskedString on: aCanvas from: 1 to: length at: aPoint kern: kernDelta! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'yo 1/7/2005 11:50'! displayPasswordOn: aCanvas length: length at: aPoint kern: kernDelta baselineY: baselineY | maskedString | maskedString := String new: length. maskedString atAllPut: substitutionCharacter. ^ baseFont displayString: maskedString on: aCanvas from: 1 to: length at: aPoint kern: kernDelta baselineY: baselineY! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'yo 1/7/2005 12:00'! displayString: aString on: aDisplayContext from: startIndex to: stopIndex at: aPoint kern: kernDelta | size | size := stopIndex - startIndex + 1. ^ self perform: displaySelector withArguments: (Array with: aDisplayContext with: size with: aPoint with: kernDelta with: aPoint y + self ascent).! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'yo 1/7/2005 12:19'! displayString: aString on: aDisplayContext from: startIndex to: stopIndex at: aPoint kern: kernDelta baselineY: baselineY | size | size := stopIndex - startIndex + 1. ^ self perform: displaySelector withArguments: (Array with: aDisplayContext with: size with: aPoint with: kernDelta with: baselineY).! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'tak 12/20/2004 11:10'! displayString: aString on: aBitBlt from: startIndex to: stopIndex at: aPoint kern: kernDelta from: fromFont | destPoint | destPoint := self displayString: aString on: aBitBlt from: startIndex to: stopIndex at: aPoint kern: kernDelta. ^ Array with: stopIndex + 1 with: destPoint! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'yo 1/7/2005 11:51'! displayString: aString on: aBitBlt from: startIndex to: stopIndex at: aPoint kern: kernDelta from: fromFont baselineY: baselineY | destPoint | destPoint := self displayString: aString on: aBitBlt from: startIndex to: stopIndex at: aPoint kern: kernDelta baselineY: baselineY. ^ Array with: stopIndex + 1 with: destPoint! ! !FixedFaceFont methodsFor: 'displaying' stamp: 'ar 1/5/2003 17:00'! installOn: aDisplayContext foregroundColor: foregroundColor backgroundColor: backgroundColor ^baseFont installOn: aDisplayContext foregroundColor: foregroundColor backgroundColor: backgroundColor! ! !FixedFaceFont methodsFor: 'initialize-release' stamp: 'yo 1/7/2005 11:59'! errorFont displaySelector := #displayErrorOn:length:at:kern:baselineY:. substitutionCharacter := $?.! ! !FixedFaceFont methodsFor: 'initialize-release' stamp: 'tak 12/20/2004 10:37'! initialize baseFont := TextStyle defaultFont. self passwordFont! ! !FixedFaceFont methodsFor: 'initialize-release' stamp: 'yo 1/7/2005 11:59'! passwordFont displaySelector := #displayPasswordOn:length:at:kern:baselineY:. substitutionCharacter := $*! ! !FixedFaceFont methodsFor: 'measuring' stamp: 'tak 12/20/2004 18:05'! widthOf: aCharacter ^ baseFont widthOf: substitutionCharacter! ! !FixedFaceFont methodsFor: 'private' stamp: 'yo 1/11/2005 18:54'! glyphInfoOf: aCharacter into: glyphInfoArray ^ baseFont glyphInfoOf: substitutionCharacter into: glyphInfoArray. ! ! Array variableSubclass: #FixedIdentitySet instanceVariableNames: 'tally capacity' classVariableNames: '' poolDictionaries: '' category: 'Traits-Requires'! !FixedIdentitySet commentStamp: 'NS 5/26/2005 13:00' prior: 0! This is a fast but lazy implementation of fixed size identity sets. The two main difference to regular identity sets are: 1) These identity sets have a fixed size. If they are full, adding another element doesn't have any effect. 2) No rehashing. If two elements were to be stored on the same position in the underlying array, one of them is simply discarded. As a consequence of (1) and (2), these identity sets are very fast!! Note that this class inherits form Array. This is not clean but reduces memory overhead when instances are created.! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 10:49'! addAll: aCollection aCollection do: [:each | self isFull ifTrue: [^ self]. self add: each. ].! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 10:50'! addAll: aCollection notIn: notCollection aCollection do: [:each | self isFull ifTrue: [^ self]. (notCollection includes: each) ifFalse: [self add: each]. ].! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 10:47'! add: anObject | index old | self isFull ifTrue: [^ false]. index _ self indexOf: anObject. old _ self basicAt: index. old == anObject ifTrue: [^ true]. old ifNotNil: [^ false]. self basicAt: index put: anObject. tally _ tally + 1. ^ true! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 08:44'! at: index self shouldNotImplement! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 08:43'! at: index put: anObject self shouldNotImplement! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/23/2005 17:40'! capacity ^ capacity! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 10:46'! destructiveAdd: anObject | index old | self isFull ifTrue: [^ false]. index _ self indexOf: anObject. old _ self basicAt: index. self basicAt: index put: anObject. old ifNil: [tally _ tally + 1]. ^ true! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 08:45'! includes: anObject ^ (self basicAt: (self indexOf: anObject)) == anObject! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/24/2005 13:12'! remove: anObject ifAbsent: aBlock | index | index _ self indexOf: anObject. ^ (self basicAt: index) == anObject ifTrue: [self basicAt: index put: nil. tally _ tally - 1. anObject] ifFalse: [aBlock value].! ! !FixedIdentitySet methodsFor: 'accessing' stamp: 'NS 5/23/2005 13:13'! size ^ tally! ! !FixedIdentitySet methodsFor: 'comparing' stamp: 'NS 5/24/2005 08:56'! 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! ! !FixedIdentitySet methodsFor: 'comparing' stamp: 'NS 5/24/2005 08:59'! = aCollection self == aCollection ifTrue: [^ true]. aCollection size = self size ifFalse: [^ false]. aCollection do: [:each | (self includes: each) ifFalse: [^ false]]. ^ true! ! !FixedIdentitySet methodsFor: 'enumerating' stamp: 'NS 5/24/2005 09:04'! do: aBlock | obj count | count _ 0. 1 to: self basicSize do: [:index | count >= tally ifTrue: [^ self]. obj _ self basicAt: index. obj ifNotNil: [count _ count + 1. aBlock value: obj]. ]. ! ! !FixedIdentitySet methodsFor: 'enumerating' stamp: 'NS 5/24/2005 13:52'! select: aBlock | result | result _ self species new: self capacity. self do: [:each | (aBlock value: each) ifTrue: [result add: each]]. ^ result.! ! !FixedIdentitySet methodsFor: 'initialize-release' stamp: 'NS 5/23/2005 17:39'! initializeCapacity: anInteger tally _ 0. capacity _ anInteger.! ! !FixedIdentitySet methodsFor: 'printing' stamp: 'NS 5/23/2005 18:32'! printOn: aStream | count | aStream nextPutAll: '#('. count _ 0. self do: [:each | count _ count + 1. each printOn: aStream. count < self size ifTrue: [aStream nextPut: $ ] ]. aStream nextPut: $).! ! !FixedIdentitySet methodsFor: 'testing' stamp: 'NS 5/24/2005 10:45'! isFull ^ tally >= capacity! ! !FixedIdentitySet methodsFor: 'testing' stamp: 'NS 5/24/2005 10:46'! notFull ^ tally < capacity! ! !FixedIdentitySet methodsFor: 'private' stamp: 'NS 5/23/2005 18:08'! arraySize ^ super size! ! !FixedIdentitySet methodsFor: 'private' stamp: 'NS 5/24/2005 10:48'! indexOf: anObject anObject isNil ifTrue: [self error: 'This class collection cannot handle nil as an element']. ^ (anObject identityHash bitAnd: self basicSize - 1) + 1! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FixedIdentitySet class instanceVariableNames: ''! !FixedIdentitySet class methodsFor: 'constants' stamp: 'NS 5/26/2005 13:02'! arraySizeForCapacity: anInteger "Because of the hash performance, the array size is always a power of 2 and at least twice as big as the capacity anInteger" ^ anInteger <= 0 ifTrue: [0] ifFalse: [1 << (anInteger << 1 - 1) highBit].! ! !FixedIdentitySet class methodsFor: 'constants' stamp: 'NS 5/26/2005 13:03'! defaultSize ^ 4! ! !FixedIdentitySet class methodsFor: 'constants' stamp: 'NS 5/23/2005 13:09'! new ^ self new: self defaultSize! ! !FixedIdentitySet class methodsFor: 'constants' stamp: 'NS 5/23/2005 17:40'! new: anInteger ^ (super new: (self arraySizeForCapacity: anInteger)) initializeCapacity: anInteger! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/26/2005 13:05'! readonlyWithAll: aCollection notIn: notCollection "For performance reasons, this method may return an array rather than a FixedIdentitySet. Therefore it should only be used if the return value does not need to be modified. Use #withAll:notIn: if the return value might need to be modified." | size | aCollection isEmpty ifTrue: [^ #()]. size _ aCollection size = 1 ifTrue: [1] ifFalse: [self sizeFor: aCollection]. ^ (self new: size) addAll: aCollection notIn: notCollection; yourself! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 13:57'! withAll: aCollection "Create a new collection containing all the elements from aCollection." ^ (self new: (self sizeFor: aCollection)) addAll: aCollection; yourself! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 13:58'! withAll: aCollection notIn: notCollection ^ (self new: (self sizeFor: aCollection)) addAll: aCollection notIn: notCollection; yourself! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:51'! with: anObject "Answer an instance of me containing anObject." ^ self new add: anObject; yourself! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:51'! with: firstObject with: secondObject "Answer an instance of me containing the two arguments as elements." ^ self new add: firstObject; add: secondObject; yourself! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:51'! 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! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:51'! 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! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:52'! 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! ! !FixedIdentitySet class methodsFor: 'instance creation' stamp: 'NS 5/24/2005 08:52'! 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! ! !FixedIdentitySet class methodsFor: 'private' stamp: 'NS 5/24/2005 13:57'! sizeFor: aCollection ^ aCollection species == self ifTrue: [aCollection capacity] ifFalse: [self defaultSize].! ! ReferenceMorph subclass: #FlapTab instanceVariableNames: 'flapShowing edgeToAdhereTo slidesOtherObjects popOutOnDragOver popOutOnMouseOver inboard dragged lastReferentThickness edgeFraction labelString' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Flaps'! !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: 'access' stamp: 'dgd 8/31/2003 18:58'! acquirePlausibleFlapID "Give the receiver a flapID that is globally unique; try to hit the mark vis a vis the standard system flap id's, for the case when this method is invoked as part of the one-time transition" | wording | wording _ self wording. (wording isEmpty or: [wording = '---']) ifTrue: [wording _ 'Flap' translated]. ^ self provideDefaultFlapIDBasedOn: wording! ! !FlapTab methodsFor: 'access' stamp: 'sw 4/30/2001 18:39'! flapID "Answer the receiver's flapID, creating it if necessary" ^ self knownName ifNil: [self acquirePlausibleFlapID]! ! !FlapTab methodsFor: 'access' stamp: 'sw 4/30/2001 18:39'! flapID: anID "Set the receiver's flapID" self setNameTo: anID! ! !FlapTab methodsFor: 'access' stamp: 'sw 5/4/2001 23:25'! flapIDOrNil "If the receiver has a flapID, answer it, else answer nil" ^ self knownName! ! !FlapTab methodsFor: 'access' stamp: 'sw 2/9/1999 14:44'! flapShowing ^ flapShowing == true! ! !FlapTab methodsFor: 'access' stamp: 'MAL 1/7/2005 12:25'! orientation ^ (#left == edgeToAdhereTo or: [#right == edgeToAdhereTo]) ifTrue: [#vertical] ifFalse: [#horizontal]! ! !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: 'accessing' stamp: 'tk 9/25/2002 18:08'! labelString ^labelString! ! !FlapTab methodsFor: 'change reporting' stamp: 'ar 10/26/2000 17:36'! ownerChanged self fitOnScreen. ^super ownerChanged.! ! !FlapTab methodsFor: 'classification' stamp: 'ar 9/28/2000 13:53'! isFlapTab ^true! ! !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 2/11/1999 10:55'! slidesOtherObjects ^ slidesOtherObjects! ! !FlapTab methodsFor: 'edge' stamp: 'sw 10/31/2001 20:51'! applyEdgeFractionWithin: aBoundsRectangle "Make the receiver reflect remembered edgeFraction" | newPosition | edgeFraction ifNil: [^ self]. self isCurrentlySolid ifTrue: [^ self]. newPosition _ self ifVertical: [self left @ (self edgeFraction * (aBoundsRectangle height - self height))] ifHorizontal: [(self edgeFraction * (aBoundsRectangle width - self width) @ self top)]. self position: (aBoundsRectangle origin + newPosition) ! ! !FlapTab methodsFor: 'edge' stamp: 'sw 10/31/2001 15:01'! computeEdgeFraction "Compute and remember the edge fraction" | aBox aFraction | self isCurrentlySolid ifTrue: [^ edgeFraction ifNil: [self edgeFraction: 0.5]]. aBox _ ((owner ifNil: [ActiveWorld]) bounds) insetBy: (self extent // 2). aFraction _ self ifVertical: [(self center y - aBox top) / (aBox height max: 1)] ifHorizontal: [(self center x - aBox left) / (aBox width max: 1)]. ^ self edgeFraction: aFraction! ! !FlapTab methodsFor: 'edge' stamp: 'sw 10/31/2001 06:56'! edgeFraction ^ edgeFraction ifNil: [self computeEdgeFraction]! ! !FlapTab methodsFor: 'edge' stamp: 'sw 10/31/2001 08:38'! edgeFraction: aNumber "Set my edgeFraction to the given number, without side effects" edgeFraction _ aNumber asFloat! ! !FlapTab methodsFor: 'edge' stamp: 'yo 2/10/2005 18:06'! edgeString ^ 'cling to edge... (current: {1})' translated format: {edgeToAdhereTo translated}! ! !FlapTab methodsFor: 'edge' stamp: 'sw 2/11/1999 00:41'! edgeToAdhereTo ^ edgeToAdhereTo! ! !FlapTab methodsFor: 'edge' stamp: 'MAL 1/7/2005 12:20'! edgeToAdhereTo: e edgeToAdhereTo _ e asSymbol! ! !FlapTab methodsFor: 'edge' stamp: 'sw 10/31/2001 15:58'! ifVertical: block1 ifHorizontal: block2 "Evaluate and return the value of either the first or the second block, depending whether I am vertically or horizontally oriented" ^ self orientation == #vertical ifTrue: [block1 value] ifFalse: [block2 value] ! ! !FlapTab methodsFor: 'edge' stamp: 'MAL 1/7/2005 12:24'! setEdge: anEdge "Set the edge as indicated, if possible" | newOrientation e | e _ anEdge asSymbol. self edgeToAdhereTo = anEdge ifTrue: [^ self]. newOrientation _ nil. self orientation == #vertical ifTrue: [(#top == e or: [#bottom == e]) ifTrue: [newOrientation _ #horizontal]] ifFalse: [(#top == e or: [#bottom == e]) ifFalse: [newOrientation _ #vertical]]. self edgeToAdhereTo: e. newOrientation ifNotNil: [self transposeParts]. referent isInWorld ifTrue: [self positionReferent]. self adjustPositionVisAVisFlap! ! !FlapTab methodsFor: 'edge' stamp: 'dgd 10/17/2003 22:36'! setEdgeToAdhereTo | aMenu | aMenu _ MenuMorph new defaultTarget: self. #(left top right bottom) do: [:sym | aMenu add: sym asString translated target: self selector: #setEdge: argument: sym]. aMenu popUpEvent: self currentEvent in: self world! ! !FlapTab methodsFor: 'event handling' stamp: 'sw 10/31/2001 15:46'! 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. self computeEdgeFraction]! ! !FlapTab methodsFor: 'event handling' stamp: 'sw 11/22/2001 08:11'! mouseUp: evt "The mouse came back up, presumably after having dragged the tab. Caution: if not operating full-screen, this notification can easily be *missed*, which is why the edge-fraction-computation is also being done on mouseMove." super mouseUp: evt. (self referentThickness <= 0 or: [(referent isInWorld and: [(referent boundsInWorld intersects: referent owner boundsInWorld) not])]) ifTrue: [self hideFlap. flapShowing _ false]. self fitOnScreen. dragged ifTrue: [self computeEdgeFraction. dragged _ false]. Flaps doAutomaticLayoutOfFlapsIfAppropriate! ! !FlapTab methodsFor: 'events' stamp: 'sw 2/12/2001 17:04'! tabSelected "The user clicked on the tab. Show or hide the flap. Try to be a little smart about a click on a tab whose flap is open but only just barely." dragged == true ifTrue: [^ dragged _ false]. self flapShowing ifTrue: [self referentThickness < 23 "an attractive number" ifTrue: [self openFully] ifFalse: [self hideFlap]] ifFalse: [self showFlap]! ! !FlapTab methodsFor: 'globalness' stamp: 'sw 5/4/2001 23:25'! isGlobalFlap "Answer whether the receiver is currently a shared flap" ^ Flaps globalFlapTabsIfAny includes: self! ! !FlapTab methodsFor: 'globalness' stamp: 'dgd 8/30/2003 21:36'! isGlobalFlapString "Answer a string to construct a menu item representing control over whether the receiver is or is not a shared flap" ^ (self isGlobalFlap ifTrue: [''] ifFalse: ['']) , 'shared by all projects' translated! ! !FlapTab methodsFor: 'globalness' stamp: 'sw 4/30/2001 18:52'! toggleIsGlobalFlap "Toggle whether the receiver is currently a global flap or not" | oldWorld | self hideFlap. oldWorld _ self currentWorld. self isGlobalFlap ifTrue: [Flaps removeFromGlobalFlapTabList: self. oldWorld addMorphFront: self] ifFalse: [self delete. Flaps addGlobalFlap: self. self currentWorld addGlobalFlaps]. ActiveWorld reformulateUpdatingMenus ! ! !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: 'dgd 8/30/2003 21:29'! graphicalTabString ^ (self isCurrentlyGraphical ifTrue: ['choose new graphic...'] ifFalse: ['use graphical tab']) translated! ! !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: 'dgd 3/7/2003 14:49'! initialize "initialize the state of the receiver" super initialize. "" edgeToAdhereTo _ #left. flapShowing _ false. slidesOtherObjects _ false. popOutOnDragOver _ false. popOutOnMouseOver _ false. inboard _ false. dragged _ false! ! !FlapTab methodsFor: 'initialization' stamp: 'di 11/18/2001 13:09'! provideDefaultFlapIDBasedOn: aStem "Provide the receiver with a default flap id" | aNumber usedIDs anID | aNumber _ 0. usedIDs _ FlapTab allSubInstances select: [:f | f ~~ self] thenCollect: [:f | f flapIDOrNil]. anID _ aStem. [usedIDs includes: anID] whileTrue: [aNumber _ aNumber + 1. anID _ aStem, (aNumber asString)]. self flapID: anID. ^ anID! ! !FlapTab methodsFor: 'initialization' stamp: 'di 11/19/2001 21:20'! setName: nameString edge: edgeSymbol color: flapColor "Set me up with the usual..." self setNameTo: nameString. self edgeToAdhereTo: edgeSymbol; inboard: false. self assumeString: nameString font: Preferences standardFlapFont orientation: self orientation color: flapColor. self setToPopOutOnDragOver: true. self setToPopOutOnMouseOver: false. ! ! !FlapTab methodsFor: 'layout' stamp: 'ar 10/26/2000 17:36'! layoutChanged self fitOnScreen. ^super layoutChanged! ! !FlapTab methodsFor: 'menu' stamp: 'dgd 8/30/2003 21:21'! addCustomMenuItems: aMenu hand: aHandMorph "Add further items to the menu as appropriate" aMenu add: 'tab color...' translated target: self action: #changeColor. aMenu add: 'flap color...' translated target: self action: #changeFlapColor. aMenu addLine. 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 enablement: #sharedFlapsAllowed action: #toggleIsGlobalFlap. aMenu balloonTextForLastItem: 'If checked, this flap will be available in all morphic projects; if not, it will be private to this project.,' translated. aMenu addLine. aMenu add: 'destroy this flap' translated 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/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: 'dgd 9/21/2003 17:55'! 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.' translated]. super changeColor ! ! !FlapTab methodsFor: 'menu' stamp: 'dgd 9/21/2003 17:55'! changeFlapColor (self flapShowing) ifTrue: [referent changeColor] ifFalse: [self inform: 'The flap itself needs to be open before you can change its color.' translated]! ! !FlapTab methodsFor: 'menu' stamp: 'yo 7/2/2004 17:58'! changeTabText "Allow the user to change the text on the tab" | reply | reply _ FillInTheBlank request: 'new wording for this tab:' translated initialAnswer: self existingWording. reply isEmptyOrNil ifTrue: [^ self]. self changeTabText: reply. ! ! !FlapTab methodsFor: 'menu' stamp: 'dgd 9/5/2003 18:25'! destroyFlap "Destroy the receiver" | reply request | request _ self isGlobalFlap 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 translated orCancel: [^ self]. reply ifTrue: [self isGlobalFlap ifTrue: [Flaps removeFlapTab: self keepInList: false. self currentWorld reformulateUpdatingMenus] ifFalse: [referent isInWorld ifTrue: [referent delete]. self delete]]! ! !FlapTab methodsFor: 'menu' stamp: 'di 11/17/2001 20:17'! existingWording ^ labelString! ! !FlapTab methodsFor: 'menu' stamp: 'sw 7/8/1999 15:44'! flapMenuTitle ^ 'flap: ', self wording! ! !FlapTab methodsFor: 'menu' stamp: 'gm 2/22/2003 13:11'! isCurrentlyTextual | first | ^submorphs notEmpty and: [((first := submorphs first) isKindOf: StringMorph) or: [first isTextMorph]]! ! !FlapTab methodsFor: 'menu' 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: 'menu' stamp: 'sw 4/24/2001 11:04'! sharedFlapsAllowed "Answer (for the benefit of a menu item for which I am the target) whether the system presently allows shared flaps" ^ Flaps sharedFlapsAllowed! ! !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: 'menus' stamp: 'nk 2/15/2004 08:19'! addGestureMenuItems: aMenu hand: aHandMorph "If the receiver wishes the Genie menu items, add a line to the menu and then those Genie items, else do nothing"! ! !FlapTab methodsFor: 'menus' stamp: 'sw 6/19/1999 23:16'! addTitleForHaloMenu: aMenu aMenu addTitle: self externalName updatingSelector: #flapMenuTitle updateTarget: self! ! !FlapTab methodsFor: 'misc' stamp: 'di 11/19/2001 12:19'! fitContents self isCurrentlyTextual ifFalse: [^ super fitContents]. self ifVertical: [self extent: submorphs first extent + (2 * self borderWidth) + (0@4). submorphs first position: self position + self borderWidth + (1@4)] ifHorizontal: [self extent: submorphs first extent + (2 * self borderWidth) + (8@-1). submorphs first position: self position + self borderWidth + (5@1)]! ! !FlapTab methodsFor: 'miscellaneous' stamp: 'dgd 8/31/2003 18:43'! balloonTextForFlapsMenu "Answer the balloon text to show on a menu item in the flaps menu that governs the visibility of the receiver in the current project" | id | id _ self flapID. #( ('Squeak' 'Has a few generally-useful controls; it is also a place where you can "park" objects') ('Tools' 'A quick way to get browsers, change sorters, file lists, etc.') ('Widgets' 'A variety of controls and media tools') ('Supplies' 'A source for many basic types of objects') ('Stack Tools' 'Tools for building stacks. Caution!! Powerful but young and underdocumented') ('Scripting' 'Tools useful when doing tile scripting') ('Navigator' 'Project navigator: includes controls for navigating through linked projects. Also supports finding, loading and publishing projects in a shared environment') ('Painting' 'A flap housing the paint palette. Click on the closed tab to make make a new painting')) do: [:pair | (FlapTab givenID: id matches: pair first translated) ifTrue: [^ pair second translated]]. ^ self balloonText! ! !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 7/31/2002 00:53'! 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: 'dgd 8/30/2003 21:32'! dragoverString "Answer the string to be shown in a menu to represent the dragover status" ^ (popOutOnDragOver ifTrue: [''] ifFalse: ['']), 'pop out on dragover' translated! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'ar 12/18/2000 01:14'! makeNewDrawing: evt self flapShowing ifTrue:[ self world makeNewDrawing: evt. ] ifFalse:[ self world assureNotPaintingEvent: evt. ].! ! !FlapTab methodsFor: 'mouseover & dragover' stamp: 'dgd 8/30/2003 21:36'! mouseoverString "Answer the string to be shown in a menu to represent the mouseover status" ^ (popOutOnMouseOver ifTrue: [''] ifFalse: ['']) , 'pop out on mouseover' translated ! ! !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: 'ar 2/8/2001 19:27'! startOrFinishDrawing: evt | w | self flapShowing ifTrue:[ (w _ self world) makeNewDrawing: evt at: w center. ] ifFalse:[ self world endDrawing: evt. ].! ! !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: 'objects from disk' stamp: 'sw 5/4/2001 23:27'! objectForDataStream: refStrm "I am about to be written on an object file. If I am a global flap, write a proxy instead." | dp | self isGlobalFlap ifTrue: [dp _ DiskProxy global: #Flaps selector: #globalFlapTabOrDummy: args: {self flapID}. refStrm replace: self with: dp. ^ dp]. ^ super objectForDataStream: refStrm! ! !FlapTab methodsFor: 'parts bin' stamp: 'dgd 8/30/2003 21:31'! partsBinString "Answer the string to be shown in a menu to represent the parts-bin status" ^ (referent isPartsBin ifTrue: [''] ifFalse: ['']), 'parts-bin' translated! ! !FlapTab methodsFor: 'parts bin' stamp: 'sw 2/25/1999 13:17'! togglePartsBinMode referent setPartsBinStatusTo: referent isPartsBin 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: 'dgd 4/4/2006 16:12'! fitOnScreen "19 sept 2000 - allow flaps in any paste up" | constrainer t l | constrainer _ (owner ifNil: [self]) clearArea. self flapShowing "otherwise no point in doing this" ifTrue:[self spanWorld]. self orientation == #vertical ifTrue: [ t _ ((self top min: (constrainer bottom- self height)) max: constrainer top). t = self top ifFalse: [self top: t]. ] ifFalse: [ l _ ((self left min: (constrainer right - self width)) max: constrainer left). l = self left ifFalse: [self left: l]. ]. self flapShowing ifFalse: [self positionObject: self atEdgeOf: constrainer]. ! ! !FlapTab methodsFor: 'positioning' stamp: 'dgd 4/4/2006 16:08'! 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" | pum | pum _ self pasteUpMorph ifNil: [^ self]. ^self positionObject: anObject atEdgeOf: pum clearArea! ! !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: 'dgd 4/4/2006 16:10'! spanWorld | container area | container := self pasteUpMorph ifNil: [self currentWorld]. area := container clearArea. self orientation == #vertical ifTrue: [ referent vResizing == #rigid ifTrue: [referent height: area height]. referent hResizing == #rigid ifTrue: [referent width: (referent width min: area width - self width)]. referent top: area top. referent bottom: (area bottom min: referent bottom) ] ifFalse: [ referent hResizing == #rigid ifTrue: [referent width: area width]. referent vResizing == #rigid ifTrue: [referent height: (referent height min: area height - self height)]. referent left: area left. referent right: (area right min: referent right) ]. ! ! !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: 'di 11/21/2001 16:02'! transposeParts "The receiver's orientation has just been changed from vertical to horizontal or vice-versa." "First expand the flap to screen size, letting the submorphs lay out to fit, and then shrink the minor dimension back to the last row." self isCurrentlyTextual ifTrue: "First recreate the tab with proper orientation" [self assumeString: self existingWording font: Preferences standardFlapFont orientation: self orientation color: self color]. self orientation == #vertical ifTrue: "changed from horizontal" [referent listDirection: #topToBottom; wrapDirection: #leftToRight. referent hasSubmorphs ifTrue: [referent extent: self currentWorld extent. referent fullBounds. "Needed to trigger layout" referent width: (referent submorphs collect: [:m | m right]) max - referent left + self width]] ifFalse: [referent listDirection: #leftToRight; wrapDirection: #topToBottom. referent hasSubmorphs ifTrue: [referent extent: self currentWorld extent. referent fullBounds. "Needed to trigger layout" referent height: (referent submorphs collect: [:m | m bottom]) max - referent top + self height]]. referent hasSubmorphs ifFalse: [referent extent: 100@100]. self spanWorld. flapShowing ifTrue: [self showFlap]! ! !FlapTab methodsFor: 'printing' 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: 'rounding' stamp: 'di 11/20/2001 08:20'! roundedCorners edgeToAdhereTo == #bottom ifTrue: [^ #(1 4)]. edgeToAdhereTo == #right ifTrue: [^ #(1 2)]. edgeToAdhereTo == #left ifTrue: [^ #(3 4)]. ^ #(2 3) "#top and undefined" ! ! !FlapTab methodsFor: 'rounding' stamp: 'ar 12/22/2001 22:45'! wantsRoundedCorners ^self isCurrentlyTextual or:[super wantsRoundedCorners]! ! !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: 'sw 11/24/2001 21:50'! hideFlapUnlessOverReferent "Hide the flap unless the mouse is over my referent." | aWorld where | (referent isInWorld and: [where _ self outermostWorldMorph activeHand lastEvent cursorPoint. referent bounds containsPoint: (referent globalPointToLocal: where)]) ifTrue: [^ self]. (aWorld _ self world) ifNil: [^ self]. "In case flap tabs just got hidden" self referent delete. aWorld removeAccommodationForFlap: self. flapShowing _ false. self isInWorld ifFalse: [self inboard ifTrue: [aWorld addMorphFront: self]]. self adjustPositionAfterHidingFlap! ! !FlapTab methodsFor: 'show & hide' stamp: 'sw 2/12/2001 16:49'! lastReferentThickness: anInteger "Set the last remembered referent thickness to the given integer" lastReferentThickness _ anInteger! ! !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: 'sw 2/12/2001 16:59'! openFully "Make an educated guess at how wide or tall we are to be, and open to that thickness" | thickness amt | thickness _ referent boundingBoxOfSubmorphs extent max: (100 @ 100). self applyThickness: (amt _ self orientation == #horizontal ifTrue: [thickness y] ifFalse: [thickness x]). self lastReferentThickness: amt. self showFlap! ! !FlapTab methodsFor: 'show & hide' stamp: 'dgd 8/31/2004 16:25'! showFlap "Open the flap up" | thicknessToUse flapOwner | "19 sept 2000 - going for all paste ups <- raa note" 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. self pasteUpMorph hideFlapsOtherThan: self ifClingingTo: edgeToAdhereTo. flapOwner bringTopmostsToFront! ! !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: '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: 'dgd 2/21/2003 22:39'! changeTabThickness | newThickness | newThickness := FillInTheBlank request: 'New thickness:' initialAnswer: self tabThickness printString. newThickness notEmpty 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: 'dgd 8/30/2003 21:31'! solidTabString ^ (self isCurrentlySolid ifTrue: ['currently using solid tab'] ifFalse: ['use solid tab']) translated! ! !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: 'submorphs-add/remove' stamp: 'rbb 2/18/2005 14:13'! dismissViaHalo "Dismiss the receiver (and its referent), unless it resists" self resistsRemoval ifTrue: [(UIManager default chooseFrom: #( 'Yes' 'Um, no, let me reconsider') title: 'Really throw this flap away?') = 2 ifFalse: [^ self]]. referent delete. self delete! ! !FlapTab methodsFor: 'textual tabs' stamp: 'yo 7/16/2003 15:25'! assumeString: aString font: aFont orientation: orientationSymbol color: aColor | aTextMorph workString tabStyle | labelString := aString asString. workString := orientationSymbol == #vertical ifTrue: [String streamContents: [:s | labelString do: [:c | s nextPut: c] separatedBy: [s nextPut: Character cr]]] ifFalse: [labelString]. tabStyle := (TextStyle new) leading: 0; newFontArray: (Array with: aFont). aTextMorph := (TextMorph new setTextStyle: tabStyle) contents: (workString asText addAttribute: (TextKern kern: 3)). self removeAllMorphs. self borderWidth: 2; borderColor: #raised. aColor ifNotNil: [self color: aColor]. self addMorph: aTextMorph centered. aTextMorph lock " FlapTab allSubInstancesDo: [:ft | ft reformatTextualTab] "! ! !FlapTab methodsFor: 'textual tabs' stamp: 'ar 9/3/2004 14:58'! changeTabText: aString | label | aString isEmptyOrNil ifTrue: [^ self]. label _ Locale current languageEnvironment class flapTabTextFor: aString in: self. label isEmptyOrNil ifTrue: [^ self]. self useStringTab: label. submorphs first delete. self assumeString: label font: Preferences standardFlapFont orientation: (Flaps orientationForEdge: self edgeToAdhereTo) color: nil. ! ! !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: 'dgd 8/30/2003 21:27'! textualTabString ^ (self isCurrentlyTextual ifTrue: ['change tab wording...'] ifFalse: ['use textual tab']) translated! ! !FlapTab methodsFor: 'textual tabs' stamp: 'di 11/17/2001 20:22'! useStringTab: aString | aLabel | labelString _ aString asString. aLabel _ StringMorph new contents: labelString. 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: 'dgd 10/8/2003 19:03'! useTextualTab | stringToUse colorToUse | self preserveDetails. colorToUse _ self valueOfProperty: #priorColor ifAbsent: [Color green muchLighter]. submorphs notEmpty ifTrue: [self removeAllMorphs]. stringToUse _ self valueOfProperty: #priorWording ifAbsent: ['Unnamed Flap' translated]. self assumeString: stringToUse font: Preferences standardFlapFont orientation: self orientation color: colorToUse! ! !FlapTab methodsFor: 'thumbnail' stamp: 'sw 6/16/1999 11:29'! permitsThumbnailing ^ false! ! !FlapTab methodsFor: 'WiW support' stamp: 'RAA 10/3/2000 09:24'! morphicLayerNumber ^self flapShowing ifTrue: [26] ifFalse: [25] "As navigators"! ! !FlapTab methodsFor: '*eToys-e-toy support' stamp: 'sw 1/25/2000 11:07'! isCandidateForAutomaticViewing ^ false! ! !FlapTab methodsFor: '*eToys-e-toy support' stamp: 'sw 7/28/2001 01:31'! succeededInRevealing: aPlayer "Try to reveal aPlayer, and answer whether we succeeded" (super succeededInRevealing: aPlayer) ifTrue: [^ true]. self flapShowing ifTrue: [^ false]. (referent succeededInRevealing: aPlayer) ifTrue: [self showFlap. aPlayer costume goHome; addHalo. ^ true]. ^ false! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlapTab class instanceVariableNames: ''! !FlapTab class methodsFor: 'as yet unclassified' stamp: 'di 11/19/2001 21:59'! givenID: aFlapID matches: pureID "eg, FlapTab givenID: 'Stack Tools2' matches: 'Stack Tools' " ^ aFlapID = pureID or: [(aFlapID beginsWith: pureID) and: [(aFlapID copyFrom: pureID size+1 to: aFlapID size) allSatisfy: [:c | c isDigit]]]! ! !FlapTab class methodsFor: 'new-morph participation' stamp: 'kfr 5/3/2000 12:51'! includeInNewMorphMenu "Not to be instantiated from the menu" ^ false! ! !FlapTab class methodsFor: 'printing' stamp: 'sw 2/11/1999 14:39'! defaultNameStemForInstances ^ 'flap tab'! ! Object subclass: #Flaps instanceVariableNames: '' classVariableNames: 'FlapsQuads SharedFlapTabs SharedFlapsAllowed' poolDictionaries: '' category: 'MorphicExtras-Flaps'! !Flaps commentStamp: 'asm 3/13/2003 12:46' prior: 0! ClassVariables FlapsQuads quads defining predefined flaps default flaps are: 'PlugIn Supplies', 'Stack Tools', 'Supplies', 'Tools', 'Widgets' and 'Scripting' SharedFlapTabs an array of flaps shared between squeak projects SharedFlapsAllowed boolean ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Flaps class instanceVariableNames: ''! !Flaps class methodsFor: 'class initialization' stamp: 'nk 6/14/2004 08:37'! initialize self initializeFlapsQuads! ! !Flaps class methodsFor: 'construction support' stamp: 'sw 5/4/2001 23:52'! addMorph: aMorph asElementNumber: aNumber inGlobalFlapSatisfying: flapBlock "If any global flap satisfies flapBlock, add aMorph to it at the given position. Applies to flaps that are parts bins and that like thumbnailing" | aFlapTab flapPasteUp | aFlapTab _ self globalFlapTabsIfAny detect: [:aTab | flapBlock value: aTab] ifNone: [^ self]. flapPasteUp _ aFlapTab referent. flapPasteUp addMorph: aMorph asElementNumber: aNumber. flapPasteUp replaceTallSubmorphsByThumbnails; setPartsBinStatusTo: true! ! !Flaps class methodsFor: 'construction support' stamp: 'sw 5/4/2001 23:52'! addMorph: aMorph asElementNumber: aNumber inGlobalFlapWithID: anID "If any global flap satisfies flapBlock, add aMorph to it at the given position. No senders in the image -- intended to be invoked by doits in code updates only, and applies to flaps that are parts bins and that like thumbnailing" ^ self addMorph: aMorph asElementNumber: aNumber inGlobalFlapSatisfying: [:aFlap | aFlap flapID = anID]! ! !Flaps class methodsFor: 'construction support' stamp: 'sw 4/30/2001 18:57'! addToSuppliesFlap: aMorph asElementNumber: aNumber "Add the given morph to the supplies flap. To be called by doits in updates, so don't be alarmed by its lack of senders." self addMorph: aMorph asElementNumber: aNumber inGlobalFlapWithID: 'Supplies'! ! !Flaps class methodsFor: 'construction support' stamp: 'sw 5/5/2001 02:12'! deleteMorphsSatisfying: deleteBlock fromGlobalFlapSatisfying: flapBlock "If any global flap satisfies flapBlock, then delete objects satisfying from deleteBlock from it. Occasionally called from do-its in updates or other fileouts." | aFlapTab flapPasteUp | aFlapTab _ self globalFlapTabsIfAny detect: [:aTab | flapBlock value: aTab] ifNone: [^ self]. flapPasteUp _ aFlapTab referent. flapPasteUp submorphs do: [:aMorph | (deleteBlock value: aMorph) ifTrue: [aMorph delete]]! ! !Flaps class methodsFor: 'construction support' stamp: 'sw 7/5/2004 17:54'! possiblyReplaceEToyFlaps "If in eToyFriendly mode, and if it's ok to reinitialize flaps, replace the existing flaps with up-too-date etoy flaps. Caution: this is destructive of existing flaps. If preserving the contents of existing flaps is important, set the preference 'okToReinitializeFlaps' to true" PartsBin thumbnailForPartsDescription: StickyPadMorph descriptionForPartsBin. "Puts StickyPadMorph's custom icon back in the cache which typically will have been called" (Preferences eToyFriendly and: [Preferences okToReinitializeFlaps]) ifTrue: [Flaps disableGlobalFlaps: false. Flaps addAndEnableEToyFlaps. Smalltalk isMorphic ifTrue: [ActiveWorld enableGlobalFlaps]]. "PartsBin clearThumbnailCache" "Flaps possiblyReplaceEToyFlaps"! ! !Flaps class methodsFor: 'flap mechanics' stamp: 'sw 2/16/1999 18:29'! clobberFlapTabList "Flaps clobberFlapTabList" SharedFlapTabs _ nil! ! !Flaps class methodsFor: 'flap mechanics' stamp: 'sw 7/12/2001 22:01'! freshFlapsStart "To be called manually only, as a drastic measure. Delete all flap artifacts and establish fresh default global flaps Flaps freshFlapsStart " self currentWorld deleteAllFlapArtifacts. self clobberFlapTabList. self addStandardFlaps ! ! !Flaps class methodsFor: 'flap mechanics' stamp: 'dgd 10/7/2003 22:47'! reinstateDefaultFlaps "Remove all existing 'standard' global flaps clear the global list, and and add fresh ones. To be called by doits in updates etc. This is a radical step, but it does *not* clobber non-standard global flaps or local flaps. To get the effect of the *former* version of this method, call Flaps freshFlapsStart" "Flaps reinstateDefaultFlaps" self globalFlapTabsIfAny do: [:aFlapTab | ({ 'Painting' translated. 'Stack Tools' translated. 'Squeak' translated. 'Menu' translated. 'Widgets' translated. 'Tools' translated. 'Supplies' translated. 'Scripting' translated. 'Objects' translated. 'Navigator' translated } includes: aFlapTab flapID) ifTrue: [self removeFlapTab: aFlapTab keepInList: false]]. "The following reduces the risk that flaps will be created with variant IDs such as 'Stack Tools2', potentially causing some shared flap logic to fail." "Smalltalk garbageCollect." "-- see if we are OK without this" self addStandardFlaps. "self disableGlobalFlapWithID: 'Scripting'. self disableGlobalFlapWithID: 'Objects'." self currentWorld addGlobalFlaps. self currentWorld reformulateUpdatingMenus. ! ! !Flaps class methodsFor: 'flap mechanics' stamp: 'sw 4/17/2001 14:47'! removeFlapTab: aFlapTab keepInList: aBoolean "Remove the given flap tab from the screen, and, if aBoolean is true, also from the global list" (SharedFlapTabs ~~ nil and: [SharedFlapTabs includes: aFlapTab]) ifTrue: [aBoolean ifFalse: [self removeFromGlobalFlapTabList: aFlapTab]]. aFlapTab ifNotNil: [aFlapTab referent delete. aFlapTab delete]! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 11:22'! defaultsQuadsDefiningScriptingFlap "Answer a structure defining the default items in the Scripting flap. previously in quadsDeiningScriptingFlap" ^ #( (TrashCanMorph new 'Trash' 'A tool for discarding objects') (ScriptingSystem scriptControlButtons 'Status' 'Buttons to run, stop, or single-step scripts') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you control all the running scripts in your world') (ScriptingSystem newScriptingSpace 'Scripting' 'A confined place for drawing and scripting, with its own private stop/step/go buttons.') (PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, etc.') (RandomNumberTile new 'Random' 'A tile that will produce a random number in a given range') (ScriptingSystem anyButtonPressedTiles 'ButtonDown?' 'Tiles for querying whether the mouse button is down') (ScriptingSystem noButtonPressedTiles 'ButtonUp?' 'Tiles for querying whether the mouse button is up') (SimpleSliderMorph authoringPrototype 'Slider' 'A slider for showing and setting numeric values.') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (TextFieldMorph exampleBackgroundField 'Scrolling Field' 'A scrolling data field which will have a different value on every card of the background') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations') (StackMorph authoringPrototype 'Stack' 'A multi-card data base' ) (TextMorph exampleBackgroundLabel 'Background Label' 'A piece of text that will occur on every card of the background') (TextMorph exampleBackgroundField 'Background Field' 'A data field which will have a different value on every card of the background') ) asOrderedCollection! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 11:22'! defaultsQuadsDefiningStackToolsFlap "Answer a structure defining the items on the default system Stack Tools flap. previously in quadsDefiningStackToolsFlap" ^ #( (StackMorph authoringPrototype 'Stack' 'A multi-card data base' ) (StackMorph stackHelpWindow 'Stack Help' 'Some hints about how to use Stacks') (TextMorph authoringPrototype 'Simple Text' 'Text that you can edit into anything you wish') (TextMorph fancyPrototype 'Fancy Text' 'A text field with a rounded shadowed border, with a fancy font.') (ScrollableField newStandAlone 'Scrolling Text' 'Holds any amount of text; has a scroll bar') (ScriptableButton authoringPrototype 'Scriptable Button' 'A button whose script will be a method of the background Player') (StackMorph previousCardButton 'Previous Card' 'A button that takes the user to the previous card in the stack') (StackMorph nextCardButton 'Next Card' 'A button that takes the user to the next card in the stack')) asOrderedCollection ! ! !Flaps class methodsFor: 'flaps registry' stamp: 'nk 9/2/2004 15:49'! defaultsQuadsDefiningSuppliesFlap "Answer a list of quads which define the objects to appear in the default Supplies flap. previously in quadsDefiningSuppliesFlap" ^ #( (RectangleMorph authoringPrototype 'Rectangle' 'A rectangle') (RectangleMorph roundRectPrototype 'RoundRect' 'A rectangle with rounded corners') (EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') (StarMorph authoringPrototype 'Star' 'A star') (CurveMorph authoringPrototype 'Curve' 'A curve') (PolygonMorph authoringPrototype 'Polygon' 'A straight-sided figure with any number of sides') (TextMorph boldAuthoringPrototype 'Text' 'Text that you can edit into anything you desire.') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, etc.') (ImageMorph authoringPrototype 'Picture' 'A non-editable picture of something') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (SimpleSliderMorph authoringPrototype 'Slider' 'A slider for showing and setting numeric values.') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations') (BookMorph authoringPrototype 'Book' 'A multi-paged structure') (TabbedPalette authoringPrototype 'TabbedPalette' 'A structure with tabs') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (ClockMorph authoringPrototype 'Clock' 'A simple digital clock') (BookMorph previousPageButton 'PreviousPage' 'A button that takes you to the previous page') (BookMorph nextPageButton 'NextPage' 'A button that takes you to the next page') ) asOrderedCollection! ! !Flaps class methodsFor: 'flaps registry' stamp: 'md 2/24/2006 15:20'! defaultsQuadsDefiningToolsFlap "Answer a structure defining the default Tools flap. previously in quadsDefiningToolsFlap" ^ OrderedCollection new addAll: #( (Browser prototypicalToolWindow 'Browser' 'A Browser is a tool that allows you to view all the code of all the classes in the system') (TranscriptStream openMorphicTranscript 'Transcript' 'A Transcript is a window usable for logging and debugging; browse references to #Transcript for examples of how to write to it.') (Workspace prototypicalToolWindow 'Workspace' 'A Workspace is a simple window for editing text. You can later save the contents to a file if you desire.')); add: { FileList2 . #prototypicalToolWindow. 'File List'. 'A File List is a tool for browsing folders and files on disks and FTP servers.' }; addAll: #( (DualChangeSorter prototypicalToolWindow 'Change Sorter' 'Shows two change sets side by side') (SelectorBrowser prototypicalToolWindow 'Method Finder' 'A tool for discovering methods by providing sample values for arguments and results') (MessageNames prototypicalToolWindow 'Message Names' 'A tool for finding, viewing, and editing all methods whose names contain a given character sequence.') (Preferences preferencesControlPanel 'Preferences' 'Allows you to control numerous options') (Utilities recentSubmissionsWindow 'Recent' 'A message browser that tracks the most recently-submitted methods') (ProcessBrowser prototypicalToolWindow 'Processes' 'A Process Browser shows you all the running processes') (Preferences annotationEditingWindow 'Annotations' 'Allows you to specify the annotations to be shown in the annotation panes of browsers, etc.') (Scamper newOpenableMorph 'Scamper' 'A web browser') (Celeste newOpenableMorph 'Celeste' 'Celeste -- an EMail reader') (PackagePaneBrowser prototypicalToolWindow 'Packages' 'Package Browser: like a System Browser, except that if has extra level of categorization in the top-left pane, such that class-categories are further organized into groups called "packages"') (ChangeSorter prototypicalToolWindow 'Change Set' 'A tool that allows you to view and manipulate all the code changes in a single change set')); yourself! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 11:21'! defaultsQuadsDefiningWidgetsFlap "Answer a structure defining the default Widgets flap. previously in quadsDefiningWidgetsFlap" ^ #( (TrashCanMorph new 'Trash' 'A tool for discarding objects') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you see and control all the running scripts in your project') (PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there') (GeeMailMorph new 'Gee-Mail' 'A place to present annotated content') (RecordingControlsMorph authoringPrototype 'Sound' 'A device for making sound recordings.') (MPEGMoviePlayerMorph authoringPrototype 'Movie Player' 'A Player for MPEG movies') (FrameRateMorph authoringPrototype 'Frame Rate' 'An indicator of how fast your system is running') (MagnifierMorph newRound 'Magnifier' 'A magnifying glass') (ScriptingSystem newScriptingSpace 'Scripting' 'A confined place for drawing and scripting, with its own private stop/step/go buttons.') (ScriptingSystem holderWithAlphabet 'Alphabet' 'A source for single-letter objects') (BouncingAtomsMorph new 'Bouncing Atoms' 'Atoms, mate') (ObjectsTool newStandAlone 'Object Catalog' 'A tool that lets you browse the catalog of objects') ) asOrderedCollection! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 10:58'! initializeFlapsQuads "initialize the list of dynamic flaps quads. self initializeFlapsQuads" FlapsQuads _ nil. self registeredFlapsQuads at: 'PlugIn Supplies' put: self defaultsQuadsDefiningPlugInSuppliesFlap; at: 'Stack Tools' put: self defaultsQuadsDefiningStackToolsFlap; at: 'Supplies' put: self defaultsQuadsDefiningSuppliesFlap; at: 'Tools' put: self defaultsQuadsDefiningToolsFlap; at: 'Widgets' put: self defaultsQuadsDefiningWidgetsFlap; at: 'Scripting' put: self defaultsQuadsDefiningScriptingFlap. ^ self registeredFlapsQuads! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 09:55'! registeredFlapsQuads "Answer the list of dynamic flaps quads" FlapsQuads ifNil: [FlapsQuads _ Dictionary new]. ^ FlapsQuads " FlapsQuads _ nil. "! ! !Flaps class methodsFor: 'flaps registry' stamp: 'hpt 4/26/2004 16:46'! registeredFlapsQuadsAt: aLabel "Answer the list of dynamic flaps quads at aLabel" ^ (self registeredFlapsQuads at: aLabel) removeAllSuchThat: [:q | (self environment includesKey: q first) not or: [(self environment at: q first) isNil]] ! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 11:09'! registerQuad: aQuad forFlapNamed: aLabel "If any previous registration of the same label string is already known, delete the old one." "aQuad received must be an array of the form {TargetObject. #command label 'A Help String'} Flaps registerQuad: #(FileList2 openMorphicViewInWorld 'Enhanced File List' 'A nicer File List.') forFlapNamed: 'Tools' " self unregisterQuad: aQuad forFlapNamed: aLabel. (self registeredFlapsQuads at: aLabel) add: aQuad! ! !Flaps class methodsFor: 'flaps registry' stamp: 'ar 9/27/2005 22:10'! unregisterQuadsWithReceiver: aReceiver "delete all quads with receiver aReceiver." self registeredFlapsQuads do: [:assoc | assoc value removeAllSuchThat: [:q | (self environment at: (q first) ifAbsent:[nil]) = aReceiver ]]! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 4/12/2003 14:16'! unregisterQuadsWithReceiver: aReceiver fromFlapNamed: aLabel "delete all quads with receiver aReceiver." (self registeredFlapsQuads at: aLabel) removeAllSuchThat: [:q | q first = aReceiver name]! ! !Flaps class methodsFor: 'flaps registry' stamp: 'asm 3/13/2003 10:34'! unregisterQuad: aQuad forFlapNamed: aLabel "If any previous registration at the same label string has the same receiver-command, delete the old one." (self registeredFlapsQuadsAt: aLabel) removeAllSuchThat: [:q | q first = aQuad first and: [q second = aQuad second]]! ! !Flaps class methodsFor: 'menu commands' stamp: 'mir 8/22/2001 18:55'! disableGlobalFlaps "Clobber all the shared flaps structures. First read the user her Miranda rights." self disableGlobalFlaps: true! ! !Flaps class methodsFor: 'menu commands' stamp: 'dgd 8/31/2003 19:01'! disableGlobalFlaps: interactive "Clobber all the shared flaps structures. First read the user her Miranda rights." interactive ifTrue: [(self confirm: 'CAUTION!! This will destroy all the shared flaps, so that they will not be present in *any* project. If, later, you want them back, you will have to reenable them, from this same menu, whereupon the standard default set of shared flaps will be created. Do you really want to go ahead and clobber all shared flaps at this time?' translated) ifFalse: [^ self]]. self globalFlapTabsIfAny do: [:aFlapTab | self removeFlapTab: aFlapTab keepInList: false. aFlapTab isInWorld ifTrue: [self error: 'Flap problem' translated]]. self clobberFlapTabList. SharedFlapsAllowed _ false. Smalltalk isMorphic ifTrue: [ActiveWorld restoreMorphicDisplay. ActiveWorld reformulateUpdatingMenus]. "The following reduces the risk that flaps will be created with variant IDs such as 'Stack Tools2', potentially causing some shared flap logic to fail." "Smalltalk garbageCollect." "-- see if we are OK without this" ! ! !Flaps class methodsFor: 'menu commands' stamp: 'sw 5/7/2001 13:15'! disableGlobalFlapWithID: aFlapID "Mark this project as having the given flapID disabled" | disabledFlapIDs aFlapTab currentProject | (currentProject _ Project current) assureFlapIntegrity. Smalltalk isMorphic ifFalse: [^ self]. disabledFlapIDs _ currentProject parameterAt: #disabledGlobalFlapIDs. (aFlapTab _ self globalFlapTabWithID: aFlapID) ifNotNil: [aFlapTab hideFlap]. (disabledFlapIDs includes: aFlapID) ifFalse: [disabledFlapIDs add: aFlapID]. aFlapTab ifNotNil: [aFlapTab delete] ! ! !Flaps class methodsFor: 'menu commands' stamp: 'sw 11/22/2001 08:31'! enableDisableGlobalFlapWithID: aFlapID "Toggle the enable/disable status of the given global flap" | disabledFlapIDs aFlapTab currentProject | (currentProject _ Project current) assureFlapIntegrity. Smalltalk isMorphic ifFalse: [^ self]. disabledFlapIDs _ currentProject parameterAt: #disabledGlobalFlapIDs. (aFlapTab _ self globalFlapTabWithID: aFlapID) ifNotNil: [aFlapTab hideFlap]. (disabledFlapIDs includes: aFlapID) ifTrue: [disabledFlapIDs remove: aFlapID. self currentWorld addGlobalFlaps] ifFalse: [disabledFlapIDs add: aFlapID. aFlapTab ifNotNil: [aFlapTab delete]]. self doAutomaticLayoutOfFlapsIfAppropriate! ! !Flaps class methodsFor: 'menu commands' stamp: 'sw 5/7/2001 13:15'! enableGlobalFlapWithID: aFlapID "Remove any memory of this flap being disabled in this project" | disabledFlapIDs currentProject | (currentProject _ Project current) assureFlapIntegrity. Smalltalk isMorphic ifFalse: [^ self]. disabledFlapIDs _ currentProject parameterAt: #disabledGlobalFlapIDs ifAbsent: [^ self]. disabledFlapIDs remove: aFlapID ifAbsent: [] ! ! !Flaps class methodsFor: 'menu commands' stamp: 'sw 3/3/2004 15:49'! explainFlaps "Open a window giving flap help." (StringHolder new contents: self explainFlapsText translated) openLabel: 'Flaps' translated "Flaps explainFlaps" ! ! !Flaps class methodsFor: 'menu commands' stamp: 'sw 3/3/2004 15:51'! explainFlapsText "Answer the text, in English, to show in a help-window about Flaps." ^'Flaps are like drawers on the edge of the screen, which can be opened so that you can use what is inside them, and closed when you do not need them. They have many possible uses, a few of which are illustrated by the default set of flaps you can get as described below. ''Shared flaps'' are available in every morphic project. As you move from project to project, you will see these same shared flaps in each, though there are also options, on a project-by-project basis, to choose which of the shared flaps should be shown, and also momentarily to suppress the showing of all shared flaps. To get started using flaps, bring up the desktop menu and choose ''flaps...'', and make the menu stay up by choosing ''keep this menu up''. If you see, in this flaps menu, a list of flap names such as ''Squeak'', ''Tools'', etc., it means that shared flaps are already set up in your image. If you do not see the list, you will instead see a menu item that invites you to ''install default shared flaps''; choose that, and new flaps will be created, and the flaps menu will change to reflect their presence. ''Project flaps'' are flaps that belong to a single morphic project. You will see them when you are in that project, but not when you are in any other morphic project. If a flap is set up as a parts bin (such as the default Tools and Supplies flaps), you can use it to create new objects -- just open the flap, then find the object you want, and drag it out; when the cursor leaves the flap, the flap itself will snap closed, and you''ll be left holding the new object -- just click to place it exactly where you want it. If a flap is *not* set up as a parts bin (such as the default ''Squeak'' flap at the left edge of the screen) you can park objects there (this is an easy way to move objects from project to project) and you can place your own private controls there, etc. Everything in the default ''Squeak'' flap (and all the other default flaps, for that matter) is there only for illustrative purposes -- every user will want to fine-tune the flaps to suit his/her own style and needs. Each flap may be set up to appear on mouseover, dragover, both, or neither. See the menu items described below for more about these and other options. You can open a closed flap by clicking on its tab, or by dragging the tab toward the center of the screen You can close an open flap by clicking on its tab or by dragging the tab back off the edge of the screen. Drag the tab of a flap to reposition the tab and to resize the flap itself. Repositioning starts when you drag the cursor out of the original tab area. If flaps or their tabs seem wrongly positioned or lost, try issuing a restoreDisplay from the screen menu. The red-halo menu on a flap allows you to change the flap''s properties. For greatest ease of use, request ''keep this menu up'' here -- that way, you can easily explore all the options in the menu. tab color... Lets you change the color of the flap''s tab. flap color... Lets you change the color of the flap itself. use textual tab... If the tab is not textual, makes it become textual. change tab wording... If the tab is already textual, allows you to edit its wording. use graphical tab... If the tab is not graphical, makes it become graphical. choose tab graphic... If the tab is already graphical, allows you to change the picture. use solid tab... If the tab is not solid, makes it become solid, i.e. appear as a solid band of color along the entire length or width of the screen. parts-bin behavior If set, then dragging an object from the flap tears off a new copy of the object. dragover If set, the flap opens on dragover and closes again on drag-leave. mouseover If set, the flap opens on mouseover and closes again on mouse-leave. cling to edge... Governs which edge (left, right, top, bottom) the flap adheres to. shared If set, the same flap will be available in all projects; if not, the flap will will occur only in one project. destroy this flap Deletes the flap. To define a new flap, use ''make a new flap'', found in the ''flaps'' menu. To reinstate the default system flaps, you can use ''destroy all shared flaps'' from the ''flaps'' menu, and once they are destroyed, choose ''install default shared flaps''. To add, delete, or edit things on a given flap, it is often wise first to suspend the flap''s mouse-over and drag-over sensitivity, so it won''t keep disappearing on you while you''re trying to work with it. Besides the three standard flaps delivered with the default system, there are two other flaps readily available on demand from the ''flaps'' menu -- one is called ''Stack Tools'', which provides some tools useful for building stack-like content, the other is called ''Painting'', which provides a quick way to make a new painting. Simply clicking on the appropriate checkbox in the ''flaps'' menu will toggle the corresponding flap between being visible and not being visible in the project.'! ! !Flaps class methodsFor: 'menu support' stamp: 'sw 4/24/2001 11:03'! addIndividualGlobalFlapItemsTo: aMenu "Add items governing the enablement of specific global flaps to aMenu" | anItem | self globalFlapTabsIfAny do: [:aFlapTab | anItem _ aMenu addUpdating: #globalFlapWithIDEnabledString: enablementSelector: #showSharedFlaps target: self selector: #enableDisableGlobalFlapWithID: argumentList: {aFlapTab flapID}. anItem wordingArgument: aFlapTab flapID. anItem setBalloonText: aFlapTab balloonTextForFlapsMenu].! ! !Flaps class methodsFor: 'menu support' stamp: 'sw 6/11/2002 14:05'! enableEToyFlaps "Start using global flaps, plug-in version, given that they were not present." Cursor wait showWhile: [self addAndEnableEToyFlaps. self enableGlobalFlaps]! ! !Flaps class methodsFor: 'menu support' stamp: 'sw 11/22/2001 11:15'! enableGlobalFlaps "Start using global flaps, given that they were not present." Cursor wait showWhile: [SharedFlapsAllowed _ true. self globalFlapTabs. "This will create them" Smalltalk isMorphic ifTrue: [ActiveWorld addGlobalFlaps. self doAutomaticLayoutOfFlapsIfAppropriate. FlapTab allInstancesDo: [:aTab | aTab computeEdgeFraction]. ActiveWorld reformulateUpdatingMenus]]! ! !Flaps class methodsFor: 'menu support' stamp: 'sw 4/17/2001 13:50'! globalFlapWithIDEnabledString: aFlapID "Answer the string to be shown in a menu to represent the status of the givne flap regarding whether it it should be shown in this project." | aFlapTab wording | aFlapTab _ self globalFlapTabWithID: aFlapID. wording _ aFlapTab ifNotNil: [aFlapTab wording] ifNil: ['(', aFlapID, ')']. ^ (Project current isFlapIDEnabled: aFlapID) ifTrue: ['', wording] ifFalse: ['', wording]! ! !Flaps class methodsFor: 'menu support' stamp: 'dgd 8/31/2003 19:39'! setUpSuppliesFlapOnly "Set up the Supplies flap as the only shared flap. A special version formulated for this stand-alone use is used, defined in #newLoneSuppliesFlap" | supplies | SharedFlapTabs isEmptyOrNil ifFalse: "get rid of pre-existing guys if any" [SharedFlapTabs do: [:t | t referent delete. t delete]]. SharedFlapsAllowed _ true. SharedFlapTabs _ OrderedCollection new. SharedFlapTabs add: (supplies _ self newLoneSuppliesFlap). self enableGlobalFlapWithID: 'Supplies' translated. supplies setToPopOutOnMouseOver: false. Smalltalk isMorphic ifTrue: [ActiveWorld addGlobalFlaps. ActiveWorld reformulateUpdatingMenus]! ! !Flaps class methodsFor: 'menu support' stamp: 'dao 10/1/2004 12:59'! showSharedFlaps "Answer whether shared flaps are currently showing. Presumably it is in service of Alan's wishes to have flaps show sometimes on interior subprojects and sometomes on outer projects that Bob's CurrentProjectRefactoring is threaded into the logic here." ^ Project current showSharedFlaps! ! !Flaps class methodsFor: 'menu support' stamp: 'dao 10/1/2004 13:12'! suppressFlapsString "Answer the string to be shown in a menu to represent the suppress-flaps-in-this-project status" ^ Project current suppressFlapsString! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sw 11/22/2001 10:04'! automaticFlapLayoutChanged "Sent when the automaticFlapLayout preference changes. No senders in easily traceable in the image, but this is really sent by a Preference object!!" Preferences automaticFlapLayout ifTrue: [self positionNavigatorAndOtherFlapsAccordingToPreference]! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sw 11/22/2001 09:58'! doAutomaticLayoutOfFlapsIfAppropriate "Do automatic layout of flaps if appropriate" Preferences automaticFlapLayout ifTrue: [self positionNavigatorAndOtherFlapsAccordingToPreference]! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'dgd 8/31/2003 19:26'! enableClassicNavigatorChanged "The #classicNavigatorEnabled preference has changed. No senders in easily traceable in the image, but this is really sent by a Preference object!!" Preferences classicNavigatorEnabled ifTrue: [Flaps disableGlobalFlapWithID: 'Navigator' translated. Preferences enable: #showProjectNavigator. self disableGlobalFlapWithID: 'Navigator' translated.] ifFalse: [self enableGlobalFlapWithID: 'Navigator' translated. ActiveWorld addGlobalFlaps]. self doAutomaticLayoutOfFlapsIfAppropriate. Project current assureNavigatorPresenceMatchesPreference. ActiveWorld reformulateUpdatingMenus! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sd 1/16/2004 21:33'! fileOutChanges "Bug workaround for squeak-flap 'fileOutChanges' buttons which for a while were mistakenly sending their requests here..." ^ ChangeSet current verboseFileOut. ! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'dgd 8/31/2003 19:28'! makeNavigatorFlapResembleGoldenBar "At explicit request, make the flap-based navigator resemble the golden bar. No senders in the image, but sendable from a doit" "Flaps makeNavigatorFlapResembleGoldenBar" Preferences setPreference: #classicNavigatorEnabled toValue: false. Preferences setPreference: #showProjectNavigator toValue: false. (self globalFlapTabWithID: 'Navigator' translated) ifNil: [SharedFlapTabs add: self newNavigatorFlap delete]. self enableGlobalFlapWithID: 'Navigator' translated. Preferences setPreference: #navigatorOnLeftEdge toValue: true. (self globalFlapTabWithID: 'Navigator' translated) arrangeToPopOutOnMouseOver: true. ActiveWorld addGlobalFlaps. self doAutomaticLayoutOfFlapsIfAppropriate. Project current assureNavigatorPresenceMatchesPreference. ! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sw 4/17/2001 13:24'! orientationForEdge: anEdge "Answer the orientation -- #horizontal or #vertical -- that corresponds to the edge symbol" ^ (#(left right) includes: anEdge) ifTrue: [#vertical] ifFalse: [#horizontal]! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sw 4/17/2001 13:24'! paintFlapButton "Answer a button to serve as the paint flap" | pb oldArgs brush myButton m | pb _ PaintBoxMorph new submorphNamed: #paint:. pb ifNil: [(brush _ Form extent: 16@16 depth: 16) fillColor: Color red] ifNotNil: [oldArgs _ pb arguments. brush _ oldArgs third. brush _ brush copy: (2@0 extent: 42@38). brush _ brush scaledToSize: brush extent // 2]. myButton _ BorderedMorph new. myButton color: (Color r: 0.833 g: 0.5 b: 0.0); borderWidth: 2; borderColor: #raised. myButton addMorph: (m _ brush asMorph lock). myButton extent: m extent + (myButton borderWidth + 6). m position: myButton center - (m extent // 2). ^ myButton ! ! !Flaps class methodsFor: 'miscellaneous' stamp: 'sw 11/6/2000 14:23'! removeFromGlobalFlapTabList: aFlapTab "If the flap tab is in the global list, remove it" SharedFlapTabs remove: aFlapTab ifAbsent: []! ! !Flaps class methodsFor: 'new flap' stamp: 'dgd 8/31/2003 18:58'! addLocalFlap "Menu command -- let the user add a new project-local flap. Once the new flap is born, the user can tell it to become a shared flap. Obtain an initial name and edge for the flap, launch the flap, and also launch a menu governing the flap, so that the user can get started right away with customizing it." | aMenu reply aFlapTab aWorld edge | aMenu _ MVCMenuMorph entitled: 'Where should the new flap cling?' translated. aMenu defaultTarget: aMenu. #(left right top bottom) do: [:sym | aMenu add: sym asString translated selector: #selectMVCItem: argument: sym]. edge _ aMenu invokeAt: self currentHand position in: self currentWorld. edge ifNotNil: [reply _ FillInTheBlank request: 'Wording for this flap: ' translated initialAnswer: 'Flap' translated. reply isEmptyOrNil ifFalse: [aFlapTab _ self newFlapTitled: reply onEdge: edge. (aWorld _ self currentWorld) addMorphFront: aFlapTab. aFlapTab adaptToWorld: aWorld. aMenu _ aFlapTab buildHandleMenu: ActiveHand. aFlapTab addTitleForHaloMenu: aMenu. aFlapTab computeEdgeFraction. aMenu popUpEvent: ActiveEvent in: ActiveWorld]] ! ! !Flaps class methodsFor: 'new flap' stamp: 'sw 5/4/2001 23:59'! defaultColorForFlapBackgrounds "Answer the color to use, by default, in new flap backgrounds" ^ (Color blue mixed: 0.8 with: Color white) alpha: 0.6! ! !Flaps class methodsFor: 'new flap' stamp: 'sw 4/17/2001 13:24'! newFlapTitled: aString onEdge: anEdge "Create a new flap with the given title and place it on the given edge" ^ self newFlapTitled: aString onEdge: anEdge inPasteUp: self currentWorld ! ! !Flaps class methodsFor: 'new flap' stamp: 'di 11/19/2001 21:07'! newFlapTitled: aString onEdge: anEdge inPasteUp: aPasteUpMorph "Add a flap with the given title, placing it on the given edge, in the given pasteup" | aFlapBody aFlapTab | aFlapBody _ PasteUpMorph newSticky. aFlapTab _ FlapTab new referent: aFlapBody. aFlapTab setName: aString edge: anEdge color: (Color r: 0.516 g: 0.452 b: 1.0). anEdge == #left ifTrue: [aFlapTab position: (aPasteUpMorph left @ aPasteUpMorph top). aFlapBody extent: (200 @ aPasteUpMorph height)]. anEdge == #right ifTrue: [aFlapTab position: ((aPasteUpMorph right - aFlapTab width) @ aPasteUpMorph top). aFlapBody extent: (200 @ aPasteUpMorph height)]. anEdge == #top ifTrue: [aFlapTab position: ((aPasteUpMorph left + 50) @ aPasteUpMorph top). aFlapBody extent: (aPasteUpMorph width @ 200)]. anEdge == #bottom ifTrue: [aFlapTab position: ((aPasteUpMorph left + 50) @ (aPasteUpMorph bottom - aFlapTab height)). aFlapBody extent: (aPasteUpMorph width @ 200)]. aFlapBody beFlap: true. aFlapBody color: self defaultColorForFlapBackgrounds. ^ aFlapTab! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'dgd 4/3/2006 13:22'! addAndEnableEToyFlaps "Initialize the standard default out-of-box set of global flaps. This method creates them and places them in my class variable #SharedFlapTabs, but does not itself get them displayed." | aSuppliesFlap | SharedFlapTabs ifNotNil: [^ self]. SharedFlapTabs _ OrderedCollection new. aSuppliesFlap _ self newSuppliesFlapFromQuads: self quadsDefiningPlugInSuppliesFlap positioning: #right. aSuppliesFlap referent setNameTo: 'Supplies Flap' translated. "Per request from Kim Rose, 7/19/02" SharedFlapTabs add: aSuppliesFlap. "The #center designation doesn't quite work at the moment" Preferences showProjectNavigator ifTrue:[ SharedFlapTabs add: self newNavigatorFlap ]. self enableGlobalFlapWithID: 'Supplies' translated. Preferences showProjectNavigator ifTrue:[ self enableGlobalFlapWithID: 'Navigator' translated ]. SharedFlapsAllowed _ true. Project current flapsSuppressed: false. ^ SharedFlapTabs "Flaps addAndEnableEToyFlaps"! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'dgd 8/31/2003 18:44'! addNewDefaultSharedFlaps "Add the stack tools flap and the navigator flap to the global list, but do not have them showing initially. Transitional, called by the postscript of the FlapsOnBottom update; probably dispensable afterwards." SharedFlapTabs ifNotNil: [(self globalFlapTabWithID: 'Stack Tools' translated) ifNil: [SharedFlapTabs add: self newStackToolsFlap delete]. self enableGlobalFlapWithID: 'Stack Tools' translated. (self globalFlapTabWithID: 'Navigator' translated) ifNil: [SharedFlapTabs add: self newNavigatorFlap delete]. self enableGlobalFlapWithID: 'Navigator' translated. self currentWorld addGlobalFlaps]! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'dgd 4/3/2006 13:21'! addStandardFlaps "Initialize the standard default out-of-box set of global flaps. This method creates them and places them in my class variable #SharedFlapTabs, but does not itself get them displayed. " SharedFlapTabs ifNil: [SharedFlapTabs := OrderedCollection new]. SharedFlapTabs add: self newSqueakFlap. SharedFlapTabs add: self newSuppliesFlap. SharedFlapTabs add: self newToolsFlap. SharedFlapTabs add: self newWidgetsFlap. SharedFlapTabs add: self newStackToolsFlap. Preferences showProjectNavigator ifTrue:[SharedFlapTabs add: self newNavigatorFlap]. SharedFlapTabs add: self newPaintingFlap. SharedFlapTabs add: self newObjectsFlap. self disableGlobalFlapWithID: 'Stack Tools' translated. self disableGlobalFlapWithID: 'Painting' translated. Preferences showProjectNavigator ifTrue:[self disableGlobalFlapWithID: 'Navigator' translated]. ^ SharedFlapTabs! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 9/2/2004 15:49'! defaultsQuadsDefiningPlugInSuppliesFlap "Answer a list of quads which define the objects to appear in the default Supplies flap used in the Plug-in image" ^ #( (ObjectsTool newStandAlone 'Object Catalog' 'A tool that lets you browse the catalog of available objects') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'Stop, Step, and Go buttons for controlling all your scripts at once. The tool can also be "opened up" to control each script in your project individually.') (TrashCanMorph new 'Trash' 'A tool for discarding objects') (GrabPatchMorph new 'Grab Patch' 'Allows you to create a new Sketch by grabbing a rectangular patch from the screen') (LassoPatchMorph new 'Lasso' 'Allows you to create a new Sketch by lassoing an area from the screen') (StickyPadMorph newStandAlone 'Sticky Pad' 'Each time you obtain one of these pastel, translucent, borderless rectangles, it will be a different color from the previous time.') "(PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there')" (TextMorph boldAuthoringPrototype 'Text' 'Text that you can edit into anything you desire.') (RecordingControlsMorph authoringPrototype 'Sound' 'A device for making sound recordings.') (RectangleMorph authoringPrototype 'Rectangle' 'A rectangle') (RectangleMorph roundRectPrototype 'RoundRect' 'A rectangle with rounded corners') (EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') (StarMorph authoringPrototype 'Star' 'A star') (CurveMorph authoringPrototype 'Curve' 'A curve') (PolygonMorph authoringPrototype 'Polygon' 'A straight-sided figure with any number of sides') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (BookMorph nextPageButton 'NextPage' 'A button that takes you to the next page') (BookMorph previousPageButton 'PreviousPage' 'A button that takes you to the previous page') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, etc.') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations') (SimpleSliderMorph authoringPrototype 'Slider' 'A slider for showing and setting numeric values.') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (BookMorph authoringPrototype 'Book' 'A multi-paged structure') (ClockMorph authoringPrototype 'Clock' 'A simple digital clock') (RandomNumberTile new 'Random' 'A random-number tile for use with tile scripting')) asOrderedCollection! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 8/12/2001 16:55'! initializeStandardFlaps "Initialize the standard default out-of-box set of global flaps. This method creates them and places them in my class variable #SharedFlapTabs, but does not itself get them displayed." SharedFlapTabs _ nil. self addStandardFlaps! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 8/6/2004 11:37'! newLoneSuppliesFlap "Answer a fully-instantiated flap named 'Supplies' to be placed at the bottom of the screen, for use when it is the only flap shown upon web launch" | aFlapTab aStrip leftEdge | "Flaps setUpSuppliesFlapOnly" aStrip _ PartsBin newPartsBinWithOrientation: #leftToRight andColor: Color red muchLighter from: #( (TrashCanMorph new 'Trash' 'A tool for discarding objects') (ScriptingSystem scriptControlButtons 'Status' 'Buttons to run, stop, or single-step scripts') (AllScriptsTool allScriptsToolForActiveWorld 'All Scripts' 'A tool that lets you control all the running scripts in your world') (PaintInvokingMorph new 'Paint' 'Drop this into an area to start making a fresh painting there') (RectangleMorph authoringPrototype 'Rectangle' 'A rectangle' ) (RectangleMorph roundRectPrototype 'RoundRect' 'A rectangle with rounded corners') (EllipseMorph authoringPrototype 'Ellipse' 'An ellipse or circle') (StarMorph authoringPrototype 'Star' 'A star') (CurveMorph authoringPrototype 'Curve' 'A curve') (PolygonMorph authoringPrototype 'Polygon' 'A straight-sided figure with any number of sides') (TextMorph authoringPrototype 'Text' 'Text that you can edit into anything you desire.') (SimpleSliderMorph authoringPrototype 'Slider' 'A slider for showing and setting numeric values.') (JoystickMorph authoringPrototype 'Joystick' 'A joystick-like control') (ScriptingSystem prototypicalHolder 'Holder' 'A place for storing alternative pictures in an animation, ec.') (ScriptableButton authoringPrototype 'Button' 'A Scriptable button') (PasteUpMorph authoringPrototype 'Playfield' 'A place for assembling parts or for staging animations') (BookMorph authoringPrototype 'Book' 'A multi-paged structure') (TabbedPalette authoringPrototype 'Tabs' 'A structure with tabs') (RecordingControlsMorph authoringPrototype 'Sound' 'A device for making sound recordings.') (MagnifierMorph newRound 'Magnifier' 'A magnifying glass') (ImageMorph authoringPrototype 'Picture' 'A non-editable picture of something') (ClockMorph authoringPrototype 'Clock' 'A simple digital clock') (BookMorph previousPageButton 'Previous' 'A button that takes you to the previous page') (BookMorph nextPageButton 'Next' 'A button that takes you to the next page') ). aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Supplies' translated edge: #bottom color: Color red lighter. aStrip extent: self currentWorld width @ 78. leftEdge _ ((Display width - (16 + aFlapTab width)) + 556) // 2. aFlapTab position: (leftEdge @ (self currentWorld height - aFlapTab height)). aStrip beFlap: true. aStrip autoLineLayout: true. ^ aFlapTab! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'dgd 8/31/2003 19:03'! newNavigatorFlap "Answer a newly-created flap which adheres to the bottom edge of the screen and which holds the project navigator controls. " | aFlapTab navBar aFlap | navBar _ ProjectNavigationMorph preferredNavigator new. aFlap _ PasteUpMorph newSticky borderWidth: 0; extent: navBar extent + (0@20); color: (Color orange alpha: 0.8); beFlap: true; addMorph: navBar beSticky. aFlap hResizing: #shrinkWrap; vResizing: #shrinkWrap. aFlap useRoundedCorners. aFlap setNameTo: 'Navigator Flap' translated. navBar fullBounds. "to establish width" aFlapTab _ FlapTab new referent: aFlap. aFlapTab setName: 'Navigator' translated edge: #bottom color: Color orange. aFlapTab position: ((navBar width // 2) - (aFlapTab width // 2)) @ (self currentWorld height - aFlapTab height). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Navigator' translated " ! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 9/3/2004 12:51'! newObjectsFlap "Answer a fully-instantiated flap named 'Objects' to be placed at the top of the screen." | aFlapTab anObjectsTool | anObjectsTool _ ObjectsTool new. anObjectsTool initializeForFlap. aFlapTab _ FlapTab new referent: anObjectsTool beSticky. aFlapTab setName: 'Objects' translated edge: #top color: Color red lighter. aFlapTab position: ((Display width - (aFlapTab width + 22)) @ 0). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. anObjectsTool extent: self currentWorld width @ 200. anObjectsTool beFlap: true. anObjectsTool color: Color red muchLighter. anObjectsTool clipSubmorphs: true. anObjectsTool showCategories. ^ aFlapTab! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'dgd 8/31/2003 19:50'! newPaintingFlap "Add a flap with the paint palette in it" | aFlap aFlapTab | "Flaps reinstateDefaultFlaps. Flaps addPaintingFlap" aFlap _ PasteUpMorph new borderWidth: 0. aFlap color: Color transparent. aFlap layoutPolicy: TableLayout new. aFlap hResizing: #shrinkWrap. aFlap vResizing: #shrinkWrap. aFlap cellPositioning: #topLeft. aFlap clipSubmorphs: false. aFlap beSticky. "really?!!" aFlap addMorphFront: PaintBoxMorph new. aFlap setProperty: #flap toValue: true. aFlap fullBounds. "force layout" aFlapTab _ FlapTab new referent: aFlap. aFlapTab setNameTo: 'Painting' translated. aFlapTab setProperty: #priorWording toValue: 'Paint' translated. aFlapTab useGraphicalTab. aFlapTab removeAllMorphs. aFlapTab setProperty: #paintingFlap toValue: true. aFlapTab addMorphFront: "(SketchMorph withForm: (ScriptingSystem formAtKey: #PaintingFlapPic))" self paintFlapButton. aFlapTab cornerStyle: #rounded. aFlapTab edgeToAdhereTo: #right. aFlapTab setToPopOutOnDragOver: false. aFlapTab setToPopOutOnMouseOver: false. aFlapTab on: #mouseUp send: #startOrFinishDrawing: to: aFlapTab. aFlapTab setBalloonText:'Click here to start or finish painting.' translated. aFlapTab fullBounds. "force layout" aFlapTab position: (0@6). self currentWorld addMorphFront: aFlapTab. ^ aFlapTab! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 7/29/2004 10:12'! newSqueakFlap "Answer a new default 'Squeak' flap for the left edge of the screen" | aFlap aFlapTab aButton aClock buttonColor anOffset bb aFont | aFlap _ PasteUpMorph newSticky borderWidth: 0. aFlapTab _ FlapTab new referent: aFlap. aFlapTab setName: 'Squeak' translated edge: #left color: Color brown lighter lighter. aFlapTab position: (0 @ ((Display height - aFlapTab height) // 2)). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. aFlap cellInset: 14@14. aFlap beFlap: true. aFlap color: (Color brown muchLighter lighter "alpha: 0.3"). aFlap extent: 150 @ self currentWorld height. aFlap layoutPolicy: TableLayout new. aFlap wrapCentering: #topLeft. aFlap layoutInset: 2. aFlap listDirection: #topToBottom. aFlap wrapDirection: #leftToRight. "self addProjectNavigationButtonsTo: aFlap." anOffset _ 16. aClock _ ClockMorph newSticky. aClock color: Color red. aClock showSeconds: false. aClock font: (TextStyle default fontAt: 3). aClock step. aClock setBalloonText: 'The time of day. If you prefer to see seconds, check out my menu.' translated. aFlap addCenteredAtBottom: aClock offset: anOffset. buttonColor _ Color cyan muchLighter. bb _ SimpleButtonMorph new target: SmalltalkImage current. bb color: buttonColor. aButton _ bb copy. aButton actionSelector: #saveSession. aButton setBalloonText: 'Make a complete snapshot of the current state of the image onto disk.' translated. aButton label: 'save' translated font: (aFont _ ScriptingSystem fontForTiles). aFlap addCenteredAtBottom: aButton offset: anOffset. aButton _ bb copy target: Utilities. aButton actionSelector: #updateFromServer. aButton label: 'load code updates' translated font: aFont. aButton color: buttonColor. aButton setBalloonText: 'Check the Squeak server for any new code updates, and load any that are found.' translated. aFlap addCenteredAtBottom: aButton offset: anOffset. aButton _ SimpleButtonMorph new target: SmalltalkImage current; actionSelector: #aboutThisSystem; label: 'about this system' translated font: aFont. aButton color: buttonColor. aButton setBalloonText: 'click here to find out version information' translated. aFlap addCenteredAtBottom: aButton offset: anOffset. aFlap addCenteredAtBottom: (Preferences themeChoiceButtonOfColor: buttonColor font: aFont) offset: anOffset. aButton _ TrashCanMorph newSticky. aFlap addCenteredAtBottom: aButton offset: anOffset. aButton startStepping. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Squeak' translated "! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 8/6/2004 11:39'! newStackToolsFlap "Add a flap with stack tools in it" | aFlapTab aStrip | aStrip _ PartsBin newPartsBinWithOrientation: #leftToRight andColor: (Color red muchLighter "alpha: 0.2") from: self quadsDefiningStackToolsFlap. aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Stack Tools' translated edge: #bottom color: Color brown lighter lighter. aFlapTab position: ((Display width - (aFlapTab width + 226)) @ (self currentWorld height - aFlapTab height)). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. aStrip extent: self currentWorld width @ 78. aStrip beFlap: true. aStrip autoLineLayout: true. aStrip extent: self currentWorld width @ 70. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Stack Tools' translated"! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 6/11/2002 14:00'! newSuppliesFlap "Answer a fully-instantiated flap named 'Supplies' to be placed at the bottom of the screen; this is for the non-plug-in-version" ^ self newSuppliesFlapFromQuads: self quadsDefiningSuppliesFlap positioning: #right! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'mir 10/7/2005 16:32'! newSuppliesFlapFromQuads: quads positioning: positionSymbol "Answer a fully-instantiated flap named 'Supplies' to be placed at the bottom of the screen. Use #center as the positionSymbol to have it centered at the bottom of the screen, or #right to have it placed off near the right edge." | aFlapTab aStrip hPosition | aStrip _ PartsBin newPartsBinWithOrientation: #leftToRight andColor: Color red muchLighter from: quads. self twiddleSuppliesButtonsIn: aStrip. aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Supplies' translated edge: #bottom color: Color red lighter. hPosition _ positionSymbol == #center ifTrue: [(Display width // 2) - (aFlapTab width // 2)] ifFalse: [Display width - (aFlapTab width + 22)]. aFlapTab position: (hPosition @ (self currentWorld height - aFlapTab height)). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. aStrip extent: self currentWorld width @ 136. aStrip beFlap: true. aStrip autoLineLayout: true. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Supplies' translated"! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 8/6/2004 11:39'! newToolsFlap "Answer a newly-created flap which adheres to the right edge of the screen and which holds prototypes of standard tools." | aFlapTab aStrip | aStrip _ PartsBin newPartsBinWithOrientation: #topToBottom andColor: (Color orange muchLighter alpha: 0.8) from: self quadsDefiningToolsFlap. aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Tools' translated edge: #right color: Color orange lighter. aFlapTab position: (self currentWorld width - aFlapTab width) @ ((Display height - aFlapTab height) // 2). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. aStrip extent: (90 @ self currentWorld height). aStrip beFlap: true. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Tools' translated " ! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'nk 8/6/2004 11:40'! newWidgetsFlap "Answer a newly-created flap which adheres to the bottom edge of the screen and which holds prototypes of standard widgets. " | aFlapTab aStrip | aStrip _ PartsBin newPartsBinWithOrientation: #leftToRight andColor: (Color blue muchLighter alpha: 0.8) from: self quadsDefiningWidgetsFlap. aFlapTab _ FlapTab new referent: aStrip beSticky. aFlapTab setName: 'Widgets' translated edge: #bottom color: Color blue lighter lighter. aFlapTab position: ((Display width - (aFlapTab width + 122)) @ (self currentWorld height - aFlapTab height)). aFlapTab setBalloonText: aFlapTab balloonTextForFlapsMenu. aStrip extent: self currentWorld width @ 78. aStrip beFlap: true. aStrip autoLineLayout: true. ^ aFlapTab "Flaps replaceGlobalFlapwithID: 'Widgets' translated " ! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 3/3/2004 13:38'! quadsDefiningPlugInSuppliesFlap "Answer a list of quads which define the objects to appear in the default Supplies flap used in the Plug-in image" ^ self registeredFlapsQuadsAt: 'PlugIn Supplies'! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'asm 3/13/2003 10:25'! quadsDefiningStackToolsFlap "Answer a structure defining the items on the default system Stack Tools flap" ^ self registeredFlapsQuadsAt: 'Stack Tools' "Flaps replaceGlobalFlapwithID: 'Stack Tools'"! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'asm 3/13/2003 10:26'! quadsDefiningSuppliesFlap "Answer a list of quads which define the objects to appear in the default Supplies flap" ^ self registeredFlapsQuadsAt: 'Supplies'! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'asm 3/13/2003 10:51'! quadsDefiningToolsFlap "Answer a structure defining the default Tools flap" ^ self registeredFlapsQuadsAt: 'Tools'! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'asm 3/13/2003 10:26'! quadsDefiningWidgetsFlap "Answer a structure defining the default Widgets flap" ^ self registeredFlapsQuadsAt: 'Widgets'! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'asm 3/13/2003 10:26'! quadsDeiningScriptingFlap "Answer a structure defining the default items in the Scripting flap" ^ self registeredFlapsQuadsAt: 'Scripting'! ! !Flaps class methodsFor: 'predefined flaps' stamp: 'sw 4/3/2003 16:35'! twiddleSuppliesButtonsIn: aStrip "Munge item(s) in the strip whose names as seen in the parts bin should be different from the names to be given to resulting torn-off instances" (aStrip submorphs detect: [:m | m target == StickyPadMorph] ifNone: [nil]) ifNotNilDo: [:aButton | aButton arguments: {#newStandAlone. 'tear off'}]! ! !Flaps class methodsFor: 'replacement' stamp: 'sw 7/25/2004 00:56'! replaceGlobalFlapwithID: flapID "If there is a global flap with flapID, replace it with an updated one." | replacement tabs | (tabs _ self globalFlapTabsWithID: flapID) size = 0 ifTrue: [^ self]. tabs do: [:tab | self removeFlapTab: tab keepInList: false]. flapID = 'Stack Tools' translated ifTrue: [replacement _ self newStackToolsFlap]. flapID = 'Supplies' translated ifTrue: [replacement _ self newSuppliesFlapFromQuads: (Preferences eToyFriendly ifFalse: [self quadsDefiningSuppliesFlap] ifTrue: [self quadsDefiningPlugInSuppliesFlap]) positioning: #right]. flapID = 'Tools' translated ifTrue: [replacement _ self newToolsFlap]. flapID = 'Widgets' translated ifTrue: [replacement _ self newWidgetsFlap]. flapID = 'Navigator' translated ifTrue: [replacement _ self newNavigatorFlap]. flapID = 'Squeak' translated ifTrue: [replacement _ self newSqueakFlap]. replacement ifNil: [^ self]. self addGlobalFlap: replacement. self currentWorld ifNotNil: [self currentWorld addGlobalFlaps] "Flaps replaceFlapwithID: 'Widgets' translated "! ! !Flaps class methodsFor: 'replacement' stamp: 'sw 5/3/1999 22:44'! replacePartSatisfying: elementBlock inGlobalFlapSatisfying: flapBlock with: replacement "If any global flap satisfies flapBlock, look in it for a part satisfying elementBlock; if such a part is found, replace it with the replacement morph, make sure the flap's layout is made right, etc." | aFlapTab flapPasteUp anElement | aFlapTab _ self globalFlapTabsIfAny detect: [:aTab | flapBlock value: aTab] ifNone: [^ self]. flapPasteUp _ aFlapTab referent. anElement _ flapPasteUp submorphs detect: [:aMorph | elementBlock value: aMorph] ifNone: [^ self]. flapPasteUp replaceSubmorph: anElement by: replacement. flapPasteUp replaceTallSubmorphsByThumbnails; setPartsBinStatusTo: true. "Flaps replacePartSatisfying: [:el | (el isKindOf: MorphThumbnail) and: [(el morphRepresented isKindOf: SystemWindow) and: [el morphRepresented label = 'scripting area']]] inGlobalFlapSatisfying: [:fl | (fl submorphs size > 0) and: [(fl submorphs first isKindOf: TextMorph) and: [(fl submorphs first contents string copyWithout: Character cr) = 'Tools']]] with: ScriptingSystem newScriptingSpace"! ! !Flaps class methodsFor: 'replacement' stamp: 'sw 4/17/2001 13:15'! replacePartSatisfying: elementBlock inGlobalFlapWithID: aFlapID with: replacement "If a global flapl exists with the given flapID, look in it for a part satisfying elementBlock; if such a part is found, replace it with the replacement morph, make sure the flap's layout is made right, etc." ^ self replacePartSatisfying: elementBlock inGlobalFlapSatisfying: [:fl | fl flapID = aFlapID] with: replacement! ! !Flaps class methodsFor: 'replacement' stamp: 'dgd 8/31/2003 19:41'! replaceToolsFlap "if there is a global tools flap, replace it with an updated one." self replaceGlobalFlapwithID: 'Tools' translated "Flaps replaceToolsFlap"! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/17/2001 13:31'! addGlobalFlap: aFlapTab "Add the given flap tab to the list of shared flaps" SharedFlapTabs ifNil: [SharedFlapTabs _ OrderedCollection new]. SharedFlapTabs add: aFlapTab! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 7/24/2001 22:01'! enableOnlyGlobalFlapsWithIDs: survivorList "In the current project, suppress all global flaps other than those with ids in the survivorList" self globalFlapTabsIfAny do: [:aFlapTab | (survivorList includes: aFlapTab flapID) ifTrue: [self enableGlobalFlapWithID: aFlapTab flapID] ifFalse: [self disableGlobalFlapWithID: aFlapTab flapID]]. ActiveWorld addGlobalFlaps "Flaps enableOnlyGlobalFlapsWithIDs: #('Supplies')"! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/27/2001 16:36'! globalFlapTabOrDummy: aName "Answer a global flap tab in the current image with the given name. If none is found, answer a dummy StringMorph for some reason (check with tk about the use of this)" | gg | (gg _ self globalFlapTab: aName) ifNil: [^ StringMorph contents: aName, ' can''t be found']. ^ gg! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 5/5/2001 02:41'! globalFlapTabs "Answer the list of shared flap tabs, creating it if necessary. Much less aggressive is #globalFlapTabsIfAny" SharedFlapTabs ifNil: [self initializeStandardFlaps]. ^ SharedFlapTabs copy! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/23/2001 18:04'! globalFlapTabsIfAny "Answer a list of the global flap tabs, but it they don't exist, just answer an empty list" ^ SharedFlapTabs copy ifNil: [Array new]! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/8/2002 08:41'! globalFlapTabsWithID: aFlapID "Answer all flap tabs whose ids start with the given id" ^ self globalFlapTabsIfAny select: [:aFlapTab | (aFlapTab flapID = aFlapID) or: [FlapTab givenID: aFlapTab flapID matches: aFlapID]] "Flaps globalFlapTabsWithID: 'Stack Tools'"! ! !Flaps class methodsFor: 'shared flaps' stamp: 'di 11/19/2001 22:07'! globalFlapTabWithID: aFlapID "answer the global flap tab with the given id, or nil if none" ^ self globalFlapTabsIfAny detect: [:aFlapTab | aFlapTab flapID = aFlapID] ifNone: ["Second try allows sequence numbers" self globalFlapTabsIfAny detect: [:aFlapTab | FlapTab givenID: aFlapTab flapID matches: aFlapID] ifNone: [nil]]! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/27/2001 16:34'! globalFlapTab: aName "Answer the global flap tab in the current system whose flapID is the same as aName, or nil if none found." | idToMatch | idToMatch _ (aName beginsWith: 'flap: ') ifTrue: "Ted's old scheme; this convention may still be found in pre-existing content that has been externalized" [aName copyFrom: 7 to: aName size] ifFalse: [aName]. ^ self globalFlapTabsIfAny detect: [:ft | ft flapID = idToMatch] ifNone: [nil]! ! !Flaps class methodsFor: 'shared flaps' stamp: 'dgd 8/31/2003 19:27'! positionNavigatorAndOtherFlapsAccordingToPreference "Lay out flaps along the designated edge right-to-left, possibly positioning the navigator flap, exceptionally, on the left." | ids | ids _ Preferences navigatorOnLeftEdge ifTrue: [{'Navigator' translated}] ifFalse: [#()]. Flaps positionVisibleFlapsRightToLeftOnEdge: #bottom butPlaceAtLeftFlapsWithIDs: ids "Flaps positionNavigatorAndOtherFlapsAccordingToPreference"! ! !Flaps class methodsFor: 'shared flaps' stamp: 'dgd 8/31/2003 19:29'! positionVisibleFlapsRightToLeftOnEdge: edgeSymbol butPlaceAtLeftFlapsWithIDs: idList "Lay out flaps along the designated edge right-to-left, while laying left-to-right any flaps found in the exception list Flaps positionVisibleFlapsRightToLeftOnEdge: #bottom butPlaceAtLeftFlapWithIDs: {'Navigator' translated. 'Supplies' translated} Flaps sharedFlapsAlongBottom" | leftX flapList flapsOnRight flapsOnLeft | flapList _ self globalFlapTabsIfAny select: [:aFlapTab | aFlapTab isInWorld and: [aFlapTab edgeToAdhereTo == edgeSymbol]]. flapsOnLeft _ flapList select: [:fl | idList includes: fl flapID]. flapList removeAll: flapsOnLeft. flapsOnRight _ flapList asSortedCollection: [:f1 :f2 | f1 left > f2 left]. leftX _ ActiveWorld width - 15. flapsOnRight do: [:aFlapTab | aFlapTab right: leftX - 3. leftX _ aFlapTab left]. leftX _ ActiveWorld left. flapsOnLeft _ flapsOnLeft asSortedCollection: [:f1 :f2 | f1 left > f2 left]. flapsOnLeft do: [:aFlapTab | aFlapTab left: leftX + 3. leftX _ aFlapTab right]. (flapsOnLeft asOrderedCollection, flapsOnRight asOrderedCollection) do: [:ft | ft computeEdgeFraction. ft flapID = 'Navigator' translated ifTrue: [ft referent left: (ft center x - (ft referent width//2) max: 0)]] ! ! !Flaps class methodsFor: 'shared flaps' stamp: 'mir 8/24/2001 20:42'! removeDuplicateFlapTabs "Remove flaps that were accidentally added multiple times" "Flaps removeDuplicateFlapTabs" | tabs duplicates same | SharedFlapTabs copy ifNil: [^self]. tabs _ SharedFlapTabs copy. duplicates _ Set new. tabs do: [:tab | same _ tabs select: [:each | each wording = tab wording]. same isEmpty not ifTrue: [ same removeFirst. duplicates addAll: same]]. SharedFlapTabs removeAll: duplicates! ! !Flaps class methodsFor: 'shared flaps' stamp: 'sw 4/24/2001 11:17'! sharedFlapsAllowed "Answer whether the shared flaps feature is allowed in this system" ^ SharedFlapsAllowed ifNil: [SharedFlapsAllowed _ SharedFlapTabs isEmptyOrNil not]! ! !Flaps class methodsFor: 'shared flaps' stamp: 'dgd 10/7/2003 22:47'! sharedFlapsAlongBottom "Put all shared flaps (except Painting which can't be moved) along the bottom" "Flaps sharedFlapsAlongBottom" | leftX unordered ordered | unordered _ self globalFlapTabsIfAny asIdentitySet. ordered _ Array streamContents: [:s | { 'Squeak' translated. 'Navigator' translated. 'Supplies' translated. 'Widgets' translated. 'Stack Tools' translated. 'Tools' translated. 'Painting' translated. } do: [:id | (self globalFlapTabWithID: id) ifNotNilDo: [:ft | unordered remove: ft. id = 'Painting' translated ifFalse: [s nextPut: ft]]]]. "Pace off in order from right to left, setting positions" leftX _ Display width-15. ordered , unordered asArray reverseDo: [:ft | ft setEdge: #bottom. ft right: leftX - 3. leftX _ ft left]. "Put Nav Bar centered under tab if possible" (self globalFlapTabWithID: 'Navigator' translated) ifNotNilDo: [:ft | ft referent left: (ft center x - (ft referent width//2) max: 0)]. self positionNavigatorAndOtherFlapsAccordingToPreference. ! ! CompressedBoundaryShape subclass: #FlashBoundaryShape instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support'! !FlashBoundaryShape methodsFor: 'accessing' stamp: 'ar 11/9/1998 02:30'! complexity ^points size // 3! ! !FlashBoundaryShape methodsFor: 'disk i/o' stamp: 'nk 8/30/2004 07:49'! compress (points isOctetString) ifFalse:[ points := FlashCodec compress: self. leftFills := rightFills := lineWidths := lineFills := fillStyles := nil].! ! !FlashBoundaryShape methodsFor: 'disk i/o' stamp: 'nk 8/30/2004 07:49'! decompress | newShape | (points isOctetString) 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].! ! !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! ! FlashCharacterMorph subclass: #FlashButtonMorph instanceVariableNames: 'events sounds target' classVariableNames: 'ActionHelpText' poolDictionaries: '' category: 'Flash-Morphs'! !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: '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: '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: '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: 'events-processing' stamp: 'ar 9/12/2000 23:05'! handlerForMouseDown: anEvent "Don't give anybody over me a chance" ^self! ! !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: 'geometry testing' 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: '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: '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: 'dgd 8/30/2003 21:41'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addLine. aCustomMenu add: 'set custom action' translated action: #addCustomAction. aCustomMenu add: 'remove all actions' translated action: #removeActions. ! ! !FlashButtonMorph methodsFor: 'menu' stamp: 'ar 2/23/1999 00:37'! removeActions events := nil. target := nil.! ! !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 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: 'Flash-Morphs'! !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: '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: 'drawing' stamp: 'ar 11/17/1998 17:52'! fullDrawOn: canvas renderTime := Time millisecondsToRun:[super fullDrawOn: canvas].! ! !FlashCharacterMorph methodsFor: 'initialization' 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: '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: 'menu' stamp: 'dgd 8/30/2003 21:41'! addCustomMenuItems: aMenu hand: aHand super addCustomMenuItems: aMenu hand: aHand. aMenu add:'add project target' translated action: #addProjectTarget. aMenu add:'remove project target' translated 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.! ! !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: '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: 'testing' stamp: 'ar 8/14/1998 18:17'! stepTime ^stepTime ifNil:[super stepTime]! ! !FlashCharacterMorph methodsFor: 'testing' 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.! ! Object subclass: #FlashCodec instanceVariableNames: 'stream' classVariableNames: '' poolDictionaries: '' category: 'Flash-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: '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 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 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 methodsFor: 'initialize'! initialize stream := WriteStream on: (String new: 1000).! ! !FlashCodec methodsFor: 'initialize'! on: aStream stream := aStream! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashCodec class instanceVariableNames: ''! !FlashCodec class methodsFor: 'compressing'! compress: aFlashBoundaryShape ^self new compress: aFlashBoundaryShape! ! !FlashCodec class methodsFor: 'compressing' stamp: 'nk 7/30/2004 21:51'! compressPoints: points ^(self new compressPoints: points) contents! ! !FlashCodec class methodsFor: 'decompressing'! decompress: aStream ^(self new on: aStream) decompress! ! !FlashCodec class methodsFor: 'decompressing'! decompressPoints: aStream ^(self new on: aStream) decompressPoints! ! Object subclass: #FlashColorTransform instanceVariableNames: 'rMul rAdd gMul gAdd bMul bAdd aMul aAdd' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support'! !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: 'initialize' stamp: 'ar 11/24/1998 15:01'! initialize rMul := bMul := gMul := aMul := 1.0. rAdd := bAdd := gAdd := aAdd := 0.0.! ! !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! ! DamageRecorder subclass: #FlashDamageRecorder instanceVariableNames: 'fullDamageRect' classVariableNames: '' poolDictionaries: '' category: 'Flash-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: 'Flash-Import'! !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 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 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: '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 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: 'initialize' stamp: 'ar 7/4/1998 20:14'! on: aStream aStream binary. stream := FlashFileStream on: aStream. log := Transcript. log := nil.! ! !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 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 5/4/2001 16:21'! processActionWaitForFrame: data | length frame skip | length := data nextWord. length = 3 ifFalse:["Something is wrong" 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 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 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 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: '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 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 7/8/2006 21:19'! processAlphaBitmapData: data " read zlib compressed alphabitmapdata from data stream" | zLibStream width height r g b a image | "read width and height of image" width := data nextWord. height := data nextWord. "self halt." zLibStream := ZLibReadStream on: data stream contents from: data position + 1 to: data size. image := Form extent: (width @ height) depth: 32. 1 to: image bits size do:[:i| a := zLibStream next. r := zLibStream next. g := zLibStream next. b := zLibStream next. a = 0 ifTrue:[ image bits at: i put: 0] ifFalse:[image bits at: i put: ( a << 24 ) + ( r << 16) + ( g << 8) + b] ]. ^ image ! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/8/2006 21:20'! processAlphaColorMapData: data " read zlib compressed alphacolormapdata from data stream" | zLibStream width height colorTableSize colorTable r g b a image color | "read width and height of image" width := data nextWord. height := data nextWord. colorTableSize := data nextByte. zLibStream := ZLibReadStream on: data stream contents from: data position + 1 to: data size. "read color table" colorTable := Array new: colorTableSize + 1. 1 to: colorTableSize + 1 do:[ :i| r := zLibStream next. g := zLibStream next. b := zLibStream next. a := zLibStream next. colorTable at: i put: ( a << 24 ) + ( r << 16) + ( g << 8) + b. ]. "round width to 32 bit allignment" (width \\ 32) > 0 ifTrue:[ width := 32 * (( width // 32 ) + 1)]. image := Form extent: (width @ height) depth: 32. 1 to: image bits size do:[:i| color := colorTable at: zLibStream next. (color >> 24) = 0 ifTrue:[ image bits at: i put: 0] ifFalse:[image bits at: i put: color] ]. ^ image ! ! !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 7/8/2006 21:20'! processDefineBitsJPEG2: data | id image decoder sPos | id := data nextWord. decoder := FlashJPEGDecoder new. decoder isStreaming: self isStreaming. sPos := data stream position. decoder decodeJPEGTables: data. data stream position: sPos. data atEnd ifFalse: [ image := decoder decodeNextImageFrom: data. Preferences compressFlashImages ifTrue:[image := image asFormOfDepth: 8]. self recordBitmap: id data: image]. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/8/2006 21:20'! processDefineBitsJPEG3: data | id image decoder alphaDataOffset sPos alphaBytes zLibStream alphaByte rgbWord | id := data nextWord. self flag: #wrongSpec. alphaDataOffset := data nextLong. decoder := FlashJPEGDecoder new. decoder isStreaming: self isStreaming. sPos := data stream position. decoder decodeJPEGTables: data. data stream position: sPos. image := decoder decodeNextImage32From: (data streamNextBytes: alphaDataOffset) . alphaBytes := image height * image width. "Note: We must read the zlib compressed alpha values here." data stream position: ( sPos + alphaDataOffset). zLibStream := ZLibReadStream on: data stream contents from: ( sPos + alphaDataOffset + 1 ) to: data size. 1 to: alphaBytes do:[ :i | alphaByte := zLibStream next. rgbWord := image bits at: i. alphaByte = 0 ifTrue:[image bits at: i put: 0 ] ifFalse:[image bits at: i put: (alphaByte << 24 + (rgbWord bitAnd: 16r00FFFFFF))] ]. self recordBitmap: id data: image. ^true! ! !FlashFileReader methodsFor: 'processing tags' stamp: 'ar 7/8/2006 21:20'! processDefineBitsLossless2: data | id format image | id := data nextWord. format := data nextByte. format = 3 ifTrue:[image := self processAlphaColorMapData: data ] ifFalse:[image := self processAlphaBitmapData: data]. self recordBitmap: id data: image. ^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: '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: 'sd 1/30/2004 15:17'! 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 close. ]. Transcript cr; print: time / 1000.0; show:' secs to read file'! ! !FlashFileReader methodsFor: 'reading' stamp: 'dgd 9/21/2003 17:38'! 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:[ (self confirm:('This file''s version ({1}) is higher than the currently supported version ({2}). It may contain features that are not supported and it may not display correctly. Do you want to continue?' translated format:{version. self maximumSupportedVersion})) ifFalse:[^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: '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: 'ar 5/4/2001 16:22'! maximumSupportedVersion ^3! ! !FlashFileReader methodsFor: 'private' stamp: 'ar 7/12/1998 23:41'! warn: aString Transcript cr; show: aString.! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashFileReader class instanceVariableNames: ''! !FlashFileReader class methodsFor: 'accessing' stamp: 'ar 10/16/1998 00:29'! tagTable ^TagTable! ! !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! ! Stream subclass: #FlashFileStream instanceVariableNames: 'stream bitBuffer bitPosition hasAlpha' classVariableNames: '' poolDictionaries: '' category: 'Flash-Import'! !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: 'jf 12/13/2005 11:25'! contents ^ stream contents! ! !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: 'jf 12/14/2005 14:07'! streamNextBytes: nBytes ^ FlashFileStream on: (ReadStream on: stream contents from: stream position + 1 to: stream position + nBytes).! ! !FlashFileStream methodsFor: 'accessing' stamp: 'ar 8/10/1998 14:18'! upToEnd ^self stream upToEnd.! ! !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: '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: 'Flash-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: 'Flash-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: 'Flash-Import'! !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! ! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'RAA 8/21/2001 23:15'! decodeJPEGTables: aStream " fixing the #atEnd allows the following to work: (FlashMorphReader on: (HTTPSocket httpGet: 'http://www.audi.co.uk/flash/intro1.swf' accept:'application/x-shockwave-flash')) processFile startPlaying openInWorld. " self setStream: aStream. eoiSeen := false. self parseFirstMarker. [eoiSeen or: [stream atEnd]] whileFalse:[self parseNextMarker]. ! ! !FlashJPEGDecoder methodsFor: 'decoding' stamp: 'ar 7/8/2006 21:21'! decodeNextImage32From: aStream | image | self setStream: aStream. self isStreaming ifFalse:[Cursor wait show]. image := self nextImageDitheredToDepth: 32. self isStreaming ifFalse:[Cursor normal show]. ^image! ! !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 10/28/2001 16: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']. residuals := WordArray new: 3. sosSeen := false. self parseFirstMarker. [sosSeen] whileFalse: [self parseNextMarker]. form := Form extent: (width @ height) depth: depth. 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! ! Object subclass: #FlashKeyframe instanceVariableNames: 'start stop data' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support'! !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: 'initialize' stamp: 'ar 11/12/1998 22:55'! from: startValue to: stopValue data: newData start := startValue. stop := stopValue. data := newData.! ! !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 methodsFor: 'testing' stamp: 'di 11/21/1999 20:26'! includesFrame: aNumber ^aNumber >= start and:[aNumber <= stop]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'Flash-Support'! !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: 'initialize' stamp: 'ar 8/14/1998 19:32'! initialize kfList := OrderedCollection new.! ! !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! ! Object subclass: #FlashLineStyle instanceVariableNames: 'width color' classVariableNames: '' poolDictionaries: '' category: 'Flash-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 methodsFor: 'comparing' stamp: 'ar 9/9/2003 22:03'! hash "#hash is re-implemented because #= is re-implemented" ^self color hash bitXor: self width hash! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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! ! HashAndEqualsTestCase subclass: #FlashLineStyleTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support-Tests'! !FlashLineStyleTest methodsFor: 'as yet unclassified' stamp: 'mjr 8/20/2003 18:56'! setUp super setUp. prototypes add: (FlashLineStyle color: 1 width: 1); add: (FlashLineStyle color: 1 width: 2); add: (FlashLineStyle color: 2 width: 1); add: (FlashLineStyle color: 2 width: 2) ! ! MatrixTransformMorph subclass: #FlashMorph instanceVariableNames: 'colorTransform' classVariableNames: 'FlashSoundVolume' poolDictionaries: '' category: 'Flash-Morphs'! !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 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 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: 'copying' stamp: 'dgd 2/16/2003 19:58'! copyExtension "Copy my extensions dictionary" | copiedExtension | self hasExtension ifFalse: [^ self]. copiedExtension := self extension copy. copiedExtension removeOtherProperties. self extension otherProperties ifNotNil: [self extension otherProperties associationsDo: [:assoc | copiedExtension setProperty: assoc key toValue: assoc value copy]]. self privateExtension: copiedExtension! ! !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: 'copying' stamp: 'dgd 2/22/2003 14:24'! duplicate "Usually, FlashMorphs exist in a player. If they're grabbed and moved outside the player they should keep their position." | dup player | dup := super duplicate. player := self flashPlayer. dup transform: (self transformFrom: self world). "If extracted from player and no default AA level is set use prefs" (player notNil and: [self defaultAALevel isNil]) ifTrue: [Preferences extractFlashInHighQuality ifTrue: [dup defaultAALevel: 2]. Preferences extractFlashInHighestQuality ifTrue: [dup defaultAALevel: 4]]. ^dup! ! !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 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: 'drawing' stamp: 'ar 5/6/2001 19:03'! fullDrawOn: aCanvas | myCanvas | aCanvas isBalloonCanvas ifTrue:[^super fullDrawOn: aCanvas]. myCanvas := aCanvas asBalloonCanvas. myCanvas deferred: true. super fullDrawOn: myCanvas. myCanvas flush.! ! !FlashMorph methodsFor: 'dropping/grabbing' stamp: 'dgd 2/22/2003 14:24'! 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 isNil]) ifTrue: [Preferences extractFlashInHighQuality ifTrue: [self defaultAALevel: 2]. Preferences extractFlashInHighestQuality ifTrue: [self defaultAALevel: 4]]. ^self "Grab me"! ! !FlashMorph methodsFor: 'dropping/grabbing' 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: '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: 'menu' stamp: 'dgd 8/30/2003 21:42'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu addUpdating: #getSmoothingLevel action: #nextSmoothingLevel. aCustomMenu add:'show compressed size' translated action: #showCompressedSize.! ! !FlashMorph methodsFor: 'menu' stamp: 'dgd 8/30/2003 21:42'! getSmoothingLevel "Menu support" | aaLevel | aaLevel := self defaultAALevel ifNil: [1]. aaLevel = 1 ifTrue: [^ 'turn on smoothing' translated]. aaLevel = 2 ifTrue: [^ 'more smoothing' translated]. aaLevel = 4 ifTrue: [^ 'turn off smoothing' translated]! ! !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: 'gm 2/28/2003 00:16'! showCompressedSize | size string | size := self originalFileSize. string := size = 0 ifTrue: ['Compressed size: not available'] ifFalse: ['Compressed size: ' , size asStringWithCommas , ' bytes']. self world primaryHand attachMorph: ((TextMorph new) contents: string; beAllFont: ScriptingSystem fontForTiles)! ! !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: 'rotate scale and flex' 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: '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: 'submorphs-add/remove' stamp: 'ar 11/16/1998 16:13'! delete | player | player := self flashPlayer. player ifNotNil:[player noticeRemovalOf: self]. ^super delete! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'Flash-Import'! !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.! ! !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 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: 'tk 2/16/2001 11:30'! recordButton: buttonId character: characterId state: state layer: layer matrix: matrix colorTransform: cxForm | button children shape | button := buttons at: buttonId ifAbsent:[^self error: 'button missing']. button id: buttonId. shape := self oldMorphFromShape: characterId. shape isNil ifTrue:[^nil]. children := shape submorphs collect:[:m| m veryDeepCopy]. 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: '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: '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: '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: 'tk 2/15/2001 16:34'! recordNextChar: glyphIndex advanceWidth: advance | shape transform | (activeFont includesKey: glyphIndex) ifTrue:[ shape := (activeFont at: glyphIndex) veryDeepCopy 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: '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: '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: '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: 'testing' stamp: 'ar 11/18/1998 21:37'! isStreaming ^player isStreaming! ! !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: 'tk 2/15/2001 16:33'! 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 veryDeepCopy] 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 class instanceVariableNames: ''! !FlashMorphReader class methodsFor: 'class initialization' stamp: 'hg 8/1/2000 20:07'! initialize FileList registerFileReader: self! ! !FlashMorphReader class methodsFor: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !FlashMorphReader class methodsFor: 'read Flash file' stamp: 'sd 2/6/2002 21:35'! fileReaderServicesForFile: fullName suffix: suffix ^(suffix = 'swf') | (suffix = '*') ifTrue: [ self services] ifFalse: [#()] ! ! !FlashMorphReader class methodsFor: 'read Flash file' stamp: 'hg 8/3/2000 16:04'! openAsFlash: fullFileName "Open a MoviePlayerMorph on the file (must be in .movie format)." | f player | f := (FileStream readOnlyFileNamed: fullFileName) binary. player := (FlashMorphReader on: f) processFile. player startPlaying. player open. ! ! !FlashMorphReader class methodsFor: 'read Flash file' stamp: 'sw 2/17/2002 02:42'! serviceOpenAsFlash "Answer a service for opening a flash file" ^ SimpleServiceEntry provider: self label: 'open as Flash' selector: #openAsFlash: description: 'open file as flash' buttonLabel: 'open'! ! !FlashMorphReader class methodsFor: 'read Flash file' stamp: 'sd 2/1/2002 22:09'! services ^ Array with: self serviceOpenAsFlash! ! FlashCharacterMorph subclass: #FlashMorphingMorph instanceVariableNames: 'srcShapes dstShapes morphShapes' classVariableNames: '' poolDictionaries: '' category: 'Flash-Morphs'! !FlashMorphingMorph methodsFor: 'copying' stamp: 'dgd 2/21/2003 23:04'! updateReferencesUsing: aDictionary | srcMorph dstMorph | super updateReferencesUsing: aDictionary. srcMorph := submorphs at: submorphs size - 1. dstMorph := submorphs last. self removeAllMorphs. self from: srcMorph to: dstMorph! ! !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 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: 'Flash-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: 'Flash-Morphs'! !FlashPlayerMorph methodsFor: 'accessing' stamp: 'ar 2/10/1999 03:37'! activeMorphs ^activeMorphs! ! !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: 'ar 3/17/2001 23:40'! 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: [World doOneCycle] 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: 'classification' stamp: 'ar 11/15/1998 19:05'! isFlashPlayer ^true! ! !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: '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 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: 'e-toy support' stamp: 'nk 1/6/2004 12:36'! asWearableCostumeOfExtent: extent "Return a wearable costume for some player" | image oldExtent | oldExtent := self extent. self extent: extent. image := self imageForm. self extent: oldExtent. image mapColor: self color to: Color transparent. ^(World drawingClass withForm: image) copyCostumeStateFrom: self! ! !FlashPlayerMorph methodsFor: 'e-toy support' stamp: 'mir 6/12/2001 12:07'! cursor ^self frameNumber ! ! !FlashPlayerMorph methodsFor: 'e-toy support' stamp: 'mir 6/12/2001 12:08'! cursor: aNumber "for backward compatibility" self cursorWrapped: aNumber! ! !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 testing' stamp: 'ar 6/2/1999 02:41'! containsPoint: aPoint ^self bounds containsPoint: aPoint! ! !FlashPlayerMorph methodsFor: 'geometry testing' stamp: 'ar 11/19/1998 20:48'! fullContainsPoint: pt "The player clips its children" (bounds containsPoint: pt) ifFalse:[^false]. ^super fullContainsPoint: pt! ! !FlashPlayerMorph methodsFor: 'holder' stamp: 'mir 6/12/2001 15:05'! cursorWrapped: aNumber "Set the cursor to the given number, modulo the number of items I contain. Fractional cursor values are allowed." | nextFrame | nextFrame := aNumber truncated abs. nextFrame >= self maxFrames ifTrue: [nextFrame := 1]. self stepToFrame: nextFrame! ! !FlashPlayerMorph methodsFor: 'holder' stamp: 'mir 6/12/2001 12:30'! numberAtCursor "Answer the number represented by the object at my current cursor position" ^0! ! !FlashPlayerMorph methodsFor: 'holder' stamp: 'mir 6/12/2001 12:32'! selectedRect "Return a rectangle enclosing the morph at the current cursor. Note that the cursor may be a float and may be out of range, so pick the nearest morph. Assume there is at least one submorph." self transform localBoundsToGlobal: self localBounds! ! !FlashPlayerMorph methodsFor: 'holder' stamp: 'mir 6/12/2001 12:32'! valueAtCursor "Answer the submorph of mine indexed by the value of my 'cursor' slot" ^self! ! !FlashPlayerMorph methodsFor: 'holder' stamp: 'mir 6/12/2001 12:33'! valueAtCursor: aMorph self shouldNotImplement! ! !FlashPlayerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color white! ! !FlashPlayerMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:38'! initialize "initialize the state of the receiver" super initialize. "" 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 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 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: 'tk 2/19/2001 17:47'! makeControls | bb r loopSwitch | r := AlignmentMorph newRow. r hResizing: #shrinkWrap; vResizing: #shrinkWrap; extent: 5@5. bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Rewind'; actionSelector: #rewind). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Play'; actionSelector: #startPlaying). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Pause'; actionSelector: #stopPlaying). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Next'; actionSelector: #stepForward). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb 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. bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Fastest'; actionSelector: #drawFastest). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Medium'; actionSelector: #drawMedium). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: 'Nicest'; actionSelector: #drawNicest). bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r addMorphBack: (bb label: '+10'; actionSelector: #jump10). ^ 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: 'dgd 9/20/2003 16:42'! openInMVC | window extent | self localBounds: localBounds. extent := bounds extent. window := FlashPlayerWindow labelled:'Flash Player' translated. window model: (FlashPlayerModel player: self). window addMorph: self frame:(0@0 corner: 1@1). window openInMVCExtent: extent! ! !FlashPlayerMorph methodsFor: 'initialize' stamp: 'dgd 9/20/2003 16:42'! openInWorld | window extent | self localBounds: localBounds. extent := bounds extent. window := FlashPlayerWindow labelled:'Flash Player' translated. 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: 'layout' stamp: 'ar 11/15/1998 16:07'! fullBounds "The player clips its children" ^bounds! ! !FlashPlayerMorph methodsFor: 'menu' stamp: 'dgd 8/30/2003 21:42'! addCustomMenuItems: aCustomMenu hand: aHandMorph super addCustomMenuItems: aCustomMenu hand: aHandMorph. aCustomMenu add: 'open sorter' translated action: #openSorter. aCustomMenu add: 'make controls' translated 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: 'player' stamp: 'mir 6/13/2001 14:45'! shouldRememberCostumes ^false! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:22'! 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. self updateProjectFillsFrom: Project current. 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: 'dao 10/1/2004 13:35'! beTransitionFrom: srcProjectName ^self beTransitionFrom: srcProjectName to: Project current name! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:19'! beTransitionFrom: srcProjectName to: dstProjectName "Make the receiver the animation between the two projects" | srcProject dstProject | srcProject := Project namedOrCurrent: srcProjectName. dstProject := Project namedOrCurrent: dstProjectName. (dstProject projectParameters at: #flashTransition ifAbsentPut:[IdentityDictionary new]) at: srcProject put: self. ! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:35'! beTransitionTo: dstProjectName ^self beTransitionFrom: Project current name to: dstProjectName! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'ar 8/10/2003 18:17'! 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 removeMorph: self]. 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: [World doOneCycleNow]. self stopPlaying. self stopStepping. owner removeMorph: self. self removeProperty: #transitionBackground. Display deferUpdates: true. ActiveWorld fullDrawOn: (Display getCanvas). Display deferUpdates: false.! ! !FlashPlayerMorph methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:21'! removeFillForProjectTarget: aFillStyle | fillStyles | fillStyles := self valueOfProperty: #projectTargetFills ifAbsent:[^self]. aFillStyle form: (fillStyles at: aFillStyle ifAbsent:[^self]). fillStyles removeKey: aFillStyle. self updateProjectFillsFrom: Project current. 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: '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 5/24/2001 16:50'! 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) from: self. ]. ]. 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: 'submorphs-add/remove' 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: '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: 'private' stamp: 'ar 6/12/2001 06:37'! privateFullMoveBy: delta self handleBoundsChange:[super privateMoveBy: delta]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlashPlayerMorph class instanceVariableNames: ''! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:35'! transitionFrom: srcProjectName ^self transitionFrom: srcProjectName to: Project current name! ! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:19'! transitionFrom: srcProjectName to: dstProjectName "Return the transition between the two projects" | srcProject dstProject | srcProject := Project namedOrCurrent: srcProjectName. dstProject := Project namedOrCurrent: dstProjectName. ^dstProject projectParameters at: #flashTransition ifPresent:[:dict| dict at: srcProject ifAbsent:[nil]]. ! ! !FlashPlayerMorph class methodsFor: 'project transition' stamp: 'dao 10/1/2004 13:36'! transitionTo: dstProjectName ^self transitionFrom: Project current name to: dstProjectName! ! !FlashPlayerMorph class methodsFor: 'scripting' stamp: 'sw 9/26/2001 03:56'! additionsToViewerCategories "Answer a list of ( ) pairs that characterize the phrases this kind of morph wishes to add to various Viewer categories." ^ # ( (collections ( (slot cursor 'The index of the chosen element' Number readWrite player getCursor player setCursorWrapped:) (slot playerAtCursor 'the object currently at the cursor' Player readWrite player getValueAtCursor unused unused) (slot firstElement 'The first object in my contents' Player readWrite player getFirstElement player setFirstElement:) (slot graphicAtCursor 'the graphic worn by the object at the cursor' Graphic readOnly player getGraphicAtCursor unused unused) )) ) ! ! SystemWindow subclass: #FlashPlayerWindow instanceVariableNames: 'startButton stopButton progress' classVariableNames: '' poolDictionaries: '' category: 'Flash-Morphs'! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'dgd 5/1/2003 21:05'! addProgressIndicator progress := ProgressBarMorph 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: 'dgd 5/1/2003 21:05'! addProgressIndicator: aValueHolder progress := ProgressBarMorph 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/19/1998 20:46'! startPlaying model startPlaying! ! !FlashPlayerWindow methodsFor: 'as yet unclassified' stamp: 'ar 11/19/1998 20:43'! stopPlaying model stopPlaying! ! !FlashPlayerWindow methodsFor: 'geometry' stamp: 'ar 11/17/1998 15:09'! extent: newExtent super extent: (newExtent max: 100 @ 50). self adjustBookControls! ! !FlashPlayerWindow methodsFor: 'initialization' stamp: 'dgd 9/20/2003 16:43'! initialize | aFont | super initialize. aFont := Preferences standardButtonFont. self addMorph: (startButton := SimpleButtonMorph new borderWidth: 0; label: 'play' translated font: aFont; color: Color transparent; actionSelector: #startPlaying; target: self). startButton setBalloonText: 'continue playing' translated. self addMorph: (stopButton := SimpleButtonMorph new borderWidth: 0; label: 'stop' translated font: aFont; color: Color transparent; actionSelector: #stopPlaying; target: self). stopButton setBalloonText: 'stop playing' translated. startButton submorphs first color: Color blue. stopButton submorphs first color: Color red. self adjustBookControls! ! !FlashPlayerWindow methodsFor: 'initialization' stamp: 'ar 11/19/1998 21:10'! model: aFlashPlayerModel aFlashPlayerModel isStreaming ifTrue:[self addProgressIndicator: aFlashPlayerModel progressValue]. ^super model: aFlashPlayerModel! ! !FlashPlayerWindow methodsFor: 'open/close' stamp: 'ar 11/17/1998 15:39'! openInMVCExtent: extent Smalltalk isMorphic ifTrue:[^self openInWorldExtent: extent]. super openInMVCExtent: (extent + borderWidth + (0@self labelHeight))! ! !FlashPlayerWindow methodsFor: 'open/close' stamp: 'ar 11/17/1998 15:39'! openInWorldExtent: extent Smalltalk isMorphic ifFalse:[^self openInMVCExtent: extent]. super openInWorldExtent: (extent + borderWidth + (0@self labelHeight))! ! !FlashPlayerWindow methodsFor: 'panes' 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: 'resize/collapse' 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. ].! ! FlashMorph subclass: #FlashShapeMorph instanceVariableNames: 'shape' classVariableNames: '' poolDictionaries: '' category: 'Flash-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/15/1998 15:31'! shape ^shape! ! !FlashShapeMorph methodsFor: 'accessing' stamp: 'ar 11/15/1998 16:43'! shape: newShape shape := newShape. self computeBounds.! ! !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 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 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: 'Flash-Morphs'! !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! ! !FlashSorterMorph methodsFor: 'geometry' stamp: 'ar 5/19/1999 16:25'! extent: extentPoint super extent: extentPoint. self doLayout.! ! !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: 'dgd 2/14/2003 22:27'! defaultColor "answer the default color/fill style for the receiver" ^ Color transparent! ! !FlashSorterMorph methodsFor: 'initialization' stamp: 'tk 2/19/2001 17:48'! makeControls | bb r | bb := SimpleButtonMorph new target: self; borderColor: #raised; borderWidth: 2. r := AlignmentMorph newRow. r hResizing: #spaceFill; vResizing: #spaceFill; layoutInset: 2. r addMorphBack: (bb 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: '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: '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]. ! ! Object subclass: #FlashSoundEnvelope instanceVariableNames: 'mark44 level0 level1' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support'! !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 methodsFor: 'initialize' stamp: 'ar 8/10/1998 15:35'! initialize mark44 := level0 := level1 := 0.! ! Object subclass: #FlashSoundInformation instanceVariableNames: 'syncFlags inPoint outPoint loopCount envelopes' classVariableNames: '' poolDictionaries: '' category: 'Flash-Support'! !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: 'initialize' stamp: 'ar 8/10/1998 15:30'! initialize syncFlags := 0. inPoint := -1. outPoint := -1. loopCount := 0. envelopes := #().! ! !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! ! FlashMorph subclass: #FlashSpriteMorph instanceVariableNames: 'playing maxFrames loadedFrames frameNumber stepTime damageRecorder sounds actions labels lastStepTime useTimeSync' classVariableNames: '' poolDictionaries: '' category: 'Flash-Morphs'! !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: '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: '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: '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: '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: 'initialization' stamp: 'dgd 2/14/2003 20:38'! initialize "initialize the state of the receiver" 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: 'objects from disk' stamp: 'RAA 12/20/2000 17:45'! convertToCurrentVersion: varDict refStream: smartRefStrm lastStepTime ifNil: [lastStepTime := 0]. useTimeSync ifNil: [useTimeSync := true]. ^super convertToCurrentVersion: varDict refStream: smartRefStrm. ! ! !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 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 11/16/1998 17:07'! stepTime: time stepTime := time.! ! !FlashSpriteMorph methodsFor: 'stepping' stamp: 'ar 5/24/2001 16:50'! 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) from: self. ]. ]. 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 and presenter' 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: 'testing' 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: 'testing' stamp: 'ar 11/16/1998 16:27'! wantsSteps ^true! ! !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! ! Object subclass: #FlashStreamingSound instanceVariableNames: 'mixFmt stereo samplingRate bitsPerSample sampleCount compressed firstFrame frameNumber buffers' classVariableNames: '' poolDictionaries: '' category: 'Flash-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: 'Flash-Morphs'! BorderedMorph subclass: #FlashThumbnailMorph instanceVariableNames: 'player frameNumber image selected' classVariableNames: '' poolDictionaries: '' category: 'Flash-Morphs'! !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: 'dgd 2/16/2003 21:42'! player "answer the receiver's player" ^ player! ! !FlashThumbnailMorph methodsFor: 'accessing' stamp: 'ar 5/19/1999 16:01'! player: aFlashPlayerMorph player := aFlashPlayerMorph.! ! !FlashThumbnailMorph methodsFor: 'drawing' stamp: 'dgd 2/22/2003 13:29'! drawOn: aCanvas (player isNil or: [frameNumber isNil]) ifTrue: [^super drawOn: aCanvas]. false ifTrue: [super drawOn: aCanvas. ^aCanvas drawString: frameNumber printString in: 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! ! !FlashThumbnailMorph methodsFor: 'initialization' stamp: 'ar 5/19/1999 17:29'! initialize super initialize. selected := false.! ! EllipseMorph subclass: #Flasher instanceVariableNames: 'onColor' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Demo'! !Flasher commentStamp: '' prior: 0! A simple example - a circle that flashes. The "onColor" instance variable indicates the color to use when "on", A darker color is used to represent "off". The #step method, called every 500ms. by default, alternatively makes the flasher show its "on" and its "off" color.! !Flasher methodsFor: 'operations' stamp: 'sw 5/28/2002 18:44'! onColor "Answer my onColor" ^ onColor ifNil: [onColor _ Color red]! ! !Flasher methodsFor: 'operations' stamp: 'sd 4/21/2002 09:55'! onColor: aColor "Change my on color to be aColor" onColor := aColor. self color: aColor! ! !Flasher methodsFor: 'parts bin' stamp: 'sd 4/21/2002 09:36'! initializeToStandAlone "Initialize the flasher." super initializeToStandAlone. self color: Color red. self onColor: Color red. self borderWidth: 2. self extent: 25@25! ! !Flasher methodsFor: 'stepping and presenter' stamp: 'sw 5/28/2002 18:45'! step "Perform my standard periodic action" super step. self color = self onColor ifTrue: [self color: (onColor alphaMixed: 0.5 with: Color black)] ifFalse: [self color: onColor]! ! !Flasher methodsFor: 'testing' stamp: 'sw 4/17/2002 12:05'! stepTime "Answer the desired time between steps, in milliseconds." ^ 500! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Flasher class instanceVariableNames: ''! !Flasher class methodsFor: 'parts bin' stamp: 'sw 4/17/2002 11:37'! descriptionForPartsBin "Answer a description of the receiver for use in a parts bin" ^ self partName: 'Flasher' categories: #('Demo') documentation: 'A circle that flashes'! ! NullEncoder subclass: #FlattenEncoder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Support'! !FlattenEncoder commentStamp: '' prior: 0! The simplest possible encoding: leave the objects as is. ! !FlattenEncoder methodsFor: 'filter streaming' stamp: 'MPW 1/1/1901 01:32'! elementSeparator ^target elementSeparator.! ! !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 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: 'MorphicExtras-AdditionalMorphs'! !FlexMorph methodsFor: 'accessing' stamp: 'di 1/11/1999 21:43'! borderColor: aColor borderColor _ aColor. self updateFromOriginal! ! !FlexMorph methodsFor: 'accessing' stamp: 'di 1/11/1999 21:43'! borderWidth: width borderWidth _ width asPoint. self updateFromOriginal! ! !FlexMorph methodsFor: 'accessing' stamp: 'di 1/11/1999 20:35'! form self loadOriginalForm. "make sure it's not nil" ^ super form! ! !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: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 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! ! !FlexMorph methodsFor: 'caching' 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: 'drawing' stamp: 'di 1/11/1999 20:54'! drawOn: aCanvas originalForm _ nil. "Aggressively uncache the originalForm" ^ super drawOn: aCanvas! ! !FlexMorph methodsFor: 'drawing' stamp: 'di 1/11/1999 20:35'! generateRotatedForm self loadOriginalForm. "make sure it's not nil" ^ super generateRotatedForm! ! !FlexMorph methodsFor: 'geometry' stamp: 'di 1/11/1999 20:35'! extent: newExtent self loadOriginalForm. "make sure it's not nil" ^ super extent: newExtent! ! !FlexMorph methodsFor: 'initialization' stamp: 'di 1/11/1999 21:37'! initialize super initialize. borderWidth _ 2@2. borderColor _ Color black.! ! !FlexMorph methodsFor: 'layout' stamp: 'di 1/11/1999 20:35'! layoutChanged self loadOriginalForm. "make sure it's not nil" ^ super layoutChanged! ! !FlexMorph methodsFor: 'menus' stamp: 'dgd 8/30/2003 21:44'! addCustomMenuItems: aCustomMenu hand: aHandMorph "super addCustomMenuItems: aCustomMenu hand: aHandMorph." aCustomMenu addLine. aCustomMenu add: 'update from original' translated action: #updateFromOriginal. aCustomMenu addList: { {'border color...' translated. #changeBorderColor:}. {'border width...' translated. #changeBorderWidth:}. }. aCustomMenu addLine. ! ! PackageInfo subclass: #FlexibleVocabulariesInfo instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FlexibleVocabularies-Info'! !FlexibleVocabulariesInfo commentStamp: 'nk 3/11/2004 16:38' prior: 0! Package: FlexibleVocabularies-nk Date: 12 October 2003 Author: Ned Konz This makes it possible for packages to extend Morph class vocabularies. Previously, you'd have to edit #additionsToViewerCategories, which would result in potential conflicts between different packages that all wanted to (for instance) extend Morph's vocabulary. Subclasses that have additions can do one or both of: - override #additionsToViewerCategories (as before) - define one or more additionToViewerCategory* methods. The advantage of the latter technique is that class extensions may be added by external packages without having to re-define additionsToViewerCategories. So, for instance, package A could add a method named #additionsToViewerCategoryPackageABasic and its methods would be added to the vocabulary automatically. NOTE: this change set is hand-rearranged to avoid problems on file-in. Specifically, Morph>>hasAdditionsToViewerCategories must come before Morph class>>additionsToViewerCategories ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FlexibleVocabulariesInfo class instanceVariableNames: ''! !FlexibleVocabulariesInfo class methodsFor: 'class initialization' stamp: 'nk 5/3/2004 15:48'! initialize [self new register] on: MessageNotUnderstood do: []. SyntaxMorph class removeSelector: #initialize. SyntaxMorph removeSelector: #allSpecs. EToyVocabulary removeSelector: #morphClassesDeclaringViewerAdditions. SyntaxMorph clearAllSpecs. Vocabulary initialize. ! ! Number variableWordSubclass: #Float instanceVariableNames: '' classVariableNames: 'E Epsilon Halfpi Infinity Ln10 Ln2 MaxVal MaxValLn MinValLogBase2 NaN NegativeInfinity NegativeZero Pi RadiansPerDegree Sqrt2 ThreePi 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.! !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: 'hh 10/3/2000 11:46'! / aNumber "Primitive. Answer the result of dividing receiver by aNumber. Fail if the argument is not a Float. Essential. See Object documentation whatIsAPrimitive." aNumber isZero 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' stamp: 'RAH 4/25/2000 19:49'! reciprocal #Numeric. "Changed 200/01/19 For ANSI support." self = 0 ifTrue: ["<- Chg" ^ (ZeroDivide dividend: self) signal"<- Chg"]. "<- Chg" ^ 1.0 / self! ! !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: 'converting' stamp: 'mk 10/27/2003 18:16'! adaptToComplex: rcvr andSend: selector "If I am involved in arithmetic with a Complex number, convert me to a Complex number." ^ rcvr perform: selector with: self asComplex! ! !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' stamp: 'RAH 4/25/2000 19:49'! adaptToScaledDecimal: receiverScaledDecimal andSend: arithmeticOpSelector "Convert receiverScaledDecimal to a Float and do the arithmetic. receiverScaledDecimal arithmeticOpSelector self." #Numeric. "add 200/01/19 For ScaledDecimal support." ^ receiverScaledDecimal asFloat perform: arithmeticOpSelector with: self! ! !Float methodsFor: 'converting' stamp: 'st 9/17/2004 17:17'! asApproximateFraction "Answer a Fraction approximating the receiver. This conversion uses the continued fraction method to approximate a floating point number." ^ self asApproximateFractionAtOrder: 0! ! !Float methodsFor: 'converting' stamp: 'st 9/17/2004 17:14'! asApproximateFractionAtOrder: maxOrder "Answer a Fraction approximating the receiver. This conversion uses the continued fraction method to approximate a floating point number. If maxOrder is zero, use maximum order" | num1 denom1 num2 denom2 int frac newD temp order | 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" order := maxOrder = 0 ifTrue: [-1] ifFalse: [maxOrder]. [frac = 0 or: [order = 0] ] whileFalse: ["repeat while the fractional part is not zero and max order is not reached" order _ order - 1. 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' stamp: 'mk 10/27/2003 17:46'! asComplex "Answer a Complex number that represents value of the the receiver." ^ Complex real: self imaginary: 0! ! !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: 'nice 5/30/2006 02:29'! 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 truncatedBits mask roundToUpper | "skip fast positive and nnegative zero" self = 0.0 ifTrue: [^self basicAt: 1]. "retrieve 64 bits of IEEE 754 double" word1 := self basicAt: 1. word2 := self basicAt: 2. "prepare sign exponent and mantissa of 32 bits float" sign := word1 bitAnd: 16r80000000. exponent := ((word1 bitShift: -20) bitAnd: 16r7FF) - 1023 + 127. mantissa := (word2 bitShift: -29) + ((word1 bitAnd: 16rFFFFF) bitShift: 3). truncatedBits := (word2 bitAnd: 16r1FFFFFFF). "We must now honour default IEEE rounding mode (round to nearest even)" "we are below gradual underflow, even if rounded to upper mantissa" exponent < -24 ifTrue: [^sign "this can be negative zero"]. "BEWARE: rounding occurs on less than 23bits when gradual underflow" exponent <= 0 ifTrue: [mask := 1 bitShift: exponent negated. mantissa := mantissa bitOr: 16r800000. roundToUpper := (mantissa bitAnd: mask) isZero not and: [truncatedBits isZero not or: [(mantissa bitAnd: mask - 1) isZero not or: [(mantissa bitAnd: mask*2) isZero not]]]. mantissa := mantissa bitShift: exponent - 1. "exponent := exponent + 1"] ifFalse: [roundToUpper := (truncatedBits bitAnd: 16r10000000) isZero not and: [(mantissa bitAnd: 16r1) isZero not or: [(truncatedBits bitAnd: 16r0FFFFFFF) isZero not]] ]. "adjust mantissa and exponent due to IEEE rounding mode" roundToUpper ifTrue: [mantissa := mantissa + 1. mantissa > 16r7FFFFF ifTrue: [mantissa := 0. exponent := exponent+1]]. exponent > 254 ifTrue: ["Overflow" exponent := 255. self isNaN ifTrue: [mantissa isZero ifTrue: ["BEWARE: do not convert a NaN to infinity due to truncatedBits" mantissa := 1]] ifFalse: [mantissa := 0]]. "Encode the word" destWord := (sign bitOr: ((exponent max: 0) bitShift: 23)) bitOr: mantissa. ^ destWord! ! !Float methodsFor: 'converting' stamp: 'nice 3/29/2006 01:01'! 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 handle gradual underflow but not NANs.) By David N. Smith with significant performance improvements by Luciano Esteban Notarfrancesco. (Version of 11April97)" | signexp positive expPart exp fraction fractionPart signedFraction 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 sign and the biased exponent " signexp := (self basicAt: 1) bitShift: -20. positive := (signexp bitAnd: 16r800) = 0. expPart := signexp bitAnd: 16r7FF. " Extract fractional part; answer 0 if this is a true 0.0 value " fractionPart := (((self basicAt: 1) bitAnd: 16rFFFFF) bitShift: 32)+ (self basicAt: 2). ( expPart=0 and: [ fractionPart=0 ] ) ifTrue: [ ^ 0 ]. " Replace omitted leading 1 in fraction unless gradual underflow" fraction := expPart = 0 ifTrue: [fractionPart bitShift: 1] ifFalse: [fractionPart bitOr: 16r0010000000000000]. signedFraction := positive ifTrue: [fraction] ifFalse: [fraction negated]. "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 := signedFraction 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 := signedFraction bitShift: zeroBitsCount negated ] ifFalse: [ result := Fraction numerator: (signedFraction 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: '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: 'HilaireFernandes 1/16/2006 21:47'! 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: [ (self > 0) ifTrue: [result := ((self / denominator) arcTan) + Pi ] 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: 'AFi 11/23/2002 21:06'! 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: [ ArithmeticError signal: ' 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' stamp: 'RAH 4/25/2000 19:49'! sqrt "Answer the square root of the receiver. Optional. See Object documentation whatIsAPrimitive." | exp guess eps delta | #Numeric. "Changed 200/01/19 For ANSI support." "Newton-Raphson" self <= 0.0 ifTrue: [self = 0.0 ifTrue: [^ 0.0] ifFalse: ["v Chg" ^ FloatingPointException signal: 'undefined if less than zero.']]. "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: '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: 'printing' stamp: 'RAH 4/25/2000 19:49'! printPaddedWith: aCharacter to: aNumber "Answer the string containing the ASCII representation of the receiver padded on the left with aCharacter to be at least on aNumber integerPart characters and padded the right with aCharacter to be at least anInteger fractionPart characters." | aStream digits fPadding fLen iPadding iLen curLen periodIndex | #Numeric. "2000/03/04 Harmon R. Added Date and Time support" aStream := WriteStream on: (String new: 10). self printOn: aStream. digits := aStream contents. periodIndex := digits indexOf: $.. curLen := periodIndex - 1. iLen := aNumber integerPart. curLen < iLen ifTrue: [iPadding := (String new: (iLen - curLen) asInteger) atAllPut: aCharacter; yourself] ifFalse: [iPadding := '']. curLen := digits size - periodIndex. fLen := (aNumber fractionPart * (aNumber asFloat exponent * 10)) asInteger. curLen < fLen ifTrue: [fPadding := (String new: fLen - curLen) atAllPut: aCharacter; yourself] ifFalse: [fPadding := '']. ^ iPadding , digits , fPadding! ! !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: 'nice 4/26/2006 05:09'! 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" | di df q r | di := (SmallInteger maxVal bitShift: -1)+1. df := di asFloat. q := self quo: df. r := self - (q asFloat * df). ^q*di+r truncated] ifFalse: [^ self asTrueFraction. "Extract all bits of the mantissa and shift if necess"] ! ! !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: 'yo 6/17/2004 17:44'! 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. ThreePi _ Pi * 3.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: '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' stamp: 'RAH 4/25/2000 19:49'! one #Numeric. "add 200/01/19 For protocol support." ^ 1.0! ! !Float class methodsFor: 'constants'! pi "Answer the constant, Pi." ^Pi! ! !Float class methodsFor: 'constants' stamp: 'yo 6/17/2004 17:44'! threePi ^ ThreePi ! ! !Float class methodsFor: 'constants' stamp: 'yo 6/17/2004 17:41'! twoPi ^ Twopi ! ! !Float class methodsFor: 'instance creation' stamp: 'nice 5/30/2006 03:13'! 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 delta | word negative ifTrue: [^ self error:'Cannot deal with negative numbers']. word = 0 ifTrue: [^ 0.0]. sign := word bitAnd: 16r80000000. word = sign ifTrue: [^self negativeZero]. exponent := ((word bitShift: -23) bitAnd: 16rFF) - 127. mantissa := word bitAnd: 16r7FFFFF. exponent = 128 ifTrue:["Either NAN or INF" mantissa = 0 ifFalse:[^ Float nan]. sign = 0 ifTrue:[^ Float infinity] ifFalse:[^ Float infinity negated]]. exponent = -127 ifTrue: [ "gradual underflow (denormalized number) Remove first bit of mantissa and adjust exponent" delta := mantissa highBit. mantissa := (mantissa bitShift: 1) bitAnd: (1 bitShift: delta) - 1. exponent := exponent + delta - 23]. "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! ! 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/7/2001 23:07'! adaptToNumber: rcvr andSend: selector "If I am involved in arithmetic with a Number. If possible, convert it to a float and perform the (more efficient) primitive operation." selector == #+ ifTrue:[^self + rcvr]. selector == #* ifTrue:[^self * rcvr]. selector == #- ifTrue:[^self negated += rcvr]. selector == #/ ifTrue:[^self * (1.0 / rcvr)]. ^super adaptToNumber: rcvr andSend: selector! ! !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: 'arithmetic' stamp: 'ar 9/7/2001 23:04'! negated ^self clone *= -1! ! !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: 'yo 9/14/2004 17:12'! \\= other other isNumber ifTrue: [ 1 to: self size do: [:i | self at: i put: (self at: i) \\ other ]. ^ self. ]. 1 to: (self size min: other size) do: [:i | self at: i put: (self at: i) \\ (other at: i). ]. ! ! !FloatArray methodsFor: 'comparing' stamp: 'ar 5/3/2001 13:02'! hash | result | result _ 0. 1 to: self size do:[:i| result _ result + (self basicAt: i) ]. ^result bitAnd: 16r1FFFFFFF! ! !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: 'converting' stamp: 'ar 9/14/1998 23:46'! asFloatArray ^self! ! !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: 'primitives-plugin' stamp: 'jcg 6/12/2003 17:54'! sum ^ super sum! ! !FloatArray methodsFor: '*Tools-Inspector' stamp: 'ar 9/27/2005 18:33'! inspectorClass "Answer the class of the inspector to be used on the receiver. Called by inspect; use basicInspect to get a normal (less useful) type of inspector." ^OrderedCollectionInspector! ! !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! ! ClassTestCase subclass: #FloatArrayTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'CollectionsTests-Arrayed'! !FloatArrayTest commentStamp: 'nice 5/30/2006 01:24' prior: 0! These tests are used to assert that FloatArrayPlugin has same results as Float asIEEE32BitWord! !FloatArrayTest methodsFor: 'testing' stamp: 'nice 5/30/2006 03:17'! testFloatArrayPluginPrimitiveAt "if FloatArrayPlugin primitive are not here, this test is dumb. Otherwise, it will compare primitive and #fromIEEE32Bit:" #( "regular numbers no truncation or rounding" 2r0.0 2r1.0 2r1.1 2r1.00000000000000000000001 2r1.0e-10 2r1.1e-10 2r1.00000000000000000000001e-10 2r1.0e10 2r1.1e10 2r1.00000000000000000000001e10 "smallest float32 before gradual underflow" 2r1.0e-126 "biggest float32" 2r1.11111111111111111111111e127 "overflow" 2r1.11111111111111111111111e128 "gradual underflow" 2r0.11111111111111111111111e-126 2r0.00000000000000000000001e-126 "with rounding mode : tests on 25 bits" 2r1.0000000000000000000000001 2r1.0000000000000000000000010 2r1.0000000000000000000000011 2r1.0000000000000000000000100 2r1.0000000000000000000000101 2r1.0000000000000000000000110 2r1.0000000000000000000000111 2r1.1111111111111111111111001 2r1.1111111111111111111111010 2r1.1111111111111111111111011 2r1.1111111111111111111111101 2r1.1111111111111111111111110 2r1.1111111111111111111111111 "overflow" 2r1.1111111111111111111111110e127 "gradual underflow" 2r0.1111111111111111111111111e-126 2r0.1111111111111111111111110e-126 2r0.1111111111111111111111101e-126 2r0.1111111111111111111111011e-126 2r0.1111111111111111111111010e-126 2r0.1111111111111111111111001e-126 2r0.0000000000000000000000111e-126 2r0.0000000000000000000000110e-126 2r0.0000000000000000000000101e-126 2r0.0000000000000000000000011e-126 2r0.0000000000000000000000010e-126 2r0.0000000000000000000000001e-126 2r0.0000000000000000000000010000000000000000000000000001e-126 ) do: [:e | self assert: ((FloatArray with: e) at: 1) = (Float fromIEEE32Bit: ((FloatArray with: e) basicAt: 1)). self assert: ((FloatArray with: e negated) at: 1) = (Float fromIEEE32Bit: ((FloatArray with: e negated) basicAt: 1))]. "special cases" (Array with: Float infinity with: Float infinity negated with: Float negativeZero) do: [:e | self assert: ((FloatArray with: e) at: 1) = (Float fromIEEE32Bit: ((FloatArray with: e) basicAt: 1))]. "Cannot compare NaN" (Array with: Float nan) do: [:e | self assert: (Float fromIEEE32Bit: ((FloatArray with: e) basicAt: 1)) isNaN].! ! !FloatArrayTest methodsFor: 'testing' stamp: 'nice 5/30/2006 03:17'! testFloatArrayPluginPrimitiveAtPut "if FloatArrayPlugin primitive are not here, this test is dumb. Otherwise, it will compare primitive and #asIEEE32BitWord" #( "regular numbers no truncation or rounding" 2r0.0 2r1.0 2r1.1 2r1.00000000000000000000001 2r1.0e-10 2r1.1e-10 2r1.00000000000000000000001e-10 2r1.0e10 2r1.1e10 2r1.00000000000000000000001e10 "smallest float32 before gradual underflow" 2r1.0e-126 "biggest float32" 2r1.11111111111111111111111e127 "overflow" 2r1.11111111111111111111111e128 "gradual underflow" 2r0.11111111111111111111111e-126 2r0.00000000000000000000001e-126 "with rounding mode : tests on 25 bits" 2r1.0000000000000000000000001 2r1.0000000000000000000000010 2r1.0000000000000000000000011 2r1.0000000000000000000000100 2r1.0000000000000000000000101 2r1.0000000000000000000000110 2r1.0000000000000000000000111 2r1.1111111111111111111111001 2r1.1111111111111111111111010 2r1.1111111111111111111111011 2r1.1111111111111111111111101 2r1.1111111111111111111111110 2r1.1111111111111111111111111 "overflow" 2r1.1111111111111111111111110e127 "gradual underflow" 2r0.1111111111111111111111111e-126 2r0.1111111111111111111111110e-126 2r0.1111111111111111111111101e-126 2r0.1111111111111111111111011e-126 2r0.1111111111111111111111010e-126 2r0.1111111111111111111111001e-126 2r0.0000000000000000000000111e-126 2r0.0000000000000000000000110e-126 2r0.0000000000000000000000101e-126 2r0.0000000000000000000000011e-126 2r0.0000000000000000000000010e-126 2r0.0000000000000000000000001e-126 2r0.0000000000000000000000010000000000000000000000000001e-126 ) do: [:e | self assert: ((FloatArray with: e) basicAt: 1) = e asIEEE32BitWord. self assert: ((FloatArray with: e negated) basicAt: 1) = e negated asIEEE32BitWord]. "special cases" (Array with: Float infinity with: Float infinity negated with: Float negativeZero with: Float nan) do: [:e | self assert: ((FloatArray with: e) basicAt: 1) = e asIEEE32BitWord]. ! ! TestCase subclass: #FloatMathPluginTests instanceVariableNames: 'random' classVariableNames: '' poolDictionaries: '' category: 'Tests-VM'! !FloatMathPluginTests commentStamp: '' prior: 0! FloatMathPluginTests buildSuite run.! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:34'! arcCosH: f ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:34'! arcCos: f ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:34'! arcSinH: f ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:34'! arcSin: f ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:35'! arcTan2: value with: arg ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:35'! arcTanH: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! arcTan: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:35'! cosH: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:35'! cos: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! exp: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! fractionPart: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:36'! hypot: x with: y ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:24'! ln: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:41'! log10: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:37'! sinH: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! sin: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! sqrt: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:37'! tanH: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 17:37'! tan: value ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'math' stamp: 'ar 3/26/2006 14:29'! timesTwoPower: f with: arg ^self primitiveFailed! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 16:52'! makeLargeTestData "self basicNew makeLargeTestData" self makeTestData: 'sin-large.dat' using:[:f| self sin: f] seed: 432567 rounds: 1000000. self makeTestData: 'log-large.dat' using:[:f| self ln: f abs] seed: 432567 rounds: 1000000. self makeTestData: 'sqrt-large.dat' using:[:f| self sqrt: f abs] seed: 432567 rounds: 1000000. self makeTestData: 'atan-large.dat' using:[:f| self arcTan: f] seed: 432567 rounds: 1000000. self makeTestData: 'exp-large.dat' using:[:f| self exp: f] seed: 432567 rounds: 1000000. ! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 16:52'! makeSmallTestData "self basicNew makeSmallTestData" self makeTestData: 'sin-small.dat' using:[:f| self sin: f] seed: 321567 rounds: 10000. self makeTestData: 'log-small.dat' using:[:f| self ln: f abs] seed: 321567 rounds: 10000. self makeTestData: 'sqrt-small.dat' using:[:f| self sqrt: f abs] seed: 321567 rounds: 10000. self makeTestData: 'atan-small.dat' using:[:f| self arcTan: f] seed: 321567 rounds: 10000. self makeTestData: 'exp-small.dat' using:[:f| self exp: f] seed: 321567 rounds: 10000. ! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 16:11'! makeTestData: fileName using: aBlock seed: seed rounds: rounds | bytes out float result | bytes := ByteArray new: 8. out := FileStream newFileNamed: fileName. [ out binary. out nextNumber: 4 put: rounds. out nextNumber: 4 put: seed. random := Random seed: seed. float := Float basicNew: 2. 'Creating test data for: ', fileName displayProgressAt: Sensor cursorPoint from: 1 to: rounds during:[:bar| 1 to: rounds do:[:i| i \\ 10000 = 0 ifTrue:[bar value: i]. [1 to: 8 do:[:j| bytes at: j put: (random nextInt: 256)-1]. float basicAt: 1 put: (bytes unsignedLongAt: 1 bigEndian: true). float basicAt: 2 put: (bytes unsignedLongAt: 5 bigEndian: true). float isNaN] whileTrue. result := aBlock value: float. out nextNumber: 4 put: (result basicAt: 1). out nextNumber: 4 put: (result basicAt: 2). ]. ]. ] ensure:[out close]. ! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 16:58'! runTest: aBlock | bytes out float result | bytes := ByteArray new: 8. out := WriteStream on: ByteArray new. float := Float basicNew: 2. 1 to: 10000 do:[:i| [1 to: 8 do:[:j| bytes at: j put: (random nextInt: 256)-1]. float basicAt: 1 put: (bytes unsignedLongAt: 1 bigEndian: true). float basicAt: 2 put: (bytes unsignedLongAt: 5 bigEndian: true). float isNaN] whileTrue. result := aBlock value: float. out nextNumber: 4 put: (result basicAt: 1). out nextNumber: 4 put: (result basicAt: 2). ]. ^self md5HashMessage: out contents.! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 14:38'! setUp random := Random seed: 253213.! ! !FloatMathPluginTests methodsFor: 'running' stamp: 'ar 3/26/2006 16:53'! verifyTestData: fileName using: aBlock | rounds seed bytes float result in expected count bits | in := [FileStream readOnlyFileNamed: fileName] on: FileDoesNotExistException do:[:ex| ex return: nil]. in ifNil:[^nil]. count := bits := 0. bytes := ByteArray new: 8. [ in binary. rounds := in nextNumber: 4. seed := in nextNumber: 4. random := Random seed: seed. float := Float basicNew: 2. expected := Float basicNew: 2. 'Verifying test data from: ', fileName displayProgressAt: Sensor cursorPoint from: 1 to: rounds during:[:bar| 1 to: rounds do:[:i| i \\ 10000 = 0 ifTrue:[bar value: i]. [1 to: 8 do:[:j| bytes at: j put: (random nextInt: 256)-1]. float basicAt: 1 put: (bytes unsignedLongAt: 1 bigEndian: true). float basicAt: 2 put: (bytes unsignedLongAt: 5 bigEndian: true). float isNaN] whileTrue. result := aBlock value: float. expected basicAt: 1 put: (in nextNumber: 4). expected basicAt: 2 put: (in nextNumber: 4). ((expected isNaN and:[result isNaN]) or:[expected = result]) ifFalse:[ (expected basicAt: 1) = (result basicAt: 1) ifFalse:[self error: 'Verification failure']. count := count + 1. bits := bits + ((expected basicAt: 2) - (result basicAt: 2)) abs. ]. ]. ]. ] ensure:[in close]. self assert: count = 0. "all the same"! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! md5HashMessage: aStringOrByteArray ^ self md5HashStream: (ReadStream on: aStringOrByteArray asByteArray) ! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 19:49'! md5HashStream: aStream | start buffer bytes sz n words hash large | hash := WordArray with: 16r67452301 with: 16rEFCDAB89 with: 16r98BADCFE with: 16r10325476. words := WordArray new: 16. buffer := ByteArray new: 64. start _ aStream position. [aStream atEnd] whileFalse: [ bytes _ aStream nextInto: buffer. (bytes size < 64 or:[aStream atEnd]) ifTrue:[ sz := bytes size. buffer replaceFrom: 1 to: sz with: bytes startingAt: 1. buffer from: sz+1 to: buffer size put: 0. sz < 56 ifTrue:[ buffer at: sz + 1 put: 128. "trailing bit" ] ifFalse:[ "not enough room for the length, so just pad this one, then..." sz < 64 ifTrue:[buffer at: sz + 1 put: 128]. 1 to: 16 do:[:i| words at: i put: (buffer unsignedLongAt: i*4-3 bigEndian: false)]. self md5Transform: words hash: hash. "process one additional block of padding ending with the length" buffer atAllPut: 0. sz = 64 ifTrue: [buffer at: 1 put: 128]. ]. "Fill in the final 8 bytes with the 64-bit length in bits." n _ (aStream position - start) * 8. 7 to: 0 by: -1 do:[:i| buffer at: (buffer size - i) put: ((n bitShift: 7-i*-8) bitAnd: 255)]. ]. 1 to: 16 do:[:i| words at: i put: (buffer unsignedLongAt: i*4-3 bigEndian: false)]. self md5Transform: words hash: hash. ]. bytes := ByteArray new: 16. bytes unsignedLongAt: 1 put: (hash at: 4) bigEndian: true. bytes unsignedLongAt: 5 put: (hash at: 3) bigEndian: true. bytes unsignedLongAt: 9 put: (hash at: 2) bigEndian: true. bytes unsignedLongAt: 13 put: (hash at: 1) bigEndian: true. large := LargePositiveInteger new: 16. 1 to: 16 do:[:i| large digitAt: i put: (bytes at: i)]. ^large normalize! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 19:45'! md5Transform: in hash: hash "This adds the incoming words to the existing hash" | a b c d | a := hash at: 1. b := hash at: 2. c := hash at: 3. d := hash at: 4. a := self step1: a x: b y: c z: d data: (in at: 1) add: 16rD76AA478 shift: 7. d := self step1: d x: a y: b z: c data: (in at: 2) add: 16rE8C7B756 shift: 12. c := self step1: c x: d y: a z: b data: (in at: 3) add: 16r242070DB shift: 17. b := self step1: b x: c y: d z: a data: (in at: 4) add: 16rC1BDCEEE shift: 22. a := self step1: a x: b y: c z: d data: (in at: 5) add: 16rF57C0FAF shift: 7. d := self step1: d x: a y: b z: c data: (in at: 6) add: 16r4787C62A shift: 12. c := self step1: c x: d y: a z: b data: (in at: 7) add: 16rA8304613 shift: 17. b := self step1: b x: c y: d z: a data: (in at: 8) add: 16rFD469501 shift: 22. a := self step1: a x: b y: c z: d data: (in at: 9) add: 16r698098D8 shift: 7. d := self step1: d x: a y: b z: c data: (in at: 10) add: 16r8B44F7AF shift: 12. c := self step1: c x: d y: a z: b data: (in at: 11) add: 16rFFFF5BB1 shift: 17. b := self step1: b x: c y: d z: a data: (in at: 12) add: 16r895CD7BE shift: 22. a := self step1: a x: b y: c z: d data: (in at: 13) add: 16r6B901122 shift: 7. d := self step1: d x: a y: b z: c data: (in at: 14) add: 16rFD987193 shift: 12. c := self step1: c x: d y: a z: b data: (in at: 15) add: 16rA679438E shift: 17. b := self step1: b x: c y: d z: a data: (in at: 16) add: 16r49B40821 shift: 22. a := self step2: a x: b y: c z: d data: (in at: 2) add: 16rF61E2562 shift: 5. d := self step2: d x: a y: b z: c data: (in at: 7) add: 16rC040B340 shift: 9. c := self step2: c x: d y: a z: b data: (in at: 12) add: 16r265E5A51 shift: 14. b := self step2: b x: c y: d z: a data: (in at: 1) add: 16rE9B6C7AA shift: 20. a := self step2: a x: b y: c z: d data: (in at: 6) add: 16rD62F105D shift: 5. d := self step2: d x: a y: b z: c data: (in at: 11) add: 16r02441453 shift: 9. c := self step2: c x: d y: a z: b data: (in at: 16) add: 16rD8A1E681 shift: 14. b := self step2: b x: c y: d z: a data: (in at: 5) add: 16rE7D3FBC8 shift: 20. a := self step2: a x: b y: c z: d data: (in at: 10) add: 16r21E1CDE6 shift: 5. d := self step2: d x: a y: b z: c data: (in at: 15) add: 16rC33707D6 shift: 9. c := self step2: c x: d y: a z: b data: (in at: 4) add: 16rF4D50D87 shift: 14. b := self step2: b x: c y: d z: a data: (in at: 9) add: 16r455A14ED shift: 20. a := self step2: a x: b y: c z: d data: (in at: 14) add: 16rA9E3E905 shift: 5. d := self step2: d x: a y: b z: c data: (in at: 3) add: 16rFCEFA3F8 shift: 9. c := self step2: c x: d y: a z: b data: (in at: 8) add: 16r676F02D9 shift: 14. b := self step2: b x: c y: d z: a data: (in at: 13) add: 16r8D2A4C8A shift: 20. a := self step3: a x: b y: c z: d data: (in at: 6) add: 16rFFFA3942 shift: 4. d := self step3: d x: a y: b z: c data: (in at: 9) add: 16r8771F681 shift: 11. c := self step3: c x: d y: a z: b data: (in at: 12) add: 16r6D9D6122 shift: 16. b := self step3: b x: c y: d z: a data: (in at: 15) add: 16rFDE5380C shift: 23. a := self step3: a x: b y: c z: d data: (in at: 2) add: 16rA4BEEA44 shift: 4. d := self step3: d x: a y: b z: c data: (in at: 5) add: 16r4BDECFA9 shift: 11. c := self step3: c x: d y: a z: b data: (in at: 8) add: 16rF6BB4B60 shift: 16. b := self step3: b x: c y: d z: a data: (in at: 11) add: 16rBEBFBC70 shift: 23. a := self step3: a x: b y: c z: d data: (in at: 14) add: 16r289B7EC6 shift: 4. d := self step3: d x: a y: b z: c data: (in at: 1) add: 16rEAA127FA shift: 11. c := self step3: c x: d y: a z: b data: (in at: 4) add: 16rD4EF3085 shift: 16. b := self step3: b x: c y: d z: a data: (in at: 7) add: 16r04881D05 shift: 23. a := self step3: a x: b y: c z: d data: (in at: 10) add: 16rD9D4D039 shift: 4. d := self step3: d x: a y: b z: c data: (in at: 13) add: 16rE6DB99E5 shift: 11. c := self step3: c x: d y: a z: b data: (in at: 16) add: 16r1FA27CF8 shift: 16. b := self step3: b x: c y: d z: a data: (in at: 3) add: 16rC4AC5665 shift: 23. a := self step4: a x: b y: c z: d data: (in at: 1) add: 16rF4292244 shift: 6. d := self step4: d x: a y: b z: c data: (in at: 8) add: 16r432AFF97 shift: 10. c := self step4: c x: d y: a z: b data: (in at: 15) add: 16rAB9423A7 shift: 15. b := self step4: b x: c y: d z: a data: (in at: 6) add: 16rFC93A039 shift: 21. a := self step4: a x: b y: c z: d data: (in at: 13) add: 16r655B59C3 shift: 6. d := self step4: d x: a y: b z: c data: (in at: 4) add: 16r8F0CCC92 shift: 10. c := self step4: c x: d y: a z: b data: (in at: 11) add: 16rFFEFF47D shift: 15. b := self step4: b x: c y: d z: a data: (in at: 2) add: 16r85845DD1 shift: 21. a := self step4: a x: b y: c z: d data: (in at: 9) add: 16r6FA87E4F shift: 6. d := self step4: d x: a y: b z: c data: (in at: 16) add: 16rFE2CE6E0 shift: 10. c := self step4: c x: d y: a z: b data: (in at: 7) add: 16rA3014314 shift: 15. b := self step4: b x: c y: d z: a data: (in at: 14) add: 16r4E0811A1 shift: 21. a := self step4: a x: b y: c z: d data: (in at: 5) add: 16rF7537E82 shift: 6. d := self step4: d x: a y: b z: c data: (in at: 12) add: 16rBD3AF235 shift: 10. c := self step4: c x: d y: a z: b data: (in at: 3) add: 16r2AD7D2BB shift: 15. b := self step4: b x: c y: d z: a data: (in at: 10) add: 16rEB86D391 shift: 21. a := (a + (hash at: 1)) bitAnd: 16rFFFFFFFF. hash at: 1 put: a. b := (b + (hash at: 2)) bitAnd: 16rFFFFFFFF. hash at: 2 put: b. c := (c + (hash at: 3)) bitAnd: 16rFFFFFFFF. hash at: 3 put: c. d := (d + (hash at: 4)) bitAnd: 16rFFFFFFFF. hash at: 4 put: d. ^hash! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! rotate: value by: amount "Rotate value left by amount" | lowMask highMask | lowMask := (1 bitShift: 32-amount) - 1. highMask := 16rFFFFFFFF - lowMask. ^((value bitAnd: lowMask) bitShift: amount) + ((value bitAnd: highMask) bitShift: amount-32)! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! step1: w x: x y: y z: z data: data add: add shift: s "First step in MD5 transformation" | f result | f := z bitXor: (x bitAnd: (y bitXor: z)). result := w + f + data + add. result := self rotate: result by: s. ^result + x bitAnd: 16rFFFFFFFF! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! step2: w x: x y: y z: z data: data add: add shift: s "First step in MD5 transformation" | f result | f := y bitXor: (z bitAnd: (x bitXor: y)). result := w + f + data + add. result := self rotate: result by: s. ^result + x bitAnd: 16rFFFFFFFF! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! step3: w x: x y: y z: z data: data add: add shift: s "First step in MD5 transformation" | f result | f := (x bitXor: y) bitXor: z. result := w + f + data + add. result := self rotate: result by: s. ^result + x bitAnd: 16rFFFFFFFF! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:19'! step4: w x: x y: y z: z data: data add: add shift: s "First step in MD5 transformation" | f result | f := y bitXor: (x bitOr: (z bitXor: 16rFFFFFFFF)). result := w + f + data + add. result := self rotate: result by: s. ^result + x bitAnd: 16rFFFFFFFF! ! !FloatMathPluginTests methodsFor: 'md5' stamp: 'ar 3/26/2006 15:20'! testMD5 self assert: (self md5HashMessage: 'a') = 16r0CC175B9C0F1B6A831C399E269772661. self assert: (self md5HashMessage: 'abc') = 16r900150983CD24FB0D6963F7D28E17F72. self assert: (self md5HashMessage: 'message digest') = 16rF96B697D7CB7938D525A2F31AAF161D0. self assert: (self md5HashMessage: 'abcdefghijklmnopqrstuvwxyz') = 16rC3FCD3D76192E4007DFB496CCA67E13B. self assert: (self md5HashMessage: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') = 16rD174AB98D277D9F5A5611C2C9F419D9F. self assert: (self md5HashMessage: '12345678901234567890123456789012345678901234567890123456789012345678901234567890') = 16r57EDF4A22BE3C955AC49DA2E2107B67A.! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:42'! testArcCos | hash | hash := self runTest:[:f| self arcCos: f]. self assert: hash = 175366936335278026567589867783483480383! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:44'! testArcCosH | hash | hash := self runTest:[:f| self arcCosH: f]. self assert: hash = 6724426144112251941037505276242428134! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:43'! testArcSin | hash | hash := self runTest:[:f| self arcSin: f]. self assert: hash = 27372132577303862731837100895783885417! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:42'! testArcSinH | hash | hash := self runTest:[:f| self arcSinH: f]. self assert: hash = 255911863578190171815115260235896145802! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:43'! testArcTan | hash | hash := self runTest:[:f| self arcTan: f]. self assert: hash = 17311773710959114634056077345168823659! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:51'! testArcTan2 | hash | hash := self runTest:[:f| self arcTan2: f with: f]. self assert: hash = 287068347279655848752274030373495709564! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:44'! testArcTanH | hash | hash := self runTest:[:f| self arcTanH: f]. self assert: hash = 295711907369004359459882231908879164929! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:45'! testCos | hash | hash := self runTest:[:f| self cos: f]. self assert: hash = 110207739557966732640546618158077332978! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:42'! testCosH | hash | hash := self runTest:[:f| self cosH: f]. self assert: hash = 139309299067563830037108641802292492276! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:27'! testExp | hash | hash := self runTest:[:f| self exp: f]. self assert: hash = 264681209343177480335132131244505189510! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:27'! testFloatAt | hash flt | flt := FloatArray new: 1. hash := self runTest:[:f| flt at: 1 put: f. flt at: 1]. self assert: hash = 80498428122197125691266588764018905399! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:27'! testFraction | hash | hash := self runTest:[:f| self fractionPart: f]. self assert: hash = 320444785026869345695277323179170692004! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:51'! testHypot | hash | hash := self runTest:[:f| self hypot: f with: f+1]. self assert: hash = 217113721886532765853628735806816720346! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:27'! testLog | hash | hash := self runTest:[:f| self ln: f abs]. self assert: hash = 24389651894375564945708989023746058645! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:42'! testLog10 | hash | hash := self runTest:[:f| self log10: f abs]. self assert: hash = 135564553959509933253581837789050718785! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:44'! testSin | hash | hash := self runTest:[:f| self sin: f]. self assert: hash = 290162321010315440569513182938961037473! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:44'! testSinH | hash | hash := self runTest:[:f| self sinH: f]. self assert: hash = 146029709156303766079448006055284064911! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:28'! testSqrt | hash | hash := self runTest:[:f| self sqrt: f abs]. self assert: hash = 112236588358122834093969606123302196127! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:45'! testTan | hash | hash := self runTest:[:f| self tan: f]. self assert: hash = 169918898922109300293069449425089094780! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 17:43'! testTanH | hash | hash := self runTest:[:f| self tanH: f]. self assert: hash = 15738508136206638425252880299326548123! ! !FloatMathPluginTests methodsFor: 'tests' stamp: 'ar 3/26/2006 14:28'! testTimesTwoPower | hash | hash := self runTest:[:f| self timesTwoPower: f with: (random nextInt: 200) - 100]. self assert: hash = 278837335583284459890979576373223649870.! ! !FloatMathPluginTests methodsFor: 'data' stamp: 'ar 3/26/2006 17:56'! testAtanData self verifyTestData: 'atan-small.dat' using:[:f| self arcTan: f]. self verifyTestData: 'atan-large.dat' using:[:f| self arcTan: f]. ! ! !FloatMathPluginTests methodsFor: 'data' stamp: 'ar 3/26/2006 17:56'! testExpData self verifyTestData: 'exp-small.dat' using:[:f| self exp: f]. self verifyTestData: 'exp-large.dat' using:[:f| self exp: f]. ! ! !FloatMathPluginTests methodsFor: 'data' stamp: 'ar 3/26/2006 17:56'! testLogData self verifyTestData: 'log-small.dat' using:[:f| self ln: f abs]. self verifyTestData: 'log-large.dat' using:[:f| self ln: f abs]. ! ! !FloatMathPluginTests methodsFor: 'data' stamp: 'ar 3/26/2006 17:56'! testSinData self verifyTestData: 'sin-small.dat' using:[:f| self sin: f]. self verifyTestData: 'sin-large.dat' using:[:f| self sin: f]. ! ! !FloatMathPluginTests methodsFor: 'data' stamp: 'ar 3/26/2006 17:56'! testSqrtData self verifyTestData: 'sqrt-small.dat' using:[:f| self sqrt: f abs]. self verifyTestData: 'sqrt-large.dat' using:[:f| self sqrt: f abs].! ! ClassTestCase subclass: #FloatTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Numbers'! !FloatTest commentStamp: 'fbs 3/8/2004 22:13' prior: 0! I provide a test suite for Float values. Examine my tests to see how Floats should behave, and see how to use them.! !FloatTest methodsFor: 'testing - arithmetic' stamp: 'st 9/20/2004 17:04'! testContinuedFractions self assert: (Float pi asApproximateFractionAtOrder: 1) = (22/7). self assert: (Float pi asApproximateFractionAtOrder: 3) = (355/113)! ! !FloatTest methodsFor: 'testing - arithmetic' stamp: 'fbs 3/8/2004 22:10'! testDivide self assert: 2.0 / 1 = 2. self should: [ 2.0 / 0 ] raise: ZeroDivide.! ! !FloatTest methodsFor: 'infinity behavior' stamp: 'sd 6/5/2005 08:30'! testInfinity1 "FloatTest new testInfinity1" | i1 i2 | i1 := 10000 exp. i2 := 1000000000 exp. self assert: i1 isInfinite & i2 isInfinite & (i1 = i2). "All infinities are equal. (This is a very substantial difference to NaN's, which are never equal." ! ! !FloatTest methodsFor: 'infinity behavior' stamp: 'sd 6/5/2005 08:30'! testInfinity2 "FloatTest new testInfinity2" | i1 i2 | i1 := 10000 exp. i2 := 1000000000 exp. i2 := 0 - i2. " this is entirely ok. You can compute with infinite values." self assert: i1 isInfinite & i2 isInfinite & i1 positive & i2 negative. self deny: i1 = i2. "All infinities are signed. Negative infinity is not equal to Infinity" ! ! !FloatTest methodsFor: 'IEEE 754' stamp: 'nice 5/30/2006 02:34'! test32bitGradualUnderflow "method asIEEE32BitWord did not respect IEEE gradual underflow" | conv expected exponentPart | "IEEE 32 bits Float have 1 bit sign/8 bit exponent/23 bits of mantissa after leading 1 2r1.mmmmmmmmmmmmmmmmmmmmmmm * (2 raisedTo: 2reeeeeeee-127) * sign except when 2reeeeeeee isZero, which is a gradual underflow: 2r0.mmmmmmmmmmmmmmmmmmmmmmm * (2 raisedTo: 2r00000000-126) * sign and when 2reeeeeeee = 255, which is infinity if mantissa all zero or nan otherwise" "case 1: This example is the first gradual underflow case" conv := 2r0.11111111111111111111111e-126 asIEEE32BitWord. "expected float encoded as sign/exponent/mantissa (whithout leading 1 or 0)" exponentPart := 0. expected := exponentPart bitOr: 2r11111111111111111111111. self assert: expected = conv. "case 2: smallest number" conv := 2r0.00000000000000000000001e-126 asIEEE32BitWord. expected := exponentPart bitOr: 2r1. self assert: expected = conv. "case 3: round to nearest even also in underflow cases... here round to upper" conv := 2r0.000000000000000000000011e-126 asIEEE32BitWord. expected := exponentPart bitOr: 2r10. self assert: expected = conv. "case 4: round to nearest even also in underflow cases... here round to lower" conv := 2r0.000000000000000000000101e-126 asIEEE32BitWord. expected := exponentPart bitOr: 2r10. self assert: expected = conv. "case 5: round to nearest even also in underflow cases... here round to upper" conv := 2r0.0000000000000000000001011e-126 asIEEE32BitWord. expected := exponentPart bitOr: 2r11. self assert: expected = conv. ! ! !FloatTest methodsFor: 'IEEE 754' stamp: 'nice 5/30/2006 00:07'! test32bitRoundingMode "method asIEEE32BitWord did not respect IEEE default rounding mode" | conv expected exponentPart | "IEEE 32 bits Float have 1 bit sign/8 bit exponent/23 bits of mantissa after leading 1 2r1.mmmmmmmmmmmmmmmmmmmmmmm * (2 raisedTo: 2reeeeeeee-127) * sign except when 2reeeeeeee isZero, which is a gradual underflow: 2r0.mmmmmmmmmmmmmmmmmmmmmmm * (2 raisedTo: 2r00000000-127) * sign and when 2reeeeeeee = 255, which is infinity if mantissa all zero or nan otherwise" "This example has two extra bits in mantissa for testing rounding mode case 1: should obviously round to upper" conv := 2r1.0000000000000000000000111e25 asIEEE32BitWord. "expected float encoded as sign/exponent/mantissa (whithout leading 1)" exponentPart := 25+127 bitShift: 23. "127 is 2r01111111 or 16r7F" expected := exponentPart bitOr: 2r10. self assert: expected = conv. "case 2: exactly in the mid point of two 32 bit float: round toward nearest even (to upper)" conv := 2r1.0000000000000000000000110e25 asIEEE32BitWord. expected := exponentPart bitOr: 2r10. self assert: expected = conv. "case 3: exactly in the mid point of two 32 bit float: round toward nearest even (to lower)" conv := 2r1.0000000000000000000000010e25 asIEEE32BitWord. expected := exponentPart bitOr: 2r0. self assert: expected = conv. "case 4: obviously round to upper" conv := 2r1.0000000000000000000000011e25 asIEEE32BitWord. expected := exponentPart bitOr: 2r1. self assert: expected = conv. ! ! !FloatTest methodsFor: 'IEEE 754' stamp: 'pmm 2/27/2006 10:49'! testInfinity3 self assert: (Float infinity negated asIEEE32BitWord printPaddedWith: $0 to: 32 base: 2) = '11111111100000000000000000000000'. self assert: (Float fromIEEE32Bit: (Integer readFrom: '11111111100000000000000000000000' readStream base: 2)) = Float infinity negated! ! !FloatTest methodsFor: 'IEEE 754' stamp: 'pmm 2/27/2006 10:48'! testNaN5 self assert: (Float nan asIEEE32BitWord printPaddedWith: $0 to: 32 base: 2) = '01111111110000000000000000000000'. self assert: (Float fromIEEE32Bit: (Integer readFrom: '01111111110000000000000000000000' readStream base: 2)) isNaN! ! !FloatTest methodsFor: 'IEEE 754' stamp: 'pmm 2/27/2006 10:49'! testZero2 self assert: (Float negativeZero asIEEE32BitWord printPaddedWith: $0 to: 32 base: 2) = '10000000000000000000000000000000'. self assert: (Float fromIEEE32Bit: (Integer readFrom: '10000000000000000000000000000000' readStream base: 2)) = Float negativeZero! ! !FloatTest methodsFor: 'zero behavior' stamp: 'md 4/16/2003 15:02'! testIsZero self assert: 0.0 isZero. self deny: 0.1 isZero.! ! !FloatTest methodsFor: 'zero behavior' stamp: 'sd 6/5/2005 08:33'! testZero1 "FloatTest new testZero1" self assert: Float negativeZero = 0 asFloat. self assert: (Float negativeZero at: 1) ~= (0 asFloat at: 1). "The negative zero has a bit representation that is different from the bit representation of the positive zero. Nevertheless, both values are defined to be equal." ! ! !FloatTest methodsFor: 'NaN behavior' stamp: 'sd 6/5/2005 08:31'! testNaN1 "FloatTest new testNaN1" self assert: Float nan == Float nan. self deny: Float nan = Float nan. "a NaN is not equal to itself." ! ! !FloatTest methodsFor: 'NaN behavior' stamp: 'dtl 10/1/2004 18:26'! testNaN2 "Two NaN values are always considered to be different. On an little-endian machine (32 bit Intel), Float nan is 16rFFF80000 16r00000000. On a big-endian machine (PowerPC), Float nan is 16r7FF80000 16r00000000. Changing the bit pattern of the first word of a NaN produces another value that is still considered equal to NaN. This test should work on both little endian and big endian machines. However, it is not guaranteed to work on future 64 bit versions of Squeak, for which Float may have different internal representations." "FloatTest new testNaN2" | nan1 nan2 | nan1 := Float nan copy. nan2 := Float nan copy. "test two instances of NaN with the same bit pattern" self deny: nan1 = nan2. self deny: nan1 == nan2. self deny: nan1 = nan1. self assert: nan1 == nan1. "change the bit pattern of nan1" self assert: nan1 size == 2. self assert: (nan1 at: 2) = 0. nan1 at: 1 put: (nan1 at: 1) + 999. self assert: nan1 isNaN. self assert: nan2 isNaN. self deny: (nan1 at: 1) = (nan2 at: 1). "test two instances of NaN with different bit patterns" self deny: nan1 = nan2. self deny: nan1 == nan2. self deny: nan1 = nan1. self assert: nan1 == nan1 ! ! !FloatTest methodsFor: 'NaN behavior' stamp: 'sd 6/5/2005 08:32'! testNaN3 "FloatTest new testNaN3" | set item identitySet | set := Set new. set add: (item := Float nan). self deny: (set includes: item). identitySet := IdentitySet new. identitySet add: (item := Float nan). self assert: (identitySet includes: item). "as a NaN is not equal to itself, it can not be retrieved from a set" ! ! !FloatTest methodsFor: 'NaN behavior' stamp: 'sd 6/5/2005 08:32'! testNaN4 "FloatTest new testNaN4" | dict | dict := Dictionary new. dict at: Float nan put: #NaN. self deny: (dict includes: Float nan). "as a NaN is not equal to itself, it can not be retrieved when it is used as a dictionary key" ! ! !FloatTest methodsFor: 'testing - conversion' stamp: 'nice 4/26/2006 05:21'! testFloatTruncated "(10 raisedTo: 16) asFloat has an exact representation (no round off error). It should convert back to integer without loosing bits. This is a no regression test on http://bugs.impara.de/view.php?id=3504" | x y int r | int := 10 raisedTo: 16. x := int asFloat. y := (5 raisedTo: 16) asFloat timesTwoPower: 16. self assert: x = y. self assert: x asInteger = int. "this one should be true for any float" self assert: x asInteger = x asTrueFraction asInteger. "a random test" r := Random new. 10000 timesRepeat: [ x := r next * 1.9999e16 + 1.0e12 . self assert: x truncated = x asTrueFraction truncated]! ! !FloatTest methodsFor: 'testing - conversion' stamp: 'nice 5/7/2006 16:22'! testFractionAsFloat "use a random test" | r m frac err collec | r := Random new seed: 1234567. m := (2 raisedTo: 54) - 1. 200 timesRepeat: [ frac := ((r nextInt: m) * (r nextInt: m) + 1) / ((r nextInt: m) * (r nextInt: m) + 1). err := (frac - frac asFloat asTrueFraction) * frac reciprocal * (1 bitShift: 52). self assert: err < (1/2)]. collec := #(16r10000000000000 16r1FFFFFFFFFFFFF 1 2 16r20000000000000 16r20000000000001 16r3FFFFFFFFFFFFF 16r3FFFFFFFFFFFFE 16r3FFFFFFFFFFFFD). collec do: [:num | collec do: [:den | frac := Fraction numerator: num denominator: den. err := (frac - frac asFloat asTrueFraction) * frac reciprocal * (1 bitShift: 52). self assert: err <= (1/2)]].! ! !FloatTest methodsFor: 'testing - conversion' stamp: 'nice 5/6/2006 22:13'! testIntegerAsFloat "assert IEEE 754 round to nearest even mode is honoured" self deny: 16r1FFFFFFFFFFFF0801 asFloat = 16r1FFFFFFFFFFFF0800 asFloat. "this test is on 65 bits" self deny: 16r1FFFFFFFFFFFF0802 asFloat = 16r1FFFFFFFFFFFF0800 asFloat. "this test is on 64 bits" self assert: 16r1FFFFFFFFFFF1F800 asFloat = 16r1FFFFFFFFFFF20000 asFloat. "nearest even is upper" self assert: 16r1FFFFFFFFFFFF0800 asFloat = 16r1FFFFFFFFFFFF0000 asFloat. "nearest even is lower" ! ! !FloatTest methodsFor: 'testing - conversion' stamp: 'dtl 9/18/2004 12:40'! testStringAsNumber "This covers parsing in Number>>readFrom:" | aFloat | aFloat _ '10r-12.3456' asNumber. self assert: -12.3456 = aFloat. aFloat _ '10r-12.3456e2' asNumber. self assert: -1234.56 = aFloat. aFloat _ '10r-12.3456d2' asNumber. self assert: -1234.56 = aFloat. aFloat _ '10r-12.3456q2' asNumber. self assert: -1234.56 = aFloat. aFloat _ '-12.3456q2' asNumber. self assert: -1234.56 = aFloat. aFloat _ '12.3456q2' asNumber. self assert: 1234.56 = aFloat. ! ! AlignmentMorph subclass: #FloatingBookControlsMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Navigators'! !FloatingBookControlsMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 22:38'! defaultBorderWidth "answer the default border width for the receiver" ^ 1! ! !FloatingBookControlsMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 19:16'! initialize "initialize the state of the receiver" super initialize. "" self layoutInset: 0; hResizing: #shrinkWrap; vResizing: #shrinkWrap ! ! !FloatingBookControlsMorph methodsFor: 'stepping and presenter' stamp: 'RAA 8/23/2000 12:48'! step owner == self world ifFalse: [^ self]. owner addMorphInLayer: self. ! ! !FloatingBookControlsMorph methodsFor: 'testing' stamp: 'RAA 8/23/2000 12:47'! stepTime ^1000! ! !FloatingBookControlsMorph methodsFor: 'testing' stamp: 'RAA 8/23/2000 12:47'! wantsSteps ^true! ! !FloatingBookControlsMorph methodsFor: 'WiW support' 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"! ! ArithmeticError subclass: #FloatingPointException instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Exceptions-Kernel'! Object subclass: #FontCache instanceVariableNames: 'fonts' classVariableNames: '' poolDictionaries: '' category: 'Nebraska-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-Fonts'! !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: '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! ! !FontSet class methodsFor: 'compiling' stamp: 'di 1/24/2005 12:40'! compileFont: strikeFont | tempName literalString header sizeStr familyName | tempName _ 'FontTemp.sf2'. strikeFont writeAsStrike2named: tempName. literalString _ (Base64MimeConverter mimeEncode: (FileStream readOnlyFileNamed: tempName) binary) contents fullPrintString. sizeStr _ strikeFont pointSize asString. familyName _ strikeFont name first: (strikeFont name findLast: [:x | x isDigit not]). header _ 'size' , sizeStr , ' ^ self fontNamed: ''' , familyName , sizeStr , ''' fromMimeLiteral: ' . self class compile: header , literalString classified: 'fonts' notifying: nil. FileDirectory default deleteFileNamed: tempName ! ! !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: 'di 1/24/2005 12:48'! fontNamed: fontName fromLiteral: aString "NOTE -- THIS IS AN OBSOLETE METHOD THAT MAY CAUSE ERRORS. The old form of fileOut for FontSets produced binary literal strings which may not be accurately read in systems with support for international character sets. If possible, file the FontSet out again from a system that produces the newer MIME encoding (current def of compileFont:), and uses the corresponding altered version of this method. If this is not easy, then file the fontSet into an older system (3.7 or earlier), assume it is called FontSetZork... execute FontSetZork installAsTextStyle. copy the compileFont: method from this system into that older one. remove the class FontSetZork. Execute: FontSet convertTextStyleNamed: 'Zork', and see that it creates a new FontSetZork. FileOut the new class FontSetZork. The resulting file should be able to be read into this system. " ^ StrikeFont new name: fontName; readFromStrike2Stream: (ReadStream on: aString asByteArray)! ! !FontSet class methodsFor: 'installing' stamp: 'di 1/24/2005 11:13'! fontNamed: fontName fromMimeLiteral: 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 following the logic in compileFont: to encode and add a heading. See the method installAsTextStyle to see how this can be used." ^ StrikeFont new name: fontName; readFromStrike2Stream: (Base64MimeConverter mimeDecodeToBytes: aString readStream)! ! !FontSet class methodsFor: 'installing' stamp: 'rbb 2/18/2005 13:20'! 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. "rbb 2/18/2005 13:20 - How should this change for UIManger, if at all?" 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: 'nk 8/31/2004 09:23'! 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); yourself! ! !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! ! Notification subclass: #FontSubstitutionDuringLoading instanceVariableNames: 'familyName pixelSize' classVariableNames: '' poolDictionaries: '' category: 'System-Support'! !FontSubstitutionDuringLoading commentStamp: '' prior: 0! signaled by font loading code when reading a DiskProxy that calls for a missing font.! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 15:04'! defaultAction familyName ifNil: [ familyName := 'NoName' ]. pixelSize ifNil: [ pixelSize := 12 ]. ^((familyName beginsWith: 'Comic') ifTrue: [ TextStyle named: (Preferences standardEToysFont familyName) ] ifFalse: [ TextStyle default ]) fontOfSize: pixelSize.! ! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 15:01'! familyName "Answer the value of familyName" ^ familyName! ! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 15:01'! familyName: anObject "Set the value of familyName" familyName _ anObject! ! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 15:01'! pixelSize "Answer the value of pixelSize" ^ pixelSize! ! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 15:01'! pixelSize: anObject "Set the value of pixelSize" pixelSize _ anObject! ! !FontSubstitutionDuringLoading methodsFor: 'accessing' stamp: 'nk 11/8/2004 16:55'! printOn: aStream super printOn: aStream. aStream nextPut: $(; nextPutAll: familyName; nextPut: $-; print: pixelSize; nextPut: $).! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FontSubstitutionDuringLoading class instanceVariableNames: ''! !FontSubstitutionDuringLoading class methodsFor: 'instance creation' stamp: 'nk 11/8/2004 15:07'! forFamilyName: aName pixelSize: aSize ^(self new) familyName: aName; pixelSize: aSize; yourself.! ! TestCase subclass: #FontTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Multilingual-Display'! !FontTest commentStamp: 'tak 3/11/2005 14:31' prior: 0! I am mainly a test for fallback font. FontTest buildSuite run! !FontTest methodsFor: 'testing' stamp: 'yo 1/13/2005 16:44'! testDisplay "self debug: #testDisplay" | text font bb destPoint width | text _ 'test' asText. font _ TextStyle default fontOfSize: 21. text addAttribute: (TextFontReference toFont: font). bb _ (Form extent: 100 @ 30) getCanvas privatePort. bb combinationRule: Form paint. font installOn: bb foregroundColor: Color black backgroundColor: Color white. destPoint _ font displayString: text on: bb from: 1 to: 4 at: 0@0 kern: 1. width _ text inject: 0 into: [:max :char | max + (font widthOf: char)]. self assert: destPoint x = (width + 4). "bb destForm asMorph openInHand." ! ! !FontTest methodsFor: 'testing' stamp: 'yo 1/13/2005 16:41'! testFallback "self debug: #testFallback" | text font bb destPoint | text _ (Character value: 257) asString asText. font _ TextStyle default fontOfSize: 21. text addAttribute: (TextFontReference toFont: font). bb _ (Form extent: 100 @ 30) getCanvas privatePort. bb combinationRule: Form paint. font installOn: bb foregroundColor: Color black backgroundColor: Color white. destPoint _ font displayString: text on: bb from: 1 to: 1 at: 0@0 kern: 1. "bb destForm asMorph openInHand." self assert: destPoint x = ((font widthOf: $?) + 1). ! ! !FontTest methodsFor: 'testing' stamp: 'tak 12/22/2004 00:56'! testMultistringFallbackFont "self debug: #testMultistringFallbackFont" | text p style height width | [(TextStyle default fontArray at: JapaneseEnvironment leadingChar) ifNil: [^ self]] ifError: [:err :rcvr | ^ self]. text := ((#(20983874 20983876 20983878 ) collect: [:e | e asCharacter]) as: String) asText. p := MultiNewParagraph new. style := TextStyle new leading: 0; newFontArray: {Preferences standardFlapFont}. p compose: text style: style from: 1 in: (0 @ 0 corner: 100 @ 100). "See CompositionScanner>>setActualFont: & CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide:" height := style defaultFont height + style leading. width := text inject: 0 into: [:tally :next | tally + (style defaultFont widthOf: next)]. p adjustRightX. self assert: p extent = (width @ height). "Display getCanvas paragraph: p bounds: (10 @ 10 extent: 100 @ 100) color: Color black"! ! !FontTest methodsFor: 'testing' stamp: 'tak 12/21/2004 18:02'! testMultistringFont "self debug: #testMultistringFont" | text p style height width | [(TextStyle default fontArray at: JapaneseEnvironment leadingChar) ifNil: [^ self]] ifError: [:err :rcvr | ^ self]. text := ((#(20983874 20983876 20983878 ) collect: [:e | e asCharacter]) as: String) asText. p := MultiNewParagraph new. style := TextStyle default. p compose: text style: style from: 1 in: (0 @ 0 corner: 100 @ 100). "See CompositionScanner>>setActualFont: & CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide:" height := style defaultFont height + style leading. width := text inject: 0 into: [:tally :next | tally + (style defaultFont widthOf: next)]. p adjustRightX. self assert: p extent = (width @ height). "Display getCanvas paragraph: p bounds: (10 @ 10 extent: 100 @ 100) color: Color black"! ! !FontTest methodsFor: 'testing' stamp: 'tak 12/21/2004 14:50'! testParagraph "self debug: #testParagraph" | text p style height width | text := 'test' asText. p := MultiNewParagraph new. style := TextStyle default. p compose: text style: style from: 1 in: (0 @ 0 corner: 100 @ 100). "See CompositionScanner>>setActualFont: & CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide:" height := style defaultFont height + style leading. width := text inject: 0 into: [:tally :next | tally + (style defaultFont widthOf: next)]. p adjustRightX. self assert: p extent = (width @ height)! ! !FontTest methodsFor: 'testing' stamp: 'tak 12/21/2004 17:19'! testParagraphFallback "self debug: #testParagraphFallback" | text p style height width e expect | e := (Character value: 257) asString. text := ('test' , e , e , e , e , 'test') asText. expect := 'test????test'. p := MultiNewParagraph new. style := TextStyle default. p compose: text style: style from: 1 in: (0 @ 0 corner: 100 @ 100). "See CompositionScanner>>setActualFont: & CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide:" height := style defaultFont height + style leading. width := expect inject: 0 into: [:tally :next | tally + (style defaultFont widthOf: next)]. p adjustRightX. self assert: p extent = (width @ height). "Display getCanvas paragraph: p bounds: (10 @ 10 extent: 100 @ 100) color: Color black"! ! !FontTest methodsFor: 'testing' stamp: 'DF 5/25/2006 20:49'! testResetAfterEmphasized "self debug: #testResetAfterEmphasized" | normal derivative | normal _ TextStyle defaultFont. derivative _ normal emphasized: 3. self assert: (normal derivativeFonts at: 3) == derivative. normal reset. self assert: normal derivativeFonts isEmpty ! ! DisplayMedium subclass: #Form instanceVariableNames: 'bits width height depth offset' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !Form commentStamp: 'ls 1/4/2004 17:16' 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 indexed starting at 0 instead of 1; thus, the top-left pixel of a Form has coordinates 0@0. Forms are combined using BitBlt. See the comment in class BitBlt. 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. The encoding depends on the depth. ! ]style[(223 6 62 5 374 6 11 23 64 12 40 5 337)f1,f1LBitmap Definition;,f1,f1LColor Definition;,f1,f1LBitBlt Definition;,f1,f1LBitBlt Comment;,f1,f1LInfiniteForm Definition;,f1,f1RColor;,f1! !Form methodsFor: 'accessing'! bits "Answer the receiver's Bitmap containing its bits." ^ bits! ! !Form methodsFor: 'accessing' stamp: 'ar 5/17/2001 15:41'! bitsSize | pixPerWord | depth == nil ifTrue: [depth _ 1]. pixPerWord _ 32 // self depth. ^ width + pixPerWord - 1 // pixPerWord * height! ! !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' stamp: 'ar 5/17/2001 15:45'! depth ^ depth < 0 ifTrue:[0-depth] ifFalse:[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 5/17/2001 15:50'! nativeDepth "Return the 'native' depth of the receiver, e.g., including the endianess" ^depth! ! !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: 'analyzing'! 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: 'analyzing' 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: 'analyzing' stamp: 'ar 5/17/2001 15:40'! dominantColor | tally max maxi | self 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: self depth! ! !Form methodsFor: 'analyzing'! 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: 'analyzing' stamp: 'ar 5/17/2001 15:40'! 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//self 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: 'analyzing' stamp: 'ar 5/17/2001 15:42'! primCountBits "Count the non-zero pixels of this form." self 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: 'analyzing' stamp: 'ar 5/17/2001 15:37'! 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: (self depth min: 15)). cm primFill: 1. cm at: (aColor indexInMap: cm) put: 0. "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: 'analyzing' 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: 'analyzing' 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: 'analyzing' 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: 'analyzing' 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: 'bordering' stamp: 'ar 5/17/2001 15:42'! 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 | self 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' 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: 'di 10/21/2001 09:39'! shapeBorder: aColor width: borderWidth "A simplified version for shapes surrounded by transparency (as SketchMorphs). Note also this returns a new form that may be larger, and does not affect the original." | shapeForm borderForm newForm | newForm _ Form extent: self extent + (borderWidth*2) depth: self depth. newForm fillColor: Color transparent. self displayOn: newForm at: (0@0) + borderWidth. "First identify the shape in question as a B/W form" shapeForm _ (newForm makeBWForm: Color transparent) reverse. "Now find the border of that shape" borderForm _ shapeForm borderFormOfWidth: borderWidth sharpCorners: false. "Finally use that shape as a mask to paint the border with color" ^ newForm fillShape: borderForm fillColor: aColor! ! !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: 'color mapping' stamp: 'ar 5/15/2001 16:16'! balancedPatternFor: aColor "Return the pixel word for representing the given color on the receiver" self hasNonStandardPalette ifTrue:[^self bitPatternFor: aColor] ifFalse:[^aColor balancedPatternForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/15/2001 16:16'! bitPatternFor: aColor "Return the pixel word for representing the given color on the receiver" aColor isColor ifFalse:[^aColor bitPatternForDepth: self depth]. self hasNonStandardPalette ifTrue:[^Bitmap with: (self pixelWordFor: aColor)] ifFalse:[^aColor bitPatternForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/15/2001 16:16'! colormapFromARGB "Return a ColorMap mapping from canonical ARGB space into the receiver. Note: This version is optimized for Squeak forms." | map nBits | self hasNonStandardPalette 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 5/17/2001 15:42'! colormapIfNeededForDepth: destDepth "Return a colormap for displaying the receiver at the given depth, or nil if no colormap is needed." self depth = destDepth ifTrue: [^ nil]. "not needed if depths are the same" ^ Color colorMapIfNeededFrom: self depth to: destDepth ! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/16/2001 22:23'! colormapIfNeededFor: destForm "Return a ColorMap mapping from the receiver to destForm." (self hasNonStandardPalette or:[destForm hasNonStandardPalette]) ifTrue:[^self colormapFromARGB mappingTo: destForm colormapFromARGB] ifFalse:[^self colormapIfNeededForDepth: destForm depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/15/2001 16:16'! colormapToARGB "Return a ColorMap mapping from the receiver into canonical ARGB space." self hasNonStandardPalette 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'! 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: 'color mapping' stamp: 'ar 5/17/2001 15:40'! 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 | self depth < 16 ifTrue: [map _ (Color cachedColormapFrom: self depth to: self 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: self 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: 'color mapping' stamp: 'ar 5/17/2001 15:38'! 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: self depth to: self depth) copy. map at: (oldColor indexInMap: map) put: (newColor pixelWordForDepth: self 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: 'color mapping' stamp: 'ar 12/14/2001 18:11'! maskingMap "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." ^Color maskingMap: self depth! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/17/2001 15:41'! newColorMap "Return an uninitialized color map array appropriate to this Form's depth." ^ Bitmap new: (1 bitShift: (self depth min: 15)) ! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/15/2001 16:16'! pixelValueFor: aColor "Return the pixel word for representing the given color on the receiver" self hasNonStandardPalette ifTrue:[^self colormapFromARGB mapPixel: (aColor pixelValueForDepth: 32)] ifFalse:[^aColor pixelValueForDepth: self depth]! ! !Form methodsFor: 'color mapping' stamp: 'ar 5/15/2001 16:16'! pixelWordFor: aColor "Return the pixel word for representing the given color on the receiver" | basicPattern | self hasNonStandardPalette 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: 'di 10/16/2001 15:23'! reducedPaletteOfSize: nColors "Return an array of colors of size nColors, such that those colors represent well the pixel values actually found in this form." | threshold tallies colorTallies dist delta palette cts top cluster | tallies _ self tallyPixelValues. "An array of tallies for each pixel value" threshold _ width * height // 500. "Make an array of (color -> tally) for all tallies over threshold" colorTallies _ Array streamContents: [:s | tallies withIndexDo: [:v :i | v >= threshold ifTrue: [s nextPut: (Color colorFromPixelValue: i-1 depth: depth) -> v]]]. "Extract a set of clusters by picking the top tally, and then removing all others whose color is within dist of it. Iterate the process, adjusting dist until we get nColors." dist _ 0.2. delta _ dist / 2. [cts _ colorTallies copy. palette _ Array streamContents: [:s | [cts isEmpty] whileFalse: [top _ cts detectMax: [:a | a value]. cluster _ cts select: [:a | (a key diff: top key) < dist]. s nextPut: top key -> (cluster detectSum: [:a | a value]). cts _ cts copyWithoutAll: cluster]]. palette size = nColors or: [delta < 0.001]] whileFalse: [palette size > nColors ifTrue: [dist _ dist + delta] ifFalse: [dist _ dist - delta]. delta _ delta / 2]. ^ palette collect: [:a | a key] ! ! !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: 'converting' 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: 'converting' stamp: 'RAA 8/14/2000 10:13'! asCursorForm ^ self as: StaticForm! ! !Form methodsFor: 'converting' stamp: 'ar 6/16/2002 17:44'! asFormOfDepth: d | newForm | d = self depth ifTrue:[^self]. newForm _ Form extent: self extent depth: d. (BitBlt current toForm: newForm) colorMap: (self colormapIfNeededFor: newForm); copy: (self boundingBox) from: 0@0 in: self fillColor: nil rule: Form over. ^newForm! ! !Form methodsFor: 'converting' stamp: 'ar 5/17/2001 15:39'! 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 | self 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: 'converting' stamp: 'ar 11/7/1999 20:29'! asMorph ^ImageMorph new image: self! ! !Form methodsFor: 'converting' stamp: 'ar 2/7/2004 18:16'! asSourceForm ^self! ! !Form methodsFor: 'converting' 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: 'converting' stamp: 'di 10/16/2001 19:23'! copyWithColorsReducedTo: nColors "Note: this has not been engineered. There are better solutions in the literature." | palette colorMap pc closest | palette _ self reducedPaletteOfSize: nColors. colorMap _ (1 to: (1 bitShift: depth)) collect: [:i | pc _ Color colorFromPixelValue: i-1 depth: depth. closest _ palette detectMin: [:c | c diff: pc]. closest pixelValueForDepth: depth]. ^ self deepCopy copyBits: self boundingBox from: self at: 0@0 colorMap: (colorMap as: Bitmap) ! ! !Form methodsFor: 'converting' 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: 'copying' stamp: 'RAA 9/28/1999 11:20'! blankCopyOf: aRectangle scaledBy: scale ^ self class extent: (aRectangle extent * scale) truncated depth: depth! ! !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' 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: 'copying' 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: 'copying' 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: 'copying' 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: '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: '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: 'displaying' stamp: 'ar 2/13/2001 22:13'! 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: ((Smalltalk at: #B3DMaterial) new emission: Color white). engine texture: self. engine render: ((Smalltalk at: #B3DIndexedQuadMesh) new plainTextureRect). engine finish.! ! !Form methodsFor: 'displaying' stamp: 'ar 2/13/2001 22:12'! 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: ((Smalltalk at: #B3DMaterial) new emission: Color white). engine texture: self. engine render: ((Smalltalk at: #B3DIndexedQuadMesh) new plainTextureRect). engine finish.! ! !Form methodsFor: 'displaying'! displayOnPort: port at: location port copyForm: self to: location rule: Form over! ! !Form methodsFor: 'displaying' stamp: 'ar 5/14/2001 23:33'! 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 colormapIfNeededFor: aDisplayMedium). ! ! !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' stamp: 'ar 5/17/2001 15:40'! displayResourceFormOn: aForm "a special display method for blowing up resource thumbnails" | engine tx cmap blitter | self extent = aForm extent ifTrue:[^self displayOn: aForm]. Smalltalk at: #B3DRenderEngine ifPresentAndInMemory: [: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 ]. tx _ self asTexture. (blitter _ BitBlt current toForm: tx) sourceForm: self; destRect: aForm boundingBox; sourceOrigin: 0@0; combinationRule: Form paint. "map transparency to current World background color" (World color respondsTo: #pixelWordForDepth:) ifTrue: [ cmap _ Bitmap new: (self depth <= 8 ifTrue: [1 << self depth] ifFalse: [4096]). cmap at: 1 put: (tx pixelWordFor: World color). blitter colorMap: cmap. ]. blitter copyBits. engine viewport: aForm boundingBox. engine material: ((Smalltalk at: #B3DMaterial) new emission: Color white). engine texture: tx. engine render: ((Smalltalk at: #B3DIndexedQuadMesh) new plainTextureRect). engine finish. "the above, using bilinear interpolation doesn't leave transparent pixel values intact" (WarpBlt current toForm: aForm) sourceForm: self destRect: aForm boundingBox; combinationRule: Form and; colorMap: (Color maskingMap: self depth); warpBits.! ! !Form methodsFor: 'displaying' stamp: 'ar 3/2/2001 21:32'! displayScaledOn: aForm "Display the receiver on aForm, scaling if necessary. Form fromUser displayScaledOn: Display. " self extent = aForm extent ifTrue:[^self displayOn: aForm]. (WarpBlt current toForm: aForm) sourceForm: self destRect: aForm boundingBox; combinationRule: Form paint; cellSize: 2; warpBits.! ! !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: '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: 'fileIn/Out' stamp: 'ar 2/24/2001 22:41'! comeFullyUpOnReload: smartRefStream bits isForm ifFalse:[^self]. "make sure the resource gets loaded afterwards" ResourceCollector current ifNil:[^self]. ResourceCollector current noteResource: bits replacing: self. ! ! !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: 'ar 3/3/2001 16:16'! objectForDataStream: refStream | prj repl | prj _ refStream project. prj ifNil:[^super objectForDataStream: refStream]. ResourceCollector current ifNil:[^super objectForDataStream: refStream]. repl _ ResourceCollector current objectForDataStream: refStream fromForm: self. ^repl! ! !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: 'mu 8/17/2003 00:44'! readAttributesFrom: aBinaryStream | offsetX offsetY | depth _ aBinaryStream next. (self depth isPowerOfTwo and: [self 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]. offset _ Point x: offsetX y: offsetY. ! ! !Form methodsFor: 'fileIn/Out' stamp: 'mu 8/17/2003 00:43'! readBitsFrom: aBinaryStream 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: 'mu 8/17/2003 00:44'! readFrom: aBinaryStream "Reads the receiver from the given binary stream with the format: depth, extent, offset, bits." self readAttributesFrom: aBinaryStream. self readBitsFrom: aBinaryStream! ! !Form methodsFor: 'fileIn/Out' stamp: 'ar 2/24/2001 22:39'! replaceByResource: aForm "Replace the receiver by some resource that just got loaded" (self extent = aForm extent and:[self depth = aForm depth]) ifTrue:[ bits _ aForm bits. ].! ! !Form methodsFor: 'fileIn/Out' stamp: 'nk 12/31/2003 16:06'! store15To24HexBitsOn:aStream | buf i lineWidth | "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" lineWidth _ 0. 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 nextPutAll: buf. lineWidth _ lineWidth + 12. lineWidth > 100 ifTrue: [ aStream cr. lineWidth _ 0 ]. "#( 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' stamp: 'laza 3/29/2004 12:21'! storeBitsOn:aStream base:anInteger bits do: [:word | anInteger = 10 ifTrue: [aStream space] ifFalse: [aStream crtab: 2]. word storeOn: aStream base: anInteger]. ! ! !Form methodsFor: 'fileIn/Out'! storeBits:startBit to:stopBit on:aStream bits storeBits:startBit to:stopBit on:aStream.! ! !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 3/3/2001 15:50'! unhibernate "If my bitmap has been compressed into a ByteArray, then expand it now, and return true." | resBits | bits isForm ifTrue:[ resBits _ bits. bits _ Bitmap new: self bitsSize. resBits displayResourceFormOn: self. ^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: 'mu 8/17/2003 00:35'! writeAttributesOn: file 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]). ! ! !Form methodsFor: 'fileIn/Out' stamp: 'mu 8/17/2003 00:35'! writeBitsOn: file bits writeOn: file! ! !Form methodsFor: 'fileIn/Out' stamp: 'ar 6/16/2002 17:53'! writeBMPfileNamed: fName "Display writeBMPfileNamed: 'display.bmp'" BMPReadWriter putForm: self onFileNamed: fName! ! !Form methodsFor: 'fileIn/Out' stamp: 'sw 2/20/2002 15:37'! writeJPEGfileNamed: fileName "Write a JPEG file to the given filename using default settings" self writeJPEGfileNamed: fileName progressive: false " Display writeJPEGfileNamed: 'display.jpeg' Form fromUser writeJPEGfileNamed: 'yourPatch.jpeg' "! ! !Form methodsFor: 'fileIn/Out' stamp: 'sw 2/20/2002 15:29'! writeJPEGfileNamed: fileName progressive: aBoolean "Write a JPEG file to the given filename using default settings. Make it progressive or not, depending on the boolean argument" JPEGReadWriter2 putForm: self quality: -1 "default" progressiveJPEG: aBoolean onFileNamed: fileName " Display writeJPEGfileNamed: 'display.jpeg' progressive: false. Form fromUser writeJPEGfileNamed: 'yourPatch.jpeg' progressive: true "! ! !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: 'mu 8/17/2003 00:36'! writeOn: file "Write the receiver on the file in the format depth, extent, offset, bits." self writeAttributesOn: file. self writeBitsOn: 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: '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: '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: 'di 10/17/2001 10:09'! eraseShape: bwForm "use bwForm as a mask to clear all pixels where bwForm has 1's" ((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. ! ! !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/17/2001 15:38'! 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: self 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'! 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' stamp: 'ar 5/17/2001 15:38'! 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 | self 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 5/14/2001 23:46'! 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 _ self pixelValueFor: aColor. 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: 'di 10/20/2001 10:09'! floodFillMapFrom: sourceForm to: scanlineForm mappingColorsWithin: dist to: centerPixVal "This is a helper routine for floodFill. It's written for clarity (scanning the entire map using colors) rather than speed (which would require hacking rgb components in the nieghborhood of centerPixVal. Note that some day a better proximity metric would be (h s v) where tolerance could be reduced in hue." | colorMap centerColor | scanlineForm depth = 32 ifFalse: [self error: 'depth 32 assumed']. "First get a modifiable identity map" colorMap _ (Color cachedColormapFrom: sourceForm depth to: scanlineForm depth) copy. centerColor _ Color colorFromPixelValue: (centerPixVal bitOr: 16rFFe6) depth: scanlineForm depth. "Now replace all entries that are close to the centerColor" 1 to: colorMap size do: [:i | ((Color colorFromPixelValue: ((colorMap at: i) bitOr: 16rFFe6) depth: scanlineForm depth) diff: centerColor) <= dist ifTrue: [colorMap at: i put: centerPixVal]]. ^ colorMap! ! !Form methodsFor: 'filling' stamp: 'di 10/20/2001 22:03'! floodFill: aColor at: interiorPoint Preferences areaFillsAreVeryTolerant ifTrue: [^ self floodFill: aColor at: interiorPoint tolerance: 0.2]. Preferences areaFillsAreTolerant ifTrue: [^ self floodFill: aColor at: interiorPoint tolerance: 0.1]. ^ self floodFill: aColor at: interiorPoint tolerance: 0 ! ! !Form methodsFor: 'filling' stamp: 'di 10/20/2001 08:47'! floodFill: aColor at: interiorPoint tolerance: tolerance "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 (ar): 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. Further note (di): I have added a feature that allows this routine to fill areas of approximately constant color (such as photos, scans, and jpegs). It does this by computing a color map for the peeker that maps all colors close to 'old' into colors identical to old. This mild colorblindness achieves the desired effect with no further change or degradation of the algorithm. tolerance should be 0 (exact match), or a value corresponding to those returned by Color>>diff:, with 0.1 being a reasonable starting choice." | 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. spanBits _ span bits. peeker _ BitBlt current toForm: span. peeker sourceForm: self; combinationRule: 3; width: width; height: 1. "read old pixel value" peeker sourceOrigin: interiorPoint; destOrigin: interiorPoint x @ 0; width: 1; copyBits. old _ spanBits at: interiorPoint x + 1. "compute new value (take care since the algorithm will fail if old = new)" new _ self privateFloodFillValue: aColor. old = new ifTrue: [^ box]. tolerance > 0 ifTrue: ["Set up color map for approximate fills" peeker colorMap: (self floodFillMapFrom: self to: span mappingColorsWithin: tolerance to: old)]. poker _ BitBlt current toForm: self. poker fillColor: aColor; combinationRule: 3; width: width; height: 1. stack _ OrderedCollection new: 50. 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 10/17/2001 10:10'! 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 | self 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." "bwForm _ self makeBWForm: interiorColor." "won't work for two whites" interiorPixVal _ self pixelValueAt: interiorPoint. bwForm _ Form extent: self extent. map _ Bitmap new: (1 bitShift: (self depth min: 12)). "Not calling newColorMap. All non-foreground go to 0. Length is 2 to 4096." ppd _ self 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: self 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 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" self eraseShape: bwForm. self fillShape: bwForm fillColor: aColor. ^ bwForm! ! !Form methodsFor: 'filling' stamp: 'ar 5/17/2001 15:38'! shapeFill: aColor seedBlock: seedBlock self 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: 'image manipulation' stamp: 'ar 5/17/2001 15:40'! replaceColor: oldColor withColor: newColor "Replace one color with another everywhere is this form" | cm newInd target ff | self depth = 32 ifTrue: [cm _ (Color cachedColormapFrom: 16 to: 32) copy] ifFalse: [cm _ Bitmap new: (1 bitShift: (self depth min: 15)). 1 to: cm size do: [:i | cm at: i put: i - 1]]. newInd _ newColor pixelValueForDepth: self depth. cm at: (oldColor pixelValueForDepth: (self 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: 'LB 8/26/2002 18:08'! stencil "return a 1-bit deep, black-and-white stencil of myself" | canvas | canvas _ FormCanvas extent: self extent depth: 1. canvas fillColor: (Color white). canvas stencil: self at: 0@0 sourceRect: (Rectangle origin: 0@0 corner: self extent) color: Color black. ^ canvas form ! ! !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: 'initialize-release' stamp: 'ar 5/17/2001 22:54'! allocateForm: extentPoint "Allocate a new form which is similar to the receiver and can be used for accelerated blts" ^Form extent: extentPoint depth: self nativeDepth! ! !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: 'initialize-release' stamp: 'ar 6/16/2002 18:39'! swapEndianness "Swap from big to little endian pixels and vice versa" depth := 0 - depth.! ! !Form methodsFor: 'other' stamp: 'ar 12/12/2003 18:24'! fixAlpha "Fix the alpha channel if the receiver is 32bit" | bb | self depth = 32 ifFalse:[^self]. bb := BitBlt toForm: self. bb combinationRule: 40 "fixAlpha:with:". bb copyBits.! ! !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: '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: 'other' stamp: 'RAA 1/30/2002 16:42'! relativeTextAnchorPosition ^nil "so forms can be in TextAnchors"! ! !Form methodsFor: 'other' stamp: 'dgd 8/26/2003 21:44'! 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' translated; 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: 'pixel access' stamp: 'ar 5/17/2001 15:42'! colorAt: aPoint "Return the color in the pixel at the given point. " ^ Color colorFromPixelValue: (self pixelValueAt: aPoint) depth: self depth ! ! !Form methodsFor: 'pixel access' stamp: 'ar 5/14/2001 23:46'! colorAt: aPoint put: aColor "Store a Color into the pixel at coordinate aPoint. " self pixelValueAt: aPoint put: (self pixelValueFor: aColor). "[Sensor anyButtonPressed] whileFalse: [Display colorAt: Sensor cursorPoint put: Color red]" ! ! !Form methodsFor: 'pixel access' stamp: 'ar 5/17/2001 15:39'! isTransparentAt: aPoint "Return true if the receiver is transparent at the given point." self depth = 1 ifTrue: [^ false]. "no transparency at depth 1" ^ (self pixelValueAt: aPoint) = (self pixelValueFor: Color transparent) ! ! !Form methodsFor: 'pixel access' 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: 'pixel access' 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: 'postscript generation' stamp: 'ar 5/17/2001 15:36'! bitsPerComponent ^self depth <= 8 ifTrue:[self 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' stamp: 'ar 5/17/2001 15:39'! decodeArray ^self depth <= 8 ifTrue:['[1 0]'] ifFalse:['[0 1 0 1 0 1 ]']. ! ! !Form methodsFor: 'postscript generation' stamp: 'ar 5/17/2001 15:43'! numComponents ^self 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: 'nk 12/31/2003 15:46'! 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 storePostscriptHexOn: 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: 'postscript generation' stamp: 'nk 12/31/2003 15:46'! storePostscriptHexOn: inner self depth <= 8 ifTrue: [self storeHexBitsOn: inner]. self depth = 16 ifTrue: [self store15To24HexBitsOn: inner]. self depth = 32 ifTrue: [self store32To24HexBitsOn: inner]! ! !Form methodsFor: 'resources' stamp: 'ar 12/9/2002 16:04'! readNativeResourceFrom: byteStream | img aStream | (byteStream isKindOf: FileStream) ifTrue:[ "Ugly, but ImageReadWriter will send #reset which is implemented as #reopen and we may not be able to do so." aStream _ RWBinaryOrTextStream with: byteStream contents. ] ifFalse:[ aStream _ byteStream. ]. img _ [ImageReadWriter formFromStream: aStream] on: Error do:[:ex| nil]. img ifNil:[^nil]. (img isColorForm and:[self isColorForm]) ifTrue:[ | cc | cc := img colors. img colors: nil. img displayOn: self. img colors: cc. ] ifFalse:[ img displayOn: self. ]. img _ nil.! ! !Form methodsFor: 'resources' stamp: 'nk 7/30/2004 17:53'! readResourceFrom: aStream "Store a resource representation of the receiver on aStream. Must be specific to the receiver so that no code is filed out." | bitsSize msb | (aStream next: 4) asString = self resourceTag ifFalse: [aStream position: aStream position - 4. ^self readNativeResourceFrom: aStream]. width := aStream nextNumber: 4. height := aStream nextNumber: 4. depth := aStream nextNumber: 4. bitsSize := aStream nextNumber: 4. bitsSize = 0 ifFalse: [bits := aStream next: bitsSize. ^self]. msb := (aStream nextNumber: 4) = 1. bitsSize := aStream nextNumber: 4. bits := Bitmap new: self bitsSize. (Form extent: width @ height depth: depth bits: (aStream next: bitsSize * 4)) displayOn: self. msb = SmalltalkImage current isBigEndian ifFalse: [Bitmap swapBytesIn: bits from: 1 to: bits size]! ! !Form methodsFor: 'resources' stamp: 'ar 2/27/2001 14:56'! resourceTag ^'FORM'! ! !Form methodsFor: 'resources' stamp: 'sd 9/30/2003 13:41'! storeResourceOn: aStream "Store a resource representation of the receiver on aStream. Must be specific to the receiver so that no code is filed out." self hibernate. aStream nextPutAll: self resourceTag asByteArray. "tag" aStream nextNumber: 4 put: width. aStream nextNumber: 4 put: height. aStream nextNumber: 4 put: depth. (bits isMemberOf: ByteArray) ifFalse:[ "must store bitmap" aStream nextNumber: 4 put: 0. "tag" aStream nextNumber: 4 put: (SmalltalkImage current isBigEndian ifTrue:[1] ifFalse:[0]). ]. aStream nextNumber: 4 put: bits size. aStream nextPutAll: bits. ! ! !Form methodsFor: 'scaling, rotation' stamp: 'ar 5/14/2001 23:33'! 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 colormapIfNeededFor: newForm); 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' stamp: 'tpr 9/28/2004 17:00'! magnifyBy: scale "Answer a Form created as a scaling of the receiver. Scale may be a Float or even a Point, 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'! 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: 'tpr 9/28/2004 17:00'! magnify: aRectangle by: scale smoothing: cellSize "Answer a Form created as a scaling of the receiver. Scale may be a Float or even a Point, and may be greater or less than 1.0." | newForm | newForm _ self blankCopyOf: aRectangle scaledBy: scale. (WarpBlt current toForm: newForm) sourceForm: self; colorMap: (self colormapIfNeededFor: newForm); 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'! 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: 'wiz 1/22/2006 01:15'! rotateBy: direction centerAt: aPoint "Return a rotated copy of the receiver. direction = #none, #right, #left, or #pi" | newForm quad rot scale | direction == #none ifTrue: [^ self]. scale := (direction = #pi ifTrue: [width@height] ifFalse: [height@width]) / self extent . newForm := self blankCopyOf: self boundingBox scaledBy: scale. quad _ self boundingBox innerCorners. rot _ #(right pi left) indexOf: direction. (WarpBlt current toForm: newForm) sourceForm: self; colorMap: (self colormapIfNeededFor: newForm); 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 3/1/2006 23:04'! rotateBy: deg magnify: scale smoothing: cellSize "Rotate the receiver by the indicated number of degrees and magnify. scale can be a Point to make for interesting 3D effects " "rot is the destination form, big enough for any angle." | side rot warp r1 pts p bigSide | side _ 1 + self extent r asInteger. bigSide _ (side asPoint * scale) rounded. rot := self blankCopyOf: self boundingBox scaledBy: ( bigSide / self extent ). warp _ (WarpBlt current toForm: rot) sourceForm: self; colorMap: (self colormapIfNeededFor: rot); 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@2 smoothing: 2) display]. f display "! ! !Form methodsFor: 'scaling, rotation' stamp: 'tpr 9/28/2004 16:55'! rotateBy: deg smoothing: cellSize "Rotate the receiver by the indicated number of degrees." ^self rotateBy: deg magnify: 1 smoothing: cellSize " | 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: 'testing' stamp: 'ar 5/15/2001 16:14'! hasNonStandardPalette "Return true if the receiver has a non-standard palette. Non-standard means that RGBA components may be located at positions differing from the standard Squeak RGBA layout at the receiver's depth." ^false! ! !Form methodsFor: 'testing' 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: 'testing' stamp: 'ar 5/17/2001 15:46'! isBigEndian "Return true if the receiver contains big endian pixels, meaning the left-most pixel is stored in the most significant bits of a word." ^depth > 0! ! !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: 'ar 10/30/2000 23:23'! isForm ^true! ! !Form methodsFor: 'testing' stamp: 'ar 5/17/2001 15:47'! isLittleEndian "Return true if the receiver contains little endian pixels, meaning the left-most pixel is stored in the least significant bits of a word." ^depth < 0! ! !Form methodsFor: 'testing' stamp: 'RAA 8/14/2000 10:00'! isStatic ^false! ! !Form methodsFor: 'testing' stamp: 'ar 2/10/2004 17:18'! isTranslucent "Answer whether this form may be translucent" ^self depth = 32! ! !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 methodsFor: 'transitions' stamp: 'ar 5/17/2001 15:42'! fadeImageCoarse: otherImage at: topLeft "Display fadeImageCoarse: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" | pix j d | d _ self depth. ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | i=1 ifTrue: [pix _ (1 bitShift: d) - 1. 1 to: 8//d-1 do: [:q | pix _ pix bitOr: (pix bitShift: d*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*d))]. "mask display." true] ifFalse: [false]]! ! !Form methodsFor: 'transitions' stamp: 'ar 5/17/2001 15:41'! fadeImageFine: otherImage at: topLeft "Display fadeImageFine: (Form fromDisplay: (40@40 extent: 300@300)) reverse at: 40@40" | pix j ii d | d _ self depth. ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | i=1 ifTrue: [pix _ (1 bitShift: d) - 1. 1 to: 8//d-1 do: [:q | pix _ pix bitOr: (pix bitShift: d*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*d))]. true] ifFalse: [false]]! ! !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'! 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'! 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' stamp: 'ar 5/17/2001 15:39'! fadeImageVert: otherImage at: topLeft "Display fadeImageVert: (Form fromDisplay: (10@10 extent: 300@300)) reverse at: 10@10" | d | d _ self depth. ^ self fadeImage: otherImage at: topLeft indexAndMaskDo: [:i :mask | mask fill: ((mask width//2//d-i*d)@0 extent: i*2*d@mask height) fillColor: Color black. i <= (mask width//d)]! ! !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' 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 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 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: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: '*eToys-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: '*MorphicExtras-other' stamp: 'sw 5/3/2001 16:23'! graphicForViewerTab "Answer the graphic to be used in the tab of a viewer open on me" ^ self! ! !Form methodsFor: '*MorphicExtras-postscript generation' stamp: 'RAA 4/20/2001 15:40'! encodePostscriptOn: aStream self unhibernate. "since current Postscript support treats 8-bit forms as 0 to 255 gray scale, convert to 16 first so we get more faithful results" self depth <= 8 ifTrue: [^(self asFormOfDepth: 16) encodePostscriptOn: aStream]. ^ self printPostscript: aStream operator: (self depth = 1 ifTrue: ['imagemask'] ifFalse: ['image'])! ! !Form methodsFor: '*nebraska-encoding' 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: '*nebraska-encoding' stamp: 'sd 11/20/2005 21:25'! 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: '*nebraska-encoding' stamp: 'sd 11/20/2005 21:25'! 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: '*nebraska-encoding' 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: '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: 'private' stamp: 'ar 10/30/2000 23:22'! setResourceBits: aForm "Private. Really. Used for setting the 'resource bits' when externalizing some form" bits _ aForm.! ! !Form methodsFor: '*Morphic' stamp: 'ar 7/8/2006 21:01'! iconOrThumbnailOfSize: aNumberOrPoint "Answer an appropiate form to represent the receiver" ^ self scaledIntoFormOfSize: aNumberOrPoint! ! !Form methodsFor: '*Morphic' stamp: 'ar 7/8/2006 21:01'! scaledIntoFormOfSize: aNumberOrPoint "Scale and center the receiver into a form of a given size" | extent scale scaledForm result | extent := aNumberOrPoint asPoint. extent = self extent ifTrue: [^ self]. (self height isZero or: [self width isZero]) ifTrue: [^ Form extent: extent depth: self depth]. scale := extent y / self height min: extent x / self width. scaledForm := self magnify: self boundingBox by: scale smoothing: 8. result := Form extent: extent depth: 32. result getCanvas translucentImage: scaledForm at: extent - scaledForm extent // 2. ^ result ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Form class instanceVariableNames: ''! !Form class methodsFor: 'BMP file reading' stamp: 'ar 6/16/2002 17:41'! fromBMPFile: aBinaryStream "Obsolete" ^self fromBinaryStream: aBinaryStream.! ! !Form class methodsFor: 'BMP file reading' stamp: 'ar 6/16/2002 17:41'! fromBMPFileNamed: fileName "Obsolete" ^self fromFileNamed: fileName ! ! !Form class methodsFor: 'class initialization' stamp: 'SD 11/15/2001 22:21'! unload FileList unregisterFileReader: self ! ! !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: 'file list services' stamp: 'nk 6/12/2004 12:56'! fileReaderServicesForDirectory: aFileDirectory ^{ self serviceImageImportDirectory. self serviceImageImportDirectoryWithSubdirectories. }! ! !Form class methodsFor: 'file list services' stamp: 'nk 7/16/2003 18:01'! fileReaderServicesForFile: fullName suffix: suffix ^((ImageReadWriter allTypicalFileExtensions add: '*'; add: 'form'; yourself) includes: suffix) ifTrue: [ self services ] ifFalse: [#()] ! ! !Form class methodsFor: 'file list services' stamp: 'hg 8/3/2000 16:26'! openAsBackground: fullName "Set an image as a background image. Support Squeak's common file format (GIF, JPG, PNG, 'Form stoteOn: (run coded)' and BMP)" (self fromFileNamed: fullName) setAsBackground! ! !Form class methodsFor: 'file list services' stamp: 'nk 1/6/2004 12:36'! openImageInWindow: fullName "Handle five file formats: GIF, JPG, PNG, Form storeOn: (run coded), and BMP. Fail if file format is not recognized." | image myStream | myStream _ (FileStream readOnlyFileNamed: fullName) binary. image _ self fromBinaryStream: myStream. myStream close. Smalltalk isMorphic ifTrue:[ Project current resourceManager addResource: image url: (FileDirectory urlForFileNamed: fullName) asString. ]. Smalltalk isMorphic ifTrue: [(World drawingClass withForm: image) openInWorld] ifFalse: [FormView open: image named: fullName]! ! !Form class methodsFor: 'file list services' stamp: 'sw 2/17/2002 01:38'! serviceImageAsBackground "Answer a service for setting the desktop background from a given graphical file's contents" ^ SimpleServiceEntry provider: self label: 'use graphic as background' selector: #openAsBackground: description: 'use the graphic as the background for the desktop' buttonLabel: 'background'! ! !Form class methodsFor: 'file list services' stamp: 'nk 6/12/2004 13:16'! serviceImageImportDirectory "Answer a service for reading a graphic into ImageImports" ^(SimpleServiceEntry provider: self label: 'import all images from this directory' selector: #importImageDirectory: description: 'Load all graphics found in this directory, adding them to the ImageImports repository.' buttonLabel: 'import dir') argumentGetter: [ :fileList | fileList directory ]; yourself ! ! !Form class methodsFor: 'file list services' stamp: 'nk 6/12/2004 13:15'! serviceImageImportDirectoryWithSubdirectories "Answer a service for reading all graphics from a directory and its subdirectories into ImageImports" ^(SimpleServiceEntry provider: self label: 'import all images from here and subdirectories' selector: #importImageDirectoryWithSubdirectories: description: 'Load all graphics found in this directory and its subdirectories, adding them to the ImageImports repository.' buttonLabel: 'import subdirs') argumentGetter: [ :fileList | fileList directory ]; yourself ! ! !Form class methodsFor: 'file list services' stamp: 'sw 2/17/2002 01:39'! serviceImageImports "Answer a service for reading a graphic into ImageImports" ^ SimpleServiceEntry provider: self label: 'read graphic into ImageImports' selector: #importImage: description: 'Load a graphic, placing it in the ImageImports repository.' buttonLabel: 'import'! ! !Form class methodsFor: 'file list services' stamp: 'sw 2/17/2002 00:31'! serviceOpenImageInWindow "Answer a service for opening a graphic in a window" ^ SimpleServiceEntry provider: self label: 'open graphic in a window' selector: #openImageInWindow: description: 'open a graphic file in a window' buttonLabel: 'open'! ! !Form class methodsFor: 'file list services' stamp: 'sd 2/1/2002 21:43'! services ^ Array with: self serviceImageImports with: self serviceOpenImageInWindow with: self serviceImageAsBackground ! ! !Form class methodsFor: 'fileIn/Out' stamp: 'nk 6/12/2004 12:47'! importImage: fullName "Import the given image file and store the resulting Form in the default Imports. The image is named with the short filename up to the first period, possibly with additions from the directory path to make it unique." Imports default importImageFromFileNamed: fullName. ! ! !Form class methodsFor: 'fileIn/Out' stamp: 'nk 6/12/2004 13:08'! importImageDirectory: dir "Import the given image file and store the resulting Form in the default Imports. The image is named with the short filename up to the first period, possibly with additions from the directory path to make it unique." Imports default importImageDirectory: dir ! ! !Form class methodsFor: 'fileIn/Out' stamp: 'nk 6/12/2004 12:55'! importImageDirectoryWithSubdirectories: dir "Import the given image file and store the resulting Form in the default Imports. The image is named with the short filename up to the first period, possibly with additions from the directory path to make it unique." Imports default importImageDirectoryWithSubdirectories: dir ! ! !Form class methodsFor: 'initialize-release' stamp: 'hg 8/3/2000 16:25'! initialize FileList registerFileReader: self! ! !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: 'nk 7/7/2003 18:19'! 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]. "Try for JPG, GIF, or PCX..." "Note: The following call closes the stream." ^ ImageReadWriter 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: 'mir 11/19/2001 14:13'! fromFileNamed: fileName "Read a Form or ColorForm from the given file." | file form | file _ (FileStream readOnlyFileNamed: fileName) binary. form _ self fromBinaryStream: file. Smalltalk isMorphic ifTrue:[ Project current resourceManager addResource: form url: (FileDirectory urlForFileNamed: file name) asString]. 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' stamp: 'ar 3/1/2006 22:50'! fromUser: gridPoint "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. Ensures that the returned form has positive extent." | rect | rect := Rectangle fromUser: gridPoint. ^ self fromDisplay: (rect origin extent: (rect extent max: gridPoint))! ! !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' stamp: 'hg 1/29/2001 17:28'! rgbMul "Answer the integer denoting 'Multiply each color component, their values regarded as fractions of 1' rule." ^ 37! ! !Form class methodsFor: 'mode constants'! under "Answer the integer denoting mode under." ^7! ! !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].! ! 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: '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 12/31/2001 03:26'! contentsOfArea: aRectangle into: aForm | bb | self flush. bb _ BitBlt toForm: aForm. bb sourceForm: form; combinationRule: Form over; sourceX: (aRectangle left + origin x); sourceY: (aRectangle top + origin y); width: aRectangle width; height: aRectangle height; copyBits. ^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: '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: '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 5/14/2001 23:34'! line: pt1 to: pt2 brushForm: brush | offset | offset _ origin. self setPaintColor: Color black. port sourceForm: brush; fillColor: nil; sourceRect: brush boundingBox; colorMap: (brush colormapIfNeededFor: form); 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: 'yo 1/23/2003 17:50'! paragraph3: para bounds: bounds color: c | scanner | self setPaintColor: c. scanner _ (port clippedBy: (bounds translateBy: origin)) displayScannerForMulti: para foreground: (self shadowColor ifNil:[c]) background: Color transparent ignoreColorChanges: self shadowColor notNil. para displayOnTest: (self copyClipRect: bounds) using: scanner at: origin+ bounds topLeft. ! ! !FormCanvas methodsFor: 'drawing' stamp: 'di 9/12/2001 21:38'! paragraph: para bounds: bounds color: c | scanner | self setPaintColor: c. scanner _ (port clippedBy: (bounds translateBy: origin)) displayScannerFor: para foreground: (self shadowColor ifNil:[c]) background: Color transparent ignoreColorChanges: self shadowColor notNil. para displayOn: (self copyClipRect: bounds) using: scanner at: origin+ bounds topLeft. ! ! !FormCanvas methodsFor: 'drawing'! point: pt color: c form colorAt: (pt + origin) put: c.! ! !FormCanvas methodsFor: 'drawing' stamp: 'ar 9/9/2000 22:18'! render: anObject "Do some 3D operations with the object if possible" ^self asBalloonCanvas render: anObject! ! !FormCanvas methodsFor: 'drawing-general' stamp: 'ar 12/30/2001 18:58'! roundCornersOf: aMorph in: bounds during: aBlock aMorph wantsRoundedCorners ifFalse:[^aBlock value]. (self seesNothingOutside: (CornerRounder rectWithinCornersOf: bounds)) ifTrue: ["Don't bother with corner logic if the region is inside them" ^ aBlock value]. CornerRounder roundCornersOf: aMorph on: self in: bounds displayBlock: aBlock borderWidth: aMorph borderWidthForRounding corners: aMorph roundedCorners! ! !FormCanvas methodsFor: 'drawing-images' stamp: 'tpr 9/15/2004 10:27'! stencil: stencilForm at: aPoint sourceRect: sourceRect color: aColor "Flood this canvas with aColor wherever stencilForm has non-zero pixels" self setPaintColor: aColor. port colorMap: stencilForm maskingMap. port stencil: stencilForm at: aPoint + origin sourceRect: sourceRect.! ! !FormCanvas methodsFor: 'drawing-images' stamp: 'ar 12/30/2001 16:36'! warpImage: aForm transform: aTransform at: extraOffset sourceRect: sourceRect cellSize: cellSize "Warp the given using the appropriate transform and offset." | tfm | tfm _ (MatrixTransform2x3 withOffset: origin) composedWithLocal: aTransform. ^self privateWarp: aForm transform: tfm at: extraOffset sourceRect: sourceRect cellSize: cellSize! ! !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: 'di 5/25/2001 01:40'! fillOval: r color: fillColor borderWidth: borderWidth borderColor: borderColor | rect | "draw the border of the oval" rect _ (r translateBy: origin) truncated. (borderWidth = 0 or: [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-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 2/6/2001 14:00'! infiniteFillRectangle: aRectangle fillStyle: aFillStyle | additionalOffset rInPortTerms clippedPort targetTopLeft clipOffset ex | "this is a bit of a kludge to get the form to be aligned where I *think* it should be. something better is needed, but not now" additionalOffset _ 0@0. ex _ aFillStyle form extent. rInPortTerms _ aRectangle translateBy: origin. clippedPort _ port clippedBy: rInPortTerms. targetTopLeft _ clippedPort clipRect topLeft truncateTo: ex. clipOffset _ rInPortTerms topLeft - targetTopLeft. additionalOffset _ (clipOffset \\ ex) - ex. ^aFillStyle displayOnPort: clippedPort offsetBy: additionalOffset ! ! !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: 'ar 10/18/2004 00:05'! 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) truncated. 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: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: 'drawing-text' stamp: 'ar 2/5/2002 19:03'! drawString: aString from: firstIndex to: lastIndex at: aPoint font: fontOrNil color: c | font | port colorMap: nil. font _ fontOrNil ifNil: [TextStyle defaultFont]. port combinationRule: Form paint. font installOn: port foregroundColor: (self shadowColor ifNil:[c]) backgroundColor: Color transparent. font displayString: aString on: port from: firstIndex to: lastIndex at: (origin + aPoint) kern: 0.! ! !FormCanvas methodsFor: 'drawing-text' stamp: 'ar 2/5/2002 19:03'! drawString: aString from: firstIndex to: lastIndex in: bounds font: fontOrNil color: c | font portRect | port colorMap: nil. portRect _ port clipRect. port clipByX1: bounds left + origin x y1: bounds top + origin y x2: bounds right + origin x y2: bounds bottom + origin y. font _ fontOrNil ifNil: [TextStyle defaultFont]. port combinationRule: Form paint. font installOn: port foregroundColor: (self shadowColor ifNil:[c]) backgroundColor: Color transparent. font displayString: aString asString on: port from: firstIndex to: lastIndex at: (bounds topLeft + origin) kern: 0. port clipRect: portRect.! ! !FormCanvas methodsFor: 'initialization' 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: 'initialization' 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: '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'! 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: 'printing' stamp: 'ar 5/28/2000 17:07'! printOn: aStream super printOn: aStream. aStream nextPutAll:' on: '; print: form.! ! !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: 'private' stamp: 'ar 5/14/2001 23:34'! 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 colorMap: (aForm colormapIfNeededFor: form); fillColor: nil. port image: aForm at: aPoint + origin sourceRect: sourceRect rule: rule.! ! !FormCanvas methodsFor: 'private' stamp: 'ar 8/8/2001 14:21'! image: aForm at: aPoint sourceRect: sourceRect rule: rule alpha: sourceAlpha "Draw the portion of the given Form defined by sourceRect at the given point using the given BitBlt combination rule." port colorMap: (aForm colormapIfNeededFor: form); fillColor: nil. port image: aForm at: aPoint + origin sourceRect: sourceRect rule: rule alpha: sourceAlpha.! ! !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: 'yo 6/18/2004 15:11'! privateWarp: aForm transform: aTransform at: extraOffset sourceRect: sourceRect cellSize: cellSize "Warp the given using the appropriate transform and offset." | globalRect sourceQuad warp tfm | tfm _ aTransform. globalRect _ tfm localBoundsToGlobal: sourceRect. sourceQuad _ (tfm sourceQuadFor: globalRect) collect:[:p| p - sourceRect topLeft]. extraOffset ifNotNil:[globalRect _ globalRect translateBy: extraOffset]. warp _ (WarpBlt current toForm: port destForm) combinationRule: Form paint; sourceQuad: sourceQuad destRect: (globalRect origin corner: globalRect corner+(1@1)); clipRect: port clipRect. warp cellSize: cellSize. warp sourceForm: aForm. warp warpBits! ! !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: 'tpr 9/15/2004 10:28'! setClearColor: aColor "Install a new clear color - e.g., a color is used for clearing the background" | clearColor | 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: (form balancedPatternFor: clearColor)]. ! ! !FormCanvas methodsFor: 'private' stamp: 'tpr 9/15/2004 10:28'! setFillColor: aColor "Install a new color used for filling." | screen patternWord fillColor | 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: (form balancedPatternFor: fillColor)]. ^self]. "fillColor is some translucent color" 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 _ form pixelWordFor: fillColor. 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: 'tpr 9/15/2004 10:28'! setPaintColor: aColor "Install a new color used for filling." | paintColor screen patternWord | 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: (form balancedPatternFor: paintColor)]. ^self]. "paintColor is translucent color" 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 _ form pixelWordFor: paintColor. port fillPattern: (screen collect: [:maskWord | maskWord bitAnd: patternWord]). port combinationRule: Form paint ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: 'nk 7/4/2003 10:11'! extent: extent depth: depth origin: aPoint clipRect: aRectangle ^ self new setForm: (Form extent: extent depth: depth); setOrigin: aPoint clipRect: aRectangle; yourself! ! !FormCanvas class methodsFor: 'instance creation' stamp: 'jm 8/2/97 13:54'! on: aForm ^ self new setForm: aForm ! ! !FormCanvas class methodsFor: 'testing' stamp: 'ar 12/31/2001 02:26'! 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 drawString: '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 12/31/2001 02:26'! 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 drawString: '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 12/31/2001 02:25'! 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 drawString: '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 hasUnsavedChanges' 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: 'BG 12/5/2003 22:40' 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. The form to be edited is stored in instance variable model. The instance variable form references the paint brush.! !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: 'cursor'! cursorPoint "Answer the mouse coordinate data gridded according to the receiver's grid." ^sensor cursorPoint grid: grid! ! !FormEditor methodsFor: 'editing tools' stamp: 'BG 12/5/2003 23:00'! block "Allow the user to fill a rectangle with the gray tone and mode currently selected." | rectangle originRect | originRect := (Sensor cursorPoint grid: grid) extent: 2 @ 2. rectangle := Cursor corner showWhile: [originRect newRectFrom: [:f | f origin corner: (Sensor cursorPoint grid: grid)]]. rectangle isNil ifFalse: [sensor waitNoButton. Display fill: (rectangle intersect: view insetDisplayBox) rule: mode fillColor: color. hasUnsavedChanges contents: true.]! ! !FormEditor methodsFor: 'editing tools' stamp: 'rbb 3/1/2005 11:21'! 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 := UIManager default 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 := UIManager default 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: 'BG 12/10/2003 16:21'! 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 drawForm | "sensor noButtonPressed ifTrue: [^self]." firstPoint _ self cursorPoint. secondPoint _ self rubberBandFrom: firstPoint until: [sensor noButtonPressed]. thirdPoint _ self rubberBandFrom: secondPoint until: [sensor redButtonPressed]. Display depth > 1 ifTrue: [self deleteRubberBandFrom: secondPoint to: thirdPoint. self deleteRubberBandFrom: firstPoint to: secondPoint]. curve _ CurveFitter new. curve firstPoint: firstPoint. curve secondPoint: secondPoint. curve thirdPoint: thirdPoint. drawForm := form asFormOfDepth: Display depth. Display depth > 1 ifTrue: [drawForm mapColor: Color white to: Color transparent; mapColor: Color black to: color]. curve form: drawForm. curve displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [mode ~= Form erase ifTrue: [Form paint] ifFalse: [mode]] ifFalse: [mode]) fillColor: (Display depth = 1 ifTrue: [color] ifFalse: [nil]). sensor waitNoButton. hasUnsavedChanges contents: true.! ! !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: 'CdG 10/17/2005 20:51'! 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 := UIManager default request: 'File name?' translated initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. form := Form fromFileNamed: fileName. tool := previousTool. ! ! !FormEditor methodsFor: 'editing tools' stamp: 'CdG 10/17/2005 20:52'! fileOutForm "Ask the user for a file name and save the current source form under that name. Does not change the tool." | fileName | fileName := UIManager default request: 'File name?' translated initialAnswer: 'Filename.form'. fileName isEmpty ifTrue: [^ self]. Cursor normal showWhile: [form writeOnFileNamed: fileName]. tool := previousTool. ! ! !FormEditor methodsFor: 'editing tools' stamp: 'BG 12/12/2003 15:51'! 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 drawForm | drawForm := form asFormOfDepth: Display depth. Display depth > 1 ifTrue: [drawForm mapColor: Color white to: Color transparent; mapColor: Color black to: color]. firstPoint _ self cursorPoint. endPoint _ self rubberBandFrom: firstPoint until: [sensor noButtonPressed]. endPoint isNil ifTrue: [^self]. Display depth > 1 ifTrue: [self deleteRubberBandFrom: firstPoint to: endPoint.]. (Line from: firstPoint to: endPoint withForm: drawForm) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: (Display depth > 1 ifTrue: [mode ~= Form erase ifTrue: [Form paint] ifFalse: [mode]] ifFalse: [mode]) fillColor: (Display depth = 1 ifTrue: [color] ifFalse: [nil]). hasUnsavedChanges contents: true.! ! !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: 'BG 12/10/2003 15:59'! repeatCopy "As long as the red button is pressed, copy the source form onto the display screen." | drawingWasChanged | drawingWasChanged := false. [sensor redButtonPressed] whileTrue: [(BitBlt current destForm: Display sourceForm: form halftoneForm: color combinationRule: (Display depth > 1 ifTrue: [mode ~= Form erase ifTrue: [Form paint] ifFalse: [mode]] ifFalse: [mode]) destOrigin: self cursorPoint sourceOrigin: 0@0 extent: form extent clipRect: view insetDisplayBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF); copyBits. drawingWasChanged := true. ]. drawingWasChanged ifTrue: [hasUnsavedChanges contents: true.]! ! !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' stamp: 'BG 2/25/2001 21:36'! 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: (unNormalizedColor := Display depth > 1 ifTrue: [Color fromUser] ifFalse: [aColor]). tool _ previousTool! ! !FormEditor methodsFor: 'editing tools' stamp: 'BG 12/10/2003 16:00'! singleCopy "If the red button is clicked, copy the source form onto the display screen." (BitBlt destForm: Display sourceForm: form halftoneForm: color combinationRule: (Display depth > 1 ifTrue: [mode ~= Form erase ifTrue: [Form paint] ifFalse: [mode]] ifFalse: [mode]) destOrigin: self cursorPoint sourceOrigin: 0@0 extent: form extent clipRect: view insetDisplayBox) colorMap: (Bitmap with: 0 with: 16rFFFFFFFF); copyBits. sensor waitNoButton. hasUnsavedChanges contents: true.! ! !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: '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: 'menu messages' stamp: 'BG 12/5/2003 22:59'! accept "The edited information should now be accepted by the view." view updateDisplay. view accept. hasUnsavedChanges contents: false.! ! !FormEditor methodsFor: 'menu messages' stamp: 'BG 12/5/2003 22:59'! cancel "The edited information should be forgotten by the view." view cancel. hasUnsavedChanges contents: false.! ! !FormEditor methodsFor: 'menu messages'! edit model edit! ! !FormEditor methodsFor: 'menu messages' stamp: 'CdG 10/17/2005 20:52'! fileOut | fileName | fileName := UIManager default request: 'File name?' translated 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: 'pluggable menus' stamp: 'sma 3/11/2000 15:08'! getPluggableYellowButtonMenu: shiftKeyState ^ YellowButtonMenu! ! !FormEditor methodsFor: 'window support' stamp: 'rbb 2/16/2005 16:49'! okToChange ^hasUnsavedChanges contents not ifFalse: [self confirm: 'This drawing was not saved.\Is it OK to close this window?' withCRs ] ifTrue: [true] ! ! !FormEditor methodsFor: 'private' stamp: 'BG 12/10/2003 17:02'! deleteRubberBandFrom: startPoint to: endPoint (Line from: startPoint to: endPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: (Display depth = 1 ifTrue: [Color black] ifFalse: [Color gray]).! ! !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' stamp: 'BG 12/10/2003 16:47'! 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 gray. (Line from: startPoint to: endPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: Color gray. previousEndPoint _ endPoint]]. (Line from: startPoint to: previousEndPoint withForm: form) displayOn: Display at: 0 @ 0 clippingBox: view insetDisplayBox rule: Form reverse fillColor: (Display depth = 1 ifTrue: [Color gray] ifFalse: [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' stamp: 'BG 12/5/2003 22:58'! 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. hasUnsavedChanges := ValueHolder new contents: false. ! ! !FormEditor methodsFor: 'private' stamp: 'BG 12/12/2003 15:50'! trackFormUntil: aBlock | previousPoint cursorPoint displayForm | previousPoint _ self cursorPoint. displayForm := Form extent: form extent depth: form depth. displayForm copy: (0 @ 0 extent: form extent) from: form to: 0 @ 0 rule: Form over. Display depth > 1 ifTrue: [displayForm reverse]. displayForm displayOn: Display at: previousPoint rule: Form reverse. [aBlock value] whileFalse: [cursorPoint _ self cursorPoint. (FlashCursor or: [cursorPoint ~= previousPoint]) ifTrue: [displayForm displayOn: Display at: previousPoint rule: Form reverse. displayForm displayOn: Display at: cursorPoint rule: Form reverse. previousPoint _ cursorPoint]]. displayForm displayOn: Display at: previousPoint rule: Form reverse. ^previousPoint! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! 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: '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' stamp: 'BG 12/5/2003 22:39'! newForm "Create an instance of me on a new form at a location designated by the user. " (Form extent: 400 @ 200 depth: Display depth) fillWhite; edit "FormEditor newForm"! ! !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: '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: 'BG 12/5/2003 23:18'! 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 _ "ColorSystemView" FormEditorView new. topView model: formEditor. 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: '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: 'initialize-release'! release super release. displayedForm release. displayedForm _ nil! ! !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! ! !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! ! 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! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FormInspectView class instanceVariableNames: ''! !FormInspectView class methodsFor: 'instance creation' stamp: 'sd 5/11/2003 21:36'! openOn: aFormDictionary withLabel: aLabel "open a graphical dictionary in a window having the label aLabel. aFormDictionary should be a dictionary containing as value a form." ^ DictionaryInspector openOn: aFormDictionary withEvalPane: true withLabel: aLabel valueViewClass: self! ! 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: 'controller access'! defaultControllerClass "Refer to the comment in View|defaultControllerClass." ^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: 'private' stamp: 'BG 12/4/2003 14:22'! makeButton: index | buttonCache button | buttonCache _ (FormButtons at: index) shallowCopy. buttonCache form: (FormButtons at: index) form copy. button _ Button newOff. button onAction: [model changeTool: buttonCache value]. self makeViews: buttonCache for: button. ! ! !FormMenuView methodsFor: 'private' stamp: 'BG 12/4/2003 14:23'! makeColorConnections: indexInterval | connector buttonCache button aSwitchView | connector _ Object new. "a dummy model for connecting dependents" indexInterval do: [:index | buttonCache _ (FormButtons at: index) shallowCopy. buttonCache form: (FormButtons at: index) form copy. 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: 'BG 12/4/2003 14:23'! makeConnections: indexInterval | connector buttonCache button aSwitchView | connector _ Object new. "a dummy model for connecting dependents." indexInterval do: [:index | buttonCache _ (FormButtons at: index) shallowCopy. buttonCache form: (FormButtons at: index) form copy. 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: 'BG 12/4/2003 15:24'! makeGridSwitch: index | buttonCache button | buttonCache _ FormButtons at: index. buttonCache form: (FormButtons at: index) form copy. 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: 'BG 12/4/2003 14:23'! makeSwitch: index | buttonCache button | buttonCache _ (FormButtons at: index) shallowCopy. buttonCache form: (FormButtons at: index) form copy. 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: 'accessing' stamp: 'BG 12/4/2003 12:11'! formButtons ^FormButtons! ! !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: 'gk 2/28/2005 16:38'! 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-Fonts'! !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! ! Form subclass: #FormStub instanceVariableNames: 'locator' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Display Objects'! !FormStub methodsFor: 'accessing' stamp: 'ar 2/24/2001 22:02'! locator ^locator! ! !FormStub methodsFor: 'accessing' stamp: 'ar 2/24/2001 22:02'! locator: aString locator _ aString! ! !FormStub methodsFor: 'fileIn/Out' stamp: 'ar 2/27/2001 21:36'! objectForDataStream: refStream "Force me into outPointers so that I get notified about startup" refStream replace: self with: self. ^self! ! 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: '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: '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: '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: 'updating'! update: aFormView "Refer to the comment in View|update:." self == aFormView ifFalse: [self display]! ! !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: '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: 'BG 12/5/2003 14:45'! 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: aForm extent; maximumSize: aForm extent. 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! Fraction provides methods for dealing with fractions like 1/3 as fractions (not as 0.33333...). All public arithmetic operations answer reduced fractions (see examples). instance variables: 'numerator denominator ' Examples: (note the parentheses required to get the right answers in Smalltalk and Squeak): (2/3) + (2/3) (2/3) + (1/2) "answers shows the reduced fraction" (2/3) raisedToInteger: 5 "fractions also can have exponents" ! !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' stamp: 'RAH 4/25/2000 19:49'! reciprocal "Refer to the comment in Number|reciprocal." #Numeric. "Changed 200/01/19 For ANSI support." numerator = 0 ifTrue: [^ (ZeroDivide dividend: self) signal"<- Chg"]. 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: # 0 ifTrue: [b := b bitShift: exponent] ifFalse: [a := a bitShift: exponent negated]. q := a quo: b. r := a - (q * b). hq := q highBit. "check for gradual underflow, in which case we should use less bits" floatExponent := exponent + hq - 1. n := floatExponent > -1023 ifTrue: [54] ifFalse: [54 + floatExponent + 1022]. hq > n ifTrue: [exponent := exponent + hq - n. r := (q bitAnd: (1 bitShift: n - hq) - 1) * b + r. q := q bitShift: n - hq]. hq < n ifTrue: [exponent := exponent + hq - n. q1 := (r bitShift: n - hq) quo: b. q := (q bitShift: n - hq) bitAnd: q1. r := r - (q1 * b)]. "check if we should round upward. The case of exact half (q bitAnd: 1) isZero not & (r isZero) will be handled by Integer>>asFloat" ((q bitAnd: 1) isZero or: [r isZero]) ifFalse: [q := q + 1]. ^ (self positive ifTrue: [q asFloat] ifFalse: [q asFloat negated]) timesTwoPower: exponent! ! !Fraction methodsFor: 'converting'! asFraction "Answer the receiver itself." ^self! ! !Fraction methodsFor: 'converting'! isFraction ^ true! ! !Fraction methodsFor: 'mathematical functions' stamp: 'LC 4/22/1998 14:03'! raisedToInteger: anInteger "See Number | raisedToInteger:" anInteger = 0 ifTrue: [^ 1]. anInteger < 0 ifTrue: [^ self reciprocal raisedToInteger: anInteger negated]. ^ Fraction numerator: (numerator raisedToInteger: anInteger) denominator: (denominator raisedToInteger: anInteger)! ! !Fraction methodsFor: 'mathematical functions' stamp: 'LC 4/22/1998 14:05'! squared "See Fraction (Number) | squared" ^ Fraction numerator: numerator squared denominator: denominator squared! ! !Fraction methodsFor: 'printing'! printOn: aStream aStream nextPut: $(. numerator printOn: aStream. aStream nextPut: $/. denominator printOn: aStream. aStream nextPut: $). ! ! !Fraction methodsFor: 'printing' stamp: 'laza 3/29/2004 12:56'! printOn: aStream base: base aStream nextPut: $(. numerator printOn: aStream base: base. aStream nextPut: $/. denominator printOn: aStream base: base. aStream nextPut: $). ! ! !Fraction methodsFor: 'printing' stamp: 'laza 3/29/2004 13:25'! storeOn: aStream base: base aStream nextPut: $(. numerator storeOn: aStream base: base. aStream nextPut: $/. denominator storeOn: aStream base: base. aStream nextPut: $). ! ! !Fraction methodsFor: 'truncation and round off'! truncated "Refer to the comment in Number|truncated." ^numerator quo: denominator! ! !Fraction methodsFor: 'private'! denominator ^denominator! ! !Fraction methodsFor: 'private'! numerator ^numerator! ! !Fraction methodsFor: 'private'! reduced | gcd numer denom | numerator = 0 ifTrue: [^0]. gcd _ numerator gcd: denominator. numer _ numerator // gcd. denom _ denominator // gcd. denom = 1 ifTrue: [^numer]. ^Fraction numerator: numer denominator: denom! ! !Fraction methodsFor: 'private' stamp: 'tfei 4/12/1999 12:45'! setNumerator: n denominator: d d = 0 ifTrue: [^(ZeroDivide dividend: n) signal] ifFalse: [numerator _ n asInteger. denominator _ d asInteger abs. "keep sign in numerator" d < 0 ifTrue: [numerator _ numerator negated]]! ! !Fraction methodsFor: 'self evaluating' stamp: 'apb 4/20/2006 18:41'! isSelfEvaluating ^ true! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Fraction class instanceVariableNames: ''! !Fraction class methodsFor: 'constants' stamp: 'RAH 4/25/2000 19:49'! one #Numeric. "add 200/01/19 For protocol support." ^ self numerator: 1 denominator: 1! ! !Fraction class methodsFor: 'instance creation' stamp: 'di 8/31/1999 10:16'! numerator: numInteger denominator: denInteger "Answer an instance of me (numInteger/denInteger). NOTE: This primitive initialization method will not reduce improper fractions, so normal usage should be coded as, eg, (Fraction numerator: a denominator: b) reduced or, more simply, as a / b." ^self new setNumerator: numInteger denominator: denInteger! ! ClassTestCase subclass: #FractionTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'KernelTests-Numbers'! !FractionTest methodsFor: 'tests - printing' stamp: 'laza 3/30/2004 09:28'! testFractionPrinting self assert: (353/359) printString = '(353/359)'. self assert: ((2/3) printStringBase: 2) = '(10/11)'. self assert: ((2/3) storeStringBase: 2) = '(2r10/2r11)'. self assert: ((5/7) printStringBase: 3) = '(12/21)'. self assert: ((5/7) storeStringBase: 3) = '(3r12/3r21)'. self assert: ((11/13) printStringBase: 4) = '(23/31)'. self assert: ((11/13) storeStringBase: 4) = '(4r23/4r31)'. self assert: ((17/19) printStringBase: 5) = '(32/34)'. self assert: ((17/19) storeStringBase: 5) = '(5r32/5r34)'. self assert: ((23/29) printStringBase: 6) = '(35/45)'. self assert: ((23/29) storeStringBase: 6) = '(6r35/6r45)'. self assert: ((31/37) printStringBase: 7) = '(43/52)'. self assert: ((31/37) storeStringBase: 7) = '(7r43/7r52)'. self assert: ((41/43) printStringBase: 8) = '(51/53)'. self assert: ((41/43) storeStringBase: 8) = '(8r51/8r53)'. self assert: ((47/53) printStringBase: 9) = '(52/58)'. self assert: ((47/53) storeStringBase: 9) = '(9r52/9r58)'. self assert: ((59/61) printStringBase: 10) = '(59/61)'. self assert: ((59/61) storeStringBase: 10) = '(59/61)'. self assert: ((67/71) printStringBase: 11) = '(61/65)'. self assert: ((67/71) storeStringBase: 11) = '(11r61/11r65)'. self assert: ((73/79) printStringBase: 12) = '(61/67)'. self assert: ((73/79) storeStringBase: 12) = '(12r61/12r67)'. self assert: ((83/89) printStringBase: 13) = '(65/6B)'. self assert: ((83/89) storeStringBase: 13) = '(13r65/13r6B)'. self assert: ((97/101) printStringBase: 14) = '(6D/73)'. self assert: ((97/101) storeStringBase: 14) = '(14r6D/14r73)'. self assert: ((103/107) printStringBase: 15) = '(6D/72)'. self assert: ((103/107) storeStringBase: 15) = '(15r6D/15r72)'. self assert: ((109/113) printStringBase: 16) = '(6D/71)'. self assert: ((109/113) storeStringBase: 16) = '(16r6D/16r71)'. self assert: ((127/131) printStringBase: 17) = '(78/7C)'. self assert: ((127/131) storeStringBase: 17) = '(17r78/17r7C)'. self assert: ((137/139) printStringBase: 18) = '(7B/7D)'. self assert: ((137/139) storeStringBase: 18) = '(18r7B/18r7D)'. self assert: ((149/151) printStringBase: 19) = '(7G/7I)'. self assert: ((149/151) storeStringBase: 19) = '(19r7G/19r7I)'. self assert: ((157/163) printStringBase: 20) = '(7H/83)'. self assert: ((157/163) storeStringBase: 20) = '(20r7H/20r83)'. self assert: ((167/173) printStringBase: 21) = '(7K/85)'. self assert: ((167/173) storeStringBase: 21) = '(21r7K/21r85)'. self assert: ((179/181) printStringBase: 22) = '(83/85)'. self assert: ((179/181) storeStringBase: 22) = '(22r83/22r85)'. self assert: ((191/193) printStringBase: 23) = '(87/89)'. self assert: ((191/193) storeStringBase: 23) = '(23r87/23r89)'. self assert: ((197/199) printStringBase: 24) = '(85/87)'. self assert: ((197/199) storeStringBase: 24) = '(24r85/24r87)'. self assert: ((211/223) printStringBase: 25) = '(8B/8N)'. self assert: ((211/223) storeStringBase: 25) = '(25r8B/25r8N)'. self assert: ((227/229) printStringBase: 26) = '(8J/8L)'. self assert: ((227/229) storeStringBase: 26) = '(26r8J/26r8L)'. self assert: ((233/239) printStringBase: 27) = '(8H/8N)'. self assert: ((233/239) storeStringBase: 27) = '(27r8H/27r8N)'. self assert: ((241/251) printStringBase: 28) = '(8H/8R)'. self assert: ((241/251) storeStringBase: 28) = '(28r8H/28r8R)'. self assert: ((257/263) printStringBase: 29) = '(8P/92)'. self assert: ((257/263) storeStringBase: 29) = '(29r8P/29r92)'. self assert: ((269/271) printStringBase: 30) = '(8T/91)'. self assert: ((269/271) storeStringBase: 30) = '(30r8T/30r91)'. self assert: ((277/281) printStringBase: 31) = '(8T/92)'. self assert: ((277/281) storeStringBase: 31) = '(31r8T/31r92)'. self assert: ((283/293) printStringBase: 32) = '(8R/95)'. self assert: ((283/293) storeStringBase: 32) = '(32r8R/32r95)'. self assert: ((307/311) printStringBase: 33) = '(9A/9E)'. self assert: ((307/311) storeStringBase: 33) = '(33r9A/33r9E)'. self assert: ((313/317) printStringBase: 34) = '(97/9B)'. self assert: ((313/317) storeStringBase: 34) = '(34r97/34r9B)'. self assert: ((331/337) printStringBase: 35) = '(9G/9M)'. self assert: ((331/337) storeStringBase: 35) = '(35r9G/35r9M)'. self assert: ((347/349) printStringBase: 36) = '(9N/9P)'. self assert: ((347/349) storeStringBase: 36) = '(36r9N/36r9P)'. self assert: ((-2/3) printStringBase: 2) = '(-10/11)'. self assert: ((-2/3) storeStringBase: 2) = '(-2r10/2r11)'. self assert: ((5/-7) printStringBase: 3) = '(-12/21)'. self assert: ((5/-7) storeStringBase: 3) = '(-3r12/3r21)'. ! ! !FractionTest methodsFor: 'tests - sinuses' stamp: 'sd 3/4/2004 21:13'! testDegreeCos "self run: #testDegreeCos" self shouldnt: [ (4/3) degreeCos] raise: Error. self assert: (1/3) degreeCos printString = '0.999983076857744'! ! !FractionTest methodsFor: 'tests - sinuses' stamp: 'sd 3/5/2004 14:54'! testDegreeSin "self run: #testDegreeSin" self shouldnt: [ (4/3) degreeSin] raise: Error. self assert: (1/3) degreeSin printString = '0.005817731354993834'.! ! StringMorph subclass: #FrameRateMorph instanceVariableNames: 'lastDisplayTime framesSinceLastDisplay' classVariableNames: '' poolDictionaries: '' category: 'MorphicExtras-Demo'! !FrameRateMorph methodsFor: 'initialization' stamp: 'dgd 2/14/2003 20:42'! initialize "initialize the state of the receiver" super initialize. "" lastDisplayTime _ 0. framesSinceLastDisplay _ 0! ! !FrameRateMorph methodsFor: 'parts bin' stamp: 'sw 7/19/2001 13:39'! initializeToStandAlone "Initialize the receiver as a stand-alone entity" super initializeToStandAlone. self color: Color blue. self step! ! !FrameRateMorph methodsFor: 'stepping and presenter' stamp: 'sw 10/5/2000 06:52'! step "Compute and display (every half second or so) the current framerate" | now mSecs mSecsPerFrame framesPerSec newContents | framesSinceLastDisplay _ framesSinceLastDisplay + 1. now _ Time millisecondClockValue. mSecs _ now - lastDisplayTime. (mSecs > 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: 'testing' stamp: 'jm 2/23/98 18:41'! stepTime "Answer the desired time between steps in milliseconds." ^ 0 ! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! FrameRateMorph class instanceVariableNames: ''! !FrameRateMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 13:05'! initialize self registerInFlapsRegistry. ! ! !FrameRateMorph class methodsFor: 'class initialization' stamp: 'asm 4/10/2003 13:06'! registerInFlapsRegistry "Register the receiver in the system's flaps registry" self environment at: #Flaps ifPresent: [:cl | cl registerQuad: #(FrameRateMorph authoringPrototype 'Frame Rate' 'An indicator of how fast your system is running') forFlapNamed: 'Widgets']! ! !FrameRateMorph class methodsFor: 'class initialization' stamp: 'asm 4/11/2003 12:36'! unload "Unload the receiver from global registries" self environment at: #Flaps ifPresent: [:cl | cl unregisterQuadsWithReceiver: self] ! ! !FrameRateMorph class methodsFor: 'parts bin' stamp: 'sw 8/2/2001 12:49'! descriptionForPartsBin ^ self partName: 'FrameRate' categories: #('Useful') documentation: 'A readout that allows you to monitor the frame rate of your system'! ! !FrameRateMorph class methodsFor: 'scripting' stamp: 'sw 6/13/2001 00:57'! authoringPrototype "Answer a morph representing a prototypical instance of the receiver" | aMorph | aMorph _ self new. aMorph color: Color blue. aMorph step. ^ aMorph! ! HierarchicalUrl subclass: #FtpUrl instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Network-Url'! !FtpUrl commentStamp: 'ls 6/15/2003 13:44' prior: 0! a reference to a file which may be downloaded by anonymous ftp . TODO: use the username and password, if specified ! !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 ] ]! ! !FtpUrl methodsFor: 'downloading' stamp: 'mir 8/5/2004 11:55'! downloadUrl "Returns a http download url for the location defined by this url." | ans | ans _ WriteStream on: String new. ans nextPutAll: self schemeName. ans nextPutAll: '://'. ans nextPutAll: self authority. port ifNotNil: [ans nextPut: $:; print: port]. path do: [ :pathElem | ans nextPut: $/. ans nextPutAll: pathElem encodeForHTTP. ]. self query isNil ifFalse: [ ans nextPut: $?. ans nextPutAll: self query. ]. self fragment isNil ifFalse: [ ans nextPut: $#. ans nextPutAll: self fragment encodeForHTTP. ]. ^ans contents! ! !FtpUrl methodsFor: 'downloading' stamp: 'mir 6/27/2003 19:42'! retrieveContents "currently assumes directories end in /, and things that don't end in / are files. Also, doesn't handle errors real well...." | server contents pathString listing auth idx fileName serverName userName password | pathString _ self pathString. pathString _ pathString copyFrom: 2 to: pathString size. "remove the leading /" pathString last = $/ ifTrue:["directory?!!" fileName _ nil. ] ifFalse:[ fileName _ pathString copyFrom: (pathString lastIndexOf: $/)+1 to: pathString size. pathString _ pathString copyFrom: 1 to: (pathString lastIndexOf: $/) - 1. ]. auth _ self authority. idx _ auth indexOf: $@. idx > 0 ifTrue:[ serverName _ (auth copyFrom: idx+1 to: auth size). userName _ (auth copyFrom: 1 to: idx-1). password _ nil. ] ifFalse:[ serverName _ auth. userName _ 'anonymous'. password _ 'SqueakUser'. ]. server _ ServerDirectory servers detect:[:s| s isTypeFTP and:[s server asLowercase = serverName asLowercase]] ifNone:[nil]. server ifNil:[ server _ ServerDirectory new. server server: serverName. ] ifNotNil:[server _ server copy reset]. server user: userName. password ifNotNil:[server password: password]. server directory: pathString. fileName == nil ifFalse:[ "a file" contents _ (server getFileNamed: fileName). server sleep. ^MIMEDocument contentType: (MIMEDocument guessTypeFromName: self path last) content: contents]. "a directory?" listing _ String streamContents: [ :stream | stream nextPutAll: '', self pathString, ''; cr. stream nextPutAll: '

Listing for ', self pathString, '

'; cr. stream nextPutAll: '