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

com.pekinsoft.wizard.api.WizardSideBar 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) 2022 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    :   ntos
 *  Class      :   WizardSideBar.java
 *  Author     :   Sean Carrick
 *  Created    :   Dec 16, 2022
 *  Modified   :   Dec 16, 2022
 *  
 *  Purpose: See class JavaDoc for explanation
 *  
 *  Revision History:
 *  
 *  WHEN          BY                   REASON
 *  ------------  -------------------  -----------------------------------------
 *  Dec 16, 2022  Sean Carrick         Initial creation.
 * *****************************************************************************
 */
package com.pekinsoft.wizard.api;

import com.pekinsoft.utils.ColorUtils;
import com.pekinsoft.wizard.api.displayer.InstructionsPanel;
import com.pekinsoft.wizard.spi.Wizard;
import com.pekinsoft.wizard.spi.WizardObserver;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.IllegalComponentStateException;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Locale;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
import javax.accessibility.AccessibleText;
import javax.imageio.ImageIO;
import javax.swing.CellRendererPane;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import com.pekinsoft.framework.Application;
import com.pekinsoft.framework.ResourceMap;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;

/**
 * The {@code WizardSideBar} class is an extension of the {@link
 * javax.swing.JPanel JPanel} class that provides for painting of a background
 * image and the step descriptions of a Wizard. Instances of this class should
 * be added to a Wizard dialog in the {@link java.awt.BorderLayout#WEST
 * BorderLayout.WEST} position.
 * 

* The background image on the sidebar panel can be changed in three ways: *

    *
  1. Place a {@link java.awt.image.BufferedImage BufferedImage} into the * {@link javax.swing.UIManager UIManager} with the property key * "wizard.sidebar.background". *
  2. Create a {@link java.lang.System System} property named * "wizard.sidebar.background" with the value the path to the image, which may be a * path to an image resource in your JAr file. *
  3. As a last resort, you can try placing a resource entry named * "wizard.sidebar.background" in your calling class' {@link * com.pekinsoft.framework.ResourceMap ResourceMap} properties file. *
*

* If a {@code BufferedImage} or its path is not found in any of these places, * the default background image will be used. The default image is designed in * such a way that it will still look nice, even if it is skewed by the size of * the sidebar panel. This image is light-colored, yellow and gold, with hints * of red and blue in it. If you desire to have a more customized sidebar image, * we provide that ability as described below. *

* To customize the sidebar image, you can place a custom wizard image in the * same places as described above (i.e., the {@code UIManager}, {@code system} * properties, or {@code ResourceMap} for you class), under the property key * "wizard.sidebar.image". The image pointed to should be no larger than 180x180 * to look nice on the sidebar. This image will be painted only at its size and * will never become skewed due to the sidebar being larger or smaller. This * image is painted to the bottom-right corner of the sidebar, with the {@code x} * location being the width of the sidebar minus the width of the image. The * {@code y} location will be the height of the sidebar minus the height of the * image. The supplied image should have a transparent background, with portable * network graphics (PNG) files working the best. * * @author Sean Carrick */ public class WizardSideBar extends javax.swing.JPanel implements PropertyChangeListener, WizardObserver, InstructionsPanel { public static final String PROP_WIZARD_SIDEBAR_IMAGE = "wizard.sidebar.image"; private static final long serialVersionUID = 4441673040479415503L; private static final Logger logger = System.getLogger(WizardSideBar.class.getName()); private static final int MARGIN = 5; private static final String PROP_WIZARD_SIDEBAR_BACKGROUND = "wizard.sidebar.background"; private static final ResourceMap resourceMap = Application .getInstance() .getContext() .getResourceMap(WizardSideBar.class); private final Wizard wizard; private final BufferedImage background; private final BufferedImage img; private int currentStep = 0; private boolean inSummaryPage = false; /** * Constructs a new {@code WizardSideBar} instance with the given * properties. * * @param wizard the Wizard in which this side bar is being displayed */ public WizardSideBar(Wizard wizard) { this(null, wizard); Font f = UIManager.getFont ("Tree.font"); //NOI18N if (f != null) { setFont (f); } } /** * Get the wizard this panel is monitoring. * @return */ protected final Wizard getWizard() { return wizard; } public final Container getComponent() { return this; } /** * Overridden to start listening to the wizard when added to a container */ @Override public void addNotify() { super.addNotify(); wizard.addWizardObserver (this); } /** * Overridden to stop listening to the wizard when removed from a container */ @Override public void removeNotify() { wizard.removeWizardObserver (this); super.removeNotify(); } public WizardSideBar(BufferedImage img, Wizard wizard) { if (img == null) { // In the event of classloader issues, also have a way to get the //+ image from the UIManager - slightly more portable for large //+ applications. img = (BufferedImage) UIManager.get(PROP_WIZARD_SIDEBAR_IMAGE); } String imgStr = System.getProperty(PROP_WIZARD_SIDEBAR_IMAGE); // Image has not been loaded and user wishes to supply their own image. if (img == null && imgStr != null) { // Get a URL, works for JArs. ClassLoader cl = this.getClass().getClassLoader(); URL url = cl.getResource(imgStr); if (url == null) { // Let's try getting it from the Wizard's classloader. url = wizard.getClass().getClassLoader().getResource(imgStr); } if (url == null) { // One last attempt: Let's see if we can get it from the wizard's //+ ResourceMap: try { url = new URL(Application .getInstance() .getContext() .getResourceMap(wizard.getClass()) .getString(PROP_WIZARD_SIDEBAR_IMAGE)); } catch (MalformedURLException mue) { } } // successfully parsed the URL if (url != null) { try { img = ImageIO.read(url); } catch (IOException ioe) { logger.log(Level.WARNING, "Could not load wizard image {0}", ioe, ioe.getMessage()); // NOI18N System.setProperty(PROP_WIZARD_SIDEBAR_IMAGE, null); // NOI18N img = null; // Error loading image, set to {@code null} to not use. } } else { logger.log(Level.WARNING, "Bad URL for wizard image ", imgStr); System.setProperty(PROP_WIZARD_SIDEBAR_IMAGE, null); img = null; } } BufferedImage background = null; if (background == null) { // In the event of classloader issues, also have a way to get the //+ image from the UIManager - slightly more portable for large //+ applications. background = (BufferedImage) UIManager.get(PROP_WIZARD_SIDEBAR_BACKGROUND); } String bgStr = System.getProperty(PROP_WIZARD_SIDEBAR_BACKGROUND); // Image has not been loaded and user wishes to supply their own image. if (background == null && bgStr != null) { // Get a URL, works for JArs. ClassLoader cl = this.getClass().getClassLoader(); URL url = cl.getResource(bgStr); if (url == null) { // Let's try getting it from the Wizard's classloader. url = wizard.getClass().getClassLoader().getResource(bgStr); } if (url == null) { // One last attempt: Let's see if we can get it from the wizard's //+ ResourceMap: try { url = new URL(Application .getInstance() .getContext() .getResourceMap(wizard.getClass()) .getString(PROP_WIZARD_SIDEBAR_BACKGROUND)); } catch (MalformedURLException mue) { } } // successfully parsed the URL if (url != null) { try { background = ImageIO.read(url); } catch (IOException ioe) { logger.log(Level.WARNING, "Could not load wizard image {0}", ioe, ioe.getMessage()); // NOI18N System.setProperty(PROP_WIZARD_SIDEBAR_BACKGROUND, null); // NOI18N background = null; // Error loading image, set to {@code null} to not use. } } else { logger.log(Level.WARNING, "Bad URL for wizard image ", bgStr); System.setProperty(PROP_WIZARD_SIDEBAR_BACKGROUND, null); background = null; } } this.img = img; this.background = background; this.wizard = wizard; initComponents(); } @Override public boolean isOpaque() { return background != null; } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName() != null) { switch (evt.getPropertyName()) { case "stepsChanged": currentStep = (int) evt.getNewValue(); break; case "inSummaryPage": break; } } repaint(); } public void setInSummaryPage(boolean inSummaryPage) { boolean oldValue = this.inSummaryPage; this.inSummaryPage = inSummaryPage; firePropertyChange("inSummaryPage", oldValue, this.inSummaryPage); repaint(); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { setMinimumSize(new java.awt.Dimension(198, 361)); setPreferredSize(new java.awt.Dimension(198, 361)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 198, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 361, Short.MAX_VALUE) ); }// //GEN-END:initComponents private BufferedImage getImage() { return img; } private BufferedImage getBgImage() { return background; } /** * Paints the background image for this component, or fills the background * with a color if no image present. * * @param g A Graphic2D into which we can paint * @param x The x coordinate of the area that should contain the image * @param y The y coordinate of the area that should contain the image * @param w The width of the area that should contain the image * @param h The height of the area that should contain the image */ protected void paintBackground(Graphics2D g, int x, int y, int w, int h) { BufferedImage image = getBgImage(); if (image != null) { g.drawImage(image, x, y, w, h, this); } else { Color c = g.getColor(); g.setColor(Color.WHITE); g.fillRect(x, y, w, h); g.setColor(c); } } /** * Paints the custom image for this component, unless no image is present. * * @param g a {@code Graphics2D} object into which we can paint */ protected void paintImage(Graphics2D g) { BufferedImage image = getImage(); int x = 0; int y = 0; int w = 0; int h = 0; if (image != null) { x = getWidth() - image.getWidth(); y = getHeight() - image.getHeight(); w = image.getWidth(); h = image.getHeight(); } if (image != null) { g.drawImage(image, x, y, w, h, this); } } String[] steps = new String[0]; @Override public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; Font f = getFont() != null ? getFont() : UIManager.getFont("controlFont"); //NOI18N FontMetrics fm = g.getFontMetrics (f); Insets ins = getInsets(); int dx = ins.left; int dy = ins.top; int w = getWidth() - (ins. left + ins.right); int hh = getHeight() - (ins.top + ins.bottom); paintBackground(g2d, dx, dy, w, hh); paintImage(g2d); color = g2d.getColor(); g2d.setColor(getGoodColor()); String currentStep = wizard.getCurrentStep(); if (!inSummaryPage) { //Don't fetch step list if in summary page, there will //only be the base ones steps = wizard.getAllSteps(); } String steps[] = this.steps; if (inSummaryPage) { String summaryStep = resourceMap.getString("sideBar.title"); //NOI18N String[] nue = new String[steps.length + 1]; System.arraycopy(steps, 0, nue, 0, steps.length); nue[nue.length - 1] = summaryStep; steps = nue; } int y = fm.getMaxAscent() + ins.top + MARGIN; int x = ins.left + MARGIN; int h = fm.getMaxAscent() + fm.getMaxDescent() + 3; Font boldFont = f.deriveFont (Font.BOLD); g.setFont (boldFont); g.drawString (resourceMap.getString("sideBar.title"), x, y); //NOI18N int underlineY = ins.top + MARGIN + fm.getAscent() + 3; g.drawLine (x, underlineY, x + (getWidth() - (x + ins.left + MARGIN)), underlineY); int bottom = getComponentCount() == 0 ? getHeight() - getInsets().bottom : getHeight() - getInsets().bottom - getComponents()[0].getPreferredSize().height; y += h + 10; int first = 0; int stop = steps.length; String elipsis = resourceMap.getString("ellipsis"); //NOI18N boolean wontFit = y + (h * (steps.length)) > getHeight(); if (wontFit) { //try to center the current step int availHeight = bottom - y; int willFit = availHeight / h; int currStepIndex = Arrays.asList (steps).indexOf(currentStep); int rangeStart = Math.max (0, currStepIndex - (willFit / 2)); int rangeEnd = Math.min (rangeStart + willFit, steps.length); if (rangeStart + willFit > steps.length) { //Don't scroll off if there's room rangeStart = steps.length - willFit; rangeEnd = steps.length; } steps = (String[]) steps.clone(); if (rangeStart != 0) { steps[rangeStart] = elipsis; first = rangeStart; } if (rangeEnd != steps.length && rangeEnd > 0) { steps[rangeEnd - 1] = elipsis; stop = rangeEnd; } } /* if (wontFit) { int currStepIndex = Arrays.asList (steps).indexOf(currentStep); if (currStepIndex != -1) { //shouldn't happen steps = (String[]) steps.clone(); first = Math.max (0, currStepIndex - 2); if (first != 0) { if (y + ((currStepIndex - first) * h) > getHeight()) { //Best effort to keep current step on screen first++; } if (first != currStepIndex) { steps[first] = elipsis; } } } } if (y + ((stop - first) * h) > bottom) { int avail = bottom - y; int willFit = avail / h; int last = first + willFit - 1; if (last < steps.length - 1) { steps[last] = elipsis; stop = last + 1; } } */ g.setFont (getFont()); g.setColor (getGoodColor()); for (int i=first; i < stop; i++) { boolean isUndetermined = Wizard.UNDETERMINED_STEP.equals(steps[i]); boolean canOnlyFinish = wizard.getForwardNavigationMode() == Wizard.MODE_CAN_FINISH; if (isUndetermined && canOnlyFinish) { break; } String curr; if (!elipsis.equals(steps[i])) { if (inSummaryPage && i == this.steps.length) { curr = (i + 1) + ". " + steps[i]; } else { curr = (i + 1) + ". " + (isUndetermined ? resourceMap.getString("ellipsis") : //NOI18N steps[i].equals(elipsis) ? elipsis : wizard.getStepDescription(steps[i])); //NOI18N } } else { curr = elipsis; } if (curr != null) { boolean selected = (steps[i].equals (currentStep) && !inSummaryPage) || (inSummaryPage && i == steps.length - 1); if (selected) { g.setFont (boldFont); } int width = fm.stringWidth(curr); while (width > getWidth() - (ins.left + ins.right) && curr.length() > 5) { curr = curr.substring(0, curr.length() - 5) + resourceMap.getString("ellipsis"); //NOI18N } g.drawString (curr, x, y); if (selected) { g.setFont (f); } y += h; } } } private Color color; private Color getGoodColor() { Color c = (UIManager.getColor("textText") == null) ? UIManager.getColor("controlText") : UIManager.getColor("textText"); if (!ColorUtils.isDarkColor(c)) { c = Color.darkGray; } return c; } private int historicWidth = Integer.MIN_VALUE; @Override public final Dimension getPreferredSize() { Font f = getFont() != null ? getFont() // NOI18N : UIManager.getFont("controlFont"); // NOI18N Graphics g = getGraphics(); if (g == null) { g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics(); } f = f.deriveFont(Font.BOLD); FontMetrics fm = g.getFontMetrics(f); Insets ins = getInsets(); int h = fm.getHeight(); String[] steps = wizard.getAllSteps(); int w = Integer.MIN_VALUE; int i = 1; for (String step : steps) { String desc = (i++) + ". " + (Wizard.UNDETERMINED_STEP.equals(step) ? resourceMap.getString("ellipsis") : wizard.getStepDescription(step)); if (desc != null) { w = Math.max(w, fm.stringWidth(desc) + MARGIN); } } if (Integer.MIN_VALUE == 2) { w = 350; } BufferedImage img = getBgImage(); if (img != null) { w = Math.max(w, img.getWidth()); } // Make sure we can grow but not shrink. w = Math.max(w, historicWidth); historicWidth = w; return new Dimension(w, ins.top + ins.bottom + ((h + 3) * steps.length)); } @Override public final Dimension getMinimumSize() { return getPreferredSize(); } public void stepsChanged(Wizard wizard) { repaint(); } public void navigabilityChanged(Wizard wizard) { // do nothing } public void selectionChanged(Wizard wizard) { repaint(); } @Override public final void doLayout() { Component[] c = getComponents(); Insets ins = getInsets(); int y = getHeight() - (MARGIN + ins.bottom); int x = MARGIN + ins.left; int w = getWidth() - ((MARGIN * 2) + ins.left + ins.right); if (w < 0) w = 0; if (c.length > 0) { for (int i = c.length; i >= 0; i--) { Dimension d = c[i].getPreferredSize(); c[i].setBounds(x, y - d.height, w, d.height); y -= d.height; } } } @Override public final AccessibleContext getAccessibleContext() { return new ACI(this); } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables private static final class ACI extends AccessibleContext { private final Wizard wizard; private final WizardSideBar panel; public ACI(WizardSideBar pnl) { this.wizard = pnl.wizard; panel = pnl; if (pnl.getParent() instanceof Accessible) { setAccessibleParent((Accessible) pnl.getParent()); } setAccessibleName(resourceMap.getString("ACN_WizardSideBar")); setAccessibleDescription(resourceMap.getString("ACSD_WizardSideBar")); } JEditorPane pane; public AccessibleText getAccessibleText() { if (pane == null) { // Cheat just a bit here - will do for now - the text is there, //+ more or less where it should be, and a screen reader should //+ be able to find it; exact bounds do not make much difference. pane = new JEditorPane(); pane.setBounds(panel.getBounds()); pane.getAccessibleContext().getAccessibleText(); pane.setFont(panel.getFont()); CellRendererPane cell = new CellRendererPane(); cell.add(pane); } pane.setText(getText()); pane.selectAll(); pane.validate(); return pane.getAccessibleContext().getAccessibleText(); } public String getText() { StringBuilder sb = new StringBuilder(); String[] s = wizard.getAllSteps(); for (String a : s) { sb.append(a).append("\n"); } return sb.toString(); } @Override public AccessibleRole getAccessibleRole() { return AccessibleRole.LIST; } @Override public AccessibleStateSet getAccessibleStateSet() { AccessibleState[] states = new AccessibleState[] { AccessibleState.VISIBLE, AccessibleState.OPAQUE, AccessibleState.SHOWING, AccessibleState.MULTI_LINE }; return new AccessibleStateSet(states); } @Override public int getAccessibleIndexInParent() { return -1; } @Override public int getAccessibleChildrenCount() { return 0; } @Override public Accessible getAccessibleChild(int i) { throw new IndexOutOfBoundsException(i); } @Override public Locale getLocale() throws IllegalComponentStateException { return Locale.getDefault(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy