View Javadoc

1   /***
2    * Created by IntelliJ IDEA.
3    * User: gareth
4    * Date: Aug 27, 2003
5    * Time: 8:34:11 AM
6    */
7   package com.tapina.robe.swi;
8   
9   import com.tapina.robe.runtime.*;
10  import com.tapina.robe.swi.os.SpriteArea;
11  import com.tapina.robe.swi.wimp.*;
12  import com.tapina.robe.swi.wimp.Icon;
13  import com.tapina.robe.swi.wimp.Window;
14  import com.tapina.robe.debug.FlexWalk;
15  
16  import javax.swing.*;
17  import java.awt.*;
18  import java.awt.image.BufferedImage;
19  import java.lang.reflect.Method;
20  import java.io.RandomAccessFile;
21  import java.io.IOException;
22  import java.util.Iterator;
23  
24  public final class Wimp extends SWIHandler {
25      private static Wimp ourInstance;
26      private static final int WIMP_VERSION = 310;
27      private static final int WINDOW_BLOCK_SIZE = 88;
28      private static final int WINDOW_STATE_SIZE = 32;
29      private static final int ICON_BLOCK_SIZE = 32;
30  
31      public synchronized static Wimp getInstance() {
32          if (ourInstance == null) {
33              ourInstance = new Wimp();
34          }
35          return ourInstance;
36      }
37  
38      private Wimp() {
39      }
40  
41      public static int getBase() {
42          return 0x400c0;
43      }
44  
45      public String getName() {
46          return "WindowManager";
47      }
48  
49      private static final String[] methodNames = new String[]{
50          "Initialise", "CreateWindow", "CreateIcon", "DeleteWindow", "DeleteIcon", "OpenWindow", "CloseWindow", "Poll", // 0x400c0
51          "RedrawWindow", "UpdateWindow", "GetRectangle", "GetWindowState", "GetWindowInfo", "SetIconState", "GetIconState", "GetPointerInfo", // 0x400c8
52          "DragBox", "ForceRedraw", "SetCaretPosition", "GetCaretPosition", "CreateMenu", "DecodeMenu", "WhichIcon", "SetExtent", // 0x400d0
53          "SetPointerShape", "OpenTemplate", "CloseTemplate", "LoadTemplate", "ProcessKey", "CloseDown", "StartTask", "ReportError", // 0x400d8
54          "GetWindowOutline", "PollIdle", "PlotIcon", "SetMode", "SetPalette", "ReadPalette", "SetColour", "SendMessage", // 0x400e0
55          "CreateSubMenu", "SpriteOp", "BaseOfSprites", "BlockCopy", "SlotSize", "ReadPixTrans", "ClaimFreeMemory", "CommandWindow", // 0x400e8
56          "TextColour", "TransferBlock", "ReadSysInfo", "SetFontColours", "GetMenuState", "RegisterFilter", "AddMessages", "RemoveMessages", // 0x400f0
57          "SetColourMapping", "TextOp", "SetWatchdogState", "Extend", "ResizeIcon", "AutoScroll", "", "" // 0x400f8
58      };
59  
60      public static Method getMethod(Integer offset) throws NoSuchMethodException {
61          return getMethod(methodNames[offset.intValue()]);
62      }
63  
64      private static final Method getMethod(String name) throws NoSuchMethodException {
65          return Wimp.class.getMethod(name, METHOD_PARAMETERS);
66      }
67  
68      private static final class SlotSize {
69          final int current;
70          final int next;
71          final int free;
72  
73          public SlotSize(int current, int free, int next) {
74              this.current = current;
75              this.free = free;
76              this.next = next;
77          }
78      }
79  
80      private WimpTask wimpTask = null;
81  
82      public final void Initialise(Environment env) {
83          final int[] R = env.getCpu().R;
84          if (R[1] != 0x4b534154) { // "TASK"
85              throw new SWIError(0, "Wimp.Initialise with incorrect magic number");
86          }
87          String taskDescription = env.getMemoryMap().getString0(R[2]);
88          final int[] acceptableMessages = new int[]{0}; // All messages important
89          if (R[3] != 0) {
90              if (env.getMemoryMap().getWord(R[3]) != 0) {
91                  log.warning("Message filter list ignored, all messages considered important");
92              }
93              initialise(R[0], taskDescription, acceptableMessages);
94          } else {
95              initialise(R[0], taskDescription, null);
96          }
97      }
98  
99      /***
100      * @param knownWimpVersion
101      * @param description
102      * @param acceptableMessages null for no acceptable messages, array containing just 0 for all messages acceptable,
103      *                           otherwise a list
104      */
105     public final void initialise(int knownWimpVersion, String description, int[] acceptableMessages) {
106         if (knownWimpVersion > WIMP_VERSION) {
107             throw new SWIError(0, "Wimp version requested later than current available version");
108         }
109         wimpTask = new WimpTask(knownWimpVersion, description);
110         Thread.currentThread().setName(description);
111     }
112 
113     /***
114      * This tells the Wimp what the characteristics of a window are. The Wimp takes a copy of this block, so you could
115      * throw it away afterwards.
116      * @param env IN: R1 = Pointer to window block; OUT: R0 = Window handle
117      */
118     public final void CreateWindow(Environment env) {
119         final int[] R = env.getCpu().R;
120         final MemoryMap memoryMap = env.getMemoryMap();
121         final int size = Wimp.WINDOW_BLOCK_SIZE + memoryMap.getWord(R[1] + 84) * Wimp.ICON_BLOCK_SIZE;
122         final WindowBlock windowBlock = windowBlockFromTemplateData(memoryMap.getByteArray(R[1], size, false), memoryMap);
123         R[0] = createWindow(windowBlock).getWindowHandle();
124     }
125 
126     public final Window createWindow(WindowBlock windowBlock) {
127         return new Window(windowBlock);
128     }
129 
130     public final void CreateIcon(Environment env) {
131         final int[] R = env.getCpu().R;
132         final MemoryMap memoryMap = env.getMemoryMap();
133 
134         final int blockAddress = R[1] + 4;
135         final Icon icon = iconFromTemplateData(memoryMap.getByteArray(blockAddress, ICON_BLOCK_SIZE, false), memoryMap); //iconFromMemory(memoryMap, blockAddress);
136         createIcon(memoryMap.getWord(R[1]), icon);
137     }
138 
139     private Icon iconFromMemory(final MemoryMap memoryMap, final int blockAddress) {
140         final int minX = memoryMap.getWord(blockAddress);
141         final int minY = memoryMap.getWord(blockAddress + 4);
142         final Rectangle boundingBox = new Rectangle(minX, minY,
143                                                     memoryMap.getWord(blockAddress + 8) - minX,
144                                                     memoryMap.getWord(blockAddress + 12) - minY);
145         final int iconFlags = memoryMap.getWord(blockAddress + 16);
146         final Icon icon = new Icon(boundingBox, iconFlags, (iconFlags & 0xf000) >> 12,
147                                    (iconFlags & 0xf0000) >> 16, (iconFlags & 0xf000000) >> 24,
148                                    (iconFlags & 0xf0000000) >>> 28);
149         if (icon.isTextIcon()) {
150             icon.setText("Test");
151         }
152         if (icon.isSpriteIcon()) {
153             if ((iconFlags & Icon.INDIRECTED) == 0) {
154                 icon.setSprite(WIMP_SPRITE_AREA.getSprite(memoryMap.getStringN(blockAddress + 20, 12)));
155             } else {
156                 if (icon.isTextIcon()) {
157                     log.warning("text + sprite icons not supported");
158                 } else {
159                     final int pointer = memoryMap.getWord(blockAddress + 20);
160                     final int spriteArea = memoryMap.getWord(blockAddress + 24);
161                     final int nameLength = memoryMap.getWord(blockAddress + 28);
162                     if (spriteArea == 1) {
163                         // Named sprite from Wimp sprite area again
164                         icon.setSprite(WIMP_SPRITE_AREA.getSprite(memoryMap.getStringN(pointer, 12)));
165                     } else {
166                         log.warning("only named sprites from the WIMP sprite area supported");
167                     }
168                 }
169             }
170         }
171         return icon;
172     }
173 
174     public final void createIcon(int windowHandle, Icon icon) {
175         if (windowHandle == -1) {
176             IconBar.getInstance().createIcon(icon);
177         } else {
178             log.warning("We only support creating icons in the icon bar");
179         }
180     }
181 
182     /***
183      * This SWI removes the definition of the given icon. If it is the last icon in the windows list, the memory is
184      * reallocated, if not it is only marked as deleted.
185      * To update screen, you must call Wimp_ForceRedraw .
186      * @param env IN: R1 = pointer to +0 Window handle, or -2 for iconbar; +4 Icon handle
187      */
188     public final void DeleteIcon(Environment env) {
189         final int[] R = env.getCpu().R;
190         final MemoryMap memoryMap = env.getMemoryMap();
191         final int windowHandle = memoryMap.getWord(R[1]);
192         if (windowHandle == -2) {
193             log.warning("We do not remove icons from the icon bar yet");
194         } else {
195             final Window window = Window.find(windowHandle);
196             deleteIcon(window, memoryMap.getWord(R[1] + 4));
197         }
198     }
199 
200     public void deleteIcon(Window window, int iconHandle) {
201         window.deleteIcon(iconHandle);
202     }
203 
204     /***
205      * This call reads a windows visible state. The nested version can be used to read the windows parent.
206      * Nested version has R2 = "TASK" (&4B534154) on entry and returns R3 = parent window handle, or -1 if top level,
207      * R4 = nesting flags.
208      * @param env IN: R1 = pointer to block which contains window handle; OUT: Block in R1 contains :<pre>
209      * +0 Window Handle (or -2 for icon bar)
210      * +4 Visible area minimum x
211      * +8 Visible area minimum y
212      * +12 Visible area maximum x
213      * +16 Visible area maximum y
214      * +20 Scroll x offset
215      * +24 Scroll y offset
216      * +28 Handle to open window behind
217      * +32 Window flags</pre>
218      */
219     public void GetWindowState(Environment env) {
220         final int[] R = env.getCpu().R;
221         final MemoryMap memoryMap = env.getMemoryMap();
222         final int windowHandle = memoryMap.getWord(R[1]);
223         final Window window = getWindowState(windowHandle);
224         windowStateFromWindow(window, memoryMap.getByteArray(R[1] + 4, WINDOW_STATE_SIZE, false));
225     }
226 
227     public Window getWindowState(final int windowHandle) {
228         return Window.find(windowHandle);
229     }
230 
231     /***
232      * This SWI sets the icon's flags as follows : new = (old BIC clear_word) EOR EOR_word.
233      * This SWI also causes the icon to be redrawn, even if no bits are changed.
234      * @param env IN: R1 = pointer to block :<pre>
235      * 0 Window handle (-1,-2 for iconbar)
236      * 4 Icon handle
237      * 8 EOR word
238      * 12 Clear word
239      * </pre>
240      */
241     public void SetIconState(Environment env) {
242         final int[] R = env.getCpu().R;
243         final MemoryMap memoryMap = env.getMemoryMap();
244         final Window window = Window.find(memoryMap.getWord(R[1]));
245         setIconState(window.getIcon(memoryMap.getWord(R[1] + 4)), memoryMap.getWord(R[1] + 8),
246                      memoryMap.getWord(R[1] + 12));
247     }
248 
249     public void setIconState(Icon icon, int eorWord, int clearWord) {
250         int flags = icon.getIconFlags();
251         flags = (flags & (~clearWord)) ^ eorWord;
252         icon.setIconFlags(flags);
253     }
254 
255     /***
256      * Get the icon definition for an icon.
257      * If you want to search for an icon with particular flag settings, use Wimp_WhichIcon.
258      * @param env IN: R1 = pointer to block : +0 Window handle, +4 Icon handle OUT: R0 corrupt; on exit the block will also contain +8 32-byte icon block.
259      */
260     public void GetIconState(Environment env) {
261         final int[] R = env.getCpu().R;
262         final MemoryMap memoryMap = env.getMemoryMap();
263         final int windowHandle = memoryMap.getWord(R[1]);
264         final int iconHandle = memoryMap.getWord(R[1] + 4);
265         final Window window = Window.find(windowHandle);
266         final Icon icon = getIconState(window, iconHandle);
267         iconBlockFromIcon(icon, memoryMap.getByteArray(R[1] + 8, ICON_BLOCK_SIZE, false));
268     }
269 
270     private final static Icon DELETED_ICON = new Icon();
271     static {
272         DELETED_ICON.setIconFlags(Icon.DELETED);
273     }
274 
275     public Icon getIconState(Window window, int iconHandle) {
276         try {
277             return window.getIcon(iconHandle);
278         } catch (IndexOutOfBoundsException e) {
279             return DELETED_ICON;
280         }
281     }
282 
283     /***
284      * Get the current position of the text input caret.
285      * @param env IN: R1 = pointer to block; OUT: block contains :<pre>
286      * 0 window handle (-1 if off)
287      * 4 icon handle, or -1
288      * 8 x offset
289      * 12 y offset
290      * 16 height + flags , or -1
291      * 20 index into string, or -1
292      * Flags:
293      * 0-15 Height of caret in OS units.
294      * 16-23 Colour (if bit 26 is set)
295      * 24 1 => Use VDU5 type caret.
296      *    0 => Use anti-aliased caret.
297      * 25 1 => Caret is invisible.
298      * 26 1 => Use bits 16-23 for colour.
299      *    0 => Use wimp colour 11.
300      * 27 1 => 16-23 is untranslated col.
301      *    0 => 16-23 is wimp colour.
302      * </pre>
303      * @todo Write this method
304      */
305     public final void GetCaretPosition(Environment env) {
306         final MemoryMap memoryMap = env.getMemoryMap();
307         final int blockAddress = env.getCpu().R[1];
308         final CaretPosition caretPosition = getCaretPosition();
309         final Window window = caretPosition.getWindow();
310         memoryMap.storeWord(blockAddress, window == null? -1 : window.getWindowHandle());
311         memoryMap.storeWord(blockAddress + 4, caretPosition.getIconHandle());
312         memoryMap.storePoint(blockAddress + 8, caretPosition.getOffset());
313         memoryMap.storeWord(blockAddress + 16, window == null? -1 : caretPosition.getHeight() | (caretPosition.getColour() << 16) | caretPosition.getFlags());
314         memoryMap.storeWord(blockAddress + 20, caretPosition.getIndex());
315     }
316 
317     /***
318      * @todo Need to populate the caret position with real data
319      */
320     public final CaretPosition getCaretPosition() {
321         final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
322         final java.awt.Window jWindow = focusManager.getFocusedWindow();
323         final java.awt.Component jComponent = focusManager.getFocusOwner();
324         final CaretPosition caretPosition = new CaretPosition(null, -1);
325         if (jWindow != null) {
326             log.warning("Caret information is incorrect");
327         }
328         return caretPosition;
329     }
330 
331     /***
332      * All icons with (icon flags AND R2) = (R3 AND R2) are added to the list in R1.
333      * The list is terminated by -1.
334      * @param env IN: R0 = Window handle, R1 = Block to contain icon handles, R2 = Bit mask, R3 = Bit settings to match
335      */
336     public final void WhichIcon(Environment env) {
337         final int[] R = env.getCpu().R;
338         final MemoryMap memoryMap = env.getMemoryMap();
339         final Window window = Window.find(R[0]);
340         final Iterator iterator = window.icons();
341         int out = R[1];
342         for (int iconHandle = 0; iterator.hasNext(); iconHandle++) {
343             Icon icon = (Icon) iterator.next();
344             if ((icon.getIconFlags() & R[2]) == (R[3] & R[2])) {
345                 memoryMap.storeWord(out, iconHandle);
346                 out += 4;
347             }
348         }
349         memoryMap.storeWord(out, -1);
350     }
351 
352     /***
353      * Visible workarea must be wholly within new workarea extent.
354      * On RISC OS 2 workarea extent must correspond to a whole number of pixels, or strange effects may occur.
355      * Later Wimps round the values.
356      * @param env IN: R0 = window handle, R1 = pointer to block <pre>
357      * +0 new workarea min x
358      * +4 new workarea min y
359      * +8 new workarea max x
360      * +12 new workarea max y
361      * </pre>
362      */
363     public void SetExtent(Environment env) {
364         final int[] R = env.getCpu().R;
365         final MemoryMap memoryMap = env.getMemoryMap();
366         final Window window = Window.find(R[0]);
367         final Rectangle extent = env.getMemoryMap().getRectangle(R[1]);
368         setExtent(window, extent);
369     }
370 
371     public void setExtent(Window window, Rectangle extent) {
372         window.setExtent(extent);
373     }
374 
375     private TemplateFile templateFile = null;
376 
377     /***
378      * This call opens the said template file.
379      * Now you can do repeated calls to {@link #LoadTemplate}, and finally {@link #CloseTemplate}.
380      * Entry: R1 = pathname of template file to open; Exit: R0 corrupt
381      *
382      * @param env
383      */
384     public final void OpenTemplate(Environment env) {
385         openTemplate(env.getMemoryMap().getString0(env.getCpu().R[1]));
386     }
387 
388     public final void openTemplate(String filename) {
389         try {
390             if (templateFile != null) {
391                 templateFile.getFile().close();
392             }
393             templateFile = new TemplateFile(new RandomAccessFile(FilenameUtils.acornToNative(filename), "r"));
394         } catch (IOException e) {
395             throw new SWIError(0, e);
396         }
397     }
398 
399     public final void CloseTemplate(Environment env) {
400         closeTemplate();
401     }
402 
403     public final void closeTemplate() {
404         if (templateFile != null) {
405             try {
406                 templateFile.getFile().close();
407             } catch (IOException e) {
408                 throw new SWIError(0, e);
409             }
410         }
411     }
412 
413     /***
414      * Note that R5 must point to a word aligned 12 byte buffer, which is used as workspace by the call.
415      * Errors will not be returned if the template is not found. Instead R6 is set to 0.
416      * Entry: R1 = pointer to userbuffer for template, or 0 to find required buffersize
417      * R2 = pointer to workspace for indirected data.
418      * R3 = pointer to end of workspace (+1?)
419      * R4 = pointer to 256-byte array for font ref, or -1 for no fonts
420      * R5 = pointer to (wildcarded) name to match (3 aligned words)
421      * R6 = position to search from, or 0 for the first call
422      * Exit: R0 corrupt
423      * R2 = pointer to remaining workspace
424      * R5 is overwritten with actual name
425      * R6 = pos of next entry, or 0 if template not found
426      * if R1 was 0, then :
427      * R1 = required size for window and icons
428      * R2 = required size for indirected data
429      * @param env
430      */
431     public final void LoadTemplate(Environment env) {
432         final int[] R = env.getCpu().R;
433         final String name = env.getMemoryMap().getStringN(R[5], 12);
434         if (R[4] != -1) {
435             log.warning("We do not handle template loading with font arrays when loading: " + name);
436         }
437         final StringBuffer nameBuffer = new StringBuffer(name);
438         byte[] template = loadTemplateData(nameBuffer, R[6]);
439         if (template != null) {
440             final int numIcons = ByteArrayUtils.getInt(template, 84);
441             final int windowBlockSize = WINDOW_BLOCK_SIZE + ICON_BLOCK_SIZE * numIcons;
442             final int indirectedDataSize = template.length - windowBlockSize;
443             if (R[1] <= 0) {
444                 // Just want to read size
445                 R[1] = windowBlockSize;
446                 R[2] = indirectedDataSize;
447             } else {
448                 ByteArray data = env.getMemoryMap().getByteArray(R[1], windowBlockSize, true);
449                 System.arraycopy(template, 0, data.getArray(), data.getOffset(), windowBlockSize);
450                 if (indirectedDataSize > 0) {
451                     remapIndirectedData(data, numIcons, 0, R[2]);
452                     env.getMemoryMap().storeBytes(R[2], template, windowBlockSize, indirectedDataSize);
453                     R[2] += indirectedDataSize;
454                 }
455             }
456             env.getMemoryMap().storeString0(R[5], nameBuffer.toString());
457             R[6]++;
458         } else {
459             R[6] = 0;
460         }
461     }
462 
463     /***
464      * Adjust the indirected data pointers of the window block template to be rebased at a new address.
465      * The title and the icons may have indirected data, which is altered.
466      * @param template ByteArray containing template window block data.
467      * @param numIcons number of icons in window definition (read from template + 84)
468      * @param oldAddress old base address of indirected data
469      * @param newAddress new base address of indirected data
470      */
471     final void remapIndirectedData(ByteArray template, int numIcons, int oldAddress, int newAddress) {
472         final int offset = newAddress - oldAddress;
473         // Remap title data
474         final int titleFlags = template.getWord(56);
475         remapIndirectedIconData(template, titleFlags, 72, offset);
476         // Remap icon data
477         for (int i = 0; i < numIcons; i++) {
478             final int iconBase = WINDOW_BLOCK_SIZE + i * ICON_BLOCK_SIZE;
479             final int iconFlags = template.getWord(iconBase + 16);
480             remapIndirectedIconData(template, iconFlags, iconBase + 20, offset);
481         }
482     }
483 
484     /***
485      * Adjust the indirected data pointers of an icon by a specified offset.
486      * If the icon is not indirected then nothing will change.
487      * @param template ByteArray containing template window block data
488      * @param iconFlags flags of icon to be adjusted, used to check if indirection is in use
489      * @param iconData offset within template of 12 bytes of icon data to adjust
490      * @param offset positive or negative adjustment required to pointers in order to rebase indirected data.
491      */
492     void remapIndirectedIconData(ByteArray template, final int iconFlags, final int iconData, final int offset) {
493         if ((iconFlags & Icon.INDIRECTED) != 0) {
494             final int text = template.getWord(iconData);
495             final int validation = template.getWord(iconData + 4);
496             template.setInt(iconData, text + offset);
497             if (validation != 1) {
498                 template.setInt(iconData + 4, validation + offset);
499             }
500         }
501     }
502 
503     /***
504      * Load the named template. The name may be wildcarded in which case 'index' will determine which template is
505      * loaded - to start off with index should be 0 then incremented each time until the method returns null.
506      * @param name StringBuffer containing (possibly wildcarded) name - will be overwritten with actual name
507      * @param index index of template to load
508      * @return template matching name, at specified index, or null if not found/no more
509      */
510     public final WindowBlock loadTemplate(StringBuffer name, int index) {
511         final byte[] data = loadTemplateData(name, index);
512         if (data == null) {
513             return null;
514         }
515         final ByteArray dataSource = new ByteArray(data, 0, data.length);
516         return windowBlockFromTemplateData(dataSource, dataSource);
517     }
518 
519     /***
520      * Load the named template. The name may be wildcarded in which case 'index' will determine which template is loaded
521      * - to start off with index should be 0 then incremented each time until the method returns null.
522      *
523      * @param name  StringBuffer containing (possibly wildcarded) name - will be overwritten with actual name
524      * @param index index of template to load
525      * @return template matching name, at specified index, or null if not found/no more
526      */
527     private byte[] loadTemplateData(StringBuffer name, int index) {
528         if (templateFile == null) {
529             throw new SWIError(0, "No template file open");
530         }
531         try {
532             final TemplateFile.Template[] templates = templateFile.getTemplates(name.toString());
533             if (index >= templates.length) {
534                 return null;
535             } else {
536                 final TemplateFile.Template template = templates[index];
537                 name.replace(0, name.length(), template.getName());
538                 return template.load();
539             }
540         } catch (IOException e) {
541             throw new SWIError(0, e);
542         }
543     }
544 
545     private final WindowBlock windowBlockFromTemplateData(ByteArray data, BinaryDataSource indirectedData) {
546         final WindowBlock windowBlock = new WindowBlock();
547         windowBlock.setVisibleArea(data.getRectangle(0));
548         windowBlock.setScrollOffset(data.getPoint(16));
549         windowBlock.setHandleOfWindowAbove(data.getWord(24));
550         windowBlock.setWindowFlags(data.getWord(28));
551         windowBlock.setTitleForegroundColour(data.getByte(32));
552         windowBlock.setTitleBackgroundColour(data.getByte(33));
553         windowBlock.setWorkAreaForegroundColour(data.getByte(34));
554         windowBlock.setWorkAreaBackgroundColour(data.getByte(35));
555         windowBlock.setScrollBarOuterColour(data.getByte(36));
556         windowBlock.setScrollBarInnerColour(data.getByte(37));
557         windowBlock.setActiveTitleBackgroundColour(data.getByte(38));
558         windowBlock.setWindowExtraFlags(data.getByte(39));
559         windowBlock.setWorkArea(data.getRectangle(40));
560         windowBlock.setTitleBarIconFlags(data.getWord(56));
561         windowBlock.setWorkAreaButtonFlags(data.getWord(60));
562         final int spriteArea = data.getWord(64);
563         if (spriteArea != 1) {
564             log.warning("We always use the WIMP sprite area for windows");
565         }
566         windowBlock.setSpriteArea(WIMP_SPRITE_AREA);
567         windowBlock.setMinimumSize(new Dimension(data.getWord(68) & 0xffff, (data.getWord(68) & 0xffff0000) >>> 16));
568         if ((windowBlock.getTitleBarIconFlags() & Icon.INDIRECTED) != 0) {
569             final int address = data.getWord(72);
570             windowBlock.setTitleText(indirectedData.getStringControlTerminated(address));
571             windowBlock.setTitleIndirectAddress(address);
572         } else {
573             windowBlock.setTitleText(data.getStringControlTerminated(72, 12));
574         }
575         int numberOfIcons = data.getWord(84);
576         for (int i = 0; i < numberOfIcons; i++) {
577             final int iconBase = WINDOW_BLOCK_SIZE + i * ICON_BLOCK_SIZE;
578             final Icon icon = iconFromTemplateData(data.getByteArray(iconBase, ICON_BLOCK_SIZE, false), indirectedData);
579             windowBlock.addIcon(icon);
580         }
581         return windowBlock;
582     }
583 
584     private Icon iconFromTemplateData(ByteArray data, BinaryDataSource indirectedData) {
585         final Icon icon = new Icon();
586         icon.setBoundingBox(data.getRectangle(0));
587         final int iconFlags = data.getWord(16);
588         icon.setIconFlags(iconFlags & 0x00f00fff);
589         icon.setButtonType((iconFlags & 0xf000) >> 12);
590         icon.setSelectionGroup((iconFlags & 0xf0000) >> 16);
591         icon.setForegroundColour((iconFlags & 0xf000000) >> 24);
592         icon.setBackgroundColour((iconFlags & 0xf0000000) >>> 28);
593         if (icon.isTextIcon()) {
594             if ((iconFlags & Icon.INDIRECTED) == 0) {
595                 icon.setText(data.getStringControlTerminated(20, 12));
596             } else {
597                 final int address = data.getWord(20);
598                 icon.setText(indirectedData.getStringControlTerminated(address, data.getWord(28)));
599                 icon.setTextIndirectAddress(address);
600             }
601         }
602         if (icon.isSpriteIcon()) {
603             if ((iconFlags & Icon.INDIRECTED) == 0) {
604                 icon.setSprite(WIMP_SPRITE_AREA.getSprite(data.getStringControlTerminated(20, 12)));
605             } else {
606                 if (icon.isTextIcon()) {
607                     final int address = data.getWord(24);
608                     icon.setValidationIndirectAddress(address);
609                     final String validationString = indirectedData.getStringControlTerminated(address);
610                     final String[] commands = validationString.split(";");
611                     for (int j = 0; j < commands.length; j++) {
612                         String command = commands[j];
613                         if (command.charAt(0) == 'S') {
614                             final String spriteName = command.substring(1).split(",")[0];
615                             icon.setSprite(WIMP_SPRITE_AREA.getSprite(spriteName));
616                             break;
617                         }
618                     }
619                 } else {
620                     final int iconSpriteArea = data.getWord(24);
621                     if (data.getWord(28) != 0 && iconSpriteArea == 1) {
622                         // Named sprite from Wimp sprite area again
623                         final int address = data.getWord(20);
624                         final String spriteName = indirectedData.getStringControlTerminated(address, 12);
625                                                                                   //data.getWord(28));
626                         icon.setSprite(WIMP_SPRITE_AREA.getSprite(spriteName));
627                         icon.setTextIndirectAddress(address);
628                     } else {
629                         log.warning("only named sprites from the WIMP sprite area supported");
630                     }
631                 }
632             }
633         }
634         return icon;
635     }
636 
637     private void windowStateFromWindow(Window window, ByteArray data) {
638         data.setRectangle(0, window.getVisibleArea());
639         data.setPoint(16, window.getScrollOffset());
640         data.setInt(24, window.getHandleOfWindowAbove());
641         data.setInt(28, window.getWindowFlags());
642     }
643 
644     private void iconBlockFromIcon(Icon icon, ByteArray data) {
645         data.setRectangle(0, icon.getBoundingBox());
646         data.setInt(16, icon.getIconFlags() | (icon.getButtonType() << 12) | (icon.getSelectionGroup() << 16) |
647                         (icon.getForegroundColour() << 24) | (icon.getBackgroundColour() << 28));
648         final boolean indirected = (icon.getIconFlags() & Icon.INDIRECTED) != 0;
649         if (icon.isTextIcon()) {
650             if (indirected) {
651                 data.setInt(20, icon.getTextIndirectAddress());
652             } else {
653                 data.setString0(20, icon.getText(), 12);
654             }
655         }
656         if (icon.isSpriteIcon()) {
657             if (indirected) {
658                 if (icon.isTextIcon()) {
659                     data.setInt(24, icon.getValidationIndirectAddress());
660                     data.setInt(28, icon.getValidationString().length());
661                 } else {
662                     data.setInt(24, 1); // TODO: Wimp Sprite area only
663                     data.setInt(20, icon.getTextIndirectAddress());
664                     data.setInt(28, icon.getSprite().getName().length());
665                 }
666             } else {
667                 data.setString0(20, icon.getSprite().getName(), 12);
668             }
669         }
670     }
671 
672     /***
673      * This SWI opens a standard, non-multitasking, errorbox on the screen. Avoid, if possible. It is boring and, as
674      * stated above, non-multitasking. However, the RO3.5 extensions make the use of error boxes much more appealing to
675      * the programmer. Entry: R0 = pointer to standard SWI errorblock R1 = flags (1 = OK, 2 cancel, 4 = default cancel,
676      * 8 = no prompt, 16 = no "Error from App:", 32 = return immediately, 64 = pretend click, 128 = silent, 256 = use
677      * categories bits 9-11 = category, bits 24-27 = Continue value, bits 28-31 = Quit value) Categories are: 1=info,
678      * 2=warning, 3=program msg, 4=question, 5=user1, 6=user2, 7=reserved R2 = pointer to application name (or title if
679      * bit 4 set), or 0 for simply 'Error' R3 = sprite name (if bit 8 set) R4 = sprite area (if bit 8 set) R5 = pointer
680      * to extra buttons string (if bit 8 set) Exit: R1 = 0 if nothing selected, 1 if OK selected and 2 if Cancel, 3-5 if
681      * extra buttons Return gives default, Escape the alternate R0 corrupt
682      *
683      * @param env
684      */
685     public final void ReportError(Environment env) {
686         final int[] R = env.getCpu().R;
687         final MemoryMap memoryMap = env.getMemoryMap();
688         final int errorNumber = memoryMap.getWord(R[0]);
689         final String errorMessage = memoryMap.getString0(R[0] + 4);
690         final SWIError swiError = new SWIError(errorNumber, errorMessage);
691         final String title;
692         if (R[2] == 0) {
693             title = "Error";
694         } else if ((R[1] & 16) != 16) {
695             title = "Error from " + memoryMap.getString0(R[2]);
696         } else {
697             title = memoryMap.getString0(R[2]);
698         }
699         try {
700             FlexWalk.walk(env, 0x50000000); //TODO: remove debugging
701         } catch (IOException e) {
702             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
703         }
704         R[1] = reportError(swiError, title);
705     }
706 
707     public final int reportError(SWIError err, String title) {
708         JFrame f;
709         log.warning("Wimp.reportError does not pay any attention to the options specified");
710         log.info("reportError: " + err.getMessage());
711         JOptionPane.showMessageDialog(f = new JFrame(), err.getMessage(), title, JOptionPane.ERROR_MESSAGE);
712         f.dispose();
713         return 0;
714     }
715 
716     /***
717      * This SWI returns the current Wimp palette settings, in the form &B0G0R0LL, where L is the logical colour number
718      * used for the Wimp colour 0 to 15 (or %TTGGGGGG in 256 colour modes). Note that when even when the values are
719      * masked to give &B0R0G000 this is not correct for passing to ColourTrans. If 'TRUE' is used, then the true colour
720      * values are returned.
721      *
722      * @param env IN: R1 = pointer to 20-word palette block, R2 = "TRUE" (&45555254) to return true colour; OUT: R0
723      *            corrupt, block contains palette definitions
724      */
725     public final void ReadPalette(Environment env) {
726         final int[] R = env.getCpu().R;
727         final int TRUE = 0x45555254;
728         final boolean trueColours = R[2] == TRUE;
729         final ByteArray block = env.getMemoryMap().getByteArray(R[1], 80, false);
730         final Color[] palette = readPalette();
731         for (int i = 0; i < palette.length; i++) {
732             final int bbggrr00 = ColourUtils.nativeToAcorn(palette[i]);
733             block.setInt(i * 4, trueColours? bbggrr00 : ((bbggrr00 & 0xf0f0f000) | i));
734         }
735     }
736 
737     /***
738      * Sixteen Wimp colours, border colour and pointer colours 0-2 makes 20 colours altogether.
739      * Colours are formatted &BBGGRR00.
740      */
741     private Color[] palette = new Color[] {
742         Color.BLACK, Color.DARK_GRAY, Color.DARK_GRAY, Color.GRAY,
743         Color.GRAY, Color.LIGHT_GRAY, Color.LIGHT_GRAY, Color.WHITE,
744         Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE,
745         Color.MAGENTA, Color.CYAN, Color.PINK, Color.ORANGE,
746         Color.BLACK, Color.BLUE, Color.CYAN, Color.RED
747     };
748 
749     public Color[] readPalette() {
750         return palette;
751     }
752 
753     private final static SpriteArea WIMP_SPRITE_AREA = new SpriteArea(0x400000);
754 
755     /***
756      * This call is mapped to OS_SpriteOp.
757      * &100 is added to reason code in R0, and R1 is set to the Wimp sprite-area pointer.
758      * @param env IN: R0 = reason code (0..&FF), R1 unused, R2 = pointer to sprite name, R3.. as for OS_SpriteOp; OUT: as for OS_SpriteOp
759      */
760     public final void SpriteOp(Environment env) {
761         final int[] R = env.getCpu().R;
762         OS.getInstance().SpriteOp(R, WIMP_SPRITE_AREA, env.getMemoryMap().getString0(R[2]));
763     }
764 
765     public final void spriteOp(int operation, String spriteName) {
766         synchronized (WIMP_SPRITE_AREA) {
767             OS.getInstance().spriteOp(operation, WIMP_SPRITE_AREA, spriteName);
768         }
769     }
770 
771     public final BufferedImage spriteOp40(String spriteName) {
772         synchronized (WIMP_SPRITE_AREA) {
773             return OS.getInstance().spriteOp40(WIMP_SPRITE_AREA, spriteName);
774         }
775     }
776 
777     public final void SlotSize(Environment env) {
778         final int[] R = env.getCpu().R;
779         SlotSize slotSize = getSlotSize(env.getMemoryMap());
780         if (R[0] != -1) {
781 //            log.warning("We do not support setting current slot size, returning size of &"
782 //                    + Integer.toHexString(R[0]));
783             R[0] = setSlotSize(R[0], env.getMemoryMap());
784         } else {
785             R[0] = slotSize.current;
786         }
787         if (R[1] != -1) {
788             log.warning("We do not support setting next slot size, returning success");
789         } else {
790             R[1] = slotSize.next;
791         }
792         R[2] = slotSize.free;
793     }
794 
795     public int setSlotSize(final int newSize, final MemoryMap memoryMap) {
796         memoryMap.setImageSize(newSize);
797         return memoryMap.getImageSize();
798     }
799 
800     public final SlotSize getSlotSize(MemoryMap mem) {
801         return new SlotSize(mem.getImageSize(), Integer.MAX_VALUE, Integer.MAX_VALUE);
802     }
803 
804     public final void ReadSysInfo(Environment env) {
805         final int[] R = env.getCpu().R;
806         int[] results = readSysInfo(env.getMemoryMap(), R[0]);
807         System.arraycopy(results, 0, R, 0, results.length);
808     }
809 
810     RawDataBlock spriteSuffixBlock = null;
811 
812     private int[] readSysInfo(MemoryMap memoryMap, int itemIndex) {
813         switch (itemIndex) {
814             case 2: // Sprite suffix
815                 if (spriteSuffixBlock == null) {
816                     spriteSuffixBlock = memoryMap.createSystemDataBlock("22");
817                 }
818                 return new int[]{spriteSuffixBlock.getAddress()};
819             case 3: // Desktop state
820                 return new int[]{(wimpTask == null) ? 0 : 1};
821             case 5: // Current task
822                 if (wimpTask == null) {
823                     return new int[]{0, 0};
824                 } else {
825                     return new int[]{wimpTask.getTaskHandle(), wimpTask.getKnownWimpVersion()};
826                 }
827             case 7: // Window manager version
828                 return new int[]{WIMP_VERSION};
829             default:
830                 log.warning("Unknown Wimp.ReadSysInfo " + itemIndex);
831                 throw new SWIError(0, "Unknown Wimp.ReadSysInfo " + itemIndex);
832         }
833     }
834 }
835