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

com.pekinsoft.desktop.MainWindow 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      :   MainWindow.java
 *  Author     :   Sean Carrick
 *  Created    :   Oct 28, 2024
 *  Modified   :   Oct 28, 2024
 *
 *  Purpose: See class JavaDoc for explanation
 *
 *  Revision History:
 *
 *  WHEN          BY                   REASON
 *  ------------  -------------------  -----------------------------------------
 *  Oct 28, 2024  Sean Carrick         Initial creation.
 * *****************************************************************************
 */
package com.pekinsoft.desktop;

import com.pekinsoft.api.PreferenceKeys;
import com.pekinsoft.api.WindowManager;
import com.pekinsoft.framework.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.Timer;

/**
 *
 * @author Sean Carrick <sean at pekinsoft dot com>
 *
 * @version 1.0
 * @since 1.0
 */
public abstract class MainWindow extends JFrame
        implements WindowManager, PropertyChangeListener {

    /**
     * Constructs a new {@code MainWindow} for an {@link Application}, with the
     * given {@link ApplicationContext}.
     * 

* This constructor is only callable by subclasses. * * @param context the application context */ protected MainWindow(ApplicationContext context) { this.context = context; resourceMap = context.getResourceMap(getClass()); logger = context.getLogger(getClass()); openWindows = new ArrayList<>(); // Retrieve the login timeout from the SYSTEM settings, with a default //+ value of 5 minutes int timeout = context.getPreferences(ApplicationContext.SYSTEM) .getInt(PreferenceKeys.PREF_LOGIN_TIMEOUT, 300000); if (timeout > 0) { loginTimer = new Timer(timeout, (ActionEvent evt) -> { performLogout(); }); } initComponents(); } @Override public void showMainFrame() { getResourceMap().injectComponents(this); restore(this); setVisible(true); } @Override public void show(JComponent component) { if (component == null) { return; } getContext().getResourceMap(component.getClass()) .injectComponents(component); restore(component); setComponent(component); } @Override public void show(JFrame frame) { if (frame == null) { return; } getContext().getResourceMap(frame.getClass()) .injectComponents(frame); restore(frame); openWindows.add(frame); frame.setVisible(true); } @Override public void show(JDialog dialog) { getContext().getResourceMap(dialog.getClass()) .injectComponents(dialog); restore(dialog); // We do not save dialogs to the open windows listing. dialog.setVisible(true); } @Override public void closeMainFrame() { save(this); setVisible(false); dispose(); } @Override public void close(JComponent component) { save(component); setComponent(this.component); } @Override public void close(JFrame frame) { save(frame); openWindows.remove(frame); frame.setVisible(false); frame.dispose(); } @Override public void close(JDialog dialog) { save(dialog); dialog.setVisible(false); // We do not dispose of a dialog because we do not know if the method //+ that showed it is done with it. } @Override public Container find(String name) { for (Window window : openWindows) { if (name.equals(window.getName())) { return window; } else if (window instanceof Frame frame) { if (name.equals(frame.getTitle())) { return frame; } } } return null; } @Override public JFrame getMainFrame() { return this; } @Override public void addToolBar(JToolBar toolBar) { if (toolBar == null) { return; } List oldValue = getToolBars(); if (toolBars == null) { toolBars = new ArrayList<>(); } toolBars.add(toolBar); updateToolBarsPanel(oldValue); } @Override public void showToolBar(String name) { if (name == null || name.isBlank() || name.isEmpty()) { return; } for (JToolBar toolBar : getToolBars()) { if (name.equals(toolBar.getName())) { toolBar.setVisible(true); getContext().getPreferences(ApplicationContext.USER) .putBoolean(name + ".visible", true); } } } @Override public void hideToolBar(String name) { if (name == null || name.isBlank() || name.isEmpty()) { return; } for (JToolBar toolBar : getToolBars()) { if (name.equals(toolBar.getName())) { toolBar.setVisible(false); getContext().getPreferences(ApplicationContext.USER) .putBoolean(name + ".visible", false); } } } @Override public void showButtonText(boolean showText) { getContext().getPreferences(ApplicationContext.USER) .putBoolean(PreferenceKeys.PREF_HIDE_ACTION_TEXT, !showText); for (JToolBar toolBar : getToolBars()) { toolBar.putClientProperty("hideActionText", !showText); for (Component c : toolBar.getComponents()) { if (c instanceof AbstractButton button) { button.putClientProperty("hideActionText", !showText); } } } } /** * Retrieves a read-only {@link List} of the * {@link JToolBar toolbars} installed on this window. * * @return a list of installed toolbars */ public List getToolBars() { return Collections.unmodifiableList(toolBars); } /** * Sets the {@code List} of {@link JToolBar toolbars} to be installed on * this window. *

* If the specified {@code toolBars} list is {@code null}, a * {@link NullPointerException} is thrown. *

* This is a bound property. * * @param toolBars the toolbars to install * * @throws NullPointerException if {@code toolBars} is {@code null} */ public void setToolBars(List toolBars) { if (toolBars == null) { throw new NullPointerException("null toolbars"); } List oldValue = getToolBars(); this.toolBars = new ArrayList<>(toolBars); updateToolBarsPanel(oldValue); } /** * Removes the supplied {@link JToolBar} from the toolbars installed in this * window. *

* If the specified {@code toolBar} is {@code null}, no exception is thrown * and no action is taken. *

* This is a bound property. * * @param toolBar the toolbar to remove */ @Override public void removeToolBar(JToolBar toolBar) { if (toolBar == null) { return; } List oldValue = getToolBars(); if (toolBars != null && !toolBars.isEmpty() && toolBars .contains(toolBar)) { toolBars.remove(toolBar); updateToolBarsPanel(oldValue); } } /** * Removes the {@link JToolBar} that has its * {@link java.awt.Component#getName() name property} set to the given * value. *

* If the specified {@code name} is blank, empty, or {@code null}, no * exception is thrown and no action is taken. *

* This is a bound property. * * @param name the name of the toolbar to remove */ public void removeToolBar(String name) { if (name == null || name.isBlank() || name.isEmpty()) { return; } List oldValue = getToolBars(); for (JToolBar tb : toolBars) { if (name.equals(tb.getName())) { toolBars.remove(tb); updateToolBarsPanel(oldValue); } } } /** * Sets the status bar for the {@code Application}. Typically, the specified * status bar will implement the * {@link com.pekinsoft.api.ProgressHandler ProgressHandler}, * {@link com.pekinsoft.api.StatusDisplayer StatusDisplayer}, and * {@link com.pekinsoft.api.Notifier Notifier} interfaces. *

* This is a bound property. * * @param statusBar */ public void setStatusBar(JComponent statusBar) { if (statusBar == null) { throw new NullPointerException("statusBar is null"); } JComponent oldValue = getStatusBar(); this.statusBar = statusBar; replaceContentPaneChild(oldValue, this.statusBar, BorderLayout.PAGE_END); firePropertyChange("statusBar", oldValue, getStatusBar()); } /** * Retrieves the status bar for this window. The status bar is located at * {@link BorderLayout#PAGE_END BorderLayout.PAGE_END}. * * @return the status bar */ public JComponent getStatusBar() { return statusBar; } /** * Sets the main component for this window. This component is located at * {@link BorderLayout#CENTER BorderLayout.CENTER}. *

* If the specified {@code component} is {@code null}, a * {@link NullPointerException} is thrown. *

* This is a bound property. * * @param component the new main component * * @throws NullPointerException */ public void setComponent(JComponent component) { if (component == null) { throw new NullPointerException("component is null"); } JComponent oldValue = getComponent(); this.component = component; replaceContentPaneChild(oldValue, this.component, BorderLayout.CENTER); firePropertyChange("component", oldValue, this.component); } /** * Retrieves the main component of this window. This component is located at * {@link BorderLayout#CENTER BorderLayout.CENTER}. * * @return the main component */ public JComponent getComponent() { return component; } /** * Method called from the {@link MouseAdapter} and/or {@link KeyAdapter} to * allow the user to log back in after a period of inactivity has locked the * window. */ protected abstract void performLogin(); /** * Retrieves the {@link ApplicationContext} with which this * {@code MainWindow} was constructed. * * @return the context */ protected ApplicationContext getContext() { return context; } /** * Convenience method for retrieving the {@link Application} for which this * is the main window. * * @return the application */ protected Application getApplication() { return context.getApplication(); } /** * Retrieves the {@code ResourceMap} containing the resources defined for * this {@code MainWindow}. * * @return the resource map */ protected ResourceMap getResourceMap() { return resourceMap; } /** * Updates the toolbar panel whenever a {@link JToolBar} is added to or * removed from this window. *

* This is a bound property. * * @param oldToolBars the old toolbars list */ protected void updateToolBarsPanel(List oldToolBars) { List oldValue = oldToolBars; JComponent oldToolBarsPanel = this.toolBarsPanel; JComponent newToolBarsPanel = new JPanel(new FlowLayout( FlowLayout.LEADING)); boolean hideActionText = getContext() .getPreferences(ApplicationContext.USER) .getBoolean(PreferenceKeys.PREF_HIDE_ACTION_TEXT, true); for (JToolBar tb : toolBars) { tb.putClientProperty("hideActionText", hideActionText); for (Component c : tb.getComponents()) { if (c instanceof AbstractButton button) { button.putClientProperty("hideActionText", hideActionText); } } newToolBarsPanel.add(tb); } replaceContentPaneChild(oldToolBarsPanel, newToolBarsPanel, BorderLayout.NORTH); firePropertyChange("toolBars", oldValue, this.toolBars); } /** * Replaces the specified {@code oldChild} {@link Component} with the * specified {@code newChild} component, using the given constraints. *

* If either of the specified components is {@code null}, no exception is * thrown and no action is taken. * * @param oldChild the component being replaced * @param newChild the component to place * @param constraint constraints for the placement of the new component */ protected void replaceContentPaneChild(JComponent oldChild, JComponent newChild, String constraint) { Container contentPane = getRootPane().getContentPane(); if (oldChild != null) { contentPane.remove(oldChild); } if (newChild != null) { contentPane.add(newChild, constraint); SwingUtilities.updateComponentTreeUI(contentPane); SwingUtilities.updateComponentTreeUI(newChild); for (Component c : newChild.getComponents()) { SwingUtilities.updateComponentTreeUI(c); } contentPane.invalidate(); newChild.invalidate(); } } /** * Restores the last saved session state for the specified * {@code component}. * * @param component the component for which session state is to be restored */ protected void restore(Component component) { getContext().getResourceMap(component.getClass()) .injectComponents(component); String fileName = sessionFileName(component); try { getContext().getSessionStorage().restore(component, fileName); } catch (IOException e) { log(Level.WARNING, "Unable to restore session state for \"" + "{0}\": {1}", new Object[]{fileName, e.getMessage()}); } } /** * Saves the session state for the specified {@code component}. * * @param component the component for which session state is to be saved */ protected void save(Component component) { String fileName = sessionFileName(component); try { getContext().getSessionStorage().save(component, fileName); } catch (IOException e) { log(Level.WARNING, "Unable to save session state for \"" + "{0}\": {1}", new Object[]{fileName, e.getMessage()}); } } /** * Retrieves a file name for the session state file for the specified * {@link Component}. If the component is {@code null}, {@code null} is * returned. * * @param c the component for which a session state file name is needed * * @return the session state file name */ protected String sessionFileName(Component c) { if (c == null) { return null; } return c.getName() + ".session.xml"; } /** * Convenience method for subclasses to perform logging using the * {@link java.util.logging.Logger Logger} obtained from the * {@link ApplicationContext} for this class. * * @param level the {@link java.util.logging.Level Level} at which the * message should be logged * @param msg the message to be logged * @param args any arguments to the message or a {@link Throwable} */ protected void log(Level level, String msg, Object... args) { logger.log(level, msg, args); } private void performLogout() { setGlassPane(opaqueGlassPane); getGlassPane().setVisible(true); } private void resetLogoutTimer() { loginTimer.restart(); } private void initComponents() { setStatusBar(StatusBar.getInstance()); setLayout(new BorderLayout()); component = new JPanel(); component.setLayout(new BorderLayout()); add(statusBar, BorderLayout.SOUTH); add(toolBarsPanel, BorderLayout.NORTH); add(component, BorderLayout.CENTER); addMouseListener(new MouseTimerListener()); addKeyListener(new KeyTimerListener()); } protected final List openWindows; private final Logger logger; private final ApplicationContext context; private final ResourceMap resourceMap; private final JComponent opaqueGlassPane = new SemiOpaqueGlassPane(); private List toolBars = new ArrayList<>(); private Timer loginTimer; private JComponent toolBarsPanel = new JPanel(new FlowLayout( FlowLayout.LEADING)); private JComponent component = null; private JComponent statusBar = null; private final class MouseTimerListener extends MouseAdapter { @Override public void mouseMoved(MouseEvent e) { resetLogoutTimer(); } @Override public void mouseExited(MouseEvent e) { resetLogoutTimer(); } @Override public void mouseEntered(MouseEvent e) { resetLogoutTimer(); } @Override public void mouseReleased(MouseEvent e) { resetLogoutTimer(); } @Override public void mousePressed(MouseEvent e) { resetLogoutTimer(); } } private final class KeyTimerListener extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { resetLogoutTimer(); } @Override public void keyPressed(KeyEvent e) { resetLogoutTimer(); } @Override public void keyTyped(KeyEvent e) { resetLogoutTimer(); } } private final class SemiOpaqueGlassPane extends JComponent { public SemiOpaqueGlassPane() { setSize(MainWindow.this.getSize()); } @Override public void paint(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g.create(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f)); g2.setColor(getBackground()); g2.fillRect(0, 0, getWidth(), getHeight()); g2.dispose(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy