/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.util; import java.awt.Component; import java.awt.Dimension; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.StringTokenizer; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import net.miginfocom.swing.MigLayout; import org.jajuk.ui.windows.JajukMainWindow; import org.jajuk.util.log.Log; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Utility class to get strings from localized property files * <p> * Singleton * </p>. */ public class Messages extends DefaultHandler { /** Messages initialized flag. */ protected static boolean bInitialized = false; /** All choice option, completes JDialog options. */ public static final int ALL_OPTION = 10; /** Specific Yes NO All Cancel option. */ public static final int YES_NO_ALL_CANCEL_OPTION = 11; /** User choice. */ private static int choice; /** Messages themselves extracted from an XML file to this properties class*. */ protected static Properties properties; /** English messages used as default*. */ private static Properties propertiesEn; /** * Contains. * * @param sKey * * @return whether given key exists */ public static boolean contains(final String sKey) { return getPropertiesEn().containsKey(sKey); } /** * Gets the string. * * @param key * * @return the string */ public static String getString(final String key) { String sOut = key; try { sOut = getProperties().getProperty(key); if (sOut == null) { // this property is unknown for this local, try // in English sOut = getPropertiesEn().getProperty(key); } // at least, returned property is the key name but we trace an // error to show it if (sOut == null) { Log.error(105, "key: " + key, new Exception()); sOut = key; } } catch (final Exception e) { // system error Log.error(e); } return sOut; } /** * Fetch all messages from a given base key. * <P/> * Example: * * <pre> * example.0=Message 1 * example.1=Message 2 * example.2=Message 3 * </pre> * * Using <tt>Messages.getAll("example");</tt> will return a size 3 String * array containing the messages in order. * <P/> * The keys need to have continuous numbers. So, adding * <tt>example.5=Message 5</tt> to the bundle, will not result in adding it to * the array without first adding <tt>example.3</tt> and <tt>example.4</tt>. * * @param base The base to use for generating the keys. * * @return An array of Strings containing the messages linked to the key, * never <tt>null</tt>. If <tt>base.0</tt> is not found, and empty * array is returned. */ public static String[] getAll(final String base) { final List<String> msgs = new ArrayList<String>(); final String prefix = base + "."; try { final Properties lProperties = getProperties(); final Properties defaultProperties = getPropertiesEn(); for (int i = 0;; i++) { String sOut = lProperties.getProperty(prefix + i); if (sOut == null) { // this property is unknown for this local, try in English sOut = defaultProperties.getProperty(prefix + i); // unknown property, assume we found all properties in the set if (sOut == null) { break; } } else { // Remove HTML tags sOut = sOut.replaceAll("<.*>", ""); } msgs.add(sOut); } } catch (final Exception e) { // System error Log.error(e); } return msgs.toArray(new String[msgs.size()]); } /** * Gets the shuffle tip of the day. * * @return a shuffled tip of the day <br> */ public static String getShuffleTipOfTheDay() { try { String totd = null; String[] tips = Messages.getAll("TipOfTheDay"); // index contains the index of the last provided totd int index = (int) (UtilSystem.getRandom().nextFloat() * (tips.length - 1)); // display the next one totd = Messages.getString("TipOfTheDay." + index); // Remove <img> tags totd = totd.replaceAll("<.*>", ""); // Increment and save index return totd; } catch (Exception e) { Log.error(e); // Make sure to handle every problem: this code is used in slash screen // and we won't propagate exception that could prevent jajuk from starting return ""; } } /** * Return Flag icon for given description. * * @param sDesc * * @return the icon */ public static Icon getIcon(final String sDesc) { Icon icon = new ImageIcon(UtilSystem.getResource("icons/16x16/flag_" + LocaleManager.getLocaleForDesc(sDesc) + ".png")); return icon; } /** * *************************************************************************** * Parse a fake properties file inside an XML file as CDATA. * * @param locale * @return a properties with all entries * @throws SAXException the SAX exception * @throws IOException Signals that an I/O exception has occurred. * @throws ParserConfigurationException the parser configuration exception */ private static Properties parseLangpack(final Locale locale) throws SAXException, IOException, ParserConfigurationException { final Properties lProperties = new Properties(); // Choose right jajuk_<lang>.properties file to load final StringBuilder sbFilename = new StringBuilder(Const.FILE_LANGPACK_PART1); if (!Locale.ENGLISH.equals(locale)) { // for English, properties file is // simply jajuk.properties sbFilename.append('_').append(locale); } sbFilename.append(Const.FILE_LANGPACK_PART2); // property file URL, either in the jajuk.jar jar // (normal execution) or found as regular file if in // development debug mode String resource = "org/jajuk/i18n/" + sbFilename.toString(); URL url = UtilSystem.getResource(resource); if (url == null) { throw new IOException("Could not read resource: " + resource); } // parse it, actually it is a big properties file as CDATA in an XML // file final SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setValidating(false); spf.setNamespaceAware(false); final SAXParser saxParser = spf.newSAXParser(); saxParser.parse(url.openStream(), new DefaultHandler() { // this buffer will contain the entire properties strings StringBuilder sb = new StringBuilder(15000); // call for each element strings, actually will be called // several time if the element is large (our case : large CDATA) @Override public void characters(final char[] ch, final int start, final int length) throws SAXException { sb.append(ch, start, length); } // call when closing the tag (</body> in our case ) @Override public void endElement(final String uri, final String localName, final String qName) throws SAXException { final String sWhole = sb.toString(); // ok, parse it ( comments start with #) final StringTokenizer st = new StringTokenizer(sWhole, "\n"); while (st.hasMoreTokens()) { final String sLine = st.nextToken(); if ((sLine.length() > 0) && !sLine.startsWith("#") && (sLine.indexOf('=') != -1)) { final StringTokenizer stLine = new StringTokenizer(sLine, "="); // get full value after the '=', we don't use the // stringtokenizer to allow // using = characters in the value final String sValue = sLine.substring(sLine.indexOf('=') + 1); // trim to ignore space at begin end end of lines lProperties.put(stLine.nextToken().trim(), sValue); } } } }); return lProperties; } /** * Return the message display to the user corresponding to the error code. * * @param code Error code. * * @return String Message corresponding to the error code. */ public static String getErrorMessage(final int code) { String sOut = Integer.toString(code); try { sOut = getString("Error." + UtilString.padNumber(code, 3)); } catch (final Exception e) { System.out.println("### Error getting error message for code: " + code); } return sOut; } /** * Show a dialog waiting for a user decision * <p> * CAUTION! the thread which calls this method musn't have locks on resources * : otherwise it can conduct to GUI freeze * </p>. * * @param sText : dialog text * @param optionsType * @param iType message type like JOptionPane.WARNING * @return the choice */ public static int getChoice(final String sText, final int optionsType, final int iType) { try { // Make sure to reset the choice and to return a non-existing choice if // the GUI fails choice = JOptionPane.DEFAULT_OPTION; Runnable t = new Thread("Get choice thread") { @Override public void run() { // This must be done in the EDT final ConfirmDialog confirm = new ConfirmDialog(sText, getTitleForType(iType), optionsType, iType, JajukMainWindow.getInstance()); choice = confirm.getResu(); } }; // invokeAndWait method cannot be called from the EDT if (SwingUtilities.isEventDispatchThread()) { t.run(); } else { SwingUtilities.invokeAndWait(t); } } catch (InterruptedException e) { Log.error(e); } catch (InvocationTargetException e) { Log.error(e); } return choice; } /** * Gets the title for type. * * @param iType * * @return String for given JOptionPane message type */ private static String getTitleForType(final int iType) { switch (iType) { case JOptionPane.ERROR_MESSAGE: return Messages.getString("Error"); case JOptionPane.WARNING_MESSAGE: return Messages.getString("Warning"); case JOptionPane.INFORMATION_MESSAGE: return Messages.getString("Info"); } return ""; } /** * Show a dialog with specified warning message. * * @param sMessage */ public static void showWarningMessage(final String sMessage) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DetailsMessageDialog(sMessage, getTitleForType(JOptionPane.WARNING_MESSAGE), JOptionPane.WARNING_MESSAGE, null, null); } }); } /** * Show a dialog with specified warning message + a "not show again" button. * * @param sMessage * @param sProperty : property name */ public static void showHideableWarningMessage(final String sMessage, final String sProperty) { // User required to hide this message if (Conf.getBoolean(sProperty)) { return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { final HideableMessageDialog message = new HideableMessageDialog(sMessage, getTitleForType(JOptionPane.WARNING_MESSAGE), sProperty, JOptionPane.WARNING_MESSAGE, null); message.getResu(); } }); } /** * Show a dialog with specified error message and an icon. * * @param sMessage * @param icon */ public static void showInfoMessage(final String sMessage, final Icon icon) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DetailsMessageDialog(sMessage, getTitleForType(JOptionPane.INFORMATION_MESSAGE), JOptionPane.INFORMATION_MESSAGE, null, icon); } }); } /** * Show a dialog with specified error message and infosup. * * @param code * @param sInfoSup */ public static void showErrorMessage(final int code, final String sInfoSup) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new ErrorMessageDialog(code, sInfoSup); } }); } /** * Show a dialog with specified error message. * * @param code */ public static void showErrorMessage(final int code) { showErrorMessage(code, null); } /** * Show a dialog with specified error message and infosup and details. * * @param code * @param sInfoSup * @param sDetails */ public static void showDetailedErrorMessage(final int code, final String sInfoSup, final String sDetails) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DetailsMessageDialog(Messages.getErrorMessage(code) + " : " + sInfoSup, getTitleForType(JOptionPane.ERROR_MESSAGE), JOptionPane.ERROR_MESSAGE, sDetails, null); } }); } /** * Show a dialog with specified error message with infos up. * * @param sMessage * @param sInfoSup */ public static void showInfoMessage(final String sMessage, final String sInfoSup) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DetailsMessageDialog(sMessage + " : " + sInfoSup, getTitleForType(JOptionPane.INFORMATION_MESSAGE), JOptionPane.INFORMATION_MESSAGE, null, null); } }); } /** * Show a dialog with specified error message. * * @param sMessage */ public static void showInfoMessage(final String sMessage) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DetailsMessageDialog(sMessage, getTitleForType(JOptionPane.INFORMATION_MESSAGE), JOptionPane.INFORMATION_MESSAGE, null, null); } }); } /** * Return true if the messaging system is started, can be useful mainly at * startup by services ( like logs) using them to avoid dead locks Messages * service is initialized after current has been set. * * @return true, if checks if is initialized */ public static boolean isInitialized() { return bInitialized; } /** * Gets localized and human property name for given key. * * @param sKey * * @return the human property name or the property itself if not translated */ public static String getHumanPropertyName(String sKey) { String sOut = sKey; if (Messages.contains(Const.PROPERTY_SEPARATOR + sKey)) { return Messages.getString(Const.PROPERTY_SEPARATOR + sKey); } return sOut; } /** * Gets the properties. * * @return Returns the properties. * @throws SAXException the SAX exception * @throws IOException Signals that an I/O exception has occurred. * @throws ParserConfigurationException the parser configuration exception */ public static Properties getProperties() throws SAXException, IOException, ParserConfigurationException { if (properties == null) { // reuse English if possible if (Locale.ENGLISH.equals(LocaleManager.getLocale())) { properties = getPropertiesEn(); } else { properties = parseLangpack(LocaleManager.getLocale()); } } return properties; } /** * Gets the properties en. * * @return Returns the propertiesEn. */ public static Properties getPropertiesEn() { if (propertiesEn == null) { try { propertiesEn = parseLangpack(Locale.ENGLISH); } catch (final Exception e) { Log.error(e); } } return propertiesEn; } } /** * Confirmation Dialog */ class ConfirmDialog extends JajukDialog { /** * Confirm dialog constructor * * @param sText * @param sTitle * @param int optionType : kind of options like JOptionPane.OK_CANCEL * <p> * Specific option: Messages.ALL_OPTION * </p> * @param iType * message type like JOptionPane.WARNING */ ConfirmDialog(final String sText, final String sTitle, final int optionsType, final int iType, Component parent) { super(); final JOptionPane optionPane = UtilGUI.getNarrowOptionPane(72); if (optionsType == Messages.YES_NO_ALL_CANCEL_OPTION) { optionPane.setOptions(new Object[] { Messages.getString("Yes"), Messages.getString("No"), Messages.getString("YestoAll"), Messages.getString("Cancel") }); } else { optionPane.setOptionType(optionsType); } optionPane.setMessageType(iType); optionPane.setMessage(UtilGUI.getLimitedMessage(sText, 20)); final JDialog dialog = optionPane.createDialog(null, sTitle); dialog.setModal(true); dialog.setAlwaysOnTop(true); dialog.pack(); dialog.setLocationRelativeTo(parent); dialog.setVisible(true); final Object resu = optionPane.getValue(); // Set Cancel as default iResu = JOptionPane.CANCEL_OPTION; if (optionPane.getValue() == null) { // User closed the dialog using the cross icon iResu = JOptionPane.CANCEL_OPTION; } else if (resu instanceof String) { // Options are string when using custom options if (resu.equals(Messages.getString("YestoAll"))) { iResu = Messages.ALL_OPTION; } else if (resu.equals(Messages.getString("Yes"))) { iResu = JOptionPane.YES_OPTION; } else if (resu.equals(Messages.getString("No"))) { iResu = JOptionPane.NO_OPTION; } else if (resu.equals(Messages.getString("Cancel"))) { iResu = JOptionPane.CANCEL_OPTION; } else if (resu.equals(Messages.getString("Ok"))) { iResu = JOptionPane.OK_OPTION; } else if (resu.equals(Messages.getString("Default"))) { iResu = JOptionPane.DEFAULT_OPTION; } } else if (resu instanceof Integer) { // result is an integer when using JOptionPane standard types iResu = (Integer) resu; } // manually dispose to free up memory, somehow this is not done automatically! dialog.dispose(); } } /** * Message Dialog */ class DetailsMessageDialog extends JajukDialog { /** * Message dialog constructor * * @param sText * @param sTitle * @param iType */ DetailsMessageDialog(final String sText, final String sTitle, final int iType, final String sDetails, final Icon icon) { super(); final JOptionPane optionPane = UtilGUI.getNarrowOptionPane(72); optionPane.setMessage(sText); if (sDetails != null) { final Object[] options = { Messages.getString("Ok"), Messages.getString("Details") }; optionPane.setOptions(options); } optionPane.setMessageType(iType); if (icon != null) { optionPane.setIcon(icon); } final JDialog dialog = optionPane.createDialog(null, sTitle); dialog.setModal(true); dialog.setAlwaysOnTop(true); dialog.setVisible(true); if (optionPane.getValue().equals(Messages.getString("Details"))) { // details final JDialog dialogDetail = new JDialog(dialog, Messages.getString("Details")); dialogDetail.setMaximumSize(new Dimension(800, 600)); final JPanel jp = new JPanel(); jp.setLayout(new MigLayout("ins 5", "[grow]", "[grow]")); final JTextArea jta = new JTextArea(sDetails); jta.setEditable(false); jp.add(new JScrollPane(jta), "wrap,grow"); dialogDetail.setModal(true); dialogDetail.setAlwaysOnTop(true); dialogDetail.setContentPane(jp); dialogDetail.pack(); dialogDetail.setLocationRelativeTo(JajukMainWindow.getInstance()); dialogDetail.setVisible(true); // manually dispose to free up memory, somehow this is not done automatically! dialog.dispose(); } // manually dispose to free up memory, somehow this is not done automatically! dialog.dispose(); } } /** * Hideable message dialog (has a "not show again" button) */ class HideableMessageDialog extends JajukDialog { /** * Message dialog constructor * * @param sText * @param sTitle * @param sProperty * @param iType * @param icon */ HideableMessageDialog(final String sText, final String sTitle, final String sProperty, final int iType, final Icon icon) { super(); final JOptionPane optionPane = UtilGUI.getNarrowOptionPane(72); optionPane.setMessage(UtilGUI.getLimitedMessage(sText, 20)); final Object[] options = { Messages.getString("Ok"), Messages.getString("Hide") }; optionPane.setOptions(options); optionPane.setMessageType(iType); if (icon != null) { optionPane.setIcon(icon); } final JDialog dialog = optionPane.createDialog(null, sTitle); dialog.setAlwaysOnTop(true); // keep it modal (useful at startup) dialog.setModal(true); dialog.pack(); dialog.setLocationRelativeTo(JajukMainWindow.getInstance()); dialog.setVisible(true); if (Messages.getString("Hide").equals(optionPane.getValue())) { // Not show again Conf.setProperty(sProperty, Const.TRUE); } // manually dispose to free up memory, somehow this is not done automatically! dialog.dispose(); } } /** * Error message dialog */ class ErrorMessageDialog extends JajukDialog { /** * Message dialog constructor * * @param sText * @param sTitle * @param sProperty * @param iType * @param icon */ ErrorMessageDialog(final int code, final String sInfoSup) { super(); final JOptionPane optionPane = UtilGUI.getNarrowOptionPane(72); optionPane.setMessage(UtilGUI.getLimitedMessage(Messages.getErrorMessage(code) + (sInfoSup != null ? (" : " + sInfoSup) : ""), 20)); final Object[] options = { Messages.getString("Ok") }; optionPane.setOptions(options); optionPane.setMessageType(JOptionPane.ERROR_MESSAGE); final JDialog dialog = optionPane.createDialog(null, Messages.getString("Error")); dialog.setAlwaysOnTop(true); // keep it modal (useful at startup) dialog.setModal(true); dialog.pack(); dialog.setLocationRelativeTo(JajukMainWindow.getInstance()); dialog.setVisible(true); // manually dispose to free up memory, somehow this is not done automatically! dialog.dispose(); } } abstract class JajukDialog { /** Dialog output */ protected int iResu = -2; /** * * @return the user option */ public int getResu() { return iResu; } }