io.github.albertus82.jface.cocoa.CocoaUIEnhancer Maven / Gradle / Ivy
Show all versions of jface-utils Show documentation
/* 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;
}
}
}