Use local directory files in preference to files from .jar
[jpcrr.git] / org / jpc / plugins / VirtualKeyboard.java
bloba9ed947ac99d82882e279362c14c89bc8c1b010c
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
7 Copyright (C) 2010 Foone Debonte
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 2 as published by
11 the Free Software Foundation.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 Based on JPC x86 PC Hardware emulator,
23 A project from the Physics Dept, The University of Oxford
25 Details about original JPC can be found at:
27 www-jpc.physics.ox.ac.uk
31 package org.jpc.plugins;
33 import org.jpc.Misc;
34 import org.jpc.emulator.peripheral.Keyboard;
35 import org.jpc.emulator.KeyboardStatusListener;
36 import org.jpc.jrsr.UTFInputLineStream;
37 import org.jpc.pluginsbase.Plugins;
38 import org.jpc.pluginsbase.Plugin;
39 import org.jpc.pluginsaux.ConstantTableLayout;
40 import static org.jpc.Misc.errorDialog;
41 import static org.jpc.Misc.moveWindow;
42 import static org.jpc.Misc.parseStringToComponents;
43 import static org.jpc.Misc.openStream;
45 import java.io.*;
46 import javax.swing.*;
47 import javax.swing.border.*;
48 import java.util.*;
49 import java.awt.event.*;
50 import java.awt.*;
51 import javax.swing.plaf.basic.*;
52 import javax.swing.plaf.*;
54 public class VirtualKeyboard implements ActionListener, Plugin, KeyboardStatusListener
56 private JFrame window;
57 private JPanel panel;
58 private HashMap<String, Integer> commandToKey;
59 private HashMap<String, JToggleButton> commandToButton;
60 private JToggleButton capsLock;
61 private JToggleButton numLock;
62 private JToggleButton scrollLock;
63 private Font keyFont;
64 private Font smallKeyFont;
65 private Border keyBorder, smallKeyBorder, classicBorder;
66 private boolean nativeButtons;
67 private static String DEFAULT_KEYBOARD_FILENAME = "datafiles/keyboards/default";
69 private org.jpc.emulator.peripheral.Keyboard keyboard;
70 private int keyNo;
71 private boolean[] cachedState;
72 private Plugins pluginManager;
73 private int nativeWidth, nativeHeight;
75 public JToggleButton addKey(String name, String topKey, int scanCode, int x, int y, int w, int h, char sizeCode,
76 boolean special)
78 String cmdName = name + "-" + (keyNo++);
79 String label = name;
80 if(topKey != null) {
81 label = "<html>" + topKey + "<br>" + name + "</html>";
82 } else if(label.indexOf('&') >= 0) {
83 label = "<html>" + name + "</html>";
85 JToggleButton button = new JToggleButton(label, false);
86 if(sizeCode == 'N') {
87 button.setFont(keyFont);
88 button.setBorder(keyBorder);
89 } else if(sizeCode == 'S') {
90 button.setFont(smallKeyFont);
91 button.setBorder(smallKeyBorder);
92 } else if(sizeCode == 'C' && !nativeButtons) {
93 button.setBorder(classicBorder);
96 button.setRolloverEnabled(false);
97 if(special) {
98 button.setEnabled(false);
99 button.setVisible(false);
100 } else {
101 commandToKey.put(cmdName, new Integer(scanCode));
102 commandToButton.put(cmdName, button);
103 button.setActionCommand(cmdName);
104 button.addActionListener(this);
107 if(!nativeButtons) button.setUI(new KeyboardButtonUI());
109 panel.add(button, new ConstantTableLayout.Placement(x, y, w, h));
110 return button;
113 public void eci_virtualkeyboard_setwinpos(Integer x, Integer y)
115 moveWindow(window, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
118 public VirtualKeyboard(Plugins _pluginManager) throws IOException
120 this(_pluginManager, "");
123 public VirtualKeyboard(Plugins _pluginManager, String args) throws IOException
125 pluginManager = _pluginManager;
126 Map<String, String> params = parseStringToComponents(args);
127 String keyboardPath = params.get("keyboard");
129 nativeButtons = "native".equalsIgnoreCase(params.get("style")) || (keyboardPath == null);
131 keyNo = 0;
132 keyboard = null;
133 commandToKey = new HashMap<String, Integer>();
134 commandToButton = new HashMap<String, JToggleButton>();
135 window = new JFrame("Virtual Keyboard");
136 ConstantTableLayout layout = new ConstantTableLayout();
137 cachedState = new boolean[256];
138 panel = new JPanel(layout);
139 keyFont = new Font("SanSerif", Font.PLAIN, 11);
140 smallKeyFont = keyFont.deriveFont(9.0f);
142 if(nativeButtons) {
143 keyBorder = new EmptyBorder(0, 5, 0, 5);
144 smallKeyBorder = new EmptyBorder(0, 1, 0, 1);
145 // classicBorder isn't used with native buttons
146 } else {
147 Border outerBorder = new CompoundBorder(new EmptyBorder(1, 1, 0, 0), new SimpleButtonBorder(false));
148 keyBorder = new CompoundBorder(outerBorder, new EmptyBorder(0, 3, 0, 3));
149 smallKeyBorder = new CompoundBorder(outerBorder, new EmptyBorder(0, 1, 0, 1));
150 classicBorder = new SimpleButtonBorder(true);
153 window.add(panel);
155 parseKeyboardFile(keyboardPath);
157 window.pack();
158 window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
159 Dimension d = window.getSize();
160 nativeWidth = d.width;
161 nativeHeight = d.height;
162 window.setVisible(true);
165 private static int parseCoord(String value, int next)
167 if("-".equals(value))
168 return next;
169 else
170 return Integer.valueOf(value);
173 private void parseKeyboardFile(String filename) throws IOException
175 InputStream in = openStream(filename, DEFAULT_KEYBOARD_FILENAME);
176 if((in = openStream(filename, DEFAULT_KEYBOARD_FILENAME)) == null)
177 throw new IOException("Neither primary keyboard file nor fallback file exists.");
179 UTFInputLineStream keyboardFile = new UTFInputLineStream(in);
180 String[] line;
181 int nextX = 0, nextY = 0;
183 while((line = Misc.nextParseLine(keyboardFile)) != null)
184 if(line.length <= 1)
185 continue;
186 else if(line.length == 7 || line.length == 8) {
187 int x = parseCoord(line[1], nextX), y = parseCoord(line[2], nextY);
188 int w = Integer.parseInt(line[3]), h = Integer.parseInt(line[4]);
189 char sizeCode = line[5].charAt(0);
190 String name = line[6], shifted = null;
191 if(line.length == 8)
192 shifted = line[7];
194 try {
195 int scanCode = Integer.parseInt(line[0]);
196 addKey(name, shifted, scanCode, x, y, w, h, sizeCode, false);
197 } catch(NumberFormatException nfe) { // The scanCode wasn't a number, so this must be a special key
198 String scanName = line[0];
199 JToggleButton specialButton = addKey(name, null, 0, x, y, w, h, sizeCode, true);
200 if(scanName.equalsIgnoreCase("numlock"))
201 numLock=specialButton;
202 else if(scanName.equalsIgnoreCase("capslock"))
203 capsLock=specialButton;
204 else if(scanName.equalsIgnoreCase("scrolllock"))
205 scrollLock=specialButton;
208 nextX = x + w;
209 nextY = y;
210 } else
211 throw new IOException("Invalid line in keyboard layout.");
214 //-1 if unknown, bit 2 is capslock, bit 1 is numlock, bit 0 is scrollock.
215 private void updateLEDs(int status)
217 if(status < 0) {
218 numLock.setVisible(false);
219 numLock.setSelected(false);
220 capsLock.setVisible(false);
221 capsLock.setSelected(false);
222 scrollLock.setVisible(false);
223 scrollLock.setSelected(false);
224 } else {
225 numLock.setVisible((status & 2) != 0);
226 capsLock.setVisible((status & 4) != 0);
227 scrollLock.setVisible((status & 1) != 0);
231 public void resetButtons()
233 for(Map.Entry<String, Integer> entry : commandToKey.entrySet()) {
234 int scan = entry.getValue().intValue();
235 JToggleButton button = commandToButton.get(entry.getKey());
236 if(keyboard.getKeyStatus((byte)scan) != cachedState[scan]) {
237 cachedState[scan] = keyboard.getKeyStatus((byte)scan);
238 button.setSelected(cachedState[scan]);
241 updateLEDs(keyboard.getLEDStatus());
244 private void keyStatusChangeEventThread(int scancode, boolean pressed)
246 /* THIS IS JUST PLAIN BROKEN.
247 for(Map.Entry<String, Integer> entry : commandToKey.entrySet()) {
248 int scan = entry.getValue().intValue();
249 if(scan != scancode)
250 continue;
251 JToggleButton button = commandToButton.get(entry.getKey());
252 if(pressed != cachedState[scan]) {
253 cachedState[scan] = pressed;
254 button.setSelected(pressed);
260 public void keyExecStatusChange(int scancode, boolean pressed)
262 //These aren't currently shown.
265 public void keyStatusChange(int scancode, boolean pressed)
267 if(!SwingUtilities.isEventDispatchThread())
268 try {
269 final int _scancode = scancode;
270 final boolean _pressed = pressed;
271 SwingUtilities.invokeLater(new Thread() { public void run() {
272 VirtualKeyboard.this.keyStatusChangeEventThread(_scancode, _pressed); }});
273 } catch(Exception e) {
275 else
276 keyStatusChangeEventThread(scancode, pressed);
279 public void keyStatusReload()
281 if(!SwingUtilities.isEventDispatchThread())
282 try {
283 SwingUtilities.invokeLater(new Thread() { public void run() { VirtualKeyboard.this.resetButtons(); }});
284 } catch(Exception e) {
286 else
287 resetButtons();
290 public void ledStatusChange(int newstatus)
292 if(!SwingUtilities.isEventDispatchThread())
293 try {
294 final int _newstatus = newstatus;
295 SwingUtilities.invokeLater(new Thread() { public void run() {
296 VirtualKeyboard.this.updateLEDs(_newstatus); }});
297 } catch(Exception e) {
299 else
300 updateLEDs(newstatus);
303 public void mouseButtonsChange(int newstatus)
305 //Not interesting.
308 public void mouseExecButtonsChange(int newstatus)
310 //Not interesting.
313 public void main()
315 //This runs entierely in UI thread.
318 public boolean systemShutdown()
320 //OK to proceed with JVM shutdown.
321 return true;
324 public void pcStarting()
326 //Not interested.
329 public void pcStopping()
331 if(pluginManager.isShuttingDown())
332 return; //Too much of deadlock risk.
334 if(!SwingUtilities.isEventDispatchThread())
335 try {
336 SwingUtilities.invokeAndWait(new Thread() { public void run() { VirtualKeyboard.this.resetButtons(); }});
337 } catch(Exception e) {
339 else
340 resetButtons();
343 public void reconnect(org.jpc.emulator.PC pc)
345 if(keyboard != null)
346 keyboard.removeStatusListener(this);
347 if(pc != null) {
348 Keyboard keys = (Keyboard)pc.getComponent(Keyboard.class);
349 keyboard = keys;
350 keyboard.addStatusListener(this);
351 keyStatusReload();
352 } else {
353 keyboard = null;
354 Iterator<Map.Entry<String, Integer> > itt = commandToKey.entrySet().iterator();
355 while (itt.hasNext())
357 Map.Entry<String, Integer> entry = itt.next();
358 String n = entry.getKey();
359 Integer s = entry.getValue();
360 cachedState[s.intValue()] = false;
361 commandToButton.get(n).setSelected(false);
362 ledStatusChange(-1);
367 public void actionPerformed(ActionEvent evt)
369 if(keyboard == null)
370 return;
372 String command = evt.getActionCommand();
373 JToggleButton button = commandToButton.get(command);
374 int scan = commandToKey.get(command).intValue();
375 boolean doubleEdge = (scan != 255) && ((evt.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
376 if(button.isSelected())
377 if(doubleEdge)
378 System.err.println("Informational: Keyhit on key " + scan + ".");
379 else
380 System.err.println("Informational: Keydown on key " + scan + ".");
381 else
382 if(doubleEdge)
383 System.err.println("Informational: Keyupdown on key " + scan + ".");
384 else
385 System.err.println("Informational: Keyup on key " + scan + ".");
386 try {
387 keyboard.sendEdge(scan);
388 if(doubleEdge)
389 keyboard.sendEdge(scan);
390 } catch(Exception e) {
391 System.err.println("Error: Sending command failed: " + e);
392 errorDialog(e, "Failed to send keyboard key edge", null, "Dismiss");
394 if(!doubleEdge)
395 cachedState[scan] = !cachedState[scan];
396 button.setSelected(cachedState[scan]);
399 protected static class KeyboardButtonUI extends BasicToggleButtonUI
401 protected static Color highlightColor = new Color(200, 200, 200);
402 protected static Color backgroundColor = new Color(220, 220, 220);
404 protected void simplePaint(Graphics g, JComponent c, Color color)
406 Rectangle viewRect = new Rectangle(c.getSize());
407 Insets margin;
408 try {
409 /* We want to ignore the inner margins while calculating the background size, so we have to
410 * pull the outside border out of the compound border */
411 CompoundBorder border = (CompoundBorder)c.getBorder();
412 margin = border.getOutsideBorder().getBorderInsets(c);
413 } catch(ClassCastException cce) {
414 // We were called on a button without our elaborate triple-border, so default to the whole inset
415 margin = c.getBorder().getBorderInsets(c);
418 g.setColor(color);
420 g.fillRect(viewRect.x + margin.left, viewRect.y + margin.top,
421 viewRect.width - (margin.left + margin.right),
422 viewRect.height - (margin.top + margin.bottom));
425 protected void paintButtonPressed(Graphics g, AbstractButton b)
427 simplePaint(g, b, highlightColor);
430 public void paint(Graphics g, JComponent c)
432 simplePaint(g, c, backgroundColor);
433 super.paint(g, c);
437 protected static class SimpleButtonBorder extends LineBorder
439 private static final long serialVersionUID = 1L;
441 protected static Color nwColor=new Color(240, 240, 240);
442 protected static Color seColor=new Color(130, 130, 130);
443 protected static Color pressedColor=new Color(160, 160, 160);
444 protected boolean thin;
445 public SimpleButtonBorder(boolean thin)
447 super(Color.BLACK, 1, true);
448 this.thin = thin;
451 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width,
452 final int height)
454 Color oldColor = g.getColor();
455 JToggleButton button = (JToggleButton)c;
456 ButtonModel model = button.getModel();
457 int adjust = thin ? 0 : 1;
459 // Draw inner highlights
460 if(model.isSelected() || model.isPressed()) {
461 // Draw the north-west highlight, but in the south-east color
462 g.setColor(seColor);
463 g.drawRect(x + 1, y + 1, width - 2, 0);
464 g.drawRect(x + 1, y + 1, 0, height - 2);
465 } else {
466 // Draw the north-west highlight
467 g.setColor(nwColor);
468 g.drawRect(x + 1, y + 1, width - 2, adjust);
469 g.drawRect(x + 1, y + 1, adjust, height - 2);
470 // Draw the south-east highlight
471 g.setColor(seColor);
472 g.drawRect(x + 1, y + height - 2, width - 2, 0);
473 g.drawRect(x + width - 2, y + 1, 0, height - 2);
474 if(!thin) { // Draw inner line of shadow
475 g.drawRect(x + 2, y + height - 3, width - 3, 0);
476 g.drawRect(x + width - 3, y + 2, 0, height - 3);
480 // Draw actual border
481 g.setColor(model.isPressed() ? pressedColor : lineColor);
482 g.drawRoundRect(x, y, width - 1, height - 1, 2, 2);
484 // Restore color state
485 g.setColor(oldColor);