001    /*
002     *  Copyright (C) 2003 Adam Olsen
003     *  This program is free software; you can redistribute it and/or modify
004     *  it under the terms of the GNU General Public License as published by
005     *  the Free Software Foundation; either version 1, or (at your option)
006     *  any later version.
007     *  This program is distributed in the hope that it will be useful,
008     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
009     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
010     *  GNU General Public License for more details.
011     *  You should have received a copy of the GNU General Public License
012     *  along with this program; if not, write to the Free Software
013     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
014     */
015    package com.valhalla.jbother.preferences;
016    
017    import java.awt.Component;
018    import java.awt.Container;
019    import java.awt.Font;
020    import java.awt.Frame;
021    import java.awt.GridBagConstraints;
022    import java.awt.GridBagLayout;
023    import java.awt.Window;
024    import java.awt.event.ActionEvent;
025    import java.awt.event.ActionListener;
026    import java.io.BufferedReader;
027    import java.io.File;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.io.InputStreamReader;
031    import java.util.*;
032    
033    import javax.swing.BorderFactory;
034    import javax.swing.JButton;
035    import javax.swing.JComboBox;
036    import javax.swing.JComponent;
037    import javax.swing.JLabel;
038    import javax.swing.JMenu;
039    import javax.swing.JPanel;
040    import javax.swing.JToolBar;
041    import javax.swing.UIManager;
042    
043    import org.jfree.ui.FontChooserDialog;
044    
045    import com.valhalla.gui.CopyPasteContextMenu;
046    import com.valhalla.jbother.BuddyList;
047    import com.valhalla.jbother.ConversationFormatter;
048    import com.valhalla.jbother.JBother;
049    import com.valhalla.jbother.JBotherLoader;
050    import com.valhalla.jbother.jabber.BuddyStatus;
051    import com.valhalla.settings.Settings;
052    import com.valhalla.settings.TempSettings;
053    
054    /**
055     * Allows the user to change appearance preferences
056     *
057     * @author Adam Olsen
058     * @created March 9, 2005
059     * @version 1.0
060     */
061    public class AppearancePreferencesPanel extends JPanel implements
062            PreferencesPanel {
063        private ResourceBundle resources = ResourceBundle.getBundle(
064                "JBotherBundle", Locale.getDefault());
065    
066        private PreferencesDialog prefs;
067    
068        private GridBagLayout grid = new GridBagLayout();
069    
070        private GridBagConstraints c = new GridBagConstraints();
071    
072        private JLabel lafLabel = new JLabel(resources.getString("themeLabel")
073                + ": ");
074    
075        private JLabel statusLabel = new JLabel(resources.getString("statusLabel")
076                + ": ");
077    
078        private JLabel emoticonLabel = new JLabel(resources
079                .getString("emoticonLabel")
080                + ": ");
081    
082        private JLabel messageLabel = new JLabel(resources
083                .getString("messageWindowFont")
084                + ": ");
085    
086        private JButton messageFontButton = new JButton("Font");
087    
088        private JLabel appFontLabel = new JLabel(resources
089                .getString("appFontLabel")
090                + ": ");
091    
092        private JButton appFontButton = new JButton("Font");
093    
094        private JComboBox lookAndFeel, statusTheme, emoticonTheme;
095    
096        private int current = -1;
097    
098        private UIManager.LookAndFeelInfo[] lfs;
099    
100        private String[] names;
101    
102        /**
103         * Sets up the AppearancePreferences
104         *
105         * @param dialog
106         *            the enclosing PreferencesDialog
107         */
108        public AppearancePreferencesPanel(PreferencesDialog dialog) {
109            this.prefs = dialog;
110            setBorder(BorderFactory.createTitledBorder(resources
111                    .getString("appearancePreferences")));
112            setLayout(grid);
113    
114            lfs = UIManager.getInstalledLookAndFeels();
115            String lf = UIManager.getLookAndFeel().getClass().getName();
116    
117            ArrayList list = new ArrayList();
118            Properties displayed = new Properties();
119    
120            int index = 0;
121            for (int i = 0; i < lfs.length; i++) {
122    
123                if( displayed.getProperty( lfs[i].getName() ) == null )
124                {
125                    list.add(lfs[i].getName());
126    
127                    if (lf.equals(lfs[i].getClassName()) && current == -1) {
128                        index = i;
129                        current = index;
130                    }
131                }
132    
133                displayed.setProperty(lfs[i].getName(), "1" );
134            }
135    
136            if (current == -1)
137                index = 0;
138    
139            names = new String[list.size()];
140            list.toArray(names);
141    
142            lookAndFeel = new JComboBox(names);
143            lookAndFeel.setSelectedIndex(index);
144            lookAndFeel.validate();
145            lookAndFeel.repaint();
146    
147            c.gridx = 0;
148            c.gridy = 0;
149            c.fill = GridBagConstraints.HORIZONTAL;
150            c.anchor = GridBagConstraints.WEST;
151            c.gridwidth = 1;
152    
153            //display name stuff
154            lafLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
155            c.weightx = -.1;
156            c.fill = GridBagConstraints.NONE;
157            grid.setConstraints(lafLabel, c);
158            add(lafLabel);
159    
160            c.gridx = 1;
161    
162            grid.setConstraints(lookAndFeel, c);
163            add(lookAndFeel);
164    
165            c.gridx = 0;
166            c.gridy++;
167    
168            statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
169            grid.setConstraints(statusLabel, c);
170            add(statusLabel);
171    
172            statusTheme = getStatusThemes();
173            c.gridx++;
174            grid.setConstraints(statusTheme, c);
175            add(statusTheme);
176    
177            c.gridx = 0;
178            c.gridy++;
179    
180            emoticonLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
181            grid.setConstraints(emoticonLabel, c);
182            add(emoticonLabel);
183    
184            emoticonTheme = getEmoticonThemes();
185            c.gridx++;
186            grid.setConstraints(emoticonTheme, c);
187            add(emoticonTheme);
188    
189            FontChangeListener fontListener = new FontChangeListener();
190    
191            // get the message window font settings
192            String messageFont = Settings.getInstance().getProperty(
193                    "messageWindowFont");
194            if (messageFont == null) {
195                messageFont = "Default-PLAIN-12";
196            }
197    
198            Font font = Font.decode(messageFont);
199            messageFontButton
200                    .setText(getEncodedFontName(font).replaceAll("-", " "));
201            messageFontButton.setFont(font);
202    
203            c.gridx = 0;
204            c.gridy++;
205            messageLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
206            grid.setConstraints(messageLabel, c);
207            add(messageLabel);
208            c.gridx++;
209            messageFontButton.addActionListener(fontListener);
210            grid.setConstraints(messageFontButton, c);
211            add(messageFontButton);
212    
213            // get the application font settings
214            String appFont = Settings.getInstance().getProperty("applicationFont");
215            if (appFont == null) {
216                appFont = "Default-PLAIN-12";
217            }
218    
219            font = Font.decode(appFont);
220            appFontButton.setText(getEncodedFontName(font).replaceAll("-", " "));
221            appFontButton.setFont(font);
222    
223            c.gridx = 0;
224            c.gridy++;
225            appFontLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
226            grid.setConstraints(appFontLabel, c);
227            add(appFontLabel);
228            c.gridx++;
229            appFontButton.addActionListener(fontListener);
230            grid.setConstraints(appFontButton, c);
231            add(appFontButton);
232    
233            //this is the space taker
234            JLabel blankLabel = new JLabel("");
235            c.weighty = 1;
236            c.weightx = 1;
237            c.gridx = 0;
238            c.gridwidth = 3;
239            c.gridy++;
240            grid.setConstraints(blankLabel, c);
241            add(blankLabel);
242        }
243    
244        /**
245         * Listens for one of the font buttons to be clicked and displays a
246         * FontChooserDialog for it
247         *
248         * @author Adam Olsen
249         * @created March 9, 2005
250         * @version 1.0
251         */
252        private class FontChangeListener implements ActionListener {
253            /**
254             * Description of the Method
255             *
256             * @param e
257             *            Description of the Parameter
258             */
259            public void actionPerformed(ActionEvent e) {
260                JButton button = (JButton) e.getSource();
261                FontChooserDialog dialog = new FontChooserDialog(prefs, resources
262                        .getString("messageWindowFont"), true, button.getFont());
263    
264                dialog.setVisible(true);
265    
266                if (!dialog.isCancelled()) {
267                    Font newFont = dialog.getSelectedFont();
268                    button.setFont(newFont);
269                    button
270                            .setText(getEncodedFontName(newFont).replaceAll("-",
271                                    " "));
272                }
273            }
274        }
275    
276        /**
277         * Checks the default jar file and the user theme directory for status
278         * themes
279         *
280         * @return The statusThemes value
281         */
282        private JComboBox getStatusThemes() {
283            JComboBox box = new JComboBox();
284            String current = Settings.getInstance().getProperty("statusTheme");
285            if (current == null) {
286                current = "default";
287            }
288            box.addItem(current);
289    
290            // user defined themes
291            File path = new File(JBother.settingsDir + File.separatorChar
292                    + "themes" + File.separatorChar + "statusicons");
293    
294            String[] userThemes = new String[0];
295    
296            if (!path.isDirectory() && !path.mkdirs()) {
297                com.valhalla.Logger
298                        .debug("Could not create user defined settings directories.");
299            } else {
300                userThemes = path.list();
301                for (int i = 0; i < userThemes.length; i++) {
302                    if (!current.equals(userThemes[i])) {
303                        box.addItem(userThemes[i]);
304                    }
305                }
306            }
307    
308            // default themes in the jar
309            InputStream stream = getClass().getClassLoader().getResourceAsStream(
310                    "imagethemes/statusicons/index.dat");
311            if (stream == null) {
312                com.valhalla.Logger.debug("Bad status theme file.");
313                return box;
314            }
315    
316            InputStreamReader in = new InputStreamReader(stream);
317            BufferedReader reader = new BufferedReader(in);
318            String line;
319            try {
320                while ((line = reader.readLine()) != null) {
321                    boolean userTheme = false;
322                    for (int i = 0; i < userThemes.length; i++) {
323                        if (line.equals(userThemes[i])) {
324                            userTheme = true;
325                            break;
326                        }
327                    }
328                    if (!userTheme && !line.equals(current)) {
329                        box.addItem(line);
330                    }
331                }
332            } catch (IOException e) {
333            }
334    
335            return box;
336        }
337    
338        /**
339         * Gets a list of emoticon themes
340         *
341         * @return The emoticonThemes value
342         */
343        private JComboBox getEmoticonThemes() {
344            JComboBox box = new JComboBox();
345            String current = Settings.getInstance().getProperty("emoticonTheme");
346            if (current == null) {
347                current = "default";
348            }
349            box.addItem(current);
350    
351            // default themes in the jar
352            InputStream stream = getClass().getClassLoader().getResourceAsStream(
353                    "imagethemes/emoticons/index.dat");
354            if (stream == null) {
355                com.valhalla.Logger.debug("Bad emoticon theme file.");
356                return box;
357            }
358    
359            InputStreamReader in = new InputStreamReader(stream);
360            BufferedReader reader = new BufferedReader(in);
361            String line;
362            try {
363                while ((line = reader.readLine()) != null) {
364                    if (!line.equals(current)) {
365                        box.addItem(line);
366                    }
367                }
368            } catch (IOException e) {
369            }
370    
371            return box;
372        }
373    
374        /**
375         * Gets the String representation of a font
376         *
377         * @param font
378         *            Description of the Parameter
379         * @return the string representation of a font
380         */
381        private String getEncodedFontName(Font font) {
382            String fontString = font.getName() + "-";
383    
384            if (font.isPlain()) {
385                fontString += "PLAIN";
386            }
387            if (font.isBold()) {
388                fontString += "BOLD";
389            }
390            if (font.isItalic()) {
391                fontString += "ITALIC";
392            }
393            fontString += "-" + font.getSize();
394    
395            return fontString;
396        }
397    
398        /**
399         * Gets temporary settings
400         *
401         * @return temporary settings
402         */
403        public TempSettings getSettings() {
404            TempSettings tempSettings = new TempSettings();
405    
406            if (lookAndFeel.getSelectedIndex() != current) {
407                //Standard.noticeMessage( prefs, resources.getString( "themeLabel"
408                // ),
409                //  resources.getString( "themeSettingsApplied" ) );
410    
411                String laf = lfs[lookAndFeel.getSelectedIndex()].getClassName();
412    
413                JBotherLoader.loadLAF(laf);
414                tempSettings.setProperty("lookAndFeel", laf);
415    
416                current = lookAndFeel.getSelectedIndex();
417            }
418    
419            tempSettings.setProperty("messageWindowFont",
420                    getEncodedFontName(messageFontButton.getFont()));
421            // update all open conversation areas
422            Hashtable buddyStatuses = BuddyList.getInstance().getBuddyStatuses();
423            if (buddyStatuses != null) {
424                Iterator i = buddyStatuses.keySet().iterator();
425    
426                while (i.hasNext()) {
427                    BuddyStatus buddy = BuddyList.getInstance().getBuddyStatus(
428                            (String) i.next());
429                    if (buddy.getConversation() != null) {
430                        buddy.getConversation().updateStyle(
431                                messageFontButton.getFont());
432                    }
433                }
434            }
435    
436            if (BuddyList.getInstance().getTabFrame() != null) {
437                BuddyList.getInstance().getTabFrame().updateStyles(
438                        messageFontButton.getFont());
439            }
440    
441            tempSettings.setProperty("applicationFont",
442                    getEncodedFontName(appFontButton.getFont()));
443            updateApplicationFonts(appFontButton.getFont(), prefs);
444    
445            String selectedStatus = (String) statusTheme.getItemAt(statusTheme
446                    .getSelectedIndex());
447            tempSettings.setProperty("statusTheme", selectedStatus);
448            tempSettings.setProperty("emoticonTheme", (String) emoticonTheme
449                    .getItemAt(emoticonTheme.getSelectedIndex()));
450    
451            BuddyList.getInstance().getBuddyListTree().repaint();
452            if (BuddyList.getInstance().getTabFrame() != null) {
453                BuddyList.getInstance().getTabFrame().repaint();
454            }
455            BuddyList.getInstance().updateButtons(selectedStatus);
456    
457            ConversationFormatter.getInstance().switchTheme(
458                    tempSettings.getProperty("emoticonTheme"));
459    
460            return tempSettings;
461        }
462    
463        /**
464         * Updates all the fonts in the application
465         *
466         * @param font
467         *            the font to update to
468         * @param prefs
469         *            Description of the Parameter
470         */
471        public static void updateApplicationFonts(Font font, PreferencesDialog prefs) {
472            com.valhalla.jbother.JBotherLoader.setupFont(font);
473            updateAllUIs();
474    
475            if (prefs != null) {
476                updateComponentTreeUI0(prefs.getTree());
477                Iterator i = prefs.getPanels().keySet().iterator();
478                while (i.hasNext()) {
479                    updateComponentTreeUI0((Component) prefs.getPanels().get(
480                            i.next()));
481                }
482    
483                i = PreferencesDialog.getPluginPanels().keySet().iterator();
484                while (i.hasNext()) {
485                    updateComponentTreeUI0((Component) prefs.getPanels().get(
486                            i.next()));
487                }
488            }
489        }
490    
491        /**
492         * Method to attempt a dynamic update for any GUI accessible by this JVM. It
493         * will filter through all frames and sub-components of the frames.
494         */
495        public static void updateAllUIs() {
496            CopyPasteContextMenu.newInstance();
497            Frame frames[];
498            int i1;
499            frames = Frame.getFrames();
500            i1 = 0;
501    
502            for (int i = 0; i < frames.length; i++) {
503                updateWindowUI(frames[i]);
504            }
505        }
506    
507        /**
508         * Method to attempt a dynamic update for all components of the given
509         * <code>Window</code>.
510         *
511         * @param window
512         *            The <code>Window</code> for which the look and feel update
513         *            has to be performed against.
514         */
515        public static void updateWindowUI(Window window) {
516            try {
517                updateComponentTreeUI(window);
518            } catch (Exception exception) {
519            }
520    
521            Window windows[] = window.getOwnedWindows();
522    
523            for (int i = 0; i < windows.length; i++) {
524                updateWindowUI(windows[i]);
525            }
526        }
527    
528        /**
529         * A simple minded look and feel change: ask each node in the tree to
530         * <code>updateUI()</code>-- that is, to initialize its UI property with
531         * the current look and feel. Based on the Sun
532         * SwingUtilities.updateComponentTreeUI, but ensures that the update happens
533         * on the components of a JToolbar before the JToolbar itself.
534         *
535         * @param c
536         *            Description of the Parameter
537         */
538        public static void updateComponentTreeUI(Component c) {
539            updateComponentTreeUI0(c);
540            c.invalidate();
541            c.validate();
542            c.repaint();
543        }
544    
545        /**
546         * Description of the Method
547         *
548         * @param c
549         *            Description of the Parameter
550         */
551        private static void updateComponentTreeUI0(Component c) {
552    
553            Component[] children = null;
554    
555            if (c instanceof JToolBar) {
556                children = ((JToolBar) c).getComponents();
557    
558                if (children != null) {
559                    for (int i = 0; i < children.length; i++) {
560                        updateComponentTreeUI0(children[i]);
561                    }
562                }
563    
564                ((JComponent) c).updateUI();
565            } else {
566                if (c instanceof JComponent) {
567                    ((JComponent) c).updateUI();
568                }
569    
570                if (c instanceof JMenu) {
571                    children = ((JMenu) c).getMenuComponents();
572                } else if (c instanceof Container) {
573                    children = ((Container) c).getComponents();
574                }
575    
576                if (children != null) {
577                    for (int i = 0; i < children.length; i++) {
578                        updateComponentTreeUI0(children[i]);
579                    }
580                }
581            }
582        }
583    }
584