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