com.pekinsoft.framework.MenuGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of application-framework-api Show documentation
Show all versions of application-framework-api Show documentation
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 : MenuGenerator.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 java.beans.PropertyChangeListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.*;
import javax.swing.*;
/**
* @status TESTED: Good to Go
*
* @author Sean Carrick <sean at pekinsoft dot com>
*
* @version 2.4
* @since 1.5
*/
class MenuGenerator {
static JMenuBar createMenuSystem(ApplicationContext ctx, List actionMaps) {
context = ctx;
/*
* First, get all instances of ActionX objects from the ActionMaps into
* a list we'll be able to use throughout.
*/
List allActions = getAllActions(actionMaps);
/*
* Next, we need to get all TOP-LEVEL menus by name and position, keyed
* once by menu name, and again by index position hint. We will store
* the map of menu names keyed to position in a class-level field.
*/
Map topLevelMenusByName = getTopLevelMenusByName(allActions);
/*
* Then, we need to sort the ActionX instances by their TOP-LEVEL menu
* name. We will then be able to create the submenus, if any, in the
* proper position within the menus.
*/
Map> actionsByMenuName = getActionsByMenuName(allActions);
/*
* Lastly, we need to create the menus into a map, keyed on their
* positions within the menubar.
*/
Map menusMap = createAllMenus(actionsByMenuName);
/*
* Finally, return the application's generated menu bar.
*/
return createMenuBar(menusMap);
}
private static JMenuBar createMenuBar(Map menusMap) {
List indices = new ArrayList<>(menusMap.keySet());
Collections.sort(indices);
JMenuBar menuBar = new JMenuBar();
menuBar.setName("menuBar");
for (Byte index : indices) {
menuBar.add(menusMap.get(index));
}
return menuBar;
}
private static Map createAllMenus(Map> actionsByMenuName) {
Map menusMap = new HashMap<>();
List menuIndices = new ArrayList<>(topLevelMenusByPosition.keySet());
Collections.sort(menuIndices);
for (Byte index : menuIndices) {
String menuName = topLevelMenusByPosition.get(index);
List actions = actionsByMenuName.get(menuName);
List orderedActions = orderActionsList(actions);
menusMap.put(index, createMenuFromActions(menuName, orderedActions));
}
return menusMap;
}
private static JMenu createMenuFromActions(String menuName, List actions) {
JMenu menu = createMenu(menuName, actions);
menu.setName(menuName);
return menu;
}
private static List orderActionsList(List actions) {
Map actionsToOrder = new HashMap<>();
List indices = new ArrayList<>();
List orderedActions = new ArrayList<>();
for (ActionX action : actions) {
Byte idx = action.getMenuActionIndex();
if (idx == null || idx == -1) { // One of cut, copy, paste, or delete?
if (action.getName() != null) switch (action.getName()) {
case "cut" -> idx = -128;
case "copy" -> idx = -127;
case "paste" -> idx = -126;
case "delete" -> idx = 127;
}
action.putValue(ActionX.MENU_ACTION_INDEX, idx);
}
if (!actionsToOrder.containsKey(idx)) {
actionsToOrder.put(idx, action);
}
}
indices.addAll(actionsToOrder.keySet());
for (Byte idx : indices) {
orderedActions.add(actionsToOrder.get(idx));
}
Collections.sort(orderedActions, (ActionX a1, ActionX a2) -> {
byte a1Idx = a1.getMenuActionIndex();
byte a2Idx = a2.getMenuActionIndex();
return Byte.compare(a1Idx, a2Idx);
});
return orderedActions;
}
private static Map>> generateMenuMap(Map> actionsByMenuName) {
Map>> menuMap = new HashMap<>();
for (String menuName : actionsByMenuName.keySet()) {
for (ActionX action : actionsByMenuName.get(menuName)) {
ResourceMap rm = action.getResourceMap();
String baseName = action.getMenuBaseName();
if (baseName.contains("/")) {
String childMenu = getChildMenuName(baseName);
while (childMenu.contains("/")) {
}
}
}
}
return menuMap;
}
private static Map> getActionsByMenuName(List allActions) {
Map> actionsByMenuName = new HashMap<>();
for (ActionX action : allActions) {
String menuBaseName = action.getMenuBaseName();
String menuName = getParentMenuName(menuBaseName);
if (!actionsByMenuName.containsKey(menuName)) {
actionsByMenuName.put(menuName, new ArrayList<>());
}
actionsByMenuName.get(menuName).add(action);
}
return actionsByMenuName;
}
// Working properly: returns a Map that contains all actions by top-level menu.
private static Map getTopLevelMenusByName(List allActions) {
Map topLevelMenusByName = new HashMap<>();
for (ActionX action : allActions) {
ResourceMap rm = action.getResourceMap();
String menuBaseName = action.getMenuBaseName();
if (menuBaseName == null && (action.getName().equals("cut")
|| action.getName().equals("copy")
|| action.getName().equals("paste")
|| action.getName().equals("delete"))) {
menuBaseName = "edit";
action.putValue(ActionX.MENU_BASE_NAME, menuBaseName);
}
String menuName = getParentMenuName(menuBaseName);
Byte index = rm.getByte(menuBaseName + ".menu.index");
if (index != null && !topLevelMenusByName.containsKey(menuName)) {
topLevelMenusByName.put(menuName, index);
topLevelMenusByPosition.put(index, menuName);
}
}
return topLevelMenusByName;
}
private static List getAllActions(List actionMaps) {
List allActions = new ArrayList<>();
for (ActionMap map : actionMaps) {
if (map.keys() != null) {
for (Object key : map.keys()) {
Action a = map.get(key);
if (a instanceof ActionX action) {
allActions.add(action);
}
}
} else if (map.allKeys() != null) {
try {
for (Object key : map.allKeys()) {
Action a = map.get(key);
if (a instanceof ActionX action) {
allActions.add(action);
}
}
} catch (Exception e) {
String msg = String.format("Caught exeption \"%1$s\" in "
+ "MenuGenerator.getAllActions with ActionMap."
+ "allKeys().length=%2$d. Message: %3$s",
e.getClass().getSimpleName(), map.allKeys().length,
e.getMessage());
logger.log(Level.ERROR, msg);
throw e;
}
}
}
return allActions;
}
private static JMenuItem createMenuItems(List actions, JMenu menu) {
Map itemsByPosition = new HashMap<>();
JMenuItem item = null;
String menuName = menu.getName();
for (ActionX action : actions) {
ResourceMap rm = action.getResourceMap();
String actionMenuName = rm.getString(action.getMenuBaseName() + ".menu.name");
if (!menuName.equals(actionMenuName)) {
String subMenuName = getChildMenuName(action.getMenuBaseName());
JMenu subMenu = new JMenu();
subMenu.setName(subMenuName);
List subActions = new ArrayList<>();
for (ActionX ax : actions) {
ResourceMap r = ax.getResourceMap();
String mn = r.getString(ax.getMenuBaseName() + ".menu.name");
if (mn.equals(actionMenuName)) {
subActions.add(ax);
}
}
item = createMenuItems(subActions, subMenu);
byte index = action.getMenuIndex();
itemsByPosition.put(index, item);
} else {
item = createMenuItem(action, menu);
byte index = action.getMenuActionIndex();
item.addMouseListener(new ActionHelpProvider(context));
itemsByPosition.put(index, item);
}
}
// Sort the map keys.
List keys = new ArrayList<>(itemsByPosition.keySet());
Collections.sort(keys);
for (Byte key : keys) {
menu.add(itemsByPosition.get(key));
}
return menu;
}
private static JMenuItem createMenuItem(ActionX action, JMenu menu) {
JMenuItem item = null;
if (action.isStateAction()) {
String groupId = action.getGroup();
// Store the group name for the ToolbarGenerator.
groupName = action.getGroup();
if (groupId != null && !groupId.isBlank() && !groupId.isEmpty()) {
int hashCode = groupId.hashCode();
hashCode ^= menu.hashCode();
ButtonGroup group = buttonGroups.get(hashCode);
if (group == null) {
group = new ButtonGroup();
buttonGroups.put(hashCode, group);
}
item = createRadioButtonMenuItem(group, action);
} else {
item = createCheckBoxMenuItem(action);
}
} else {
item = new JMenuItem(action);
}
String name = action.getActionCommand();
if (name == null) {
name = action.getName();
}
item.setName(name + "MenuItem");
item.addMouseListener(new ActionHelpProvider(context));
logger.log(Level.DEBUG, "Adding ActionX \"{0}\" to menu \"{1}\"", action,
menu.getName());
action.getResourceMap().injectComponent(item);
return item;
}
private static JMenu createMenu(String menuName, List actions) {
JMenu menu = new JMenu();
menu.setName(menuName);
boolean sepAdded = false;
for (ActionX a : actions) {
boolean before = a.isMenuSeparatorBefore();
boolean after = a.isMenuSeparatorAfter();
String menuPath = getChildMenuName(a.getMenuBaseName());
if (menuPath.equals(menuName)) {
if (!sepAdded && before) {
menu.addSeparator();
}
menu.add(createMenuItem(a, menu));
sepAdded = false;
if (after) {
menu.addSeparator();
sepAdded = true;
}
} else {
String childMenuName = getChildMenuName(a.getMenuBaseName());
while (childMenuName.contains("/")) {
childMenuName = getChildMenuName(childMenuName);
}
menu.add(createMenuFromActions(childMenuName, actions));
}
}
if (menu.getMenuComponent(0) instanceof JSeparator) {
menu.remove(0);
}
int menuCompCount = menu.getMenuComponentCount();
if (menuCompCount > 0) {
if (menu.getMenuComponent(menuCompCount - 1) instanceof JSeparator) {
menu.remove(menuCompCount - 1);
}
}
context.getResourceMap().injectComponents(menu);
return menu;
}
private static JCheckBoxMenuItem createCheckBoxMenuItem(ActionX a) {
JCheckBoxMenuItem mi = new JCheckBoxMenuItem();
configureSelectableButton(mi, a, null);
return mi;
}
private static JRadioButtonMenuItem createRadioButtonMenuItem(ButtonGroup group, ActionX a) {
JRadioButtonMenuItem mi = new JRadioButtonMenuItem();
mi.setName(a.getName() + "MenuItem");
configureSelectableButton(mi, a, group);
return mi;
}
private static void configureMenuItemFromActionProperties(JMenuItem mi, ActionX a) {
if (a.getValue(ActionX.SHORT_DESCRIPTION) == null) {
mi.setToolTipText(a.getName());
}
if (a.getValue(ActionX.SMALL_ICON) != null) {
mi.setIcon((Icon) a.getValue(ActionX.SMALL_ICON));
}
mi.addMouseListener(new ActionHelpProvider(context));
}
private static void configureSelectableButton(AbstractButton button, ActionX a, ButtonGroup group) {
if ((a != null) && !a.isStateAction()) {
throw new IllegalArgumentException("The Action must be a stateAction");
}
if (button.getAction() == a) {
return;
}
Action oldAction = button.getAction();
if (oldAction instanceof ActionX ax) {
button.removeItemListener(ax);
PropertyChangeListener[] listeners = ax.getPropertyChangeListeners();
for (PropertyChangeListener l : listeners) {
if (l instanceof ToggleActionPropertyChangeListener togglePCL) {
if (togglePCL.isToggling(button)) {
ax.removePropertyChangeListener(togglePCL);
}
}
}
}
button.setAction(a);
if (group != null) {
group.add(button);
}
if (a != null) {
button.addItemListener(a);
button.setSelected(a.isSelected());
new ToggleActionPropertyChangeListener(a, button);
}
}
private static ButtonGroup getGroup(String groupId, JComponent container) {
int hashCode = groupId.hashCode();
if (container != null) {
hashCode ^= container.hashCode();
}
ButtonGroup group = buttonGroups.get(hashCode);
if (group == null) {
group = new ButtonGroup();
buttonGroups.put(hashCode, group);
}
return group;
}
private static String getParentMenuName(String menuPath) {
int index = menuPath.indexOf('/');
return index == -1
? context.getResourceMap().getString(menuPath + ".menu.name")
: context.getResourceMap().getString(menuPath.substring(0, index) + ".menu.name");
}
private static String getChildMenuName(String menuPath) {
int index = menuPath.indexOf('/');
return index == -1
? context.getResourceMap().getString(menuPath + ".menu.name")
: context.getResourceMap().getString(menuPath.substring(index + 1) + ".menu.name");
}
private MenuGenerator () {
// No instantiation necessary.
}
static String groupName = null;
private static final Logger logger = System.getLogger(MenuGenerator.class.getName());
private static ApplicationContext context;
private static final Map> actionsByMenuPath
= new HashMap<>();
private static final Map> menusByName
= new HashMap<>();
private static final Map menuPositionsByName
= new HashMap<>();
private static final Map menusMap = new HashMap<>();
private static final Map topLevelMenusByPosition
= new HashMap<>();
private static final Map buttonGroups = new HashMap<>();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy