All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.albertus82.jface.cocoa.CocoaUIEnhancer Maven / Gradle / Ivy

Go to download

Java SWT/JFace Utility Library including a Preferences Framework, Lightweight HTTP Server and macOS support.

There is a newer version: 20.1.0
Show newest version
/* Eclipse Public License, Version 1.0 (EPL-1.0)
 * 
 * THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
 * LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
 * CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
 * 
 * 1. DEFINITIONS
 * 
 * "Contribution" means:
 * 
 *     a) in the case of the initial Contributor, the initial code and
 *        documentation distributed under this Agreement, and
 * 
 *     b) in the case of each subsequent Contributor:
 *         i) changes to the Program, and
 *         ii) additions to the Program;
 * 
 * where such changes and/or additions to the Program originate from and are
 * distributed by that particular Contributor. A Contribution 'originates'
 * from a Contributor if it was added to the Program by such Contributor itself
 * or anyone acting on such Contributor's behalf. Contributions do not include
 * additions to the Program which: (i) are separate modules of software
 * distributed in conjunction with the Program under their own license agreement,
 * and (ii) are not derivative works of the Program.
 * 
 * "Contributor" means any person or entity that distributes the Program.
 * 
 * "Licensed Patents " mean patent claims licensable by a Contributor which are
 * necessarily infringed by the use or sale of its Contribution alone or
 * when combined with the Program.
 * 
 * "Program" means the Contributions distributed in accordance with
 * this Agreement.
 * 
 * "Recipient" means anyone who receives the Program under this Agreement,
 * including all Contributors.
 * 
 * 2. GRANT OF RIGHTS
 * 
 *     a) Subject to the terms of this Agreement, each Contributor hereby grants
 *        Recipient a non-exclusive, worldwide, royalty-free copyright license to
 *        reproduce, prepare derivative works of, publicly display, publicly
 *        perform, distribute and sublicense the Contribution of such
 *        Contributor, if any, and such derivative works,
 *        in source code and object code form.
 * 
 *     b) Subject to the terms of this Agreement, each Contributor hereby grants
 *        Recipient a non-exclusive, worldwide, royalty-free patent license under
 *        Licensed Patents to make, use, sell, offer to sell, import and
 *        otherwise transfer the Contribution of such Contributor, if any,
 *        in source code and object code form. This patent license shall apply
 *        to the combination of the Contribution and the Program if, at the time
 *        the Contribution is added by the Contributor, such addition of the
 *        Contribution causes such combination to be covered by the
 *        Licensed Patents. The patent license shall not apply to any other
 *        combinations which include the Contribution.
 *        No hardware per se is licensed hereunder.
 * 
 *     c) Recipient understands that although each Contributor grants the
 *        licenses to its Contributions set forth herein, no assurances are
 *        provided by any Contributor that the Program does not infringe the
 *        patent or other intellectual property rights of any other entity.
 *        Each Contributor disclaims any liability to Recipient for claims
 *        brought by any other entity based on infringement of intellectual
 *        property rights or otherwise. As a condition to exercising the
 *        rights and licenses granted hereunder, each Recipient hereby assumes
 *        sole responsibility to secure any other intellectual property rights
 *        needed, if any. For example, if a third party patent license is
 *        required to allow Recipient to distribute the Program, it is
 *        Recipient's responsibility to acquire that license
 *        before distributing the Program.
 * 
 *     d) Each Contributor represents that to its knowledge it has sufficient
 *        copyright rights in its Contribution, if any, to grant the copyright
 *        license set forth in this Agreement.
 * 
 * 3. REQUIREMENTS
 * 
 * A Contributor may choose to distribute the Program in object code form under
 * its own license agreement, provided that:
 * 
 *     a) it complies with the terms and conditions of this Agreement; and
 * 
 *     b) its license agreement:
 * 
 *         i) effectively disclaims on behalf of all Contributors all warranties
 *         and conditions, express and implied, including warranties or
 *         conditions of title and non-infringement, and implied warranties or
 *         conditions of merchantability and fitness for a particular purpose;
 * 
 *         ii) effectively excludes on behalf of all Contributors all liability
 *         for damages, including direct, indirect, special, incidental and
 *         consequential damages, such as lost profits;
 * 
 *         iii) states that any provisions which differ from this Agreement are
 *         offered by that Contributor alone and not by any other party; and
 * 
 *         iv) states that source code for the Program is available from such
 *         Contributor, and informs licensees how to obtain it in a reasonable
 *         manner on or through a medium customarily used for software exchange.
 * 
 * When the Program is made available in source code form:
 * 
 *     a) it must be made available under this Agreement; and
 *     b) a copy of this Agreement must be included with each copy of the Program.
 * 
 * Contributors may not remove or alter any copyright notices contained
 * within the Program.
 * 
 * Each Contributor must identify itself as the originator of its Contribution,
 * if any, in a manner that reasonably allows subsequent Recipients to
 * identify the originator of the Contribution.
 * 
 * 4. COMMERCIAL DISTRIBUTION
 * 
 * Commercial distributors of software may accept certain responsibilities with
 * respect to end users, business partners and the like. While this license is
 * intended to facilitate the commercial use of the Program, the Contributor who
 * includes the Program in a commercial product offering should do so in a manner
 * which does not create potential liability for other Contributors. Therefore,
 * if a Contributor includes the Program in a commercial product offering,
 * such Contributor ("Commercial Contributor") hereby agrees to defend and
 * indemnify every other Contributor ("Indemnified Contributor") against any
 * losses, damages and costs (collectively "Losses") arising from claims,
 * lawsuits and other legal actions brought by a third party against the
 * Indemnified Contributor to the extent caused by the acts or omissions of
 * such Commercial Contributor in connection with its distribution of the Program
 * in a commercial product offering. The obligations in this section do not apply
 * to any claims or Losses relating to any actual or alleged intellectual
 * property infringement. In order to qualify, an Indemnified Contributor must:
 * a) promptly notify the Commercial Contributor in writing of such claim,
 * and b) allow the Commercial Contributor to control, and cooperate with the
 * Commercial Contributor in, the defense and any related settlement
 * negotiations. The Indemnified Contributor may participate in any such
 * claim at its own expense.
 * 
 * For example, a Contributor might include the Program in a commercial product
 * offering, Product X. That Contributor is then a Commercial Contributor.
 * If that Commercial Contributor then makes performance claims, or offers
 * warranties related to Product X, those performance claims and warranties
 * are such Commercial Contributor's responsibility alone. Under this section,
 * the Commercial Contributor would have to defend claims against the other
 * Contributors related to those performance claims and warranties, and if a
 * court requires any other Contributor to pay any damages as a result,
 * the Commercial Contributor must pay those damages.
 * 
 * 5. NO WARRANTY
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
 * NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * Each Recipient is solely responsible for determining the appropriateness of
 * using and distributing the Program and assumes all risks associated with its
 * exercise of rights under this Agreement , including but not limited to the
 * risks and costs of program errors, compliance with applicable laws, damage to
 * or loss of data, programs or equipment, and unavailability
 * or interruption of operations.
 * 
 * 6. DISCLAIMER OF LIABILITY
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
 * CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
 * LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
 * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 * 
 * 7. GENERAL
 * 
 * If any provision of this Agreement is invalid or unenforceable under
 * applicable law, it shall not affect the validity or enforceability of the
 * remainder of the terms of this Agreement, and without further action by
 * the parties hereto, such provision shall be reformed to the minimum extent
 * necessary to make such provision valid and enforceable.
 * 
 * If Recipient institutes patent litigation against any entity (including a
 * cross-claim or counterclaim in a lawsuit) alleging that the Program itself
 * (excluding combinations of the Program with other software or hardware)
 * infringes such Recipient's patent(s), then such Recipient's rights granted
 * under Section 2(b) shall terminate as of the date such litigation is filed.
 * 
 * All Recipient's rights under this Agreement shall terminate if it fails to
 * comply with any of the material terms or conditions of this Agreement and
 * does not cure such failure in a reasonable period of time after becoming
 * aware of such noncompliance. If all Recipient's rights under this
 * Agreement terminate, Recipient agrees to cease use and distribution of the
 * Program as soon as reasonably practicable. However, Recipient's obligations
 * under this Agreement and any licenses granted by Recipient relating to the
 * Program shall continue and survive.
 * 
 * Everyone is permitted to copy and distribute copies of this Agreement,
 * but in order to avoid inconsistency the Agreement is copyrighted and may
 * only be modified in the following manner. The Agreement Steward reserves
 * the right to publish new versions (including revisions) of this Agreement
 * from time to time. No one other than the Agreement Steward has the right to
 * modify this Agreement. The Eclipse Foundation is the initial
 * Agreement Steward. The Eclipse Foundation may assign the responsibility to
 * serve as the Agreement Steward to a suitable separate entity. Each new version
 * of the Agreement will be given a distinguishing version number. The Program
 * (including Contributions) may always be distributed subject to the version
 * of the Agreement under which it was received. In addition, after a new version
 * of the Agreement is published, Contributor may elect to distribute the Program
 * (including its Contributions) under the new version. Except as expressly
 * stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
 * licenses to the intellectual property of any Contributor under this Agreement,
 * whether expressly, by implication, estoppel or otherwise. All rights in the
 * Program not expressly granted under this Agreement are reserved.
 * 
 * This Agreement is governed by the laws of the State of New York and the
 * intellectual property laws of the United States of America. No party to
 * this Agreement will bring a legal action under this Agreement more than one
 * year after the cause of action arose. Each party waives its rights to a
 * jury trial in any resulting litigation.
 */
