We have produced our basic interactive calculator with an ncurses interface, but it’s a bit unsatisfying. The UI code has a somewhat more intimate knowledge of the internals of the engine than is really necessary, which means writing new user interfaces is difficult. What we really want to do is to switch this around from a system where the UI does the polling to one where the event loop is located in the engine, and the UI knows only as much as it needs to display the calculator as seen by the specific interface.
What we want to produce is a system whereby the user interface sets itself up, paints its appearance, then invokes the engine. The engine will then call back to the user interface to inform it of state changes and to tell it when and how to redraw itself.
We’ll put this in a new namespace, and create a base class from which real UIs will be derived. The base class will contain many slots which describe those things that it might need in order to display correctly. Generic functions will fill in these slots. We’re defining the base class slots with readers, rather than accessors, to emphasize that the derived classes that define the user interfaces should not be modifying these fields, only reading them. UIs that must take specialized action when one of these setter methods is called should generally use an :after method, unless there is a compelling reason to do something different. The painting method will not have a base class definition, as every user interface will be different.
We will augment the list of generic functions as necessary. For now, this list is a set of functions related to drawing of the user interface. We have not yet written a definition for generic functions related to keypresses and input.
The current generic function definitions are found in a new file, ui.lisp. They are reproduced here:
ui.lisp
(defpackage :HP67-UI
(:use :COMMON-LISP)
(:export
))
(in-package :HP67-UI)
(defclass ui-base ()
((active-keys :reader get-active-keys
:initform nil)
(active-shift :reader get-active-shift
:initform nil)
(active-mode :reader get-active-mode
:initform nil)
(display-mode :reader get-display-mode
:initform nil)
(display-digits :reader get-display-digits
:initform nil)
(error-text :reader get-error-text
:initform "")
(stack-real-contents :reader get-stack-real-contents
:initform nil)
(stack-imag-contents :reader get-stack-imag-contents
:initform nil)
(memory-contents :reader get-memory-contents
:initform nil)
(program-contents :reader get-program-contents
:initform nil)
(program-counter :reader get-program-counter
:initform nil)))
(defgeneric ui-set-active-keys (ui active-key-list)
(:documentation "Used to inform the UI of what keys can be
pressed given the current state of the calculator. The engine
will attempt not to call this function unless the active keys
list has changed."))
(defgeneric ui-set-active-shift (ui active-shift)
(:documentation "Used to inform the UI that a shift key, (F, G,
or H) has been pressed."))
(defgeneric ui-set-active-mode (ui active-mode)
(:documentation "Used to inform the UI of any change in
mode (interactive, program, running). The engine will attempt
not to call this function unless the active mode has changed."))
(defgeneric ui-set-display-mode (ui display-mode display-digits)
(:documentation "Used to inform the UI what the display mode
is. The engine will attempt not to call this function unless the
display mode has changed."))
(defgeneric ui-set-complex-mode (ui how)
(:documentation "Used to inform the UI whether or not we want
to display complext numbers. If 'how' is non-nil, complex
display is requested. The engine will attempt not to call this
function unless the complex mode has changed."))
(defgeneric ui-set-error-text (ui error-text)
(:documentation "Used to inform the UI when an error has
occured. The engine will not call this function unless an error
has just been set, or just been cleared."))
(defgeneric ui-has-error-text (ui)
(:documentation "Used by the UI to establish whether there is
an error message to display."))
(defgeneric ui-clear-stack-contents (ui max-depth)
(:documentation "Erase the UI's knowledge of the stack
contents, and allocate space for up values with depth up to
max-depth. The engine will call this when setting up for a
paint."))
(defgeneric ui-add-stack-value (ui stack-depth
real-part-string
&optional imag-part-string)
(:documentation "Inform the UI of the contents of the stack. X
is at depth=0. The engine will call this at least once when
setting up for a paint."))
(defgeneric ui-clear-memory-contents (ui)
(:documentation "Erase the UI's knowledge of the memory
contents. The engine will call this when setting up for a
paint."))
(defgeneric ui-add-memory-value (ui precedence label
real-part-string
&optional imag-part-string)
(:documentation "Inform the UI of the contents of memory. When
not enough space is available to display all memory contents,
those with higher precedence should be displayed ahead of those
with lower. The engine may call this one or more times when
setting up for a paint."))
(defgeneric ui-clear-program-contents (ui)
(:documentation "Erase the UI's knowledge of program memory.
The engine will normally not call this unless program steps have
been deleted."))
(defgeneric ui-add-program-step (ui step-num display-string)
(:documentation "Inform the UI of the contents of a single step
of memory. Step-num will be unique. The engine will call
whenever new program steps are present."))
(defgeneric ui-get-program-step-string (ui step-num)
(:documentation "Used by the UI to retrieve a particular step
number from the program memory."))
(defgeneric ui-set-program-counter (ui)
(:documentation "Inform the UI of what program step would next
be executed if program execution were to begin. The engine will
attempt to call this only when it has changed."))
(defgeneric ui-paint (ui)
(:documentation "Ask the UI to repaint itself based on its new
settings."))
The current code is checked into the git repository under the tag v2014-11-30.