TextCollector.st
author claus
Mon, 06 Mar 1995 20:29:54 +0100
changeset 97 cbf495fe3b64
parent 80 5a878a1eebf2
child 110 eb59f6e31e84
permissions -rw-r--r--
*** empty log message ***

"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

EditTextView subclass:#TextCollector
       instanceVariableNames:'entryStream lineLimit destroyAction
			      outstandingLines outstandingLine
			      flushBlock flushPending collecting timeDelay access'
       classVariableNames:''
       poolDictionaries:''
       category:'Views-Text'
!

TextCollector comment:'
COPYRIGHT (c) 1989 by Claus Gittinger
	      All Rights Reserved

$Header: /cvs/stx/stx/libwidg/TextCollector.st,v 1.15 1995-03-06 19:29:31 claus Exp $
'!

!TextCollector class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

version
"
$Header: /cvs/stx/stx/libwidg/TextCollector.st,v 1.15 1995-03-06 19:29:31 claus Exp $
"
!

documentation
"
    a view for editable text, which also understands some stream messages.
    Instances of this view can take the place of a stream and display the 
    received text.
    Its main use in the system is the Transcript, but it can also be used for
    things like trace-windows etc.

    If collecting is turned on, a textcollector will not immediately display 
    entered text, but wait for some short time (timeDelay) and collect incoming 
    data - finally updating the whole chunk in one piece. 
    This helps slow display devices, which would otherwise scroll a lot. 
    (on fast displays this is less of a problem).

    The total number of lines kept is controlled by lineLimit, if more lines 
    than this limit are added at the bottom, the textcollector will forget lines 
    at the top. 
    You can set linelimit to nil (i.e. no limit), but you may need a lot 
    of memory then ...

    StyleSheet paramters:

	transcriptForegroundColor       defaults to textForegroundColor
	transcriptBackgroundColor'      defaults to textBackgroundColor.

	transcriptCursorForegroundColor
	transcriptCursorBackgroundColor
"
! !

!TextCollector class methodsFor:'defaults'!

defaultLineLimit
    "the number of lines remembered by default"

    ^ 600
! 

defaultTranscriptSize
    "the number of cols/lines by which the Transcript should come up"

    ^ 70@11
! 

defaultTimeDelay
    "the time in seconds to wait & collect by default"

    ^ 0.3 
! !

!TextCollector class methodsFor:'instance creation'!

