/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2012 GanttProject Team
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.sourceforge.ganttproject;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import net.java.balloontip.BalloonTip;
import net.java.balloontip.styles.EdgedBalloonStyle;
import net.sourceforge.ganttproject.action.CancelAction;
import net.sourceforge.ganttproject.action.OkAction;
import net.sourceforge.ganttproject.gui.DialogAligner;
import net.sourceforge.ganttproject.gui.NotificationComponent.AnimationView;
import net.sourceforge.ganttproject.gui.NotificationManager;
import net.sourceforge.ganttproject.gui.UIFacade.Centering;
import net.sourceforge.ganttproject.gui.UIFacade.Dialog;
import net.sourceforge.ganttproject.gui.UIUtil;
/**
* Builds standard dialog windows in GanttProject
*
* @author dbarashev (Dmitry Barashev)
*/
class DialogBuilder {
private static class Commiter {
private boolean isCommited;
void commit() {
isCommited = true;
}
boolean isCommited() {
return isCommited;
}
}
private static class NotificationViewImpl implements AnimationView {
private final JDialog myDlg;
private BalloonTip myBalloon;
private Runnable myOnHide;
private final JButton myNotificationOwner;
public NotificationViewImpl(JDialog dlg, JButton notificationOwner) {
myDlg = dlg;
myNotificationOwner = notificationOwner;
notificationOwner.setAction(new AbstractAction("Error") {
@Override
public void actionPerformed(ActionEvent e) {
showBalloon();
}
});
}
protected void showBalloon() {
myBalloon.setVisible(true);
}
@Override
public boolean isReady() {
return myDlg.isVisible();
}
@Override
public boolean isVisible() {
return myBalloon != null && myBalloon.isVisible();
}
@Override
public void setComponent(final JComponent component, JComponent owner, final Runnable onHide) {
myNotificationOwner.setVisible(true);
myBalloon = new BalloonTip(myNotificationOwner, component, new EdgedBalloonStyle(Color.WHITE, Color.BLACK),
BalloonTip.Orientation.LEFT_ABOVE, BalloonTip.AttachLocation.ALIGNED, 30, 10, false);
myBalloon.setVisible(false);
myOnHide = onHide;
}
@Override
public void close() {
if (myBalloon != null) {
myBalloon.setVisible(false);
}
myOnHide.run();
myNotificationOwner.setVisible(false);
}
}
private static class DialogImpl implements Dialog {
/** Original animation view, used to set it back when the dialog is closed again */
private AnimationView myOriginalAnimationView;
private final JDialog myDlg;
private final JFrame myMainFrame;
private final NotificationManager myNotificationManager;
private JButton myButton;
DialogImpl(JDialog dlg, JFrame mainFrame, NotificationManager notificationManager) {
myDlg = dlg;
myMainFrame = mainFrame;
myNotificationManager = notificationManager;
}
@Override
public void hide() {
if (myDlg.isVisible()) {
myDlg.setVisible(false);
myDlg.dispose();
}
myNotificationManager.setAnimationView(myOriginalAnimationView);
}
@Override
public void show() {
myOriginalAnimationView = myNotificationManager.setAnimationView(new NotificationViewImpl(myDlg, myButton));
center(Centering.WINDOW);
myDlg.setVisible(true);
}
@Override
public void layout() {
myDlg.pack();
}
@Override
public void center(Centering centering) {
DialogAligner.center(myDlg, myMainFrame, centering);
}
void setNotificationOwner(JButton button) {
myButton = button;
}
}
private final JFrame myMainFrame;
DialogBuilder(JFrame mainFrame) {
myMainFrame = mainFrame;
}
/**
* Creates a dialog given its {@code title}, {@code content} component and an array
* of actions which are represented as buttons in the bottom of the dialog. Actions
* which extend {@link OkAction} or {@link CancelAction} will automatically close
* dialog when invoked.
*
* @param content dialog content component
* @param buttonActions actions for the button row
* @param title dialog title
* @return dialog object
*/
Dialog createDialog(Component content, Action[] buttonActions, String title, final NotificationManager notificationManager) {
final JDialog dlg = new JDialog(myMainFrame, true);
final DialogImpl result = new DialogImpl(dlg, myMainFrame, notificationManager);
dlg.setTitle(title);
final Commiter commiter = new Commiter();
Action cancelAction = null;
JPanel buttonBox = new JPanel(new GridLayout(1, buttonActions.length, 5, 0));
for (final Action nextAction : buttonActions) {
JButton nextButton = null;
if (nextAction instanceof OkAction) {
final JButton _btn = new JButton();
final AbstractAction _delegate = (AbstractAction) nextAction;
OkAction proxy = new OkAction() {
// These two steps handel the case when focus is somewhere in text input
// and user hits Ctrl+Enter
// First we want to move focus to OK button to allow focus listeners, if any,
// to catch focusLost event
// Second, we want it to happen before original OkAction runs
// So we wrap original OkAction into proxy which moves focus and schedules "later" command
// which call the original action. Between them EDT sends out focusLost events.
final Runnable myStep2 = new Runnable() {
@Override
public void run() {
result.hide();
commiter.commit();
nextAction.actionPerformed(null);
_delegate.removePropertyChangeListener(myDelegateListener);
}
};
final Runnable myStep1 = new Runnable() {
@Override
public void run() {
_btn.requestFocus();
SwingUtilities.invokeLater(myStep2);
}
};
@Override
public void actionPerformed(final ActionEvent e) {
SwingUtilities.invokeLater(myStep1);
}
private void copyValues() {
for (Object key : _delegate.getKeys()) {
putValue(key.toString(), _delegate.getValue(key.toString()));
}
setEnabled(_delegate.isEnabled());
}
private PropertyChangeListener myDelegateListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
copyValues();
}
};
{
_delegate.addPropertyChangeListener(myDelegateListener);
copyValues();
}
};
_btn.setAction(proxy);
nextButton = _btn;
if (((OkAction)nextAction).isDefault()) {
dlg.getRootPane().setDefaultButton(nextButton);
}
}
if (nextAction instanceof CancelAction) {
cancelAction = nextAction;
nextButton = new JButton(nextAction);
nextButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
result.hide();
commiter.commit();
}
});
dlg.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), nextAction.getValue(Action.NAME));
dlg.getRootPane().getActionMap().put(nextAction.getValue(Action.NAME), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
nextAction.actionPerformed(e);
result.hide();
}
});
}
if (nextButton == null) {
nextButton = new JButton(nextAction);
}
buttonBox.add(nextButton);
KeyStroke accelerator = (KeyStroke) nextAction.getValue(Action.ACCELERATOR_KEY);
if (accelerator != null) {
dlg.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(accelerator, nextAction);
dlg.getRootPane().getActionMap().put(nextAction, nextAction);
}
}
dlg.getContentPane().setLayout(new BorderLayout());
dlg.getContentPane().add(content, BorderLayout.CENTER);
JButton errorButton = new JButton("Error");
errorButton.setBackground(UIUtil.ERROR_BACKGROUND);
//errorLabel.setBorder(BorderFactory.createCompoundBorder(errorLabel.getBorder(), BorderFactory.createEmptyBorder(2,2,2,2)));
JPanel buttonPanel = new JPanel(new BorderLayout());
buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 5));
buttonPanel.add(buttonBox, BorderLayout.EAST);
buttonPanel.add(errorButton, BorderLayout.WEST);
dlg.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
result.setNotificationOwner(errorButton);
errorButton.setVisible(false);
dlg.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final Action localCancelAction = cancelAction;
dlg.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
if (localCancelAction != null && !commiter.isCommited()) {
localCancelAction.actionPerformed(null);
}
}
});
dlg.pack();
return result;
}
}