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