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-2016 ForgeRock AS.
016 */
017package org.opends.quicksetup.ui;
018
019import static org.opends.messages.QuickSetupMessages.*;
020
021import java.awt.CardLayout;
022import java.awt.Component;
023import java.awt.GridBagConstraints;
024import java.awt.GridBagLayout;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029
030import javax.swing.Box;
031import javax.swing.JEditorPane;
032import javax.swing.JLabel;
033import javax.swing.JPanel;
034import javax.swing.event.HyperlinkEvent;
035import javax.swing.event.HyperlinkListener;
036
037import org.forgerock.i18n.LocalizableMessage;
038import org.opends.quicksetup.ProgressDescriptor;
039import org.opends.quicksetup.UserData;
040import org.opends.quicksetup.event.ButtonActionListener;
041import org.opends.quicksetup.event.ButtonEvent;
042import org.opends.quicksetup.util.HtmlProgressMessageFormatter;
043import org.opends.quicksetup.util.ProgressMessageFormatter;
044import org.opends.quicksetup.util.URLWorker;
045
046/**
047 * This is an abstract class that is extended by all the classes that are in
048 * the CardLayout of CurrentStepPanel.  All the panels that appear on the
049 * top-right side of the dialog extend this class: WelcomePane, ReviewPanel,
050 * etc.
051 */
052public abstract class QuickSetupStepPanel extends QuickSetupPanel
053implements HyperlinkListener
054{
055  private static final long serialVersionUID = -1983448318085588324L;
056  private JPanel inputContainer;
057  private Component inputPanel;
058
059  private final Set<ButtonActionListener> buttonListeners = new HashSet<>();
060
061  private ProgressMessageFormatter formatter;
062
063  private static final String INPUT_PANEL = "input";
064  private static final String CHECKING_PANEL = "checking";
065
066  private boolean isCheckingVisible;
067
068  /**
069   * We can use a HashMap (not multi-thread safe) because all
070   * the calls to this object are done in the event-thread.
071  */
072  private final Map<String, URLWorker> hmURLWorkers = new HashMap<>();
073
074  /**
075   * Creates a default instance.
076   * @param application Application this panel represents
077   */
078  public QuickSetupStepPanel(GuiApplication application) {
079    super(application);
080  }
081
082  /**
083   * Initializes this panel.  Called soon after creation.  In general this
084   * is where maps should be populated etc.
085   */
086  public void initialize() {
087    createLayout();
088  }
089
090  /**
091   * Called just before the panel is shown: used to update the contents of the
092   * panel with new UserData (used in particular in the review panel).
093   *
094   * @param data the new user data.
095   */
096  public void beginDisplay(UserData data)
097  {
098    // no-op
099  }
100
101  /** Called just after the panel is shown: used to set focus properly. */
102  public void endDisplay()
103  {
104    // no-op
105  }
106
107  /**
108   * Tells whether the method beginDisplay can be long and so should be called
109   * outside the event thread.
110   * @return <CODE>true</CODE> if the method beginDisplay can be long and so
111   * should be called outside the event thread and <CODE>true</CODE> otherwise.
112   */
113  public boolean blockingBeginDisplay()
114  {
115    return false;
116  }
117
118  /**
119   * Called when a progress change must be reflected in the panels.  Only
120   * ProgressPanel overwrites this method and for all the others it stays empty.
121   * @param descriptor the descriptor of the Installation progress.
122   */
123  public void displayProgress(ProgressDescriptor descriptor)
124  {
125    // no-op
126  }
127
128  /**
129   * Implements HyperlinkListener.  When the user clicks on a link we will
130   * try to display the associated URL in the browser of the user.
131   *
132   * @param e the HyperlinkEvent.
133   */
134  @Override
135  public void hyperlinkUpdate(HyperlinkEvent e)
136  {
137    if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
138    {
139      String url = e.getURL().toString();
140      if (!isURLWorkerRunning(url))
141      {
142        /* Only launch the worker if there is not already a worker trying to display this URL. */
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  /** Creates the layout of the panel. */
274  private void createLayout()
275  {
276    setLayout(new GridBagLayout());
277
278    setOpaque(false);
279
280    GridBagConstraints gbc = new GridBagConstraints();
281
282    Component titlePanel = createTitlePanel();
283    Component instructionsPanel = createInstructionsPanel();
284    inputPanel = createInputPanel();
285
286    boolean somethingAdded = false;
287
288    if (titlePanel != null)
289    {
290      gbc.weightx = 1.0;
291      gbc.weighty = 0.0;
292      gbc.gridwidth = GridBagConstraints.REMAINDER;
293      gbc.fill = GridBagConstraints.HORIZONTAL;
294      gbc.anchor = GridBagConstraints.NORTHWEST;
295      gbc.insets.left = 0;
296      add(titlePanel, gbc);
297      somethingAdded = true;
298    }
299
300    if (instructionsPanel != null)
301    {
302      if (somethingAdded)
303      {
304        gbc.insets.top = UIFactory.TOP_INSET_PRIMARY_FIELD;
305      } else
306      {
307        gbc.insets.top = 0;
308      }
309      gbc.insets.left = 0;
310      gbc.weightx = 1.0;
311      gbc.weighty = 0.0;
312      gbc.gridwidth = GridBagConstraints.REMAINDER;
313      gbc.fill = GridBagConstraints.BOTH;
314      gbc.anchor = GridBagConstraints.NORTHWEST;
315      add(instructionsPanel, gbc);
316      somethingAdded = true;
317    }
318
319    if (inputPanel != null)
320    {
321      inputContainer = new JPanel(new CardLayout());
322      inputContainer.setOpaque(false);
323      if (requiresScroll())
324      {
325        inputContainer.add(UIFactory.createBorderLessScrollBar(inputPanel),
326            INPUT_PANEL);
327      }
328      else
329      {
330        inputContainer.add(inputPanel, INPUT_PANEL);
331      }
332
333      JPanel checkingPanel = UIFactory.makeJPanel();
334      checkingPanel.setLayout(new GridBagLayout());
335      checkingPanel.add(UIFactory.makeJLabel(UIFactory.IconType.WAIT,
336          INFO_GENERAL_CHECKING_DATA.get(),
337          UIFactory.TextStyle.PRIMARY_FIELD_VALID),
338          new GridBagConstraints());
339      inputContainer.add(checkingPanel, CHECKING_PANEL);
340
341      if (somethingAdded)
342      {
343        gbc.insets.top = UIFactory.TOP_INSET_INPUT_SUBPANEL;
344      } else
345      {
346        gbc.insets.top = 0;
347      }
348      gbc.weightx = 1.0;
349      gbc.weighty = 1.0;
350      gbc.gridwidth = GridBagConstraints.REMAINDER;
351      gbc.fill = GridBagConstraints.BOTH;
352      gbc.anchor = GridBagConstraints.NORTHWEST;
353      gbc.insets.left = 0;
354      add(inputContainer, gbc);
355    }
356    else
357    {
358      addVerticalGlue(this);
359    }
360  }
361
362  /**
363   * Creates and returns the panel that contains the layout specific to the
364   * panel.
365   * @return the panel that contains the layout specific to the
366   * panel.
367   */
368  protected abstract Component createInputPanel();
369
370  /**
371   * Returns the title of this panel.
372   * @return the title of this panel.
373   */
374  protected abstract LocalizableMessage getTitle();
375
376  /**
377   * Returns the instruction of this panel.
378   * @return the instruction of this panel.
379   */
380  protected abstract LocalizableMessage getInstructions();
381
382  /**
383   * Commodity method that adds a vertical glue at the bottom of a given panel.
384   * @param panel the panel to which we want to add a vertical glue.
385   */
386  protected void addVerticalGlue(JPanel panel)
387  {
388    GridBagConstraints gbc = new GridBagConstraints();
389    gbc.gridwidth = GridBagConstraints.REMAINDER;
390    gbc.insets = UIFactory.getEmptyInsets();
391    gbc.weighty = 1.0;
392    gbc.fill = GridBagConstraints.VERTICAL;
393    panel.add(Box.createVerticalGlue(), gbc);
394  }
395
396  /**
397   * This method is called by the URLWorker when it has finished its task.
398   * @param worker the URLWorker that finished its task.
399   */
400  public void urlWorkerFinished(URLWorker worker)
401  {
402    hmURLWorkers.remove(worker.getURL());
403  }
404
405  /**
406   * Tells whether the input panel should have a scroll or not.
407   * @return <CODE>true</CODE> if the input panel should have a scroll and
408   * <CODE>false</CODE> otherwise.
409   */
410  protected boolean requiresScroll()
411  {
412    return true;
413  }
414
415  /**
416   * Returns the formatter that will be used to display the messages in this
417   * panel.
418   * @return the formatter that will be used to display the messages in this
419   * panel.
420   */
421  ProgressMessageFormatter getFormatter()
422  {
423    if (formatter == null)
424    {
425      formatter = new HtmlProgressMessageFormatter();
426    }
427    return formatter;
428  }
429
430  /**
431   * Creates and returns the title panel.
432   * @return the title panel.
433   */
434  private Component createTitlePanel()
435  {
436    Component titlePanel = null;
437    LocalizableMessage title = getTitle();
438    if (title != null)
439    {
440      JPanel p = new JPanel(new GridBagLayout());
441      p.setOpaque(false);
442      GridBagConstraints gbc = new GridBagConstraints();
443      gbc.anchor = GridBagConstraints.NORTHWEST;
444      gbc.fill = GridBagConstraints.HORIZONTAL;
445      gbc.weightx = 0.0;
446      gbc.gridwidth = GridBagConstraints.RELATIVE;
447
448      JLabel l =
449          UIFactory.makeJLabel(UIFactory.IconType.NO_ICON, title,
450              UIFactory.TextStyle.TITLE);
451      p.add(l, gbc);
452
453      gbc.weightx = 1.0;
454      gbc.gridwidth = GridBagConstraints.REMAINDER;
455      p.add(Box.createHorizontalGlue(), gbc);
456
457      titlePanel = p;
458    }
459    return titlePanel;
460  }
461
462  /**
463   * Creates and returns the instructions panel.
464   * @return the instructions panel.
465   */
466  protected Component createInstructionsPanel()
467  {
468    Component instructionsPanel = null;
469    LocalizableMessage instructions = getInstructions();
470    if (instructions != null)
471    {
472      JEditorPane p =
473          UIFactory.makeHtmlPane(instructions, UIFactory.INSTRUCTIONS_FONT);
474      p.setOpaque(false);
475      p.setEditable(false);
476      p.addHyperlinkListener(this);
477      instructionsPanel = p;
478    }
479    return instructionsPanel;
480  }
481
482  /**
483   * Returns <CODE>true</CODE> if there is URLWorker running for the given url
484   * and <CODE>false</CODE> otherwise.
485   * @param url the url.
486   * @return <CODE>true</CODE> if there is URLWorker running for the given url
487   * and <CODE>false</CODE> otherwise.
488   */
489  private boolean isURLWorkerRunning(String url)
490  {
491    return hmURLWorkers.get(url) != null;
492  }
493
494  /**
495   * Starts a worker.
496   * @param worker the URLWorker to be started.
497   */
498  private void startWorker(URLWorker worker)
499  {
500    hmURLWorkers.put(worker.getURL(), worker);
501    worker.startBackgroundTask();
502  }
503}
504