newTranscript
    |topView transcript f v fg bg cFg cBg lines cols|

    topView := StandardSystemView label:'Transcript' minExtent:(100 @ 100).

    v := HVScrollableView for:self miniScrollerH:true miniScrollerV:false in:topView.
    v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
    transcript := v scrolledView.
    "transcript partialLines:false."

    f := transcript font.

    "
     should add the height of the frame & scrollbars to be exact ...
    "
    cols := self defaultTranscriptSize x.
    lines := self defaultTranscriptSize y.
    topView extent:(((f widthOf:'x') * cols) @ (f height * lines)).

    Smalltalk at:#Transcript put:transcript.

    "
     fancy feature: whenever Transcript is closed, reset to StdError
    "
    transcript destroyAction:[Smalltalk at:#Transcript put:Stderr].

    fg := StyleSheet colorAt:'transcriptForegroundColor' default:transcript foregroundColor.
    bg := StyleSheet colorAt:'transcriptBackgroundColor' default:transcript backgroundColor.
    transcript foregroundColor:fg backgroundColor:bg.
    transcript viewBackground:bg.

    cFg := StyleSheet colorAt:'transcriptCursorForegroundColor' default:bg.
    cBg := StyleSheet colorAt:'transcriptCursorBackgroundColor' default:fg.
    transcript cursorForegroundColor:cFg backgroundColor:cBg. 

    "
     run it at a slightly higher prio, to allow for
     delayed buffered updates to be performed
    "
    topView openWithPriority:(Processor userSchedulingPriority + 1).
    "transcript lineLimit:1000. " "or whatever you think makes sense"

    ^ transcript
! !

!TextCollector methodsFor:'initialize / release'!

initialize
    super initialize.

    outstandingLines := OrderedCollection new.

    flushBlock := [self endEntry].
    flushPending := false.
    collecting := true.
    timeDelay := self class defaultTimeDelay.
    access := Semaphore forMutualExclusion.

    lineLimit := self class defaultLineLimit.
    entryStream := ActorStream new.
    entryStream nextPutBlock:[:something | self nextPut:something].
    entryStream nextPutAllBlock:[:something | self nextPutAll:something]
!

editMenu
    |m idx|

    m := super editMenu.

    "
     textcollectors do not support #accept
     remove it from the menu (and the preceeding separating line)
    "
    idx := m indexOf:#accept.
    idx ~~ 0 ifTrue:[
	m remove:idx.
	(m labels at:(idx - 1)) = '-' ifTrue:[
	    m remove:idx - 1
	].
    ].
    ^ m
!

reinitialize
    "recreate access-semaphore; image could have been save (theoretically)
     with the semaphore locked - int this case, we had a deadlock"

    flushPending := false.
    access := Semaphore forMutualExclusion.
    super reinitialize.
!

mapped
    "view became visible - show collected lines (if any)"

    super mapped.
    self endEntry
!

destroy
    destroyAction notNil ifTrue:[
	destroyAction value
    ].
    Processor removeTimedBlock:flushBlock.
    flushBlock := nil.
    outstandingLines := OrderedCollection new.
    outstandingLine := ''.
    super destroy
! !

!TextCollector methodsFor:'accessing'!

collect:aBoolean
    "turn on/off collecting - if on, do not output immediately
     but collect text and output en-bloque after some time delta"

    collecting := aBoolean
!

lineLimit:aNumber
    "define the number of text-lines I am supposed to hold"

    lineLimit := aNumber
!

destroyAction:aBlock
    "define the action to be performed when I get destroyed.
     This is a special feature, to allow resetting Transcript to Stderr
     when closed. (see TextCollectorclass>>newTranscript)"

    destroyAction := aBlock
!

endEntry
    "flush collected output"

    |nLines lines|

    shown ifFalse:[^ self].

    Processor removeTimedBlock:flushBlock.
    flushPending ifFalse:[^ self].

    access wouldBlock ifTrue:[
	Processor activeProcessIsSystemProcess ifTrue:[
	    "/ Stderr nextPutAll:'Blocking in Transcript avoided: ' , aString.
	    ^ self
	]
    ].

    access critical:[
	flushPending := false.
	outstandingLines size ~~ 0 ifTrue:[
	    "insert the bunch of lines - if any"
	    lines := outstandingLines.
	    outstandingLines := OrderedCollection new.

	    nLines := lines size.
	    (nLines ~~ 0) ifTrue:[
		self insertLines:lines withCr:true.
		self withCursorOffDo:[
		    (cursorLine >= (firstLineShown + nFullLinesShown)) ifTrue:[
			self scrollDown:nLines
		    ]
		].
	    ].
	].
	"and the last partial line - if any"
	outstandingLine notNil ifTrue:[
	    self insertStringAtCursor:outstandingLine.
	    outstandingLine := ''.
	].
	self checkLineLimit
    ]
! !

!TextCollector methodsFor:'private'!

checkLineLimit
    "this method checks if the text has become too large (> lineLimit)
     and cuts off some lines at the top if so; it must be called whenever lines
     have been added to the bottom"

    |nDel|

    lineLimit notNil ifTrue:[
	(cursorLine > lineLimit) ifTrue:[
	    nDel := list size - lineLimit.
	    list removeFromIndex:1 toIndex:nDel.
	    cursorLine := cursorLine - nDel.
	    firstLineShown := firstLineShown - nDel.
	    (firstLineShown < 1) ifTrue:[
		cursorLine := cursorLine - firstLineShown + 1.
		firstLineShown := 1
	    ].
	    self contentsChanged
	]
    ]
!

installDelayedUpdate
    "arrange for collecting input for some time,
     and output all buffered strings at once after a while.
     This makes output to the transcript much faster on systems
     with poor scrolling performance (i.e. dump vga cards ...)."

    |wg p|

    flushPending ifFalse:[
	flushPending := true.
	"
	 we could run under a process, which dies in the meantime;
	 therefore, we have to arrange for the transcript process to
	 be interrupted and do the update.
	"
	wg := self windowGroup.
	wg isNil ifTrue:[
	    p := Processor activeProcess
	] ifFalse:[
	    p := wg process
	].
	Processor addTimedBlock:flushBlock for:p afterSeconds:timeDelay.
    ]
! !

!TextCollector methodsFor:'stream messages'!

lineLength
    ^ width // (font width)
!

nextPut:something
    "this allows TextCollectors to be used Stream-wise"

    flushPending ifTrue:[
	self endEntry
    ].
    (something isMemberOf:Character) ifTrue:[
	((something == Character cr) or:[something == Character nl]) ifTrue:[
	    ^ self cr
	].
	self insertCharAtCursor:something
    ] ifFalse:[
	self insertStringAtCursor:(something printString).
	self checkLineLimit
    ].
    device synchronizeOutput
!

nextPutAll:something
    "this allows TextCollectors to be used Stream-wise"

    ^ self nextPut:something
!

cr
    "output a carriage return, finishing the current line"

    access wouldBlock ifTrue:[
	Processor activeProcessIsSystemProcess ifTrue:[
	    Stderr cr.
	    ^ self
	]
    ].

    collecting ifTrue:[
	access critical:[
	    outstandingLine notNil ifTrue:[
		outstandingLines add:outstandingLine.
	    ].
	    outstandingLine := ''.
	].
	flushPending ifFalse:[
	    self installDelayedUpdate
	]
    ] ifFalse:[
	access critical:[
	    self cursorReturn.
	    self checkLineLimit.
	].
    ]
!

show:anObject
    "insert the argument aString at current cursor position"

    |aString|

    aString := anObject printString.
    access wouldBlock ifTrue:[
	Processor activeProcessIsSystemProcess ifTrue:[
	    Stderr nextPutAll:'Blocking in Transcript avoided: ' , aString.
	    ^ self
	]
    ].
    collecting ifTrue:[
	access critical:[
	    outstandingLine notNil ifTrue:[
		outstandingLine := outstandingLine , aString
	    ] ifFalse:[
		outstandingLine := aString
	    ].
	].
	flushPending ifFalse:[
	    self installDelayedUpdate
	]
    ] ifFalse:[
	access critical:[
	    self nextPut:aString.
	].
    ]
!

showCr:aString
    "insert the argument aString followed by a newline
     at current cursor position"

    self show:aString.
    self cr
!

doesNotUnderstand:aMessage
    "this is funny: all message we do not understand, are passed
     on to the stream which will send the characters via nextPut:
     This way, we understand all Stream messages - great isn't it !!
    "
     ^ entryStream perform:(aMessage selector) withArguments:(aMessage arguments)
! !