All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.jaxx.runtime.swing.SwingUtil Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
/*
 * #%L
 * JAXX :: Runtime
 * %%
 * Copyright (C) 2008 - 2017 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.nuiton.jaxx.runtime.swing;

import com.google.common.collect.ImmutableSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jdesktop.swingx.JXTreeTable;
import org.nuiton.jaxx.runtime.JAXXObject;
import org.nuiton.jaxx.runtime.JAXXUtil;
import org.nuiton.jaxx.runtime.swing.editor.BooleanCellEditor;
import org.nuiton.jaxx.runtime.swing.model.JaxxDefaultComboBoxModel;
import org.nuiton.jaxx.runtime.swing.model.JaxxDefaultListModel;
import org.nuiton.jaxx.runtime.swing.renderer.BooleanCellRenderer;
import org.nuiton.jaxx.runtime.swing.renderer.EmptyNumberTableCellRenderer;
import org.nuiton.jaxx.runtime.swing.renderer.EnumTableCellRenderer;
import org.nuiton.jaxx.runtime.swing.renderer.I18nTableCellRenderer;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JRootPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.HyperlinkEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.text.AbstractDocument;
import javax.swing.text.JTextComponent;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.function.Predicate;

/**
 * The runtime swing util class with some nice stuff.
 *
 * Note : Replace previous class jaxx.runtime.swing.Utils in previous versions.
 *
 * @author Tony Chemit - [email protected]
 * @since 1.2
 */
public class SwingUtil extends JAXXUtil {

    /** Logger */
    static private final Log log = LogFactory.getLog(SwingUtil.class);

    public static final String DEFAULT_ICON_PATH = "/icons/";

    public static final String DEFAULT_ICON_PATH_PROPERTY = "default.icon.path";

    private static Field numReaders;

    private static Field notifyingListeners;

    public static final String ICON_PREFIX = "icon.";

    public static final String COLOR_PREFIX = "color.";

    public static Dimension newMinDimension() {
        return new Dimension(0, 0);
    }

    public static Dimension newMaxXDimension() {
        return new Dimension(Short.MAX_VALUE, 0);
    }

    public static Dimension newMaxYDimension() {
        return new Dimension(0, Short.MAX_VALUE);
    }

    public static Dimension newMaxXYDimension() {
        return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
    }