package io.github.albertus82.jface.cocoa;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.Nullable;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.C;
import org.eclipse.swt.internal.Callback;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;

import io.github.albertus82.jface.JFaceMessages;
import io.github.albertus82.util.logging.LoggerFactory;

/**
 * This class provides a hook to connect the Preferences, About
 * and Quit menu items of the macOS application menu.
 * 

* This is a modified version of the {@code CocoaUIEnhancer} class available at * * TransparenTech, and it is released under the * Eclipse Public * License (EPL). * * @see * CocoaUIEnhancer - Connect the About, Preferences and Quit menus in Mac * OS X Cocoa SWT and JFace applications */ public class CocoaUIEnhancer { private static final Logger log = LoggerFactory.getLogger(CocoaUIEnhancer.class); private static final String ITEM_AT_INDEX = "itemAtIndex"; private static final int kAboutMenuItem = 0; private static final int kPreferencesMenuItem = 2; private final Display display; private Callback proc3Args; private long sel_aboutMenuItemSelected_; private long sel_preferencesMenuItemSelected_; private long sel_toolbarButtonClicked_; /** * Creates a new instance associated with the provided display object. *

* Note: in order to better integrate your JFace application with macOS, * you should call the following static methods of * {@link org.eclipse.swt.widgets.Display Display} before calling this * constructor: * *

	 * Display.setAppName("My JFace Application");
	 * Display.setAppVersion("1.2.3");
	 * 
* * @param display the display that contains the macOS menu bar * * @see org.eclipse.swt.widgets.Display */ public CocoaUIEnhancer(final Display display) { this.display = display; } /** * Activates the macOS application menu items and binds them to the provided * listeners. This method must be called before opening the shell. *

* If one argument is null, then the respective menu item will be disabled; so, * for instance, if your application does not have a preferences management, you * can pass null in place of {@code preferencesListener} and the * Preferences... menu item will be grayed out. * * @param quitListener the listener that will be notified when the user selects * the Quit menu item; should not be null. * @param aboutListener the listener that will be notified when the user selects * the About menu item; can be null. * @param preferencesListener the listener that will be notified when the user * selects the Preferences... menu item; can be null. * @throws CocoaEnhancerException if the UI cannot be improved because of an * error. */ public void hookApplicationMenu(@Nullable final Listener quitListener, @Nullable final Listener aboutListener, @Nullable final Listener preferencesListener) throws CocoaEnhancerException { try { hookApplicationMenu(quitListener, new ListenerCallbackObject(aboutListener, preferencesListener)); } catch (final Exception e) { throw new CocoaEnhancerException(e); } catch (final LinkageError le) { // reflective methods might also (erroneously) throw LinkageError! throw new CocoaEnhancerException(le); } } /** * Activates the macOS application menu items and binds them to the provided * listener and actions. This method must be called before opening the shell. *

* If one argument is null, then the respective menu item will be disabled; so, * for instance, if your application does not have a preferences management, you * can pass null in place of {@code preferencesAction} and the * Preferences... menu item will be grayed out. * * @param quitListener the listener that will be notified when the user selects * the Quit menu item; should not be null. * @param aboutAction the action that will be activated when the user selects * the About menu item; can be null. * @param preferencesAction the action that will be activated when the user * selects the Preferences... menu item; can be null. * @throws CocoaEnhancerException if the UI cannot be improved because of an * error. */ public void hookApplicationMenu(@Nullable final Listener quitListener, @Nullable final IAction aboutAction, @Nullable final IAction preferencesAction) throws CocoaEnhancerException { try { hookApplicationMenu(quitListener, new ActionCallbackObject(aboutAction, preferencesAction)); } catch (final Exception e) { throw new CocoaEnhancerException(e); } catch (final LinkageError le) { // reflective methods might also (erroneously) throw LinkageError! throw new CocoaEnhancerException(le); } } private void hookApplicationMenu(@Nullable final Listener quitListener, final CallbackObject callbackObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { // Check platform if (!Util.isCocoa()) { log.log(Level.WARNING, JFaceMessages.get("err.cocoa.enhancer.platform")); } initialize(callbackObject); // Connect the quit/exit menu. if (!display.isDisposed() && quitListener != null) { display.addListener(SWT.Close, quitListener); } // Schedule disposal of callback object. display.disposeExec(new Runnable() { @Override public void run() { proc3Args.dispose(); } }); } private void initialize(final CallbackObject callbackObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { final Class osCls = Class.forName("org.eclipse.swt.internal.cocoa.OS"); // Register names in Objective-C. if (sel_toolbarButtonClicked_ == 0) { sel_preferencesMenuItemSelected_ = registerName(osCls, "preferencesMenuItemSelected:"); sel_aboutMenuItemSelected_ = registerName(osCls, "aboutMenuItemSelected:"); } // Create an SWT Callback object that will invoke the actionProc method of our internal callbackObject. proc3Args = new Callback(callbackObject, "actionProc", 3); final long proc3 = proc3Args.getAddress(); if (proc3 == 0) { SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); } final Object object = invoke(osCls, "objc_lookUpClass", new Object[] { "SWTApplicationDelegate" }); final long cls = convertToLong(object); // Add the action callbacks for Preferences and About menu items. invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_preferencesMenuItemSelected_), wrapPointer(proc3), "@:@" }); invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_aboutMenuItemSelected_), wrapPointer(proc3), "@:@" }); final Class nsapplicationCls = Class.forName("org.eclipse.swt.internal.cocoa.NSApplication"); final Class nsmenuCls = Class.forName("org.eclipse.swt.internal.cocoa.NSMenu"); // Get the Mac OS X Application menu. final Object sharedApplication = invoke(nsapplicationCls, "sharedApplication"); final Object mainMenu = invoke(sharedApplication, "mainMenu"); final Object mainMenuItem = invoke(nsmenuCls, mainMenu, ITEM_AT_INDEX, new Number[] { wrapPointer(0) }); final Object appMenu = invoke(mainMenuItem, "submenu"); final Object aboutMenuItem = invoke(nsmenuCls, appMenu, ITEM_AT_INDEX, new Number[] { wrapPointer(kAboutMenuItem) }); final Object prefMenuItem = invoke(nsmenuCls, appMenu, ITEM_AT_INDEX, new Number[] { wrapPointer(kPreferencesMenuItem) }); final Class nsmenuitemCls = Class.forName("org.eclipse.swt.internal.cocoa.NSMenuItem"); if (callbackObject.aboutEnabled) { invoke(nsmenuitemCls, aboutMenuItem, "setAction", new Number[] { wrapPointer(sel_aboutMenuItemSelected_) }); } invoke(nsmenuitemCls, aboutMenuItem, "setEnabled", new Boolean[] { callbackObject.aboutEnabled }); if (callbackObject.preferencesEnabled) { invoke(nsmenuitemCls, prefMenuItem, "setAction", new Number[] { wrapPointer(sel_preferencesMenuItemSelected_) }); } invoke(nsmenuitemCls, prefMenuItem, "setEnabled", new Boolean[] { callbackObject.preferencesEnabled }); } private static Object invoke(final Class clazz, final Object target, final String methodName, final Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Class[] signature = new Class[args.length]; for (int i = 0; i < args.length; i++) { final Class thisClass = args[i].getClass(); if (thisClass == Integer.class) { signature[i] = int.class; } else if (thisClass == Long.class) { signature[i] = long.class; } else if (thisClass == Byte.class) { signature[i] = byte.class; } else if (thisClass == Boolean.class) { signature[i] = boolean.class; } else { signature[i] = thisClass; } } final Method method = clazz.getMethod(methodName, signature); return method.invoke(target, args); } private static Object invoke(final Class clazz, final String methodName, final Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return invoke(clazz, null, methodName, args); } private static Object invoke(final Class cls, final String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return invoke(cls, methodName, (Class[]) null, (Object[]) null); } private static Object invoke(final Class cls, final String methodName, final Class[] paramTypes, final Object... arguments) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Method method = cls.getDeclaredMethod(methodName, paramTypes); return method.invoke(null, arguments); } private static Object invoke(final Object obj, final String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return invoke(obj, methodName, (Class[]) null, (Object[]) null); } private static Object invoke(final Object obj, final String methodName, final Class[] paramTypes, final Object... arguments) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Method method = obj.getClass().getDeclaredMethod(methodName, paramTypes); return method.invoke(obj, arguments); } private static long convertToLong(final Object object) { if (object instanceof Integer) { final Integer i = (Integer) object; return i.longValue(); } if (object instanceof Long) { final Long l = (Long) object; return l.longValue(); } return 0; } private static Number wrapPointer(final long value) { final Class ptrClass = C.PTR_SIZEOF == 8 ? long.class : int.class; if (ptrClass == long.class) { return Long.valueOf(value); } else { return Integer.valueOf((int) value); } } private static long registerName(final Class osCls, final String name) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Object object = invoke(osCls, "sel_registerName", new Object[] { name }); return convertToLong(object); } private abstract class CallbackObject { static final long RETURN_VALUE = 99; private final boolean aboutEnabled; private final boolean preferencesEnabled; protected CallbackObject(final boolean aboutEnabled, final boolean preferencesEnabled) { this.aboutEnabled = aboutEnabled; this.preferencesEnabled = preferencesEnabled; } @SuppressWarnings("unused") int actionProc(final int id, final int sel, final int arg0) { // Casts the parameters to long so and use the method for 64 bit Cocoa. return (int) actionProc((long) id, (long) sel, (long) arg0); } abstract long actionProc(final long id, final long sel, final long arg0); } private class ListenerCallbackObject extends CallbackObject { private final Listener aboutListener; private final Listener preferencesListener; private ListenerCallbackObject(@Nullable final Listener aboutListener, @Nullable final Listener preferencesListener) { super(aboutListener != null, preferencesListener != null); this.aboutListener = aboutListener; this.preferencesListener = preferencesListener; } @Override long actionProc(final long id, final long sel, final long arg0) { if (sel == sel_aboutMenuItemSelected_ && aboutListener != null) { aboutListener.handleEvent(null); } else if (sel == sel_preferencesMenuItemSelected_ && preferencesListener != null) { preferencesListener.handleEvent(null); } return RETURN_VALUE; } } private class ActionCallbackObject extends CallbackObject { private final IAction preferencesAction; private final IAction aboutAction; private ActionCallbackObject(@Nullable final IAction aboutAction, @Nullable final IAction preferencesAction) { super(aboutAction != null, preferencesAction != null); this.aboutAction = aboutAction; this.preferencesAction = preferencesAction; } @Override long actionProc(final long id, final long sel, final long arg0) { if (sel == sel_aboutMenuItemSelected_ && aboutAction != null) { aboutAction.run(); } else if (sel == sel_preferencesMenuItemSelected_ && preferencesAction != null) { preferencesAction.run(); } return RETURN_VALUE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy