TextCollector.st
author Claus Gittinger <cg@exept.de>
Thu, 25 Apr 1996 19:34:16 +0200
changeset 586 032b3245e53a
parent 553 9aeda7a25755
child 653 fdab88d1205f
permissions -rw-r--r--
documentation

"
 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 inFlush collecting
		timeDelay access'
	classVariableNames:''
	poolDictionaries:''
	category:'Views-Text'
!

!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.
"
!

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, errorLogs 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 (transcript only):]

        transcriptForegroundColor       defaults to textForegroundColor
        transcriptBackgroundColor'      defaults to textBackgroundColor.

        transcriptCursorForegroundColor
        transcriptCursorBackgroundColor

    [author:]
        Claus Gittinger

    [see also:]
        CodeView EditTextView
        ActorStream
"
! !

!TextCollector class methodsFor:'instance creation'!

newTranscript
    "create and open a new transcript."

    |topView transcript f v 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)).

    transcript beTranscript.

    "
     run it at a slightly higher prio, to allow for
     delayed buffered updates to be performed
    "
    topView openWithPriority:(Processor userSchedulingPriority + 1).

    ^ transcript

    "
     TextCollector newTranscript
    "
! !

!TextCollector class methodsFor:'defaults'!

defaultLineLimit
    "the number of lines remembered by default"

    ^ 600
!

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

    ^ 0.3 
!

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

    ^ 70@11
! !

!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
!

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:[
	flushPending ifFalse:[
	    self installDelayedUpdate.
	].
	^ self
    ].
    inFlush ifTrue:[^ self].

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

    access critical:[
	inFlush := true.
	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 cursorToEnd.
		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.
	inFlush := false.
	device flush.
    ].
    flushPending ifTrue:[
	flushPending := false.
	self installDelayedUpdate
    ]
!

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

    lineLimit := aNumber
! !

!TextCollector methodsFor:'change & update'!

getListFromModel
     super getListFromModel.
     self scrollToBottom

    "Created: 12.2.1996 / 14:27:56 / stefan"
! !

!TextCollector methodsFor:'events'!

exposeX:x y:y width:w height:h
    "flush buffered text when exposed"

    super exposeX:x y:y width:w height:h.
    self endEntry
! !

!TextCollector methodsFor:'initialize / release'!

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

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
!

initialize
    super initialize.

    outstandingLines := OrderedCollection new.

    flushBlock := [self endEntry].
    flushPending := inFlush := false.
    collecting := true.
    timeDelay := self class defaultTimeDelay.
    access := RecursionLock new. "/ Semaphore forMutualExclusion.

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

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

    super mapped.
    self endEntry
!

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

    flushPending := false.
    access := RecursionLock new. "/ Semaphore forMutualExclusion.
    super reinitialize.
! !

!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. dumb vga cards ...)."

    |p|

    flushPending ifFalse:[
        flushPending := true.
        inFlush ifFalse:[
            "
             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.
            "
            windowGroup isNil ifTrue:[
                p := Processor activeProcess
            ] ifFalse:[
                p := windowGroup process
            ].
            Processor addTimedBlock:flushBlock for:p afterSeconds:timeDelay.
        ]
    ]

    "Modified: 18.4.1996 / 20:02:01 / cg"
! !

!TextCollector methodsFor:'stream messages'!

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

    access critical:[
	collecting ifTrue:[
	    outstandingLine notNil ifTrue:[
		outstandingLines add:outstandingLine.
	    ].
	    outstandingLine := ''.

	    flushPending ifFalse:[
		self installDelayedUpdate
	    ]
	] ifFalse:[
	    self cursorReturn.
	    self checkLineLimit.
	    self cursorToEnd.
	].
    ]
!

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)
!

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 flush
!

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

    ^ self nextPut:something
!

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

    |aString lines|

    aString := anObject printString.
    (aString includes:(Character cr)) ifTrue:[
	lines := aString asStringCollection.
	lines keysAndValuesDo:[:nr :line |
	    nr == lines size ifTrue:[
		"/ the last one.
		self show:line
	    ] ifFalse:[
		self showCr:line
	    ].
	].
	^ 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
! !

!TextCollector methodsFor:'transcript specials'!

beTranscript
    |fg bg cFg cBg|

    Smalltalk at:#Transcript put:self.

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

    fg := styleSheet colorAt:'transcriptForegroundColor' default:self foregroundColor.
    bg := styleSheet colorAt:'transcriptBackgroundColor' default:self backgroundColor.
    self foregroundColor:fg backgroundColor:bg.
    self viewBackground:bg.

    cFg := styleSheet colorAt:'transcriptCursorForegroundColor' default:bg.
    cBg := styleSheet colorAt:'transcriptCursorBackgroundColor' default:fg.
    self cursorForegroundColor:cFg backgroundColor:cBg. 

    "self lineLimit:1000. " "or whatever you think makes sense"
! !

!TextCollector class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg/TextCollector.st,v 1.28 1996-04-25 17:33:16 cg Exp $'
! !