    public static void setText(final JTextComponent c, final String text) {
        try {
            // AbstractDocument deadlocks if we try to acquire a write lock while a read lock is held by the current thread
            // If there are any readers, dispatch an invokeLater.  This should only happen in the event of circular bindings.
            // Similarly, circular bindings can result in an "Attempt to mutate in notification" error, which we deal with
            // by checking for the 'notifyingListeners' property.
            AbstractDocument document = (AbstractDocument) c.getDocument();
            if (numReaders == null) {
                numReaders = AbstractDocument.class.getDeclaredField("numReaders");
                numReaders.setAccessible(true);
            }
            if (notifyingListeners == null) {
                notifyingListeners = AbstractDocument.class.getDeclaredField("notifyingListeners");
                notifyingListeners.setAccessible(true);
            }

            if (notifyingListeners.get(document).equals(Boolean.TRUE)) {
                return;
            }

            if ((Integer) numReaders.get(document) > 0) {
                SwingUtilities.invokeLater(() -> {
                    if (!c.getText().equals(text)) {
                        c.setText(text);
                    }
                });
                return;
            }

            String oldText = c.getText();
            if (oldText == null || !oldText.equals(text)) {
                c.setText(text);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (SecurityException e) {
            c.setText(text);
        }
    }

    /**
     * Fill a combo box model with some datas, and select after all the given object
     *
     * @param combo  the combo to fill
     * @param data   data ot inject in combo
     * @param select the object to select in combo after reflling his model
     */
    public static void fillComboBox(JComboBox combo, Collection data, Object select) {

        ComboBoxModel comboBoxModel = combo.getModel();

        if (comboBoxModel instanceof JaxxDefaultComboBoxModel) {
            JaxxDefaultComboBoxModel model =
                    (JaxxDefaultComboBoxModel) comboBoxModel;

            // evince the model
            model.removeListDataListener(combo);

            // set data
            model.setAllElements(data);

            // attach the model
            model.addListDataListener(combo);
            model.setSelectedItem(select);
        } else if (comboBoxModel instanceof DefaultComboBoxModel) {
            DefaultComboBoxModel model = (DefaultComboBoxModel) combo.getModel();
            // evince the model
            model.removeListDataListener(combo);
            model.removeAllElements();
            if (data != null) {
                for (Object o : data) {
                    model.addElement(o);
                }
            }
            // attach the model
            model.addListDataListener(combo);
            model.setSelectedItem(select);
        } else {
            throw new IllegalArgumentException(
                    "this method need a DefaultComboBoxModel for " +
                            "this model but was " + combo.getModel().getClass());
        }

    }

    /**
     * Fill a list model with some datas, and select after all the given object
     *
     * @param list   the list to fill
     * @param data   data ot inject in combo
     * @param select the object to select in combo after reflling his model
     */
    public static void fillList(JList list, Collection data, Object select) {
        ListModel listModel = list.getModel();
        if (listModel instanceof JaxxDefaultListModel) {
            JaxxDefaultListModel model = (JaxxDefaultListModel) listModel;

            // evince the model
//        model.removeListDataListener(combo);
            model.setAllElements(data);

            // attach the model
//        model.addListDataListener(combo);
            list.setSelectedValue(select, true);
        } else if (listModel instanceof DefaultListModel) {
            DefaultListModel model = (DefaultListModel) listModel;
            // evince the model
//        model.removeListDataListener(combo);
            model.removeAllElements();
            if (data != null) {
                for (Object o : data) {
                    model.addElement(o);
                }
            }
            // attach the model
//        model.addListDataListener(combo);
            list.setSelectedValue(select, true);
        } else {
            throw new IllegalArgumentException(
                    "this method need a DefaultListModel for this model " +
                            "but was " + listModel.getClass());
        }
    }

    /**
     * TODO move this to JAXXComboBox.
     *
     * Fill a combo box model with some datas, and select after all the given object
     *
     * @param combo     the combo to fill
     * @param data      data ot inject in combo
     * @param select    the object to select in combo after reflling his model
     * @param firstNull add a first null element
     * @deprecated since 1.7.XXX this code is moved to JAXXComboBox
     */
    @Deprecated
    public static void fillComboBox(JAXXComboBox combo,
                                    Collection data,
                                    Object select, boolean firstNull) {
        List items = new ArrayList<>();
        if (firstNull) {
            items.add(new Item("null", " ", null, false));
        }
        if (data != null) {
            for (Object d : data) {
                Item item = new Item(d.toString(), d.toString(), d,
                                     d.equals(select));
                items.add(item);
            }
        }
        combo.setItems(items);
    }

    /**
     * Return parent's container corresponding to the Class clazz
     *
     * @param    type of container to obtain from context
     * @param top   the top container
     * @param clazz desired
     * @return parent's container
     */
    @SuppressWarnings({"unchecked"})
    public static  O getParentContainer(Object top,
                                                             Class clazz) {

        return getParent(top, clazz);
    }


    /**
     * Find a parent of the given {@code top} object using the container api to get up.
     *
     * Stop on parent when it is of the given{@code clazz} type.
     *
     * @param    type of container to obtain from context
     * @param top   the top container
     * @param clazz desired
     * @return parent's container
     * @since 2.5.14
     */
    public static  O getParent(Object top, Class clazz) {
        if (top == null) {
            throw new IllegalArgumentException("top parameter can not be null");
        }
        if (!Container.class.isAssignableFrom(top.getClass())) {
            throw new IllegalArgumentException("top parameter " + top +
                                                       " is not a " + Container.class);
        }
        Container parent = ((Container) top).getParent();
        if (parent != null && !clazz.isAssignableFrom(parent.getClass())) {
            parent = (Container) getParent(parent, clazz);
        }
        return (O) parent;
    }

    public static int computeTableColumnWidth(JTable table, Font font, int columnIndex, String suffix) {
        int width = 0;
        if (font == null) {
            font = table.getFont();
        }
//        if (font == null) {
//            TableColumn column = table.getColumnModel().getColumn(columnIndex);
//            font = ((JComponent) column.getCellRenderer()).getFont();
//        }
        FontMetrics fontMetrics = table.getFontMetrics(font);
        for (int i = 0, rowCount = table.getRowCount(); i < rowCount; i++) {
            String key = (String) table.getModel().getValueAt(i, 0);
            int w = fontMetrics.stringWidth(key + suffix);
            if (w > width) {
                width = w;
            }
        }
        return width;
    }

    public static void fixTableColumnWidth(JTable table,
                                           int columnIndex,
                                           int width) {
        TableColumn column = table.getColumnModel().getColumn(columnIndex);
        column.setMaxWidth(width);
        column.setMinWidth(width);
        column.setWidth(width);
        column.setPreferredWidth(width);
    }

    public static void setTableColumnEditor(JTable table,
                                            int columnIndex,
                                            TableCellEditor editor) {
        TableColumn column = table.getColumnModel().getColumn(columnIndex);
        column.setCellEditor(editor);
    }

    public static void setTableColumnRenderer(JTable table,
                                              int columnIndex,
                                              TableCellRenderer editor) {
        TableColumn column = table.getColumnModel().getColumn(columnIndex);
        column.setCellRenderer(editor);
    }

    public static void setI18nTableHeaderRenderer(JTable table,
                                                  String... libelles) {
        I18nTableCellRenderer defaultRenderer =
                new I18nTableCellRenderer(
                        table.getTableHeader().getDefaultRenderer(), libelles);
        table.getTableHeader().setDefaultRenderer(defaultRenderer);
    }

    public static TableCellRenderer newStringTableCellRenderer(
            final DefaultTableCellRenderer renderer,
            final int length,
            final boolean tooltip) {

        return new DefaultTableCellRenderer() {

            private static final long serialVersionUID = 1L;

            @Override
            public Component getTableCellRendererComponent(
                    JTable table,
                    Object value,
                    boolean isSelected,
                    boolean hasFocus,
                    int row,
                    int column) {

                renderer.getTableCellRendererComponent(
                        table,
                        value,
                        isSelected,
                        hasFocus,
                        row,
                        column
                );
                String val = renderer.getText();
                String val2 = val;
                if (val.length() > length) {
                    val2 = val.substring(0, length - 3) + "...";
                }

                JComponent comp = (JComponent)
                        super.getTableCellRendererComponent(
                                table,
                                val2,
                                isSelected,
                                hasFocus,
                                row,
                                column
                        );
                if (tooltip) {
                    comp.setToolTipText(val);
                }
                return comp;
            }
        };
    }

    /**
     * Box a component in a {@link JXLayer}.
     *
     * @param component the component to box
     * @return the {@link JXLayer} boxing the component
     */
    public static  JXLayer boxComponentWithJxLayer(V component) {
        JXLayer layer = getLayer(component);
        if (layer != null) {
            return layer;
        }
        layer = new JXLayer<>();
        layer.setView(component);
        return layer;
    }

    public static List getLayeredComponents(JAXXObject object) {
        List result = new ArrayList<>();
        for (Entry child : object.get$objectMap().entrySet()) {
            if (child.getValue() == null) {
                log.warn("find a null object in $objectMap " + child.getKey());
                continue;
            }
            if (JComponent.class.isAssignableFrom(child.getValue().getClass())) {
                JComponent comp = (JComponent) child.getValue();
                if (isLayered(comp)) {
                    result.add(comp);
                }
            }
        }
        return result;
    }

    public static  JXLayer getLayer(V comp) {
        if (!isLayered(comp)) {
            return null;
        }
        return (JXLayer) comp.getParent();
    }

    public static void setLayerUI(JComponent comp, LayerUI ui) {
        JXLayer layer = getLayer(comp);
        layer.setUI(ui);
    }

    public static boolean isLayered(JComponent comp) {
        Container parent = comp.getParent();
        return parent != null && parent instanceof JXLayer;
    }

    /**
     * recherche les composants portant le meme nom que les champs de la classe
     * clazz. Cette methode est statique pour pouvoir eventuellement l'utiliser
     * dans un autre context (je pense par exemple a la generation jaxx).
     *
     * Si la recherche echoue pour quelque raison que se soit, aucune exception
     * n'est leve, et la map retournee est tout simplement vide ou incomplete
     *
     * @param clazz     la classe ou recherche les champs
     * @param container le container ou rechercher les composants d'edition
     * @return le dictionnaire des composants recherches.
     */
    public static Map lookingForEditor(
            Class clazz,
            Container container) {
        Map result = new HashMap<>();
        try {
            // looking for all component with name set
            Map allNamedComponent =
                    new HashMap<>();
            List todo = new LinkedList<>();
            todo.add(container);
            while (todo.size() > 0) {
                for (ListIterator i = todo.listIterator();
                     i.hasNext(); ) {
                    Container parent = i.next();
                    i.remove();
                    for (Component c : parent.getComponents()) {
                        if (c instanceof Container) {
                            i.add((Container) c);
                            String name = c.getName();
                            if (c instanceof JComponent &&
                                    name != null && !"".equals(name)) {
                                allNamedComponent.put(name, (JComponent) c);
                            }
                        }
                    }
                }
            }

            // looking for all properties on class
            BeanInfo info = Introspector.getBeanInfo(clazz);
            PropertyDescriptor[] props = info.getPropertyDescriptors();

            // find if one properties have same name that component
            for (PropertyDescriptor prop : props) {
                String name = prop.getName();
                if (allNamedComponent.containsKey(name)) {
                    result.put(name, allNamedComponent.get(name));
                }
            }

        } catch (IntrospectionException eee) {
            log.warn("Can't introspect bean", eee);
        }

        if (log.isDebugEnabled()) {
            log.debug("Result: " + result);
        }

        return result;
    }

    /**
     * Centrer un component graphique au center d'un autre component.
     *
     * Note: si le parent est null, alors on ne fait rien.
     *
     * @param parent    le component parent
     * @param component le component à centrer
     */
    public static void center(Component parent, Component component) {
        if (parent == null) {
            return;
        }
        Rectangle r = parent.getBounds();
        int x = r.x + (r.width - component.getSize().width) / 2;
        int y = r.y + (r.height - component.getSize().height) / 2;
        component.setLocation(x, y);
    }

    /**
     * Try to load the Nimbus look and feel.
     *
     * @throws UnsupportedLookAndFeelException if nimbus is not applicable
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static void initNimbusLoookAndFeel() throws UnsupportedLookAndFeelException, ClassNotFoundException, InstantiationException, IllegalAccessException {

        for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(laf.getName())) {
                UIManager.setLookAndFeel(laf.getClassName());
            }
        }
    }

    /**
     * Load the ui.properties file and set in {@link UIManager} colors and
     * icons found.
     *
     * @param defaultUIConfig le path vers le fichier de la config d'ui par
     *                        défaut (doit etre dansle class-path)
     * @param extraUIConfig   le path vers une surcharge de la config d'ui
     *                        (doit etre dans le class-path)
     * @throws IOException if could not load the ui.properties file
     */
    public static void loadUIConfig(String defaultUIConfig,
                                    String extraUIConfig) throws IOException {

        Properties p = new Properties();
        log.info("loading default UI config " + defaultUIConfig);
        p.load(SwingUtil.class.getResourceAsStream(defaultUIConfig));
        if (log.isDebugEnabled()) {
            log.debug(p.toString());
        }
        if (extraUIConfig != null) {
            InputStream extraStream =
                    SwingUtil.class.getResourceAsStream(extraUIConfig);
            if (extraStream == null) {
                log.warn("could not find extraUIConfig : " + extraUIConfig);
            } else {
                log.info("loading extra UI config " + extraUIConfig);
                Properties p2 = new Properties(p);
                p2.load(extraStream);
                if (log.isDebugEnabled()) {
                    log.debug(p2.toString());
                }
                p.putAll(p2);
            }
        }
        for (Entry entry : p.entrySet()) {
            String key = (String) entry.getKey();
            if (key.startsWith(ICON_PREFIX)) {
                ImageIcon icon;
                try {
                    icon = createImageIcon((String) entry.getValue());
                    UIManager.put(key.substring(ICON_PREFIX.length()), icon);
                } catch (Exception e) {
                    log.warn("could not load icon " + entry.getValue());
                }
                continue;
            }
            if (key.startsWith(COLOR_PREFIX)) {
                String value = (String) entry.getValue();
                String[] rgb = value.split(",");
                UIManager.put(
                        key.substring(COLOR_PREFIX.length()),
                        new Color(
                                Integer.valueOf(rgb[0]),
                                Integer.valueOf(rgb[1]),
                                Integer.valueOf(rgb[2])
                        )
                );
            }
        }
    }

    /**
     * Load the ui.properties file and set in {@link UIManager} colors and
     * icons found.
     *
     * @param incoming the ui resources to load.
     * @since 2.1
     */
    public static void loadUIConfig(Properties incoming) {

        for (Entry entry : incoming.entrySet()) {
            String key = (String) entry.getKey();
            if (key.startsWith(ICON_PREFIX)) {
                ImageIcon icon;
                try {
                    icon = createImageIcon((String) entry.getValue());
                    UIManager.put(key.substring(ICON_PREFIX.length()), icon);
                } catch (Exception e) {
                    log.warn("could not load icon " + entry.getValue());
                }
                continue;
            }
            if (key.startsWith(COLOR_PREFIX)) {
                String value = (String) entry.getValue();
                String[] rgb = value.split(",");
                UIManager.put(
                        key.substring(COLOR_PREFIX.length()),
                        new Color(
                                Integer.valueOf(rgb[0]),
                                Integer.valueOf(rgb[1]),
                                Integer.valueOf(rgb[2])
                        )
                );
            }
        }
    }

    /**
     * Iterate the components of a {@link JTabbedPane} in natural order.
     *
     * Says using method {@link JTabbedPane#getComponent(int)}
     *
     * @param tabs the
     * @return the iterator
     * @since 1.4
     */
    public static TabbedPaneIterator newTabbedPaneIterator(
            JTabbedPane tabs) {
        return new TabbedPaneIterator(false, tabs) {

            @Override
            protected Component get(int index, Component comp) {
                return comp;
            }
        };
    }

    /**
     * Return the selected rows of the table in the model coordinate or empty
     * array if selection is empty.
     *
     * @param table the table to seek
     * @return the selected rows of the table in the model coordinate or empty
     * array if selection is empty.
     * @since 2.5.29
     */
    public static int[] getSelectedModelRows(JTable table) {
        int[] selectedRows = table.getSelectedRows();
        int length = selectedRows.length;
        int[] result = new int[length];
        for (int i = 0; i < length; i++) {
            int selectedRow = selectedRows[i];
            result[i] = table.convertRowIndexToModel(selectedRow);
        }
        return result;
    }

    /**
     * Return the selected row of the table in the model coordinate or
     * {@code -1} if selection is empty.
     *
     * @param table the table to seek
     * @return the selected row of the table in the model coordinate or
     * {@code -1} if selection is empty.
     * @since 2.5.29
     */
    public static int getSelectedModelRow(JTable table) {
        int result = table.getSelectedRow();
        if (result != -1) {
            // can convert to model coordinate
            result = table.convertRowIndexToModel(result);
        }
        return result;
    }

    /**
     * Return the selected column of the table in the model coordinate or
     * {@code -1} if selection is empty.
     *
     * @param table the table to seek
     * @return the selected column of the table in the model coordinate or
     * {@code -1} if selection is empty.
     * @since 2.5.29
     */
    public static int getSelectedModelColumn(JTable table) {
        int result = table.getSelectedColumn();
        if (result != -1) {
            // can convert to model coordinate
            result = table.convertColumnIndexToModel(result);
        }
        return result;
    }

    /**
     * Select the given row index {@code rowIndex} (from the model coordinate)
     * in the selection of the given table.
     *
     * @param table    the table where to set the selection
     * @param rowIndex the row index in the model coordinate to set as selection
     * @since 2.5.29
     */
    public static void setSelectionInterval(JTable table, int rowIndex) {

        int rowViewIndex = table.convertRowIndexToView(rowIndex);
        table.getSelectionModel().setSelectionInterval(rowViewIndex, rowViewIndex);
    }

    /**
     * Add the given row index {@code rowIndex} (from the model coordinate)
     * in the selection of the given table.
     *
     * @param table    the table where to set the selection
     * @param rowIndex the row index in the model coordinate to add to selection
     * @since 2.5.29
     */
    public static void addRowSelectionInterval(JTable table, int rowIndex) {

        int rowViewIndex = table.convertRowIndexToView(rowIndex);
        table.getSelectionModel().addSelectionInterval(rowViewIndex, rowViewIndex);
    }

    /**
     * A simple iterator on a {@link JTabbedPane}.
     *
     * Implements the method {@link #get(int, Component)} to obtain
     * the data required given the component (or index).
     *
     * You can also inverse the order by usin the method {@link #reverse()}.
     *
     * Note: After the use of the method {@link #reverse()} the iterator returns
     * to the first element.
     *
     * @param  the type of return elements.
     * @since 1.4
     */
    public static abstract class TabbedPaneIterator implements Iterator {

        final JTabbedPane tabs;

        boolean reverse;

        int index;

        int increment;

        protected abstract O get(int index, Component comp);

        public TabbedPaneIterator(boolean reverse, JTabbedPane tabs) {
            this.tabs = tabs;
            setReverse(reverse);
        }

        public void reset() {
            setReverse(reverse);
        }

        public int size() {
            return tabs.getTabCount();
        }

        public TabbedPaneIterator reverse() {
            setReverse(!reverse);
            return this;
        }

        @Override
        public boolean hasNext() {
            return reverse ? index > 0 : index < tabs.getTabCount();
        }

        public int getIndex() {
            return index;
        }

        @Override
        public O next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            Component next = tabs.getComponentAt(index);
            O result = get(index, next);
            index += increment;
            return result;
        }

        @Override
        public void remove() {
            throw new IllegalStateException("not implemented for " + this);
        }

        @Override
        public String toString() {
            return super.toString() + "< reverse:" + reverse + ", index:" +
                    index + ", size:" + tabs.getTabCount() + " >";
        }

        protected void setReverse(boolean reverse) {
            if (reverse) {
                index = tabs.getTabCount() - 1;
                increment = -1;
            } else {
                index = 0;
                increment = 1;
            }
            this.reverse = reverse;
        }
    }

    public static JLabel newLabel(String text, Object iconKey, int aligment) {
        Icon icon = null;
        if (iconKey instanceof Icon) {
            icon = (Icon) iconKey;
        } else if (iconKey instanceof String) {
            icon = getUIManagerActionIcon((String) iconKey);
        }
        JLabel result;
        if (icon == null) {
            result = new JLabel(text, aligment);
        } else {
            result = new JLabel(text, icon, aligment);
        }
        return result;
    }

    /**
     * Gets the higest visible component in a ancestor hierarchy at
     * specific x,y coordinates
     *
     * @param parent
     * @param x
     * @param y
     * @return the deppest component
     */
    public static Component getDeepestObjectAt(Component parent, int x, int y) {

        if (parent instanceof Container) {
            Container cont = (Container) parent;
            // use a copy of 1.3 Container.findComponentAt
            Component child = findComponentAt(cont,
                                              cont.getWidth(),
                                              cont.getHeight(), x, y);
            if (child != null && child != cont) {
                //log.info("child find : " + child.getName());
                if (child instanceof JRootPane) {
                    JLayeredPane lp = ((JRootPane) child).getLayeredPane();
                    Rectangle b = lp.getBounds();
                    child = getDeepestObjectAt(lp, x - b.x, y - b.y);
                }
                if (child != null) {
                    return child;
                }
            }
        }
        // if the parent is not a Container then it might be a MenuItem.
        // But even if it isn't a MenuItem just return the parent because
        // that's a close as we can come.
        return parent;
    }

    public static Component findComponentAt(Container cont,
                                            int width,
                                            int height,
                                            int x,
                                            int y) {
        //log.info("container : " + cont.getName());
        synchronized (cont.getTreeLock()) {

            if (!(x >= 0 && x < width && y >= 0 && y < height &&
                    cont.isVisible() && cont.isEnabled())) {
                return null;
            }

            Component[] component = cont.getComponents();
            int ncomponents = cont.getComponentCount();

            // Two passes: see comment in sun.awt.SunGraphicsCallback
            for (int i = 0; i < ncomponents; i++) {
                Component comp = component[i];
                Rectangle rect = null;

                if (comp != null && !comp.isLightweight()) {
                    if (rect == null || rect.width == 0 || rect.height == 0) {
                        rect = comp.getBounds();
                    }
                    if (comp instanceof JXLayer) {
                        JXLayer layer = (JXLayer) comp;
                        comp = layer.getView();
                    }
                    if (comp instanceof Container) {
                        comp = findComponentAt(
                                (Container) comp,
                                rect.width,
                                rect.height,
                                x - rect.x,
                                y - rect.y
                        );
                    } else {
                        comp = comp.getComponentAt(x - rect.x, y - rect.y);
                    }
                    if (comp != null && comp.isVisible() && comp.isEnabled()) {
                        return comp;
                    }
                }
            }

            for (int i = 0; i < ncomponents; i++) {
                Component comp = component[i];
                Rectangle rect = null;

                if (comp != null && comp.isLightweight()) {
                    if (rect == null || rect.width == 0 || rect.height == 0) {
                        rect = comp.getBounds();
                    }
                    if (comp instanceof JXLayer) {
                        JXLayer layer = (JXLayer) comp;
                        comp = layer.getView();
                    }
                    if (comp instanceof Container) {
                        comp = findComponentAt(
                                (Container) comp,
                                rect.width,
                                rect.height,
                                x - rect.x,
                                y - rect.y
                        );
                    } else {
                        comp = comp.getComponentAt(x - rect.x, y - rect.y);
                    }
                    if (comp != null && comp.isVisible() && comp.isEnabled()) {
                        return comp;
                    }
                }
            }
            return cont;
        }
    }

    public static TableCellRenderer newDeleteCellRenderer(
            DefaultTableCellRenderer renderer) {
        Icon icon = UIManager.getIcon("Table.removeIcon");
        if (icon == null) {
            // try with default icon
            icon = createActionIcon("delete");
        }
        return new BooleanCellEditor(renderer, icon);
    }

    public static TableCellRenderer newBooleanTableCellRenderer(
            TableCellRenderer renderer) {
        return new BooleanCellRenderer(renderer);
    }

    public static TableCellRenderer newBooleanTableCellRenderer(
            TableCellRenderer renderer, Predicate predicate) {
        return new BooleanCellRenderer(renderer, predicate);
    }

    public static TableCellRenderer newBooleanTableCellEditorAndRenderer(
            TableCellRenderer renderer) {
        return new BooleanCellEditor(renderer);
    }

    public static BooleanCellEditor newBooleanTableCellEditor(
            TableCellRenderer renderer) {
        return new BooleanCellEditor(renderer);
    }

    public static EmptyNumberTableCellRenderer newEmptyNumberTableCellRenderer(
            TableCellRenderer renderer) {
        return new EmptyNumberTableCellRenderer(renderer);
    }

    public static > EnumTableCellRenderer
    newEnumTableCellRenderer(TableCellRenderer renderer, Class enumClass) {
        return new EnumTableCellRenderer<>(renderer, enumClass);
    }

    private static final ImmutableSet OPEN_PROTOCOLS = ImmutableSet.of("mailto", "http", "https", "ftp", "file");

    /**
     * Open a link coming from a {@link HyperlinkEvent}.
     *
     * And try to open the link if an url in a browser.
     *
     * @param he the event to treate
     * @since 1.6.0
     */
    public static void openLink(HyperlinkEvent he) {
        if (he.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {

            if (Desktop.isDesktopSupported()) {
                try {
                    URL u = he.getURL();
                    if (OPEN_PROTOCOLS.contains(u.getProtocol().toLowerCase())) {
                        Desktop.getDesktop().browse(u.toURI());
                    }
                } catch (IOException | URISyntaxException e) {
                    if (log.isErrorEnabled()) {
                        log.error("Error while opening link", e);
                    }
                }
            }
        }
    }

    public static void openLink(String url) {

        try {
            if (!Desktop.isDesktopSupported()) {
                throw new IllegalStateException("Desktop not accessible");
            }


            Desktop desktop = Desktop.getDesktop();

            if (!desktop.isSupported(Desktop.Action.BROWSE)) {

                throw new IllegalStateException("Action 'Go' not supported by desktop");
            }

            URI uri = new URI(url);
            desktop.browse(uri);
        } catch (Exception e) {

            if (log.isErrorEnabled()) {
                log.error("Error while opening link", e);
            }
        }
    }

    /**
     * Expand all childs of a tree.
     *
     * @param tree the tree to expand
     */
    public static void expandTree(final JTree tree) {
        SwingUtilities.invokeLater(() -> {
            int i = 0;
            while (i < tree.getRowCount()) {
                tree.expandRow(i++);
            }
        });
    }

    /**
     * Expand all childs of a tree table.
     *
     * FIXME : Voir pour enlever le copier coller
     *
     * @param treeTable the tree to expand
     */
    public static void expandTreeTable(final JXTreeTable treeTable) {
        SwingUtilities.invokeLater(() -> {
            int i = 0;
            while (i < treeTable.getRowCount()) {
                treeTable.expandRow(i++);
            }
        });
    }

    /**
     * Add a listener of tree selection model to expand a new selected node
     * when it is selected.
     *
     * @param tree the tree to treate
     */
    public static void addExpandOnClickListener(final JTree tree) {

        tree.getSelectionModel().addTreeSelectionListener(
                e -> {
                    TreeNode node = (TreeNode)
                            e.getPath().getLastPathComponent();
                    if (node != null && !node.isLeaf()) {

                        SwingUtilities.invokeLater(() -> {
                            for (TreePath path : e.getPaths()) {
                                if (e.isAddedPath(path) &&
                                        !tree.isExpanded(path)) {
                                    log.info("expand node [" + path
                                                     + "]");
                                    // will expand the node
                                    tree.expandPath(path);
                                }
                            }
                        });
                    }
                });
    }

    /**
     * Add a listener of tree table selection model to expand a new selected
     * node when it is selected.
     *
     * FIXME : Voir pour enlever le copier coller
     *
     * @param treeTable the tree to treate
     */
    public static void addExpandOnClickListener(final JXTreeTable treeTable) {

        treeTable.addTreeSelectionListener(e -> {
            TreeNode node = (TreeNode) e.getPath().getLastPathComponent();
            if (node != null && !node.isLeaf()) {

                SwingUtilities.invokeLater(() -> {
                    for (TreePath path : e.getPaths()) {
                        if (e.isAddedPath(path) &&
                                !treeTable.isExpanded(path)) {
                            log.info("expand node [" + path + "]");
                            // will expand the node
                            treeTable.expandPath(path);
                        }
                    }
                });
            }
        });
    }

    /**
     * Set the width of the given component
     *
     * @param component the component to resize
     * @param width     the new width to apply
     */
    public static void setComponentWidth(Component component, int width) {
        component.setSize(width, component.getHeight());
        if (component instanceof JComponent) {
            JComponent jcomponent = (JComponent) component;
            jcomponent.setPreferredSize(
                    new Dimension(width,
                                  jcomponent.getPreferredSize().height)
            );
            jcomponent.setMinimumSize(
                    new Dimension(width,
                                  jcomponent.getPreferredSize().height)
            );
            if (jcomponent.isDisplayable()) {
                jcomponent.revalidate();
            }
        }
    }

    /**
     * Set the height of a given component.
     *
     * @param component the component to resize
     * @param height    the new height to apply
     */
    public static void setComponentHeight(Component component, int height) {
        component.setSize(component.getWidth(), height);
        if (component instanceof JComponent) {
            JComponent jcomponent = (JComponent) component;
            jcomponent.setPreferredSize(
                    new Dimension(jcomponent.getPreferredSize().width, height));
            jcomponent.setMinimumSize(
                    new Dimension(jcomponent.getPreferredSize().width, height));
            if (jcomponent.isDisplayable()) {
                jcomponent.revalidate();
            }
        }
    }

    public static ImageIcon createIcon(String path) {
        URL imgURL = JAXXUtil.class.getResource(path);
        if (imgURL != null) {
            return new ImageIcon(imgURL);
        } else {
            throw new IllegalArgumentException("could not find icon " + path);
        }
    }

    /**
     * @param path the location of icons in root directory icons
     * @return the icon at {@link #getIconPath()}+path
     */
    public static ImageIcon createImageIcon(String path) {
        String iconPath = getIconPath();
        return createIcon(iconPath + path);
    }

    /**
     * @param key the key of the icon to retreave from {@link UIManager}
     * @return the icon, or {@code null} if no icon found in {@link UIManager}
     */
    public static Icon getUIManagerIcon(String key) {
        return UIManager.getIcon(key);
    }

    /**
     * retreave for the {@link UIManager} the icon prefixed by {@code action}.
     *
     * @param key the key of the action icon to retreave from {@link UIManager}
     * @return the icon, or {@code null} if no icon found in {@link UIManager}
     */
    public static Icon getUIManagerActionIcon(String key) {
        return getUIManagerIcon("action." + key);
    }

    public static ImageIcon createActionIcon(String name) {
        String iconPath = getIconPath();
        return createIcon("action", name);
    }

    public static ImageIcon createIcon(String classifier, String name) {
        String iconPath = getIconPath();
        return createIcon(iconPath + classifier + "-" + name + ".png");
    }

    public static ImageIcon createI18nIcon(String name) {
        String iconPath = getIconPath();
        return createIcon(iconPath + "i18n/" + name + ".png");
    }

    private static String getIconPath() {
        String iconPath = UIManager.getString(DEFAULT_ICON_PATH_PROPERTY);
        if (iconPath == null) {
            iconPath = DEFAULT_ICON_PATH;
        } else {
            if (!iconPath.endsWith("/")) {
                iconPath += "/";
            }
        }
        return iconPath;
    }

    /**
     * Add {@link java.awt.event.KeyListener} to focus next editable cell on TAB key
     *
     * @param table to add TAB {@link java.awt.event.KeyListener}
     */
    public static void makeTableTabFocusable(final JTable table) {
        table.setCellSelectionEnabled(true);
        table.setSurrendersFocusOnKeystroke(true);
        table.addKeyListener(new KeyAdapter() {

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_TAB) {

                    // get table informations
                    int selectedColumn = table.getSelectedColumn();
                    int selectedRow = table.getSelectedRow();

                    if (log.isDebugEnabled()) {
                        log.debug("Selected was row[" + selectedRow + "] column[" + selectedColumn + "]");
                    }

                    int columnCount = table.getColumnCount();
                    int rowCount = table.getRowCount();

                    // search on current line
                    for (int toSelectColumn = selectedColumn; toSelectColumn < columnCount; toSelectColumn++) {

                        if (editCell(table, selectedRow, toSelectColumn)) {
                            return;
                        }
                    }

                    // search on other lines
                    for (int toSelectRow = selectedRow; toSelectRow < rowCount; toSelectRow++) {
                        for (int toSelectColumn = 0; toSelectColumn < columnCount; toSelectColumn++) {

                            if (editCell(table, toSelectRow, toSelectColumn)) {
                                return;
                            }
                        }
                    }
                }
            }
        });
    }

