/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.customcontrols.wizard; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.beans.*; import java.util.*; import javax.swing.*; import javax.swing.border.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.gui.*; import org.jitsi.service.resources.*; /** * This class implements a basic wizard dialog, where the programmer can insert * one or more Components to act as panels. These panels can be navigated * through arbitrarily using the 'Next' or 'Back' buttons, or the dialog itself * can be closed using the 'Cancel' button. Note that even though the dialog * uses a CardLayout manager, the order of the panels is not linear. Each panel * determines at runtime what its next and previous panel will be. */ public class Wizard extends SIPCommDialog implements WindowListener, WizardContainer, PropertyChangeListener { /** * Indicates that the 'Finish' button was pressed to close the dialog. */ public static final int FINISH_RETURN_CODE = 0; /** * Indicates that the 'Cancel' button was pressed to close the dialog, or * the user pressed the close box in the corner of the window. */ public static final int CANCEL_RETURN_CODE = 1; /** * Indicates that the dialog closed due to an internal error. */ public static final int ERROR_RETURN_CODE = 2; /** * The String-based action command for the 'Next' button. */ public static final String NEXT_BUTTON_ACTION_COMMAND = "NextButtonActionCommand"; /** * The String-based action command for the 'Back' button. */ public static final String BACK_BUTTON_ACTION_COMMAND = "BackButtonActionCommand"; /** * The String-based action command for the 'Cancel' button. */ public static final String CANCEL_BUTTON_ACTION_COMMAND = "CancelButtonActionCommand"; private JLabel wizardIconLabel; private TransparentPanel wizardIconPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); /** * The i18n text used for the buttons. Loaded from a property resource file. */ ResourceManagementService resources = GuiActivator.getResources(); private String backButtonDefaultText = resources.getI18NString("service.gui.PREVIOUS"); private String nextButtonDefaultText = resources.getI18NString("service.gui.NEXT"); private String finishButtonDefaultText = resources.getI18NString("service.gui.FINISH"); private String cancelButtonDefaultText = resources.getI18NString("service.gui.CANCEL"); private final WizardModel wizardModel = new WizardModel(); private WizardController wizardController; protected TransparentPanel cardPanel; private CardLayout cardLayout; private JButton backButton; private JButton nextButton; private JButton cancelButton; private final java.util.List<WizardListener> wizardListeners = new Vector<WizardListener>(); /** * Status label, show when connecting. */ private JLabel statusLabel = new JLabel(); /** * If account is signing-in ignore close. */ private boolean isCurrentlySigningIn = false; /** * This method accepts a java.awt.Dialog object as the javax.swing.JDialog's * parent. * * @param owner The java.awt.Dialog object that is the owner of this dialog. */ public Wizard(Dialog owner) { super(owner, false); initComponents(); } /** * This method accepts a java.awt.Frame object as the javax.swing.JDialog's * parent. * * @param owner The java.awt.Frame object that is the owner of the * javax.swing.JDialog. */ public Wizard(Frame owner) { super(owner, false); initComponents(); } /** * Returns an instance of the JDialog that this class created.This is useful * in the event that you want to change any of the JDialog parameters * manually. * * @return The JDialog instance that this class created. */ public JDialog getDialog() { return this; } /** * Convenience method that displays a modal wizard dialog and blocks until * the dialog has completed. * * @param modal whether to show a modal dialog */ public void showDialog(boolean modal) { if (modal) this.setModal(true); this.pack(); super.setVisible(true); } /** * Returns the current model of the wizard dialog. * * @return A WizardModel instance, which serves as the model for the wizard * dialog. */ public WizardModel getModel() { return wizardModel; } /** * Adds the given WizardPage in this wizard. Each WizardPage is identified * by a unique Object-based identifier (often a String), which can be used * by the setCurrentPanel() method to display the panel at runtime. * * @param id An Object-based identifier used to identify the WizardPage * object * @param page The WizardPage object to register in this wizard */ public void registerWizardPage(Object id, WizardPage page) { // Add the incoming panel to our JPanel display that is managed by // the CardLayout layout manager. Object wizardForm = page.getWizardForm(); if (wizardForm instanceof Component) cardPanel.add((Component) wizardForm, id); // Place a reference to it in the model. wizardModel.registerPage(id, page); } /** * Removes from the wizard the <tt>WizardPage</tt> corresponding to the * given identifier. * * @param id The identifier of the wizard page. */ public void unregisterWizardPage(final Object id) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { unregisterWizardPage(id); } }); return; } WizardPage wizardPage = wizardModel.getWizardPage(id); if (wizardPage != null) { cardPanel.remove((Component) wizardModel.getWizardPage(id) .getWizardForm()); wizardModel.unregisterPage(id); } } /** * Checks whether a page with the given id exists in the wizard. * * @param id the identifier of the searched page * @return TRUE if the page with the given id exists in the wizard, FALSE * otherwise. */ public boolean containsPage(Object id) { return (wizardModel.getWizardPage(id) != null); } /** * Displays the panel identified by the object passed in. This is the same * Object-based identified used when registering the panel. * * @param id The Object-based identifier of the panel to be displayed. */ public void setCurrentPage(Object id) { // Get the hashtable reference to the panel that should // be displayed. If the identifier passed in is null, then close // the dialog. if (id == null) { close(Wizard.ERROR_RETURN_CODE); return; } WizardPage oldPanelDescriptor = wizardModel.getCurrentWizardPage(); if (oldPanelDescriptor != null) oldPanelDescriptor.pageHiding(); wizardModel.setCurrentPanel(id); wizardModel.getCurrentWizardPage().pageShowing(); // Show the panel in the dialog. cardLayout.show(cardPanel, id.toString()); wizardModel.getCurrentWizardPage().pageShown(); this.pack(); } /** * Method used to listen for property change events from the model and * update the dialog's graphical components as necessary. * * @param evt PropertyChangeEvent passed from the model to signal that one * of its properties has changed value. */ public void propertyChange(final PropertyChangeEvent evt) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { propertyChange(evt); } }); return; } String name = evt.getPropertyName(); if (WizardModel.CURRENT_PAGE_PROPERTY.equals(name)) { wizardController.resetButtonsToPanelRules(); } else if (WizardModel.NEXT_FINISH_BUTTON_TEXT_PROPERTY.equals(name)) { nextButton.setText(evt.getNewValue().toString()); } else if (WizardModel.BACK_BUTTON_TEXT_PROPERTY.equals(name)) { backButton.setText(evt.getNewValue().toString()); } else if (WizardModel.CANCEL_BUTTON_TEXT_PROPERTY.equals(name)) { cancelButton.setText(evt.getNewValue().toString()); } else if (WizardModel.NEXT_FINISH_BUTTON_ENABLED_PROPERTY.equals(name)) { nextButton.setEnabled((Boolean) evt.getNewValue()); } else if (WizardModel.BACK_BUTTON_ENABLED_PROPERTY.equals(name)) { backButton.setEnabled((Boolean) evt.getNewValue()); } else if (WizardModel.CANCEL_BUTTON_ENABLED_PROPERTY.equals(name)) { cancelButton.setEnabled((Boolean) evt.getNewValue()); } else if (WizardModel.NEXT_FINISH_BUTTON_ICON_PROPERTY.equals(name)) { nextButton.setIcon((Icon) evt.getNewValue()); } else if (WizardModel.BACK_BUTTON_ICON_PROPERTY.equals(name)) { backButton.setIcon((Icon) evt.getNewValue()); } else if (WizardModel.CANCEL_BUTTON_ICON_PROPERTY.equals(name)) { cancelButton.setIcon((Icon) evt.getNewValue()); } } /** * Mirrors the WizardModel method of the same name. * * @return A boolean indicating if the button is enabled. */ public boolean isBackButtonEnabled() { return wizardModel.getBackButtonEnabled(); } /** * Mirrors the WizardModel method of the same name. * * @param newValue The new enabled status of the button. */ public void setBackButtonEnabled(boolean newValue) { if(!(isCurrentlySigningIn && newValue)) wizardModel.setBackButtonEnabled(newValue); } /** * Mirrors the WizardModel method of the same name. * * @return A boolean indicating if the button is enabled. */ public boolean isNextFinishButtonEnabled() { return wizardModel.getNextFinishButtonEnabled(); } /** * Mirrors the WizardModel method of the same name. * * @param newValue The new enabled status of the button. */ public void setNextFinishButtonEnabled(boolean newValue) { wizardModel.setNextFinishButtonEnabled(newValue); } /** * Mirrors the WizardModel method of the same name. * * @return A boolean indicating if the button is enabled. */ public boolean isCancelButtonEnabled() { return wizardModel.getCancelButtonEnabled(); } /** * Mirrors the WizardModel method of the same name. * * @param newValue The new enabled status of the button. */ public void setCancelButtonEnabled(boolean newValue) { wizardModel.setCancelButtonEnabled(newValue); } /** * Closes the dialog and sets the return code to the integer parameter. * * @param code The return code. */ void close(int code) { WizardPage oldPanelDescriptor = wizardModel.getCurrentWizardPage(); if (oldPanelDescriptor != null) oldPanelDescriptor.pageHiding(); this.removeWizzardIcon(); if (code == CANCEL_RETURN_CODE) this.fireWizardEvent(WizardEvent.CANCEL); else if (code == FINISH_RETURN_CODE) this.fireWizardEvent(WizardEvent.SUCCESS); else if (code == ERROR_RETURN_CODE) this.fireWizardEvent(WizardEvent.ERROR); this.dispose(); } /** * This method initializes the components for the wizard dialog: it creates * a JDialog as a CardLayout panel surrounded by a small amount of space on * each side, as well as three buttons at the bottom. */ private void initComponents() { wizardModel.addPropertyChangeListener(this); wizardController = new WizardController(this); this.getContentPane().setLayout(new BorderLayout()); this.addWindowListener(this); /* * Create the outer wizard panel, which is responsible for three * buttons: Next, Back, and Cancel. It is also responsible a JPanel * above them that uses a CardLayout layout manager to display multiple * panels in the same spot. */ TransparentPanel buttonPanel = new TransparentPanel(); JSeparator separator = new JSeparator(); Box buttonBox = new Box(BoxLayout.X_AXIS); cardPanel = new TransparentPanel(); cardLayout = new CardLayout(); cardPanel.setLayout(cardLayout); backButton = new JButton(); nextButton = new JButton(); cancelButton = new JButton(); this.getDialog().getRootPane().setDefaultButton(nextButton); backButton.setActionCommand(BACK_BUTTON_ACTION_COMMAND); nextButton.setActionCommand(NEXT_BUTTON_ACTION_COMMAND); cancelButton.setActionCommand(CANCEL_BUTTON_ACTION_COMMAND); backButton.addActionListener(wizardController); nextButton.addActionListener(wizardController); cancelButton.addActionListener(wizardController); backButton.setMnemonic( GuiActivator.getResources().getI18nMnemonic("service.gui.PREVIOUS")); nextButton.setMnemonic( GuiActivator.getResources().getI18nMnemonic("service.gui.NEXT")); cancelButton.setMnemonic( GuiActivator.getResources().getI18nMnemonic("service.gui.CANCEL")); // Create the buttons with a separator above them, then place them // on the east side of the panel with a small amount of space between // the back and the next button, and a larger amount of space between // the next button and the cancel button. buttonPanel.setLayout(new BorderLayout()); buttonPanel.add(separator, BorderLayout.NORTH); buttonBox.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10))); buttonBox.add(backButton); buttonBox.add(Box.createHorizontalStrut(10)); buttonBox.add(nextButton); buttonBox.add(Box.createHorizontalStrut(30)); buttonBox.add(cancelButton); buttonPanel.add(buttonBox, java.awt.BorderLayout.EAST); JPanel statusPanel = new TransparentPanel( new FlowLayout(FlowLayout.CENTER)); statusPanel.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10))); statusPanel.add(statusLabel); buttonPanel.add(statusPanel, java.awt.BorderLayout.CENTER); java.awt.Container contentPane = getContentPane(); contentPane.add(buttonPanel, java.awt.BorderLayout.SOUTH); contentPane.add(cardPanel, java.awt.BorderLayout.CENTER); contentPane.add(wizardIconPanel, java.awt.BorderLayout.WEST); } /** * If the user presses the close box on the dialog's window, treat it as a * cancel. * * @param e The event passed in from AWT. */ public void windowClosing(WindowEvent e) { this.close(Wizard.CANCEL_RETURN_CODE); } public void setWizzardIcon(BufferedImage wizardIcon) { wizardIconLabel = new JLabel(); wizardIconLabel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(20, 20, 20, 20), BorderFactory .createTitledBorder(""))); this.wizardIconLabel.setIcon(new ImageIcon(wizardIcon)); this.wizardIconPanel.removeAll(); this.wizardIconPanel.add(wizardIconLabel); } public void removeWizzardIcon() { if (wizardIconLabel != null) this.wizardIconPanel.remove(wizardIconLabel); } public void addWizardListener(WizardListener l) { synchronized (wizardListeners) { if (!wizardListeners.contains(l)) wizardListeners.add(l); } } public void removeWizardListener(WizardListener l) { synchronized (wizardListeners) { wizardListeners.remove(l); } } private void fireWizardEvent(int eventCode) { Iterable<WizardListener> listeners; synchronized (wizardListeners) { listeners = new ArrayList<WizardListener>(wizardListeners); } WizardEvent wizardEvent = new WizardEvent(this, eventCode); for (WizardListener l : listeners) l.wizardFinished(wizardEvent); } /** * Implements the <tt>SIPCommDialog</tt> close method. */ @Override protected void close(boolean isEscaped) { if(isCurrentlySigningIn) return; this.close(Wizard.CANCEL_RETURN_CODE); } public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} /** * Returns the next wizard button. * * @return the next wizard button */ public JButton getNextButton() { return this.nextButton; } /** * Returns the back wizard button. * * @return the back wizard button */ public JButton getBackButton() { return this.backButton; } /** * Refreshes this wizard dialog. */ public void refresh() { this.pack(); this.repaint(); } /** * Returns the default text of the back wizard button. * * @return the default text of the back wizard button */ public String getBackButtonDefaultText() { return backButtonDefaultText; } /** * Sets the back button default text. * * @param backButtonDefaultText the text to set */ void setBackButtonDefaultText(String backButtonDefaultText) { this.backButtonDefaultText = backButtonDefaultText; } /** * Returns the default text of the next wizard button. * * @return the default text of the next wizard button. */ public String getNextButtonDefaultText() { return nextButtonDefaultText; } /** * Sets the next button default text. * * @param nextButtonDefaultText the text to set */ void setNextButtonDefaultText(String nextButtonDefaultText) { this.nextButtonDefaultText = nextButtonDefaultText; } /** * Returns the default text of the finish wizard button. * * @return the default text of the finish wizard button. */ public String getFinishButtonDefaultText() { return finishButtonDefaultText; } /** * Sets the finish button default text. * * @param finishButtonDefaultText the text to set */ void setFinishButtonDefaultText(String finishButtonDefaultText) { this.finishButtonDefaultText = finishButtonDefaultText; } /** * Returns the default text of the cancel wizard button. * * @return the default text of the cancel wizard button. */ public String getCancelButtonDefaultText() { return cancelButtonDefaultText; } /** * Sets the cancel button default text. * * @param cancelButtonDefaultText the text to set */ void setCancelButtonDefaultText(String cancelButtonDefaultText) { this.cancelButtonDefaultText = cancelButtonDefaultText; } /** * Sets the text label of the "Finish" wizard button. * * @param text the new label of the button */ public void setFinishButtonText(String text) { this.setFinishButtonDefaultText(text); } /** * Changes cursor and status label, informing user we are in process * of connecting. */ void startCommittingPage() { isCurrentlySigningIn = true; setBackButtonEnabled(false); setCancelButtonEnabled(false); setNextFinishButtonEnabled(false); statusLabel.setText(GuiActivator.getResources().getI18NString( "service.gui.CONNECTING")); setCursor(new Cursor(Cursor.WAIT_CURSOR)); } /** * Changes cursor and status label, informing user we finished the process * of connecting. */ void stopCommittingPage() { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public void run() { stopCommittingPage(); } }); return; } isCurrentlySigningIn = false; statusLabel.setText(""); setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); setBackButtonEnabled(true); setCancelButtonEnabled(true); setNextFinishButtonEnabled(true); } }