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
;
43 import static org
.jpc
.Misc
.openStream
;
47 import javax
.swing
.border
.*;
49 import java
.awt
.event
.*;
51 import javax
.swing
.plaf
.basic
.*;
52 import javax
.swing
.plaf
.*;
54 public class VirtualKeyboard
implements ActionListener
, Plugin
, KeyboardStatusListener
56 private JFrame window
;
58 private HashMap
<String
, Integer
> commandToKey
;
59 private HashMap
<String
, JToggleButton
> commandToButton
;
60 private JToggleButton capsLock
;
61 private JToggleButton numLock
;
62 private JToggleButton scrollLock
;
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
;
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
,
78 String cmdName
= name
+ "-" + (keyNo
++);
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);
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);
98 button
.setEnabled(false);
99 button
.setVisible(false);
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
));
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);
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
);
143 keyBorder
= new EmptyBorder(0, 5, 0, 5);
144 smallKeyBorder
= new EmptyBorder(0, 1, 0, 1);
145 // classicBorder isn't used with native buttons
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);
155 parseKeyboardFile(keyboardPath
);
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
))
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
);
181 int nextX
= 0, nextY
= 0;
183 while((line
= Misc
.nextParseLine(keyboardFile
)) != null)
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;
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
;
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
)
218 numLock
.setVisible(false);
219 numLock
.setSelected(false);
220 capsLock
.setVisible(false);
221 capsLock
.setSelected(false);
222 scrollLock
.setVisible(false);
223 scrollLock
.setSelected(false);
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();
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())
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
) {
276 keyStatusChangeEventThread(scancode
, pressed
);
279 public void keyStatusReload()
281 if(!SwingUtilities
.isEventDispatchThread())
283 SwingUtilities
.invokeLater(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
284 } catch(Exception e
) {
290 public void ledStatusChange(int newstatus
)
292 if(!SwingUtilities
.isEventDispatchThread())
294 final int _newstatus
= newstatus
;
295 SwingUtilities
.invokeLater(new Thread() { public void run() {
296 VirtualKeyboard
.this.updateLEDs(_newstatus
); }});
297 } catch(Exception e
) {
300 updateLEDs(newstatus
);
303 public void mouseButtonsChange(int newstatus
)
308 public void mouseExecButtonsChange(int newstatus
)
315 //This runs entierely in UI thread.
318 public boolean systemShutdown()
320 //OK to proceed with JVM shutdown.
324 public void pcStarting()
329 public void pcStopping()
331 if(pluginManager
.isShuttingDown())
332 return; //Too much of deadlock risk.
334 if(!SwingUtilities
.isEventDispatchThread())
336 SwingUtilities
.invokeAndWait(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
337 } catch(Exception e
) {
343 public void reconnect(org
.jpc
.emulator
.PC pc
)
346 keyboard
.removeStatusListener(this);
348 Keyboard keys
= (Keyboard
)pc
.getComponent(Keyboard
.class);
350 keyboard
.addStatusListener(this);
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);
367 public void actionPerformed(ActionEvent evt
)
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())
378 System
.err
.println("Informational: Keyhit on key " + scan
+ ".");
380 System
.err
.println("Informational: Keydown on key " + scan
+ ".");
383 System
.err
.println("Informational: Keyupdown on key " + scan
+ ".");
385 System
.err
.println("Informational: Keyup on key " + scan
+ ".");
387 keyboard
.sendEdge(scan
);
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");
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());
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
);
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
);
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);
451 public void paintBorder(final Component c
, final Graphics g
, final int x
, final int y
, final int width
,
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
463 g
.drawRect(x
+ 1, y
+ 1, width
- 2, 0);
464 g
.drawRect(x
+ 1, y
+ 1, 0, height
- 2);
466 // Draw the north-west highlight
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
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
);