001    /**
002     * ========================================
003     * JFreeReport : a free Java report library
004     * ========================================
005     *
006     * Project Info:  http://reporting.pentaho.org/
007     *
008     * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors.
009     *
010     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016     * See the GNU Lesser General Public License for more details.
017     *
018     * You should have received a copy of the GNU Lesser General Public License along with this
019     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020     * Boston, MA 02111-1307, USA.
021     *
022     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023     * in the United States and other countries.]
024     *
025     * ------------
026     * $Id: EncodingComboBoxModel.java 3525 2007-10-16 11:43:48Z tmorgner $
027     * ------------
028     * (C) Copyright 2000-2005, by Object Refinery Limited.
029     * (C) Copyright 2005-2007, by Pentaho Corporation.
030     */
031    package org.jfree.report.modules.gui.swing.common;
032    
033    import java.io.BufferedInputStream;
034    import java.io.IOException;
035    import java.io.InputStream;
036    import java.util.ArrayList;
037    import java.util.Collections;
038    import java.util.Comparator;
039    import java.util.Enumeration;
040    import java.util.Locale;
041    import java.util.Properties;
042    import java.util.ResourceBundle;
043    import javax.swing.ComboBoxModel;
044    import javax.swing.event.ListDataEvent;
045    import javax.swing.event.ListDataListener;
046    
047    import org.jfree.fonts.encoding.EncodingRegistry;
048    import org.jfree.report.JFreeReportBoot;
049    import org.jfree.util.Log;
050    import org.jfree.util.ObjectUtilities;
051    
052    /**
053     * A model for the 'encoding' combo box. This combobox model presents a selection for all
054     * available string encodings.
055     *
056     * @author Thomas Morgner.
057     */
058    public class EncodingComboBoxModel implements ComboBoxModel
059    {
060      /**
061       * A default description.
062       */
063      private static final String ENCODING_DEFAULT_DESCRIPTION =
064              "[no description]";
065    
066      /**
067       * The property that defines which encodings are available in the export dialogs.
068       */
069      public static final String AVAILABLE_ENCODINGS
070              = "org.jfree.report.modules.gui.base.EncodingsAvailable";
071    
072      /**
073       * The encodings available properties value for all properties.
074       */
075      public static final String AVAILABLE_ENCODINGS_ALL = "all";
076      /**
077       * The encodings available properties value for properties defined in the properties
078       * file.
079       */
080      public static final String AVAILABLE_ENCODINGS_FILE = "file";
081      /**
082       * The encodings available properties value for no properties defined. The encoding
083       * selection will be disabled.
084       */
085      public static final String AVAILABLE_ENCODINGS_NONE = "none";
086    
087      /**
088       * The name of the properties file used to define the available encodings. The property
089       * points to a resources in the classpath, not to a real file!
090       */
091      public static final String ENCODINGS_DEFINITION_FILE
092              = "org.jfree.report.modules.gui.base.EncodingsFile";
093    
094      /**
095       * The default name for the encoding properties file. This property defaults to
096       * "/org/jfree/report/jfreereport-encodings.properties".
097       */
098      public static final String ENCODINGS_DEFINITION_FILE_DEFAULT
099              = "org/jfree/report/modules/gui/swing/common/jfreereport-encodings.properties";
100    
101    
102      /**
103       * An encoding comparator.
104       */
105      private static class EncodingCarrierComparator implements Comparator
106      {
107        private EncodingCarrierComparator ()
108        {
109        }
110    
111        /**
112         * Compares its two arguments for order.  Returns a negative integer, zero, or a
113         * positive integer as the first argument is less than, equal to, or greater than the
114         * second.
115         *
116         * @param o1 the first object to be compared.
117         * @param o2 the second object to be compared.
118         * @return a negative integer, zero, or a positive integer as the first argument is
119         *         less than, equal to, or greater than the second.
120         *
121         * @throws java.lang.ClassCastException if the arguments' types prevent them from
122         *                                      being compared by this Comparator.
123         */
124        public int compare (final Object o1, final Object o2)
125        {
126          final EncodingCarrier e1 = (EncodingCarrier) o1;
127          final EncodingCarrier e2 = (EncodingCarrier) o2;
128          return e1.getName().toLowerCase().compareTo(e2.getName().toLowerCase());
129        }
130    
131        /**
132         * Returns <code>true</code> if this object is equal to <code>o</code>, and
133         * <code>false</code> otherwise.
134         *
135         * @param o the object.
136         * @return A boolean.
137         */
138        public boolean equals (final Object o)
139        {
140          if (o == null)
141          {
142            return false;
143          }
144          return getClass().equals(o.getClass());
145        }
146    
147        /**
148         * All comparators of this type are equal.
149         *
150         * @return A hash code.
151         */
152        public int hashCode ()
153        {
154          return getClass().hashCode();
155        }
156      }
157    
158      /**
159       * An encoding carrier.
160       */
161      private static class EncodingCarrier
162      {
163        /**
164         * The encoding name.
165         */
166        private String name;
167    
168        /**
169         * The encoding description.
170         */
171        private String description;
172    
173        /**
174         * The display name.
175         */
176        private String displayName;
177    
178        /**
179         * Creates a new encoding.
180         *
181         * @param name        the name (<code>null</code> not permitted).
182         * @param description the description.
183         */
184        private EncodingCarrier (final String name, final String description)
185        {
186          if (name == null)
187          {
188            throw new NullPointerException();
189          }
190          this.name = name;
191          this.description = description;
192          final StringBuffer dName = new StringBuffer();
193          dName.append(name);
194          dName.append(" (");
195          dName.append(description);
196          dName.append(")");
197          this.displayName = dName.toString();
198        }
199    
200        /**
201         * Returns the name.
202         *
203         * @return The name.
204         */
205        public String getName ()
206        {
207          return name;
208        }
209    
210        /**
211         * Returns the description.
212         *
213         * @return The description.
214         */
215        public String getDescription ()
216        {
217          return description;
218        }
219    
220        /**
221         * Returns the display name (the regular name followed by the description in
222         * brackets).
223         *
224         * @return The display name.
225         */
226        public String getDisplayName ()
227        {
228          return displayName;
229        }
230    
231        /**
232         * Returns <code>true</code> if the objects are equal, and <code>false</code>
233         * otherwise.
234         *
235         * @param o the object.
236         * @return A boolean.
237         */
238        public boolean equals (final Object o)
239        {
240          if (this == o)
241          {
242            return true;
243          }
244          if (!(o instanceof EncodingCarrier))
245          {
246            return false;
247          }
248    
249          final EncodingCarrier carrier = (EncodingCarrier) o;
250    
251          if (!name.equalsIgnoreCase(carrier.name))
252          {
253            return false;
254          }
255    
256          return true;
257        }
258    
259        /**
260         * Returns a hash code.
261         *
262         * @return The hash code.
263         */
264        public int hashCode ()
265        {
266          return name.hashCode();
267        }
268      }
269    
270      /**
271       * Storage for the encodings.
272       */
273      private final ArrayList encodings;
274    
275      /**
276       * Storage for registered listeners.
277       */
278      private ArrayList listDataListeners;
279    
280      /**
281       * The selected index.
282       */
283      private int selectedIndex;
284    
285      /**
286       * The selected object.
287       */
288      private Object selectedObject;
289    
290      private ResourceBundle bundle;
291      public static final String BUNDLE_NAME = "org.jfree.report.modules.gui.swing.common.encoding-names";
292    
293      /**
294       * Creates a new model.
295       * @param locale
296       */
297      public EncodingComboBoxModel(final Locale locale)
298      {
299        bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
300        encodings = new ArrayList();
301        listDataListeners = null;
302        selectedIndex = -1;
303      }
304    
305      /**
306       * Adds an encoding.
307       *
308       * @param name        the name.
309       * @param description the description.
310       * @return <code>true</code> if the encoding is valid and added to the model,
311       *         <code>false</code> otherwise.
312       */
313      public boolean addEncoding (final String name, final String description)
314      {
315        if (EncodingRegistry.getInstance().isSupportedEncoding(name))
316        {
317          encodings.add(new EncodingCarrier(name, description));
318        }
319        else
320        {
321          return false;
322        }
323    
324        fireContentsChanged();
325        return true;
326      }
327    
328      /**
329       * Adds an encoding to the model without checking its validity.
330       *
331       * @param name        the name.
332       * @param description the description.
333       */
334      public void addEncodingUnchecked (final String name, final String description)
335      {
336        encodings.add(new EncodingCarrier(name, description));
337        fireContentsChanged();
338      }
339    
340      public void removeEncoding (final String name)
341      {
342        if (encodings.remove(name))
343        {
344          fireContentsChanged();
345        }
346      }
347    
348      /**
349       * Make sure, that this encoding is defined and selectable in the combobox model.
350       *
351       * @param encoding the encoding that should be verified.
352       */
353      public void ensureEncodingAvailable (final String encoding)
354      {
355        if (encoding == null)
356        {
357          throw new NullPointerException("Encoding must not be null");
358        }
359        final String desc = getEncodingDescription(encoding);
360        final EncodingCarrier ec = new EncodingCarrier(encoding, desc);
361        if (encodings.contains(ec) == false)
362        {
363          encodings.add(ec);
364          fireContentsChanged();
365        }
366      }
367    
368      protected String getEncodingDescription (final String encoding)
369      {
370        try
371        {
372          return bundle.getString(encoding);
373        }
374        catch(Exception e)
375        {
376          return ENCODING_DEFAULT_DESCRIPTION;
377        }
378      }
379    
380      /**
381       * Sorts the encodings. Keep the selected object ...
382       */
383      public void sort ()
384      {
385        final Object selectedObject = getSelectedItem();
386        Collections.sort(encodings, new EncodingCarrierComparator());
387        setSelectedItem(selectedObject);
388        fireContentsChanged();
389      }
390    
391      /**
392       * Notifies all registered listeners that the content of the model has changed.
393       */
394      protected void fireContentsChanged ()
395      {
396        if (listDataListeners == null)
397        {
398          return;
399        }
400        fireContentsChanged(0, getSize());
401      }
402    
403      /**
404       * Notifies all registered listeners that the content of the model has changed.
405       */
406      protected void fireContentsChanged (final int start, final int length)
407      {
408        if (listDataListeners == null)
409        {
410          return;
411        }
412        final ListDataEvent evt = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, start, length);
413        for (int i = 0; i < listDataListeners.size(); i++)
414        {
415          final ListDataListener l = (ListDataListener) listDataListeners.get(i);
416          l.contentsChanged(evt);
417        }
418      }
419    
420      /**
421       * Set the selected item. The implementation of this  method should notify all
422       * registered <code>ListDataListener</code>s that the contents have changed.
423       *
424       * @param anItem the list object to select or <code>null</code> to clear the selection
425       */
426      public void setSelectedItem (final Object anItem)
427      {
428        selectedObject = anItem;
429        if (anItem instanceof String)
430        {
431          final int size = getSize();
432          for (int i = 0; i < size; i++)
433          {
434            if (anItem.equals(getElementAt(i)))
435            {
436              selectedIndex = i;
437              fireContentsChanged(-1, -1);
438              return;
439            }
440          }
441        }
442        selectedIndex = -1;
443        fireContentsChanged(-1, -1);
444      }
445    
446      /**
447       * Returns the selected index.
448       *
449       * @return The index.
450       */
451      public int getSelectedIndex ()
452      {
453        return selectedIndex;
454      }
455    
456      /**
457       * Defines the selected index for this encoding model.
458       *
459       * @param index the selected index or -1 to clear the selection.
460       * @throws java.lang.IllegalArgumentException
461       *          if the given index is invalid.
462       */
463      public void setSelectedIndex (final int index)
464      {
465        if (index == -1)
466        {
467          selectedIndex = -1;
468          selectedObject = null;
469          fireContentsChanged(-1, -1);
470          return;
471        }
472        if (index < -1 || index >= getSize())
473        {
474          throw new IllegalArgumentException("Index is invalid.");
475        }
476        selectedIndex = index;
477        selectedObject = getElementAt(index);
478        fireContentsChanged(-1, -1);
479      }
480    
481      /**
482       * Returns the selected encoding.
483       *
484       * @return The encoding (name).
485       */
486      public String getSelectedEncoding ()
487      {
488        if (selectedIndex == -1)
489        {
490          return null;
491        }
492        final EncodingCarrier ec = (EncodingCarrier) encodings.get(selectedIndex);
493        return ec.getName();
494      }
495    
496      /**
497       * Returns the selected item.
498       *
499       * @return The selected item or <code>null</code> if there is no selection
500       */
501      public Object getSelectedItem ()
502      {
503        return selectedObject;
504      }
505    
506      /**
507       * Returns the length of the list.
508       *
509       * @return the length of the list
510       */
511      public int getSize ()
512      {
513        return encodings.size();
514      }
515    
516      /**
517       * Returns the value at the specified index.
518       *
519       * @param index the requested index
520       * @return the value at <code>index</code>
521       */
522      public Object getElementAt (final int index)
523      {
524        final EncodingCarrier ec = (EncodingCarrier) encodings.get(index);
525        return ec.getDisplayName();
526      }
527    
528      /**
529       * Adds a listener to the list that's notified each time a change to the data model
530       * occurs.
531       *
532       * @param l the <code>ListDataListener</code> to be added
533       */
534      public void addListDataListener (final ListDataListener l)
535      {
536        if (listDataListeners == null)
537        {
538          listDataListeners = new ArrayList(5);
539        }
540        listDataListeners.add(l);
541      }
542    
543      /**
544       * Removes a listener from the list that's notified each time a change to the data model
545       * occurs.
546       *
547       * @param l the <code>ListDataListener</code> to be removed
548       */
549      public void removeListDataListener (final ListDataListener l)
550      {
551        if (listDataListeners == null)
552        {
553          return;
554        }
555        listDataListeners.remove(l);
556      }
557    
558      /**
559       * Creates a default model containing a selection of encodings.
560       *
561       * @return The default model.
562       */
563      public static EncodingComboBoxModel createDefaultModel (final Locale locale)
564      {
565        final EncodingComboBoxModel ecb = new EncodingComboBoxModel(locale);
566    
567        final String availEncs = getAvailableEncodings();
568        final boolean allEncodings =
569            availEncs.equalsIgnoreCase(AVAILABLE_ENCODINGS_ALL);
570    
571        if (allEncodings || availEncs.equals(AVAILABLE_ENCODINGS_FILE))
572        {
573          final String encFile = getEncodingsDefinitionFile();
574          final InputStream in = ObjectUtilities.getResourceAsStream
575                  (encFile, EncodingComboBoxModel.class);
576          if (in == null)
577          {
578            Log.warn(new Log.SimpleMessage
579                    ("The specified encodings definition file was not found: ", encFile));
580          }
581          else
582          {
583            try
584            {
585    //          final Properties defaultEncodings = getDefaultEncodings();
586              final Properties encDef = new Properties();
587              final BufferedInputStream bin = new BufferedInputStream(in);
588              encDef.load(bin);
589              bin.close();
590              final Enumeration en = encDef.keys();
591              while (en.hasMoreElements())
592              {
593                final String enc = (String) en.nextElement();
594                // if not set to "true"
595                if ("true".equalsIgnoreCase(encDef.getProperty(enc, "false")))
596                {
597                  // if the encoding is disabled ...
598                  ecb.addEncoding (enc, ecb.getEncodingDescription(enc));
599                }
600              }
601            }
602            catch (IOException e)
603            {
604              Log.warn(new Log.SimpleMessage
605                      ("There was an error while reading the encodings definition file: ", encFile), e);
606            }
607          }
608        }
609        return ecb;
610      }
611    
612      /**
613       * Returns the index of an encoding.
614       *
615       * @param encoding the encoding (name).
616       * @return The index.
617       */
618      public int indexOf (final String encoding)
619      {
620        return encodings.indexOf(new EncodingCarrier(encoding, null));
621      }
622    
623      /**
624       * Returns an encoding.
625       *
626       * @param index the index.
627       * @return The index.
628       */
629      public String getEncoding (final int index)
630      {
631        final EncodingCarrier ec = (EncodingCarrier) encodings.get(index);
632        return ec.getName();
633      }
634    
635      /**
636       * Returns a description.
637       *
638       * @param index the index.
639       * @return The description.
640       */
641      public String getDescription (final int index)
642      {
643        final EncodingCarrier ec = (EncodingCarrier) encodings.get(index);
644        return ec.getDescription();
645      }
646    
647    
648      /**
649       * Defines the loader settings for the available encodings shown to the user. The
650       * property defaults to AVAILABLE_ENCODINGS_ALL.
651       *
652       * @return either AVAILABLE_ENCODINGS_ALL, AVAILABLE_ENCODINGS_FILE or
653       *         AVAILABLE_ENCODINGS_NONE.
654       */
655      public static String getEncodingsDefinitionFile ()
656      {
657        return JFreeReportBoot.getInstance().getGlobalConfig().getConfigProperty
658                (ENCODINGS_DEFINITION_FILE, ENCODINGS_DEFINITION_FILE_DEFAULT);
659      }
660    
661    
662      /**
663       * Defines the loader settings for the available encodings shown to the user. The
664       * property defaults to AVAILABLE_ENCODINGS_ALL.
665       *
666       * @return either AVAILABLE_ENCODINGS_ALL, AVAILABLE_ENCODINGS_FILE or
667       *         AVAILABLE_ENCODINGS_NONE.
668       */
669      public static String getAvailableEncodings ()
670      {
671        return JFreeReportBoot.getInstance().getGlobalConfig().getConfigProperty
672                (AVAILABLE_ENCODINGS, AVAILABLE_ENCODINGS_ALL);
673      }
674    
675      public void setSelectedEncoding (final String encoding)
676      {
677        if (encoding == null)
678        {
679          throw new NullPointerException();
680        }
681    
682        final int size = encodings.size();
683        for (int i = 0; i < size; i++)
684        {
685          final EncodingCarrier carrier = (EncodingCarrier) encodings.get(i);
686          if (encoding.equals(carrier.getName()))
687          {
688            selectedIndex = i;
689            selectedObject = carrier.getDisplayName();
690            fireContentsChanged(-1, -1);
691            return;
692          }
693        }
694        // default fall-back to have a valid value ..
695        if (size > 0)
696        {
697          selectedIndex = 0;
698          selectedObject = getElementAt(0);
699          fireContentsChanged(-1, -1);
700        }
701      }
702    }