// ================================================================================
// François MERCIOL 2012
// Name		: Util.java
// Language	: Java
// Author	: François Merciol
// CopyLeft	: Cecil B
// Creation	: 2012
// Version	: 0.1 (xx/xx/xx)
// ================================================================================
package misc;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import static misc.Config.FS;

public class Util implements SwingConstants {

    // ========================================
    // Change Plugins.version too !
    static public final Long version = 20171101L;

    static public final int bufSize = 1024*1024;
    static public final String NL = System.getProperty ("line.separator");
    static public final String ON = "On";
    static public final String OFF = "Off";

    static public BasicButtonUI buttonNoUI = new BasicButtonUI ();
    static public Border buttonNoBorder = BorderFactory.createEmptyBorder (2, 2, 2, 2);

    // ========================================
    static public final String toNumIn2Units (long bytes) {
	if (bytes == 0)
	    return "0 ";
	int u = 0;
	for (; bytes > 1024*1024; bytes >>= 10)
	    u++;
				
	if (bytes < 1024)
	    return String.format ("%d %c", bytes, " kMGTPEZY".charAt (u)).replace ("  ", "");
	u++;
	return String.format ("%.1f %c", bytes/1024f, " kMGTPEZY".charAt (u)).replace (",0 ", " ").replace ("  ", "");
    }
    static public final String toNumIn10Units (long bytes) {
	if (bytes == 0)
	    return "0 ";
	int u = 0;
	for (; bytes > 1000*1000; bytes >>= 10)
	    u++;
				
	if (bytes < 1000)
	    return String.format ("%d %c", bytes, " kMGTPEZY".charAt (u)).replace ("  ", "");
	u++;
	return String.format ("%.1f %c", bytes/1000f, " kMGTPEZY".charAt (u)).replace (",0 ", " ").replace ("  ", "");
    }
    @SuppressWarnings ({"unchecked", "rawtypes"})
    static public final<T> T toEnum (String value, T defaultValue) {
	try  {
	    return (T) Enum.valueOf ((Class<? extends Enum>)defaultValue.getClass (), value);
	} catch (Exception e) {
	}
	return defaultValue;
    }

    // ========================================
    static public final String toColumn (String [] lines, int nbColumn) {
	int size = lines.length;
	if (size < 1)
	    return "";
	if (size < 2)
	    return lines [0]+"\n";
	String result = "";
	int nbLignes = size/nbColumn;
	int nbLongColumn = size % nbColumn;
	for (int i = 0, k = 0; k < size; i++) {
	    int idx = i;
	    String sep = "";
	    for (int j = 0; j < nbColumn && k < size; j++, k++) {
		result += sep + lines [idx];
		sep = "\t";
		idx += nbLignes;
		if (j < nbLongColumn)
		    idx++;
	    }
	    result += "\n";
	}
	return result;
    }

    // ========================================
    static public final String[] set2string (Set<String> set) {
	String [] result = new String [set.size ()];
	int idx = 0;
	for (String key : set)
	    result [idx++] = key;
	return result;
    }

