001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2015 ForgeRock AS.
016 */
017
018package org.opends.quicksetup.ui;
019
020import java.awt.event.WindowAdapter;
021import java.awt.event.WindowEvent;
022import java.util.HashSet;
023import org.forgerock.i18n.LocalizableMessage;
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025
026
027import javax.swing.JButton;
028import javax.swing.JFrame;
029import javax.swing.JPanel;
030import javax.swing.SwingUtilities;
031import javax.swing.WindowConstants;
032
033import org.opends.quicksetup.*;
034import org.opends.quicksetup.event.ButtonActionListener;
035import org.opends.quicksetup.event.ButtonEvent;
036import org.opends.quicksetup.event.MinimumSizeComponentListener;
037import org.opends.quicksetup.ProgressDescriptor;
038/**
039 * This class represents the dialog used by quicksetup applications.
040 *
041 * In its constructor it gets as parameters an object describing the current
042 * installation status and the default values to be proposed to the user
043 * in the panels.
044 *
045 * If we are installing Open DS and the server has already been installed it
046 * will display an error message.  In the other cases it will display a wizard.
047 *
048 */
049public class QuickSetupDialog
050{
051  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
052
053  private JFrame frame;
054  private QuickSetupErrorPanel installedPanel;
055  private JPanel framePanel;
056  private StepsPanel stepsPanel;
057  private CurrentStepPanel currentStepPanel;
058  private ButtonsPanel buttonsPanel;
059
060  private WizardStep displayedStep;
061
062  private CurrentInstallStatus installStatus;
063
064  private HashSet<ButtonActionListener> buttonListeners = new HashSet<>();
065
066  private GuiApplication application;
067
068  private QuickSetup quickSetup;
069
070  private boolean forceToDisplay;
071
072  /**
073   * Constructor of QuickSetupDialog.
074   * @param app Application to run in as a wizard
075   * @param installStatus of the current environment
076   * @param qs QuickSetup acting as controller
077   */
078  public QuickSetupDialog(GuiApplication app,
079      CurrentInstallStatus installStatus,
080      QuickSetup qs)
081  {
082    if (app == null) {
083      throw new IllegalArgumentException("application cannot be null");
084    }
085    this.application = app;
086    this.installStatus = installStatus;
087    this.quickSetup = qs;
088    frame = new JFrame(String.valueOf(application.getFrameTitle()));
089    frame.getContentPane().add(getFramePanel());
090    frame.addWindowListener(new WindowAdapter() {
091      public void windowClosing(WindowEvent e) {
092        application.windowClosing(QuickSetupDialog.this, e);
093      }
094    });
095    frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
096    Utilities.setFrameIcon(frame);
097  }
098
099  /**
100   * Packs and displays this dialog.
101   *
102   */
103  public void packAndShow()
104  {
105    frame.pack();
106    int minWidth = (int) frame.getPreferredSize().getWidth();
107    int minHeight = (int) frame.getPreferredSize().getHeight();
108    Utilities.centerOnScreen(frame);
109    setFocusOnButton(application.getInitialFocusButtonName());
110    frame.addComponentListener(new MinimumSizeComponentListener(frame,
111        minWidth, minHeight));
112
113    frame.setVisible(true);
114  }
115
116  /**
117   * This method is called when we detected that there is something installed
118   * we inform of this to the user and the user wants to proceed with the
119   * installation destroying the contents of the data and the configuration
120   * in the current installation.
121   */
122  public void forceToDisplay()
123  {
124    this.forceToDisplay = true;
125    framePanel = null;
126    frame.getContentPane().removeAll();
127    frame.getContentPane().add(getFramePanel());
128    frame.pack();
129    Utilities.centerOnScreen(frame);
130    setFocusOnButton(ButtonName.NEXT);
131  }
132
133  /**
134   * Displays the panel corresponding to the provided step.  The panel contents
135   * are updated with the contents of the UserData object.
136   * @param step the step that we want to display.
137   * @param userData the UserData object that must be used to populate
138   * the panels.
139   */
140  public void setDisplayedStep(WizardStep step, UserData userData)
141  {
142    displayedStep = step;
143    //  First call the panels to do the required updates on their layout
144    getButtonsPanel().updateButtons(step);
145    getStepsPanel().setDisplayedStep(step, userData);
146    getCurrentStepPanel().setDisplayedStep(step, userData);
147  }
148
149  /**
150   * Returns the currently displayed step.
151   * @return the currently displayed step.
152   */
153  public WizardStep getDisplayedStep()
154  {
155    return displayedStep;
156  }
157
158  /**
159   * Forwards to the displayed panel the ProgressDescriptor so that they
160   * can update their contents accordingly.
161   * @param descriptor the descriptor of the Installation progress.
162   */
163  public void displayProgress(ProgressDescriptor descriptor)
164  {
165    getCurrentStepPanel().displayProgress(descriptor);
166    ProgressStep status = descriptor.getProgressStep();
167    if (status.isLast()) {
168      setButtonEnabled(ButtonName.CLOSE, true);
169    }
170  }
171
172  /**
173   * Displays an error message dialog.
174   *
175   * @param msg
176   *          the error message.
177   * @param title
178   *          the title for the dialog.
179   */
180  public void displayError(LocalizableMessage msg, LocalizableMessage title)
181  {
182    Utilities.displayError(getFrame(), msg, title);
183  }
184
185  /**
186   * Displays a confirmation message dialog.
187   *
188   * @param msg
189   *          the confirmation message.
190   * @param title
191   *          the title of the dialog.
192   * @return <CODE>true</CODE> if the user confirms the message, or
193   * <CODE>false</CODE> if not.
194   */
195  public boolean displayConfirmation(LocalizableMessage msg, LocalizableMessage title)
196  {
197    return Utilities.displayConfirmation(getFrame(), msg, title);
198  }
199
200  /**
201   * Returns the value corresponding to the provided FieldName.
202   * @param fieldName the FieldName for which we want to obtain the value.
203   * @return the value corresponding to the provided FieldName.
204   */
205  public Object getFieldValue(FieldName fieldName)
206  {
207    return getCurrentStepPanel().getFieldValue(fieldName);
208  }
209
210  /**
211   * Marks as invalid (or valid depending on the value of the invalid parameter)
212   * a field corresponding to FieldName.  This basically implies udpating the
213   * style of the JLabel associated with fieldName (the association is done
214   * using the LabelFieldDescriptor class).
215   * @param fieldName the FieldName to be marked as valid or invalid.
216   * @param invalid whether to mark the field as valid or invalid.
217   */
218  public void displayFieldInvalid(FieldName fieldName, boolean invalid)
219  {
220    getCurrentStepPanel().displayFieldInvalid(fieldName, invalid);
221  }
222
223  /**
224   * Adds a button listener.  All the button listeners will be notified when
225   * the buttons are clicked (by the user or programatically).
226   * @param l the ButtonActionListener to be added.
227   */
228  public void addButtonActionListener(ButtonActionListener l)
229  {
230    getButtonsPanel().addButtonActionListener(l);
231    getInstalledPanel().addButtonActionListener(l);
232    getCurrentStepPanel().addButtonActionListener(l);
233
234    buttonListeners.add(l);
235  }
236
237  /**
238   * This method is called to inform that a worker has started (the QuickSetup
239   * is doing some data validation).  The worker is doing its tasks outside
240   * the event thread to avoid blocking of the painting and this class is
241   * notified of this fact.  The method basically simply the Next and Previous
242   * buttons.
243   *
244   * This method can be called from the event thread or outside the event
245   * thread.
246   *
247   */
248  public void workerStarted()
249  {
250    Runnable r = new Runnable()
251    {
252      public void run()
253      {
254        displayWorkingProgressImage(true);
255        setButtonEnabled(ButtonName.NEXT, false);
256        setButtonEnabled(ButtonName.PREVIOUS, false);
257        setButtonEnabled(ButtonName.FINISH, false);
258      }
259    };
260    runOnEventThread(r);
261  }
262
263  /**
264   * This method is called to inform that a worker has finished. The method just
265   * enables the Next and Previous buttons.
266   *
267   * This method can be called from the event thread or outside the event
268   * thread.
269   *
270   */
271  public void workerFinished()
272  {
273    Runnable r = new Runnable()
274    {
275      public void run()
276      {
277        displayWorkingProgressImage(false);
278        setButtonEnabled(ButtonName.NEXT, true);
279        setButtonEnabled(ButtonName.PREVIOUS, true);
280        setButtonEnabled(ButtonName.FINISH, true);
281      }
282    };
283    runOnEventThread(r);
284  }
285
286  /**
287   * Notification telling that the installation/uninstallation is finished.
288   * @param successful a boolean telling whether the setup was successful or
289   * not.
290   */
291  public void finished(boolean successful)
292  {
293    setButtonEnabled(ButtonName.CLOSE, true);
294    if (!successful)
295    {
296      // Do nothing... all the error messages
297    }
298  }
299
300  /**
301   * Returns the frame containing the dialog.
302   * @return the frame containing the dialog.
303   */
304  public JFrame getFrame()
305  {
306    return frame;
307  }
308
309  /**
310   * Enables a button associated with the given Button Name.
311   * @param buttonName the button name of the button.
312   * @param enable boolean indicating to enable or to disable the button.
313   */
314  public void setButtonEnabled(ButtonName buttonName, boolean enable)
315  {
316    getButton(buttonName).setEnabled(enable);
317  }
318
319  /**
320   * Returns the panel of the dialog.
321   * @return the panel of the dialog.
322   */
323  private JPanel getFramePanel()
324  {
325    if (framePanel == null) {
326      framePanel = application.createFramePanel(this);
327    }
328    return framePanel;
329  }
330
331  /**
332   * Returns the steps panel.
333   * @return the steps panel.
334   */
335  public StepsPanel getStepsPanel()
336  {
337    if (stepsPanel == null)
338    {
339      stepsPanel = new StepsPanel(application);
340      stepsPanel.setQuickSetup(quickSetup);
341    }
342    return stepsPanel;
343  }
344
345  /**
346   * Returns the current step panel.
347   * @return the current step panel.
348   */
349  public CurrentStepPanel getCurrentStepPanel()
350  {
351    if (currentStepPanel == null)
352    {
353      currentStepPanel = new CurrentStepPanel(application, quickSetup);
354    }
355    return currentStepPanel;
356  }
357
358
359  /**
360   * Returns the buttons panel.
361   * @return the buttons panel.
362   */
363  public ButtonsPanel getButtonsPanel()
364  {
365    if (buttonsPanel == null)
366    {
367      buttonsPanel = new ButtonsPanel(application);
368      buttonsPanel.setQuickSetup(quickSetup);
369    }
370    return buttonsPanel;
371  }
372
373  /**
374   * Returns the button corresponding to the buttonName.
375   * @param buttonName the ButtonName for which we want to get the button.
376   * @return the button corresponding to the buttonName.
377   */
378  private JButton getButton(ButtonName buttonName)
379  {
380    JButton button;
381    if (isInstalled() && !forceToDisplay)
382    {
383      if (buttonName == ButtonName.QUIT)
384      {
385        button = getInstalledPanel().getQuitButton();
386      } else if (buttonName == ButtonName.CONTINUE_INSTALL)
387      {
388        button = getInstalledPanel().getContinueInstallButton();
389      } else
390      {
391        button = getButtonsPanel().getButton(buttonName);
392      }
393    } else
394    {
395      button = getButtonsPanel().getButton(buttonName);
396    }
397    return button;
398  }
399
400  /**
401   * Sets the focus in the button associated with the ButtonName.
402   * @param buttonName the ButtonName associated with the button.
403   */
404  public void setFocusOnButton(ButtonName buttonName)
405  {
406    JButton button = getButton(buttonName);
407    if (button != null) {
408      button.requestFocusInWindow();
409    } else {
410      logger.info(LocalizableMessage.raw("Focus requested for unknown button '" +
411              buttonName + "'"));
412    }
413  }
414
415  /**
416   * Sets the default button for the frame.
417   * @param buttonName the ButtonName associated with the button.
418   */
419  public void setDefaultButton(ButtonName buttonName)
420  {
421    getFrame().getRootPane().setDefaultButton(getButton(buttonName));
422  }
423
424  /**
425   * Method used to execute a Runnable in the event thread.  If we are in the
426   * event thread it will be called synchronously and if we are not it will
427   * be executed asynchronously.
428   *
429   * @param r the Runnable to be executed.
430   */
431  private void runOnEventThread(Runnable r)
432  {
433    if (SwingUtilities.isEventDispatchThread())
434    {
435      r.run();
436    } else
437    {
438      SwingUtilities.invokeLater(r);
439    }
440  }
441
442  /**
443   * Returns <CODE>true</CODE> if the server is already installed and
444   * <CODE>false</CODE> otherwise.
445   * @return <CODE>true</CODE> if the server is already installed and
446   * <CODE>false</CODE> otherwise.
447   */
448  private boolean isInstalled()
449  {
450    return installStatus.isInstalled();
451  }
452
453  /**
454   * Returns (and creates if it is not already created) the panel that
455   * informs the user that the server is already installed when the
456   * installation has been launched.
457   * @return the panel that is used
458   * to inform the user that the server is already installed when the
459   * installation has been launched.
460   */
461  public QuickSetupErrorPanel getInstalledPanel()
462  {
463    if (installedPanel == null)
464    {
465      installedPanel = new QuickSetupErrorPanel(
466              application,
467              installStatus);
468      installedPanel.setQuickSetup(quickSetup);
469    }
470    return installedPanel;
471  }
472
473  /**
474   * Notifies the ButtonActionListener objects that an ButtonEvent has occurred
475   * in the button associated with buttonName.
476   * @param buttonName the ButtonName associated with the button.
477   */
478  public void notifyButtonEvent(ButtonName buttonName)
479  {
480    ButtonEvent be = new ButtonEvent(this, buttonName);
481    for (ButtonActionListener li : buttonListeners)
482    {
483      li.buttonActionPerformed(be);
484    }
485  }
486
487  private void displayWorkingProgressImage(boolean display)
488  {
489    getCurrentStepPanel().setCheckingVisible(display);
490  }
491}