20
20
21
21
import net .emustudio .plugins .cpu .intel8080 .api .Context8080 ;
22
22
import net .emustudio .plugins .device .zxspectrum .bus .api .ZxSpectrumBus ;
23
- import net .emustudio .plugins .device .zxspectrum .ula .gui .Keyboard ;
23
+ import net .emustudio .plugins .device .zxspectrum .ula .gui .KeyboardDispatcher ;
24
24
25
25
import java .awt .event .KeyEvent ;
26
26
import java .util .Arrays ;
27
27
import java .util .HashMap ;
28
28
import java .util .Map ;
29
29
import java .util .Objects ;
30
+ import java .util .function .BiConsumer ;
30
31
32
+ import static java .awt .event .KeyEvent .*;
31
33
import static net .emustudio .plugins .device .zxspectrum .ula .ZxParameters .*;
32
34
33
35
/**
74
76
* - P2 to P0 is the PAPER colour
75
77
* - I2 to I0 is the INK colour
76
78
*/
77
- public class ULA implements Context8080 .CpuPortDevice , Keyboard .OnKeyListener {
79
+ public class ULA implements Context8080 .CpuPortDevice , KeyboardDispatcher .OnKeyListener {
78
80
private final static byte [] RST_7 = new byte [0x38 ]; // works for IM1 and IM2 modes
79
81
private final static byte [] KEY_SHIFT = new byte []{0 , 1 };
80
82
private final static byte [] KEY_SYM_SHIFT = new byte []{7 , 2 };
@@ -88,49 +90,72 @@ public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener {
88
90
89
91
// maps host characters to ZX Spectrum key "commands"
90
92
// Byte[] = {keymap index, "zero" value, shift, symshift}
91
- private final static Map <Character , Byte []> CHAR_MAPPING = new HashMap <>();
93
+ private final static Map <Integer , Byte []> CHAR_MAPPING = new HashMap <>();
92
94
93
95
static {
94
- CHAR_MAPPING .put ('Z' , new Byte []{0 , 2 , -1 , -1 }); // z, COPY, ":"
95
- CHAR_MAPPING .put ('X' , new Byte []{0 , 4 , -1 , -1 }); // x, CLEAR, "£"
96
- CHAR_MAPPING .put ('C' , new Byte []{0 , 8 , -1 , -1 }); // c, CONT, "?"
97
- CHAR_MAPPING .put ('V' , new Byte []{0 , 16 , -1 , -1 }); // v, CLS, "/"
98
- CHAR_MAPPING .put ('B' , new Byte []{7 , 16 , -1 , -1 }); // b, BORDER, "*"
99
- CHAR_MAPPING .put ('N' , new Byte []{7 , 8 , -1 , -1 }); // n, NEXT, ","
100
- CHAR_MAPPING .put ('M' , new Byte []{7 , 4 , -1 , -1 }); // m, PAUSE, "."
101
- CHAR_MAPPING .put (' ' , new Byte []{7 , 1 , -1 , -1 }); // " "
102
- CHAR_MAPPING .put ('\n' , new Byte []{6 , 1 , -1 , -1 }); // ENTER
103
- CHAR_MAPPING .put ('A' , new Byte []{1 , 1 , -1 , -1 }); // a, NEW, "STOP"
104
- CHAR_MAPPING .put ('S' , new Byte []{1 , 2 , -1 , -1 }); // s, SAVE, "NOT"
105
- CHAR_MAPPING .put ('D' , new Byte []{1 , 4 , -1 , -1 }); // d, DIM, "STEP"
106
- CHAR_MAPPING .put ('F' , new Byte []{1 , 8 , -1 , -1 }); // f, FOR, "TO"
107
- CHAR_MAPPING .put ('G' , new Byte []{1 , 16 , -1 , -1 }); // g, GOTO, "THEN"
108
- CHAR_MAPPING .put ('H' , new Byte []{6 , 16 , -1 , -1 }); // h, GOSUB, "↑"
109
- CHAR_MAPPING .put ('J' , new Byte []{6 , 8 , -1 , -1 }); // j, LOAD, "-"
110
- CHAR_MAPPING .put ('K' , new Byte []{6 , 4 , -1 , -1 }); // k, LIST, "+"
111
- CHAR_MAPPING .put ('L' , new Byte []{6 , 2 , -1 , -1 }); // l, LET, "="
112
- CHAR_MAPPING .put ('Q' , new Byte []{2 , 1 , -1 , -1 }); // q, PLOT, "<="
113
- CHAR_MAPPING .put ('W' , new Byte []{2 , 2 , -1 , -1 }); // w, DRAW, "<>"
114
- CHAR_MAPPING .put ('E' , new Byte []{2 , 4 , -1 , -1 }); // e, REM, ">="
115
- CHAR_MAPPING .put ('R' , new Byte []{2 , 8 , -1 , -1 }); // r, RUN, "<"
116
- CHAR_MAPPING .put ('T' , new Byte []{2 , 16 , -1 , -1 }); // t, RAND, ">"
117
- CHAR_MAPPING .put ('Y' , new Byte []{5 , 16 , -1 , -1 }); // y, RETURN, "AND"
118
- CHAR_MAPPING .put ('U' , new Byte []{5 , 8 , -1 , -1 }); // u, IF, "OR"
119
- CHAR_MAPPING .put ('I' , new Byte []{5 , 4 , -1 , -1 }); // i, INPUT, "AT"
120
- CHAR_MAPPING .put ('O' , new Byte []{5 , 2 , -1 , -1 }); // o, POKE, ";"
121
- CHAR_MAPPING .put ('P' , new Byte []{5 , 1 , -1 , -1 }); // p, PRINT, "
122
- CHAR_MAPPING .put ('1' , new Byte []{3 , 1 , -1 , -1 }); // 1, "!"
123
- CHAR_MAPPING .put ('2' , new Byte []{3 , 2 , -1 , -1 }); // 2, "@"
124
- CHAR_MAPPING .put ('3' , new Byte []{3 , 4 , -1 , -1 }); // 3, "#"
125
- CHAR_MAPPING .put ('4' , new Byte []{3 , 8 , -1 , -1 }); // 4, "$"
126
- CHAR_MAPPING .put ('5' , new Byte []{3 , 16 , -1 , -1 }); // 5, "%"
127
- CHAR_MAPPING .put ('6' , new Byte []{4 , 16 , -1 , -1 }); // 6, "&"
128
- CHAR_MAPPING .put ('7' , new Byte []{4 , 8 , -1 , -1 }); // 7, "'"
129
- CHAR_MAPPING .put ('8' , new Byte []{4 , 4 , -1 , -1 }); // 8, "("
130
- CHAR_MAPPING .put ('9' , new Byte []{4 , 2 , -1 , -1 }); // 9, ")"
131
- CHAR_MAPPING .put ('0' , new Byte []{4 , 1 , -1 , -1 }); // 0, "_"
132
- CHAR_MAPPING .put ('\b' , new Byte []{4 , 1 , 1 , -1 }); // backspace
133
- CHAR_MAPPING .put ((char ) 127 , new Byte []{4 , 1 , 1 , -1 }); // delete
96
+ CHAR_MAPPING .put (VK_Z , new Byte []{0 , 2 , -1 , -1 }); // z, COPY, ":"
97
+ CHAR_MAPPING .put (VK_COLON , new Byte []{0 , 2 , 0 , 1 });
98
+ CHAR_MAPPING .put (VK_X , new Byte []{0 , 4 , -1 , -1 }); // x, CLEAR, "£"
99
+ CHAR_MAPPING .put (VK_C , new Byte []{0 , 8 , -1 , -1 }); // c, CONT, "?"
100
+ CHAR_MAPPING .put (VK_V , new Byte []{0 , 16 , -1 , -1 }); // v, CLS, "/"
101
+ CHAR_MAPPING .put (VK_SLASH , new Byte []{0 , 16 , 0 , 1 });
102
+ CHAR_MAPPING .put (VK_B , new Byte []{7 , 16 , -1 , -1 }); // b, BORDER, "*"
103
+ CHAR_MAPPING .put (VK_ASTERISK , new Byte []{7 , 16 , 0 , 1 });
104
+ CHAR_MAPPING .put (VK_N , new Byte []{7 , 8 , -1 , -1 }); // n, NEXT, ","
105
+ CHAR_MAPPING .put (VK_COMMA , new Byte []{7 , 8 , 0 , 1 });
106
+ CHAR_MAPPING .put (VK_M , new Byte []{7 , 4 , -1 , -1 }); // m, PAUSE, "."
107
+ CHAR_MAPPING .put (VK_DECIMAL , new Byte []{7 , 4 , 0 , 1 });
108
+ CHAR_MAPPING .put (VK_PERIOD , new Byte []{7 , 4 , 0 , 1 });
109
+ CHAR_MAPPING .put (VK_SPACE , new Byte []{7 , 1 , -1 , -1 }); // " "
110
+ CHAR_MAPPING .put (VK_ENTER , new Byte []{6 , 1 , -1 , -1 }); // ENTER
111
+ CHAR_MAPPING .put (VK_A , new Byte []{1 , 1 , -1 , -1 }); // a, NEW, "STOP"
112
+ CHAR_MAPPING .put (VK_S , new Byte []{1 , 2 , -1 , -1 }); // s, SAVE, "NOT"
113
+ CHAR_MAPPING .put (VK_D , new Byte []{1 , 4 , -1 , -1 }); // d, DIM, "STEP"
114
+ CHAR_MAPPING .put (VK_F , new Byte []{1 , 8 , -1 , -1 }); // f, FOR, "TO"
115
+ CHAR_MAPPING .put (VK_G , new Byte []{1 , 16 , -1 , -1 }); // g, GOTO, "THEN"
116
+ CHAR_MAPPING .put (VK_H , new Byte []{6 , 16 , -1 , -1 }); // h, GOSUB, "↑"
117
+ CHAR_MAPPING .put (VK_UP , new Byte []{6 , 16 , 0 , 1 });
118
+ CHAR_MAPPING .put (VK_J , new Byte []{6 , 8 , -1 , -1 }); // j, LOAD, "-"
119
+ CHAR_MAPPING .put (VK_SUBTRACT , new Byte []{6 , 8 , 0 , 1 });
120
+ CHAR_MAPPING .put (VK_K , new Byte []{6 , 4 , -1 , -1 }); // k, LIST, "+"
121
+ CHAR_MAPPING .put (VK_ADD , new Byte []{6 , 4 , 0 , 1 });
122
+ CHAR_MAPPING .put (VK_L , new Byte []{6 , 2 , -1 , -1 }); // l, LET, "="
123
+ CHAR_MAPPING .put (VK_EQUALS , new Byte []{6 , 2 , 0 , 1 });
124
+ CHAR_MAPPING .put (VK_Q , new Byte []{2 , 1 , -1 , -1 }); // q, PLOT, "<="
125
+ CHAR_MAPPING .put (VK_W , new Byte []{2 , 2 , -1 , -1 }); // w, DRAW, "<>"
126
+ CHAR_MAPPING .put (VK_E , new Byte []{2 , 4 , -1 , -1 }); // e, REM, ">="
127
+ CHAR_MAPPING .put (VK_R , new Byte []{2 , 8 , -1 , -1 }); // r, RUN, "<"
128
+ CHAR_MAPPING .put (VK_LESS , new Byte []{2 , 8 , 0 , 1 });
129
+ CHAR_MAPPING .put (VK_T , new Byte []{2 , 16 , -1 , -1 }); // t, RAND, ">"
130
+ CHAR_MAPPING .put (VK_GREATER , new Byte []{2 , 16 , 0 , 1 });
131
+ CHAR_MAPPING .put (VK_Y , new Byte []{5 , 16 , -1 , -1 }); // y, RETURN, "AND"
132
+ CHAR_MAPPING .put (VK_U , new Byte []{5 , 8 , -1 , -1 }); // u, IF, "OR"
133
+ CHAR_MAPPING .put (VK_I , new Byte []{5 , 4 , -1 , -1 }); // i, INPUT, "AT"
134
+ CHAR_MAPPING .put (VK_O , new Byte []{5 , 2 , -1 , -1 }); // o, POKE, ";"
135
+ CHAR_MAPPING .put (VK_SEMICOLON , new Byte []{5 , 2 , 0 , 1 });
136
+ CHAR_MAPPING .put (VK_P , new Byte []{5 , 1 , -1 , -1 }); // p, PRINT, "
137
+ CHAR_MAPPING .put (VK_QUOTEDBL , new Byte []{5 , 1 , 0 , 1 });
138
+ CHAR_MAPPING .put (VK_1 , new Byte []{3 , 1 , -1 , -1 }); // 1, "!"
139
+ CHAR_MAPPING .put (VK_EXCLAMATION_MARK , new Byte []{3 , 1 , 0 , 1 });
140
+ CHAR_MAPPING .put (VK_2 , new Byte []{3 , 2 , -1 , -1 }); // 2, "@"
141
+ CHAR_MAPPING .put (VK_AT , new Byte []{3 , 2 , 0 , 1 });
142
+ CHAR_MAPPING .put (VK_3 , new Byte []{3 , 4 , -1 , -1 }); // 3, "#"
143
+ CHAR_MAPPING .put (VK_NUMBER_SIGN , new Byte []{3 , 4 , 0 , 1 });
144
+ CHAR_MAPPING .put (VK_4 , new Byte []{3 , 8 , -1 , -1 }); // 4, "$"
145
+ CHAR_MAPPING .put (VK_DOLLAR , new Byte []{3 , 8 , 0 , 1 });
146
+ CHAR_MAPPING .put (VK_5 , new Byte []{3 , 16 , -1 , -1 }); // 5, "%"
147
+ CHAR_MAPPING .put (VK_6 , new Byte []{4 , 16 , -1 , -1 }); // 6, "&"
148
+ CHAR_MAPPING .put (VK_AMPERSAND , new Byte []{4 , 16 , 0 , 1 });
149
+ CHAR_MAPPING .put (VK_7 , new Byte []{4 , 8 , -1 , -1 }); // 7, "'"
150
+ CHAR_MAPPING .put (VK_QUOTE , new Byte []{4 , 8 , 0 , 1 });
151
+ CHAR_MAPPING .put (VK_8 , new Byte []{4 , 4 , -1 , -1 }); // 8, "("
152
+ CHAR_MAPPING .put (VK_LEFT_PARENTHESIS , new Byte []{4 , 4 , 0 , 1 });
153
+ CHAR_MAPPING .put (VK_9 , new Byte []{4 , 2 , -1 , -1 }); // 9, ")"
154
+ CHAR_MAPPING .put (VK_RIGHT_PARENTHESIS , new Byte []{4 , 2 , 0 , 1 });
155
+ CHAR_MAPPING .put (VK_0 , new Byte []{4 , 1 , -1 , -1 }); // 0, "_"
156
+ CHAR_MAPPING .put (VK_UNDERSCORE , new Byte []{4 , 1 , 0 , 1 });
157
+ CHAR_MAPPING .put (VK_BACK_SPACE , new Byte []{4 , 1 , 1 , 0 }); // backspace
158
+ CHAR_MAPPING .put (VK_DELETE , new Byte []{4 , 1 , 1 , 0 }); // delete
134
159
}
135
160
136
161
public boolean videoFlash = false ;
@@ -187,30 +212,23 @@ public byte read(int portAddress) {
187
212
// If more than one address line is made low, the result is the logical AND of all single inputs
188
213
189
214
byte result = (byte ) 0xBF ; // 1011 1111 // no EAR input
190
- if ((portAddress & 0xFEFE ) == 0xFEFE ) {
191
- // SHIFT, Z, X, C, V
192
- result &= keymap [0 ];
193
- } else if ((portAddress & 0xFDFE ) == 0xFDFE ) {
194
- // A, S, D, F, G
195
- result &= keymap [1 ];
196
- } else if ((portAddress & 0xFBFE ) == 0xFBFE ) {
197
- // Q, W, E, R, T
198
- result &= keymap [2 ];
199
- } else if ((portAddress & 0xF7FE ) == 0xF7FE ) {
200
- // 1, 2, 3, 4, 5
201
- result &= keymap [3 ];
202
- } else if ((portAddress & 0xEFFE ) == 0xEFFE ) {
203
- // 0, 9, 8, 7, 6
204
- result &= keymap [4 ];
205
- } else if ((portAddress & 0xDFFE ) == 0xDFFE ) {
206
- // P, O, I, U, Y
207
- result &= keymap [5 ];
208
- } else if ((portAddress & 0xBFFE ) == 0xBFFE ) {
209
- // ENTER, L, K, J, H
210
- result &= keymap [6 ];
211
- } else if ((portAddress & 0x7FFE ) == 0x7FFE ) {
212
- // SPACE, SYM SHIFT, M, N, B
213
- result &= keymap [7 ];
215
+ if ((portAddress & 0xFE ) == 0xFE ) {
216
+ int keyLine = 0 ;
217
+ portAddress >>>= 8 ;
218
+ while ((portAddress & 1 ) != 0 ) {
219
+ portAddress >>>= 1 ;
220
+ keyLine ++;
221
+ }
222
+
223
+ // FE = 0 1111 1110
224
+ // FD = 1 1111 1101
225
+ // FB = 2 1111 1011
226
+ // F7 = 3 1111 0111
227
+ // EF = 4 1110 1111
228
+ // DF = 5 1101 1111
229
+ // BF = 6 1011 1111
230
+ // 7F = 7 0111 1111
231
+ result &= keymap [keyLine ];
214
232
}
215
233
216
234
// LINE IN?
@@ -235,61 +253,54 @@ public String toString() {
235
253
}
236
254
237
255
@ Override
238
- public void onKeyUp (KeyEvent evt ) {
239
- int keyCode = evt .getExtendedKeyCode ();
240
- switch (keyCode ) {
241
- case KeyEvent .VK_CONTROL :
242
- keymap [KEY_SYM_SHIFT [0 ]] |= KEY_SYM_SHIFT [1 ];
243
- break ;
244
- case KeyEvent .VK_SHIFT :
245
- keymap [KEY_SHIFT [0 ]] |= KEY_SHIFT [1 ];
246
- break ;
247
- default :
248
- Byte [] command = CHAR_MAPPING .get ((char ) keyCode );
249
- if (command != null ) {
250
- if (command [2 ] == 1 ) {
251
- keymap [KEY_SHIFT [0 ]] |= KEY_SHIFT [1 ];
252
- } else if (command [2 ] == 0 ) {
253
- keymap [KEY_SHIFT [0 ]] &= (byte ) ((~KEY_SHIFT [1 ]) & 0xFF );
254
- }
255
- if (command [3 ] == 1 ) {
256
- keymap [KEY_SYM_SHIFT [0 ]] |= KEY_SYM_SHIFT [1 ];
257
- } else if (command [3 ] == 0 ) {
258
- keymap [KEY_SYM_SHIFT [0 ]] &= (byte ) ((~KEY_SYM_SHIFT [1 ]) & 0xFF );
259
- }
260
- keymap [command [0 ]] |= command [1 ];
261
- }
256
+ public void onKeyEvent (KeyEvent e ) {
257
+ boolean pressed = e .getID () == KEY_PRESSED ;
258
+ if (!pressed && e .getID () != KEY_RELEASED ) {
259
+ return ;
262
260
}
263
- }
261
+ BiConsumer <Byte , Byte > keySet = pressed ? this ::andKeyMap : this ::orKeyMap ;
262
+ BiConsumer <Byte , Byte > keyUnset = pressed ? this ::orKeyMap : this ::andKeyMap ;
264
263
265
- @ Override
266
- public void onKeyDown (KeyEvent evt ) {
267
- int keyCode = evt .getExtendedKeyCode ();
268
- switch (keyCode ) {
269
- case KeyEvent .VK_CONTROL :
270
- keymap [KEY_SYM_SHIFT [0 ]] &= (byte ) ((~KEY_SYM_SHIFT [1 ]) & 0xFF );
271
- break ;
272
- case KeyEvent .VK_SHIFT :
273
- keymap [KEY_SHIFT [0 ]] &= (byte ) ((~KEY_SHIFT [1 ]) & 0xFF );
274
- break ;
275
- default :
276
- Byte [] command = CHAR_MAPPING .get ((char ) keyCode );
277
- if (command != null ) {
278
- if (command [2 ] == 1 ) {
279
- keymap [KEY_SHIFT [0 ]] &= (byte ) ((~KEY_SHIFT [1 ]) & 0xFF );
280
- } else if (command [2 ] == 0 ) {
281
- keymap [KEY_SHIFT [0 ]] |= KEY_SHIFT [1 ];
282
- }
283
- if (command [3 ] == 1 ) {
284
- keymap [KEY_SYM_SHIFT [0 ]] &= (byte ) ((~KEY_SYM_SHIFT [1 ]) & 0xFF );
285
- } else if (command [3 ] == 0 ) {
286
- keymap [KEY_SYM_SHIFT [0 ]] |= KEY_SYM_SHIFT [1 ];
287
- }
288
- keymap [command [0 ]] &= (byte ) ((~command [1 ]) & 0xFF );
289
- }
264
+ // shift / alt / ctrl are visible in modifiersEx only if pressed = true
265
+ boolean symShift = (e .getModifiersEx () & (KeyEvent .CTRL_DOWN_MASK | KeyEvent .ALT_DOWN_MASK )) != 0 ;
266
+ boolean shift = (e .getModifiersEx () & (KeyEvent .SHIFT_DOWN_MASK )) != 0 ;
267
+
268
+ Byte [] command = CHAR_MAPPING .get (e .getKeyCode ());
269
+ if (command != null ) {
270
+ if (command [2 ] == 1 || (command [2 ] == -1 && shift )) {
271
+ keySet .accept (KEY_SHIFT [0 ], KEY_SHIFT [1 ]);
272
+ } else if (command [2 ] == 0 || !shift ) {
273
+ keyUnset .accept (KEY_SHIFT [0 ], KEY_SHIFT [1 ]);
274
+ }
275
+ if (command [3 ] == 1 || (command [3 ] == -1 && symShift )) {
276
+ keySet .accept (KEY_SYM_SHIFT [0 ], KEY_SYM_SHIFT [1 ]);
277
+ } else if (command [3 ] == 0 || !symShift ) {
278
+ keyUnset .accept (KEY_SYM_SHIFT [0 ], KEY_SYM_SHIFT [1 ]);
279
+ }
280
+ // TODO: shift/symshift are toggling for some reason
281
+ keySet .accept (command [0 ], command [1 ]);
282
+ } else {
283
+ if (shift ) {
284
+ keySet .accept (KEY_SHIFT [0 ], KEY_SHIFT [1 ]);
285
+ } else {
286
+ keyUnset .accept (KEY_SHIFT [0 ], KEY_SHIFT [1 ]);
287
+ }
288
+ if (symShift ) {
289
+ keySet .accept (KEY_SYM_SHIFT [0 ], KEY_SYM_SHIFT [1 ]);
290
+ } else {
291
+ keyUnset .accept (KEY_SYM_SHIFT [0 ], KEY_SYM_SHIFT [1 ]);
292
+ }
290
293
}
291
294
}
292
295
296
+ private void andKeyMap (byte key , byte value ) {
297
+ keymap [key ] &= (byte ) ((~value ) & 0xFF );
298
+ }
299
+
300
+ private void orKeyMap (byte key , byte value ) {
301
+ keymap [key ] |= value ;
302
+ }
303
+
293
304
/**
294
305
* Computes address offsets for each line in the screen.
295
306
* <p>
0 commit comments