2 JPC-RR: A x86 PC Hardware Emulator
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
;
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
;
46 import javax
.swing
.border
.*;
48 import java
.awt
.event
.*;
50 import javax
.swing
.plaf
.basic
.*;
51 import javax
.swing
.plaf
.*;
53 public class VirtualKeyboard
implements ActionListener
, Plugin
, KeyboardStatusListener
55 private JFrame window
;
57 private HashMap
<String
, Integer
> commandToKey
;
58 private HashMap
<String
, JToggleButton
> commandToButton
;
59 private JToggleButton capsLock
;
60 private JToggleButton numLock
;
61 private JToggleButton scrollLock
;
63 private Font smallKeyFont
;
64 private Border keyBorder
, smallKeyBorder
, classicBorder
;
65 private boolean nativeButtons
;
66 private static String DEFAULT_KEYBOARD_FILENAME
= "datafiles/keyboards/default";
68 private org
.jpc
.emulator
.peripheral
.Keyboard keyboard
;
70 private boolean[] cachedState
;
71 private Plugins pluginManager
;
72 private int nativeWidth
, nativeHeight
;
74 public JToggleButton
addKey(String name
, String topKey
, int scanCode
, int x
, int y
, int w
, int h
, char sizeCode
,
77 String cmdName
= name
+ "-" + (keyNo
++);
80 label
= "<html>" + topKey
+ "<br>" + name
+ "</html>";
81 } else if(label
.indexOf('&') >= 0) {
82 label
= "<html>" + name
+ "</html>";
84 JToggleButton button
= new JToggleButton(label
, false);
86 button
.setFont(keyFont
);
87 button
.setBorder(keyBorder
);
88 } else if(sizeCode
== 'S') {
89 button
.setFont(smallKeyFont
);
90 button
.setBorder(smallKeyBorder
);
91 } else if(sizeCode
== 'C' && !nativeButtons
) {
92 button
.setBorder(classicBorder
);
95 button
.setRolloverEnabled(false);
97 button
.setEnabled(false);
98 button
.setVisible(false);
100 commandToKey
.put(cmdName
, new Integer(scanCode
));
101 commandToButton
.put(cmdName
, button
);
102 button
.setActionCommand(cmdName
);
103 button
.addActionListener(this);
106 if(!nativeButtons
) button
.setUI(new KeyboardButtonUI());
108 panel
.add(button
, new ConstantTableLayout
.Placement(x
, y
, w
, h
));
112 public void eci_virtualkeyboard_setwinpos(Integer x
, Integer y
)
114 moveWindow(window
, x
.intValue(), y
.intValue(), nativeWidth
, nativeHeight
);
117 public VirtualKeyboard(Plugins _pluginManager
) throws IOException
119 this(_pluginManager
, "");
122 public VirtualKeyboard(Plugins _pluginManager
, String args
) throws IOException
124 pluginManager
= _pluginManager
;
125 Map
<String
, String
> params
= parseStringToComponents(args
);
126 String keyboardPath
= params
.get("keyboard");
128 nativeButtons
= "native".equalsIgnoreCase(params
.get("style")) || (keyboardPath
== null);
132 commandToKey
= new HashMap
<String
, Integer
>();
133 commandToButton
= new HashMap
<String
, JToggleButton
>();
134 window
= new JFrame("Virtual Keyboard");
135 ConstantTableLayout layout
= new ConstantTableLayout();
136 cachedState
= new boolean[256];
137 panel
= new JPanel(layout
);
138 keyFont
= new Font("SanSerif", Font
.PLAIN
, 11);
139 smallKeyFont
= keyFont
.deriveFont(9.0f
);
142 keyBorder
= new EmptyBorder(0, 5, 0, 5);
143 smallKeyBorder
= new EmptyBorder(0, 1, 0, 1);
144 // classicBorder isn't used with native buttons
146 Border outerBorder
= new CompoundBorder(new EmptyBorder(1, 1, 0, 0), new SimpleButtonBorder(false));
147 keyBorder
= new CompoundBorder(outerBorder
, new EmptyBorder(0, 3, 0, 3));
148 smallKeyBorder
= new CompoundBorder(outerBorder
, new EmptyBorder(0, 1, 0, 1));
149 classicBorder
= new SimpleButtonBorder(true);
154 parseKeyboardFile(keyboardPath
);
157 window
.setDefaultCloseOperation(JFrame
.DO_NOTHING_ON_CLOSE
);
158 Dimension d
= window
.getSize();
159 nativeWidth
= d
.width
;
160 nativeHeight
= d
.height
;
161 window
.setVisible(true);
164 private static int parseCoord(String value
, int next
)
166 if("-".equals(value
))
169 return Integer
.valueOf(value
);
172 private void parseKeyboardFile(String filename
) throws IOException
174 InputStream in
= null;
175 in
= ClassLoader
.getSystemResourceAsStream((filename
!= null) ? filename
: DEFAULT_KEYBOARD_FILENAME
);
177 System
.err
.println("Can't load keyboard file '" + filename
+ "', falling back to '" + DEFAULT_KEYBOARD_FILENAME
+ "'.");
178 if((in
= ClassLoader
.getSystemResourceAsStream(DEFAULT_KEYBOARD_FILENAME
)) == null)
179 throw new IOException("Neither primary keyboard file nor fallback file exists.");
182 UTFInputLineStream keyboardFile
= new UTFInputLineStream(in
);
184 int nextX
= 0, nextY
= 0;
186 while((line
= Misc
.nextParseLine(keyboardFile
)) != null)
189 else if(line
.length
== 7 || line
.length
== 8) {
190 int x
= parseCoord(line
[1], nextX
), y
= parseCoord(line
[2], nextY
);
191 int w
= Integer
.parseInt(line
[3]), h
= Integer
.parseInt(line
[4]);
192 char sizeCode
= line
[5].charAt(0);
193 String name
= line
[6], shifted
= null;
198 int scanCode
= Integer
.parseInt(line
[0]);
199 addKey(name
, shifted
, scanCode
, x
, y
, w
, h
, sizeCode
, false);
200 } catch(NumberFormatException nfe
) { // The scanCode wasn't a number, so this must be a special key
201 String scanName
= line
[0];
202 JToggleButton specialButton
= addKey(name
, null, 0, x
, y
, w
, h
, sizeCode
, true);
203 if(scanName
.equalsIgnoreCase("numlock"))
204 numLock
=specialButton
;
205 else if(scanName
.equalsIgnoreCase("capslock"))
206 capsLock
=specialButton
;
207 else if(scanName
.equalsIgnoreCase("scrolllock"))
208 scrollLock
=specialButton
;
214 throw new IOException("Invalid line in keyboard layout.");
217 //-1 if unknown, bit 2 is capslock, bit 1 is numlock, bit 0 is scrollock.
218 private void updateLEDs(int status
)
221 numLock
.setVisible(false);
222 numLock
.setSelected(false);
223 capsLock
.setVisible(false);
224 capsLock
.setSelected(false);
225 scrollLock
.setVisible(false);
226 scrollLock
.setSelected(false);
228 numLock
.setVisible((status
& 2) != 0);
229 capsLock
.setVisible((status
& 4) != 0);
230 scrollLock
.setVisible((status
& 1) != 0);
234 public void resetButtons()
236 for(Map
.Entry
<String
, Integer
> entry
: commandToKey
.entrySet()) {
237 int scan
= entry
.getValue().intValue();
238 JToggleButton button
= commandToButton
.get(entry
.getKey());
239 if(keyboard
.getKeyStatus((byte)scan
) != cachedState
[scan
]) {
240 cachedState
[scan
] = keyboard
.getKeyStatus((byte)scan
);
241 button
.setSelected(cachedState
[scan
]);
244 updateLEDs(keyboard
.getLEDStatus());
247 private void keyStatusChangeEventThread(int scancode
, boolean pressed
)
249 /* THIS IS JUST PLAIN BROKEN.
250 for(Map.Entry<String, Integer> entry : commandToKey.entrySet()) {
251 int scan = entry.getValue().intValue();
254 JToggleButton button = commandToButton.get(entry.getKey());
255 if(pressed != cachedState[scan]) {
256 cachedState[scan] = pressed;
257 button.setSelected(pressed);
263 public void keyExecStatusChange(int scancode
, boolean pressed
)
265 //These aren't currently shown.
268 public void keyStatusChange(int scancode
, boolean pressed
)
270 if(!SwingUtilities
.isEventDispatchThread())
272 final int _scancode
= scancode
;
273 final boolean _pressed
= pressed
;
274 SwingUtilities
.invokeLater(new Thread() { public void run() {
275 VirtualKeyboard
.this.keyStatusChangeEventThread(_scancode
, _pressed
); }});
276 } catch(Exception e
) {
279 keyStatusChangeEventThread(scancode
, pressed
);
282 public void keyStatusReload()
284 if(!SwingUtilities
.isEventDispatchThread())
286 SwingUtilities
.invokeLater(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
287 } catch(Exception e
) {
293 public void ledStatusChange(int newstatus
)
295 if(!SwingUtilities
.isEventDispatchThread())
297 final int _newstatus
= newstatus
;
298 SwingUtilities
.invokeLater(new Thread() { public void run() {
299 VirtualKeyboard
.this.updateLEDs(_newstatus
); }});
300 } catch(Exception e
) {
303 updateLEDs(newstatus
);
306 public void mouseButtonsChange(int newstatus
)
311 public void mouseExecButtonsChange(int newstatus
)
318 //This runs entierely in UI thread.
321 public boolean systemShutdown()
323 //OK to proceed with JVM shutdown.
327 public void pcStarting()
332 public void pcStopping()
334 if(pluginManager
.isShuttingDown())
335 return; //Too much of deadlock risk.
337 if(!SwingUtilities
.isEventDispatchThread())
339 SwingUtilities
.invokeAndWait(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
340 } catch(Exception e
) {
346 public void reconnect(org
.jpc
.emulator
.PC pc
)
349 keyboard
.removeStatusListener(this);
351 Keyboard keys
= (Keyboard
)pc
.getComponent(Keyboard
.class);
353 keyboard
.addStatusListener(this);
357 Iterator
<Map
.Entry
<String
, Integer
> > itt
= commandToKey
.entrySet().iterator();
358 while (itt
.hasNext())
360 Map
.Entry
<String
, Integer
> entry
= itt
.next();
361 String n
= entry
.getKey();
362 Integer s
= entry
.getValue();
363 cachedState
[s
.intValue()] = false;
364 commandToButton
.get(n
).setSelected(false);
370 public void actionPerformed(ActionEvent evt
)
375 String command
= evt
.getActionCommand();
376 JToggleButton button
= commandToButton
.get(command
);
377 int scan
= commandToKey
.get(command
).intValue();
378 boolean doubleEdge
= (scan
!= 255) && ((evt
.getModifiers() & ActionEvent
.SHIFT_MASK
) != 0);
379 if(button
.isSelected())
381 System
.err
.println("Informational: Keyhit on key " + scan
+ ".");
383 System
.err
.println("Informational: Keydown on key " + scan
+ ".");
386 System
.err
.println("Informational: Keyupdown on key " + scan
+ ".");
388 System
.err
.println("Informational: Keyup on key " + scan
+ ".");
390 keyboard
.sendEdge(scan
);
392 keyboard
.sendEdge(scan
);
393 } catch(Exception e
) {
394 System
.err
.println("Error: Sending command failed: " + e
);
395 errorDialog(e
, "Failed to send keyboard key edge", null, "Dismiss");
398 cachedState
[scan
] = !cachedState
[scan
];
399 button
.setSelected(cachedState
[scan
]);
402 protected static class KeyboardButtonUI
extends BasicToggleButtonUI
404 protected static Color highlightColor
= new Color(200, 200, 200);
405 protected static Color backgroundColor
= new Color(220, 220, 220);
407 protected void simplePaint(Graphics g
, JComponent c
, Color color
)
409 Rectangle viewRect
= new Rectangle(c
.getSize());
412 /* We want to ignore the inner margins while calculating the background size, so we have to
413 * pull the outside border out of the compound border */
414 CompoundBorder border
= (CompoundBorder
)c
.getBorder();
415 margin
= border
.getOutsideBorder().getBorderInsets(c
);
416 } catch(ClassCastException cce
) {
417 // We were called on a button without our elaborate triple-border, so default to the whole inset
418 margin
= c
.getBorder().getBorderInsets(c
);
423 g
.fillRect(viewRect
.x
+ margin
.left
, viewRect
.y
+ margin
.top
,
424 viewRect
.width
- (margin
.left
+ margin
.right
),
425 viewRect
.height
- (margin
.top
+ margin
.bottom
));
428 protected void paintButtonPressed(Graphics g
, AbstractButton b
)
430 simplePaint(g
, b
, highlightColor
);
433 public void paint(Graphics g
, JComponent c
)
435 simplePaint(g
, c
, backgroundColor
);
440 protected static class SimpleButtonBorder
extends LineBorder
442 private static final long serialVersionUID
= 1L;
444 protected static Color nwColor
=new Color(240, 240, 240);
445 protected static Color seColor
=new Color(130, 130, 130);
446 protected static Color pressedColor
=new Color(160, 160, 160);
447 protected boolean thin
;
448 public SimpleButtonBorder(boolean thin
)
450 super(Color
.BLACK
, 1, true);
454 public void paintBorder(final Component c
, final Graphics g
, final int x
, final int y
, final int width
,
457 Color oldColor
= g
.getColor();
458 JToggleButton button
= (JToggleButton
)c
;
459 ButtonModel model
= button
.getModel();
460 int adjust
= thin ?
0 : 1;
462 // Draw inner highlights
463 if(model
.isSelected() || model
.isPressed()) {
464 // Draw the north-west highlight, but in the south-east color
466 g
.drawRect(x
+ 1, y
+ 1, width
- 2, 0);
467 g
.drawRect(x
+ 1, y
+ 1, 0, height
- 2);
469 // Draw the north-west highlight
471 g
.drawRect(x
+ 1, y
+ 1, width
- 2, adjust
);
472 g
.drawRect(x
+ 1, y
+ 1, adjust
, height
- 2);
473 // Draw the south-east highlight
475 g
.drawRect(x
+ 1, y
+ height
- 2, width
- 2, 0);
476 g
.drawRect(x
+ width
- 2, y
+ 1, 0, height
- 2);
477 if(!thin
) { // Draw inner line of shadow
478 g
.drawRect(x
+ 2, y
+ height
- 3, width
- 3, 0);
479 g
.drawRect(x
+ width
- 3, y
+ 2, 0, height
- 3);
483 // Draw actual border
484 g
.setColor(model
.isPressed() ? pressedColor
: lineColor
);
485 g
.drawRoundRect(x
, y
, width
- 1, height
- 1, 2, 2);
487 // Restore color state
488 g
.setColor(oldColor
);