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

com.pekinsoft.framework.ActionX Maven / Gradle / Ivy

Go to download

A simple platform on which Java/Swing desktop applications may be built. This updated version has packaged the entire library into a single JAR file. We have also made the following changes: ToolBarGenerator should now create ButtonGroups properly for state actions. ApplicationContext has accessors for the WindowManager, DockingManager, StatusDisplayer, and ProgressHandler implementations. It defaults to testing the Application's MainView and the MainView's StatusBar, then uses the Lookup, if the MainView and its StatusBar do not implement the desired interfaces. StatusMessage now uses the com.pekinsoft.desktop.error.ErrorLevel instead of the java.util.logging.Level, so that the levels will no longer need to be cast in order to be used.

The newest version!
/*
 * Copyright (C) 2024 PekinSOFT Systems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 * 
 * *****************************************************************************
 *  Project    :   application-framework-api
 *  Class      :   ActionX.java
 *  Author     :   Sean Carrick
 *  Created    :   Jul 13, 2024
 *  Modified   :   Jul 13, 2024
 *  
 *  Purpose: See class JavaDoc for explanation
 *  
 *  Revision History:
 *  
 *  WHEN          BY                   REASON
 *  ------------  -------------------  -----------------------------------------
 *  Jul 13, 2024  Sean Carrick         Initial creation.
 * *****************************************************************************
 */

package com.pekinsoft.framework;

import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static javax.swing.Action.*;
import javax.swing.*;

/**
 * {@code ActionX} is a custom {@link AbstractAction} that is built automatically
 * from the methods decorated with the {@link AppAction @AppAction} annotation.
 * The {@code ActionX} implementations are, at their core, just a 
 * {@link javax.swing.Action Action}, which can be installed into any component
 * in which an {@code Action} can be installed. The {@code ActionX} class has
 * simply added some more default property keys that are based on the 
 * {@code @AppAction} parameters and are useful for dynamic menu bar and toolbar
 * creation in an application.
 *
 * @author Sean Carrick <sean at pekinsoft dot com>
 * 
 * @version 1.0
 * @since 1.0
 */
public class ActionX extends AbstractAction implements ItemListener {

    public ActionX(ActionMapX appAM, ResourceMap resourceMap,
            String baseName, Method actionMethod, String enabledProperty,
            String selectedProperty, BackgroundTask.BlockingScope block,
            String menuBaseName, byte menuActionIndex, boolean menuSepBefore,
            boolean menuSepAfter, boolean showOnToolbar, String toolbarBaseName,
            byte toolbarActionIndex, boolean toolbarSepBefore,
            boolean toolbarSepAfter) {
        this.appAM = appAM;
        this.resourceMap = resourceMap;
        this.actionName = baseName;
        this.actionMethod = actionMethod;
        this.enabledProperty = enabledProperty;
        this.selectedProperty = selectedProperty;
        this.block = block;

        /*
         * Verify if enabledProperty is specified, lookup the is/set methods and
         * verify that the former exists.
         */
        if (enabledProperty != null) {
            setEnabledMethod = propertySetMethod(enabledProperty, boolean.class);
            isEnabledMethod = propertyGetMethod(enabledProperty);
            if (isEnabledMethod == null) {
                throw newNoSuchPropertyException(enabledProperty);
            }
        } else {
            this.isEnabledMethod = null;
            this.setEnabledMethod = null;
        }

        /*
         * If the selectedProperty is specified, lookup the is/set methods and
         * verify that the former exists.
         */
        if (selectedProperty != null) {
            setSelectedMethod = propertySetMethod(selectedProperty,
                    boolean.class);
            isSelectedMethod = propertyGetMethod(selectedProperty);
            if (isSelectedMethod == null) {
                throw newNoSuchPropertyException(selectedProperty);
            }
        } else {
            setSelectedMethod = null;
            isSelectedMethod = null;
        }

        if (resourceMap != null) {
            initActionProperties(resourceMap, baseName);
        }
    }

