1 /*
   2  * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.glass.ui;
  26 
  27 import com.sun.glass.events.KeyEvent;
  28 import com.sun.glass.ui.CommonDialogs.ExtensionFilter;
  29 import com.sun.glass.ui.CommonDialogs.FileChooserResult;
  30 
  31 import java.io.File;
  32 import java.nio.ByteBuffer;
  33 import java.nio.IntBuffer;
  34 import java.security.AccessController;
  35 import java.security.PrivilegedAction;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.LinkedList;
  39 
  40 public abstract class Application {
  41 
  42     private final static String DEFAULT_NAME = "java";
  43     protected String name = DEFAULT_NAME;
  44 
  45     public static class EventHandler {
  46         // currently used only on Mac OS X
  47         public void handleWillFinishLaunchingAction(Application app, long time) {
  48         }
  49         // currently used only on Mac OS X
  50         public void handleDidFinishLaunchingAction(Application app, long time) {
  51         }
  52         // currently used only on Mac OS X
  53         public void handleWillBecomeActiveAction(Application app, long time) {
  54         }
  55         // currently used only on Mac OS X
  56         public void handleDidBecomeActiveAction(Application app, long time) {
  57         }
  58         // currently used only on Mac OS X
  59         public void handleWillResignActiveAction(Application app, long time) {
  60         }
  61         // currently used only on Mac OS X
  62         public void handleDidResignActiveAction(Application app, long time) {
  63         }
  64         // currently used only on iOS
  65         public void handleDidReceiveMemoryWarning(Application app, long time) {
  66         }
  67         // currently used only on Mac OS X
  68         public void handleWillHideAction(Application app, long time) {
  69         }
  70         // currently used only on Mac OS X
  71         public void handleDidHideAction(Application app, long time) {
  72         }
  73         // currently used only on Mac OS X
  74         public void handleWillUnhideAction(Application app, long time) {
  75         }
  76         // currently used only on Mac OS X
  77         public void handleDidUnhideAction(Application app, long time) {
  78         }
  79         // currently used only on Mac OS X
  80         // the open files which started up the app will arrive before app becomes active
  81         public void handleOpenFilesAction(Application app, long time, String files[]) {
  82         }
  83         // currently used only on Mac OS X
  84         public void handleQuitAction(Application app, long time) {
  85         }
  86         public boolean handleThemeChanged(String themeName) {
  87             return false;
  88         }
  89     }
  90 
  91     private EventHandler eventHandler;
  92     private boolean initialActiveEventReceived = false;
  93     private String initialOpenedFiles[] = null;
  94 
  95     private static boolean loaded = false;
  96     private static Application application;
  97     private static Thread eventThread;
  98     private static final boolean disableThreadChecks =
  99         AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 100             final String str =
 101                     System.getProperty("glass.disableThreadChecks", "false");
 102             return "true".equalsIgnoreCase(str);
 103         });
 104 
 105     // May be called on any thread.
 106     protected static synchronized void loadNativeLibrary(final String libname) {
 107         // load the native library of the specified libname.
 108         // the platform default by convention is "glass", all others should have a suffix, ie glass-x11
 109         if (!loaded) {
 110             com.sun.glass.utils.NativeLibLoader.loadLibrary(libname);
 111             loaded = true;
 112         }
 113     }
 114 
 115     // May be called on any thread.
 116     protected static synchronized void loadNativeLibrary() {
 117         // use the "platform default" name of "glass"
 118         loadNativeLibrary("glass");
 119     }
 120 
 121     private static volatile Map deviceDetails = null;
 122 
 123     // provides a means for the user to pass platorm specific details
 124     // to the native glass impl. Can be null.
 125     // May need be called before Run.
 126     // May be called on any thread.
 127     public static void setDeviceDetails(Map details) {
 128         deviceDetails = details;
 129     }
 130 
 131     // May be called on any thread.
 132     public static Map getDeviceDetails() {
 133         return deviceDetails;
 134     }
 135 
 136     protected Application() {
 137     }
 138 
 139     // May be called on any thread.
 140     public static void run(final Runnable launchable) {
 141         if (application != null) {
 142             throw new IllegalStateException("Application is already running");
 143         }
 144         application = PlatformFactory.getPlatformFactory().createApplication();
 145         // each concrete Application should set the app name using its own platform mechanism:
 146         // on Mac OS X - use NSBundle info, which can be overriden by -Xdock:name
 147         // on Windows - TODO
 148         // on Linux - TODO
 149         //application.name = DEFAULT_NAME; // default
 150         try {
 151             application.runLoop(() -> {
 152                 Screen.initScreens();
 153                 launchable.run();
 154             });
 155         } catch (Throwable t) {
 156             t.printStackTrace();
 157         }
 158     }
 159 
 160     // runLoop never exits until app terminates
 161     protected abstract void runLoop(Runnable launchable);
 162 
 163     // should return after loop termination completion
 164     protected void finishTerminating() {
 165         // To make sure application object is not used outside of the run loop
 166         application = null;
 167         // The eventThread is null at this point, no need to check it
 168     }
 169 
 170     /**
 171      * Gets the name for the application.  The application name may
 172      * be used to identify the application in the user interface or
 173      * as part of the platform specific path used to store application
 174      * data.
 175      *
 176      * This is a hint and may not be used on some platforms.
 177      *
 178      * @return the application name
 179      */
 180     public String getName() {
 181         checkEventThread();
 182         return name;
 183     }
 184 
 185     /**
 186      * Sets the name for the application.  The application name may
 187      * be used to identify the application in the user interface or
 188      * as part of the platform specific path used to store application
 189      * data.
 190      *
 191      * The name could be set only once. All subsequent calls are ignored.
 192      *
 193      * This is a hint and may not be used on some platforms.
 194      *
 195      * @param name the new application name
 196      */
 197     public void setName(String name) {
 198         checkEventThread();
 199         if (name != null && DEFAULT_NAME.equals(this.name)) {
 200             this.name = name;
 201         }
 202     }
 203 
 204     /**
 205      * Gets a platform specific path that can be used to store
 206      * application data.  The application name typically appears
 207      * as part of the path.
 208      *
 209      * On some platforms, the path may not yet exist and the caller
 210      * will need to create it.
 211      *
 212      * @return the platform specific path for the application data
 213      */
 214     public String getDataDirectory() {
 215         checkEventThread();
 216         String userHome = AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("user.home"));
 217         return userHome + File.separator + "." + name + File.separator;
 218     }
 219 
 220     private void notifyWillFinishLaunching() {
 221         EventHandler handler = getEventHandler();
 222         if (handler != null) {
 223             handler.handleWillFinishLaunchingAction(this, System.nanoTime());
 224         }
 225     }
 226 
 227     private void notifyDidFinishLaunching() {
 228         EventHandler handler = getEventHandler();
 229         if (handler != null) {
 230             handler.handleDidFinishLaunchingAction(this, System.nanoTime());
 231         }
 232     }
 233 
 234     private void notifyWillBecomeActive() {
 235         EventHandler handler = getEventHandler();
 236         if (handler != null) {
 237             handler.handleWillBecomeActiveAction(this, System.nanoTime());
 238         }
 239     }
 240 
 241     private void notifyDidBecomeActive() {
 242         this.initialActiveEventReceived = true;
 243         EventHandler handler = getEventHandler();
 244         if (handler != null) {
 245             handler.handleDidBecomeActiveAction(this, System.nanoTime());
 246         }
 247     }
 248 
 249     private void notifyWillResignActive() {
 250         EventHandler handler = getEventHandler();
 251         if (handler != null) {
 252             handler.handleWillResignActiveAction(this, System.nanoTime());
 253         }
 254     }
 255 
 256     private boolean notifyThemeChanged(String themeName) {
 257         EventHandler handler = getEventHandler();
 258         if (handler != null) {
 259             return handler.handleThemeChanged(themeName);
 260         }
 261         return false;
 262     }
 263 
 264     private void notifyDidResignActive() {
 265         EventHandler handler = getEventHandler();
 266         if (handler != null) {
 267             handler.handleDidResignActiveAction(this, System.nanoTime());
 268         }
 269     }
 270 
 271     private void notifyDidReceiveMemoryWarning() {
 272         EventHandler handler = getEventHandler();
 273         if (handler != null) {
 274             handler.handleDidReceiveMemoryWarning(this, System.nanoTime());
 275         }
 276     }
 277 
 278     private void notifyWillHide() {
 279         EventHandler handler = getEventHandler();
 280         if (handler != null) {
 281             handler.handleWillHideAction(this, System.nanoTime());
 282         }
 283     }
 284 
 285     private void notifyDidHide() {
 286         EventHandler handler = getEventHandler();
 287         if (handler != null) {
 288             handler.handleDidHideAction(this, System.nanoTime());
 289         }
 290     }
 291 
 292     private void notifyWillUnhide() {
 293         EventHandler handler = getEventHandler();
 294         if (handler != null) {
 295             handler.handleWillUnhideAction(this, System.nanoTime());
 296         }
 297     }
 298 
 299     private void notifyDidUnhide() {
 300         EventHandler handler = getEventHandler();
 301         if (handler != null) {
 302             handler.handleDidUnhideAction(this, System.nanoTime());
 303         }
 304     }
 305 
 306     // notificiation when user drag and drops files onto app icon
 307     private void notifyOpenFiles(String files[]) {
 308         if ((this.initialActiveEventReceived == false) && (this.initialOpenedFiles == null)) {
 309             // rememeber the initial opened files
 310             this.initialOpenedFiles = files;
 311         }
 312         EventHandler handler = getEventHandler();
 313         if ((handler != null) && (files != null)) {
 314             handler.handleOpenFilesAction(this, System.nanoTime(), files);
 315         }
 316     }
 317 
 318     private void notifyWillQuit() {
 319         EventHandler handler = getEventHandler();
 320         if (handler != null) {
 321             handler.handleQuitAction(this, System.nanoTime());
 322         }
 323     }
 324 
 325     /**
 326      * Install app's default native menus:
 327      * on Mac OS X - Apple menu (showing the app name) with a single Quit menu item
 328      * on Windows - NOP
 329      * on Linux - NOP
 330      */
 331     public void installDefaultMenus(MenuBar menubar) {
 332         checkEventThread();
 333         // To override in subclasses
 334     }
 335 
 336     public EventHandler getEventHandler() {
 337         //checkEventThread(); // Glass (Mac)
 338         // When an app is closing, Mac calls notify- Will/DidHide, Will/DidResignActive
 339         // on a thread other than the Main thread
 340         return eventHandler;
 341     }
 342 
 343     public void setEventHandler(EventHandler eventHandler) {
 344         checkEventThread();
 345         boolean resendOpenFiles = ((this.eventHandler != null) && (this.initialOpenedFiles != null));
 346         this.eventHandler = eventHandler;
 347         if (resendOpenFiles == true) {
 348             // notify the new event handler with initial opened files
 349             notifyOpenFiles(this.initialOpenedFiles);
 350     }
 351     }
 352 
 353     private boolean terminateWhenLastWindowClosed = true;
 354     public final boolean shouldTerminateWhenLastWindowClosed() {
 355         checkEventThread();
 356         return terminateWhenLastWindowClosed;
 357     }
 358     public final void setTerminateWhenLastWindowClosed(boolean b) {
 359         checkEventThread();
 360         terminateWhenLastWindowClosed = b;
 361     }
 362 
 363     public boolean shouldUpdateWindow() {
 364         checkEventThread();
 365         return false; // overridden in platform application class
 366     }
 367 
 368     public boolean hasWindowManager() {
 369         //checkEventThread(); // Prism (Mac)
 370         return true; // overridden in platform application class
 371     }
 372 
 373     /**
 374      * Notifies the Application that rendering has completed for current pulse.
 375      *
 376      * This is called on the render thread.
 377      */
 378     public void notifyRenderingFinished() {
 379     }
 380 
 381     public void terminate() {
 382         checkEventThread();
 383         try {
 384                 final List<Window> windows = new LinkedList<>(Window.getWindows());
 385                 for (Window window : windows) {
 386                     // first make windows invisible
 387                     window.setVisible(false);
 388                 }
 389                 for (Window window : windows) {
 390                     // now we can close windows
 391                     window.close();
 392                 }
 393         } catch (Throwable t) {
 394             t.printStackTrace();
 395         } finally {
 396             finishTerminating();
 397         }
 398     }
 399 
 400     // May be called on any thread
 401     static public Application GetApplication() {
 402         return Application.application;
 403     }
 404 
 405     // May be called on any thread
 406     protected static void setEventThread(Thread thread) {
 407         Application.eventThread = thread;
 408     }
 409 
 410     // May be called on any thread
 411     protected static Thread getEventThread() {
 412         return Application.eventThread;
 413     }
 414 
 415     /**
 416      * Returns {@code true} if the current thread is the event thread.
 417      */
 418     public static boolean isEventThread() {
 419         return Thread.currentThread() == Application.eventThread;
 420     }
 421 
 422     /**
 423      * Verifies that the current thread is the event thread, and throws
 424      * an exception if this is not so.
 425      *
 426      * The check can be disabled by setting the "glass.disableThreadChecks"
 427      * system property. It is preferred, however, to fix the application code
 428      * instead.
 429      *
 430      * @throws IllegalStateException if the current thread is not the event thread
 431      */
 432     public static void checkEventThread() {
 433         //TODO: we do NOT advertise the "glass.disableThreadChecks".
 434         //      If we never get a complaint about this check, we can consider
 435         //      dropping the system property and perform the check unconditionally
 436         if (!disableThreadChecks &&
 437                 Thread.currentThread() != Application.eventThread)
 438         {
 439             throw new IllegalStateException(
 440                     "This operation is permitted on the event thread only; currentThread = "
 441                     + Thread.currentThread().getName());
 442 
 443         }
 444     }
 445 
 446     // Called from native, when a JNI exception has occurred
 447     public static void reportException(Throwable t) {
 448         Thread currentThread = Thread.currentThread();
 449         Thread.UncaughtExceptionHandler handler =
 450                 currentThread.getUncaughtExceptionHandler();
 451         handler.uncaughtException(currentThread, t);
 452     }
 453 
 454     abstract protected void _invokeAndWait(java.lang.Runnable runnable);
 455     /**
 456      * Block the current thread and wait until the given  runnable finishes
 457      * running on the native event loop thread.
 458      */
 459     public static void invokeAndWait(java.lang.Runnable runnable) {
 460         if (runnable == null) {
 461             return;
 462         }
 463         if (isEventThread()) {
 464             runnable.run();
 465         } else {
 466             GetApplication()._invokeAndWait(runnable);
 467         }
 468     }
 469 
 470     abstract protected void _invokeLater(java.lang.Runnable runnable);
 471     /**
 472      * Schedule the given runnable to run on the native event loop thread
 473      * some time in the future, and return immediately.
 474      */
 475     public static void invokeLater(java.lang.Runnable runnable) {
 476         if (runnable == null) {
 477             return;
 478         }
 479         GetApplication()._invokeLater(runnable);
 480     }
 481 
 482     protected abstract Object _enterNestedEventLoop();
 483     protected abstract void _leaveNestedEventLoop(Object retValue);
 484 
 485     private static int nestedEventLoopCounter = 0;
 486 
 487     /**
 488      * Starts a nested event loop.
 489      *
 490      * Calling this method temporarily blocks processing of the current event,
 491      * and starts a nested event loop to handle other native events.  To
 492      * proceed with the blocked execution path, the application should call the
 493      * {@link #leaveNestedEventLoop(Object)} method.
 494      *
 495      * Note that this method may only be invoked on the main (event handling)
 496      * thread.
 497      *
 498      * An application may enter several nested loops recursively. There's no
 499      * limit of recursion other than that imposed by the native stack size.
 500      *
 501      * @return an object passed to the leaveNestedEventLoop() method
 502      * @throws RuntimeException if the current thread is not the main thread
 503      */
 504     static Object enterNestedEventLoop() {
 505         checkEventThread();
 506 
 507         nestedEventLoopCounter++;
 508         try {
 509             return GetApplication()._enterNestedEventLoop();
 510         } finally {
 511             nestedEventLoopCounter--;
 512         }
 513     }
 514 
 515     /**
 516      * Terminates the current nested event loop.
 517      *
 518      * After calling this method and returning from the current event handler,
 519      * the execusion returns to the point where the {@link #enterNestedEventLoop}
 520      * was called previously. You may specify a return value for the
 521      * enterNestedEventLoop() method by passing the argument {@code retValue} to
 522      * the leaveNestedEventLoop().
 523      *
 524      * Note that this method may only be invoked on the main (event handling)
 525      * thread.
 526      *
 527      * @throws RuntimeException if the current thread is not the main thread
 528      * @throws IllegalStateException if the application hasn't started a nested
 529      *                               event loop
 530      */
 531     static void leaveNestedEventLoop(Object retValue) {
 532         checkEventThread();
 533 
 534         if (nestedEventLoopCounter == 0) {
 535             throw new IllegalStateException("Not in a nested event loop");
 536         }
 537 
 538         GetApplication()._leaveNestedEventLoop(retValue);
 539     }
 540 
 541     public static boolean isNestedLoopRunning() {
 542         checkEventThread();
 543         return nestedEventLoopCounter > 0;
 544     }
 545 
 546     //TODO: move to the EventHandler
 547     public void menuAboutAction() {
 548         System.err.println("about");
 549     }
 550 
 551 
 552     // FACTORY METHODS
 553 
 554     /**
 555      * Create a window.
 556      *
 557      * The styleMask argument is a bitmask of window styles as defined in the
 558      * Window class.  Note, however, that visual kinds (UNTITLED, TITLED,
 559      * or TRANSPARENT) can't be combined together.  Also, functional types
 560      * (NORMAL, POPUP, or UTILITY) can't be combined together.  A window is
 561      * allowed to be of exactly one visual kind, and exactly one functional
 562      * type.
 563      */
 564     public abstract Window createWindow(Window owner, Screen screen, int styleMask);
 565 
 566     /**
 567      * Create a window.
 568      *
 569      * The styleMask argument is a bitmask of window styles as defined in the
 570      * Window class.  Note, however, that visual kinds (UNTITLED, TITLED,
 571      * or TRANSPARENT) can't be combined together.  Also, functional types
 572      * (NORMAL, POPUP, or UTILITY) can't be combined together.  A window is
 573      * allowed to be of exactly one visual kind, and exactly one functional
 574      * type.
 575      */
 576     public final Window createWindow(Screen screen, int styleMask) {
 577         return createWindow(null, screen, styleMask);
 578     }
 579 
 580     public abstract Window createWindow(long parent);
 581 
 582     public abstract View createView();
 583 
 584     public abstract Cursor createCursor(int type);
 585     public abstract Cursor createCursor(int x, int y, Pixels pixels);
 586 
 587     protected abstract void staticCursor_setVisible(boolean visible);
 588     protected abstract Size staticCursor_getBestSize(int width, int height);
 589 
 590     public final Menu createMenu(String title) {
 591         return new Menu(title);
 592     }
 593 
 594     public final Menu createMenu(String title, boolean enabled) {
 595         return new Menu(title, enabled);
 596     }
 597 
 598     public final MenuBar createMenuBar() {
 599         return new MenuBar();
 600     }
 601 
 602     public final MenuItem createMenuItem(String title) {
 603         return createMenuItem(title, null);
 604     }
 605 
 606     public final MenuItem createMenuItem(String title, MenuItem.Callback callback) {
 607         return createMenuItem(title, callback, KeyEvent.VK_UNDEFINED, KeyEvent.MODIFIER_NONE);
 608     }
 609 
 610     public final MenuItem createMenuItem(String title, MenuItem.Callback callback,
 611             int shortcutKey, int shortcutModifiers) {
 612         return createMenuItem(title, callback, shortcutKey, shortcutModifiers, null);
 613     }
 614 
 615     public final MenuItem createMenuItem(String title, MenuItem.Callback callback,
 616             int shortcutKey, int shortcutModifiers, Pixels pixels) {
 617         return new MenuItem(title, callback, shortcutKey, shortcutModifiers, pixels);
 618     }
 619 
 620     public abstract Pixels createPixels(int width, int height, ByteBuffer data);
 621     public abstract Pixels createPixels(int width, int height, IntBuffer data);
 622     public abstract Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley);
 623     protected abstract int staticPixels_getNativeFormat();
 624 
 625     /* utility method called from native code */
 626     static Pixels createPixels(int width, int height, int[] data, float scalex, float scaley) {
 627         return Application.GetApplication().createPixels(width, height, IntBuffer.wrap(data), scalex, scaley);
 628     }
 629 
 630     /* utility method called from native code */
 631     static float getScaleFactor(final int x, final int y, final int w, final int h) {
 632         float scale = 0.0f;
 633         // Find the maximum scale for screens this area overlaps
 634         for (Screen s : Screen.getScreens()) {
 635             final int sx = s.getX(), sy = s.getY(), sw = s.getWidth(), sh = s.getHeight();
 636             if (x < (sx + sw) && (x + w) > sx && y < (sy + sh) && (y + h) > sy) {
 637                 if (scale < s.getRecommendedOutputScaleX()) {
 638                     scale = s.getRecommendedOutputScaleX();
 639                 }
 640                 if (scale < s.getRecommendedOutputScaleY()) {
 641                     scale = s.getRecommendedOutputScaleY();
 642                 }
 643             }
 644         }
 645         return scale == 0.0f ? 1.0f : scale;
 646     }
 647 
 648 
 649     public abstract GlassRobot createRobot();
 650 
 651     protected abstract double staticScreen_getVideoRefreshPeriod();
 652     protected abstract Screen[] staticScreen_getScreens();
 653 
 654     public abstract Timer createTimer(Runnable runnable);
 655     protected abstract int staticTimer_getMinPeriod();
 656     protected abstract int staticTimer_getMaxPeriod();
 657 
 658     public final EventLoop createEventLoop() {
 659         return new EventLoop();
 660     }
 661 
 662     public Accessible createAccessible() { return null; }
 663 
 664     protected abstract FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type,
 665                                                      boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex);
 666 
 667     protected abstract File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title);
 668 
 669     protected abstract long staticView_getMultiClickTime();
 670     protected abstract int staticView_getMultiClickMaxX();
 671     protected abstract int staticView_getMultiClickMaxY();
 672 
 673     /**
 674      * Gets the Name of the currently active high contrast theme.
 675      * If null, then high contrast is not enabled.
 676      */
 677     public String getHighContrastTheme() {
 678         checkEventThread();
 679         return null;
 680     }
 681 
 682     protected boolean _supportsInputMethods() {
 683         // Overridden in subclasses
 684         return false;
 685     }
 686     public final boolean supportsInputMethods() {
 687         checkEventThread();
 688         return _supportsInputMethods();
 689     }
 690 
 691     protected abstract boolean _supportsTransparentWindows();
 692     public final boolean supportsTransparentWindows() {
 693         checkEventThread();
 694         return _supportsTransparentWindows();
 695     }
 696 
 697     public boolean hasTwoLevelFocus() {
 698         return false;
 699     }
 700 
 701     public boolean hasVirtualKeyboard() {
 702         return false;
 703     }
 704 
 705     public boolean hasTouch() {
 706         return false;
 707     }
 708 
 709     public boolean hasMultiTouch() {
 710         return false;
 711     }
 712 
 713     public boolean hasPointer() {
 714         return true;
 715     }
 716 
 717     protected abstract boolean _supportsUnifiedWindows();
 718     public final boolean supportsUnifiedWindows() {
 719         checkEventThread();
 720         return _supportsUnifiedWindows();
 721     }
 722 
 723     protected boolean _supportsSystemMenu() {
 724         // Overridden in subclasses
 725         return false;
 726     }
 727     public final boolean supportsSystemMenu() {
 728         checkEventThread();
 729         return _supportsSystemMenu();
 730     }
 731 
 732     protected abstract int _getKeyCodeForChar(char c);
 733     /**
 734      * Returns a VK_ code of a key capable of producing the given unicode
 735      * character with respect to the currently active keyboard layout or
 736      * VK_UNDEFINED if the character isn't present in the current layout.
 737      *
 738      * @param c the character
 739      * @return integer code for the given char
 740      */
 741     public static int getKeyCodeForChar(char c) {
 742         return application._getKeyCodeForChar(c);
 743     }
 744 }