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

io.github.jonestimd.swing.window.StatusFrame Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
// The MIT License (MIT)
//
// Copyright (c) 2017 Timothy D. Jones
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package io.github.jonestimd.swing.window;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

import io.github.jonestimd.swing.ComponentTreeUtils;
import io.github.jonestimd.swing.SettingsPersister;
import io.github.jonestimd.swing.StatusIndicator;
import io.github.jonestimd.swing.UnsavedChangesIndicator;
import io.github.jonestimd.swing.component.AlphaPanel;

/**
 * This class extends {@link JFrame} to disable user input and provide progress messages during long running processes.
 * It also uses {@link SettingsPersister} to store state when the window is closed.
 */
public class StatusFrame extends JFrame implements StatusIndicator, UnsavedChangesIndicator {
    public static final float GLASS_PANE_ALPHA = 0.5f;
    public static final String APPLICATION_RESOURCE_PREFIX = "application";
    public static final String SMALL_ICON_RESOURCE_KEY = ".smallIconImage";
    public static final String LARGE_ICON_RESOURCE_KEY = ".largeIconImage";
    private static final String STATE_SUFFIX = ".state";
    private static final String HEIGHT_SUFFIX = ".height";
    private static final String WIDTH_SUFFIX = ".width";
    public static final int DEFAULT_WIDTH = 800;
    public static final int DEFAULT_HEIGHT = 600;

    protected final Logger logger = Logger.getLogger(StatusFrame.class.getName());
    private final ResourceBundle bundle;
    private JLabel statusMessageLabel = new JLabel();
    private String resourcePrefix;
    private String baseTitle;
    private Component lastFocusOwner;

    public StatusFrame(ResourceBundle bundle, String resourcePrefix) {
        super(bundle.getString(resourcePrefix + ".title"));
        this.bundle = bundle;
        this.baseTitle = getTitle();
        this.resourcePrefix = resourcePrefix;
        setIcons(bundle, SMALL_ICON_RESOURCE_KEY, LARGE_ICON_RESOURCE_KEY);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setGlassPane(AlphaPanel.createStatusPane(statusMessageLabel));
        statusMessageLabel.setBorder(new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder(5, 5, 5, 5)));
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this::focusChanged);
    }

    private void focusChanged(PropertyChangeEvent event) {
        Component newValue = (Component) event.getNewValue();
        if (ComponentTreeUtils.findAncestor(newValue, this.getContentPane()::equals).isPresent()) {
            lastFocusOwner = newValue;
        }
    }

    private void setIcons(ResourceBundle bundle, String ... resourceKeys) {
        List icons = new ArrayList<>();
        for (String resourceKey : resourceKeys) {
            if (bundle.containsKey(resourcePrefix + resourceKey)) {
                icons.add(Toolkit.getDefaultToolkit().getImage(getClass().getResource(bundle.getString(resourcePrefix + resourceKey))));
            }
            else if (bundle.containsKey(APPLICATION_RESOURCE_PREFIX + resourceKey)) {
                icons.add(Toolkit.getDefaultToolkit().getImage(getClass().getResource(bundle.getString(APPLICATION_RESOURCE_PREFIX + resourceKey))));
            }
        }
        if (! icons.isEmpty()) {
            setIconImages(icons);
        }
    }

    @Override
    public void setTitle(String title) {
        super.setTitle(title);
        baseTitle = title;
    }

    @Override
    public void setExtendedState(int state) {
        if ((state & MAXIMIZED_BOTH) == MAXIMIZED_BOTH) {
            System.setProperty(getPropertyName(WIDTH_SUFFIX), Integer.toString(getWidth()));
            System.setProperty(getPropertyName(HEIGHT_SUFFIX), Integer.toString(getHeight()));
        }
        super.setExtendedState(state);
    }

    @Override
    public void setVisible(boolean visible) {
        if (visible) {
            pack();
            setLocationByPlatform(true);
            restoreSize();
        }
        super.setVisible(visible);
    }

    private void restoreSize() {
        int width = getInt(getPropertyName(WIDTH_SUFFIX), DEFAULT_WIDTH);
        int height = getInt(getPropertyName(HEIGHT_SUFFIX), DEFAULT_HEIGHT);
        setSize(new Dimension(width, height));
        int state = Integer.getInteger(getPropertyName(STATE_SUFFIX), 0);
        if ((state & MAXIMIZED_BOTH) == MAXIMIZED_BOTH) {
            setExtendedState(getExtendedState() | MAXIMIZED_BOTH);
        }
    }

    private int getInt(String property, int defaultValue) {
        try {
            Integer value = Integer.getInteger(property, -1);
            return value < 0 ? Integer.parseInt(bundle.getString(property)) : value;
        } catch (Exception ex) {
            return defaultValue;
        }
    }

    private String getPropertyName(String suffix) {
        return resourcePrefix + suffix;
    }

    /**
     * Override to save window state via {@link SettingsPersister}.
     */
    @Override
    public void dispose() {
        System.setProperty(getPropertyName(STATE_SUFFIX), Integer.toString(getExtendedState()));
        if ((getExtendedState() & MAXIMIZED_BOTH) != MAXIMIZED_BOTH) {
            System.setProperty(getPropertyName(WIDTH_SUFFIX), Integer.toString(getWidth()));
            System.setProperty(getPropertyName(HEIGHT_SUFFIX), Integer.toString(getHeight()));
        }
        ComponentTreeUtils.visitComponentTree(getContentPane(), JComponent.class, SettingsPersister::saveSettings);
        super.dispose();
    }

    /**
     * Update the progress message.
     */
    @Override
    public void setStatusMessage(String message) {
        statusMessageLabel.setText(message);
    }

    /**
     * Disable user input and display a progress message.
     */
    @Override
    public void disableUI(String message) {
        logger.log(Level.FINE, "disableUI {0}", message);
        if (message != null) setStatusMessage(message);
        getGlassPane().setVisible(true);
        getGlassPane().requestFocus();
    }

    /**
     * Enable user input and hide the progress message.
     */
    @Override
    public void enableUI() {
        logger.fine("enableUI");
        getGlassPane().setVisible(false);
        if (lastFocusOwner != null) {
            lastFocusOwner.requestFocusInWindow();
        }
    }

    /**
     * Update the window title to indicate unsaved changes.
     * @param unsavedChanges true to add the {@code unsaved} indicator, false to remove it
     */
    @Override
    public void setUnsavedChanges(boolean unsavedChanges) {
        super.setTitle(unsavedChanges ? baseTitle + " *": baseTitle);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy