/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> * * Copyright (C) 2000 Dave Slaughter * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross; /* * Note: Everything that calls TotalCross code in these classes must be * synchronized with respect to the Applet uiLock object to allow TotalCross * programs to be single threaded. This is because of the multi-threaded * nature of Java and because timers use multiple threads. * * Because all calls into TotalCross are synchronized and users can't call this code, * they can't deadlock the program in any way. If we moved the synchronization * into TotalCross code, we would have the possibility of deadlock. */ import java.awt.*; import java.awt.Insets; import java.awt.event.*; import java.awt.event.KeyListener; import java.awt.event.WindowListener; import java.awt.image.*; import java.io.*; import java.io.File; import java.io.FileNotFoundException; import java.net.*; import java.util.zip.*; import tc.tools.*; import totalcross.io.*; import totalcross.io.IOException; import totalcross.sys.*; import totalcross.ui.*; import totalcross.ui.event.*; import totalcross.ui.event.KeyEvent; import totalcross.util.*; import totalcross.util.zip.*; /** Represents the applet or application used as a Java Container to make possible run TotalCross at the desktop. */ final public class Launcher extends java.applet.Applet implements WindowListener, KeyListener, java.awt.event.MouseListener, MouseWheelListener, MouseMotionListener, ComponentListener { public static Launcher instance; public static boolean isApplication; public static boolean terminateIfMainClass = true; public String commandLine = ""; public int threadCount; public Hashtable htOpenedAt = new Hashtable(31); // guich@200b4_82 public IntHashtable keysPressed = new IntHashtable(129); public MainWindow mainWindow; public boolean showKeyCodes; public Hashtable htAttachedFiles = new Hashtable(5); // guich@566_28 public static int userFontSize = -1; private int toBpp = -1; private int toWidth = -1; private int toHeight = -1; private String className; private boolean appletInitialized; // guich@500_1 private LauncherFrame frame; private int toUI=-1; // guich@573_6: since now we have 4 styles, select the target one directly. private double toScale = -1; private int toX=-1,toY=-1; private WinTimer winTimer; private boolean started; // guich@120 private boolean destroyed; // guich@230_24 private boolean settingsFilled; private int[] screenPixels = new int[0]; private int lookupR[], lookupG[], lookupB[], lookupGray[]; private int pal685[]; private Class<?> _class; // used by the openInputStream method. protected MemoryImageSource screenMis; protected java.awt.Image screenImg; private AlertBox alert; private String frameTitle; private String crid4settings; // prevent from having two different crids for loading and storing the settings. private StringBuffer mmsb = new StringBuffer(32); private TCEventThread eventThread; private boolean isMainClass; private boolean isDemo; private String activationKey; @SuppressWarnings("deprecation") public Launcher() { totalcross.sys.Settings.showDesktopMessages = false; // guich@500_1: avoid messages when calling retroguard instance = this; addKeyListener(this); addMouseListener(this); addMouseWheelListener(this); addMouseMotionListener(this); try {Runtime.runFinalizersOnExit(true);} catch (Throwable t) {} //try {System.runFinalizersOnExit(true);} catch (Throwable t) {} // guich@300_31 } public void destroy() { if (mainWindow == null || destroyed) return; destroyed = true; eventThread.invokeInEventThread(true, new Runnable() { public void run() { mainWindow.appEnding(); System.runFinalization(); storeSettings(); } }); winTimer.stopGracefully(); // timer must be running when appEnding is called } private void runtimeInstructions() { System.out.println("Current path: "+System.getProperty("user.dir")); System.out.println("TotalCross "+Settings.versionStr+"."+Settings.buildNumber); // print instructions System.out.println("==================================="); System.out.println("Device key emulations:"); System.out.println("F2 : TAKE SCREEN SHOT AND SAVE TO CURRENT FOLDER"); System.out.println("F6 : MENU"); System.out.println("F7 : BACK (ESCAPE)"); System.out.println("F9 : CHANGE ORIENTATION"); System.out.println("F11: OPEN KEYBOARD"); System.out.println("==================================="); } @SuppressWarnings("static-access") public void init() { boolean showInstructionsOnError = true; appletInitialized = true; // guich@500_1 totalcross.sys.Settings.showDesktopMessages = true; // guich@500_1: redo the messages. try { alert = new AlertBox(); // NOTE: getParameter() and size() don't work in a // java applet constructor, so we need to call them here if (!isApplication) { String arguments = getParameter("arguments"); if (arguments == null) throw new Exception("Error: you must suply an 'arguments' property with all the argments to create the application"); String []args = tokenizeString(arguments,' '); parseArguments(args); } fillSettings(); new RegisterSDK(activationKey); try { _class = getClass(); // guich@500_1: we can use ourselves // if the user pass: tc/samples/ui/image/test/ImageTest.class, change to tc.samples.ui.image.test.ImageTest if (className.endsWith(".class")) className = className.substring(0,className.length()-6); className = className.replace('/','.'); Class<?> c = _class.forName(className); // guich@200b2: applets dont let you specify the path. it must be set in the codebase param - guich@520_9: changed from Class. to getClass showInstructionsOnError = false; isMainClass = checkIfMainClass(c); // guich@tc122_4 if (!isMainClass) runtimeInstructions(); Object o = c.newInstance(); if (o instanceof MainClass && !(o instanceof MainWindow)) { ((MainClass)o).appStarting(0); ((MainClass)o).appEnding(); if (terminateIfMainClass) System.exit(0); // currently we just exit after the constructor is called in a Non-GUI (headless) application else return; } mainWindow = (MainWindow)o; // NOTE: java will call a partially constructed object if show() is called before all the objects are constructed if (isApplication) { frame = new LauncherFrame(); requestFocus(); } else setLayout(new java.awt.BorderLayout()); if (toUI != -1) // now is safe mainWindow.setUIStyle((byte)toUI); } catch (LinkageError le) { System.out.println("Fatal Error when running applet: there is an error in the constructor of the class "+className+" and it could not be instantiated. Stack trace: "); le.printStackTrace(); exit(0); } catch (ClassCastException cce) { System.out.println("Error: class "+className+" does not extend MainClass nor MainWindow!"); cce.printStackTrace(); exit(-1); } catch (ClassNotFoundException cnfe) { System.out.println("The MainWindow class specified was not found: "+className+"\n\nCommon causes are:"); System.out.println(". The name is misspelled: java is case sensitive, so UIGadgets is not the same of uigadgets"); if (className.indexOf('.') < 0) System.out.println(". The package name is incorrect: if you declared a class like: \n package com.foo.bar;\n public class "+className+"\n then you must specify com.foo.bar."+className+" as the main class; only specifying "+className+" is not enough."); System.out.println(". Its location was not added to the classpath: if you're running from the prompt, be sure to add the path where your application is to the CLASSPATH argument. For example, if the class is in the current path, add a . specifying the current path: java -classpath .;tc.jar totalcross.Launcher "+className); exit(-1); } } catch (RegisterSDKException re) { System.out.println("SDK registration returned: "+re.getMessage()); exit(-2); } catch (Exception ee) { if (showInstructionsOnError) showInstructions(); ee.printStackTrace(); } // guich@120 } private static boolean checkIfMainClass(Class<?> c) { Class<?> []interfaces = c.getInterfaces(); if (interfaces != null) for (int i = 0; i < interfaces.length; i++) if (interfaces[i].getName().equals("totalcross.MainClass")) return true; return false; } private class LauncherFrame extends Frame { private Insets insets; public LauncherFrame() { setBackground(new java.awt.Color(getScreenColor(mainWindow.getBackColor()))); setResizable(Settings.resizableWindow); // guich@570_54 setLayout(null); add(instance); addNotify(); // without this, the insets will not be correctly set. insets = getInsets(); if (insets == null) insets = new Insets(0,0,0,0); setFrameSize(toWidth, toHeight, true); setLocation(toX,toY); super.setTitle(frameTitle != null ? frameTitle : mainWindow.getClass().getName()); setVisible(true); addWindowListener(instance); addComponentListener(instance); } public void update(java.awt.Graphics g) {} public void setFrameSize(int toWidth, int toHeight, boolean set) { if (set) setSize((int)(toWidth*toScale) + insets.left + insets.right, (int)(toHeight*toScale )+ insets.top + insets.bottom); instance.setBounds(insets.left,insets.top,(int)(toWidth*toScale), (int)(toHeight*toScale)); } }; private class WinTimer extends java.lang.Thread { private int interval; private boolean shouldStop; public void run() { // NOTE: because we have created an official event queue/thread, which now // resembles the device event queue much more closely, we must be // sure that all timers and TC threads are run in that event thread. This // will ensure that such things as blinking cursors will continue to work // if there is a blocking modal dialog open. This also means that TC JDK // threads will act much more like the device threads... in that, threads // will not run unless a message pump is running. while (!shouldStop) { boolean doTick = true; int millis = interval; if (millis <= 0) { // NOTE: Netscape navigator doesn't support interrupt() // so we sleep here less than we would normally need to // (1 second) if we're not doing anything to check if // the timer should start in case interrupt didn't work millis = 1 * 1000; doTick = false; } // guich@200b4_84: implement the simple thread long first = System.currentTimeMillis(); while ((System.currentTimeMillis()-first) < millis) { try { sleep(millis); doTick = true; // guich@230_3 break; // guich@230_3 } catch (InterruptedException e) { doTick = false; break; // guich@230_4 } } if (doTick && eventThread != null) { eventThread.invokeInEventThread(false, new Runnable() { public void run() { synchronized (instance) // guich@510_2: synchronize the repaint with the timer { mainWindow._onTimerTick(true); } } }); } } } void setInterval(int millis) { //System.out.println("setInterval "+millis); interval = millis<10 ? 10 : millis; // guich@230_3 interrupt(); } void stopGracefully() { // NOTE: It's not a good idea to call stop() on threads since // it can cause the JVM to crash. shouldStop = true; interrupt(); } } public boolean eventIsAvailable() { return eventThread.eventAvailable(); } void startApp() { eventThread = new TCEventThread(mainWindow); if (!started) // guich@120 - make sure that the component is available for drawing when starting the application. called by paint. { try { eventThread.invokeInEventThread(true, new Runnable() { public void run() { while (mainWindow == null) Vm.sleep(1); mainWindow.appStarting(isDemo ? 80 : -1); } // guich@200b4_107 - guich@570_3: check if mainWindow is not null to avoid problems when running on Linux. seems that the paint event is being generated before the start one. }); } catch (Throwable e) {e.printStackTrace();} started = true; } } private static void showInstructions() { System.out.println("Possible Arguments (in any order and case insensitive). Default is marked as *"); System.out.println(" /scr WIDTHxHEIGHT : sets the width and height"); System.out.println(" /scr WIDTHxHEIGHTxBPP : sets the width, height and bits per pixel (8, 16, 24 or 32)"); System.out.println(" /scr Win32 : Windows 32 (same of /scr 240x320x24)"); System.out.println(" /scr iPhone : iPhone (same of /scr 640x960x24)"); System.out.println("* /scr android : Android (same of /scr 320x480x24)"); System.out.println(" /pos x,y : Sets the openning position of the application"); System.out.println(" /uiStyle Flat : Flat user interface style"); System.out.println("* /uiStyle Vista : Vista user interface style"); System.out.println(" /uiStyle Android : Android user interface style"); System.out.println(" /penlessDevice : acts as a device that has no touchscreen."); System.out.println(" /fingerTouch : acts as a device that uses a finger instead of a pen."); System.out.println(" /unmovablesip : acts as a device whose SIP is unmovable (like in Android and iPhone)."); System.out.println(" /geofocus : enables geographical focus."); System.out.println(" /virtualKeyboard : shows the virtual keyboard when in an Edit or a MultiEdit"); System.out.println(" /showmousepos : shows the mouse position."); System.out.println(" /bpp 8 : emulates 8 bits per pixel screens (256 colors)"); System.out.println(" /bpp 16 : emulates 16 bits per pixel screens (64K colors)"); System.out.println(" /bpp 24 : emulates 24 bits per pixel screens (16M colors)"); System.out.println(" /bpp 32 : emulates 32 bits per pixel screens (16M colors without transparency)"); System.out.println(" /scale <0.1 to 8>: scales the screen, magnifying the contents using a smooth scale."); System.out.println(" /dataPath <path> : sets where the PDB and media files are stored"); System.out.println(" /cmdLine <...> : the rest of arguments-1 are passed as the command line"); System.out.println(" /fontSize <size> : set the default font size to the one passed as parameter"); System.out.println(" /r <key> : specify a registration key to be used to activate TotalCross when required"); System.out.println("The class name that extends MainWindow must always be the last argument"); } public static void main(String args[]) { if (args.length == 0) { showInstructions(); return; } isApplication = true; Launcher app = new Launcher(); app.parseArguments(args); app.init(); } private int toInt(String s) // Convert.toInt can't be used here, otherwise, the settings will be set too early! { try {return Integer.parseInt(s);} catch (Exception e) {return 0;} } private double toDouble(String s) { try {return Double.parseDouble(s);} catch (Exception e) {return 0;} } protected void parseArguments(String[] args) { int n = args.length-1,i=0; String newDataPath = null; try { className = args[n]; for (i = 0; i < n; i++) { if (args[i].equalsIgnoreCase("/fontsize")) userFontSize = toInt(args[++i]); else if (args[i].equalsIgnoreCase("/dataPath")) { newDataPath = args[++i]; System.out.println("Data path is "+newDataPath); } else if (args[i].equalsIgnoreCase("/scr")) /* /scr 320x320 or /scr 320x320x8 */ { String next = args[++i]; if (next.equalsIgnoreCase("win32")) { toWidth = 240; toHeight = 320; toBpp = 24; } else if (next.equalsIgnoreCase("iPhone")) { toWidth = 640; toHeight = 960; toBpp = 24; } else if (next.equalsIgnoreCase("android")) { toWidth = 320; toHeight = 480; toBpp = 24; } else { String []scr = tokenizeString(next.toLowerCase(),'x'); if (scr.length == 1) throw new Exception(); toWidth = toInt(scr[0]); toHeight = toInt(scr[1]); if (scr.length == 3) toBpp = toInt(scr[2]); } System.out.println("Screen is "+toWidth+"x"+toHeight+"x"+toBpp); } else if (args[i].equalsIgnoreCase("/r")) { activationKey = args[++i].toUpperCase(); if (activationKey.length() != 24) throw new RuntimeException("Invalid registration key"); } else if (args[i].equalsIgnoreCase("/pos")) /* x,y */ { String []scr = tokenizeString(args[++i].toLowerCase(),','); if (scr.length == 1) throw new Exception(); toX = toInt(scr[0]); toY = toInt(scr[1]); } else if (args[i].equalsIgnoreCase("/cmdline")) { commandLine = ""; while (++i < n) commandLine += args[i] + " "; commandLine = commandLine.trim(); System.out.println("Command line is '"+commandLine+"'"); } else if (args[i].equalsIgnoreCase("/uiStyle")) { String next = args[++i]; if (next.equalsIgnoreCase("Flat")) toUI = Settings.Flat; else if (next.equalsIgnoreCase("Vista")) toUI = Settings.Vista; else if (next.equalsIgnoreCase("Android")) // guich@580_33 toUI = Settings.Android; else throw new Exception(); System.out.println("UI style is "+toUI); } else if (args[i].equalsIgnoreCase("/penlessDevice")) // guich@573_20 { Settings.keyboardFocusTraversable = true; System.out.println("Penless device is on"); } else if (args[i].equalsIgnoreCase("/fingertouch")) // guich@573_20 { Settings.fingerTouch = true; System.out.println("Finger touch is on"); } else if (args[i].equalsIgnoreCase("/unmovablesip")) // guich@573_20 { Settings.unmovableSIP = true; System.out.println("Unmovable SIP is on"); } else if (args[i].equalsIgnoreCase("/geofocus")) // guich@tc114_31 { Settings.geographicalFocus = Settings.keyboardFocusTraversable = true; System.out.println("Geographical focus is on"); } else if (args[i].equalsIgnoreCase("/virtualKeyboard")) // bruno@tc110 { Settings.virtualKeyboard = true; System.out.println("Virtual keyboard is on"); } else if (args[i].equalsIgnoreCase("/bpp")) { toBpp = toInt(args[++i]); if (toBpp != 8 && toBpp != 16 && toBpp != 24 && toBpp != 32) // guich@450_4 throw new Exception(); System.out.println("Bpp is "+toBpp); } else if (args[i].equalsIgnoreCase("/scale")) { toScale = toDouble(args[++i]); // guich@tc126_74: use a if (toScale < 0 || toScale > 8) throw new Exception(); System.out.println("Scale is "+toScale); } else if (args[i].equalsIgnoreCase("/showmousepos")) Settings.showMousePosition = true; else if (args[i].equalsIgnoreCase("/demo")) isDemo = true; else throw new Exception(); } } catch (Exception e) { showInstructions(); System.out.println("Invalid or incomplete argument at position "+i+": "+args[i]); String s = ""; for (i = 0; i < args.length; i++) s += " "+args[i]; System.out.println("Full command line:\n"+s.trim()); exit(-1); return; } // verify the parameters if (toWidth == -1 || toHeight == -1) // if no width specified, use the lowest one { if (isApplication) { toWidth = 320; toHeight = 480; // guich@tc100b5_35: now default is palm hi } else { toWidth = getSize().width; toHeight = getSize().height; } } if (toScale == -1) // if no scale specified, adjust depending on the resolution toScale = toWidth < 240 ? 2 : 1; if (toBpp == -1) toBpp = isApplication ? 16 : 32; Settings.dataPath = newDataPath; if (activationKey == null || activationKey.length() != 24) { System.out.println("Error: you must provide a registration key with /r in totalcross.Launcher arguments!"); System.exit(0); return; } } private String[] tokenizeString(String string, char c) { java.util.StringTokenizer st = new java.util.StringTokenizer(string, ""+c); String []ret = new String[st.countTokens()]; for (int i =0; i < ret.length; i++) ret[i] = st.nextToken(); return ret; } public void start() { mainWindow = MainWindow.getMainWindow(); } ///////// guich@200b2: to make the vm easier to port, i removed all methods from the TotalCross classes that uses the jdk classes ///////// public void registerMainWindow(totalcross.ui.MainWindow main) { (winTimer = new WinTimer()).start(); // guich@510_2: start the timer only after we had added the others } public void setTimerInterval(int milliseconds) { winTimer.setInterval(milliseconds); } public void exit(int exitCode) { destroy(); // guich@230_24 if (isApplication) System.exit(exitCode); } public void minimize() { if (frame != null) frame.setExtendedState(Frame.ICONIFIED); } public void restore() { if (frame != null) frame.setExtendedState(Frame.NORMAL); } public void print(java.awt.Graphics g) { } public boolean isFocusTraversable() // guich@512_1: inform that we want to handle tab { return true; } private int modifiers; private void updateModifiers(java.awt.event.KeyEvent event) { if (event.isShiftDown()) { keysPressed.put(SpecialKeys.SHIFT,1); modifiers |= SpecialKeys.SHIFT; } else { keysPressed.put(SpecialKeys.SHIFT,0); modifiers &= ~SpecialKeys.SHIFT; } if (event.isControlDown()) { keysPressed.put(SpecialKeys.CONTROL,1); modifiers |= SpecialKeys.CONTROL; } else { keysPressed.put(SpecialKeys.CONTROL,0); modifiers &= ~SpecialKeys.CONTROL; } if (event.isAltDown()) { keysPressed.put(SpecialKeys.ALT,1); modifiers |= SpecialKeys.ALT; } else { keysPressed.put(SpecialKeys.ALT,0); modifiers &= ~SpecialKeys.ALT; } } public void keyPressed(final java.awt.event.KeyEvent event) { if (event.getKeyChar() == '1' && event.isControlDown()) totalcross.ui.Window.onRobotKey(); updateModifiers(event); if (event.isActionKey()) { updateModifiers(event); int key = 0; switch (event.getKeyCode()) { case java.awt.event.KeyEvent.VK_HOME: key = SpecialKeys.HOME; break; case java.awt.event.KeyEvent.VK_END: key = SpecialKeys.END; break; case java.awt.event.KeyEvent.VK_UP: key = SpecialKeys.UP; break; case java.awt.event.KeyEvent.VK_DOWN: key = SpecialKeys.DOWN; break; case java.awt.event.KeyEvent.VK_LEFT: key = SpecialKeys.LEFT; break; case java.awt.event.KeyEvent.VK_RIGHT: key = SpecialKeys.RIGHT; break; case java.awt.event.KeyEvent.VK_INSERT: key = SpecialKeys.INSERT; break; case java.awt.event.KeyEvent.VK_ENTER: key = SpecialKeys.ENTER; break; case java.awt.event.KeyEvent.VK_TAB: key = SpecialKeys.TAB; break; case java.awt.event.KeyEvent.VK_BACK_SPACE: key = SpecialKeys.BACKSPACE; break; case java.awt.event.KeyEvent.VK_ESCAPE: key = SpecialKeys.ESCAPE; break; case java.awt.event.KeyEvent.VK_DELETE: key = SpecialKeys.DELETE; break; case java.awt.event.KeyEvent.VK_PAGE_UP: key = SpecialKeys.PAGE_UP; keysPressed.put(key,1); keysPressed.put(java.awt.event.KeyEvent.VK_PAGE_DOWN,0); break; // don't let down/up simultanealy case java.awt.event.KeyEvent.VK_PAGE_DOWN: key = SpecialKeys.PAGE_DOWN; keysPressed.put(key,1); keysPressed.put(java.awt.event.KeyEvent.VK_PAGE_UP,0); break; // guich@120 - emulate more keys case java.awt.event.KeyEvent.VK_F1: break; case java.awt.event.KeyEvent.VK_F2: takeScreenShot(); break; case java.awt.event.KeyEvent.VK_F3: break; case java.awt.event.KeyEvent.VK_F4: break; case java.awt.event.KeyEvent.VK_F5: break; case java.awt.event.KeyEvent.VK_F6: key = SpecialKeys.MENU; break; case java.awt.event.KeyEvent.VK_F7: key = SpecialKeys.ESCAPE; break; case java.awt.event.KeyEvent.VK_F8: break; case java.awt.event.KeyEvent.VK_F10: break; case java.awt.event.KeyEvent.VK_F11: key = SpecialKeys.KEYBOARD_ABC; break; case java.awt.event.KeyEvent.VK_F12: break; case java.awt.event.KeyEvent.VK_F9: if (isApplication && !Settings.disableScreenRotation && Settings.screenWidth != Settings.screenHeight && eventThread != null) // guich@tc: changed orientation? { int t = toWidth; toWidth = toHeight; toHeight = t; screenResized(Settings.screenHeight,Settings.screenWidth,true); key = 0; ignoreNextResize = true; } break; default: key = 0; break; } if (key != 0 && eventThread != null) // sometimes, when debugging in applet, eventThread can be null { eventThread.pushEvent(KeyEvent.SPECIAL_KEY_PRESS, key, 0, 0, modifiers, Vm.getTimeStamp()); } if (showKeyCodes && eventThread != null) { final String msg = "Key code: " + (key == 0 ? event.getKeyCode() : key) + ", Modifier: " + modifiers; new Thread() {public void run() {Vm.alert(msg);}}.start(); // must place this in a separate thread, or the vm dies } } } private void takeScreenShot() { try { totalcross.ui.image.Image img = MainWindow.getScreenShot(); String name = totalcross.sys.Settings.appPath+new Time().getTimeLong()+".png"; totalcross.io.File f = new totalcross.io.File(name,totalcross.io.File.CREATE_EMPTY); img.createPng(f); f.close(); System.out.println("Saved at "+name); } catch (Exception e) { e.printStackTrace(); } } private void screenResized(int w, int h, boolean setframe) { if (screenMis == null || (Settings.screenWidth == w && Settings.screenHeight == h)) return; Settings.screenWidth = w; Settings.screenHeight = h; frame.setFrameSize(w,h,setframe); screenMis = null; // force the creation of a new screen image eventThread.pushEvent(KeyEvent.SPECIAL_KEY_PRESS, SpecialKeys.SCREEN_CHANGE, 0, 0, modifiers, Vm.getTimeStamp()); } public void transferFocus() // guich@512_1: handle the tab key. { super.transferFocus(); if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null eventThread.pushEvent(KeyEvent.SPECIAL_KEY_PRESS, SpecialKeys.TAB, 0, 0, modifiers, Vm.getTimeStamp()); } public void keyReleased(java.awt.event.KeyEvent event) { updateModifiers(event); if (event.isActionKey()) switch (event.getKeyCode()) { // case java.awt.event.KeyEvent.VK_F1: keysPressed.put(SpecialKeys.HARD1,0); break; // case java.awt.event.KeyEvent.VK_F2: keysPressed.put(SpecialKeys.HARD2,0); break; // case java.awt.event.KeyEvent.VK_F3: keysPressed.put(SpecialKeys.HARD3,0); break; // case java.awt.event.KeyEvent.VK_F4: keysPressed.put(SpecialKeys.HARD4,0); break; case java.awt.event.KeyEvent.VK_PAGE_UP: keysPressed.put(SpecialKeys.PAGE_UP,0); break; case java.awt.event.KeyEvent.VK_PAGE_DOWN: keysPressed.put(SpecialKeys.PAGE_DOWN,0); break; } } public void keyTyped(java.awt.event.KeyEvent event) { updateModifiers(event); if (!event.isActionKey() && eventThread != null) { int key = event.getKeyChar(), orig = key; switch (key) { case 8 : key = SpecialKeys.BACKSPACE; break; case 10 : key = SpecialKeys.ENTER; break; case 127: key = SpecialKeys.DELETE; break; case 27 : key = SpecialKeys.ESCAPE; break; // guich@tc110_79 } eventThread.pushEvent(orig < 32 ? KeyEvent.SPECIAL_KEY_PRESS : KeyEvent.KEY_PRESS, key, 0, 0, modifiers, Vm.getTimeStamp()); } } boolean isRightButton; int startPY; public void mousePressed(java.awt.event.MouseEvent event) { int px = (int)(event.getX()/toScale); int py = (int)(event.getY()/toScale); if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null eventThread.pushEvent(PenEvent.PEN_DOWN, 0, px,py, modifiers, Vm.getTimeStamp()); if (isRightButton = (event.getButton() & 2) != 0) eventThread.pushEvent(MultiTouchEvent.SCALE, 1, px,startPY = py, modifiers, Vm.getTimeStamp()); } public void mouseReleased(java.awt.event.MouseEvent event) { int px = (int)(event.getX()/toScale); int py = (int)(event.getY()/toScale); if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null eventThread.pushEvent(PenEvent.PEN_UP, 0, px,py, modifiers, Vm.getTimeStamp()); if ((event.getButton() & 2) != 0) eventThread.pushEvent(MultiTouchEvent.SCALE, 2, px,py, modifiers, Vm.getTimeStamp()); } public void mouseDragged(java.awt.event.MouseEvent event) { int px = (int)(event.getX()/toScale); int py = (int)(event.getY()/toScale); if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null { if ((event.getButton() & 2) != 0 || isRightButton) { double scale = py < startPY ? 1.05 : 0.95; long l = Double.doubleToLongBits(scale); int x = (int)(l >>> 32); int y = (int)l; if (!eventThread.hasEvent(MultiTouchEvent.SCALE)) eventThread.pushEvent(MultiTouchEvent.SCALE, 0, x,y, modifiers, Vm.getTimeStamp()); } else if (!eventThread.hasEvent(PenEvent.PEN_DRAG)) eventThread.pushEvent(PenEvent.PEN_DRAG, 0, px, py, modifiers, Vm.getTimeStamp()); // guich@580_40: changed from 201 to 203; PenEvent.PEN_MOVE is deprecated } } public void mouseWheelMoved(MouseWheelEvent e) { if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null { int ev = totalcross.ui.event.MouseEvent.MOUSE_WHEEL; if (!eventThread.hasEvent(ev)) { int px = (int)(e.getX()/toScale); int py = (int)(e.getY()/toScale); eventThread.pushEvent(ev, e.getWheelRotation() < 0 ? totalcross.ui.event.DragEvent.UP : totalcross.ui.event.DragEvent.DOWN, px, py, modifiers, Vm.getTimeStamp()); // guich@580_40: changed from 201 to 203; PenEvent.PEN_MOVE is deprecated } } } public void windowClosing(java.awt.event.WindowEvent event) { if (Settings.closeButtonType == Settings.NO_BUTTON) eventThread.pushEvent(totalcross.ui.event.KeyEvent.SPECIAL_KEY_PRESS, SpecialKeys.MENU, 0,0,0, Vm.getTimeStamp()); else { destroy(); exit(0); } } public void mouseEntered(java.awt.event.MouseEvent event) { if (frame != null && frame.getFocusOwner() != this && !destroyed) // guich@320_39 requestFocus(); // guich@200b4: correct a bug that sometimes key events was not being sent anymore to the canvas. } public void mouseClicked(java.awt.event.MouseEvent event) {} public void mouseExited(java.awt.event.MouseEvent event) {} public void windowActivated(java.awt.event.WindowEvent event) {} public void windowClosed(java.awt.event.WindowEvent event) {} public void windowDeactivated(java.awt.event.WindowEvent event) {} public void windowDeiconified(java.awt.event.WindowEvent event) { if (mainWindow != null) mainWindow.onRestore(); } public void windowIconified(java.awt.event.WindowEvent event) { if (mainWindow != null) mainWindow.onMinimize(); } public void windowOpened(java.awt.event.WindowEvent event) {} public void mouseMoved(java.awt.event.MouseEvent event) { if (eventThread != null) // sometimes, when debugging in applet, eventThread can be null eventThread.pushEvent(totalcross.ui.event.MouseEvent.MOUSE_MOVE, 0, (int)(event.getX()/toScale), (int)(event.getY()/toScale), modifiers, Vm.getTimeStamp()); if (frame != null && Settings.showMousePosition) // guich@tc115_48 { mmsb.setLength(0); if (frameTitle != null) mmsb.append(frameTitle).append(" ("); mmsb.append(event.getX()).append(",").append(event.getY()); if (frameTitle != null) mmsb.append(")"); frame.setTitle(mmsb.toString()); } } public void paint(java.awt.Graphics g) { if (!started) // guich@120 - only call initUI after the component is valid startApp(); else eventThread.invokeInEventThread(false, new Runnable() { public void run() { try { totalcross.ui.Window.repaintActiveWindows(); } catch (Exception e) {System.out.println("Exception in Launcher.paint"); e.printStackTrace();} } }); } public void pumpEvents() { if (eventThread != null) eventThread.pumpEvents(); } public void update(java.awt.Graphics g) {} public void setNewMainWindow(MainWindow newInstance, String args) // called on Vm.exec { commandLine = args; // guich@200b3: added command line support for desktop classes. winTimer.stopGracefully(); // guich@120 MainWindow.destroyZStack(); mainWindow = newInstance; mainWindow.initUI(); // ps: since we are being called from an app, we cannot use the synchronized method } /** Calls System.out.println. TotalCross system debugging uses this method. See also debug(String s). */ public static void print(String s) { if (totalcross.sys.Settings.showDesktopMessages) System.err.println(s); } //// Graphics //////////////////////////////////////////////////////////////////// private void createColorPaletteLookupTables() { int i,r,g,b; lookupR = new int[256]; lookupG = new int[256]; lookupB = new int[256]; lookupGray = new int[256]; for (i = 0; i < 256; i++) { r = (i+1) * 6 / 256; if (r > 0) r--; g = (i+1) * 8 / 256; if (g > 0) g--; b = (i+1) * 5 / 256; if (b > 0) b--; lookupR[i] = r*40; lookupG[i] = g*5; lookupB[i] = b+16; lookupGray[i] = i / 0x11; } pal685 = totalcross.ui.gfx.Graphics.getPalette(); } private int getScreenColor(int p) { int r = (p >> 16) & 0xFF; int g = (p >> 8) & 0xFF; int b = p & 0xFF; switch (toBpp) { case 8: if (lookupR == null) createColorPaletteLookupTables(); return pal685[(g == r && g == b) ? lookupGray[r] : (lookupR[r] + lookupG[g] + lookupB[b])]; case 16: return (((r) >> 3) << 19) | (((g) >> 2) << 10) | (((b >> 3) << 3)); default: return p; } } public void updateScreen() { //int ini = totalcross.sys.Vm.getTimeStamp(); int[] pixels = (int[])totalcross.ui.gfx.Graphics.mainWindowPixels; int n = Settings.screenWidth * Settings.screenHeight; if (toBpp >= 24) screenPixels = pixels; else if (screenPixels.length < n) screenPixels = new int[n]; // convert to the target bpp on-the-fly switch (toBpp) { case 8: { if (lookupR == null) createColorPaletteLookupTables(); int[] pal = pal685; int[] toR = lookupR; int[] toG = lookupG; int[] toB = lookupB; int[] toGray = lookupGray; while (--n >= 0) { int p = pixels[n]; int r = (p >> 16) & 0xFF; int g = (p >> 8) & 0xFF; int b = p & 0xFF; screenPixels[n] = pal[(g == r && g == b) ? toGray[r] : (toR[r] + toG[g] + toB[b])]; } break; } case 16: { while (--n >= 0) screenPixels[n] = pixels[n] & 0xF8FCF8; // guich@tc100b4_2: use a direct and instead of a bunch of shifts. note: using a DirectColorModel(32,0xF80000,0x00FC00,0x0000F8,0) is 5x SLOWER than doing the mapping by ourselves. break; } } int w = totalcross.sys.Settings.screenWidth; int h = totalcross.sys.Settings.screenHeight; if (screenMis == null) { screenMis = new MemoryImageSource(w, h, new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0), screenPixels, 0, w); screenMis.setAnimated(true); screenMis.setFullBufferUpdates(true); screenImg = Toolkit.getDefaultToolkit().createImage(screenMis); } screenMis.newPixels(); Graphics g = getGraphics(); int ww = (int)(w*toScale); int hh = (int)(h*toScale); int shiftY = totalcross.ui.Window.shiftY; int shiftH = totalcross.ui.Window.shiftH; if ((shiftY+shiftH) > h) totalcross.ui.Window.shiftY = shiftY = h - shiftH; if (shiftY != 0) { g.setColor(new Color(UIColors.shiftScreenColor)); int yy = (int)(shiftH*toScale); g.fillRect(0, yy, ww, hh-yy); // erase empty area g.setClip(0,0,ww,yy); // limit drawing area g.translate(0,-(int)(shiftY*toScale)); } if (toScale != 1) // guich@tc126_74 - guich@tc130 { if (!MainWindow.isMainThread()) g.drawImage(screenImg, 0, 0, ww, hh, 0,0,w,h, this); // this is faster than use img.getScaledInstance else { Image img = screenImg.getScaledInstance(ww, hh, toScale != (int)toScale ? Image.SCALE_AREA_AVERAGING : Image.SCALE_FAST); g.drawImage(img, 0, 0, this); // this is faster than use img.getScaledInstance img.flush(); } } else if (g != null) g.drawImage(screenImg, 0, 0, ww, hh, 0,0,w,h, this); // this is faster than use img.getScaledInstance if (shiftY != 0) { g.translate(0,(int)(shiftY*toScale)); g.setClip(0,0,ww,hh); } // make the emulator work like OpenGL: erase the screen to instruct the user that everything must be drawn always //java.util.Arrays.fill(pixels, getScreenColor(UIColors.shiftScreenColor)); } //static int count; /////////////////////// I/O ///////////////////////////////////// private File[] getClassPathDirectories() throws Exception { char dirSeparator = File.pathSeparatorChar; File[] classPath; String pathstr = System.getProperty("java.class.path"); // Count the number of path separators int i=0; int n=0; int j=0; while ((i = pathstr.indexOf(dirSeparator, i)) != -1) { n++; i++; } // Build the class path File[] path = new File[n+1]; int len = pathstr.length(); for (i = n = 0; i < len; i = j + 1) { if ((j = pathstr.indexOf(dirSeparator, i)) == -1) j = len; if (i != j) { String p = pathstr.substring(i, j); File file = new File(p); if (!file.isDirectory()) file = new File(getPathOf(p)); // add the parent path of the file if (file.isDirectory()) path[n++]=file; } } // Trim class path to exact size classPath = new File[n]; System.arraycopy(path, 0, classPath, 0, n); return classPath; } private InputStream readJavaInputStream(java.io.InputStream is) { if (is == null) return null; ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); byte []buf = new byte[128]; int len; while (true) { try { len = is.read(buf); } catch (java.io.IOException e) {break;} if (len > 0) baos.write(buf,0,len); else break; } return new ByteArrayInputStream(baos.toByteArray()); } private String getPathOf(String pathAndFileName) { char []chars = pathAndFileName.toCharArray(); for (int i = chars.length-1; i >=0; i--) if (chars[i] == '\\' || chars[i] == '/') return new String(chars,0,i); return ""; // no path } public String getDataPath() // guich@420_11 - this now is needed because the user may change the datapath anywhere in the program { String path = totalcross.sys.Settings.dataPath; if (path != null) { path = path.replace('\\','/'); if (!path.endsWith("/")) path += "/"; // don't check for folder to keep compatibility with win32 vm //java.io.File f = new java.io.File(newDataPath); //if (!f.isDirectory()) // System.out.println("ERROR: dataPath specified is not a directory or does not exist! "+newDataPath); } return path; } private String getMainWindowPath() { if (MainWindow.getMainWindow() == null) return null; String main = MainWindow.getMainWindow().getClass().getName().replace('.','/'); return getPathOf(main)+"/"; } /** used in some classes so they can correctly open files. now can open jar files. */ @SuppressWarnings("resource") public InputStream openInputStream(String path) { String sread = "\nopening for read "+path+"\n"; String dataPath = getDataPath(); InputStream stream = null; String mainpath = getMainWindowPath(); try { try // guich@tc100: removed the nonGuiApp flag { sread += "#0 - the file given: "+path+"\n"; stream = new FileInputStream(path); // guich@421_72 } catch (Exception e) {stream = null;} if (stream == null && isApplication) { // search in the Settings.dataPath try { String p = isOk(dataPath)?(dataPath+path):path; sread += "#1 - dataPath: "+p+"\n"; stream = new FileInputStream(p); htOpenedAt.put(path, getPathOf(p)); // guich@200b4_82 - jr: i changed getPathOf(path) to getPathOf(p) } catch (Exception e) {stream = null;} if (stream == null && mainpath != null) try { String p = mainpath + path; sread += "#2 - MainWindow's path from current folder: "+p+"\n"; stream = new FileInputStream(p); htOpenedAt.put(path, getPathOf(p)); // guich@200b4_82 - jr: i changed getPathOf(path) to getPathOf(p) } catch (Exception e) {stream = null;} // search in the classpath if (stream == null) { sread += "#3 - classpath\n"; File []dirs = getClassPathDirectories(); File f = null; for (int i = 0; i < dirs.length; i++) try { f = new File(dirs[i],path); if (!f.isFile() && mainpath != null) f = new File(dirs[i],mainpath+path); // guich@tc100: search in the path of the main window if (f.isFile()) { String ff = getPathOf(f.getAbsolutePath()); htOpenedAt.put(path,ff); // guich@200b4_82 - jr: changed dirs[i].getAbsolutePath - guich@tc112_20: using f.getAbsolutePath instead of dirs[i].getAbsolutePath break; } else f = null; // guich@400_8: fixed problem when file was not found so the #3 can be tried below } catch (Exception e) {f = null;} if (f != null) stream = new FileInputStream(f); } if (stream == null && _class != null) // guich@400_6: now the resources can be read from the jar file { sread += "#4 - jar file\n"; try { InputStream is = (InputStream)_class.getResourceAsStream("/"+path); if (is != null) { stream = readJavaInputStream(is); } } catch (Throwable tt) {if (tt.getMessage() != null) System.out.println(tt.getMessage());} } if (stream == null && htAttachedFiles.size() > 0) // guich@tc100: load from attached libraries too { sread += "#5 - attached libraries\n"; totalcross.io.ByteArrayStream bas = (totalcross.io.ByteArrayStream)htAttachedFiles.get(path.toLowerCase()); if (bas != null) stream = new ByteArrayInputStream(bas.getBuffer()); // buffer is the same size of the loaded file. } } else if (stream == null) { URL url; // zero in the jar file (normal way) InputStream is=null; try { is = (InputStream)_class.getResourceAsStream("/"+path); } catch (Throwable tt) {if (tt.getMessage() != null) System.out.println(tt.getMessage());} sread += "#1 - resource: "+is+"\n"; // guich@200b4_59 if (is != null) stream = readJavaInputStream(is); // first in the jar file // guich@200b4: using this in Internet makes the archive be fetched from the server at each call of this function. if (stream == null) { String archive = getParameter("archive"); sread += "#2 - archive: "+archive+"\n"; if (isOk(archive) && !archive.equals("null")) { String[] archives = tokenizeString(archive,','); // guich@580_39: if there are more than one file, split them for (int i=0; i < archives.length; i++) { archive = archives[i]; if(archive.startsWith("null")) archive = archive.substring(4); URL codeBase = getCodeBase(); url = new URL(codeBase+"/"+archive); try { ZipInputStream zIn = new ZipInputStream(url.openStream()); java.util.zip.ZipEntry zEntry = zIn.getNextEntry(); while(!zEntry.getName().equals(path)) { zEntry = zIn.getNextEntry(); if (zEntry == null) throw new Exception("doh"); } // guich@200b2: ok. the zIn.available() returns 1 and not the real size of the zip entry. so, here we read all into a byte stream stream = readJavaInputStream(zIn); } catch (Exception e){if (!e.getMessage().equals("doh")) e.printStackTrace();/* doh didn't find it in the jar thing */} } } } // second under the codebase if (stream == null) try { URL codeBase = getCodeBase(); String cb = codeBase.toString(); char lastc = cb.charAt(cb.length() - 1); char firstc = path.charAt(0); if (lastc != '/' && firstc != '/') cb += "/"; sread += "#3 - url: "+cb+path+"\n"; url = new URL(cb + path); stream = url.openStream(); } catch (FileNotFoundException ee) {} catch (Exception e) {e.printStackTrace();/* neither in the codebase */} // third in the localhost if (stream == null) try { sread += "#4- url: file://localhost/"+dataPath+path+"\n"; url = new URL("file://localhost/"+dataPath + path); // guich@120 stream = url.openStream(); } catch (Exception e) {}; if (stream == null && htAttachedFiles.size() > 0) // guich@tc100: load from attached libraries too { sread += "#5 - attached libraries\n"; totalcross.io.ByteArrayStream bas = (totalcross.io.ByteArrayStream)htAttachedFiles.get(path.toLowerCase()); if (bas != null) stream = new ByteArrayInputStream(bas.getBuffer()); // buffer is the same size of the loaded file. } } if (stream == null) print(sread+"file not found\n"); } catch (FileNotFoundException ee) { print("file not found"); } catch (Exception e) // guich@120 { if (isOk(e.getMessage())) // guich@500_something: only show message if something is to be displayed print("error in JavaBridge.openInputStream: "+e.getMessage()); return null; } return stream; } private OutputStream openOutputUrl(URL url) { try { URLConnection con = url.openConnection(); con.setUseCaches(false); con.setDoOutput(true); con.setDoInput(false); return con.getOutputStream(); } catch (Exception u) // try another way { try { String path = url+""; return new FileOutputStream(isOk(totalcross.sys.Settings.dataPath)?(getDataPath()+path):path); } catch (Exception ee) {return null;} } } /** used in some classes so they can correctly open files. used internally by readBytes. */ public OutputStream openOutputStream(String path) { print("\nopening for write "+path); String dataPath = getDataPath(); OutputStream stream = null; String readPath = (String)htOpenedAt.get(path); // guich@tc112_20 try { try // guich@tc100: removed the nonGuiApp flag { String pp = isOk(dataPath) ? (dataPath+path) : isOk(readPath) ? totalcross.sys.Convert.appendPath(readPath,path) : path; // guich@tc112_20: use readPath if not null stream = new FileOutputStream(pp); // guich@421_11: added support for dataPath } catch (Exception e) {stream = null;} if (stream == null && isApplication) { // search in the place where it was read - guich@200b4_82 if (readPath != null) // guich@400_58 - guich@421_10: changed to != instead of == try { print("#1 - read path"); stream = new FileOutputStream(new java.io.File(readPath,path)); print("found in "+readPath); } catch (Exception e) {stream = null;} if (stream != null) return stream; // search in the Settings.dataPath try { String p = isOk(dataPath)?(dataPath+path):path; print("#2 - Settings.dataPath"); stream = new FileOutputStream(p); print("found in "+p); } catch (Exception e) {stream = null;} // search in the classpath if (stream == null) { print("#3 - classpath"); File []dirs = getClassPathDirectories(); File f = null; for (int i = 0; i < dirs.length; i++) try { f = new File(dirs[i],path); if (f.isFile()) { print("found in "+dirs[i]); break; } } catch (Exception e) {f = null;} if (f == null) print("could not find file in the classpath"); else stream = new FileOutputStream(f); } } else if (stream == null) { URL url; // first under the codebase if (stream == null) try { URL codeBase = getCodeBase(); print("#1- codeBase: "+codeBase); String cb = codeBase.toString(); char lastc = cb.charAt(cb.length() - 1); char firstc = path.charAt(0); if (lastc != '/' && firstc != '/') cb += "/"; url = new URL(cb + path); stream = openOutputUrl(url); print("found under codebase: "+url); } catch (Exception e) {e.printStackTrace();/* neither in the codebase */} // third in the localhost if (stream == null) try { print("#2- url: file://localhost/" + dataPath + path); url = new URL("file://localhost/" + dataPath + path); // guich@120 stream = openOutputUrl(url); print("found under localhost: "+url); } catch (Exception e) {}; } if (stream == null) print("file not found"); } catch (FileNotFoundException ee) {print("file not found");} catch (Exception e) {/*if (!msgShowed) */print("error in Vm.openOutputStream: "+e.getMessage()); return null;} // guich@200 return stream; } /** read the available bytes from the stream getted with openInputStream. * called by totalcross.ui.image.Image and totalcross.io.PDBFile */ public byte [] readBytes(String path) { byte [] bytes = null; try { InputStream is = openInputStream(path); if (is != null) { int n = is.available(); bytes = new byte[n]; is.read(bytes); is.close(); } } catch (Exception e) {e.printStackTrace();} return bytes; } /** write the available bytes to the stream getted with openOutputStream. * called by totalcross.io.PDBFile */ public boolean writeBytes(String path, byte []buf, int len) { boolean ret = true; try { OutputStream os = openOutputStream(path); if (os != null) { if (buf != null) { os.write(buf,0,len); os.close(); // pietj@330_1 } else print("ATT: you sent to stream.writeBytes a null buffer!"); } } catch (Exception e) {e.printStackTrace(); ret = false;} return ret; } /** return true is the string is valid. called by openInputStream and openOutputStream in this class. */ private boolean isOk(String s) { return s != null && s.length() > 0; } String getDefaultCrid(String name) { if (name == null) return null; if (name.indexOf('.') != -1) name = name.substring(name.lastIndexOf('.')+1); int i; int n = name.length(); int hash = 0; byte[] creat=new byte[4]; for (i = 0; i < n; i++) hash += (byte)name.charAt(i); for (i = 0; i < 4; i++) { creat[i] = (byte)((hash % 26) + 'a'); if ((hash & 64)>0) creat[i] += ('A'-'a'); hash = hash / 2; } return new String(creat); } void storeSettings() { try { String crid = crid4settings;//totalcross.sys.Settings.applicationId; // first verify if the PDBFile is created but the String is null totalcross.sys.Settings.showDesktopMessages = false; // guich@340_49 boolean saveSettings = totalcross.sys.Settings.appSettings != null || totalcross.sys.Settings.appSecretKey != null || totalcross.sys.Settings.appSettingsBin != null; // guich@570_9: also check if appSecretKey is null totalcross.io.PDBFile cat; if (!saveSettings) { try { cat = new totalcross.io.PDBFile("Settings4"+crid+".TCVM."+crid,totalcross.io.PDBFile.READ_WRITE); // guich@241_17: changed READ_ONLY to READ_WRITE to fix "operation invalid" error cat.delete(); } catch(totalcross.io.FileNotFoundException e) { } } else { cat = new totalcross.io.PDBFile("Settings4"+crid+".TCVM."+crid,totalcross.io.PDBFile.CREATE); totalcross.io.ResizeRecord rs = new totalcross.io.ResizeRecord(cat,256); totalcross.io.DataStream ds = new totalcross.io.DataStream(rs); try { cat.setRecordPos(1); cat.deleteRecord(); } catch (totalcross.io.IOException e) {} try { cat.setRecordPos(0); cat.deleteRecord(); } catch (totalcross.io.IOException e) {} rs.startRecord(); // store the appSettings record ds.writeString(totalcross.sys.Settings.appSettings); ds.writeString(totalcross.sys.Settings.appSecretKey); rs.endRecord(); // guich@573_16: store the bin in another record if (totalcross.sys.Settings.appSettingsBin != null) { int len = totalcross.sys.Settings.appSettingsBin.length; cat.addRecord(len); cat.writeBytes(totalcross.sys.Settings.appSettingsBin,0,len); } cat.close(); } totalcross.sys.Settings.showDesktopMessages = true; } catch (Throwable t) {System.out.println("Settings can't be stored: "+t.toString());} } private void getAppSettings() { String crid = crid4settings = totalcross.sys.Settings.applicationId; totalcross.sys.Settings.showDesktopMessages = false; // guich@340_49 try { totalcross.io.PDBFile cat = new totalcross.io.PDBFile("Settings4"+crid+".TCVM."+crid,totalcross.io.PDBFile.READ_WRITE); totalcross.io.DataStream ds = new totalcross.io.DataStream(cat); cat.setRecordPos(0); String s; s = ds.readString(); if (!"".equals(s)) totalcross.sys.Settings.appSettings = s; try { s = ds.readString(); if (!"".equals(s)) totalcross.sys.Settings.appSecretKey = s; } catch (Throwable t) {System.out.println("Reading an old settings file; no appSecretKey available.");} if (cat.getRecordCount() > 1) // guich@573_16 { cat.setRecordPos(1); byte[] buf = new byte[cat.getRecordSize()]; cat.readBytes(buf,0,buf.length); totalcross.sys.Settings.appSettingsBin = buf; } cat.close(); } catch (Throwable t) {} totalcross.sys.Settings.showDesktopMessages = true; // guich@340_49 } private char getFirstSymbol(String s) { char []c = s.toCharArray(); for (int i =0; i < c.length; i++) if (c[i] != ' ' && !('0' <= c[i] && c[i] <= '9')) return c[i]; return ' '; } /** called by totalcross.Launcher.init() */ public void fillSettings() { if (settingsFilled) return; settingsFilled = true; java.util.Calendar cal = java.util.Calendar.getInstance(); // guich@340_34: since java can't provide us good methods to return these values, we use parse the return of some formatting methods cal.set(2002,11,25,20,0,0); // guich@401_32 java.text.DateFormat df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT); // guich@401_32: fixed wrong results in some systems String d = df.format(cal.getTime()); totalcross.sys.Settings.dateFormat = d.startsWith("25") ? totalcross.sys.Settings.DATE_DMY : d.startsWith("12") ? totalcross.sys.Settings.DATE_MDY : totalcross.sys.Settings.DATE_YMD; totalcross.sys.Settings.dateSeparator = getFirstSymbol(d); df = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT); // guich@401_32 d = df.format(cal.getTime()); totalcross.sys.Settings.is24Hour = d.toLowerCase().indexOf("am") == -1 && d.toLowerCase().indexOf("pm") == -1; totalcross.sys.Settings.timeSeparator = getFirstSymbol(d); // totalcross.sys.Settings.weekStart = (byte) (cal.getFirstDayOfWeek() - 1); settingsRefresh(false); java.text.DecimalFormatSymbols dfs = new java.text.DecimalFormatSymbols(); totalcross.sys.Settings.thousandsSeparator = dfs.getGroupingSeparator(); totalcross.sys.Settings.decimalSeparator = dfs.getDecimalSeparator(); totalcross.sys.Settings.screenBPP = toBpp; try { totalcross.sys.Settings.screenWidthInDPI = totalcross.sys.Settings.screenHeightInDPI = Toolkit.getDefaultToolkit().getScreenResolution(); } catch (Throwable t) { totalcross.sys.Settings.screenWidthInDPI = 96; } totalcross.sys.Settings.romVersion = 0x02000000; totalcross.sys.Settings.uiStyle = totalcross.sys.Settings.Vista; totalcross.sys.Settings.screenWidth = toWidth; totalcross.sys.Settings.screenHeight = toHeight; totalcross.sys.Settings.onJavaSE = true; totalcross.sys.Settings.platform = Settings.JAVA; totalcross.sys.Settings.applicationId = getDefaultCrid(className); // dhaysmith@420_4 totalcross.sys.Settings.deviceId = "Desktop"; // guich@568_2 if (totalcross.sys.Settings.applicationId != null) getAppSettings(); // guich@330_47 try { // Fill all paths String basePath = System.getProperty("user.dir"); totalcross.sys.Settings.vmPath = basePath; totalcross.sys.Settings.appPath = basePath; // guich@tc112_21: commented - if (totalcross.sys.Settings.dataPath == null) totalcross.sys.Settings.dataPath = basePath; // flsobral@tc100b5_51: Settings.dataPath was being overwritten if set before the Launcher was initialized. if (totalcross.sys.Settings.appPath != null) // guich@582_17: make sure that it ends with a slash { if (totalcross.sys.Settings.appPath.indexOf('/') >= 0 && !totalcross.sys.Settings.appPath.endsWith("/")) totalcross.sys.Settings.appPath += "/"; else if (totalcross.sys.Settings.appPath.indexOf('\\') >= 0 && !totalcross.sys.Settings.appPath.endsWith("\\")) totalcross.sys.Settings.appPath += "\\"; } totalcross.sys.Settings.userName = !isApplication?null:java.lang.System.getProperty("user.name"); } catch (SecurityException se) {totalcross.sys.Settings.userName = null;} } @SuppressWarnings("deprecation") public void settingsRefresh(boolean callStoreSettings) // guich@tc115_81 { java.util.TimeZone tz = java.util.TimeZone.getDefault(); // guich@340_33 Settings.daylightSavingsMinutes = tz.getDSTSavings() / 60000; Settings.daylightSavings = Settings.daylightSavingsMinutes != 0; Settings.timeZone = tz.getRawOffset() / (60*60000); Settings.timeZoneMinutes = tz.getRawOffset() / 60000; Settings.timeZoneStr = java.util.TimeZone.getDefault().getID(); if (callStoreSettings) try { storeSettings(); } catch (Exception e) {} } //// font and font metrics ////////////////////////////////////////////////////// static final int AA_NO = 0; static final int AA_4BPP = 1; static final int AA_8BPP = 2; static int []realSizes = {7,8,9,10,11,12,13,14,15,16,17,18,19,20,40,60,80}; private totalcross.util.Hashtable htLoadedFonts = new totalcross.util.Hashtable(31); static Hashtable htBaseFonts = new Hashtable(5); // static totalcross.ui.font.Font getBaseFont(String name, boolean bold, int size, String suffix) { String key = name+"|"+bold+"|"+size+"|"+suffix; totalcross.ui.font.Font f = (totalcross.ui.font.Font)htBaseFonts.get(key); if (f == null) { int i; for (i = 0; i < realSizes.length-1; i++) if (size <= realSizes[i]) break; int idx = Integer.parseInt(suffix.substring(suffix.indexOf('u') + 1)); totalcross.ui.font.Font.baseChar = (char)idx; f = totalcross.ui.font.Font.getFont(name,bold,realSizes[i]); totalcross.ui.font.Font.baseChar = ' '; if (f != null) { f.removeFromCache(); htBaseFonts.put(key,f); } } return f; } private UserFont loadUF(String fontName, String suffix) { try { if (totalcross.ui.font.Font.baseChar == ' ') // test if there's another 8bpp native font. { boolean bold = suffix.charAt(1) == 'b'; int size = Integer.parseInt(suffix.substring(2,suffix.indexOf('u'))); totalcross.ui.font.Font base = getBaseFont(fontName, bold, size, suffix); if (base != null) return new UserFont(fontName, suffix, size, base); } return new UserFont(fontName, suffix); } catch (Exception e) { String msg = ""+e.getMessage(); if (!msg.startsWith("name") || !msg.endsWith("not found")) if (Settings.onJavaSE) e.printStackTrace(); } return null; } public UserFont getFont(totalcross.ui.font.Font f, char c) { UserFont uf=null; try { // verify if its in the cache. String fontName = f.name; int size = Math.max(f.size,totalcross.ui.font.Font.MIN_FONT_SIZE); // guich@tc122_15: don't check for the maximum font size here char faceType = c < 0x3000 && f.style == 1 ? 'b' : 'p'; int uIndex = ((int)c >> 8) << 8; String suffix = "$"+faceType+size+"u"+uIndex; String key = fontName+suffix; uf = (UserFont)htLoadedFonts.get(key); if (uf != null) return uf; if (fontName.charAt(0) == '$') // bruno@tc114_37: native fonts always start with '$' print("Native fonts are not supported on Desktop"); else { // first, try to load the font itself using the current font pattern uf = loadUF(fontName, suffix); if (uf == null) // try now as a plain font uf = loadUF(fontName, "$p"+size+"u"+uIndex); // guich@tc122_15: ... check only here if (uf == null && f.size != totalcross.ui.font.Font.NORMAL_SIZE) { int t = f.size; while (uf == null && --t >= 5) // try to find the nearest size uf = loadUF(fontName, "$p"+t+"u"+uIndex); } if (uf == null) // try now as the default size original face uf = loadUF(fontName, "$"+faceType+totalcross.ui.font.Font.NORMAL_SIZE+"u"+uIndex); if (uf == null && faceType != 'p') // try now as the default size plain font uf = loadUF(fontName, "$p"+totalcross.ui.font.Font.NORMAL_SIZE+"u"+uIndex); } // at last, use the default font if (uf == null) uf = loadUF(totalcross.ui.font.Font.DEFAULT, suffix); if (uf == null && fontName.charAt(0) != '$') // check if there's a font of any size - maybe the file has only one font? for (int i = totalcross.ui.font.Font.MIN_FONT_SIZE; i <= totalcross.ui.font.Font.MAX_FONT_SIZE; i++) if ((uf = loadUF(fontName,"$p"+i+"u"+uIndex)) != null) break; if (uf == null) // check if there's a font of any size - at least with the default font for (int i = totalcross.ui.font.Font.MIN_FONT_SIZE; i <= totalcross.ui.font.Font.MAX_FONT_SIZE; i++) if ((uf = loadUF(totalcross.ui.font.Font.DEFAULT,"$p"+i+"u"+uIndex)) != null) break; if (uf != null) { if (totalcross.ui.font.Font.baseChar == ' ') htLoadedFonts.put(key,uf); // note that we will use the original key to avoid entering all exception handlers. f.name = uf.fontName; // update the name, the font may have been replaced. } else if (htLoadedFonts.size() > 0) return c == ' ' ? null : getFont(f, ' '); // probably the index was outside the available ranges at this font - guich@tc110_28: if space, just return null else if (appletInitialized) // guich@500_1: when retroguard is loaded, Applet.init is never called, so we just skip here. { System.err.println("No fonts found! be sure to place the file "+totalcross.ui.font.Font.DEFAULT+".tcz in the same directory from where you're running your application"+(isApplication ? " or put a reference to TotalCross3/etc folder in the classpath!" : "or in your applet's codebase or in a jar file!")); System.exit(2); } } catch (Exception e) {System.out.println(""+e);} return uf; } /** Represents the internal font structure, read from a pdb file. used internally. */ // created by guich@200b2 public static class CharBits // pgr@402_50 - describe the bitmap for a given character { public int rowWIB; // width in bytes public byte[] charBitmapTable; public int offset; // offset relative to the bitmap table public int width; public int index; public totalcross.ui.image.Image img; } private static Hashtable loadedTCZs = new Hashtable(31); public class UserFont { // 25/120 14/70 4/25 2/15 public UserFont ubase; public totalcross.ui.image.Image[] nativeFonts; // stores the system font in some platforms public int antialiased; // AA_ flags public int firstChar; // ASCII code of first character public int lastChar; // ASCII code of last character public int spaceWidth; // width of the space char public int maxWidth; // width of font rectangle - unused public int maxHeight; // height of font rectangle public int owTLoc; // offset to offset/width table - unused public int ascent; // ascent public int descent; // descent public int rowWords; // row width of bit image / 2 - used only to compute rowWidthInBytes private int rowWidthInBytes; private byte []bitmapTable; private int []bitIndexTable; private String fontName; private int numberWidth; private int minusW; private UserFont(String fontName, String sufix, int size, totalcross.ui.font.Font base) throws Exception { UserFont ubase = (UserFont)base.hv_UserFont; this.ubase = ubase; this.maxHeight = size; this.rowWidthInBytes = ubase.rowWidthInBytes * maxHeight / ubase.maxHeight; this.bitIndexTable = new int[ubase.bitIndexTable.length]; for (int i = 0; i < bitIndexTable.length; i++) // compute the target size using the rule of three this.bitIndexTable[i] = ubase.bitIndexTable[i] * maxHeight / ubase.maxHeight; this.nativeFonts = new totalcross.ui.image.Image[bitIndexTable.length]; this.fontName = fontName; this.firstChar = ubase.firstChar; this.lastChar = ubase.lastChar; this.antialiased = ubase.antialiased; this.descent = ubase.descent * maxHeight / ubase.maxHeight; this.ascent = size - this.descent; this.numberWidth = ubase.numberWidth * maxHeight / ubase.maxHeight; this.spaceWidth = ubase.spaceWidth * maxHeight / ubase.maxHeight; this.minusW = ubase.minusW; } private UserFont(String fontName, String sufix) throws Exception { this.fontName = fontName; String fileName = fontName+".tcz"; TCZ z = (TCZ)loadedTCZs.get(fileName.toLowerCase()); if (z == null) { InputStream is = openInputStream(fileName); if (is == null) { is = openInputStream("vm/"+fileName); // for the release sdk, there's no etc/fonts. the tcfont.tcz is located at dist/vm/tcfont.tcz if (is == null) { is = openInputStream("etc/fonts/"+fileName); // if looking for the default font when debugging, use etc/fonts if (is == null) throw new Exception("file "+fileName+" not found"); // loaded = false } } z = new TCZ(new IS2S(is)); totalcross.io.ByteArrayStream fontChunks[]; fontChunks = new totalcross.io.ByteArrayStream[z.numberOfChunks]; for (int i =0; i < fontChunks.length; i++) { int s = z.getNextChunkSize(); fontChunks[i] = new totalcross.io.ByteArrayStream(s); z.readNextChunk(fontChunks[i]); } z.bag = fontChunks; loadedTCZs.put(fileName.toLowerCase(), z); } fontName += sufix; int index = z.findNamePosition(fontName.toLowerCase()); if (index == -1) throw new Exception("name "+fontName+" not found"); // loaded = false totalcross.io.ByteArrayStream bas = ((totalcross.io.ByteArrayStream[])z.bag)[index]; bas.reset(); totalcross.io.DataStreamLE ds = new totalcross.io.DataStreamLE(bas); antialiased = ds.readUnsignedShort(); firstChar = ds.readUnsignedShort(); lastChar = ds.readUnsignedShort(); spaceWidth = ds.readUnsignedShort(); maxWidth = ds.readUnsignedShort(); maxHeight = ds.readUnsignedShort(); owTLoc = ds.readUnsignedShort(); ascent = ds.readUnsignedShort(); descent = ds.readUnsignedShort(); rowWords = ds.readUnsignedShort(); rowWidthInBytes = 2 * rowWords * (antialiased == AA_NO ? 1 : antialiased == AA_4BPP ? 4 : 8); int bitmapTableSize = (int)rowWidthInBytes * (int)maxHeight; bitmapTable = new byte[bitmapTableSize]; ds.readBytes(bitmapTable); bitIndexTable = new int[lastChar - firstChar + 1 + 1]; for (int i=0; i < bitIndexTable.length; i++) bitIndexTable[i] = ds.readUnsignedShort(); // minusW = antialiased == AA_8BPP && fontName.equals("TCFont") ? 1 : 0; if (firstChar <= '0' && '0' <= lastChar) { index = (int)'0' - (int)firstChar; numberWidth = bitIndexTable[index+1] - bitIndexTable[index] - minusW; } if (antialiased == AA_8BPP) nativeFonts = new totalcross.ui.image.Image[bitIndexTable.length]; } private totalcross.ui.image.Image getBaseCharImage(int index) throws totalcross.ui.image.ImageException // called only in ubase instances { int offset = bitIndexTable[index]; int width = bitIndexTable[index+1] - offset - minusW; totalcross.ui.image.Image img = new totalcross.ui.image.Image(width,maxHeight); int[] pixels = img.getPixels(); for (int y = 0,idx=0; y < maxHeight; y++) for (int x = 0; x < width; x++,idx++) pixels[idx] = bitmapTable[y * rowWidthInBytes + x + offset] << 24; return img; } // Get the source x coordinate and width of the character public void setCharBits(char ch, CharBits bits) { if (firstChar <= ch && ch <= lastChar) { int index = (int)ch - (int)firstChar; bits.index = index; bits.rowWIB = rowWidthInBytes; bits.charBitmapTable = bitmapTable; bits.offset = bitIndexTable[index]; bits.width = bitIndexTable[index+1] - bits.offset - minusW; if (bits.width == 0) bits.width += minusW; if (ubase != null) try { if (ubase.nativeFonts[index] == null) // character at original size ubase.nativeFonts[index] = ubase.getBaseCharImage(index); if (nativeFonts[index] == null) // character at the target size nativeFonts[index] = ubase.nativeFonts[index].getHwScaledInstance(bits.width,maxHeight); bits.img = nativeFonts[index]; bits.rowWIB = bits.width; } catch (Exception e) {e.printStackTrace();} } else { bits.width = spaceWidth; bits.offset = -1; } } } public int getCharWidth(totalcross.ui.font.Font f, char ch) // guich@tc122_16: moved to outside UserFont, because each char may be in a different UserFont { UserFont font = (UserFont)f.hv_UserFont; if (ch < font.firstChar || ch > font.lastChar) f.hv_UserFont = font = Launcher.instance.getFont(f, ch); if (ch == 160) return font.numberWidth; if (ch < ' ') return (ch == '\t') ? font.spaceWidth * totalcross.ui.font.Font.TAB_SIZE : 0; // guich@tc100: handle tabs int index = (int)ch - (int)font.firstChar; return (font.firstChar <= ch && ch <= font.lastChar) ? font.bitIndexTable[index+1] - font.bitIndexTable[index] - font.minusW : font.spaceWidth; } private class AlertBox extends Frame implements java.awt.event.ActionListener { private java.awt.Button ok; private java.awt.TextArea ta; public AlertBox() { super("Alert"); setLayout(new BorderLayout()); add("Center",ta = new java.awt.TextArea()); Panel p = new Panel(); p.setLayout(new FlowLayout()); p.add(ok = new java.awt.Button("Ok")); ok.addActionListener(this); add("South",p); pack(); Dimension d = getToolkit().getScreenSize(); setLocation(d.width/3,d.height/3); } public void actionPerformed(java.awt.event.ActionEvent ae) { if (ae.getSource() == ok) setVisible(false); } public void setText(String s) { ta.setText(s); } } public void alert(String msg) { if (!started) System.out.println(msg); else { alert.setText(msg); alert.setVisible(true); while (alert.isVisible()) try {Thread.sleep(10);} catch (Exception e) {} } } /** Converts a java.io.InputStream into a totalcross.io.Stream */ public static class IS2S extends totalcross.io.Stream { InputStream is; public IS2S(InputStream is) { this.is = is; } public void close() { try {is.close();} catch (Exception e) {} is = null; } public int readBytes(byte[] buf, int start, int count) { try { return is.read(buf, start, count); } catch (Exception e) {return -1;} } public int writeBytes(byte[] buf, int start, int count) { return 0; // not supported } } public static class S2IS extends java.io.InputStream { private Stream s; private byte[] oneByte = new byte[1]; private int left; private boolean closeUnderlying; public S2IS(Stream s) { this(s, -1, true); } public S2IS(Stream s, int max) { this(s, max, true); } public S2IS(Stream s, int max, boolean closeUnderlying) { this.s = s; this.left = max; this.closeUnderlying = closeUnderlying; } public int read() throws java.io.IOException { if (left == 0) return -1; try { int r = s.readBytes(oneByte, 0, 1); if (left != -1 && r == 1) left--; return r > 0 ? ((int)oneByte[0] & 0xFF) : -1; } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } public int read(byte[] buf, int off, int len) throws java.io.IOException { if (left == 0) return -1; try { if (left != -1 && len > left) len = left; int r = s.readBytes(buf, off, len); if (left != -1 && r > 0) left -= r; return r; } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } public void close() throws java.io.IOException { if (closeUnderlying) { try { s.close(); } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } } } public static class S2OS extends java.io.OutputStream { private Stream s; private byte[] oneByte = new byte[1]; private int count; private boolean closeUnderlying; public S2OS(Stream s) { this(s, true); } public S2OS(Stream s, boolean closeUnderlying) { this.s = s; this.closeUnderlying = closeUnderlying; } public int count() { return count; } public void write(int b) throws java.io.IOException { try { oneByte[0] = (byte)(b & 0xFF); int c = s.writeBytes(oneByte, 0, 1); if (c < 0) throw new java.io.IOException("Unknown error when writing to stream"); count++; } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } public void write(byte[] b, int off, int len) throws java.io.IOException { try { int c = s.writeBytes(b, off, len); if (c < 0) throw new java.io.IOException("Unknown error when writing to stream"); count += c; } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } public void close() throws java.io.IOException { if (closeUnderlying) { try { s.close(); } catch (IOException e) { throw new java.io.IOException(e.getMessage()); } } } } public void setTitle(String title) { if (isApplication) { frameTitle = title; if (frame != null) frame.setTitle(title); } } public void vibrate(final int millis) { if (isApplication && frame != null) { new Thread() { public void run() { Point p = frame.getLocation(); int x = p.x, y = p.y; int[] xPoints = { x - 3, x, x + 3, x, x + 3, x, x - 3, x}; int[] yPoints = { y - 3, y, y + 3, y, y - 3, y, y + 3, y}; int i = 0; int j = 0; int t = Vm.getTimeStamp(); do { frame.setLocation(xPoints[i], yPoints[j]); i = ++i % xPoints.length; if (i == 0) j = ++j % yPoints.length; Thread.yield();// give some time for the other threads to execute } while (Vm.getTimeStamp() - t < millis); frame.setLocation(x, y); // restore original location } }.start(); } } public void setSIP(int option, Control edit, boolean secret) { } public static void checkLitebaseAllowed() { } public void componentHidden(ComponentEvent arg0) { } public void componentMoved(ComponentEvent arg0) { } public void componentShown(ComponentEvent arg0) { } boolean ignoreNextResize; // guich@tc168: ignore when using F9 public void componentResized(ComponentEvent ev) { if (ignoreNextResize) { ignoreNextResize = false; return; } int w = frame.getWidth()-frame.insets.left-frame.insets.right; int h = frame.getHeight()-frame.insets.top-frame.insets.bottom; w /= toScale; // guich@tc168: consider scale h /= toScale; if (w < toWidth || h < toHeight) screenResized(w >= toWidth ? w : toWidth,h >= toHeight ? h : toHeight,true); else screenResized(w,h,false); } }