1 |
/* |
//(c):Benja Fallenstein |
|
Fallback.java |
|
|
* |
|
|
* Copyright (c) 1999-2001, Ted Nelson and Tuomas Lukka |
|
|
* |
|
|
* You may use and distribute under the terms of either the GNU Lesser |
|
|
* General Public License, either version 2 of the license or, |
|
|
* at your choice, any later version. Alternatively, you may use and |
|
|
* distribute under the terms of the XPL. |
|
|
* |
|
|
* See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of |
|
|
* the licenses. |
|
|
* |
|
|
* This software is distributed in the hope that it will be useful, |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README |
|
|
* file for more details. |
|
|
* |
|
|
*/ |
|
|
/* |
|
|
* Written by Tuomas Lukka |
|
|
*/ |
|
2 |
package gzz.client; |
package gzz.client; |
3 |
import gzz.*; |
import gzz.*; |
|
import gzz.errors.*; |
|
|
import gzz.impl.*; |
|
|
import gzz.mediaserver.*; |
|
|
import gzz.mediaserver.storage.*; |
|
|
import java.util.*; |
|
|
import java.io.*; |
|
|
import gzz.vob.VobScene; |
|
4 |
import java.awt.event.*; |
import java.awt.event.*; |
|
import org.python.util.PythonInterpreter; |
|
|
import gzz.vob.*; |
|
|
|
|
|
import java.awt.*; |
|
|
|
|
|
/** Keybindings for the Fallback client. |
|
|
*/ |
|
|
public class FallbackBinder extends AbstractBinder { |
|
|
public static final String rcsid = "$Id$"; |
|
|
public static boolean dbg = true; |
|
|
private static void p(String s) { if(dbg) pa(s); } |
|
|
private static void pa(String s) { System.err.println(s); } |
|
|
|
|
|
static final int NORMAL = Fallback.NORMAL; |
|
|
static final int TEXT_1 = Fallback.TEXT_1; |
|
|
|
|
|
// Directional operations |
|
|
static final int NONE = 0; |
|
|
static final int CONNECT = 1; |
|
|
static final int DISCONNECT = 2; |
|
|
static final int HOP = 3; |
|
|
static final int NEW = 4; |
|
|
static final int CLONE_R = 5; |
|
|
static final int CLONE_L = 6; |
|
|
|
|
|
/** The current operation waiting for a directional operation. |
|
|
*/ |
|
|
int directOp; |
|
|
|
|
|
/** Our <code>Fallback</code> client. |
|
|
*/ |
|
|
Fallback fallback; |
|
|
|
|
|
/** The text buffer for entering search strings. |
|
|
*/ |
|
|
String buffer; |
|
|
|
|
|
Cell searchStart; |
|
|
|
|
|
public FallbackBinder(Fallback fallback) { |
|
|
this.fallback = fallback; |
|
|
} |
|
|
|
|
|
|
|
|
void dir(int win, int dim, int dir) { |
|
|
Fallback.Win w = fallback.windows[win]; |
|
|
Dim[] dims = w.dims; |
|
|
|
|
|
Cell n = dims[dim].s(w.cursor, dir); |
|
|
Cell c; |
|
|
p("Dir: "+dim+" "+dir+" '"+n+"'"); |
|
|
switch(directOp) { |
|
|
case NONE: if(n != null) w.cursor = n; break; |
|
|
case CONNECT: dims[dim].connect(w.cursor, dir, w.other.cursor); break; |
|
|
case DISCONNECT: dims[dim].disconnect(w.cursor, dir); break; |
|
|
case HOP: dims[dim].hop(w.cursor, dir); break; |
|
|
case NEW: w.cursor = w.cursor.N(dims[dim], dir); break; |
|
|
case CLONE_R: |
|
|
c = fallback.windows[1].cursor.zzclone(); |
|
|
w.cursor.insert(dims[dim], dir, c); |
|
|
w.cursor = c; |
|
|
break; |
|
|
case CLONE_L: |
|
|
c = fallback.windows[0].cursor.zzclone(); |
|
|
w.cursor.insert(dims[dim], dir, c); |
|
|
w.cursor = c; |
|
|
break; |
|
|
default: |
|
|
pa("???"); |
|
|
} |
|
|
directOp = NONE; |
|
|
AbstractUpdateManager.prioritize(this.screen); |
|
|
} |
|
|
|
|
|
void op(int op) { |
|
|
directOp = op; |
|
|
} |
|
|
|
|
|
boolean save() { |
|
|
try { |
|
|
gzz.slices.SliceSpace sp = (gzz.slices.SliceSpace)fallback.space; |
|
|
fallback.filers.saveAll(sp.getSlicer().exportAll()); |
|
|
pa("Saved."); |
|
|
return true; |
|
|
} catch(IOException e) { |
|
|
pa("Exception while saving: "+e); |
|
|
e.printStackTrace(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
public void keystroke(String k) { |
|
|
p("Key: "+k); |
|
|
|
|
|
if(k.equals("\uFFFF")) { |
|
|
p("That strange \\uFFFF again-- ignore"); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
if(fallback.mode == NORMAL) |
|
|
normalModeKeystroke(k); |
|
|
else if(fallback.mode == TEXT_1) |
|
|
textModeKeystroke(k); |
|
|
else if(fallback.mode == fallback.SEARCH) |
|
|
searchModeKeystroke(k); |
|
|
else { |
|
|
System.err.println("Oops, don't know this mode: "+fallback.mode); |
|
|
System.err.println("Resetting to NORMAL."); |
|
|
|
|
|
fallback.mode = NORMAL; |
|
|
} |
|
|
} catch(Error e) { |
|
|
// when a directOp throws an exception, reset the state |
|
|
// so that the next keypress doesn't throw |
|
|
// the same exception again... |
|
|
directOp = NONE; throw e; |
|
|
} catch(RuntimeException e) { |
|
|
directOp = NONE; throw e; |
|
|
} finally { |
|
|
AbstractUpdateManager.chg(); |
|
|
} |
|
|
} |
|
|
|
|
|
public void normalModeKeystroke(String k) { |
|
|
if(k.equals("Tab")) { |
|
|
p("Switching to text edit mode."); |
|
|
fallback.mode = TEXT_1; |
|
|
fallback.windows[1].textCursor = 0; |
|
|
} else if(k.equals("Backspace")) { |
|
|
directOp = NONE; |
|
|
} else if(k.equals("Left")) { |
|
|
dir(1, 0, -1); |
|
|
} else if(k.equals("Right")) { |
|
|
dir(1, 0, 1); |
|
|
} else if(k.equals("Up")) { |
|
|
dir(1, 1, -1); |
|
|
} else if(k.equals("Down")) { |
|
|
dir(1, 1, 1); |
|
|
} else if(k.equals("Ctrl-S")) { |
|
|
save(); |
|
|
} else if(k.equals("Ctrl-E")) { |
|
|
editPlainText(fallback.windows[1].cursor); |
|
|
} else if(k.equals("Ctrl-F")) { |
|
|
fallback.mode = fallback.SEARCH; |
|
|
buffer = ""; |
|
|
searchStart = fallback.windows[1].cursor; |
|
|
} else if(k.length() == 1) { |
|
|
switch(k.charAt(0)) { |
|
|
// Right Rose |
|
|
case 'i': case 'I': dir(1, 1, -1); break; |
|
|
case 'j': case 'J': dir(1, 0, -1); break; |
|
|
case 'l': case 'L': dir(1, 0, 1); break; |
|
|
case ',': case '<': dir(1, 1, 1); break; |
|
|
case 'k': dir(1, 2, 1); break; |
|
|
case 'K': dir(1, 2, -1); break; |
|
|
|
|
|
// Left Rose |
|
|
case 'e': case 'E': dir(0, 1, -1); break; |
|
|
case 's': case 'S': dir(0, 0, -1); break; |
|
|
case 'f': case 'F': dir(0, 0, 1); break; |
|
|
case 'c': case 'C': dir(0, 1, 1); break; |
|
|
case 'd': dir(0, 2, 1); break; |
|
|
case 'D': dir(0, 2, -1); break; |
|
|
|
|
|
// Ops |
|
|
case 'n': op(NEW); break; |
|
|
case '-': op(CONNECT); break; |
|
|
case 'b': op(DISCONNECT); break; |
|
|
case 'h': op(HOP); break; |
|
|
case 't': op(CLONE_R); break; |
|
|
case 'T': op(CLONE_L); break; |
|
|
|
|
|
// Dims |
|
|
case 'x': rotate(1, 0, 1); break; |
|
|
case 'X': rotate(0, 0, 1); break; |
|
|
case 'y': rotate(1, 1, 1); break; |
|
|
case 'Y': rotate(0, 1, 1); break; |
|
|
case 'z': rotate(1, 2, 1); break; |
|
|
case 'Z': rotate(0, 2, 1); break; |
|
|
|
|
|
// Special |
|
|
case 'v': changeView(1, 1); break; |
|
|
case 'V': changeView(0, 1); break; |
|
|
case 'r': |
|
|
fallback.windows[1].cursor = |
|
|
fallback.windows[1].cursor.getRootclone(); |
|
|
break; |
|
|
case 'R': |
|
|
fallback.windows[0].cursor = |
|
|
fallback.windows[0].cursor.getRootclone(); |
|
|
break; |
|
|
case 'q': if(save()) System.exit(0); |
|
|
} |
|
|
} else if(k.length() == 5 && k.startsWith("Alt-")) { |
|
|
switch(k.charAt(4)) { |
|
|
case 'X': rotate(1, 0, -1); break; |
|
|
case 'Y': rotate(1, 1, -1); break; |
|
|
case 'Z': rotate(1, 2, -1); break; |
|
|
case 'V': changeView(1, -1); break; |
|
|
} |
|
|
} else if(k.length() == 11 && k.startsWith("Alt-Shift-")) { |
|
|
switch(k.charAt(10)) { |
|
|
case 'X': rotate(0, 0, -1); break; |
|
|
case 'Y': rotate(0, 1, -1); break; |
|
|
case 'Z': rotate(0, 2, -1); break; |
|
|
case 'V': changeView(0, -1); break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
public void textModeKeystroke(String k) { |
|
|
if(k.equals("Tab")) { |
|
|
p("Switching to normal mode."); |
|
|
fallback.mode = NORMAL; |
|
|
fallback.windows[1].textCursor = -1; |
|
|
} else if(k.equals("Left")) |
|
|
fallback.windows[1].moveTextCursor(-1); |
|
|
else if(k.equals("Right")) |
|
|
fallback.windows[1].moveTextCursor(1); |
|
|
else if(k.equals("Home") || k.equals("Ctrl-A")) |
|
|
fallback.windows[1].textCursor = 0; |
|
|
else if(k.equals("End") || k.equals("Ctrl-E")) |
|
|
fallback.windows[1].textCursor = |
|
|
fallback.windows[1].cursor.t().length(); |
|
|
else if(k.equals("Backspace")) |
|
|
fallback.windows[1].backspaceTextCursor(); |
|
|
else if(k.length() == 1) |
|
|
fallback.windows[1].insertText(""+k); |
|
|
} |
|
|
|
|
|
void searchModeKeystroke(String k) { |
|
|
if(k.equals("Enter")) { fallback.mode = NORMAL; return; } |
|
|
if(k.equals("Shift-Enter")) { |
|
|
fallback.windows[0].cursor = fallback.windows[1].cursor; |
|
|
fallback.windows[1].cursor = searchStart; |
|
|
fallback.mode = NORMAL; |
|
|
return; |
|
|
} |
|
|
if(k.equals("ESC")) { |
|
|
fallback.windows[1].cursor = searchStart; |
|
|
fallback.mode = NORMAL; |
|
|
return; |
|
|
} |
|
|
|
|
|
if(k.length() == 1) buffer += k; |
|
|
else if(k.equals("Backspace")) { |
|
|
if(buffer.equals("")) return; |
|
|
buffer = buffer.substring(0, buffer.length()-1); |
|
|
} |
|
|
|
|
|
if(buffer.length() == 0) { |
|
|
fallback.windows[1].cursor = searchStart; return; |
|
|
} |
|
|
|
|
|
SearchableCellTexter ct = |
|
|
(SearchableCellTexter)fallback.space.getCellTexter(); |
|
|
Collection cs = ct.getStringSearcher().search(buffer); |
|
|
if(cs == null || cs.isEmpty()) { |
|
|
fallback.windows[1].cursor = searchStart; return; |
|
|
} |
|
|
Cell c = (Cell)cs.iterator().next(); |
|
|
fallback.windows[1].cursor = c; |
|
|
} |
|
|
|
|
|
void rotate(int win, int dim, int dir) { |
|
|
int[] ind = fallback.windows[win].dimsIndices; |
|
|
ind[dim] += dir; |
|
|
if(ind[dim] < 0) ind[dim] = fallback.dimList.length - 1; |
|
|
else if(ind[dim] >= fallback.dimList.length) ind[dim] = 0; |
|
|
fallback.windows[win].updateDims(); |
|
|
} |
|
|
|
|
|
void changeView(int win, int dir) { |
|
|
Fallback.Win w = fallback.windows[win]; |
|
|
w.viewIndex += dir; |
|
|
if(w.viewIndex < 0) w.viewIndex = fallback.viewList.length - 1; |
|
|
else if(w.viewIndex >= fallback.viewList.length) w.viewIndex = 0; |
|
|
} |
|
|
|
|
|
/** Edit a cell's content as plain text. |
|
|
* This opens either an external editor, if one is specified in the |
|
|
* system property <code>user.editor</code>, or alternatively uses |
|
|
* an AWT window with a multiline text area (XXX not implemented). |
|
|
*/ |
|
|
public void editPlainText(Cell c) { |
|
|
VStreamCellTexter texter = (VStreamCellTexter)c.space.getCellTexter(); |
|
|
gzz.media.EnfiladeAligner.Instance aligner = |
|
|
texter.startEnfiladeAligner(c); |
|
|
String editor = System.getProperty("user.editor", null); |
|
|
String text = aligner.getString(); |
|
|
if(editor != null) { |
|
|
text = execExternalEditor(editor, text, "UTF-8"); |
|
|
} else { |
|
|
final Dialog win = |
|
|
new Dialog(new Frame(), "Gzz text editor", true); |
|
|
win.setLocation(0, 0); |
|
|
win.setSize(600, 400); |
|
|
TextArea ta = new TextArea(text); |
|
|
win.add(BorderLayout.CENTER, ta); |
|
|
Panel p = new Panel(), q = new Panel(); |
|
|
win.add(BorderLayout.SOUTH, p); |
|
|
p.setLayout(new BorderLayout()); |
|
|
p.add(q, BorderLayout.CENTER); |
|
|
q.setLayout(new FlowLayout()); |
|
|
Button ok = new Button("OK"), cancel = new Button("Cancel"); |
|
|
q.add(ok); q.add(cancel); |
|
|
/*p.add(new Label("(You can use Ctrl-E to close this box.)"), |
|
|
BorderLayout.SOUTH);*/ |
|
|
|
|
|
is_ok = false; |
|
|
ok.addActionListener(new ActionListener() { |
|
|
public void actionPerformed(ActionEvent e) { |
|
|
is_ok = true; win.setVisible(false); |
|
|
} |
|
|
}); |
|
|
cancel.addActionListener(new ActionListener() { |
|
|
public void actionPerformed(ActionEvent e) { |
|
|
is_ok = false; win.setVisible(false); |
|
|
} |
|
|
}); |
|
|
|
|
|
/* |
|
|
KeyListener kl = new KeyListener() { |
|
|
public void keyPressed(KeyEvent e) {} |
|
|
public void keyTyped(KeyEvent e) {} |
|
|
public void keyReleased(KeyEvent e) { |
|
|
if(e.getKeyCode() == (e.VK_CONTROL | e.VK_E)) { |
|
|
is_ok = true; win.setVisible(false); |
|
|
} else if(e.getKeyCode() == (e.VK_ESCAPE)) { |
|
|
is_ok = false; win.setVisible(false); |
|
|
} |
|
|
} |
|
|
}; |
|
|
win.addKeyListener(kl); |
|
|
ta.addKeyListener(kl); |
|
|
*/ |
|
|
|
|
|
win.setVisible(true); |
|
|
if(!is_ok) return; |
|
|
text = ta.getText(); |
|
|
} |
|
|
texter.setEnfilade(c, aligner.getEnfilade(text)); |
|
|
} |
|
|
private boolean is_ok; |
|
|
|
|
|
/** Execute the external editor with the given input and return the |
|
|
* output. |
|
|
* @param editor The name of the editor. The filename is appended |
|
|
* to this after a space, so the editor can be something like |
|
|
* <pre> |
|
|
* perl -pi -es/bcd/foo/g |
|
|
* </pre> |
|
|
* The important point is that it has to put its output in the |
|
|
* same file as it got its input from. |
|
|
* @param text The text to be edited. |
|
|
* @param encoding The encoding to use when writing the characters |
|
|
* to the disk. |
|
|
* Currently recommended: ISO8859_1, but others |
|
|
* may well work. |
|
|
* @returns The edited text. |
|
|
*/ |
|
|
String execExternalEditor(String editor, String text, String encoding) { |
|
|
File tmp = null; |
|
|
|
|
|
try { |
|
|
File dir = new File(System.getProperty("user.dir")); |
|
|
tmp = gzz.util.TestingUtil.tmpFile(dir); |
|
|
|
|
|
byte[] out = text.getBytes(encoding); |
|
|
FileOutputStream fout = new FileOutputStream(tmp); |
|
|
fout.write(out); |
|
|
fout.close(); |
|
|
|
|
|
Process p = Runtime.getRuntime() |
|
|
.exec(editor+" "+tmp.getAbsolutePath()); |
|
|
p.waitFor(); |
|
|
|
|
|
int l = (int)tmp.length(); |
|
|
FileInputStream fin = new FileInputStream(tmp); |
|
|
byte[] in = new byte[l]; |
|
|
if(fin.read(in) != in.length) |
|
|
throw new Error("Couldn't read fully"); |
|
|
fin.close(); |
|
|
|
|
|
return new String(in, encoding); |
|
|
} catch(Exception e) { |
|
|
e.printStackTrace(); |
|
|
throw new Error("Execing editor "+e); |
|
|
} finally { |
|
|
p("deleting "+tmp+" now"); |
|
|
if(tmp != null) tmp.delete(); |
|
|
} |
|
|
} |
|
|
|
|
|
public void timeout(Object o) { |
|
|
} |
|
|
} |
|
5 |
|
|
6 |
|
public interface FallbackBinder { |
7 |
|
void keystroke(String k, Fallback.Win win); |
8 |
|
void mouse(MouseEvent e, Fallback.Win win); |
9 |
|
void windowClosed(Fallback.Win win); |
10 |
|
} |