001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, Institut de Recherche pour le Développement
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: package org.geotools.gui.swing;
018:
019: // User interface
020: import java.awt.Dimension;
021: import java.awt.Component;
022: import java.awt.EventQueue;
023: import java.awt.GridLayout;
024: import java.awt.BorderLayout;
025: import java.awt.GridBagLayout;
026: import java.awt.GridBagConstraints;
027: import javax.swing.event.ListDataListener;
028: import javax.swing.tree.DefaultMutableTreeNode;
029: import javax.swing.tree.MutableTreeNode;
030: import javax.swing.AbstractListModel;
031: import javax.swing.BorderFactory;
032: import javax.swing.JOptionPane;
033: import javax.swing.JTabbedPane;
034: import javax.swing.JScrollPane;
035: import javax.swing.JComponent;
036: import javax.swing.ImageIcon;
037: import javax.swing.JPanel;
038: import javax.swing.JLabel;
039: import javax.swing.JList;
040: import javax.swing.JTree;
041: import javax.swing.Icon;
042:
043: // Input/output
044: import java.net.URL;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import javax.imageio.spi.IIORegistry;
048: import javax.imageio.spi.ImageReaderSpi;
049: import javax.imageio.spi.ImageWriterSpi;
050: import javax.imageio.spi.ImageReaderWriterSpi;
051:
052: // Manifest
053: import java.util.jar.Manifest;
054: import java.util.jar.Attributes;
055:
056: // Formatting
057: import java.text.DateFormat;
058: import java.text.FieldPosition;
059: import java.text.ParseException;
060: import java.text.SimpleDateFormat;
061:
062: // Miscellaneous
063: import java.util.Map;
064: import java.util.Date;
065: import java.util.Locale;
066: import java.util.Arrays;
067: import java.util.TreeMap;
068: import java.util.Iterator;
069:
070: // Java Advanced Imaging
071: import javax.media.jai.JAI;
072:
073: // Geotools dependencies
074: import org.geotools.util.logging.Logging;
075: import org.geotools.resources.XArray;
076: import org.geotools.resources.Arguments;
077: import org.geotools.resources.SwingUtilities;
078: import org.geotools.resources.i18n.Vocabulary;
079: import org.geotools.resources.i18n.VocabularyKeys;
080: import org.geotools.gui.swing.image.RegisteredOperationBrowser;
081:
082: /**
083: * An "About" dialog box. This dialog box contains the application's title and some
084: * system informations (Java and OS version, free memory, image readers and writers, running
085: * threads, etc.). The application version can be fetched from a {@link Manifest} object,
086: * usually build from the {@code META-INF/Manifest.mf} file. This manifest should contains
087: * entries for {@code Implementation-Title}, {@code Implementation-Version} and
088: * {@code Implementation-Vendor} values, as suggested in the
089: * <A HREF="http://java.sun.com/docs/books/tutorial/jar/basics/manifest.html#versioning">Java
090: * tutorial</A>.
091: * In addition to the above-cited standard entries, the {@code About} class understand also
092: * an optional {@code Implementation-Date} entry. This entry can contains the product date
093: * in the <code>"yyyy-MM-dd HH:mm:ss"</code> patter. If presents, this date will be localized
094: * according user's locale and appended to the version number.
095: *
096: * <p> </p>
097: * <p align="center"><img src="doc-files/About.png"></p>
098: * <p> </p>
099: *
100: * @since 2.0
101: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/About.java $
102: * @version $Id: About.java 27848 2007-11-12 13:10:32Z desruisseaux $
103: * @author Martin Desruisseaux
104: */
105: public class About extends JPanel {
106: /**
107: * The amount of bytes in one "unit of memory" to be displayed.
108: */
109: private static final float HEAP_SIZE_UNIT = (1024f * 1024f);
110:
111: /**
112: * The entry for timestamp in the manifest file.
113: */
114: private static final String TIMESTAMP = "Implementation-Date";
115:
116: /**
117: * Thread qui aura la charge de faire des mises à jour en arrière-plan.
118: * Ce champ sera {@code null} s'il n'y en a pas.
119: */
120: private final ThreadList updater;
121:
122: /**
123: * The localized resources to use.
124: */
125: private final Vocabulary resources;
126:
127: /**
128: * Construct a new dialog box with the Geotools's logo.
129: */
130: public About() {
131: this ("org/geotools/resources/logo/Geotools.png", About.class,
132: null);
133: }
134:
135: /**
136: * Constructs a new dialog box for the specified application class. This constructor
137: * uses the class loader for loading the manifest file. It also use the class package
138: * to find the right entry into the manifest.
139: *
140: * @param logo The application's logo. It may be a {@link JComponent}, an
141: * {@link Icon} object or an resource path (i.e. a file to be
142: * fetch in the classpath) as a {@link String}.
143: * @param application The application's class. Application name will be fetch
144: * from the manifest file ({@code META-INF/Manifest.mf}).
145: * @param tasks Group of running threads, or {@code null} if there is none.
146: */
147: public About(final Object logo, final Class application,
148: final ThreadGroup tasks) {
149: this (logo, getAttributes(application), application
150: .getClassLoader(), tasks);
151: }
152:
153: /**
154: * Constructs a new dialog box from the specified manifest attributes.
155: *
156: * @param logo The application's logo. It may be a {@link JComponent}, an
157: * {@link Icon} object or an resource path (i.e. a file to be
158: * fetch in the classpath) as a {@link String}.
159: * @param attributes The manifest attributes containing application name and version number.
160: * @param tasks Group of running threads, or {@code null} if there is none.
161: */
162: public About(final Object logo, final Attributes attributes,
163: final ThreadGroup tasks) {
164: this (logo, attributes, null, tasks);
165: }
166:
167: /**
168: * Constructs a new dialog box.
169: *
170: * @param logo The application's logo. It may be a {@link JComponent}, an
171: * {@link Icon} object or an resource path (i.e. a file to be
172: * fetch in the classpath) as a {@link String}.
173: * @param attributes The manifest attributes containing application name and version number.
174: * @param loader The application's class loader.
175: * @param tasks Group of running threads, or {@code null} if there is none.
176: */
177: private About(final Object logo, final Attributes attributes,
178: ClassLoader loader, final ThreadGroup tasks) {
179: super (new GridBagLayout());
180: final Locale locale = getDefaultLocale();
181: resources = Vocabulary.getResources(locale);
182: if (loader == null) {
183: loader = getClass().getClassLoader();
184: // TODO: it would be nice to fetch the caller's class loader instead
185: }
186: /*
187: * Get the free memory before any futher work.
188: */
189: final Runtime system = Runtime.getRuntime();
190: system.gc();
191: final float freeMemory = system.freeMemory() / HEAP_SIZE_UNIT;
192: final float totalMemory = system.totalMemory() / HEAP_SIZE_UNIT;
193: /*
194: * Get application's name, version and vendor from the manifest attributes.
195: * If an implementation date is specified, append it to the version string.
196: */
197: String application = attributes
198: .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
199: String version = attributes
200: .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
201: String vendor = attributes
202: .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
203: try {
204: final String dateString = attributes.getValue(TIMESTAMP);
205: if (dateString != null) {
206: final Date date = getDateFormat().parse(dateString);
207: final DateFormat format = DateFormat
208: .getDateInstance(DateFormat.LONG);
209: if (version != null && version.trim().length() != 0) {
210: StringBuffer buffer = new StringBuffer(version);
211: buffer.append(" (");
212: buffer = format.format(date, buffer,
213: new FieldPosition(0));
214: buffer.append(')');
215: version = buffer.toString();
216: } else {
217: version = format.format(date);
218: }
219: }
220: } catch (ParseException exception) {
221: /*
222: * The implementation date can't be parsed. This is not a show-stopper;
223: * the "About" dialog box will just not includes the implementation date.
224: */
225: Logging.unexpectedException("org.geotools.gui.swing",
226: About.class, "<init>", exception);
227: }
228: /*
229: * If the user supplied a logo, load it and display it in the dialog's upper part (NORTH).
230: * The tabbed pane will be added below the logo, in the dialog's central part (CENTER).
231: */
232: final GridBagConstraints gc = new GridBagConstraints();
233: if (logo != null) {
234: final JComponent title;
235: if (logo instanceof JComponent) {
236: title = (JComponent) logo;
237: } else if (logo instanceof Icon) {
238: title = new JLabel((Icon) logo);
239: } else {
240: final String text = String.valueOf(logo);
241: final URL url = loader.getResource(text);
242: if (url == null) {
243: final JLabel label = new JLabel(text);
244: label.setHorizontalAlignment(JLabel.CENTER);
245: label.setBorder(BorderFactory
246: .createEmptyBorder(6/*top*/, 6/*left*/,
247: 6/*bottom*/, 6/*right*/));
248: title = label;
249: } else {
250: title = new JLabel(new ImageIcon(url));
251: }
252: }
253: title.setBorder(BorderFactory.createCompoundBorder(
254: BorderFactory.createEmptyBorder(0/*top*/,
255: 0/*left*/, 6/*bottom*/, 0/*right*/),
256: BorderFactory.createCompoundBorder(BorderFactory
257: .createLoweredBevelBorder(), title
258: .getBorder())));
259: gc.gridx = 0;
260: gc.gridy = 0;
261: gc.weightx = 1;
262: gc.insets.top = 9;
263: add(title, gc);
264: }
265: final JTabbedPane tabs = new JTabbedPane();
266: final JLabel totalMemoryLabel = new JLabel(resources.getString(
267: VocabularyKeys.MEMORY_HEAP_SIZE_$1, new Float(
268: totalMemory)));
269: final JLabel percentUsedLabel = new JLabel(resources.getString(
270: VocabularyKeys.MEMORY_HEAP_USAGE_$1, new Float(1
271: - freeMemory / totalMemory)));
272: gc.gridx = 0;
273: gc.gridy = 1;
274: gc.weightx = 1;
275: gc.weighty = 1;
276: gc.fill = gc.BOTH;
277: add(tabs, gc);
278: /*
279: * MAIN TAB (Application name and version informations)
280: */
281: if (true) {
282: final JPanel pane = new JPanel(new GridBagLayout());
283: final GridBagConstraints c = new GridBagConstraints();
284: c.gridx = 0;
285: c.weightx = 1;
286: c.gridy = 0;
287: c.insets.top = 12;
288: pane.add(new JLabel(application), c);
289: c.gridy++;
290: c.insets.top = 0;
291: pane.add(new JLabel(resources.getString(
292: VocabularyKeys.VERSION_$1, version)), c);
293: c.gridy++;
294: pane.add(new JLabel(vendor), c);
295: c.gridy++;
296: c.insets.top = 6;
297: pane.add(new JLabel(resources.getString(
298: VocabularyKeys.JAVA_VERSION_$1, System
299: .getProperty("java.version"))), c);
300: c.gridy++;
301: c.insets.top = 0;
302: pane.add(new JLabel(resources.getString(
303: VocabularyKeys.JAVA_VENDOR_$1, System
304: .getProperty("java.vendor"))), c);
305: c.gridy++;
306: c.insets.top = 6;
307: pane.add(new JLabel(resources.getString(
308: VocabularyKeys.OS_NAME_$1, System
309: .getProperty("os.name"))), c);
310: c.gridy++;
311: c.insets.top = 0;
312: pane.add(new JLabel(resources.getString(
313: VocabularyKeys.OS_VERSION_$2, System
314: .getProperty("os.version"), System
315: .getProperty("os.arch"))), c);
316: c.gridy++;
317: c.insets.top = 12;
318: pane.add(new JLabel(resources.getString(
319: VocabularyKeys.TILE_CACHE_CAPACITY_$1, new Float(
320: JAI.getDefaultInstance().getTileCache()
321: .getMemoryCapacity()
322: / HEAP_SIZE_UNIT))), c);
323: c.gridy++;
324: c.insets.top = 0;
325: pane.add(totalMemoryLabel, c);
326: c.gridy++;
327: c.insets.bottom = 12;
328: pane.add(percentUsedLabel, c);
329: tabs.addTab(resources.getString(VocabularyKeys.SYSTEM),
330: pane);
331: }
332: /*
333: * RUNNING TASKS TAB
334: */
335: if (tasks != null) {
336: updater = new ThreadList(tasks, totalMemoryLabel,
337: percentUsedLabel, resources);
338: final JPanel pane = new JPanel(new BorderLayout());
339: final JList list = new JList(updater);
340: pane.add(new JLabel(resources
341: .getString(VocabularyKeys.RUNNING_TASKS)),
342: BorderLayout.NORTH);
343: pane.add(new JScrollPane(list), BorderLayout.CENTER);
344: pane.setBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9));
345: tabs
346: .addTab(resources.getString(VocabularyKeys.TASKS),
347: pane);
348: } else {
349: updater = null;
350: }
351: /*
352: * IMAGE ENCODERS/DECODERS TAB
353: */
354: if (true) {
355: final StringBuffer buffer = new StringBuffer();
356: final Map mimes = new TreeMap();
357: boolean writer = false;
358: do {
359: final int titleKey;
360: final Class category;
361: if (writer) {
362: titleKey = VocabularyKeys.ENCODERS;
363: category = ImageWriterSpi.class;
364: } else {
365: titleKey = VocabularyKeys.DECODERS;
366: category = ImageReaderSpi.class;
367: }
368: String title = resources.getString(titleKey);
369: Iterator it = IIORegistry.getDefaultInstance()
370: .getServiceProviders(category, true);
371: while (it.hasNext()) {
372: final ImageReaderWriterSpi spi = (ImageReaderWriterSpi) it
373: .next();
374: final String name = spi.getDescription(locale);
375: final String[] mimeTypes = spi.getMIMETypes();
376: patchMimes(mimeTypes);
377: for (int i = 0; i < mimeTypes.length; i++) {
378: final String mimeType = mimeTypes[i];
379: DefaultMutableTreeNode child = (DefaultMutableTreeNode) mimes
380: .get(mimeType);
381: if (child == null) {
382: child = new DefaultMutableTreeNode(mimeType);
383: mimes.put(mimeType, child);
384: }
385: child.add(new DefaultMutableTreeNode(name,
386: false));
387: }
388: if (title != null && mimeTypes.length != 0) {
389: if (buffer.length() != 0) {
390: buffer.append(" / ");
391: }
392: buffer.append(title);
393: title = null;
394: }
395: }
396: } while ((writer = !writer) == true);
397: final String title = buffer.toString();
398: final DefaultMutableTreeNode root = new DefaultMutableTreeNode(
399: title);
400: for (final Iterator it = mimes.values().iterator(); it
401: .hasNext();) {
402: root.add((DefaultMutableTreeNode) it.next());
403: }
404: JComponent tree = new JTree(root);
405: tree.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 0));
406: tree = new JScrollPane(tree);
407: tabs.addTab(resources.getString(VocabularyKeys.IMAGES),
408: setup(tree));
409: }
410: /*
411: * JAI OPERATIONS TAB
412: */
413: if (true) {
414: final JComponent tree = new RegisteredOperationBrowser();
415: tabs.addTab(resources.getString(VocabularyKeys.OPERATIONS),
416: setup(tree));
417: }
418: }
419:
420: /**
421: * Setup the border for the specified component.
422: */
423: private static JComponent setup(JComponent component) {
424: component.setBorder(BorderFactory.createCompoundBorder(
425: BorderFactory.createEmptyBorder(3, 3, 3, 3), component
426: .getBorder()));
427: component.setPreferredSize(new Dimension(200, 200));
428: return component;
429: }
430:
431: /**
432: * Patch the mime type, replacing "" by "(raw)" for JAI I/O codec.
433: */
434: private static void patchMimes(String[] mimes) {
435: for (int i = 0; i < mimes.length; i++) {
436: String name = mimes[i].trim();
437: if (name.length() == 0) {
438: name = "(raw)";
439: }
440: mimes[i] = name;
441: }
442: }
443:
444: /**
445: * Returns attribute for the specified class.
446: */
447: private static Attributes getAttributes(final Class classe) {
448: InputStream stream = classe.getClassLoader()
449: .getResourceAsStream("META-INF/Manifest.mf");
450: if (stream != null)
451: try {
452: final Manifest manifest = new Manifest(stream);
453: stream.close();
454: String name = classe.getName().replace('.', '/');
455: int index;
456: while ((index = name.lastIndexOf('/')) >= 0) {
457: final Attributes attributes = manifest
458: .getAttributes(name.substring(0, index + 1));
459: if (attributes != null)
460: return attributes;
461: name = name.substring(0, index);
462: }
463: return manifest.getMainAttributes();
464: } catch (IOException e) {
465: Logging.unexpectedException("org.geotools.gui.swing",
466: About.class, "getAttributes", e);
467: }
468: // Use empty manifest attributes.
469: return new Attributes();
470: }
471:
472: /**
473: * Returns a neutral date format for timestamp.
474: */
475: private static DateFormat getDateFormat() {
476: return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
477: Locale.CANADA);
478: }
479:
480: /**
481: * Modèle représentant la liste des processus actif dans un {@link ThreadGroup}.
482: * Cette liste se mettre automatiquement à jour de façon périodique.
483: *
484: * @version $Id: About.java 27848 2007-11-12 13:10:32Z desruisseaux $
485: * @author Martin Desruisseaux
486: */
487: private static final class ThreadList extends AbstractListModel
488: implements Runnable {
489: /**
490: * Processus qui met à jour {@code ThreadList}, ou {@code null} s'il n'y en a pas.
491: * On peut tuer le processus actif en donnant la valeur {@code null} à cette variable.
492: */
493: public transient Thread worker;
494:
495: /**
496: * Liste des processus en cours.
497: */
498: private final ThreadGroup tasks;
499:
500: /**
501: * Liste des noms des processus en cours. Cette liste sera mises à jour périodiquement.
502: */
503: private String[] names = new String[0];
504:
505: /**
506: * Texte dans lequel écrire la mémoire totale réservée.
507: */
508: private final JLabel totalMemory;
509:
510: /**
511: * Texte dans lequel écrire le pourcentage de mémoire utilisée.
512: */
513: private final JLabel percentUsed;
514:
515: /**
516: * The localized resources to use.
517: */
518: private final Vocabulary resources;
519:
520: /**
521: * Construit une liste des processus actifs dans le groupe {@code tasks} spécifié.
522: */
523: public ThreadList(final ThreadGroup tasks,
524: final JLabel totalMemory, final JLabel percentUsed,
525: final Vocabulary resources) {
526: this .tasks = tasks;
527: this .totalMemory = totalMemory;
528: this .percentUsed = percentUsed;
529: this .resources = resources;
530: }
531:
532: /**
533: * Retourne le nombre d'éléments dans la liste.
534: */
535: public int getSize() { // NO synchronized here
536: return names.length;
537: }
538:
539: /**
540: * Retourne un des éléments de la liste.
541: */
542: public Object getElementAt(final int index) { // NO synchronized here
543: return names[index];
544: }
545:
546: /**
547: * Ajoute un objet à la liste des objets intéressés
548: * à être informé des changements apportés à la liste.
549: */
550: public synchronized void addListDataListener(
551: final ListDataListener listener) {
552: super .addListDataListener(listener);
553: }
554:
555: /**
556: * Démarre le thread.
557: */
558: public synchronized void start() {
559: if (worker == null) {
560: worker = new Thread(this , resources
561: .getString(VocabularyKeys.ABOUT));
562: worker.setPriority(Thread.MIN_PRIORITY);
563: worker.setDaemon(true);
564: worker.start();
565: }
566: }
567:
568: /**
569: * Met à jour le contenu de la liste à interval régulier.
570: * Cette méthode est exécutée dans une boucle jusqu'à ce
571: * qu'elle soit interrompue en donnant la valeur nulle à
572: * {@link #tasks}.
573: */
574: public synchronized void run() {
575: String oldTotalMemory = null;
576: String oldPercentUsed = null;
577: while (worker == Thread.currentThread()
578: && listenerList.getListenerCount() != 0) {
579: final Runtime system = Runtime.getRuntime();
580: final float freeMemoryN = system.freeMemory()
581: / HEAP_SIZE_UNIT;
582: final float totalMemoryN = system.totalMemory()
583: / HEAP_SIZE_UNIT;
584: String totalMemoryText = resources.getString(
585: VocabularyKeys.MEMORY_HEAP_SIZE_$1, new Float(
586: totalMemoryN));
587: String percentUsedText = resources.getString(
588: VocabularyKeys.MEMORY_HEAP_USAGE_$1, new Float(
589: 1 - freeMemoryN / totalMemoryN));
590:
591: Thread[] threadArray = new Thread[tasks.activeCount()];
592: String[] threadNames = new String[tasks
593: .enumerate(threadArray)];
594: int c = 0;
595: for (int i = 0; i < threadNames.length; i++) {
596: if (threadArray[i] != worker) {
597: threadNames[c++] = threadArray[i].getName();
598: }
599: }
600: threadNames = (String[]) XArray.resize(threadNames, c);
601:
602: if (Arrays.equals(names, threadNames)) {
603: threadNames = null;
604: }
605: if (totalMemoryText.equals(oldTotalMemory)) {
606: totalMemoryText = null;
607: } else {
608: oldTotalMemory = totalMemoryText;
609: }
610: if (percentUsedText.equals(oldPercentUsed)) {
611: percentUsedText = null;
612: } else {
613: oldPercentUsed = percentUsedText;
614: }
615: if (threadNames != null || totalMemoryText != null
616: || percentUsedText != null) {
617: final String[] names = threadNames;
618: final String totalMemory = totalMemoryText;
619: final String percentUsed = percentUsedText;
620: EventQueue.invokeLater(new Runnable() {
621: public void run() {
622: update(names, totalMemory, percentUsed);
623: }
624: });
625: }
626: try {
627: wait(4000);
628: } catch (InterruptedException exception) {
629: // Quelqu'un a réveillé ce thread. Retourne au travail.
630: }
631: }
632: worker = null;
633: }
634:
635: /**
636: * Met à jour le contenu de la liste. Cette méthode
637: * est appelée périodiquement dans le thread de Swing.
638: */
639: private synchronized void update(final String[] newNames,
640: final String totalMemory, final String percentUsed) {
641: if (newNames != null) {
642: final int count = Math.max(names.length,
643: newNames.length);
644: names = newNames;
645: fireContentsChanged(this , 0, count - 1);
646: }
647: if (totalMemory != null)
648: this .totalMemory.setText(totalMemory);
649: if (percentUsed != null)
650: this .percentUsed.setText(percentUsed);
651: }
652: }
653:
654: /**
655: * Popups the dialog box and wait for the user. This method
656: * always invoke {@link #start} before showing the dialog,
657: * and {@link #stop} after disposing it.
658: */
659: public void showDialog(final Component owner) {
660: try {
661: start();
662: SwingUtilities.showMessageDialog(owner, this , resources
663: .getMenuLabel(VocabularyKeys.ABOUT),
664: JOptionPane.PLAIN_MESSAGE);
665: } finally {
666: stop();
667: }
668: }
669:
670: /**
671: * Start a daemon thread updating dialog box information. Updated information include
672: * available memory and the list of running tasks. <strong>You <u>must</u> invoke the
673: * {@link #stop} method after {@code start()}</strong> (typically in a {@code try..finally}
674: * construct) in order to free resources. {@code stop()} is not automatically
675: * invoked by the garbage collector.
676: */
677: protected void start() {
678: final ThreadList updater = this .updater;
679: if (updater != null) {
680: updater.start();
681: }
682: }
683:
684: /**
685: * Free any resources used by this dialog box. <strong>This method must be invoked
686: * after {@link #start}</strong> in order to free resources. {@code stop()} is
687: * not automatically invoked by the garbage collector.
688: */
689: protected void stop() {
690: final ThreadList updater = this .updater;
691: if (updater != null) {
692: updater.worker = null; // Stop the thread.
693: }
694: // Le thread avait une référence indirecte vers 'this' via 'ListDataListener'
695: }
696:
697: /**
698: * Convenience method for setting the {@code Implementation-Date}
699: * attributes to the current date.
700: *
701: * @param attributes Attributes in which setting the compilation date.
702: */
703: public static void touch(final Attributes attributes) {
704: attributes.putValue(TIMESTAMP, getDateFormat().format(
705: new Date()));
706: }
707:
708: /**
709: * Display the default "About" dialog box. This method is usefull
710: * for testing the widget appareance and for checking system informations.
711: *
712: * @param args the command line arguments
713: */
714: public static void main(final String[] args) {
715: final Arguments arguments = new Arguments(args);
716: Locale.setDefault(arguments.locale);
717: new About().showDialog(null);
718: System.exit(0);
719: }
720: }
|