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