    // ======================================================================
    static public JScrollPane getJScrollPane (Component view) {
	return getJScrollPane (view, true, true);
    }
    static public JScrollPane getJScrollPane (Component view, boolean vNeed, boolean hNeed) {
	return new JScrollPane (view,
				vNeed ? JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED : JScrollPane.VERTICAL_SCROLLBAR_NEVER,
				hNeed ? JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED : JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    }

    // ========================================
    static public JSplitPane getJSplitPane (int newOrientation, Component newLeftComponent, Component newRightComponent) {
	JSplitPane result = new JSplitPane (newOrientation, newLeftComponent, newRightComponent);
	result.setResizeWeight (0);
        result.setOneTouchExpandable (true);
        result.setContinuousLayout (true);
	return result;
    }

    // ========================================
    static public final void setEnabled (Component component, boolean enabled) {
	component.setEnabled (enabled);
	try {
	    for (Component component2 : ((Container) component).getComponents ())
		setEnabled (component2, enabled);
	} catch (Exception e) {
	}
    }

    // ========================================
    static public final GridBagConstraints
	GBC, GBCNL, GBCLNL, GBCBNL;
    static {
	GBC = new GridBagConstraints ();
	GBC.insets = new Insets (1, 2, 1, 2);
	GBC.weightx = GBC.weighty = 1.;
	GBC.fill = GridBagConstraints.HORIZONTAL;

	GBCLNL = (GridBagConstraints) GBC.clone ();
	GBCLNL.anchor = GridBagConstraints.WEST;
	GBCLNL.fill = GridBagConstraints.NONE;
	GBCLNL.gridwidth = GridBagConstraints.REMAINDER;

	GBCNL = (GridBagConstraints) GBC.clone ();
	GBCNL.gridwidth = GridBagConstraints.REMAINDER;


	GBCBNL = (GridBagConstraints) GBCNL.clone ();
	GBCBNL.fill = GridBagConstraints.BOTH;
    }
    static public JPanel getGridBagPanel () {
	return new JPanel (new GridBagLayout ());
    }
    static public void addLabelFields (Container container, String label, Component... components) {
	addLabel (label, RIGHT, container, GBC);
	for (int i = 0; i < components.length; i++)
	    addComponent (components[i], container, i == components.length -1 ? GBCNL : GBC);
    }

    // ========================================
    static public AbstractButton activeButton (AbstractButton button, String action, ActionListener actionListener) {
	button.setActionCommand (action);				
	Bundle.addLocalizedActioner (button);
	button.setToolTipText (Bundle.getAction (action));
	Bundle.addLocalizedToolTip (button);
	if (actionListener != null)
	    button.addActionListener (actionListener);
	return button;
    }

    // ========================================
    static public final JCheckBox newCheckIcon (String action, ActionListener actionListener) {
	JCheckBox button = new JCheckBox (loadActionIcon (action+OFF));
	button.setSelectedIcon (loadActionIcon (action+ON)); 
	activeButton (button, action, actionListener);
	return button;
    }
    static public final JCheckBox newCheckIcon (String action, ActionListener actionListener, boolean defaultValue) {
	JCheckBox button = newCheckIcon (action, actionListener);
	button.setSelected (defaultValue);
	return button;
    }
    static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, Container container) {
	JCheckBox button = newCheckIcon (action, actionListener);
	container.add (button);
	return button;
    }
    static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, boolean defaultValue, Container container) {
	JCheckBox button = newCheckIcon (action, actionListener);
	container.add (button);
	button.setSelected (defaultValue);
	return button;
    }
    static public final void addCheckIcon (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addCheckIcon (action, actionListener, container);
    }
    static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, boolean defaultValue,
						Container container, GridBagConstraints constraint) {
	JCheckBox button = newCheckIcon (action, actionListener, defaultValue);
	addComponent (button, container, constraint);
	return button;
    }

    static public void setCheckIconTableCellEditorRenderer (String action, TableColumn tableColumn) {
	tableColumn.setCellRenderer (new CheckIconTableCellEditorRenderer (action));
	tableColumn.setCellEditor (new DefaultCellEditor (newCheckIcon (action, null)));
    }

    static public class CheckIconTableCellEditorRenderer implements TableCellRenderer {
	JCheckBox jCheckBox;
	public CheckIconTableCellEditorRenderer (String action) {
	    jCheckBox = Util.newCheckIcon (action, null);
	}
	public Component getTableCellRendererComponent (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
	    jCheckBox.setBackground (isSelected ? table.getSelectionBackground () : table.getBackground ());
	    jCheckBox.setForeground (isSelected ? table.getSelectionForeground () : table.getForeground ());
	    if (value instanceof Boolean)
		jCheckBox.setSelected (((Boolean) value).booleanValue ());
	    return jCheckBox;
	}
    }

    // ========================================
    static public final JCheckBox newCheckButton (String action, ActionListener actionListener) {
	JCheckBox button = new JCheckBox (Bundle.getAction (action), loadActionIcon (action+OFF));
	button.setSelectedIcon (loadActionIcon (action+ON)); 
	activeButton (button, action, actionListener);
	return button;
    }
    static public final JCheckBox addCheckButton (String action, ActionListener actionListener, Container container) {
	JCheckBox button = newCheckButton (action, actionListener);
	container.add (button);
	return button;
    }
    static public final void addCheckButton (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addCheckButton (action, actionListener, container);
    }
    static public final JCheckBox addCheckButton (String action, ActionListener actionListener,
						  Container container, GridBagConstraints constraint) {
	JCheckBox button = newCheckButton (action, actionListener);
	addComponent (button, container, constraint);
	return button;
    }
    // ========================================
    static public final JCheckBox newCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue) {
	boolean startValue = Config.getBoolean (action+Config.checkedPostfix, defaultValue);
	JCheckBox button = newCheckButton (action, actionListener);
	button.setSelected (startValue);
	return button;
    }

    static public final JCheckBox addCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue,
							Container container) {
	JCheckBox button = newCheckButtonConfig (action, actionListener, defaultValue);
	container.add (button);
	return button;
    }

    static public final JCheckBox addCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue,
							Container container, GridBagConstraints constraint) {
	JCheckBox button = newCheckButtonConfig (action, actionListener, defaultValue);
	// XXX le test suivant a été supprimer, il faut vérifier que cela tourne toujours
	//if (container != null)
	addComponent (button, container, constraint);
	return button;
    }

    // ========================================
    static public final JRadioButton newRadioButton (String action, ActionListener actionListener, ButtonGroup group, String selected) {
	JRadioButton button = new JRadioButton (Bundle.getAction (action), loadActionIcon (action+OFF),
						action.equals (selected));
	button.setSelectedIcon(loadActionIcon (action+ON)); 
	activeButton (button, action, actionListener);
	group.add (button);
	return button;
    }

    static public final void addRadioButton (List<String> actions, ActionListener actionListener, ButtonGroup group, String selected,
					     Container container) {
	for (String action : actions) {
	    JRadioButton button = newRadioButton (action, actionListener, group, selected);
	    container.add (button);
	}
    }

    static public final JRadioButton addRadioButton (String action, ActionListener actionListener, ButtonGroup group, String selected,
						     Container container, GridBagConstraints constraint) {
	JRadioButton button = newRadioButton (action, actionListener, group, selected);
	addComponent (button, container, constraint);
	return button;
    }

    // ========================================
    static public final JButton newButton (String action, ActionListener actionListener) {
	JButton button = new JButton (Bundle.getAction (action), loadActionIcon (action));
	activeButton (button, action, actionListener);
	return button;
    }
    static public final void newButton (List<String> actions, ActionListener actionListener) {
	for (String action : actions)
	    newButton (action, actionListener);
    }

    static public final JButton addButton (String action, ActionListener actionListener, Container container) {
	JButton button = newButton (action, actionListener);
	container.add (button);
	return button;
    }
    static public final void addButton (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addButton (action, actionListener, container);
    }
    static public final JButton addButton (String action, ActionListener actionListener, Container container, GridBagConstraints constraint) {
	JButton button = newButton (action, actionListener);
	addComponent (button, container, constraint);
	return button;
    }

    // ========================================
    static public final JButton newIconButton (String action, ActionListener actionListener) {
	JButton button = new JButton (loadActionIcon (action));
	button.setAlignmentX (Component.CENTER_ALIGNMENT);
	activeButton (button, action, actionListener);
	return button;
    }
    static public final void newIconButton (List<String> actions, ActionListener actionListener) {
	for (String action : actions)
	    newIconButton (action, actionListener);
    }

    static public final JButton addIconButton (String action, ActionListener actionListener, Container container) {
	JButton button = newIconButton (action, actionListener);
	container.add (button);
	return button;
    }
    static public final void addIconButton (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addIconButton (action, actionListener, container);
    }
    static public final JButton addIconButton (String action, ActionListener actionListener, Container container, GridBagConstraints constraint) {
	JButton button = new JButton (loadActionIcon (action));
	activeButton (button, action, actionListener);
	addComponent (button, container, constraint);
	return button;
    }

    static public final void unBoxButton (Container container) {
	for (AbstractButton button : collectButtons (null, container).values ())
	    unBoxButton (button);
    }
    static public final void unBoxButton (AbstractButton button) {
	button.setUI (buttonNoUI);
	button.setBorder (buttonNoBorder);
    }

    // ========================================
    static public final void addMenuItem (String action, ActionListener actionListener, Container container) {
	JMenuItem item = new JMenuItem (Bundle.getAction (action), loadActionIcon (action));
	activeButton (item, action, actionListener);
	container.add (item);
    }
    static public final void addMenuItem (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addMenuItem (action, actionListener, container);
    }


    // ========================================
    static public Hashtable<String, ArrayList<AbstractButton>> allCheckBox = new Hashtable<String, ArrayList<AbstractButton>> ();
    static public void updateCheckBox (String action, boolean value) {
	ArrayList<AbstractButton> buttons = allCheckBox.get (action);
	if (buttons == null)
	    return;
	Config.setBoolean (action+Config.checkedPostfix, value);
	for (AbstractButton button : buttons)
	    if (button.isSelected () != value)
		button.setSelected (value);
    }

    static public final void addCheckMenuItem (List<String> actions, ActionListener actionListener, Container container) {
	for (String action : actions)
	    addCheckMenuItem (action, actionListener, container);
    }
    static public final JCheckBoxMenuItem addCheckMenuItem (String action, ActionListener actionListener, Container container) {
	return addCheckMenuItem (action, actionListener, Config.getBoolean (action+Config.checkedPostfix, false), container);
    }
    static public final JCheckBoxMenuItem addCheckMenuItem (String action, ActionListener actionListener,
							    boolean defaultValue, Container container) {
	boolean startValue = Config.getBoolean (action+Config.checkedPostfix, defaultValue);
	JCheckBoxMenuItem item = new JCheckBoxMenuItem (Bundle.getAction (action), loadActionIcon (action+OFF), startValue);
	item.setSelectedIcon (loadActionIcon (action+ON)); 
	activeButton (item, action, actionListener);
	container.add (item);
	ArrayList<AbstractButton> checkList = allCheckBox.get (action);
	if (checkList == null)
	    allCheckBox.put (action, checkList = new ArrayList<AbstractButton> ());
	checkList.add (item);
	return item;
    }

    // ========================================
    static public final JMenu addJMenu (JMenuBar jMenuBar, String menuId) {
	JMenu menu = new JMenu (Bundle.getTitle (menuId));
	menu.setActionCommand (menuId);
	Bundle.addLocalizedMenu (menu);
	jMenuBar.add (menu);
	return menu;
    }

    // ========================================
    static public void addComponent (Component component, Container container, GridBagConstraints constraint) {
	GridBagLayout gridbag = (GridBagLayout) container.getLayout ();
	gridbag.setConstraints (component, constraint);
	container.add (component);
    }

    // ========================================
    static public final Dimension space = new Dimension (10, 10);
    static public void addTextFieldSlider (JTextField textField, JLabel label, JSlider slider, Container container, GridBagConstraints constraint) {
	JPanel panel = getGridBagPanel ();
	addComponent (textField, panel, GBC);
	addComponent (label, panel, GBC);
	addComponent (slider, panel, GBCBNL);
	addComponent (panel, container, constraint);
    }

    // ========================================
    static public JLabel newLabel (String messageId, int position) {
	JLabel jLabel = new JLabel (Bundle.getLabel (messageId), position);
	Bundle.addLocalizedLabel (jLabel, messageId);
	return jLabel;
    }

    // ========================================
    static public JLabel addLabel (String messageId, int position, Container container) {
	JLabel jLabel = newLabel (messageId, position);
	container.add (jLabel);
	return jLabel;
    }

    // ========================================
    static public JLabel addLabel (String messageId, int position, Container container, GridBagConstraints constraint) {
	JLabel jLabel = newLabel (messageId, position);
	addComponent (jLabel, container, constraint);
	return jLabel;
    }

    // ========================================
    static public JComboBox<String> newEnum (Class<? extends Enum<?>> enumClass, Enum<?> defaultValue) {
	JComboBox<String> jComboBox = Bundle.getEnum (enumClass, defaultValue);
	Bundle.addLocalizedEnum (jComboBox, enumClass);
	return jComboBox;
    }

    // ========================================
    static public JComboBox<String> addEnum (Class<? extends Enum<?>> enumClass, Enum<?> defaultValue, Container container) {
	JComboBox<String> jComboBox = newEnum (enumClass, defaultValue);
	container.add (jComboBox);
	return jComboBox;
    }

    // ========================================
    static public JComboBox<String> addEnum (Class<? extends Enum<?>> enumClass, Enum<?> defaultValue, Container container, GridBagConstraints constraint) {
	JComboBox<String> jComboBox = newEnum (enumClass, defaultValue);
	addComponent (jComboBox, container, constraint);
	return jComboBox;
    }

    // ========================================
    static public final void setColumnLabels (JTable jTable, String [] columnLabels) {
	for (int i = 0; i < columnLabels.length; i++)
	    jTable.getColumnModel ().getColumn (i).setHeaderValue (Bundle.getLabel (columnLabels [i]));
	try {
	    Container parent = jTable.getParent ();
	    parent.repaint ();
	} catch (Exception e) {
	}
    }

    static public int viewToModel (JTable table, int vColIndex) {
	if (vColIndex >= table.getColumnCount()) {
	    return -1;
	}
	return table.getColumnModel ().getColumn (vColIndex).getModelIndex ();
    }
    public int modelToView (JTable table, int mColIndex) {
	for (int c = 0; c < table.getColumnCount (); c++) {
	    TableColumn col = table.getColumnModel ().getColumn (c);
	    if (col.getModelIndex () == mColIndex) {
		return c;
	    }
	}
	return -1;
    }

    // ========================================
    static public boolean packBug = false;
    static public final void packWindow (final Component startComponent) {
	if (packBug)
	    return;
	SwingUtilities.invokeLater (new Runnable() {
		public void run () {
		    for (Component component = startComponent;
			 component != null;
			 component = component.getParent ()) {
			try {
			    ((Window) component).pack ();
			    return;
			} catch (Exception e) {
			}
		    }
		}
	    });
    }

    // ========================================
    static public final JFrame newJFrame (String title, Component component, boolean exit) {
	JFrame jFrame = new JFrame (title);
	jFrame.addWindowListener (new WindowAdapter () {
		public void windowClosing (WindowEvent e) {
		    if (exit)
			System.exit (0);
		    jFrame.setVisible (false);
		}
	    });
	jFrame.getContentPane ().add (component, BorderLayout.CENTER);
	jFrame.pack ();
	jFrame.setLocationRelativeTo (null);
	jFrame.setVisible (true);
	return jFrame;
    }

    // ========================================
    static public ImageIcon loadActionIcon (String action) {
	return loadImageIcon (Config.dataDirname, Config.iconsDirname, Config.buttonDirname, action+Config.iconsExt);
    }

    // ========================================
    static public ImageIcon loadImageIcon (String... names) {
	URL url = Config.getDataUrl (names);
	return (url != null) ? new ImageIcon (url) : null;
    }

    // ========================================
    static public Image loadImage (String... names) {
	try {
	    return loadImageIcon (names).getImage ();
	} catch (Exception e) {
	    return null;
	}
    }

    // ========================================
    static public final AudioInputStream loadAudio (String... names) {
	URL url = Config.getDataUrl (names);
	try {
	    return AudioSystem.getAudioInputStream (url);
	} catch (Exception e) {
	    return null;
	}
    }

    static public final void play (AudioInputStream stream) {
	if (stream == null)
	    return;
	try {
	    AudioFormat format = stream.getFormat ();
	    DataLine.Info info = new DataLine.Info (Clip.class, format);
	    Clip clip = (Clip) AudioSystem.getLine (info);
	    clip.open (stream);
	    clip.addLineListener (new LineListener () {
		    public void update (LineEvent event) {
			LineEvent.Type type = event.getType();
			if (type == LineEvent.Type.START) {
			    // System.err.println ("Playback started.");
			} else if (type == LineEvent.Type.STOP) {
			    // System.er.println ("Playback completed.");
			    clip.close ();
			}
		    }
		});
	    clip.start ();
	} catch (LineUnavailableException e) {
	    Log.keepLastException ("Audio line for playing back is unavailable", e);
	} catch (IOException e) {
	    Log.keepLastException ("Error playing the audio file", e);
	}

    }

    // ========================================
    @SuppressWarnings({"rawtypes", "unchecked"})
    static public final Hashtable<String, Method> collectMethod (Class javaClass, List<String>... actions) {
	Hashtable<String, Method> actionsMethod = new Hashtable<String, Method> ();
	for (List<String> arg : actions)
	    for (String action : arg) {
		try {
		    actionsMethod.put (action, javaClass.getMethod ("action"+action));
		} catch (NoSuchMethodException e) {
		    try {
			actionsMethod.put (action, javaClass.getMethod ("action"+action, new Class<?>[] { boolean.class }));
		    } catch (NoSuchMethodException e1) {
			Log.keepLastException ("Util::collectMethod can't find methode "+"action"+action, e1);
			e.printStackTrace ();
		    }
		}
	    }
	return actionsMethod;
    }

    // ========================================
    static public final Hashtable<String, AbstractButton> collectButtons (Hashtable<String, AbstractButton> buttons, JMenu jMenu) {
	if (buttons == null)
	    buttons = new Hashtable<String, AbstractButton> ();
	for (int i = 0; i < jMenu.getItemCount (); i++) {
	    try {
		AbstractButton button = jMenu.getItem (i);
		buttons.put (button.getActionCommand (), button);
	    } catch (Exception e) {
	    }
	}
	return buttons;
    }

    static public class ActionControl {
	String action;
	int key;
	boolean control;
	public ActionControl (String action, int key) {
	    this (action, key, true);
	}
	public ActionControl (String action, int key, boolean control) {
	    this.action = action;
	    this.key = key;
	    this.control = control;
	}
    }
    static public void setAccelerator (Hashtable<String, AbstractButton> buttons, ActionControl... actionsControl) {
	for (ActionControl actionControl : actionsControl)
	    ((JMenuItem) buttons.get (actionControl.action)).setAccelerator (KeyStroke.getKeyStroke (actionControl.key, actionControl.control ? InputEvent.CTRL_DOWN_MASK : 0));
    }


    // ========================================
    static public final Hashtable<String, AbstractButton> collectButtons (Hashtable<String, AbstractButton> buttons, Container container) {
	if (buttons == null)
	    buttons = new Hashtable<String, AbstractButton> ();
	for (Component component : container.getComponents ()) {
	    try {
		AbstractButton button = (AbstractButton) component;
		buttons.put (button.getActionCommand (), button);
	    } catch (Exception e) {
	    }
	}
	return buttons;
    }

    // ========================================
    static public final void actionPerformed (final Hashtable<String, Method> actionsMethod, ActionEvent e, final Object object) {
	String cmd = null;
	try {
	    final Object source = e.getSource ();
	    if (source instanceof AbstractButton)
		cmd = ((AbstractButton) source).getActionCommand ();
	    else if (source instanceof JComboBox)
		cmd = ((JComboBox) source).getActionCommand ();
	    final String cmd2 = cmd;
	    if (cmd2 != null)
		SwingUtilities.invokeLater (new Runnable() {
			public void run () {
			    if (source instanceof JCheckBoxMenuItem) {
				try {
				    boolean value = ((JCheckBoxMenuItem) source).isSelected ();
				    actionsMethod.get (cmd2).invoke (object, value);
				    updateCheckBox (cmd2, value);
				    return;
				} catch (IllegalArgumentException e2) {
				} catch (Exception e1) {
				    Log.keepLastException ("Util::actionPerformed (action "+cmd2+" on "+object+")", e1);
				    return;
				}
			    }
			    try {
				actionsMethod.get (cmd2).invoke (object);
			    } catch (Exception e1) {
				Log.keepLastException ("Util::actionPerformed (action "+cmd2+" on "+object+")", e1);
			    }
			}
		    });
	} catch (Exception e2) {
	    e2.printStackTrace ();
	    Log.keepLastException ("Util::actionPerformed (action "+cmd+" on "+object+")", e2);
	}
    }

    // ========================================
    @SuppressWarnings("unchecked")
    static public List<String> merge (List<String>... args) {
	List<String> result = new ArrayList<String> ();
	for (List<String> arg : args)
	    result.addAll (arg);
	return result;
    }

    // ========================================
    //     static public File checkExt (File file, String ext) {
    // 	try {
    // 	    String fileExt = file.getName ();
    // 	    fileExt = fileExt.substring (fileExt.lastIndexOf (".")).toLowerCase ();
    // 	    if (ext.toLowerCase ().equals (fileExt))
    // 		return file;
    // 	} catch (Exception e) {
    // 	}
    // 	return new File (file.getParent (), file.getName ()+ext);
    //     }

    static public String getExtention (File file) {
	return getExtention (file.getName ());
    }
    static public String getExtention (String filename) {
	try {
	    filename = filename.substring (filename.lastIndexOf (".")).toLowerCase ();
	    return filename.substring (1);
	} catch (Exception e) {
	    return null;
	}
    }

    static public String getBase (File file) {
	return getBase (file.getName ());
    }
    static public String getBase (String filename) {
	try {
	    return filename.substring (0, filename.lastIndexOf ("."));
	} catch (Exception e) {
	    return filename;
	}
    }

    static public String changeExtention (String filename, String extention) {
	try {
	    return filename.substring (0, filename.lastIndexOf ("."))+"."+extention;
	} catch (Exception e) {
	    return filename+"."+extention;
	}
    }

    // ========================================
    static public void backup (File file, String extention, String backExtention) {
	if (!file.exists ())
	    return;
	String newFileName = file.getName ();
	if (newFileName.endsWith ("."+extention))
	    newFileName = newFileName.substring (0, newFileName.length () - ("."+extention).length ());
	File backFile = new File (file.getParent (), newFileName+"."+backExtention);
	backFile.delete ();
	try {
	    Files.move (file.toPath (), backFile.toPath (), StandardCopyOption.REPLACE_EXISTING);
	} catch (Exception e) {
	}
    }

    // ========================================
    static public void copy (InputStream src, OutputStream dst, byte[] tmp, ProgressState progressState)
	throws IOException {
	copy (src, dst, tmp, progressState, true, true);
    }
    static public void copy (InputStream src, OutputStream dst, byte[] tmp, ProgressState progressState, boolean srcClose, boolean dstClose)
	throws IOException {
	try {
	    if (tmp == null)
		tmp = new byte[bufSize];
	    for (;;) {
		if (progressState != null && progressState.isInterrupted ())
		    return;
		int nbRead = src.read (tmp, 0, tmp.length);
		if (nbRead < 0)
		    break;
		dst.write (tmp, 0, nbRead);
		if (progressState != null && !progressState.addValue (nbRead))
		    return;
	    }
	} finally {
	    dst.flush ();
	    try {
		if (dstClose)
		    dst.close ();
		if (srcClose)
		    src.close ();
	    } catch (Exception e) {
	    }
	}
    }

    // ========================================
    static public final String ctrlName (String s) {
	return s.replaceAll ("[^0-9a-zA-Z_\\-.]", "");
    }

    // ========================================
    static public final String toCapital (final String s) {
	return s.substring (0, 1).toUpperCase ()+s.substring (1).toLowerCase ();
    }

    static public final<T> boolean containsOne (Collection<T> set1, Collection<T> set2) {
	try {
	    if (set1.size () > set2.size ()) {
		Collection<T> tmp = set1;
		set1 = set2;
		set2 = tmp;
	    }
	    for (T e : set1)
		if (set2.contains (e))
		    return true;
	} catch (Exception e) {
	}
	return false;
    }

    static public final boolean containsPart (String subString, Collection<String> set) {
	if (set == null)
	    return false;
	for (String s : set)
	    if (s.indexOf (subString) >= 0)
		return true;
	return false;
    }

    // ========================================
    @SuppressWarnings("rawtypes")
    static public final String getClassName (final Class aClass) {
	String[] path = aClass.getName ().split ("\\.");
	return path [path.length-1];
    }

    // ========================================
    /**
       retourne vrai si s1 existe et est égale à s2
    */
    static public boolean cmp (String s1, String s2) {
	return (s1 == s2) || ((s1 != null) && (s1.equals (s2)));
    }

    // ========================================
    static int max (int... values) {
	int result = 0;
	for (int val : values)
	    result = Math.max (result, val);
	return result;
    }

    // ========================================
    static public void sleep (int s) {
	try {
	    Thread.sleep (s*1000);
	} catch (InterruptedException e) {
	}
    }

    // ========================================
    static public void deciSleep (int s) {
	try {
	    Thread.sleep (s*100);
	} catch (InterruptedException e) {
	}
    }

    // ========================================
    static private String convertToHex (byte[] data) {
        StringBuffer buf = new StringBuffer ();
        for (int i = 0; i < data.length; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            do {
                if ((0 <= halfbyte) && (halfbyte <= 9))
                    buf.append ((char) ('0' + halfbyte));
                else
                    buf.append ((char) ('a' + (halfbyte - 10)));
                halfbyte = data[i] & 0x0F;
            } while (two_halfs++ < 1);
        }
        return buf.toString ();
    }
 
    public static String removeAccent (String source) {
	return Normalizer.normalize (source, Normalizer.Form.NFD).replaceAll ("[\u0300-\u036F]", "");
    }

    static public String sha1 (String text)
	throws NoSuchAlgorithmException, UnsupportedEncodingException  {
	MessageDigest md;
	md = MessageDigest.getInstance ("SHA-1");
	byte[] sha1hash = new byte[40];
	md.update (text.getBytes ("iso-8859-1"), 0, text.length ());
	sha1hash = md.digest ();
	return convertToHex (sha1hash);
    }

    // ========================================
}
