;;; -*- Mode:LISP; Package:TV; Base:8; Readtable:ZL -*-
;;;	** (c) Copyright 1981 Massachusetts Institute of Technology **

;;; Hairier who-line system

(DEFFLAVOR WHO-LINE-SCREEN () (SCREEN))

(DEFMETHOD (WHO-LINE-SCREEN :USER-VISIBLE) () NIL)  

(DEFFLAVOR WHO-LINE-MIXIN ((WHO-LINE-ITEM-STATE NIL)) ()
				;WHO-LINE-ITEM-STATE is NIL if the contents of the window
				;is unknown and needs to be redrawn.  If non-NIL it
				;represents the current contents, to avoid extra redisplay.
  (:INCLUDED-FLAVORS MINIMUM-WINDOW)
  (:DEFAULT-INIT-PLIST :MORE-P NIL :BLINKER-P NIL :FONT-MAP '(FONTS:CPTFONT))
  (:REQUIRED-METHODS :UPDATE)
  (:SELECT-METHOD-ORDER :UPDATE)
  (:INIT-KEYWORDS :FLAVOR)
  :INITABLE-INSTANCE-VARIABLES
  :SETTABLE-INSTANCE-VARIABLES
  :GETTABLE-INSTANCE-VARIABLES)

(DEFWRAPPER (WHO-LINE-MIXIN :UPDATE) (IGNORE . BODY)
  `(WITHOUT-INTERRUPTS
     (AND (SHEET-CAN-GET-LOCK SELF)
	  (NOT (SHEET-OUTPUT-HELD-P SELF))
	  (PROGN . ,BODY))))

(DEFMETHOD (WHO-LINE-MIXIN :AFTER :REFRESH) (&OPTIONAL TYPE)
  (UNLESS (AND RESTORED-BITS-P (NEQ TYPE ':SIZE-CHANGED))
    (SEND SELF :CLOBBERED)
    (SEND SELF :UPDATE)))

;;; Should this actually do the updates here??
(DEFMETHOD (WHO-LINE-MIXIN :CLOBBERED) ()
  (SETQ WHO-LINE-ITEM-STATE NIL))

(DEFFLAVOR WHO-LINE-SHEET
	((WHO-LINE-UPDATE-FUNCTION NIL) (WHO-LINE-EXTRA-STATE NIL))
	(WHO-LINE-MIXIN MINIMUM-WINDOW)
  :INITABLE-INSTANCE-VARIABLES
  :SETTABLE-INSTANCE-VARIABLES
  :GETTABLE-INSTANCE-VARIABLES)

(DEFMETHOD (WHO-LINE-SHEET :BEFORE :INIT) (PLIST)
  (PUTPROP PLIST (GET PLIST ':WHO-LINE-UPDATE-FUNCTION) ':NAME))

(DEFMETHOD (WHO-LINE-SHEET :UPDATE) ()
  (AND WHO-LINE-UPDATE-FUNCTION
       (SEND WHO-LINE-UPDATE-FUNCTION SELF)))

(DEFUN WHO-LINE-SETUP ()
  (WHEN (NULL WHO-LINE-SCREEN)
    (SETQ WHO-LINE-SCREEN
	  (DEFINE-SCREEN 'WHO-LINE-SCREEN "Who Line Screen"
	    :AREA WHO-LINE-AREA
	    :DEFAULT-FONT FONTS:CPTFONT	;not *DEFAULT-FONT*
	    :BUFFER MAIN-SCREEN-BUFFER-ADDRESS
	    :CONTROL-ADDRESS #o377760
	    :PROPERTY-LIST '(:VIDEO :BLACK-AND-WHITE
				    :CONTROLLER :SIMPLE
				    :WHO-LINE T)
	    :WIDTH MAIN-SCREEN-WIDTH
	    :LOCATIONS-PER-LINE MAIN-SCREEN-LOCATIONS-PER-LINE
	    :CHARACTER-HEIGHT 2
	    :VSP 0
	    :Y NIL			;Force this to be calculated
	    :BOTTOM MAIN-SCREEN-HEIGHT))
    ;; 18 characters of the date and time
    (SETQ NWATCH-WHO-LINE-SHEET
	  (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-SHEET
			  :WHO-LINE-UPDATE-FUNCTION 'NWATCH-WHO-FUNCTION
			  :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
			  :LEFT 0 :RIGHT 144. :BOTTOM (SHEET-HEIGHT WHO-LINE-SCREEN)))
    ;; 13 characters of user id or process
    (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-SHEET
		    :WHO-LINE-UPDATE-FUNCTION 'WHO-LINE-USER-OR-PROCESS
		    :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
		    :LEFT 144. :RIGHT 248. :BOTTOM (SHEET-HEIGHT WHO-LINE-SCREEN))
    ;; 10 characters of package
    (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-SHEET
		    :WHO-LINE-UPDATE-FUNCTION 'WHO-LINE-PACKAGE
		    :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
		    :LEFT 248. :RIGHT 328. :BOTTOM (SHEET-HEIGHT WHO-LINE-SCREEN))
    ;; 19 characters of process state
    (SETQ WHO-LINE-RUN-STATE-SHEET
	  (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-SHEET
			  :WHO-LINE-UPDATE-FUNCTION 'WHO-LINE-RUN-STATE
			  :LEFT 328. :RIGHT (SELECT-PROCESSOR
					      (:CADR 480.)
					      ((:LAMBDA :EXPLORER) 520.))
			  :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
			  :BOTTOM (SHEET-HEIGHT WHO-LINE-SCREEN)))
    ;; The remaining 36 characters go to the file/idle/boot state
    (SETQ WHO-LINE-FILE-STATE-SHEET
	  (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-FILE-SHEET
			  :LEFT (SELECT-PROCESSOR
				  (:CADR 480.)
				  ((:LAMBDA :EXPLORER) 520.))
			  :RIGHT (SELECT-PROCESSOR
				   (:CADR 768.)
				   ((:LAMBDA :EXPLORER) 1024.))
			  :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
			  :BOTTOM (SHEET-HEIGHT WHO-LINE-SCREEN)))
    ;; Above those windows is a full line of mouse button documentation
    (SETQ WHO-LINE-DOCUMENTATION-WINDOW
	  (WHO-LINE-FIELD :FLAVOR 'WHO-LINE-SHEET
			  :WHO-LINE-UPDATE-FUNCTION 'WHO-LINE-DOCUMENTATION-FUNCTION
			  :HEIGHT (SHEET-LINE-HEIGHT WHO-LINE-SCREEN)
			  :TOP 0 :REVERSE-VIDEO-P T))))

(DEFUN WHO-LINE-UPDATE (&OPTIONAL RUN-STATE-ONLY-P &AUX RL)
  (OR INHIBIT-WHO-LINE
      (NULL WHO-LINE-SCREEN)
      (WITHOUT-INTERRUPTS
	(SETQ RL (SELECT-PROCESSOR
		   (:CADR
		    (%XBUS-READ WHO-LINE-RUN-LIGHT-LOC))	;Don't clobber run light
		   (:LAMBDA
		    (COMPILER:%IO-SPACE-READ WHO-LINE-RUN-LIGHT-LOC))
		   (:explorer
		    (COMPILER:%IO-SPACE-READ WHO-LINE-RUN-LIGHT-LOC))
		   ))
	(IF RUN-STATE-ONLY-P
	    ;; The reason this is here is that this function conspires to do some
	    ;; minor nice things for you.  This note is here to remind HIC not to
	    ;; clean up this code.  --HIC
	    (AND WHO-LINE-RUN-STATE-SHEET
		 (SEND WHO-LINE-RUN-STATE-SHEET :UPDATE))
	    (DOLIST (I (SHEET-EXPOSED-INFERIORS WHO-LINE-SCREEN))
	      (AND (TYPEP I 'WHO-LINE-MIXIN)
		   (SEND I :UPDATE))))
	(SELECT-PROCESSOR
	  (:CADR
	   (%XBUS-WRITE WHO-LINE-RUN-LIGHT-LOC RL))
	  (:LAMBDA
	   (COMPILER:%IO-SPACE-WRITE WHO-LINE-RUN-LIGHT-LOC RL))
	  (:explorer
	   (COMPILER:%IO-SPACE-WRITE WHO-LINE-RUN-LIGHT-LOC RL))
	  )))
  T)

(DEFUN WHO-LINE-CLOBBERED ()
  "Inform the who-line that it must redisplay completely."
  (AND WHO-LINE-SCREEN
       (DOLIST (I (SHEET-INFERIORS WHO-LINE-SCREEN))
	 (AND (TYPEP I 'WHO-LINE-MIXIN) (SEND I :CLOBBERED)))))

(DEFUN WHO-LINE-STRING (WHO-SHEET NEW-STRING)
  "Output NEW-STRING on WHO-SHEET, a part of the who line, if it has changed.
The last value is remembered in the WHO-LINE-ITEM-STATE instance variable."
  (DECLARE (:SELF-FLAVOR WHO-LINE-SHEET))
  (SETQ NEW-STRING (STRING NEW-STRING))
  (WHEN (NEQ WHO-LINE-ITEM-STATE NEW-STRING)
    (PREPARE-SHEET (WHO-SHEET)
      (SHEET-CLEAR WHO-SHEET)
      (SHEET-STRING-OUT WHO-SHEET NEW-STRING
			0 (MIN (LENGTH NEW-STRING)
			       (TRUNCATE (SHEET-INSIDE-WIDTH WHO-SHEET)
					 (SHEET-CHAR-WIDTH WHO-SHEET)))))
    (SETQ WHO-LINE-ITEM-STATE NEW-STRING)))

(DEFUN WHO-LINE-USER-OR-PROCESS (WHO-SHEET)
  (WHO-LINE-STRING WHO-SHEET (IF WHO-LINE-PROCESS (PROCESS-NAME WHO-LINE-PROCESS) USER-ID)))

(DEFUN WHO-LINE-RUN-STATE (WHO-SHEET)
  (WHO-LINE-STRING WHO-SHEET WHO-LINE-RUN-STATE))

(DEFUN WHO-LINE-PACKAGE (WHO-SHEET &AUX VAL SG)
  (DECLARE (:SELF-FLAVOR WHO-LINE-SHEET))
  (LET ((PKG (COND ((SETQ LAST-WHO-LINE-PROCESS (OR WHO-LINE-PROCESS
						    (AND SELECTED-IO-BUFFER
							 (IO-BUFFER-LAST-OUTPUT-PROCESS
							   SELECTED-IO-BUFFER))))
		    (SETQ SG (PROCESS-STACK-GROUP LAST-WHO-LINE-PROCESS))
		    (COND ((EQ SG %CURRENT-STACK-GROUP) *PACKAGE*)
			  ((TYPEP SG 'STACK-GROUP) (SYMEVAL-IN-STACK-GROUP '*PACKAGE* SG))
			  (T PACKAGE))))))
    (WHEN (AND PKG (PACKAGEP PKG)
	       (NEQ WHO-LINE-ITEM-STATE PKG))
      (PREPARE-SHEET (WHO-SHEET)
	(SHEET-CLEAR WHO-SHEET)
	(SETQ VAL (SI:PKG-SHORTEST-NAME PKG))
	(SHEET-STRING-OUT WHO-SHEET VAL
			  0 (MIN (STRING-LENGTH VAL)
				 (- (TRUNCATE (SHEET-INSIDE-WIDTH WHO-SHEET)
					       (SHEET-CHAR-WIDTH WHO-SHEET))
				    (if si::*read-single-colon-allow-internal-symbol*
					1
					2)))))
      (SHEET-TYO WHO-SHEET #/:)
      (when (not si::*read-single-colon-allow-internal-symbol*)
	(SHEET-TYO WHO-SHEET #/:))
      (SETQ WHO-LINE-ITEM-STATE PKG))))

(DEFUN WHO-LINE-RUN-STATE-UPDATE (&AUX P)  ;Separate variable since other can be setq'ed
					   ;asynchronously by other processes
  (SETQ LAST-WHO-LINE-PROCESS
	(SETQ P (OR WHO-LINE-PROCESS
		    (PROGN (AND (NULL SELECTED-IO-BUFFER) 
				(NOT (NULL SELECTED-WINDOW))	;This can happen
				(SETQ SELECTED-IO-BUFFER
				      (SEND SELECTED-WINDOW :IO-BUFFER)))
			   (AND SELECTED-IO-BUFFER
				(IO-BUFFER-LAST-OUTPUT-PROCESS SELECTED-IO-BUFFER))))))
  (SETQ WHO-LINE-RUN-STATE (COND (*WHO-LINE-RUN-STATE-OVERRIDE*)
				 ((NULL SELECTED-WINDOW)
				  "No selected window")
				 ((NULL P)
				  "No current process")
				 ((ASSQ P ACTIVE-PROCESSES)
				  (or (si:process-wait-whostate p)
				      (si:process-run-whostate p)))
				 ((NOT (NULL (SI:PROCESS-ARREST-REASONS P)))
				  "Arrest")
				 (T "Stop")))
  (WHO-LINE-UPDATE T))

(DEFUN WHO-LINE-FIELD (&REST ARGS &AUX W)
  ;; Do sheet type consing in special area to increase locality
  (SETQ W (APPLY #'MAKE-INSTANCE (GETF ARGS ':FLAVOR)
		 :AREA WHO-LINE-AREA
		 :SUPERIOR WHO-LINE-SCREEN
		 :VSP 0
		 ARGS))
  (SEND W :ACTIVATE)
  (SEND W :EXPOSE)
  W)

(DEFFLAVOR WHO-LINE-FILE-SHEET
	   ((CURRENT-STREAM NIL)		;The one being displayed
	    ;; This is an array rather than a list to avoid consing.
	    (OPEN-STREAMS (MAKE-ARRAY 20. :TYPE 'ART-Q-LIST :FILL-POINTER 0))
	    ;; A list with elements (chaos-connection from-machine contact-name)
	    (SERVERS-LIST NIL)
	    DISPLAYED-PERCENT DISPLAYED-COUNT)
	   (WHO-LINE-MIXIN MINIMUM-WINDOW))

;;; Take the most recently opened input stream if there is one.  Otherwise
;;; take the most recently opened output stream.
(DEFUN WHO-LINE-FILE-SHEET-COMPUTE-CURRENT-STREAM (&OPTIONAL (UPDATE-P T))
  (DECLARE (:SELF-FLAVOR WHO-LINE-FILE-SHEET))
  (DO ((I (1- (FILL-POINTER OPEN-STREAMS)) (1- I))
       (OUTPUT-WINNER NIL) (STREAM) (DIRECTION))
      ((MINUSP I)
       (SETQ CURRENT-STREAM OUTPUT-WINNER))
    (SETQ STREAM (AREF OPEN-STREAMS I)
	  DIRECTION (NTH-VALUE 1 (SEND STREAM :WHO-LINE-INFORMATION)))
    (CASE DIRECTION
      ((:INPUT :BIDIRECTIONAL)
       (RETURN (SETQ CURRENT-STREAM STREAM)))
      (:OUTPUT
       (OR OUTPUT-WINNER
	   (SETQ OUTPUT-WINNER STREAM)))))
  (AND UPDATE-P (WHO-LINE-UPDATE)))

(DEFMETHOD (WHO-LINE-FILE-SHEET :ADD-STREAM) (STREAM &OPTIONAL (UPDATE-P T))
  (AND (VECTOR-PUSH-EXTEND STREAM OPEN-STREAMS)
       (WHO-LINE-FILE-SHEET-COMPUTE-CURRENT-STREAM UPDATE-P)))

(DEFMETHOD (WHO-LINE-FILE-SHEET :DELETE-STREAM) (STREAM &AUX POS)
  (WHEN (SETQ POS (FIND-POSITION-IN-LIST STREAM (G-L-P OPEN-STREAMS)))
    (IF (= POS (1- (FILL-POINTER OPEN-STREAMS)))
	(VECTOR-POP OPEN-STREAMS)
        (SETF (AREF OPEN-STREAMS POS) (VECTOR-POP OPEN-STREAMS)))
    (AND (EQ STREAM CURRENT-STREAM)
	 (WHO-LINE-FILE-SHEET-COMPUTE-CURRENT-STREAM))))

(DEFMETHOD (WHO-LINE-FILE-SHEET :DELETE-ALL-STREAMS) ()
  (STORE-ARRAY-LEADER 0 OPEN-STREAMS 0)
  (SETQ CURRENT-STREAM NIL))

(ADD-INITIALIZATION "Fix WHO-LINE-FILE-STATE-SHEET"
		    '(SEND WHO-LINE-FILE-STATE-SHEET :DELETE-ALL-STREAMS)
		    '(SYSTEM))

(DEFMETHOD (WHO-LINE-FILE-SHEET :OPEN-STREAMS) ()
  (G-L-P OPEN-STREAMS))

;;; >> Any of the stuff that records servers is going to have to be rewritten.
(DEFMETHOD (WHO-LINE-FILE-SHEET :ADD-SERVER) (CONNECTION CONTACT-NAME
					       &OPTIONAL (PROCESS SI:CURRENT-PROCESS)
					       FUNCTION &REST ARGS
					       &AUX (INHIBIT-SCHEDULING-FLAG T))
  (SEND SELF :DELETE-SERVER CONNECTION)
  (PUSH (MAKE-SERVER-DESC
	  :CONNECTION CONNECTION
	  :HOST-NAME (if (typep connection 'chaos:conn)
			 (CHAOS:HOST-SHORT-NAME (CHAOS:FOREIGN-ADDRESS CONNECTION))
		       (ip:host-short-name (send connection :remote-address)))
	  :CONTACT-NAME CONTACT-NAME
	  :PROCESS PROCESS
	  :FUNCTION FUNCTION
	  :ARGS (COPY-LIST ARGS))
	SERVERS-LIST))

;;; This isn't usually called; Normally servers are deleted automatically when
;;; it is noticed that the connection has been closed.
(DEFMETHOD (WHO-LINE-FILE-SHEET :DELETE-SERVER) (CONNECTION
						 &AUX (INHIBIT-SCHEDULING-FLAG T))
  (SETQ SERVERS-LIST (DEL #'(LAMBDA (X Y) (EQ X (SERVER-DESC-CONNECTION Y)))
			  CONNECTION SERVERS-LIST)))

(DEFMETHOD (WHO-LINE-FILE-SHEET :DELETE-ALL-SERVERS) ()
  (SETQ SERVERS-LIST NIL))

(DEFMETHOD (WHO-LINE-FILE-SHEET :CLOSE-ALL-SERVERS) (REASON)
  (LOOP FOR SERVER IN SERVERS-LIST FINALLY (SETQ SERVERS-LIST NIL) DO
	(let ((connection (server-desc-connection server)))
	  (if (typep connection 'chaos:conn)
	      (CHAOS:CLOSE-CONN (SERVER-DESC-CONNECTION SERVER) REASON)
	    (send connection :close)))))

;;; Remove all servers which aren't current anymore.
(DEFUN PURGE-SERVERS ()
  (DECLARE (:SELF-FLAVOR WHO-LINE-FILE-SHEET))
  (WITHOUT-INTERRUPTS
    (DO ((S SERVERS-LIST (CDR S)))
	((NULL S)
	 (SETQ SERVERS-LIST (DELQ NIL SERVERS-LIST)))
      (let ((connection (server-desc-connection (car s))))
	(cond ((typep connection 'chaos:conn)
	       (when (AND (NEQ (CHAOS:STATE (SERVER-DESC-CONNECTION (CAR S))) 'CHAOS:OPEN-STATE)
			  (NEQ (CHAOS:STATE (SERVER-DESC-CONNECTION (CAR S)))
			       'CHAOS:RFC-RECEIVED-STATE))
;		 (BACKGROUND-NOTIFY "Server ~A from ~A being purged; state is ~A"
;				    (SERVER-DESC-CONTACT-NAME (CAR S))
;				    (SERVER-DESC-HOST-NAME (CAR S))
;				    (CHAOS:STATE (SERVER-DESC-CONNECTION (CAR S))))
		 (SETF (CAR S) NIL)))
	      ((null (send connection :remote-address))
	       (setf (car s) nil)))))))
	       

(DEFUN BACKGROUND-NOTIFY (FORMAT-STRING &REST ARGS)
  (APPLY #'PROCESS-RUN-FUNCTION "Notify" 'NOTIFY NIL FORMAT-STRING ARGS))

(DEFMETHOD (WHO-LINE-FILE-SHEET :SERVERS) ()
  (PURGE-SERVERS)
  SERVERS-LIST)

;;;; User level functions
(DEFUN DESCRIBE-SERVERS ()
  "Describe all network servers currently serving."
  (DOLIST (S (SEND TV:WHO-LINE-FILE-STATE-SHEET :SERVERS))
    (FORMAT T "~%~A serving ~A in ~A"
	    (SERVER-DESC-CONTACT-NAME S)
	    (SERVER-DESC-HOST-NAME S)
	    (SERVER-DESC-PROCESS S))))

(DEFUN CLOSE-ALL-SERVERS (&OPTIONAL (REASON "Foo on you"))
  "Disconnect all servers on this machine from their remote users."
  (SEND TV:WHO-LINE-FILE-STATE-SHEET :CLOSE-ALL-SERVERS REASON))

(DEFMETHOD (WHO-LINE-FILE-SHEET :UPDATE) (&AUX (MAX-CHARS (TRUNCATE (SHEET-INSIDE-WIDTH)
								    CHAR-WIDTH))
					       IDLE STRING)
  (cond (pending-notifications
	 ;; each element of pending-notifications is (,time ,string ,window-of-interest)
	 (if (eq who-line-item-state pending-notifications)
	     nil
	   (sheet-clear self)
	   (if (cdr pending-notifications)
	       (progn (setq string (format nil "~D notifications are pending"
					   (length pending-notifications)))
		      (send self :string-out string 0 (min (length string) max-chars))
		      (setq string nil))
	     (setq string (cadr (car pending-notifications)))
	     (let ((string-length (length string))
		   (window (caddr (car pending-notifications))))
	       (cond ((and window
			   ;;>> What a kludge
			   (string= string "Process " :end1 #.(length "Process "))
			   (or (string= string "wants to type out"
					:start1 (max (- string-length
							#.(length "wants to type out"))
						     0))
			       (string= string "wants typein"
					:start1 (max (- string-length #.(length "wants typein"))
						     0))))
		      (send self :string-out string 0 (min string-length max-chars)))
		     (t ;(null window)
		      (let* ((n (%string-search-char #/newline string 0 string-length))
			     (len (+ #.(length "Notification: ") (or n string-length))))
			(if (eql n (1- string-length)) (setq string-length n n nil))
			(cond ((and (null n) ( len max-chars))
			       (send self :string-out "Notification: ")
			       (send self :string-out string 0 string-length))
			      (t
			       (send self :string-out "Notification: ")
			       (send self :string-out string
					  0 (min (or n string-length)
						 (- max-chars #.(length "Notification:     "))))
			       (send self :string-out "     "))
			      #||(t
			       (send self :string-out "Notification pending"
				     0 (min #.(length "Notification pending"))))||#)))))))
	 (setq who-line-item-state pending-notifications))
	(CURRENT-STREAM
	 (LET ((OLD-STREAM WHO-LINE-ITEM-STATE))
	   (MULTIPLE-VALUE-BIND (PATHNAME DIRECTION COUNT PERCENT)
	       (SEND CURRENT-STREAM :WHO-LINE-INFORMATION)
	     (UNLESS (AND (EQ OLD-STREAM CURRENT-STREAM)
			  (EQ PERCENT DISPLAYED-PERCENT)
			  (EQ COUNT DISPLAYED-COUNT))
	       (IF (EQ OLD-STREAM CURRENT-STREAM)
		   (SHEET-HOME SELF)
		   (SHEET-CLEAR SELF))
	       (SETQ WHO-LINE-ITEM-STATE CURRENT-STREAM
		     DISPLAYED-PERCENT PERCENT
		     DISPLAYED-COUNT COUNT)
	       (DISPLAY-FILE-TRANSFER SELF PATHNAME DIRECTION COUNT
				      PERCENT MAX-CHARS)))))
	((AND (NOT (NULL SERVERS-LIST))
	      (PROGN (PURGE-SERVERS)
		     (NOT (NULL SERVERS-LIST))))
	 (COND ((= (LENGTH SERVERS-LIST) 1)
		(COND ((NEQ WHO-LINE-ITEM-STATE (CAAR SERVERS-LIST))
		       (SHEET-CLEAR SELF)
		       (SETQ STRING (FORMAT NIL "~A serving ~A"
					    (CADDAR SERVERS-LIST) (CADAR SERVERS-LIST)))
		       (SEND SELF :STRING-OUT STRING 0 (MIN (STRING-LENGTH STRING) MAX-CHARS))
		       (SETQ STRING NIL)
		       (SETQ WHO-LINE-ITEM-STATE (CAAR SERVERS-LIST)))))
	       ((NEQ WHO-LINE-ITEM-STATE (LENGTH SERVERS-LIST))
		(SHEET-CLEAR SELF)
		(SETQ STRING (FORMAT NIL "~D Active Servers" (LENGTH SERVERS-LIST)))
		(SEND SELF :STRING-OUT STRING 0 (MIN (LENGTH STRING) MAX-CHARS))
		(SETQ STRING NIL)
		(SETQ WHO-LINE-ITEM-STATE (LENGTH SERVERS-LIST)))))
	(SI::WHO-LINE-JUST-COLD-BOOTED-P
	 (UNLESS (or (EQ WHO-LINE-ITEM-STATE 'COLD)
		     si:*cold-booting*)
	   (SHEET-CLEAR SELF)
	   (SETQ WHO-LINE-ITEM-STATE 'COLD)
	   (SETQ STRING (FORMAT NIL "~A cold-booted" SI::LOCAL-PRETTY-HOST-NAME))
	   (SEND SELF :STRING-OUT STRING)))
	((> (SETQ IDLE (TRUNCATE (TIME-DIFFERENCE (TIME) TV:KBD-LAST-ACTIVITY-TIME) 3600.)) 4)
	 ;; Display keyboard idle time
	 (LET ((OLD-IDLE WHO-LINE-ITEM-STATE))
	   (WHEN (OR (NOT (NUMBERP OLD-IDLE)) ( OLD-IDLE IDLE))
	     (SHEET-CLEAR SELF)
	     (WITHOUT-INTERRUPTS
	       (LET ((STRING (MAKE-IDLE-MESSAGE IDLE)))
		 (SEND SELF :STRING-OUT STRING)
		 (SETQ STRING NIL)))
	     (SETQ WHO-LINE-ITEM-STATE IDLE))))
	((NEQ WHO-LINE-ITEM-STATE 'NULL)
	 (SHEET-CLEAR SELF)
	 (SETQ WHO-LINE-ITEM-STATE 'NULL))))

(DEFCONST DISPLAY-FILE-TRANSFER-COUNT-STRING (MAKE-STRING 20. :FILL-POINTER 0))

(DEFCONST DISPLAY-FILE-TRANSFER-PERCENT-STRING (MAKE-STRING 5 :FILL-POINTER 0))

(DEFVAR LAST-WHOLINE-PATHNAME NIL
  "The last pathname displayed in the who line.")
(DEFVAR LAST-WHOLINE-PATHNAME-STRING NIL
  "The string we displayed for LAST-WHOLINE-PATHNAME.")
(DEFVAR LAST-WHOLINE-PATHNAME-LENGTH NIL
  "The length we requested, when we obtained LAST-WHOLINE-PATHNAME-STRING.")

;;; Display the who-line-information onto SHEET.  PERCENT may be NIL,
;;; but COUNT is always a fixnum.  DIRECTION is one of the keywords
;;; :INPUT, :OUTPUT, or :BIDIRECTIONAL.  MAX-CHARS is the maximum
;;; number of characters that we may output.
(DEFUN DISPLAY-FILE-TRANSFER (SHEET PATHNAME DIRECTION COUNT PERCENT MAX-CHARS)
  (SEND SHEET :STRING-OUT (CASE DIRECTION
			    (:INPUT " ")
			    (:OUTPUT " ")
			    (:BIDIRECTIONAL " ")
			    (T "? ")))
  (LET* ((FILE-NAME (SEND PATHNAME :STRING-FOR-PRINTING))
	 (FILE-NAME-LENGTH (STRING-LENGTH FILE-NAME))
	 (FILE-NAME-LIMIT NIL)
	 (COUNT-STRING-LENGTH)
	 (PERCENT-STRING-LENGTH)
	 (DISPLAY-COUNT-P NIL)
	 (DISPLAY-PERCENT-P NIL))
    (FIXNUM-INTO-STRING COUNT DISPLAY-FILE-TRANSFER-COUNT-STRING)
    (SETQ COUNT-STRING-LENGTH (LENGTH DISPLAY-FILE-TRANSFER-COUNT-STRING))
    (COND ((NULL PERCENT)
	   (SETQ DISPLAY-PERCENT-P NIL DISPLAY-COUNT-P T)
	   ;; 4 is two for the direction and two for the spaces after the file name.
	   (IF ( (+ FILE-NAME-LENGTH COUNT-STRING-LENGTH 4) MAX-CHARS)
	       (SETQ FILE-NAME-LIMIT (- MAX-CHARS 4 COUNT-STRING-LENGTH))))
	  (T
	   (FIXNUM-INTO-STRING PERCENT DISPLAY-FILE-TRANSFER-PERCENT-STRING)
	   (VECTOR-PUSH #/% DISPLAY-FILE-TRANSFER-PERCENT-STRING)
	   (SETQ PERCENT-STRING-LENGTH
		 (LENGTH DISPLAY-FILE-TRANSFER-PERCENT-STRING))
	   (SETQ DISPLAY-PERCENT-P T)
	   (COND (( (+ FILE-NAME-LENGTH COUNT-STRING-LENGTH PERCENT-STRING-LENGTH 5)
					;5 is the above 4 plus 1 space between percent & count
		     MAX-CHARS)
		  (SETQ DISPLAY-COUNT-P T))
		 ((> (+ FILE-NAME-LENGTH PERCENT-STRING-LENGTH 4) MAX-CHARS)
		  (SETQ FILE-NAME-LIMIT (- MAX-CHARS PERCENT-STRING-LENGTH 4))))))
    (WHEN FILE-NAME-LIMIT
      (IF (AND (EQ PATHNAME LAST-WHOLINE-PATHNAME)
	       (= FILE-NAME-LIMIT LAST-WHOLINE-PATHNAME-LENGTH))
	  (SETQ FILE-NAME LAST-WHOLINE-PATHNAME-STRING)
	(SETQ FILE-NAME (SEND PATHNAME :STRING-FOR-WHOLINE FILE-NAME-LIMIT))
	(SETQ LAST-WHOLINE-PATHNAME-LENGTH FILE-NAME-LIMIT)
	(SETQ LAST-WHOLINE-PATHNAME-STRING FILE-NAME)
	(SETQ LAST-WHOLINE-PATHNAME PATHNAME))
      (AND (= FILE-NAME-LIMIT (LENGTH FILE-NAME))
	   (SETQ FILE-NAME-LIMIT NIL)))
    (SHEET-CLEAR-EOL SHEET)
    (SEND SHEET :STRING-OUT FILE-NAME 0
	  (IF FILE-NAME-LIMIT (MIN FILE-NAME-LIMIT (LENGTH FILE-NAME))))
    (SEND SHEET :STRING-OUT (IF FILE-NAME-LIMIT "  " "  "))
    (WHEN DISPLAY-PERCENT-P
      (SEND SHEET :STRING-OUT DISPLAY-FILE-TRANSFER-PERCENT-STRING)
      (IF DISPLAY-COUNT-P (SEND SHEET :WRITE-CHAR #/SPACE)))
    (WHEN DISPLAY-COUNT-P
      (SEND SHEET :STRING-OUT DISPLAY-FILE-TRANSFER-COUNT-STRING))))

(DEFUN FIXNUM-INTO-STRING (NUMBER STRING &OPTIONAL (RADIX 10.))
  "Store a printout of NUMBER in RADIX into STRING.
STRING's contents are altered.  STRING is made longer if necessary."
  (SETF (FILL-POINTER STRING) 0)
  (DO ((NUM NUMBER (TRUNCATE NUM RADIX)))
      ((AND (ZEROP NUM) (NOT (ZEROP (LENGTH STRING))))
       (STRING-NREVERSE STRING))
    ;; Keep trying to push until we make array big enough to hold more.
    (DO () ((VECTOR-PUSH (INT-CHAR (+ (CHAR-INT #/0) (CL:REM NUM RADIX))) STRING))
      (ADJUST-ARRAY-SIZE STRING (+ 10. (ARRAY-LENGTH STRING))))))
       
(DEFUN MAKE-IDLE-MESSAGE (MINUTES)
  (let ((tail (cond ((< MINUTES 60.)
		     (FORMAT NIL "idle ~D minute~:P"
			     MINUTES))
		    (t
		     (MULTIPLE-VALUE-BIND (HOURS MINUTES)
			 (TRUNCATE MINUTES 60.)
		       (FORMAT NIL "idle ~D hr ~D min~:P"
			       HOURS MINUTES))))))
    (let ((whole-thing (format nil "~A's console ~A" si:local-pretty-host-name tail))
	  (who-line-length (send tv:who-line-file-state-sheet :size-in-characters))) ; apparently
                                                                                     ; NIL if in cold load
      (cond ((and (numberp who-line-length) 
		  (< (string-length whole-thing) who-line-length))
	     whole-thing)
	    (t
	     (format nil "Console ~A" tail))))))

;;;; Date and time in the who-line, continuously updating.

(DEFUN NWATCH-WHO-FUNCTION (WHO-SHEET &AUX LEFTX)
  (DECLARE (:SELF-FLAVOR WHO-LINE-SHEET))
  (OR WHO-LINE-EXTRA-STATE
      (LET ((DEFAULT-CONS-AREA WHO-LINE-AREA))
	(SETQ WHO-LINE-EXTRA-STATE (STRING-APPEND "MM//DD//YY HH:MM:SS"))))
						  ; Errgghhh! Krazy Backwards Amerikan dates.
  (MULTIPLE-VALUE-BIND (SECONDS MINUTES HOURS DAY MONTH YEAR)
      (TIME:GET-TIME)
    (COND ((NULL SECONDS)			       
	   (SHEET-CLEAR WHO-SHEET)
	   (COPY-ARRAY-CONTENTS "MM//DD//YY HH:MM:SS" WHO-LINE-EXTRA-STATE))
	  (T
	   (SETQ YEAR (MOD YEAR 100.))
	   (SETQ LEFTX (MIN (NWATCH-N MONTH WHO-LINE-EXTRA-STATE 0)
			    (NWATCH-N DAY WHO-LINE-EXTRA-STATE 3)
			    (NWATCH-N YEAR WHO-LINE-EXTRA-STATE 6)
			    (NWATCH-N HOURS WHO-LINE-EXTRA-STATE 9)
			    (NWATCH-N MINUTES WHO-LINE-EXTRA-STATE 12.)
			    (NWATCH-N SECONDS WHO-LINE-EXTRA-STATE 15.)))
	   (UNLESS WHO-LINE-ITEM-STATE (SETQ LEFTX 0))	;was clobbered, redisplay all
	   (SHEET-SET-CURSORPOS WHO-SHEET (* LEFTX CHAR-WIDTH) 0)
	   (SHEET-CLEAR-EOL WHO-SHEET)
	   (SHEET-STRING-OUT WHO-SHEET WHO-LINE-EXTRA-STATE LEFTX)
	   (SETQ WHO-LINE-ITEM-STATE T)))))

;;; Returns first character position changed
(DEFUN NWATCH-N (N STR I)
  (LET ((DIG1 (INT-CHAR (+ (TRUNCATE N 10.) (CHAR-INT #/0))))
	(DIG2 (INT-CHAR (+ (CL:REM N 10.) (CHAR-INT #/0)))))
    (PROG1 (COND ((NEQ (CHAR STR I) DIG1) I)
		 ((NEQ (CHAR STR (1+ I)) DIG2) (1+ I))
		 (T (ARRAY-LENGTH STR)))
	   (SETF (CHAR STR I) DIG1)
	   (SETF (CHAR STR (1+ I)) DIG2))))

;;;; Support for documentation in the who line

(DEFMETHOD (SHEET :WHO-LINE-DOCUMENTATION-STRING) ()
  NIL)

(DEFUN WHO-LINE-DOCUMENTATION (&OPTIONAL (ON-P T))
  "Turn display of the mouse documentation line on or off."
  (COND ((AND ON-P (NOT (SHEET-EXPOSED-P WHO-LINE-DOCUMENTATION-WINDOW)))
	 (SET-WHO-LINE-LINES (1+ (TRUNCATE (SHEET-INSIDE-HEIGHT WHO-LINE-SCREEN)
					   (SHEET-LINE-HEIGHT WHO-LINE-SCREEN))))
	 (SEND WHO-LINE-DOCUMENTATION-WINDOW :DEACTIVATE)
	 (DOLIST (I (COPY-LIST (SHEET-INFERIORS WHO-LINE-SCREEN)))
	   (AND ( (SHEET-Y-OFFSET I) (SHEET-Y-OFFSET WHO-LINE-DOCUMENTATION-WINDOW))
		(SEND I :SET-POSITION
		        (SHEET-X-OFFSET I)
			(+ (SHEET-Y-OFFSET I)
			   (SHEET-Y-OFFSET WHO-LINE-DOCUMENTATION-WINDOW)
			   (SHEET-HEIGHT WHO-LINE-DOCUMENTATION-WINDOW)))))
	 (SEND WHO-LINE-DOCUMENTATION-WINDOW :EXPOSE))
	((AND (NOT ON-P) WHO-LINE-DOCUMENTATION-WINDOW)
	 (COND ((SHEET-EXPOSED-P WHO-LINE-DOCUMENTATION-WINDOW)
		(SEND WHO-LINE-DOCUMENTATION-WINDOW :DEACTIVATE)
		(SET-WHO-LINE-LINES (1- (TRUNCATE (SHEET-INSIDE-HEIGHT WHO-LINE-SCREEN)
						  (SHEET-LINE-HEIGHT WHO-LINE-SCREEN))))
		(DOLIST (I (COPY-LIST (SHEET-INFERIORS WHO-LINE-SCREEN)))
		  (AND ( (SHEET-Y-OFFSET I) (SHEET-Y-OFFSET WHO-LINE-DOCUMENTATION-WINDOW))
		       (SEND I :SET-POSITION
			       (SHEET-X-OFFSET I)
			       (- (SHEET-Y-OFFSET I)
				  (SHEET-Y-OFFSET WHO-LINE-DOCUMENTATION-WINDOW)
				  (SHEET-HEIGHT WHO-LINE-DOCUMENTATION-WINDOW))))))))))

(DEFUN SET-WHO-LINE-LINES (N-LINES)
  "Reconfigure the screen so that the who line has N-LINES lines."
  (WITH-MOUSE-USURPED
   (LOCK-SHEET (MAIN-SCREEN)
    (LOCK-SHEET (WHO-LINE-SCREEN)
      (WITHOUT-INTERRUPTS
	(LET ((MS MOUSE-SHEET) (SW SELECTED-WINDOW))
	  (AND (SHEET-ME-OR-MY-KID-P MS MAIN-SCREEN)
	       (SETQ MOUSE-SHEET NIL))
	  (FUNCALL WHO-LINE-SCREEN :DEEXPOSE)
	  (FUNCALL MAIN-SCREEN :DEEXPOSE)
;	  (FUNCALL WHO-LINE-SCREEN :SET-VSP (IF (= N-LINES 1) 0 2))
	  (FUNCALL WHO-LINE-SCREEN :CHANGE-OF-SIZE-OR-MARGINS
		   :BOTTOM MAIN-SCREEN-HEIGHT 
		   :TOP (- MAIN-SCREEN-HEIGHT
			    (* N-LINES (SHEET-LINE-HEIGHT WHO-LINE-SCREEN))))
	  (FUNCALL MAIN-SCREEN :CHANGE-OF-SIZE-OR-MARGINS
		   :HEIGHT (- MAIN-SCREEN-HEIGHT (SHEET-HEIGHT WHO-LINE-SCREEN)))
	  (FUNCALL WHO-LINE-SCREEN :EXPOSE)
	  (FUNCALL MAIN-SCREEN :EXPOSE)
	  (AND SW (FUNCALL SW :SELECT))
	  (SETQ MOUSE-SHEET MS)))))))

;;; List of windows waiting for locks to print error notifications.
;;; DEFVAR in SHEET.
(PROCLAIM '(SPECIAL LOCKED-ERROR-WINDOWS))
(PROCLAIM '(SPECIAL PENDING-NOTIFICATIONS))

(DEFUN WHO-LINE-DOCUMENTATION-FUNCTION (WHO-SHEET)
  (DECLARE (:SELF-FLAVOR WHO-LINE-SHEET))
  (LET* ((W MOUSE-WINDOW)
	 (NEW-STATE
	   (COND (LOCKED-ERROR-WINDOWS
		  ;; To attract attention, make this message blink.
		  (LET ((MSG
			  "*** Background error with window locked; try Terminal C-M-Clear-Input or Terminal Call ***")
			(MSG1
			  "    Background error with window locked; try Terminal C-M-Clear-Input or Terminal Call"))
		    (IF (EQ WHO-LINE-ITEM-STATE MSG)
			MSG1 MSG)))
		 (*WHO-LINE-DOCUMENTATION-OVERRIDE*)
;		 (PENDING-NOTIFICATIONS
;		  (LET ((MSG
;			  "***** Notifications are pending.  Terminal N is one way to see them. *****")
;			(MSG1
;			  "      Notifications are pending.  Terminal N is one way to see them."))
;		    (IF (EQ WHO-LINE-ITEM-STATE MSG)
;			MSG1 MSG)))
		 ((SYMBOLP W)
		  (AND W WHO-LINE-MOUSE-GRABBED-DOCUMENTATION))
		 (T
		  (MULTIPLE-VALUE-BIND (DOC ERROR)
		      (CATCH-ERROR (SEND W :WHO-LINE-DOCUMENTATION-STRING)
				   NIL)
		    (IF ERROR "Error getting documentation string" DOC))))))
    (unless (stringp new-state)
      (setq new-state "Click right to get the System Menu."))
    (WHEN (NEQ WHO-LINE-ITEM-STATE NEW-STATE)
      (SETQ WHO-LINE-ITEM-STATE NEW-STATE)
      (SHEET-CLEAR WHO-SHEET)
      (SHEET-STRING-OUT WHO-SHEET NEW-STATE
			0 (MIN (OR (STRING-SEARCH-CHAR #/NEWLINE NEW-STATE)
				   (LENGTH NEW-STATE))
			       (TRUNCATE (SHEET-INSIDE-WIDTH WHO-SHEET)
					 (SHEET-CHAR-WIDTH WHO-SHEET)))))))

(DEFUN ADD-WHO-LINE-WINDOW (WINDOW)
  "Takes a window that must be an immediate inferior of the who line screen, and
exposes it at the top of the who-line, making the who-line larger if necessary."
  (OR (EQ (SEND WINDOW :SUPERIOR) WHO-LINE-SCREEN)
      (FERROR "~A is not an immediate inferior of the who line screen" WINDOW))
  (COND ((NOT (SHEET-EXPOSED-P WINDOW))
	 (LET ((H (SHEET-HEIGHT WINDOW)))
	   (SET-WHO-LINE-HEIGHT (+ H (SHEET-HEIGHT WHO-LINE-SCREEN)))
	   (DOLIST (W (COPY-LIST (SHEET-EXPOSED-INFERIORS WHO-LINE-SCREEN)))
	     (SEND W :SET-POSITION (SHEET-X-OFFSET W) (+ H (SHEET-Y-OFFSET W))))
	   (SEND WINDOW :SET-POSITION (SHEET-X-OFFSET WINDOW) 0)
	   (SEND WINDOW :EXPOSE)))))

(DEFUN DELETE-WHO-LINE-WINDOW (WINDOW)
  "Removes WINDOW from display in the who line.
WINDOW must be an inferior of WHO-LINE-SCREEN."
  (OR (EQ (SEND WINDOW :SUPERIOR) WHO-LINE-SCREEN)
      (FERROR "~A is not an immediate inferior of the who line screen" WINDOW))
  (COND ((SHEET-EXPOSED-P WINDOW)
	 (SEND WINDOW :DEACTIVATE)
	 (LET ((H (SHEET-HEIGHT WINDOW)))
	   (DOLIST (W (COPY-LIST (SHEET-EXPOSED-INFERIORS WHO-LINE-SCREEN)))
	     (SEND W :SET-POSITION (SHEET-X-OFFSET W) (- (SHEET-Y-OFFSET W) H)))
	   (SET-WHO-LINE-HEIGHT (- (SHEET-HEIGHT WHO-LINE-SCREEN) H))))))

(DEFUN SET-WHO-LINE-HEIGHT (H)
  "Set height of WHO-LINE-SCREEN to H lines, updating MAIN-SCREEN as well."
  (WITH-MOUSE-USURPED
   (LOCK-SHEET (MAIN-SCREEN)
    (LOCK-SHEET (WHO-LINE-SCREEN)
      (WITHOUT-INTERRUPTS
	(LET ((MS MOUSE-SHEET) (SW SELECTED-WINDOW))
	  (AND (SHEET-ME-OR-MY-KID-P MS MAIN-SCREEN)
	       (SETQ MOUSE-SHEET NIL))
	  (SEND WHO-LINE-SCREEN :DEEXPOSE)
	  (SEND MAIN-SCREEN :DEEXPOSE)
	  (SETQ MOUSE-SHEET MS)
	  (SEND WHO-LINE-SCREEN :CHANGE-OF-SIZE-OR-MARGINS
				:BOTTOM MAIN-SCREEN-HEIGHT
				:TOP (- MAIN-SCREEN-HEIGHT H))
	  (SEND MAIN-SCREEN :CHANGE-OF-SIZE-OR-MARGINS
			    :HEIGHT (- MAIN-SCREEN-HEIGHT (SHEET-HEIGHT WHO-LINE-SCREEN)))
	  (MOUSE-SET-SHEET MS)
	  (SEND MAIN-SCREEN :EXPOSE)
	  (SEND WHO-LINE-SCREEN :EXPOSE)
	  (AND SW (SEND SW :SELECT))))))))

(DEFFLAVOR WHO-LINE-WINDOW () (WHO-LINE-MIXIN WINDOW))

(DEFMETHOD (WHO-LINE-WINDOW :UPDATE) ()
  )

(DEFUN MAKE-WHO-LINE-WINDOW (&REST ARGS)
  "Create a window to be part of the who line.
ARGS are keyword args passed to MAKE-WINDOW.
The keyword :FLAVOR specifies the window flavor
 (default is TV:WHO-LINE-WINDOW).
The window's superior is always WHO-LINE-SCREEN."
  (APPLY #'MAKE-INSTANCE (GETF ARGS ':FLAVOR 'WHO-LINE-WINDOW)
	 :AREA WHO-LINE-AREA
	 :SUPERIOR WHO-LINE-SCREEN
	 ARGS))
