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