
com.pekinsoft.framework.TargetManager Maven / Gradle / Ivy
/*
* 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 : TargetManager.java
* Author : Sean Carrick
* Created : Jul 14, 2024
* Modified : Jul 14, 2024
*
* Purpose: See class JavaDoc for explanation
*
* Revision History:
*
* WHEN BY REASON
* ------------ ------------------- -----------------------------------------
* Jul 14, 2024 Sean Carrick Initial creation.
* *****************************************************************************
*/
package com.pekinsoft.framework;
import com.pekinsoft.api.Targetable;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
/**
* The {@code TargetManager} simply manages a single {@link Targetable} object
* to which action commands are passed. The {@code target} property of the
* {@code TargetManager} may be {@code null} if there is not currently an object
* that implements {@code Targetable} as the current permanent focus
* owner, nor do its parents implement the {@code Targetable} interface.
*
* The {@code TargetManager} is used by an application in situations where there
* are multiple windows and/or distinct component hierarchies that all share a
* common action, such as docked panels that attach to a database table and
* allow for adding, updating and deleting records. Instead of the application
* attempting to swap out its menu items and toolbar buttons based upon the
* current focus owner, it can add a single application-level action to the
* menus and toolbars that simply forward the action command to the current
* focus owner, if that component implements the {@code Targetable} interface.
*
* The {@code TargetManager} tracks the current permanent focus owner
* and updates its {@code target} property based on the component that currently
* holds the focus within an application. If the current focus owner does not
* implement the {@code Targetable} interface, the {@code target} property is
* set to {@code null}. If the current focus owner (or one of its parents) does
* implement the {@code Targetable} interface, then that component is set as the
* current {@code target}.
*
* An application that uses the {@code TargetManager} should track whether the
* {@code target} property has a valid {@code Targetable} set, and update the
* {@code enabled} property of the menu items and toolbar buttons based on this
* property's value. In other words, if {@code target == null}, the menu items
* and toolbar buttons that rely on a target should be disabled until a valid
* {@code target} exists.
*
* The {@code TargetManager} is a singleton class, so only has the one instance
* available for the application.
*
* @author Sean Carrick <sean at pekinsoft dot com>
*
* @version 1.0
* @since 2.0
*/
public class TargetManager extends AbstractBean implements
PropertyChangeListener {
/**
* Retrieves the {@code TargetManager} instance.
*
* @return the {@code TargetManager} singleton
*/
public static TargetManager getInstance() {
if (instance == null) {
instance = new TargetManager();
}
return instance;
}
/**
* Constructs a new {@code TargetManager} instance. This constructor may be
* called by subclasses, if the {@code TargetManager} class is extended.
*/
protected TargetManager() {
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener(this);
}
/**
* Determines whether a valid {@link Targetable} instance is available to
* perform an action.
*
* @return {@code true} if a {@code Targetable} instance is available
*/
public boolean isTargetAvailable() {
return target != null;
}
/**
* Retrieves the current {@code target} property value. This value will be
* either a valid {@link Targetable} instance or {@code null}.
*
* @return the current {@code target} or {@code null}
*/
public Targetable getTarget() {
return target;
}
/**
* Sets the value of the {@code target} property to the specified
* {@link Targetable} implementation. This method accepts {@code null} for
* the new value.
*
* The {@code TargetManager} object listens to the focus subsystem for
* changes in the permanent focus owner and sets the {@code target} property
* based upon those changes. If the {@code permanentFocusOwner} is not an
* implementation of the {@code Targetable} interface, then its parent
* hierarchy is examined to see if a {@code Targetable} instance exists in
* it. If a {@code Targetable} instance is found, it is set as the value of
* the {@code target} property, otherwise, {@code target} is set to
* {@code null}.
*
* While the {@code TargetManager} listens to the focus subsystem for the
* changes in the permanent focus owner, an application may set the
* {@code target} property manually at any time.
*
* This is a bound property.
*
* @param target the new {@code Targetable} implementation or {@code null}
*/
public void setTarget(Targetable target) {
Targetable old = getTarget();
this.target = target;
firePropertyChange("target", old, getTarget());
System.out.println("TARGETMANAGER->TARGET = " + (target == null
? "NULL_TARGET" : target.getClass().getSimpleName()));
}
/**
* A convenience method that is shorthand for calling:{@snippet lang="java":
* TargetManager.getInstance().getTarget().doCommand(actionCommand, param);
* }
*
* This method checks to see if the {@code target} property is set to a
* valid {@code Targetable} instance, and if that instance has the action
* command required. If both conditions are met, the {@code target}'s
* {@link Targetable#doCommand(String, EventObject) doCommand} method is
* called and its return value is passed back to the caller.
*
* If the {@code target} property is not set to a valid {@code Targetable}
* instance, or the {@code target} does not support the specified action
* command, {@code false} is returned.
*
* @param actionCommand the {@link javax.swing.Action#ACTION_COMMAND_KEY} to
* be fired
* @param param the {@link EventObject} that triggered the action
*
* @return {@code true} upon successfully executing the action;
* {@code false} if the action command had no supporting
* {@code target}
*/
public boolean doCommand(String actionCommand, EventObject param) {
if (getTarget() != null) {
if (getTarget().hasCommand(actionCommand)) {
return getTarget().doCommand(actionCommand, param);
}
}
return false;
}
/**
* A convenience method that is shorthand for calling:{@snippet lang="java":
* TargetManager.getTarget().hasCommand(actionCommand);
* }
*
* This method checks to see if the {@code target} property is set to a
* valid {@link Targetable} instance, and if that instance supports the
* action command required. The value of this call is returned to the
* calling object.
*
* If the {@code target} is {@code null}, {@code false} is returned.
*
* @param actionCommand the {@link javax.swing.Action#ACTION_COMMAND_KEY} to
* be checked
*
* @return {@code true} if the {@code target} is not {@code null} and
* supports the specified action command; {@code false} otherwise
*/
public boolean hasActionCommand(String actionCommand) {
if (getTarget() != null) {
return getTarget().hasCommand(actionCommand);
}
return false;
}
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName() != null) {
switch (pce.getPropertyName()) {
case "permanentFocusOwner" -> {
Component component = (Component) pce.getNewValue();
System.out.println("Permanent Focus Owner: "
+ (component != null
? component.getClass().getSimpleName()
: "NULL FOCUS OWNER"));
if (component != null) {
if (component instanceof Targetable target) {
setTarget(target);
} else if (component.getParent() != null) {
Component parent = component.getParent();
while (parent != null && !parent.getClass().equals(
Object.class)) {
if (parent instanceof Targetable target) {
setTarget(target);
break;
}
parent = parent.getParent();
}
} else {
setTarget(null);
}
} else {
setTarget(null);
}
}
}
}
}
private static TargetManager instance = null;
private Targetable target = null;
}