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