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