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