/*
* @(#)JActivityWindow.java
*
* Copyright (c) 2011 The authors and contributors of JHotDraw.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with the copyright holders. For details
* see accompanying license terms.
*/
package org.jhotdraw.gui;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.prefs.Preferences;
import javax.swing.*;
import javax.swing.plaf.metal.*;
import org.jhotdraw.gui.event.ActivityManagerEvent;
import org.jhotdraw.gui.event.ActivityManagerListener;
import org.jhotdraw.util.ResourceBundleUtil;
import org.jhotdraw.util.prefs.PreferencesUtil;
/**
* The {@code JActivityWindow} displays all progress models registered in the
* progress manager.
* <p>
* Once created, {@code JActivityWindow} becomes visible automatically if the
* if a progress model is added to the progress manager.
* <p>
* If an activity finishes successfully, it is automatically removed.
* <p>
* You typically only want to create a single instance. To do this,
* call {@code JActivityWindow.getInstance();}.
*
* <hr>
* <b>Design Patterns</b>
*
* <p><em>Framework</em><br>
* The interfaces and classes listed below define a framework for progress
* management.<br>
* Contract: {@link ActivityManager}, {@link ActivityModel},
* {@link JActivityWindow}, {@link JActivityIndicator}.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class JActivityWindow extends javax.swing.JFrame {
private static final long serialVersionUID = 1L;
private static JActivityWindow instance;
private JPanel progressPanel;
private ActivityManager manager;
private Object activityOwner;
/** Delay for automatic removal of successfully completed activities.
* Specify 0 for immediate removal. Specify -1 for no removal.
*
* FIXME - Changing this value to -1 requires changing code in JActivityView.
*/
private int normalRemovalDelay = 1500;
/** Delay for automatic removal of completed activities with a warning.
* Specify 0 for immediate removal. Specify -1 for no removal.
*
* FIXME - Changing this value to -1 requires changing code in JActivityView.
*/
private int warningRemovalDelay = 3000;
/** Delay for automatic removal of completed activities with an error.
* Specify 0 for immediate removal. Specify -1 for no removal.
*
* FIXME - Changing this value to -1 requires changing code in JActivityView.
*/
private int errorRemovalDelay = -1;
private HashMap<ActivityModel, JActivityView> views = new HashMap<ActivityModel, JActivityView>();
private class Handler implements ActivityManagerListener, PropertyChangeListener {
@Override
public void activityModelAdded(ActivityManagerEvent evt) {
ActivityModel pm = evt.getActivityModel();
if (activityOwner == null || activityOwner.equals(pm.getOwner())) {
addActivityModel(pm);
}
}
@Override
public void activityModelRemoved(ActivityManagerEvent evt) {
final ActivityModel pm = evt.getActivityModel();
int delay = (pm.getError() != null) ? errorRemovalDelay : ((pm.getWarning() != null) ? warningRemovalDelay : normalRemovalDelay);
if (delay == -1) {
JActivityWindow.this.setVisible(true);
return;
}
ActionListener tt = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
removeActivityModel(pm);
}
};
if (delay == 0) {
tt.actionPerformed(null);
} else {
Timer t = new Timer(pm.getError() != null ? delay : delay, tt);
t.setRepeats(false);
t.start();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == JActivityView.REQUEST_REMOVE_PROPERTY) {
removeActivityModel(((JActivityView) evt.getSource()).getModel());
}
}
}
private Handler handler = new Handler();
private ResourceBundleUtil labels;
/** Creates new form JActivityWindow */
public JActivityWindow() {
this(ActivityManager.getInstance());
}
public JActivityWindow(ActivityManager pm) {
labels = ResourceBundleUtil.getBundle("org.jhotdraw.gui.Labels");
initComponents();
setFocusable(false); // needed for Mac OS X
setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
progressPanel = new javax.swing.JPanel() {
private static final long serialVersionUID = 1L;
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.width = 300;
return d;
}
};
progressPanel.setLayout(new javax.swing.BoxLayout(progressPanel, javax.swing.BoxLayout.Y_AXIS));
//getContentPane().add(progressPanel, java.awt.BorderLayout.CENTER);
scrollPane.setViewportView(progressPanel);
disclosureToggle.setIcon(UIManager.getIcon("Tree.collapsedIcon"));
disclosureToggle.setSelectedIcon(UIManager.getIcon("Tree.expandedIcon"));
disclosureToggle.setUI((MetalToggleButtonUI) MetalToggleButtonUI.createUI(disclosureToggle));
PreferencesUtil.installFramePrefsHandler(Preferences.userNodeForPackage(JActivityWindow.class), "progressFrame", this);
setActivityManager(pm);
}
public void setActivityManager(@Nullable ActivityManager newValue) {
if (manager != null) {
manager.removeActivityManagerListener(handler);
}
this.manager = newValue;
if (manager != null) {
manager.addActivityManagerListener(handler);
}
updateActivityModels();
updateInfoPanel();
}
@Nullable public ActivityManager getActivityManager() {
return manager;
}
@SuppressWarnings("unchecked")
private void updateActivityModels() {
for (JActivityView pv : views.values()) {
pv.setModel(null);
progressPanel.remove(pv);
}
for (ActivityModel am : ((HashMap<ActivityModel, JActivityView>) views.clone()).keySet()) {
removeActivityModel(am);
}
if (manager != null) {
ArrayList<ActivityModel> pms = manager.getActivityModels();
for (ActivityModel pm : pms) {
addActivityModel(pm);
}
if (!views.isEmpty()) {
// setVisible(true);
}
}
}
public static JActivityWindow getInstance() {
if (instance == null) {
instance = new JActivityWindow();
}
return instance;
}
public void addActivityModel(final ActivityModel model) {
if (!views.containsKey(model)) {
JActivityView viewer = new JActivityView(model);
viewer.addPropertyChangeListener(handler);
progressPanel.add(viewer);
views.put(model, viewer);
updateInfoPanel();
pack();
if (!isVisible()) {
// setVisible(true);
}
viewer.repaint();
}
}
/** Set to a non-null value to only display progress models
* of a specific owner. Set to null to display all models.
*
* @param newValue
*/
public void setActivityOwner(Object newValue) {
Object oldValue = this.activityOwner;
this.activityOwner = newValue;
if (oldValue != newValue) {
updateActivityModels();
}
}
/** Owner is used to filter the progress models.
*
* @return The owner of the progress models being shown or
* null, if all models are shown.
*/
public Object getActivityOwner() {
return activityOwner;
}
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = Math.min(600, d.height);
return d;
}
public void removeActivityModel(final ActivityModel model) {
if (views.containsKey(model)) {
JActivityView viewer = views.get(model);
viewer.removePropertyChangeListener(handler);
progressPanel.remove(viewer);
views.remove(model);
updateInfoPanel();
pack();
}
}
/**
* Updates the info label and the cancel all button on the
* info panel.
*/
private void updateInfoPanel() {
int count = views.size();
switch (count) {
case 0:
infoLabel.setText(labels.getString("ActivityWindow.noActivities.text"));
cancelAllButton.setEnabled(false);
break;
case 1:
infoLabel.setText(labels.getString("ActivityWindow.oneActivity.text"));
cancelAllButton.setEnabled(true);
break;
default:
infoLabel.setText(labels.getFormatted("ActivityWindow.nActivities.text", count));
cancelAllButton.setEnabled(true);
break;
}
}
private static void invokeAndWait(Runnable r) {
if (SwingUtilities.isEventDispatchThread()) {
r.run();
} else {
try {
SwingUtilities.invokeAndWait(r);
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
@Override
public void dispose() {
super.dispose();
setActivityManager(null);
}
/** 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.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
infoPanel = new javax.swing.JPanel();
disclosureToggle = new javax.swing.JToggleButton();
infoLabel = new javax.swing.JLabel();
cancelAllButton = new javax.swing.JButton();
strutPanel = new javax.swing.JPanel();
viewPanel = new javax.swing.JPanel();
separator = new javax.swing.JSeparator();
scrollPane = new javax.swing.JScrollPane();
FormListener formListener = new FormListener();
setTitle("Activity");
infoPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(12, 12, 12, 12));
infoPanel.setLayout(new java.awt.GridBagLayout());
disclosureToggle.setSelected(true);
disclosureToggle.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
disclosureToggle.setFocusPainted(false);
disclosureToggle.setContentAreaFilled(false);
disclosureToggle.setBorderPainted(false);
disclosureToggle.setRequestFocusEnabled(false);
disclosureToggle.addItemListener(formListener);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
infoPanel.add(disclosureToggle, gridBagConstraints);
infoLabel.setFont(new java.awt.Font("Dialog", 0, 11)); // NOI18N
infoLabel.setText(labels.getString("ActivityWindow.noActivities.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0);
infoPanel.add(infoLabel, gridBagConstraints);
cancelAllButton.setText(labels.getString("ActivityWindow.cancelAll.text")); // NOI18N
cancelAllButton.setEnabled(false);
cancelAllButton.addActionListener(formListener);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 0;
gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0);
infoPanel.add(cancelAllButton, gridBagConstraints);
getContentPane().add(infoPanel, java.awt.BorderLayout.NORTH);
strutPanel.setPreferredSize(new java.awt.Dimension(400, 0));
strutPanel.setLayout(null);
getContentPane().add(strutPanel, java.awt.BorderLayout.SOUTH);
viewPanel.setLayout(new java.awt.BorderLayout());
viewPanel.add(separator, java.awt.BorderLayout.NORTH);
scrollPane.setBorder(null);
scrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
viewPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
getContentPane().add(viewPanel, java.awt.BorderLayout.CENTER);
pack();
}
// Code for dispatching events from components to event handlers.
private class FormListener implements java.awt.event.ActionListener, java.awt.event.ItemListener {
FormListener() {}
public void actionPerformed(java.awt.event.ActionEvent evt) {
if (evt.getSource() == cancelAllButton) {
JActivityWindow.this.cancelAll(evt);
}
}
public void itemStateChanged(java.awt.event.ItemEvent evt) {
if (evt.getSource() == disclosureToggle) {
JActivityWindow.this.disclosureStateChanged(evt);
}
}
}// </editor-fold>//GEN-END:initComponents
private void disclosureStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_disclosureStateChanged
viewPanel.setVisible(disclosureToggle.isSelected());
pack();
}//GEN-LAST:event_disclosureStateChanged
private void cancelAll(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelAll
Component[] components = progressPanel.getComponents();
for (int i = 0; i < components.length; i++) {
if (components[i] instanceof JActivityView) {
((JActivityView) components[i]).getModel().cancel();
}
}
}//GEN-LAST:event_cancelAll
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton cancelAllButton;
private javax.swing.JToggleButton disclosureToggle;
private javax.swing.JLabel infoLabel;
private javax.swing.JPanel infoPanel;
private javax.swing.JScrollPane scrollPane;
private javax.swing.JSeparator separator;
private javax.swing.JPanel strutPanel;
private javax.swing.JPanel viewPanel;
// End of variables declaration//GEN-END:variables
}