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