001/* 002 * $Id: JXTipOfTheDay.java 4147 2012-02-01 17:13:24Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx; 022 023import java.awt.Component; 024import java.awt.HeadlessException; 025import java.util.prefs.Preferences; 026 027import javax.swing.JDialog; 028 029import org.jdesktop.beans.JavaBean; 030import org.jdesktop.swingx.plaf.LookAndFeelAddons; 031import org.jdesktop.swingx.plaf.TipOfTheDayAddon; 032import org.jdesktop.swingx.plaf.TipOfTheDayUI; 033import org.jdesktop.swingx.tips.DefaultTipOfTheDayModel; 034import org.jdesktop.swingx.tips.TipOfTheDayModel; 035import org.jdesktop.swingx.tips.TipOfTheDayModel.Tip; 036 037/** 038 * Provides the "Tip of The Day" pane and dialog.<br> 039 * 040 * <p> 041 * Tips are retrieved from the {@link org.jdesktop.swingx.tips.TipOfTheDayModel}. 042 * In the most common usage, a tip (as returned by 043 * {@link org.jdesktop.swingx.tips.TipOfTheDayModel.Tip#getTip()}) is just a 044 * <code>String</code>. However, the return type of this method is actually 045 * <code>Object</code>. Its interpretation depends on its type: 046 * <dl compact> 047 * <dt>Component 048 * <dd>The <code>Component</code> is displayed in the dialog. 049 * <dt>Icon 050 * <dd>The <code>Icon</code> is wrapped in a <code>JLabel</code> and 051 * displayed in the dialog. 052 * <dt>others 053 * <dd>The object is converted to a <code>String</code> by calling its 054 * <code>toString</code> method. The result is wrapped in a 055 * <code>JEditorPane</code> or <code>JTextArea</code> and displayed. 056 * </dl> 057 * 058 * <p> 059 * <code>JXTipOfTheDay</code> finds its tips in its {@link org.jdesktop.swingx.tips.TipOfTheDayModel}. 060 * Such model can be programmatically built using {@link org.jdesktop.swingx.tips.DefaultTipOfTheDayModel} 061 * and {@link org.jdesktop.swingx.tips.DefaultTip} but 062 * the {@link org.jdesktop.swingx.tips.TipLoader} provides a convenient method to 063 * build a model and its tips from a {@link java.util.Properties} object. 064 * 065 * <p> 066 * Example: 067 * <p> 068 * Let's consider a file <i>tips.properties</i> with the following content: 069 * <pre> 070 * <code> 071 * tip.1.description=This is the first time! Plain text. 072 * tip.2.description=<html>This is <b>another tip</b>, it uses HTML! 073 * tip.3.description=A third one 074 * </code> 075 * </pre> 076 * 077 * To load and display the tips: 078 * 079 * <pre> 080 * <code> 081 * Properties tips = new Properties(); 082 * tips.load(new FileInputStream("tips.properties")); 083 * 084 * TipOfTheDayModel model = TipLoader.load(tips); 085 * JXTipOfTheDay totd = new JXTipOfTheDay(model); 086 * 087 * totd.showDialog(someParentComponent); 088 * </code> 089 * </pre> 090 * 091 * <p> 092 * Additionally, <code>JXTipOfTheDay</code> features an option enabling the end-user 093 * to choose to not display the "Tip Of The Day" dialog. This user choice can be stored 094 * in the user {@link java.util.prefs.Preferences} but <code>JXTipOfTheDay</code> also 095 * supports custom storage through the {@link org.jdesktop.swingx.JXTipOfTheDay.ShowOnStartupChoice} interface. 096 * 097 * <pre> 098 * <code> 099 * Preferences userPreferences = Preferences.userRoot().node("myApp"); 100 * totd.showDialog(someParentComponent, userPreferences); 101 * </code> 102 * </pre> 103 * In this code, the first time showDialog is called, the dialog will be made 104 * visible and the user will have the choice to not display it again in the future 105 * (usually this is controlled by a checkbox "Show tips on startup"). If the user 106 * unchecks the option, subsequent calls to showDialog will not display the dialog. 107 * As the choice is saved in the user Preferences, it will persist when the application is relaunched. 108 * 109 * @see org.jdesktop.swingx.tips.TipLoader 110 * @see org.jdesktop.swingx.tips.TipOfTheDayModel 111 * @see org.jdesktop.swingx.tips.TipOfTheDayModel.Tip 112 * @see #showDialog(Component, Preferences) 113 * @see #showDialog(Component, ShowOnStartupChoice) 114 * 115 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a> 116 */ 117@JavaBean 118public class JXTipOfTheDay extends JXPanel { 119 120 /** 121 * JXTipOfTheDay pluggable UI key <i>swingx/TipOfTheDayUI</i> 122 */ 123 public final static String uiClassID = "swingx/TipOfTheDayUI"; 124 125 // ensure at least the default ui is registered 126 static { 127 LookAndFeelAddons.contribute(new TipOfTheDayAddon()); 128 } 129 130 /** 131 * Key used to store the status of the "Show tip on startup" checkbox" 132 */ 133 public static final String PREFERENCE_KEY = "ShowTipOnStartup"; 134 135 /** 136 * Used when generating PropertyChangeEvents for the "currentTip" property 137 */ 138 public static final String CURRENT_TIP_CHANGED_KEY = "currentTip"; 139 140 private TipOfTheDayModel model; 141 private int currentTip = 0; 142 143 /** 144 * Constructs a new <code>JXTipOfTheDay</code> with an empty 145 * TipOfTheDayModel 146 */ 147 public JXTipOfTheDay() { 148 this(new DefaultTipOfTheDayModel(new Tip[0])); 149 } 150 151 /** 152 * Constructs a new <code>JXTipOfTheDay</code> showing tips from the given 153 * TipOfTheDayModel. 154 * 155 * @param model 156 */ 157 public JXTipOfTheDay(TipOfTheDayModel model) { 158 this.model = model; 159 updateUI(); 160 } 161 162 /** 163 * Notification from the <code>UIManager</code> that the L&F has changed. 164 * Replaces the current UI object with the latest version from the 165 * <code>UIManager</code>. 166 * 167 * @see javax.swing.JComponent#updateUI 168 */ 169 @Override 170public void updateUI() { 171 setUI((TipOfTheDayUI)LookAndFeelAddons.getUI(this, TipOfTheDayUI.class)); 172 } 173 174 /** 175 * Sets the L&F object that renders this component. 176 * 177 * @param ui 178 * the <code>TipOfTheDayUI</code> L&F object 179 * @see javax.swing.UIDefaults#getUI 180 * 181 * @beaninfo bound: true hidden: true description: The UI object that 182 * implements the taskpane group's LookAndFeel. 183 */ 184 public void setUI(TipOfTheDayUI ui) { 185 super.setUI(ui); 186 } 187 188 /** 189 * Gets the UI object which implements the L&F for this component. 190 * 191 * @return the TipOfTheDayUI object that implements the TipOfTheDayUI L&F 192 */ 193 @Override 194 public TipOfTheDayUI getUI() { 195 return (TipOfTheDayUI)ui; 196 } 197 198 /** 199 * Returns the name of the L&F class that renders this component. 200 * 201 * @return the string {@link #uiClassID} 202 * @see javax.swing.JComponent#getUIClassID 203 * @see javax.swing.UIDefaults#getUI 204 */ 205 @Override 206 public String getUIClassID() { 207 return uiClassID; 208 } 209 210 public TipOfTheDayModel getModel() { 211 return model; 212 } 213 214 public void setModel(TipOfTheDayModel model) { 215 if (model == null) { 216 throw new IllegalArgumentException("model can not be null"); 217 } 218 TipOfTheDayModel old = this.model; 219 this.model = model; 220 firePropertyChange("model", old, model); 221 } 222 223 public int getCurrentTip() { 224 return currentTip; 225 } 226 227 /** 228 * Sets the index of the tip to show 229 * 230 * @param currentTip 231 * @throws IllegalArgumentException if currentTip is not within the bounds [0, 232 * getModel().getTipCount()[. 233 */ 234 public void setCurrentTip(int currentTip) { 235 if (currentTip < 0 || currentTip >= getModel().getTipCount()) { 236 throw new IllegalArgumentException( 237 "Current tip must be within the bounds [0, " + getModel().getTipCount() 238 + "]"); 239 } 240 241 int oldTip = this.currentTip; 242 this.currentTip = currentTip; 243 firePropertyChange(CURRENT_TIP_CHANGED_KEY, oldTip, currentTip); 244 } 245 246 /** 247 * Shows the next tip in the list. It cycles the tip list. 248 */ 249 public void nextTip() { 250 int count = getModel().getTipCount(); 251 if (count == 0) { return; } 252 253 int nextTip = currentTip + 1; 254 if (nextTip >= count) { 255 nextTip = 0; 256 } 257 setCurrentTip(nextTip); 258 } 259 260 /** 261 * Shows the previous tip in the list. It cycles the tip list. 262 */ 263 public void previousTip() { 264 int count = getModel().getTipCount(); 265 if (count == 0) { return; } 266 267 int previousTip = currentTip - 1; 268 if (previousTip < 0) { 269 previousTip = count - 1; 270 } 271 setCurrentTip(previousTip); 272 } 273 274 /** 275 * Pops up a "Tip of the day" dialog. 276 * 277 * @param parentComponent 278 * @exception HeadlessException 279 * if GraphicsEnvironment.isHeadless() returns true. 280 * @see java.awt.GraphicsEnvironment#isHeadless 281 */ 282 public void showDialog(Component parentComponent) throws HeadlessException { 283 showDialog(parentComponent, (ShowOnStartupChoice)null); 284 } 285 286 /** 287 * Pops up a "Tip of the day" dialog. Additionally, it saves the state of the 288 * "Show tips on startup" checkbox in a key named "ShowTipOnStartup" in the 289 * given Preferences. 290 * 291 * @param parentComponent 292 * @param showOnStartupPref 293 * @exception HeadlessException 294 * if GraphicsEnvironment.isHeadless() returns true. 295 * @throws IllegalArgumentException 296 * if showOnStartupPref is null 297 * @see java.awt.GraphicsEnvironment#isHeadless 298 * @return true if the user chooses to see the tips again, false otherwise. 299 */ 300 public boolean showDialog(Component parentComponent, 301 Preferences showOnStartupPref) throws HeadlessException { 302 return showDialog(parentComponent, showOnStartupPref, false); 303 } 304 305 /** 306 * Pops up a "Tip of the day" dialog. Additionally, it saves the state of the 307 * "Show tips on startup" checkbox in a key named "ShowTipOnStartup" in the 308 * given Preferences. 309 * 310 * @param parentComponent 311 * @param showOnStartupPref 312 * @param force 313 * if true, the dialog is displayed even if the Preferences is set to 314 * hide the dialog 315 * @exception HeadlessException 316 * if GraphicsEnvironment.isHeadless() returns true. 317 * @throws IllegalArgumentException 318 * if showOnStartupPref is null 319 * @see java.awt.GraphicsEnvironment#isHeadless 320 * @return true if the user chooses to see the tips again, false 321 * otherwise. 322 */ 323 public boolean showDialog(Component parentComponent, 324 final Preferences showOnStartupPref, boolean force) throws HeadlessException { 325 if (showOnStartupPref == null) { throw new IllegalArgumentException( 326 "Preferences can not be null"); } 327 328 ShowOnStartupChoice store = new ShowOnStartupChoice() { 329 @Override 330 public boolean isShowingOnStartup() { 331 return showOnStartupPref.getBoolean(PREFERENCE_KEY, true); 332 } 333 @Override 334 public void setShowingOnStartup(boolean showOnStartup) { 335 if (showOnStartup && !showOnStartupPref.getBoolean(PREFERENCE_KEY, true)) { 336 // if the choice was previously not enable and now we re-enable it, we 337 // must remove the key 338 showOnStartupPref.remove(PREFERENCE_KEY); 339 } else if (!showOnStartup) { 340 // user does not want to see the tip 341 showOnStartupPref.putBoolean(PREFERENCE_KEY, showOnStartup); 342 } 343 } 344 }; 345 return showDialog(parentComponent, store, force); 346 } 347 348 /** 349 * Pops up a "Tip of the day" dialog. 350 * 351 * If <code>choice</code> is not null, the method first checks if 352 * {@link ShowOnStartupChoice#isShowingOnStartup()} is true before showing the 353 * dialog. 354 * 355 * Additionally, it saves the state of the "Show tips on startup" checkbox 356 * using the given {@link ShowOnStartupChoice} object. 357 * 358 * @param parentComponent 359 * @param choice 360 * @exception HeadlessException 361 * if GraphicsEnvironment.isHeadless() returns true. 362 * @see java.awt.GraphicsEnvironment#isHeadless 363 * @return true if the user chooses to see the tips again, false otherwise. 364 */ 365 public boolean showDialog(Component parentComponent, 366 ShowOnStartupChoice choice) { 367 return showDialog(parentComponent, choice, false); 368 } 369 370 /** 371 * Pops up a "Tip of the day" dialog. 372 * 373 * If <code>choice</code> is not null, the method first checks if 374 * <code>force</code> is true or if 375 * {@link ShowOnStartupChoice#isShowingOnStartup()} is true before showing the 376 * dialog. 377 * 378 * Additionally, it saves the state of the "Show tips on startup" checkbox 379 * using the given {@link ShowOnStartupChoice} object. 380 * 381 * @param parentComponent 382 * @param choice 383 * @param force 384 * if true, the dialog is displayed even if 385 * {@link ShowOnStartupChoice#isShowingOnStartup()} is false 386 * @exception HeadlessException 387 * if GraphicsEnvironment.isHeadless() returns true. 388 * @see java.awt.GraphicsEnvironment#isHeadless 389 * @return true if the user chooses to see the tips again, false otherwise. 390 */ 391 public boolean showDialog(Component parentComponent, 392 ShowOnStartupChoice choice, boolean force) { 393 if (choice == null) { 394 JDialog dialog = createDialog(parentComponent, choice); 395 dialog.setVisible(true); 396 dialog.dispose(); 397 return true; 398 } else if (force || choice.isShowingOnStartup()) { 399 JDialog dialog = createDialog(parentComponent, choice); 400 dialog.setVisible(true); 401 dialog.dispose(); 402 return choice.isShowingOnStartup(); 403 } else { 404 return false; 405 } 406 } 407 408 /** 409 * @param showOnStartupPref 410 * @return true if the key named "ShowTipOnStartup" is not set to false 411 */ 412 public static boolean isShowingOnStartup(Preferences showOnStartupPref) { 413 return showOnStartupPref.getBoolean(PREFERENCE_KEY, true); 414 } 415 416 /** 417 * Removes the value set for "ShowTipOnStartup" in the given Preferences to 418 * ensure the dialog shown by a later call to 419 * {@link #showDialog(Component, Preferences)} will be visible to the user. 420 * 421 * @param showOnStartupPref 422 */ 423 public static void forceShowOnStartup(Preferences showOnStartupPref) { 424 showOnStartupPref.remove(PREFERENCE_KEY); 425 } 426 427 /** 428 * Calls 429 * {@link TipOfTheDayUI#createDialog(Component, JXTipOfTheDay.ShowOnStartupChoice)}. 430 * 431 * This method can be overriden in order to control things such as the 432 * placement of the dialog or its title. 433 * 434 * @param parentComponent 435 * @param choice 436 * @return a JDialog to show this TipOfTheDay pane 437 */ 438 protected JDialog createDialog(Component parentComponent, 439 ShowOnStartupChoice choice) { 440 return getUI().createDialog(parentComponent, choice); 441 } 442 443 /** 444 * Used in conjunction with the 445 * {@link JXTipOfTheDay#showDialog(Component, ShowOnStartupChoice)} to save the 446 * "Show tips on startup" choice. 447 */ 448 public static interface ShowOnStartupChoice { 449 450 /** 451 * Persists the user choice 452 * @param showOnStartup the user choice 453 */ 454 void setShowingOnStartup(boolean showOnStartup); 455 456 /** 457 * @return the previously stored user choice 458 */ 459 boolean isShowingOnStartup(); 460 } 461 462}