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