    ActionX(ActionMapX appAM, ResourceMap resourceMap, String actionName) {
        this(appAM, resourceMap, actionName, null, null,
                null, BackgroundTask.BlockingScope.NONE, null, (byte) 0, false,
                false,
                false, null, (byte) 0, false, false);
        configureTextAction(this);
    }

    /**
     * Retrieves the name of the {@code @AppAction} enabled property whose value
     * is returned by {@link #isEnabled() isEnabled} or {@code null}.
     *
     * @return the name of the enabled property or {@code null}
     *
     * @see #isEnabled()
     */
    String getEnabledProperty() {
        return enabledProperty;
    }

    /**
     * Retrieves the name of the {@code @AppAction} selected property whose
     * value is returned by {@link #isSelected() isSelected} or {@code null}.
     *
     * @return the name of the selected property or {@code null}
     *
     * @see #isSelected()
     */
    String getSelectedProperty() {
        return selectedProperty;
    }

    /**
     * Retrieves the proxy for this action or {@code null}.
     *
     * @return the value of the proxy property
     *
     * @see #setProxy(Action)
     * @see #setProxySource(Object)
     * @see #actionPerformed(java.awt.event.ActionEvent)
     */
    public Action getProxy() {
        return proxy;
    }

    /**
     * Set the proxy for this action. if the proxy is non-{@code null} then we
     * delegate/track the following:
     * 
*
{@code actionPerformed}
*
Out {@code actionPerformed} method calls the delegate's after the * {@link ActionEvent} source to be the value of * {@code getProxySource}.
*
shortDescription
*
If the proxy's {@code shortDescription}, i.e., the value for key * {@link Action#SHORT_DESCRIPTION SHORT_DESCRIPTION} is not {@code null}, * then set this action's {@code shortDescription}. Most Swing components * use the {@code shortDescription} to initialize their tooltip.
*
{@code longDescription}
*
If the proxy's {@code longDescription}, i.e., the value for key * {@link Action#LONG_DESCRIPTION LONG_DESCRIPTION} is not {@code null}, * then set this action's {@code longDescription}.
*
* * @param proxy the new proxy action * * @see #getProxy() * @see #setProxySource(Object) * @see #actionPerformed(java.awt.event.ActionEvent) */ public void setProxy(Action proxy) { Action oldProxy = getProxy(); this.proxy = proxy; if (proxy instanceof ActionX action) { if (action.getName().equals("cut") || action.getName().equals("copy") || action.getName().equals("paste") || action.getName().equals("delete")) { configureTextAction(action); } } if (oldProxy != null) { oldProxy.removePropertyChangeListener(proxyPCL); proxyPCL = null; } if (this.proxy != null) { updateProxyProperties(); proxyPCL = new ProxyPCL(); proxy.addPropertyChangeListener(proxyPCL); } else if (oldProxy != null) { setEnabled(false); setSelected(false); } firePropertyChange("proxy", oldProxy, getProxy()); } private void configureTextAction(ActionX action) { if (action == null) { return; } switch (action.getName()) { case "cut" -> { action.putValue(MENU_ACTION_INDEX, -128); action.putValue(MENU_BASE_NAME, "edit"); action.putValue(MENU_INDEX, getResourceMap().getByte("edit.menu.index")); action.putValue(TOOLBAR_ACTION_INDEX, -128); action.putValue(TOOLBAR_NAME, getResourceMap().getString("edit.toolbar.name")); action.putValue(TOOLBAR_INDEX, getResourceMap().getByte("edit.toolbar.index")); } case "copy" -> { action.putValue(MENU_ACTION_INDEX, -127); action.putValue(MENU_BASE_NAME, "edit"); action.putValue(MENU_INDEX, getResourceMap().getByte("edit.menu.index")); action.putValue(TOOLBAR_ACTION_INDEX, -127); action.putValue(TOOLBAR_NAME, getResourceMap().getString("edit.toolbar.name")); action.putValue(TOOLBAR_INDEX, getResourceMap().getByte("edit.toolbar.index")); } case "paste" -> { action.putValue(MENU_ACTION_INDEX, -126); action.putValue(MENU_BASE_NAME, "edit"); action.putValue(MENU_INDEX, getResourceMap().getByte("edit.menu.index")); action.putValue(MENU_POST_SEPARATOR, true); action.putValue(TOOLBAR_ACTION_INDEX, -126); action.putValue(TOOLBAR_NAME, getResourceMap().getString("edit.toolbar.name")); action.putValue(TOOLBAR_INDEX, getResourceMap().getByte("edit.toolbar.index")); action.putValue(TOOLBAR_POST_SEPARATOR, true); } case "delete" -> { action.putValue(MENU_ACTION_INDEX, 127); action.putValue(MENU_BASE_NAME, "edit"); action.putValue(MENU_INDEX, getResourceMap().getByte("edit.menu.index")); action.putValue(MENU_PRE_SEPARATOR, true); action.putValue(TOOLBAR_ACTION_INDEX, 127); action.putValue(TOOLBAR_NAME, getResourceMap().getString("edit.toolbar.name")); action.putValue(TOOLBAR_INDEX, getResourceMap().getByte("edit.toolbar.index")); action.putValue(TOOLBAR_PRE_SEPARATOR, true); } } } /** * Retrieves the value that becomes the {@link ActionEvent} source before * the {@code ActionEvent} is passed along to the proxy Action. * * @return the value of the proxySource property * * @see #getProxy() * @see #setProxySource(Object) * @see ActionEvent#getSource() */ public Object getProxySource() { return proxySource; } /** * Sets the value that becomes the {@link ActionEvent} source before the * {@code ActionEvent} is passed along to the proxy Action. * * @param source the {@code ActionEvent} source * * @see #getProxy() * @see #getProxySource() * @see ActionEvent#setSource(java.lang.Object) */ public void setProxySource(Object source) { Object oldValue = getProxySource(); this.proxySource = source; firePropertyChange("proxySource", oldValue, getProxySource()); } /** * If the proxy action is {@code null} and {@code selectedProperty} was * specified, then return the value of the selected property's is/get method * applied to our {@link ActionMapX}'s {@code actionsObject}. Otherwise, * return the value of this {@code ActionX}'s enabled property. * * @return {@code true} if this {@code ActionX}'s {@link JToggleButton} is * selected * * @see #setProxy(javax.swing.Action) * @see #setSelected(boolean) * @see ActionMapX#getActionsObject() */ public boolean isSelected() { if ((getProxy() != null) || (isSelectedMethod == null)) { Object v = getValue(SELECTED_KEY); return (v instanceof Boolean selected) ? selected : false; } else { try { Object b = isSelectedMethod.invoke(appAM.getActionsObject()); return (b instanceof Boolean selected) ? selected : false; } catch (IllegalAccessException | InvocationTargetException e) { throw newInvokeError(isSelectedMethod, e); } } } /** * If the proxy action is {@code null} and {@code selectedProperty} was * specified, then set the value of the selected property by invoking the * corresponding {@code set} method on our {@code ActionMapX}'s * {@code actionsObject}. Otherwise, set the value of this {@code ActionX}'s * selected property. * * @param selected this {@code ActionX}'s {@link JToggleButton}'s value * * @see #setProxy(javax.swing.Action) * @see #isSelected() * @see ActionMapX#getActionsObject() */ public void setSelected(boolean selected) { if ((getProxy() != null) || (setSelectedMethod == null)) { super.putValue(SELECTED_KEY, selected); } else { try { super.putValue(SELECTED_KEY, selected); if (selected != isSelected()) { setSelectedMethod.invoke(appAM.getActionsObject(), selected); } } catch (IllegalAccessException | InvocationTargetException e) { throw newInvokeError(setSelectedMethod, e, selected); } } } public String getActionCommand() { String command = (String) getValue(ACTION_COMMAND_KEY); if ((getProxy() != null)) { command = (String) super.getValue(ACTION_COMMAND_KEY); } return command; } public boolean isStateAction() { Boolean state = (Boolean) getValue(SELECTED_KEY); if (state != null) { if ((getProxy() != null) || (setSelectedMethod == null)) { state = (boolean) super.getValue(SELECTED_KEY); } return state; } return setSelectedMethod != null; } public String getGroup() { return (String) getValue(GROUP); } /** * The name of this {@code ActionX}. This string begins with the name * corresponding to the {@code @AppAction} method (unless the {@code name} * {@code @AppAction} parameter was specified). *

* This name is used as a prefix to look up action resources, and the * Framework uses it as the key for this {@code ActionX} in * {@link ActionMapX}s. *

* Note: this property should not be confused with the * {@link Action#NAME Action.NAME} key. That key is actually used to * initialize the {@code text} properties of Swing components, which is why * we call the corresponding {@code AppAction} resource "Action.text", as * in: *

     * myCloseButton.Action.text = Close
     * 
* * @return the read-only value of the name property */ public String getName() { return actionName; } /** * The {@link ResourceMap} for this action. * * @return the read-only value of the {@code resourceMap} property */ public ResourceMap getResourceMap() { return resourceMap; } /** * Keeps the {@code @AppAction selectedProperty} in sync when the value of * {@code key} is {@code ActionX.SELECTED_KEY}. * * @param key {@inheritDoc } * @param value {@inheritDoc } */ @Override public void putValue(String key, Object value) { if (SELECTED_KEY.equals(key) && (value instanceof Boolean selected)) { setSelected(selected); } else { super.putValue(key, value); } } /** * This method implements this {@code ActionX}'s behavior. *

* If there is a proxy action, then call its {@code actionPerformed} method. * Otherwise, call the {@code @AppAction} method with parameter values * provided by {@link #getActionArgument(Class, String, ActionEvent) }. If * anything goes wrong, call {@link #actionFailed(ActionEvent, Exception)}. * * @param event {@inheritDoc } * * @see #setProxy(javax.swing.Action) * @see #getActionArgument(java.lang.Class, java.lang.String, * java.awt.event.ActionEvent) * @see BackgroundTask */ @Override public void actionPerformed(ActionEvent event) { Action proxy = getProxy(); if (proxy != null) { event.setSource(getProxySource()); proxy.actionPerformed(event); } else if (actionMethod != null) { noProxyActionPerformed(event); } } /** * If the proxy action is {@code null} and {@code enabledProperty} was * specified, then return the value of the enabled property's is/get method * applied to our {@link ActionMapX}'s {@code actionsObject}. Otherwise, * return the value of this {@code ActionX}'s enabled property. * * @return {@inheritDoc } * * @see #setProxy(javax.swing.Action) * @see #setEnabled(boolean) * @see ActionMapX#getActionsObject() */ @Override public boolean isEnabled() { if ((getProxy() != null) || (isEnabledMethod == null)) { return super.isEnabled(); } else { try { Object b = isEnabledMethod.invoke(appAM.getActionsObject()); return (boolean) b; } catch (IllegalAccessException | InvocationTargetException e) { throw newInvokeError(isEnabledMethod, e); } } } /** * If the proxy action is {@code null} and {@code enabledProperty} was * specified, then set the value of the enabled property by invoking the * corresponding {@code set} method on our {@link ActionMapX}'s * {@code actionsObject}. Otherwise, set the value of this {@code ActionX}'s * enabled property. * * @param newValue {@inheritDoc } * * @see #setProxy(javax.swing.Action) * @see #isEnabled() * @see ActionMapX#getActionsObject() */ @Override public void setEnabled(boolean newValue) { if ((getProxy() != null) || (setEnabledMethod == null)) { super.setEnabled(newValue); } else { try { setEnabledMethod.invoke(appAM.getActionsObject(), newValue); } catch (IllegalAccessException | InvocationTargetException e) { throw newInvokeError(setEnabledMethod, e, newValue); } } } /** * Retrieves the name of the menu or menus into which the action should be * installed. * * @return the menu path into which to install the action */ public String getMenuBaseName() { return (String) getValue(MENU_BASE_NAME); } /** * Retrieves the index position that the menu desires on the menu bar (for * parent menus) or within the parent menu (for child menus). * * @return the menu's desired index position */ public byte getMenuIndex() { Object obj = getValue(MENU_INDEX); Byte index = null; if (obj instanceof Integer i) { index = i.byteValue(); } else { index = (Byte) obj; } return index == null ? (byte) -1 : (byte) index; } /** * Retrieves the action's desired index position within the menu in which it * is installed. * * @return the index position at which to install the action */ public byte getMenuActionIndex() { Object obj = getValue(MENU_ACTION_INDEX); Byte index = null; if (obj instanceof Integer i) { index = i.byteValue(); } else { index = (Byte) obj; } return index == null ? (byte) -1 : (byte) index; } /** * Determines whether a separator should be installed before the action in * the menu. * * @return {@code true} to install a separator before the action */ public boolean isMenuSeparatorBefore() { Boolean before = (Boolean) getValue(MENU_PRE_SEPARATOR); return before == null ? false : before; } /** * Determines whether a separator should be installed after the action in * the menu. * * @return {@code true} to install the action, then a separator */ public boolean isMenuSeparatorAfter() { Boolean after = (Boolean) getValue(MENU_POST_SEPARATOR); return after == null ? false : after; } /** * Determines whether this action should be installed into a toolbar within * the application. This property simply tests whether the toolbar name * property is {@code false}. * * @return {@code true} if the action should be installed in a toolbar */ public boolean isShowInToolBar() { return getToolbarName() != null; } /** * Retrieves the name of the toolbar into which the action should be * installed. * * @return the toolbar name */ public String getToolbarName() { return (String) getValue(TOOLBAR_NAME); } /** * Retrieves the index position for the toolbar in the toolbars area. * * @return the toolbar index position */ public byte getToolBarIndex() { Byte index = (Byte) getValue(TOOLBAR_INDEX); return index == null ? (byte) -1 : (byte) index; } /** * Retrieves the index position at which the action should be installed in * the toolbar. * * @return the action's index position */ public byte getToolBarActionIndex() { Object obj = getValue(TOOLBAR_ACTION_INDEX); Byte index = null; if (obj instanceof Integer i) { index = i.byteValue(); } else { index = (Byte) obj; } return index == null ? (byte) -1 : (byte) index; } /** * Determines whether a separator should be installed before the action in * the toolbar. * * @return {@code true} to install a separator then the action */ public boolean isToolBarSeparatorBefore() { Boolean before = (Boolean) getValue(TOOLBAR_PRE_SEPARATOR); return before == null ? false : before; } /** * Determines whether a separator should be installed after the action in * the toolbar. * * @return {@code true} to install the action, then a separator */ public boolean isToolBarSeparatorAfter() { Boolean after = (Boolean) getValue(TOOLBAR_POST_SEPARATOR); return after == null ? false : after; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getName()); sb.append(" "); boolean isEnabled = isEnabled(); if (!isEnabled) { sb.append("("); } sb.append(getName()); Object selectedValue = getValue(SELECTED_KEY); if (selectedValue instanceof Boolean selected) { sb.append("+"); } if (!enabled) { sb.append(")"); } Object nameValue = getValue(NAME); if (nameValue instanceof String name) { sb.append(" \"").append(name).append("\""); } proxy = getProxy(); if (proxy != null) { sb.append(" Proxy for: ").append(proxy.toString()); } return sb.toString(); } @Override public void itemStateChanged(ItemEvent ie) { if (isStateAction()) { setSelected(ItemEvent.SELECTED == ie.getStateChange()); } } /** * Provides parameter values to @AppAction methods. By default, parameter * values are selected based exclusively on their type: *

* * * * * * * * * * * * * * * * * * * * * * * * * *
{@code @AppAction} Method Parameter Values
Parameter TypeParameter Value
{@code ActionEvent}{@code actionEvent}
{@code javax.swing.Action}this {@code ActionX} object
{@code ActionMap}the {@code ActionMap} that contains this {@code Action}
{@code ResourceMap}the {@code ResourceMap} of the the {@code ActionMap} that contains * this {@code Action}
{@code Application}the value of {@code Application.getInstance()}
* *

* ActionX subclasses may also select values based on the value of the * {@code Action.Parameter} annotation, which is passed along as the * {@code pKey} argument to this method: *

     * @AppAction public void doAction(@AppAction.Parameter("myKey") String myParameter) {
     *    // The value of myParameter is computed by:
     *    // getActionArgument(String.class, "myKey", actionEvent)
     * }
     * 
* *

* If {@code pType} and {@code pKey} aren't recognized, this method calls * {@link #actionFailed} with an IllegalArgumentException. * * @param pType parameter type * @param pKey the value of the @AppAction.Parameter annotation * @param evt the {@link ActionEvent} that triggered this action * * @return the parameter object */ protected Object getActionArgument(Class pType, String pKey, ActionEvent evt) { Object argument = null; if (pType == ActionEvent.class) { argument = evt; } else if (pType == Action.class) { argument = this; } else if (pType == ActionMap.class) { argument = appAM; } else if (pType == ResourceMap.class) { argument = resourceMap; } else { Exception e = new IllegalArgumentException( "unrecognized @AppAction " + "method parameter"); actionFailed(evt, e); } return argument; } /* * Throw an Error because invoking Method m on the actionsObject with the * specified arguments failed. */ private Error newInvokeError(Method m, Exception e, Object... args) { String argsString = (args.length == 0) ? "" : args[0].toString(); for (int i = 1; i < args.length; i++) { argsString += ", " + args[i]; } String actionClassName = appAM.getActionsObject().getClass().getName(); String msg = String.format("%s.%s(%s) failed", actionClassName, m, argsString); return new Error(msg, e); } private IllegalArgumentException newNoSuchPropertyException(String propertyName) { String actionsClassName = appAM.getActionsClass().getName(); String msg = String.format("no property named %s in %s", propertyName, actionsClassName); return new IllegalArgumentException(msg); } /* * Forward the @AppAction class' PropertyChangeEvent e to this ActionX's * PropertyChangeListeners using actionPropertyName instead of the original * @AppAction class's property name. This method is used by * ActionMapX.ActionsPCL to forward @AppAction enabledProperty and * selectedProperty changes. */ void forwardPropertyChangeEvent(PropertyChangeEvent e, String actionPropertyName) { if ("selected".equals(actionPropertyName) && (e.getNewValue() instanceof Boolean selected)) { putValue(SELECTED_KEY, selected); } firePropertyChange(actionPropertyName, e.getOldValue(), e.getNewValue()); } /* * Log enough output for a developer to figure out what went wrong. */ private void actionFailed(ActionEvent actionEvent, Exception e) { String msg = String.format("The action %s failed", actionEvent); logger.log(Level.ERROR, msg, e); throw new Error(e); } private BackgroundTask.InputBlocker createInputBlocker(BackgroundTask backgroundTask, ActionEvent event) { Object target = event.getSource(); if (block == BackgroundTask.BlockingScope.ACTION) { target = this; } return new DefaultInputBlocker(backgroundTask, block, target, this); } private void noProxyActionPerformed(ActionEvent event) { Object taskObject = null; /* * Create the arguments array for actionMethod by calling * getActionArgument() for each parameter. */ Annotation[][] allPAnnotations = actionMethod.getParameterAnnotations(); Class[] pTypes = actionMethod.getParameterTypes(); Object[] arguments = new Object[pTypes.length]; for (int i = 0; i < pTypes.length; i++) { String pKey = null; for (Annotation pAnnotation : allPAnnotations[i]) { if (pAnnotation instanceof AppAction.Parameter param) { pKey = param.value(); break; } } arguments[i] = getActionArgument(pTypes[i], pKey, event); } /* * Call target.actionMethod(arguments). If the return value is a * BackgroundTask, then execute it. */ try { Object target = appAM.getActionsObject(); taskObject = actionMethod.invoke(target, arguments); } catch (IllegalAccessException | InvocationTargetException e) { actionFailed(event, e); } if (taskObject instanceof BackgroundTask BackgroundTask) { if (BackgroundTask.getInputBlocker() == null) { BackgroundTask.setInputBlocker(createInputBlocker(BackgroundTask, event)); } Application.getInstance().getContext().getTaskService() .execute(BackgroundTask); } } private void maybePutDescriptionValue(String key, Action proxy) { Object s = proxy.getValue(key); if (s instanceof String val) { putValue(key, val); } } private void updateProxyProperties() { Action proxy = getProxy(); if (proxy != null) { setEnabled(proxy.isEnabled()); Object s = proxy.getValue(SELECTED_KEY); setSelected((s instanceof Boolean) && (Boolean) s); maybePutDescriptionValue(Action.LONG_DESCRIPTION, proxy); maybePutDescriptionValue(Action.SHORT_DESCRIPTION, proxy); } } private void initActionProperties(ResourceMap resourceMap, String baseName) { boolean iconOrNameSet = false; String typedName = null; // Action.text -> Action.NAME, MNEMONIC_KEY, DISPLAYED_MNEMONIC_INDEX_KEY String text = resourceMap.getString(baseName + ".Action.text"); if (text != null) { MnemonicText.configure(this, text); iconOrNameSet = true; } // Action.mnemonic -> Action.MNEMONIC_KEY Integer mnemonic = resourceMap.getKeyCode(baseName + ".Action.mnemonic"); if (mnemonic != null) { putValue(MNEMONIC_KEY, mnemonic); } // Action.mnemonic -> Action.DISPLAYED_MNEMONIC_INDEX_KEY Integer index = resourceMap.getInteger(baseName + ".Action.displayedMnemonicIndex"); if (index != null) { putValue(DISPLAYED_MNEMONIC_INDEX_KEY, index); } // Action.accelerator -> Action.ACCELERATOR_KEY KeyStroke key = resourceMap.getKeyStroke(baseName + ".Action.accelerator"); if (key != null) { putValue(ACCELERATOR_KEY, key); } // Action.icon -> Action.LARGE_ICON_KEY, SMALL_ICON Icon icon = resourceMap.getIcon(baseName + ".Action.icon"); if (icon != null) { putValue(SMALL_ICON, icon); putValue(LARGE_ICON_KEY, icon); iconOrNameSet = true; } // Action.smallIcon -> Action.SMALL_ICON Icon smallIcon = resourceMap.getIcon(baseName + ".Action.smallIcon"); if (smallIcon != null) { putValue(SMALL_ICON, smallIcon); iconOrNameSet = true; } // Action.largeIcon -> Action.LARGE_ICON_KEY Icon largeIcon = resourceMap.getIcon(baseName + ".Action.largeIcon"); if (largeIcon != null) { putValue(LARGE_ICON_KEY, largeIcon); iconOrNameSet = true; } // Action.shortDescription -> Action.SHORT_DESCRIPTION putValue(SHORT_DESCRIPTION, resourceMap.getString(baseName + ".Action.shortDescription")); // Action.longDescription -> Action.LONG_DESCRIPTION putValue(LONG_DESCRIPTION, resourceMap.getString(baseName + ".Action.longDescription")); // Action.command -> Action.ACTION_COMMAND_KEY putValue(ACTION_COMMAND_KEY, resourceMap.getString(baseName + ".Action.command")); // If no visual was defined for this action, i.e., no text and no icon, //+ then we default to Action.NAME. if (!iconOrNameSet) { putValue(NAME, actionName); } } private String propertyMethodName(String prefix, String propertyName) { return prefix + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); } private Method propertyGetMethod(String propertyName) { String[] getMethodNames = { propertyMethodName("is", propertyName), propertyMethodName("get", propertyName) }; Class actionsClass = appAM.getActionsClass(); for (String name : getMethodNames) { try { return actionsClass.getMethod(name); } catch (NoSuchMethodException ignore) { // Actively ignoring this exception. } } return null; } private Method propertySetMethod(String propertyName, Class type) { Class actionsClass = appAM.getActionsClass(); try { return actionsClass.getMethod( propertyMethodName("set", propertyName), type); } catch (NoSuchMethodException ignore) { // Actively ignoring this exception. } return null; } // ------------------------------------ Private Static Field Declarations -- private static final Logger logger = System.getLogger(ActionX.class. getName()); // ---------------------------------- Private Instance Field Declarations -- private final ActionMapX appAM; private final ResourceMap resourceMap; private final String actionName; private final Method actionMethod; private final String enabledProperty; private final Method isEnabledMethod; private final Method setEnabledMethod; private final String selectedProperty; private final Method isSelectedMethod; private final Method setSelectedMethod; private final BackgroundTask.BlockingScope block; private Action proxy = null; private Object proxySource = null; private PropertyChangeListener proxyPCL = null; // ------------------------------------------- Private Class Declarations -- /* * This PCL is added to the proxy action, i.e., getProxy(). We track the * following properties of the proxy action we are bound to: enabled, * selected, longDescription, and shortDescription. We only mirror the * description properties if they are non-null. */ private class ProxyPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent pce) { if (pce.getPropertyName() != null) { switch (pce.getPropertyName()) { case "enabled", "selected", Action.SHORT_DESCRIPTION, Action.LONG_DESCRIPTION -> { updateProxyProperties(); } } } } } // ----------------------------- Public Static Property Keys Declarations -- /* * The following keys are to enhance ActionX over the JDK's AbstractAction * and Action interface. These keys allow for action state * (selected/deselected) to allow for JCheckBoxMenuItems/JToggleButtons, * etc. The action state also allows for adding into a ButtonGroup so that * the state actions may be grouped together, thereby only allowing one * action to be selected within the group. This allows for * JRadioButtonMenuItems. */ /** * The key for the button group */ public static final String GROUP = "__Group__"; /** * The key for the flag which indicates that this is a state action. */ public static final String IS_STATE = "__State__"; /* * The following keys are to enhance ActionX to allow for enabled/disabled * actions. The enabled state of the action will be tracked by the * PropertyChangeSupport of the application. */ /** * The key for the name of the action's enabled property. */ public static final String ENABLED_KEY = "__Enabled__"; /* * The remaining keys are specific to the application framework and allow * for automatic actions system generation. These include properties such as * the action's ResourceMap, whether to install the action in a menu, * toolbar, and/or BackgroundTask pane, etc. */ /** * The key for storing the {@link ResourceMap} that defines the action's * properties. */ public static final String RESOURCE_MAP_KEY = "__ResourceMap__"; /** * The key for storing the action's menu path, i.e., file or view/toolbars. */ public static final String MENU_BASE_NAME = "__menuBaseName__"; /** * The index position of the menu in the menu bar. If this action belongs in * a submenu, this is the submenu's index position in its parent menu. */ public static final String MENU_INDEX = "__MenuIndex__"; /** * The key for storing the action's hint as to the order in which it should * be installed in a menu */ public static final String MENU_ACTION_INDEX = "__MenuActionIndex__"; /** * The key for determining whether a separator should be inserted prior to * the action in a menu. */ public static final String MENU_PRE_SEPARATOR = "__MenuSeparatorBefore__"; /** * The key for determining whether a separator should be inserted after the * action in a menu. */ public static final String MENU_POST_SEPARATOR = "__MenuSeparatorAfter__"; /** * The key for determining whether the action should be installed into a * {@link JToolBar}. */ public static final String SHOW_ON_TOOLBAR = "__ShowOnToolbar__"; /** * The index position of the toolbar within the application's toolbars area. */ public static final String TOOLBAR_INDEX = "__ToolBarIndex__"; /** * The key for the name of the toolbar in which the action should be * installed. */ public static final String TOOLBAR_NAME = "__toolbarBaseName__"; /** * The key for the action's hint as to the order in which it should be * installed into the toolbar. */ public static final String TOOLBAR_ACTION_INDEX = "__ToolBarActionIndex__"; /** * The key for determining whether the action should have a separator * installed before the action in a toolbar. */ public static final String TOOLBAR_PRE_SEPARATOR = "__ToolBarSeparatorBefore__"; /** * The key for determining whether a separator should be inserted after the * action in a toolbar. */ public static final String TOOLBAR_POST_SEPARATOR = "__ToolBarSeparatorAfter__"; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy