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