Artifact 2adc9c337c20cdbe85b828b52b38240e5487f312f52f4d5160c33fb395f3955e:
- Executable file
r37/lisp/csl/jlisp/CWin.java
— part of check-in
[f2fda60abd]
at
2011-09-02 18:13:33
on branch master
— Some historical releases purely for archival purposes
git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/trunk/historical@1375 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 29856) [annotate] [blame] [check-ins using] [more...]
// // CWin.java Copyright A C Norman, 2000 // // // // This code is to provide a windowed interface to programs that // were originally command-line oriented. Much of the design is // based on a version I had implemented using the Microsoft Foundation // Classes and used to support the CSL Lisp system. // // There is a single window that displays in a fixed-pitch font. // It has menus: // // FILE: read save_as save_selection to_file print print_selection exit // EDIT: cut copy paste select_all clear top bottom // FONT: reset-font reset-window-size // plain/bold (radio-style) // 8/10/12/14/16/18/24/36/48 (radio-style) // (I should almost certainly have a "custom" font size too) // BREAK: interrupt backtrace page_mode exit // HELP: contents search help_on_help about // // Text on the screen falls into three parts: // (a) Text that has been generated by the program being run // together with the echo of earlier input from the user; // (b) Prompt text, generated by the system when input is wanted; // (c) A currently active input line that can be edited and that // will in due course be sent to the program // there is also additional invisible additional components: // (d) Buffered key-strokes and events they have not yet been // reflected on the display; // (e) material on the clip-board as for CUT and PASTE operations. // // Each displayed character will have attributes, notably its font and // colour. The text overall structured into paragraphs each of which // contains a sequence of character-runs. The first paragraph starts // at the start of the buffer and a new paragraph starts at the beginning // of each bit of prompt text. Output text is displayed in black, // prompts in red and the active input line in dark blue (these colours // are of course configurable at some level). // // There will in general be five positions noted on the screen: // dot: where the mouse has most recently been clicked or // as positioned using cursor keys; // mark: a previous position of "dot", so that material from mark // to dot is "selected". If mark=dot then there is no selection; // save: the most recent position dot had had while it was within the // active input line. This is where user-generated input is // placed (keystrokes or PASTE operations); // last: the start of the active input line; // end: the end of the entire material on the screen. // // (last is maybe the most important pointer!) // // Various events may be generated by mouse action or keystroke. Many of // those can be handled just as if the whole document was a single simple // area of text, so I will comment here on ones that I want to have // behavious that does not match that of a Java default editor. I will // list things in terms of the Java Action Names (but with "Action" trimmed): // // In the main description here the window can be in one of four states: // (1) The client program has asked for input. A prompt has been displayed // and any input that the user has typed can be edited before it // is sent to the client; // (2) The client has NOT yet asked for input, but output from it is being // displayed in an ordinary sort of manner; // (3) As (2) except that the user had selected "page mode" output and // the current page has been filled. No more output will be // added to the display until the user presses a key; // (4) starting from (1), (2) or (3) the user has activated an // "interrupt" facility. A message to that effect is being sent to the // client program and in the meanwhile any output it tries to send is // just ignored. Any input that the user had already typed (if in // state (1)) has been discarded. The client program is expected // to send an explicit acknowledgement at some stage to move things // back into state (2) and from there maybe into state (1). // while in state (1) the client program is not permitted to send any // output to the window: it is supposed to be silently awaiting input. // // Now back to my list of actions: // // copy // Send the current selection to the clipboard. I would like to do this // dumping stuff there in two formats. One should be simple text // and contains the characters visible on the screen with no // associated attributes (and if I later extend things to allow // non-textual information that gets turned into a very brief // text message). Then another format is specific to this code and // preserves character attributes. // cut // Transfers all the text from the selected region to the clipbopard // as for "copy", but then reduces the selection to be its intersection // with the current active line before deleting anything. If the // current selection has no overlap with the current active line // I would like the CUT item on the edit menu disabled, and the // keyboard short-cut to give a beep. // defaultKeyTyped // If in page break mode release screen and ignore character, // adjusting window title as relevant. // If there is a current selection reduce it to be its intersection // with the active input line. If a selection remains delete that text. // If no active region exists (see later) push this event onto a // queue of things to be done when one is created. Otherwise // if dot (which now is the same as mark) lies outside the active line // then move it to save. Insert the key there. // insertBreak // If there is no active region queue this until there is one. // Stick newline character on end of active region. // Post active region to the client software as next line of input. // set status as "no active region present" //============================================================================ // What I have just described has just one line editable. Part of a result // of this is that the paste operation is a bit odd if the clipboard // contains multiple lines of stuff. It also means that if an input expression // is typed over several lines only the last of those can be corrected. // An alternative would be to have a more general idea of "un-processed // input text" which could then span several lines. But to make that seem // really sensible an input sentence should end where the client program // wanted it to, and accepting a chunk of input should make a break after // the (typically) semicolon involved so that output from processing that // sentence was put in the obvious place. By making it appear as if // data transfers to the client were character by character it would be // possible to tell where to insert the output. But now one can not quite // easily use the ENTER key to send data to the client program: I do not // know quite what would be best to do. A half-baked version of this would // let multi-line active regions arise just out of paste operations or // maybe by haing a special key sequence to allow the user to insert // newlines into text without activating the "accept" action. // // A further thought is that input should always be typed in without newlines // but that the system formats it as you go if it is a bit long. I think I // view as too hard for me to support in any general context at all. It would // involve much more integration between the parser for my input syntax // and the display systen. //============================================================================ // insertTab // As defaultKeyTyped, but tabs should display as at least // one blank and then enough to pad to a multiple of 8 characters. // [Maybe tab should insert the proper number of spaces rather than a // tab character] // paste // Grab material from the clipboard. Inspect attributes and // discard characters that were part of a prompt string. Set up a queue // as if the remaining characters were typed by the user. // Note that stuff is retrieved from the clipboard when the PASTE is // requested but multi-line pastes may stay in an input queue for // processing for some time. // See above comments about the messiness here. // insertContent // <just what is the relationship between this and PASTE?> // deleteNextChar // If there is no active region queue this until there is one. // If dot is outside the active line move it to save before attempting // the delection. If dot is at the end of the document beep and do // nothing. // deletePrevChar // If there is no active region queue this until there is one. // If dot is outside the active line move it to save before attempting // the delection. If dot is at the start of the active line beep // and do nothing. // down (and also up) // Normal: move dot down/up one line (keepin in some column if possible) // Shift: move so to create or extend a selection // Ctrl: move dot down/up one paragraph // Shift/Ctrl: combine two effects // ALT: MAYBE... // If no active region then queue up. // activate a doskey-like record of previous input lines // [keyboard combinations to do with the arrow keys already seem pretty // heavy. Fitting doskey-like stuff in too seems slightly messy. I do not // know what is best here] // left (and also right) // Normal: move dot one character // Shift: move horizontally so as to create/extend a selection // Ctrl: move to start of previous/next word // home/end keys move to start and end of current line // <many actions which reset mark> // if new mark will be outside the active region but old was within // then set save. // // To allow discussion of other key sequences, eg based on ALT or // the controll key (and possibly with additional menus in support // that I have not listed before, I will include a psuedo action // that is intended to cover both keyboard shortcuts implemented // within Java's framework and special keys that I will define for // myself: // // specialKeySequencePressed // Maybe ALT-up belongs here, since it is not even related to // the things that Java's default editorKit does. // // In addition I will want to take an action when the EDIT menu is // about to be displayed: // // showEditMenu // This is to remind me that I want CUT disabled when the selection // is empty or totally outside the active region // // and I should consider some other menu items // // fileRead // for CSL I make this insert the text <in "filename";> as new // input. This is a jolly odd thing to do! // fileSaveAs // fileSaveSelection // fileSaveExtras // PSL has special menu items for copying (etc) parts of the // text that are not just simple text. // fileToFile // by which I mean spool/dripple or whatever you want to call it // filePrint // fileOPrintSelection // fileExit // editCut // disabled if no part of the selection is in the active region // editCutCut // Maybe I should let people discard some of the historical output // if they REALLY want to. // editCopy // editPaste // editSelectAll // editClear // editTop // editBottom // // breakInterrupt // discards all queued up input events. If there is an active // area clears it and sends an INTERRUPT response to the client // application. If page mode is on resets so that another full page // can be displayed before a further pause. // If no active input region then sets flag so that subsequent // printText requests will be ignored and sends an asynchronous // break request to the application. Restones sanity on getting // a response. // // the other menus are fairly decoupled from the display of text so // I will not discuss them here. // // Yet further events reflect interaction with the program that this // window is serving: // // requestLine(promptString) // Insert print test as start of a new paragraph. Establish new // active region, putting save in it. If there are queued input // events they are dispatched until one of them is an insertBreak. // acceptLine // as mentioned under insertBreak. and breakInterrupt // printText // This should not happen while there is an active region. It // just appends text to the end of the buffer. If page_mode has // been set then count lines and set a page break pause when // a screenful is there. This is released by any key-stroke. // Think a bit about users who re-size the screen while output is // being generated! Adjust window title while this delay is in // force, and also allow invisible buffer of at least a few more // lines of output to build up even while the screen is kept // stable. // asynchronousBreak // sent to the client in nasty cases! // breakACK // resonse from client to say that the break is acknowledged. // // printPicture // It MIGHT be that the program can "print" things other than text, // for instance graphical objects. Support for that sort of thing // is not considered here (yet). For REDUCE the idea of displays with // lots of fonts and general 2D layout may well be important! // Current THOUGHT // start2DOutput(); // printText(...); material sent in a format derived from // TeX markup (since I want to display maths, and // because REDUCE already contains an option to // do this). // end2DOutput(); // // Yet another issue is that the client program should be able to make // calls that display (small amounts of) information on the title or // menu bar (eg garbage collection or timing information). And the // windowed front-end should buffer all output from the user (with great // vigour to try to keep performance under control) but should flush the // buffers before requesting input or 2 (say) second after the last // screen update happened. // // For computer algebra the issue of 2D output is a very real one and the // yet nastier issue of managing to COPY or CUT from it and get something // that can be pasted in as input is hard enough for whole expressions (but // one could have a protocol where the application always provided a // re-inputable version as well as the display-friendly one) but gets // worse if you want natural ways to allow mouse-drags to select // semantically valid parts of a huge expression. // // With one earlier user-interface I had (CML) some user keystrokes could // lead to displays that involved special symbols (in that case if you typed // in "fn" you got a display of a Greek lambda, while 'a, 'b etc gave // alpha, beta and so on. Then using the delete key has to repair things // in a witty manner. import java.io.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; public class CWin extends JApplet { // This code should be launchable as either an applet or as // an application. Note however that as of spring 2000 the use // of Java 2 facilities means that web browsers will need a // Java 1.2 plugin to be able to run this code, and that the HTML // that launches the applet will have to dircet execution to that // plugin. Furthermore in order that the file-system and clipboard // can be accesses the applet will have to come from a signed // source and suitable steps will have to be taken to grant it // authority to perform these security-impinging actions. static boolean isApplet; String [] args = new String [0]; public static void main(String [] args) { // If the command line includes an argument "-w" then I will remove // that so it doe not get looked at again and start the system up as a simple // application to run from the command line rather than in a window. // // The effect is that I can rin things in THREE ways: // (1) As an applet, ie using a browser of some form. In such cases // it is hard to pass (variable) parameters; // (2) As an application but one that pops up a window and then behaves // much like an applet EXCEPT that it does not suffer from security // limits as much and you can re-size the window; // (3) As a command line application via "java -jar <whatever.jar> -w" // where the thing behaves as a traditional application. // for (int i=0; i<args.length; i++) { if (args[i].equalsIgnoreCase("-w")) { String [] newArgs = new String[args.length-1]; for (int j=0; j<i; j++) newArgs[j] = args[j]; for (int j=i+1; j<args.length; j++) newArgs[j-1] = args[j]; Jlisp.startup(newArgs, new InputStreamReader(System.in), new PrintWriter(System.out), true); return; } } CWin m = new CWin(); m.isApplet = false; m.args = args; JFrame f = new JFrame("Codemist"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.getContentPane().add(m, BorderLayout.CENTER); // The default size I set up here would not be too good for // people running with low resolution screen-modes. The width of // 850 is designed to give 80 columns of 16-point monospaced, at least // on a Windows implementation at the time I first tried it. I will // need to re-visit the issue of setting the size of my Frame and // the choice of font. What I will do in the applet is to try to // adjust my (default) font size to get around 80 characters across // whatever window size happens. Actually when I create the fonts // I will select them to accieve 80-columns, so what I mean here // is that a width of 830 seems to manage to give me 80 columns at // 16 points, which looks reasonable on a 1280x1024 display! f.setSize(830, 700); // Note (horribly!) for the font-size adjustment to work I seem to have to // make the Frame visible before I try to measure things. f.setVisible(true); m.init(); m.start(); } public CWin() { isApplet = true; // will be reset to false if I am an application } // I put references to all the major sub-components in the // top level of the CWin class. This has the disadvantage of // tending to lock me into a single-document model, but the advantage // that a single handle on the instance of a CWin gives me easy // access to all the rest. Container container; // top of the JApplet JScrollPane scroll; // to make text scrollable InputPane textpane; // major visible component MainThread program; // task that is being supported public void init() { // I want the window to be filled with a scrollable JTextPane that // is editable and has my choice of font. // The vertical scrollbar is always visible because that makes the // width of the visible pane constant and so helps me keep my line-length // understandable. textpane = new InputPane(); scroll = new JScrollPane( textpane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); (container = getContentPane()).add(scroll); // because I have told the TextPane that its size can shrink below that // of the ScrollPane I can be left with something else visible. So I need // to make sure that ends up the same colour! // // Leave this off to see the size of the textpane more clearly // container.setBackground(new Color(yellowColor)); @@@@@@ // Menus hang off a menu bar and contain menu items JMenuBar bar; JMenu mm; setJMenuBar(bar = new JMenuBar()); bar.add(mm = new JMenu("File")); //============================================================================ // In various versions of the JDK (the issue is supposed to be closed // from 1.2.2-002 onwards) if you type a key with the ALT qualifier then // the character will be used BOTH to trigger the menu activity (as the // mnemonic requested) and to send data (without an ALT qualifier visible!) // to the application. So if you type ALT-F/S the there is a BIG change // that an "f" character will end up inserted in your window. At present // my view is that I will fetch a mended JDK as soon as I can and expect // others who try this code to do the same. It seems there are no very // tidy work-arounds. I hope very much that this comment will end up being // one of those humerous historical asides that sometimes add ornament to // code that would otherwise be very dull! //============================================================================ mm.setMnemonic('F'); // As I create each menu item I set up a listener that gets invoked // whenever the user activates the menu concerned. addMenu(mm, "Clear", new ActionListener() { public void actionPerformed(ActionEvent e) { textpane.setText(""); textpane.afterPrompt = 0; } }, 'C', KeyEvent.VK_N); // as for "New" addMenu(mm, "Open", new ActionListener() { public void actionPerformed(ActionEvent e) { try { JFileChooser ch = new JFileChooser(""); ExtensionFileFilter f = new ExtensionFileFilter( new String [] {"red", "tst"}, "REDUCE source and test files"); ch.setFileFilter(f); int r = ch.showOpenDialog(container); if (r == JFileChooser.APPROVE_OPTION) { String fileName = ch.getSelectedFile().getAbsolutePath(); // toScreen("in \"" + fileName + "\";"); // and wait for user to hit ENTER to accept it! } } catch (Exception e1) { // toScreen("++++ Could not access the file\n"); } } }, 'O', 0); addMenu(mm, "Save", new ActionListener() { public void actionPerformed(ActionEvent e) { } }, 'S', 0); // If I am running as an applet I am not permitted to quit. I may // be killed by my browser. If however I am an application I can // provide an "Exit" menu item. if (!isApplet) { addMenu(mm, "Exit", new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }, 'X', 0); } bar.add(mm = new JMenu("Edit")); mm.setMnemonic('E'); addMenu(mm, "Cut", new ActionListener() { public void actionPerformed(ActionEvent e) { textpane.cut.actionPerformed(e); } }, 'T', KeyEvent.VK_X); addMenu(mm, "Copy", new ActionListener() { public void actionPerformed(ActionEvent e) { textpane.copy.actionPerformed(e); } }, 'C', KeyEvent.VK_C); addMenu(mm, "Paste", new ActionListener() { public void actionPerformed(ActionEvent e) { textpane.paste.actionPerformed(e); } }, 'P', KeyEvent.VK_V); bar.add(mm = new JMenu("Font")); mm.setMnemonic('O'); ActionListener setFont = new ActionListener() { public void actionPerformed(ActionEvent e) { String s = e.getActionCommand(); int weight = textpane.fontWeight, size = textpane.fontWeight; if (s.equals("Bold")) weight = Font.BOLD; else if (s.equals("Plain")) weight = Font.PLAIN; else size = Integer.parseInt(s); textpane.setupFonts(size, weight); } }; ButtonGroup buttonGroup = new ButtonGroup(); addMenuRadio(mm, buttonGroup, "Plain", 'P', true, setFont); addMenuRadio(mm, buttonGroup, "Bold", 'B', false, setFont); mm.addSeparator(); buttonGroup = new ButtonGroup(); int [] fontSizes = {8, 10, 12, 14, 16, 18, 24, 36, 48}; for (int i=0; i<fontSizes.length; i++) { int sz = fontSizes[i]; char c; // Given the particular collection of font sizes that I support here // I will provide mnemonics for as many as I easily can. if (sz == 8) c = 's'; // Small else if (sz < 20) c = (char)('0' + (sz - 10)); else if (sz == 24) c = 'm'; // Medium else if (sz == 36) c = '3'; else c = 'x'; // eXtra large addMenuRadio(mm, buttonGroup, Integer.toString(sz), c, sz == textpane.fontSize, setFont); } bar.add(mm = new JMenu("Help")); // setHelpMenu? Not implemented yet! mm.setMnemonic('H'); addMenu(mm, "Contents", new ActionListener() { public void actionPerformed(ActionEvent e) { } }, 'C', 0); program = new MainThread(this); } int getLineLength() { int w = textpane.metrics.charWidth('x'); int w1 = getWidth(); // of the CWin JApplet Insets ins = textpane.getBorder().getBorderInsets(textpane); int scrollWidth = scroll.getVerticalScrollBar().getMinimumSize().width; w1 = w1 - ins.left - ins.right - scrollWidth; return w1/w; } public void start() { program.start(); } void addMenu(JMenu menu, String name, ActionListener a, char mnemonic, int accel) { JMenuItem menuItem = new JMenuItem(name); menuItem.setMnemonic(mnemonic); if (accel != 0) { menuItem.setAccelerator( KeyStroke.getKeyStroke(accel, KeyEvent.CTRL_MASK)); } menu.add(menuItem); menuItem.addActionListener(a); } void addMenuRadio(JMenu menu, ButtonGroup buttonGroup, String name, char mnemonic, boolean ticked, ActionListener a) { JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(name, ticked); menu.add(menuItem); if (mnemonic != ' ') menuItem.setMnemonic(mnemonic); buttonGroup.add(menuItem); menuItem.addActionListener(a); } } // end of the CWin class // The next collection of classes are concerned with moving data between // the windowed front-end and the code that it is there to support. class CWinReader extends Reader { MainThread body; String s; int pos, len; CWinReader(MainThread body) { this.body = body; s = null; } public int available() { if (s != null) return 1; else return 0; } public void close() { } public boolean markSupported() { return false; } public int read() { if (s == null) { body.out.flush(); body.t.stop(); // do not want flush events while reading s = body.host.textpane.getInputLine(); pos = 0; len = s.length(); body.t.restart(); body.t.start(); } if (pos == len) { s = null; return (int)'\n'; } else return (int)s.charAt(pos++); } // I am NOT expecting to want to read huge amounts from the // input window, and I expect performance to be limited by the speed // that humans can type. So I feel fairly happy implementing the // block input methods so they actually work one byte at a time. // This may be a BIT silly if the user has generated a load of stuff // using PASTE and perhaps I ought to be a bit cleverer anyway. public int read(char [] b) { if (b.length == 0) return 0; b[0] = (char)read(); return 1; } public int read(char [] b, int off, int len) { if (b.length == 0 || len == 0) return 0; b[off] = (char)read(); return 1; } } class CWinWriter extends CharArrayWriter { // As with reading from the window, the interface I support here is one // that works in terms of CHARACTERS not BYTES. This has to be synchronized // because I set up a timer that can flush it at slightly unpredictable // times (so that I can buffer LOTS when my code is generating output fast, // and so get decent performance, but so that the screen is refereshed every // couple of seconds) CWin host; CWinWriter(CWin host) { super(8000); // nice big buffer by default this.host = host; } public void close() { flush(); } public void flush() { super.flush(); if (size() != 0) // mild optimisation, I suppose! { // The write-up of the Writer class telle me to lock this way in sub-classes. // Here I MUST ensure that if I do the toString() that I do the reset() // before anybody adds any more characters to this stream. synchronized (lock) { host.textpane.toScreen(toString()); reset(); } } host.program.t.restart(); // wait 2 secs before forced flush } } // The MainThread class is used to encapsulate the behaviour that // might otherwise have gone into a dull command-line application. class MainThread extends Thread { CWin host; Reader in; // NB a Reader not an InputStream PrintWriter out; // NB a Writer not and OutputStream static final int screenRefreshInterval = 3000; Timer t; MainThread(CWin host) { this.host = host; in = new CWinReader(this); out = new PrintWriter(new CWinWriter(host), false); t = new Timer(screenRefreshInterval, new ActionListener() { public void actionPerformed(ActionEvent e) { out.flush(); } }); t.setCoalesce(true); t.start(); } public void run() { Jlisp j = new Jlisp(); Jlisp.startup(host.args, in, out, false); out.flush(); } } // end of MainThread class // end of CWin.java