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-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.quicksetup.ui;
028
029import java.awt.CardLayout;
030import java.awt.Component;
031import java.awt.GridBagConstraints;
032import java.awt.GridBagLayout;
033
034import java.util.HashMap;
035import java.util.HashSet;
036
037import javax.swing.Box;
038import javax.swing.JEditorPane;
039import javax.swing.JLabel;
040import javax.swing.JPanel;
041import javax.swing.event.HyperlinkEvent;
042import javax.swing.event.HyperlinkListener;
043
044import org.opends.quicksetup.event.ButtonActionListener;
045import org.opends.quicksetup.event.ButtonEvent;
046import org.opends.quicksetup.ProgressDescriptor;
047import org.opends.quicksetup.UserData;
048import org.opends.quicksetup.util.HtmlProgressMessageFormatter;
049import org.opends.quicksetup.util.ProgressMessageFormatter;
050import org.opends.quicksetup.util.URLWorker;
051import org.opends.quicksetup.util.Utils;
052import org.forgerock.i18n.LocalizableMessage;
053import static org.opends.messages.QuickSetupMessages.*;
054
055/**
056 * This is an abstract class that is extended by all the classes that are in
057 * the CardLayout of CurrentStepPanel.  All the panels that appear on the
058 * top-right side of the dialog extend this class: WelcomePane, ReviewPanel,
059 * etc.
060 *
061 */
062public abstract class QuickSetupStepPanel extends QuickSetupPanel
063implements HyperlinkListener
064{
065  private static final long serialVersionUID = -1983448318085588324L;
066  private JPanel inputContainer;
067  private Component inputPanel;
068
069  private HashSet<ButtonActionListener> buttonListeners = new HashSet<>();
070
071  private ProgressMessageFormatter formatter;
072
073  private static final String INPUT_PANEL = "input";
074  private static final String CHECKING_PANEL = "checking";
075
076  private boolean isCheckingVisible;
077
078  /**
079   * We can use a HashMap (not multi-thread safe) because all
080   * the calls to this object are done in the event-thread.
081  */
082  private HashMap<String, URLWorker> hmURLWorkers = new HashMap<>();
083
084  /**
085   * Creates a default instance.
086   * @param application Application this panel represents
087   */
088  public QuickSetupStepPanel(GuiApplication application) {
089    super(application);
090  }
091
092  /**
093   * Initializes this panel.  Called soon after creation.  In general this
094   * is where maps should be populated etc.
095   */
096  public void initialize() {
097    createLayout();
098  }
099
100  /**
101   * Called just before the panel is shown: used to update the contents of the
102   * panel with new UserData (used in particular in the review panel).
103   *
104   * @param data the new user data.
105   */
106  public void beginDisplay(UserData data)
107  {
108  }
109
110  /**
111   * Called just after the panel is shown: used to set focus properly.
112   */
113  public void endDisplay()
114  {
115  }
116
117  /**
118   * Tells whether the method beginDisplay can be long and so should be called
119   * outside the event thread.
120   * @return <CODE>true</CODE> if the method beginDisplay can be long and so
121   * should be called outside the event thread and <CODE>true</CODE> otherwise.
122   */
123  public boolean blockingBeginDisplay()
124  {
125    return false;
126  }
127
128  /**
129   * Called when a progress change must be reflected in the panels.  Only
130   * ProgressPanel overwrites this method and for all the others it stays empty.
131   * @param descriptor the descriptor of the Installation progress.
132   */
133  public void displayProgress(ProgressDescriptor descriptor)
134  {
135  }
136
137  /**
138   * Implements HyperlinkListener.  When the user clicks on a link we will
139   * try to display the associated URL in the browser of the user.
140   *
141   * @param e the HyperlinkEvent.
142   */
143  public void hyperlinkUpdate(HyperlinkEvent e)
144  {
145    if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
146    {
147      String url = e.getURL().toString();
148      if (!isURLWorkerRunning(url))
149      {
150        /*
151         * Only launch the worker if there is not already a worker trying to
152         * display this URL.
153         */
154        URLWorker worker = new URLWorker(this, url);
155        startWorker(worker);
156      }
157    }
158  }
159
160  /**
161   * Returns the value corresponding to the provided FieldName.
162   * @param fieldName the FieldName for which we want to obtain the value.
163   * @return the value corresponding to the provided FieldName.
164   */
165  public Object getFieldValue(FieldName fieldName)
166  {
167    return null;
168  }
169
170  /**
171   * Marks as invalid (or valid depending on the value of the invalid parameter)
172   * a field corresponding to FieldName.  This basically implies udpating the
173   * style of the JLabel associated with fieldName (the association is done
174   * using the LabelFieldDescriptor class).
175   * @param fieldName the FieldName to be marked as valid or invalid.
176   * @param invalid whether to mark the field as valid or invalid.
177   */
178  public void displayFieldInvalid(FieldName fieldName, boolean invalid)
179  {
180  }
181
182  /**
183   * Returns the minimum width of the panel.  This is used to calculate the
184   * minimum width of the dialog.
185   * @return the minimum width of the panel.
186   */
187  public int getMinimumWidth()
188  {
189    // Just take the preferred width of the inputPanel because the
190    // instructionsPanel
191    // are too wide.
192    int width = 0;
193    if (inputPanel != null)
194    {
195      width = (int) inputPanel.getPreferredSize().getWidth();
196    }
197    return width;
198  }
199
200  /**
201   * Returns the minimum height of the panel.  This is used to calculate the
202   * minimum height of the dialog.
203   * @return the minimum height of the panel.
204   */
205  public int getMinimumHeight()
206  {
207
208    return (int) getPreferredSize().getHeight();
209  }
210
211
212  /**
213   * Adds a button listener.  All the button listeners will be notified when
214   * the buttons are clicked (by the user or programatically).
215   * @param l the ButtonActionListener to be added.
216   */
217  public void addButtonActionListener(ButtonActionListener l)
218  {
219    buttonListeners.add(l);
220  }
221
222  /**
223   * Removes a button listener.
224   * @param l the ButtonActionListener to be removed.
225   */
226  public void removeButtonActionListener(ButtonActionListener l)
227  {
228    buttonListeners.remove(l);
229  }
230
231  /**
232   * This method displays a working progress icon in the panel.
233   * @param visible whether the icon must be displayed or not.
234   */
235  public void setCheckingVisible(boolean visible)
236  {
237    if (visible != isCheckingVisible && inputContainer != null)
238    {
239      CardLayout cl = (CardLayout) inputContainer.getLayout();
240      if (visible)
241      {
242        cl.show(inputContainer, CHECKING_PANEL);
243      }
244      else
245      {
246        cl.show(inputContainer, INPUT_PANEL);
247      }
248      isCheckingVisible = visible;
249    }
250  }
251
252  /**
253   * Returns the text to be displayed in the progress label for a give icon
254   * type.
255   * @param iconType the icon type.
256   * @return the text to be displayed in the progress label for a give icon
257   * type.
258   */
259  protected LocalizableMessage getTextForIcon(UIFactory.IconType iconType)
260  {
261    LocalizableMessage text;
262    if (iconType == UIFactory.IconType.WAIT)
263    {
264      text = INFO_GENERAL_CHECKING_DATA.get();
265    }
266    else
267    {
268      text = LocalizableMessage.EMPTY;
269    }
270    return text;
271  }
272
273  /**
274   * Notifies the button action listeners that an event occurred.
275   * @param ev the button event to be notified.
276   */
277  protected void notifyButtonListeners(ButtonEvent ev)
278  {
279    for (ButtonActionListener l : buttonListeners)
280    {
281      l.buttonActionPerformed(ev);
282    }
283  }
284  /**
285   * Creates the layout of the panel.
286   *
287   */
288  protected void createLayout()
289  {
290    setLayout(new GridBagLayout());
291
292    setOpaque(false);
293
294    GridBagConstraints gbc = new GridBagConstraints();
295
296    Component titlePanel = createTitlePanel();
297    Component instructionsPanel = createInstructionsPanel();
298    inputPanel = createInputPanel();
299
300    boolean somethingAdded = false;
301
302    if (titlePanel != null)
303    {
304      gbc.weightx = 1.0;
305      gbc.weighty = 0.0;
306      gbc.gridwidth = GridBagConstraints.REMAINDER;
307      gbc.fill = GridBagConstraints.HORIZONTAL;
308      gbc.anchor = GridBagConstraints.NORTHWEST;
309      gbc.insets.left = 0;
310      add(titlePanel, gbc);
311      somethingAdded = true;
312    }
313
314    if (instructionsPanel != null)
315    {
316      if (somethingAdded)
317      {
318        gbc.insets.top = UIFactory.TOP_INSET_PRIMARY_FIELD;
319      } else
320      {
321        gbc.insets.top = 0;
322      }
323      gbc.insets.left = 0;
324      gbc.weightx = 1.0;
325      gbc.weighty = 0.0;
326      gbc.gridwidth = GridBagConstraints.REMAINDER;
327      gbc.fill = GridBagConstraints.BOTH;
328      gbc.anchor = GridBagConstraints.NORTHWEST;
329      add(instructionsPanel, gbc);
330      somethingAdded = true;
331    }
332
333    if (inputPanel != null)
334    {
335      inputContainer = new JPanel(new CardLayout());
336      inputContainer.setOpaque(false);
337      if (requiresScroll())
338      {
339        inputContainer.add(UIFactory.createBorderLessScrollBar(inputPanel),
340            INPUT_PANEL);
341      }
342      else
343      {
344        inputContainer.add(inputPanel, INPUT_PANEL);
345      }
346
347      JPanel checkingPanel = UIFactory.makeJPanel();
348      checkingPanel.setLayout(new GridBagLayout());
349      checkingPanel.add(UIFactory.makeJLabel(UIFactory.IconType.WAIT,
350          INFO_GENERAL_CHECKING_DATA.get(),
351          UIFactory.TextStyle.PRIMARY_FIELD_VALID),
352          new GridBagConstraints());
353      inputContainer.add(checkingPanel, CHECKING_PANEL);
354
355      if (somethingAdded)
356      {
357        gbc.insets.top = UIFactory.TOP_INSET_INPUT_SUBPANEL;
358      } else
359      {
360        gbc.insets.top = 0;
361      }
362      gbc.weightx = 1.0;
363      gbc.weighty = 1.0;
364      gbc.gridwidth = GridBagConstraints.REMAINDER;
365      gbc.fill = GridBagConstraints.BOTH;
366      gbc.anchor = GridBagConstraints.NORTHWEST;
367      gbc.insets.left = 0;
368      add(inputContainer, gbc);
369    }
370    else
371    {
372      addVerticalGlue(this);
373    }
374  }
375
376  /**
377   * Creates and returns the panel that contains the layout specific to the
378   * panel.
379   * @return the panel that contains the layout specific to the
380   * panel.
381   */
382  protected abstract Component createInputPanel();
383
384  /**
385   * Returns the title of this panel.
386   * @return the title of this panel.
387   */
388  protected abstract LocalizableMessage getTitle();
389
390  /**
391   * Returns the instruction of this panel.
392   * @return the instruction of this panel.
393   */
394  protected abstract LocalizableMessage getInstructions();
395
396  /**
397   * Commodity method that adds a vertical glue at the bottom of a given panel.
398   * @param panel the panel to which we want to add a vertical glue.
399   */
400  protected void addVerticalGlue(JPanel panel)
401  {
402    GridBagConstraints gbc = new GridBagConstraints();
403    gbc.gridwidth = GridBagConstraints.REMAINDER;
404    gbc.insets = UIFactory.getEmptyInsets();
405    gbc.weighty = 1.0;
406    gbc.fill = GridBagConstraints.VERTICAL;
407    panel.add(Box.createVerticalGlue(), gbc);
408  }
409
410  /**
411   * This method is called by the URLWorker when it has finished its task.
412   * @param worker the URLWorker that finished its task.
413   */
414  public void urlWorkerFinished(URLWorker worker)
415  {
416    hmURLWorkers.remove(worker.getURL());
417  }
418
419  /**
420   * Tells whether the input panel should have a scroll or not.
421   * @return <CODE>true</CODE> if the input panel should have a scroll and
422   * <CODE>false</CODE> otherwise.
423   */
424  protected boolean requiresScroll()
425  {
426    return true;
427  }
428
429  /**
430   * Returns <CODE>true</CODE> if this is a WebStart based installer and
431   * <CODE>false</CODE> otherwise.
432   * @return <CODE>true</CODE> if this is a WebStart based installer and
433   * <CODE>false</CODE> otherwise.
434   */
435  protected boolean isWebStart()
436  {
437    return Utils.isWebStart();
438  }
439
440  /**
441   * Returns the formatter that will be used to display the messages in this
442   * panel.
443   * @return the formatter that will be used to display the messages in this
444   * panel.
445   */
446  ProgressMessageFormatter getFormatter()
447  {
448    if (formatter == null)
449    {
450      formatter = new HtmlProgressMessageFormatter();
451    }
452    return formatter;
453  }
454
455  /**
456   * Creates and returns the title panel.
457   * @return the title panel.
458   */
459  private Component createTitlePanel()
460  {
461    Component titlePanel = null;
462    LocalizableMessage title = getTitle();
463    if (title != null)
464    {
465      JPanel p = new JPanel(new GridBagLayout());
466      p.setOpaque(false);
467      GridBagConstraints gbc = new GridBagConstraints();
468      gbc.anchor = GridBagConstraints.NORTHWEST;
469      gbc.fill = GridBagConstraints.HORIZONTAL;
470      gbc.weightx = 0.0;
471      gbc.gridwidth = GridBagConstraints.RELATIVE;
472
473      JLabel l =
474          UIFactory.makeJLabel(UIFactory.IconType.NO_ICON, title,
475              UIFactory.TextStyle.TITLE);
476      p.add(l, gbc);
477
478      gbc.weightx = 1.0;
479      gbc.gridwidth = GridBagConstraints.REMAINDER;
480      p.add(Box.createHorizontalGlue(), gbc);
481
482      titlePanel = p;
483    }
484    return titlePanel;
485  }
486
487  /**
488   * Creates and returns the instructions panel.
489   * @return the instructions panel.
490   */
491  protected Component createInstructionsPanel()
492  {
493    Component instructionsPanel = null;
494    LocalizableMessage instructions = getInstructions();
495    if (instructions != null)
496    {
497      JEditorPane p =
498          UIFactory.makeHtmlPane(instructions, UIFactory.INSTRUCTIONS_FONT);
499      p.setOpaque(false);
500      p.setEditable(false);
501      p.addHyperlinkListener(this);
502      instructionsPanel = p;
503    }
504    return instructionsPanel;
505  }
506
507  /**
508   * Returns <CODE>true</CODE> if there is URLWorker running for the given url
509   * and <CODE>false</CODE> otherwise.
510   * @param url the url.
511   * @return <CODE>true</CODE> if there is URLWorker running for the given url
512   * and <CODE>false</CODE> otherwise.
513   */
514  private boolean isURLWorkerRunning(String url)
515  {
516    return hmURLWorkers.get(url) != null;
517  }
518
519  /**
520   * Starts a worker.
521   * @param worker the URLWorker to be started.
522   */
523  private void startWorker(URLWorker worker)
524  {
525    hmURLWorkers.put(worker.getURL(), worker);
526    worker.startBackgroundTask();
527  }
528}
529