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