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
;
41 import static org
.jpc
.Misc
.errorDialog
;
42 import static org
.jpc
.Misc
.moveWindow
;
43 import static org
.jpc
.Misc
.parseStringToComponents
;
44 import static org
.jpc
.Misc
.openStream
;
48 import javax
.swing
.border
.*;
50 import java
.awt
.event
.*;
52 import javax
.swing
.plaf
.basic
.*;
53 import javax
.swing
.plaf
.*;
55 public class VirtualKeyboard
implements ActionListener
, Plugin
, KeyboardStatusListener
57 private JFrame window
;
59 private HashMap
<String
, Integer
> commandToKey
;
60 private HashMap
<String
, JToggleButton
> commandToButton
;
61 private JToggleButton capsLock
;
62 private JToggleButton numLock
;
63 private JToggleButton scrollLock
;
65 private Font smallKeyFont
;
66 private Border keyBorder
, smallKeyBorder
, classicBorder
;
67 private boolean nativeButtons
;
68 private static String DEFAULT_KEYBOARD_FILENAME
= "datafiles/keyboards/default";
70 private org
.jpc
.emulator
.peripheral
.Keyboard keyboard
;
72 private boolean[] cachedState
;
73 private Plugins pluginManager
;
74 private int nativeWidth
, nativeHeight
;
76 public JToggleButton
addKey(String name
, String topKey
, int scanCode
, int x
, int y
, int w
, int h
, char sizeCode
,
79 String cmdName
= name
+ "-" + (keyNo
++);
82 label
= "<html>" + topKey
+ "<br>" + name
+ "</html>";
83 } else if(label
.indexOf('&') >= 0) {
84 label
= "<html>" + name
+ "</html>";
86 JToggleButton button
= new JToggleButton(label
, false);
88 button
.setFont(keyFont
);
89 button
.setBorder(keyBorder
);
90 } else if(sizeCode
== 'S') {
91 button
.setFont(smallKeyFont
);
92 button
.setBorder(smallKeyBorder
);
93 } else if(sizeCode
== 'C' && !nativeButtons
) {
94 button
.setBorder(classicBorder
);
97 button
.setRolloverEnabled(false);
99 button
.setEnabled(false);
100 button
.setVisible(false);
102 commandToKey
.put(cmdName
, new Integer(scanCode
));
103 commandToButton
.put(cmdName
, button
);
104 button
.setActionCommand(cmdName
);
105 button
.addActionListener(this);
108 if(!nativeButtons
) button
.setUI(new KeyboardButtonUI());
110 panel
.add(button
, new ConstantTableLayout
.Placement(x
, y
, w
, h
));
114 public void eci_virtualkeyboard_setwinpos(Integer x
, Integer y
)
116 moveWindow(window
, x
.intValue(), y
.intValue(), nativeWidth
, nativeHeight
);
119 public VirtualKeyboard(Plugins _pluginManager
) throws IOException
121 this(_pluginManager
, "");
124 public VirtualKeyboard(Plugins _pluginManager
, String args
) throws IOException
126 pluginManager
= _pluginManager
;
127 Map
<String
, String
> params
= parseStringToComponents(args
);
128 String keyboardPath
= params
.get("keyboard");
130 nativeButtons
= "native".equalsIgnoreCase(params
.get("style")) || (keyboardPath
== null);
134 commandToKey
= new HashMap
<String
, Integer
>();
135 commandToButton
= new HashMap
<String
, JToggleButton
>();
136 window
= new JFrame("Virtual Keyboard" + Misc
.emuname
);
137 ConstantTableLayout layout
= new ConstantTableLayout();
138 cachedState
= new boolean[256];
139 panel
= new JPanel(layout
);
140 keyFont
= new Font("SanSerif", Font
.PLAIN
, 11);
141 smallKeyFont
= keyFont
.deriveFont(9.0f
);
144 keyBorder
= new EmptyBorder(0, 5, 0, 5);
145 smallKeyBorder
= new EmptyBorder(0, 1, 0, 1);
146 // classicBorder isn't used with native buttons
148 Border outerBorder
= new CompoundBorder(new EmptyBorder(1, 1, 0, 0), new SimpleButtonBorder(false));
149 keyBorder
= new CompoundBorder(outerBorder
, new EmptyBorder(0, 3, 0, 3));
150 smallKeyBorder
= new CompoundBorder(outerBorder
, new EmptyBorder(0, 1, 0, 1));
151 classicBorder
= new SimpleButtonBorder(true);
156 parseKeyboardFile(keyboardPath
);
159 window
.setDefaultCloseOperation(JFrame
.DO_NOTHING_ON_CLOSE
);
160 Dimension d
= window
.getSize();
161 nativeWidth
= d
.width
;
162 nativeHeight
= d
.height
;
163 window
.setVisible(true);
166 private static int parseCoord(String value
, int next
)
168 if("-".equals(value
))
171 return Integer
.valueOf(value
);
174 private void parseKeyboardFile(String filename
) throws IOException
176 InputStream in
= openStream(filename
, DEFAULT_KEYBOARD_FILENAME
);
177 if((in
= openStream(filename
, DEFAULT_KEYBOARD_FILENAME
)) == null)
178 throw new IOException("Neither primary keyboard file nor fallback file exists.");
180 UTFInputLineStream keyboardFile
= new UTFInputLineStream(in
);
182 int nextX
= 0, nextY
= 0;
184 while((line
= Misc
.nextParseLine(keyboardFile
)) != null)
187 else if(line
.length
== 7 || line
.length
== 8) {
188 int x
= parseCoord(line
[1], nextX
), y
= parseCoord(line
[2], nextY
);
189 int w
= Integer
.parseInt(line
[3]), h
= Integer
.parseInt(line
[4]);
190 char sizeCode
= line
[5].charAt(0);
191 String name
= line
[6], shifted
= null;
196 int scanCode
= Integer
.parseInt(line
[0]);
197 addKey(name
, shifted
, scanCode
, x
, y
, w
, h
, sizeCode
, false);
198 } catch(NumberFormatException nfe
) { // The scanCode wasn't a number, so this must be a special key
199 String scanName
= line
[0];
200 JToggleButton specialButton
= addKey(name
, null, 0, x
, y
, w
, h
, sizeCode
, true);
201 if(scanName
.equalsIgnoreCase("numlock"))
202 numLock
=specialButton
;
203 else if(scanName
.equalsIgnoreCase("capslock"))
204 capsLock
=specialButton
;
205 else if(scanName
.equalsIgnoreCase("scrolllock"))
206 scrollLock
=specialButton
;
212 throw new IOException("Invalid line in keyboard layout.");
215 //-1 if unknown, bit 2 is capslock, bit 1 is numlock, bit 0 is scrollock.
216 private void updateLEDs(int status
)
219 numLock
.setVisible(false);
220 numLock
.setSelected(false);
221 capsLock
.setVisible(false);
222 capsLock
.setSelected(false);
223 scrollLock
.setVisible(false);
224 scrollLock
.setSelected(false);
226 numLock
.setVisible((status
& 2) != 0);
227 capsLock
.setVisible((status
& 4) != 0);
228 scrollLock
.setVisible((status
& 1) != 0);
232 public void resetButtons()
234 for(Map
.Entry
<String
, Integer
> entry
: commandToKey
.entrySet()) {
235 int scan
= entry
.getValue().intValue();
236 JToggleButton button
= commandToButton
.get(entry
.getKey());
237 if(keyboard
.getKeyStatus((byte)scan
) != cachedState
[scan
]) {
238 cachedState
[scan
] = keyboard
.getKeyStatus((byte)scan
);
239 button
.setSelected(cachedState
[scan
]);
242 updateLEDs(keyboard
.getLEDStatus());
245 private void keyStatusChangeEventThread(int scancode
, boolean pressed
)
247 /* THIS IS JUST PLAIN BROKEN.
248 for(Map.Entry<String, Integer> entry : commandToKey.entrySet()) {
249 int scan = entry.getValue().intValue();
252 JToggleButton button = commandToButton.get(entry.getKey());
253 if(pressed != cachedState[scan]) {
254 cachedState[scan] = pressed;
255 button.setSelected(pressed);
261 public void keyExecStatusChange(int scancode
, boolean pressed
)
263 //These aren't currently shown.
266 public void keyStatusChange(int scancode
, boolean pressed
)
268 if(!SwingUtilities
.isEventDispatchThread())
270 final int _scancode
= scancode
;
271 final boolean _pressed
= pressed
;
272 SwingUtilities
.invokeLater(new Thread() { public void run() {
273 VirtualKeyboard
.this.keyStatusChangeEventThread(_scancode
, _pressed
); }});
274 } catch(Exception e
) {
277 keyStatusChangeEventThread(scancode
, pressed
);
280 public void keyStatusReload()
282 if(!SwingUtilities
.isEventDispatchThread())
284 SwingUtilities
.invokeLater(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
285 } catch(Exception e
) {
291 public void ledStatusChange(int newstatus
)
293 if(!SwingUtilities
.isEventDispatchThread())
295 final int _newstatus
= newstatus
;
296 SwingUtilities
.invokeLater(new Thread() { public void run() {
297 VirtualKeyboard
.this.updateLEDs(_newstatus
); }});
298 } catch(Exception e
) {
301 updateLEDs(newstatus
);
304 public void mouseButtonsChange(int newstatus
)
309 public void mouseExecButtonsChange(int newstatus
)
316 //This runs entierely in UI thread.
319 public boolean systemShutdown()
321 //OK to proceed with JVM shutdown.
325 public void pcStarting()
330 public void pcStopping()
332 if(pluginManager
.isShuttingDown())
333 return; //Too much of deadlock risk.
335 if(!SwingUtilities
.isEventDispatchThread())
337 SwingUtilities
.invokeAndWait(new Thread() { public void run() { VirtualKeyboard
.this.resetButtons(); }});
338 } catch(Exception e
) {
344 public void reconnect(org
.jpc
.emulator
.PC pc
)
347 keyboard
.removeStatusListener(this);
349 Keyboard keys
= (Keyboard
)pc
.getComponent(Keyboard
.class);
351 keyboard
.addStatusListener(this);
355 Iterator
<Map
.Entry
<String
, Integer
> > itt
= commandToKey
.entrySet().iterator();
356 while (itt
.hasNext())
358 Map
.Entry
<String
, Integer
> entry
= itt
.next();
359 String n
= entry
.getKey();
360 Integer s
= entry
.getValue();
361 cachedState
[s
.intValue()] = false;
362 commandToButton
.get(n
).setSelected(false);
368 public void actionPerformed(ActionEvent evt
)
373 String command
= evt
.getActionCommand();
374 JToggleButton button
= commandToButton
.get(command
);
375 int scan
= commandToKey
.get(command
).intValue();
376 boolean doubleEdge
= (scan
!= 255) && ((evt
.getModifiers() & ActionEvent
.SHIFT_MASK
) != 0);
377 if(button
.isSelected())
379 System
.err
.println("Informational: Keyhit on key " + scan
+ ".");
381 System
.err
.println("Informational: Keydown on key " + scan
+ ".");
384 System
.err
.println("Informational: Keyupdown on key " + scan
+ ".");
386 System
.err
.println("Informational: Keyup on key " + scan
+ ".");
388 keyboard
.sendEdge(scan
);
390 keyboard
.sendEdge(scan
);
391 } catch(Exception e
) {
392 System
.err
.println("Error: Sending command failed: " + e
);
393 errorDialog(e
, "Failed to send keyboard key edge", null, "Dismiss");
396 cachedState
[scan
] = !cachedState
[scan
];
397 button
.setSelected(cachedState
[scan
]);
400 protected static class KeyboardButtonUI
extends BasicToggleButtonUI
402 protected static Color highlightColor
= new Color(200, 200, 200);
403 protected static Color backgroundColor
= new Color(220, 220, 220);
405 protected void simplePaint(Graphics g
, JComponent c
, Color color
)
407 Rectangle viewRect
= new Rectangle(c
.getSize());
410 /* We want to ignore the inner margins while calculating the background size, so we have to
411 * pull the outside border out of the compound border */
412 CompoundBorder border
= (CompoundBorder
)c
.getBorder();
413 margin
= border
.getOutsideBorder().getBorderInsets(c
);
414 } catch(ClassCastException cce
) {
415 // We were called on a button without our elaborate triple-border, so default to the whole inset
416 margin
= c
.getBorder().getBorderInsets(c
);
421 g
.fillRect(viewRect
.x
+ margin
.left
, viewRect
.y
+ margin
.top
,
422 viewRect
.width
- (margin
.left
+ margin
.right
),
423 viewRect
.height
- (margin
.top
+ margin
.bottom
));
426 protected void paintButtonPressed(Graphics g
, AbstractButton b
)
428 simplePaint(g
, b
, highlightColor
);
431 public void paint(Graphics g
, JComponent c
)
433 simplePaint(g
, c
, backgroundColor
);
438 protected static class SimpleButtonBorder
extends LineBorder
440 private static final long serialVersionUID
= 1L;
442 protected static Color nwColor
=new Color(240, 240, 240);
443 protected static Color seColor
=new Color(130, 130, 130);
444 protected static Color pressedColor
=new Color(160, 160, 160);
445 protected boolean thin
;
446 public SimpleButtonBorder(boolean thin
)
448 super(Color
.BLACK
, 1, true);
452 public void paintBorder(final Component c
, final Graphics g
, final int x
, final int y
, final int width
,
455 Color oldColor
= g
.getColor();
456 JToggleButton button
= (JToggleButton
)c
;
457 ButtonModel model
= button
.getModel();
458 int adjust
= thin ?
0 : 1;
460 // Draw inner highlights
461 if(model
.isSelected() || model
.isPressed()) {
462 // Draw the north-west highlight, but in the south-east color
464 g
.drawRect(x
+ 1, y
+ 1, width
- 2, 0);
465 g
.drawRect(x
+ 1, y
+ 1, 0, height
- 2);
467 // Draw the north-west highlight
469 g
.drawRect(x
+ 1, y
+ 1, width
- 2, adjust
);
470 g
.drawRect(x
+ 1, y
+ 1, adjust
, height
- 2);
471 // Draw the south-east highlight
473 g
.drawRect(x
+ 1, y
+ height
- 2, width
- 2, 0);
474 g
.drawRect(x
+ width
- 2, y
+ 1, 0, height
- 2);
475 if(!thin
) { // Draw inner line of shadow
476 g
.drawRect(x
+ 2, y
+ height
- 3, width
- 3, 0);
477 g
.drawRect(x
+ width
- 3, y
+ 2, 0, height
- 3);
481 // Draw actual border
482 g
.setColor(model
.isPressed() ? pressedColor
: lineColor
);
483 g
.drawRoundRect(x
, y
, width
- 1, height
- 1, 2, 2);
485 // Restore color state
486 g
.setColor(oldColor
);