    /**
     * Used to edit a cell of a given table.
     *
     * @param table   the table to edit
     * @param row     row index of cell to editing
     * @param colummn column index of cell to editing
     * @return {@code false} if for any reason the cell cannot be edited,
     * or if the indices are invalid
     */
    public static boolean editCell(JTable table, int row, int colummn) {

        boolean result = false;
        if (table.isCellEditable(row, colummn)) {

            if (table.isEditing()) {

                int editingRow = table.getEditingRow();
                int editingColumn = table.getEditingColumn();

                // stop edition
                TableCellEditor cellEditor = table.getCellEditor(editingRow,
                                                                 editingColumn);
                cellEditor.stopCellEditing();
            }

            // select row
            table.setColumnSelectionInterval(colummn, colummn);
            table.setRowSelectionInterval(row, row);

            // edit cell
            result = table.editCellAt(row, colummn, new EventObject(table));
            Component component = table.getEditorComponent();
            component.requestFocus();

            if (log.isDebugEnabled()) {
                log.debug("Select row[" + row + "] column[" + colummn +
                                  "] return : " + result);
            }
        }
        return result;
    }

    public static void ensureRowIndex(TableModel model, int rowIndex)
            throws ArrayIndexOutOfBoundsException {
        if (rowIndex < -1 || rowIndex >= model.getRowCount()) {
            throw new ArrayIndexOutOfBoundsException(
                    "the rowIndex was " + rowIndex + ", but should be int [0,"
                            + (model.getRowCount() - 1) + "]");
        }
    }

