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 }