    public static void ensureColumnIndex(TableModel model, int index)
            throws ArrayIndexOutOfBoundsException {
        if (index < -1 || index >= model.getColumnCount()) {
            throw new ArrayIndexOutOfBoundsException(
                    "the columnIndex was " + index + ", but should be int [0,"
                            + (model.getColumnCount() - 1) + "]");
        }
    }

    /**
     * Add to a given table a selec tion model listener to always scroll to
     * current cell selection.
     *
     * @param table the table
     * @since 2.5.3
     */
    public static void scrollToTableSelection(final JTable table) {

        table.getSelectionModel().addListSelectionListener(e -> {
            ListSelectionModel listSelectionModel =
                    (ListSelectionModel) e.getSource();
            int firstIndex = e.getFirstIndex();
            int lastIndex = e.getLastIndex();
            Integer newSelectedRow = null;

            if (listSelectionModel.isSelectionEmpty()) {

                // no selection
            } else if (listSelectionModel.isSelectedIndex(firstIndex)) {

                // use first index
                newSelectedRow = firstIndex;
            } else if (listSelectionModel.isSelectedIndex(lastIndex)) {

                // use last index
                newSelectedRow = lastIndex;
            }
            if (newSelectedRow != null) {
                Rectangle rect = table.getCellRect(newSelectedRow, 0, true);
                table.scrollRectToVisible(rect);
            }
        });
    }

    /**
     * Get the first char of a String, or return default value.
     *
     * Used for example by generated code (i18nMnemonic).
     *
     * @param text         the text to cut
     * @param defaultValue default char value if text is null, or empty
     * @return the first char of the given text or the default value if text is null or empty.
     * @since 2.6.14
     */
    public static char getFirstCharAt(String text, char defaultValue) {
        return text == null || text.trim().length() == 0 ?
                defaultValue : text.charAt(0);
    }

    public static void openLink(URI uri) {

        Desktop desktop = getDesktopForBrowse();

        try {

            desktop.browse(uri);
        } catch (Exception e) {

            throw new RuntimeException("Could not open link " + uri, e);
        }
    }

    public static Desktop getDesktopForBrowse() {

        if (!Desktop.isDesktopSupported()) {
            throw new RuntimeException("Desktop not accessible");
        }

        Desktop desktop = Desktop.getDesktop();

        if (!desktop.isSupported(Desktop.Action.BROWSE)) {

            throw new RuntimeException("Action 'Go' not supported by desktop");
        }

        return desktop;
    }

    public static DefaultComboBoxModel newComboModel(Object... items) {
        return new DefaultComboBoxModel(items);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy