001package arcommon.ui.other;
002
003import java.awt.Component;
004import java.awt.Container;
005import java.awt.Dialog;
006import java.awt.Dimension;
007import java.awt.FlowLayout;
008import java.awt.Font;
009import java.awt.Frame;
010import java.awt.Graphics;
011import java.awt.Graphics2D;
012import java.awt.GraphicsConfiguration;
013import java.awt.GraphicsDevice;
014import java.awt.GraphicsEnvironment;
015import java.awt.KeyboardFocusManager;
016import java.awt.Point;
017import java.awt.Rectangle;
018import java.awt.Toolkit;
019import java.awt.Window;
020import java.awt.event.ActionEvent;
021import java.awt.event.ActionListener;
022import java.awt.event.ComponentEvent;
023import java.awt.event.ComponentListener;
024import java.awt.event.KeyEvent;
025import java.awt.event.WindowEvent;
026import java.awt.font.TextLayout;
027import java.awt.geom.Rectangle2D;
028import java.io.File;
029import java.lang.reflect.Field;
030import java.lang.reflect.InvocationTargetException;
031
032import javax.swing.AbstractAction;
033import javax.swing.Action;
034import javax.swing.ActionMap;
035import javax.swing.InputMap;
036import javax.swing.JButton;
037import javax.swing.JComponent;
038import javax.swing.JDialog;
039import javax.swing.JFileChooser;
040import javax.swing.JFrame;
041import javax.swing.JOptionPane;
042import javax.swing.JTree;
043import javax.swing.KeyStroke;
044import javax.swing.SwingUtilities;
045import javax.swing.filechooser.FileNameExtensionFilter;
046import javax.swing.tree.DefaultMutableTreeNode;
047import javax.swing.tree.TreePath;
048
049import arcommon.DateUtil;
050import arcommon.FileUtil;
051import arcommon.MyThread;
052import arcommon.ReflectionUtil;
053import arcommon.StringUtil;
054import arcommon.Util;
055
056public class GUIUtil {
057    public static String defaultSelectAFileOrDirectoryPath;
058    
059    public enum YesNoDialogAnswer {
060        YES,
061        NO,
062        CANCEL
063    }
064    
065    public static Window getFocusedWindow() {
066        Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
067        return activeWindow;
068    }
069
070    public static String selectAFile(String baseFileName, boolean mustExist, String title) {
071        return selectAFile(baseFileName, mustExist, title, null);
072    }
073
074    public static String selectAFile(String baseFileName, boolean mustExist, String title, String buttonText) {
075        return selectAFileOrDirectory(getFocusedWindow(), baseFileName, JFileChooser.FILES_ONLY, mustExist, true, title, buttonText);
076    }
077    
078    public static String selectADirectory(String basePath, boolean mustExist, String title) {
079        return selectADirectory(basePath, mustExist, title, null);
080    }
081
082    public static String selectADirectory(String basePath, boolean mustExist, String title, String buttonText) {
083        return selectAFileOrDirectory(getFocusedWindow(), basePath, JFileChooser.DIRECTORIES_ONLY, mustExist, false, title, buttonText);
084    }
085    
086    private static String selectAFileOrDirectory(Component parentComponent, String baseFileName, final int fileSelectionMode, final boolean mustExist, boolean useFilter, String title, String buttonText) {
087        JFileChooser chooser = new JFileChooser() {
088            private static final long serialVersionUID = -8132855969954010280L;
089
090            @Override
091            public void approveSelection() {
092                if (mustExist && !getSelectedFile().exists()) {
093                    switch (fileSelectionMode) {
094                        case JFileChooser.FILES_ONLY :
095                            showErrorMessage("Select a existing file");
096                        break;
097                        case JFileChooser.DIRECTORIES_ONLY :
098                            showErrorMessage("Select a existing directory");
099                        break;
100                        case JFileChooser.FILES_AND_DIRECTORIES :
101                            showErrorMessage("Select a existing file or directory");
102                        break;
103                        default :
104                            throw new RuntimeException("Unknown fileSelectionMode " + fileSelectionMode);
105//                        break;
106                    }
107                    return ;
108                }
109    
110                super.approveSelection();
111            }
112        };
113
114        if (title != null && title.length() > 0) {
115            chooser.setDialogTitle(title);
116        }
117        
118        chooser.setFileSelectionMode(fileSelectionMode);
119        
120        if (baseFileName != null && !baseFileName.equals("")) {
121            String existingParentPath = null;
122            if (fileSelectionMode != JFileChooser.DIRECTORIES_ONLY) {
123                // File
124                String absoluteFileName = FileUtil.calculateAbsoluteFileName(baseFileName);
125                if (FileUtil.exists(absoluteFileName)) {
126                    existingParentPath = FileUtil.calculatePath(absoluteFileName);
127                } else {
128                    if (defaultSelectAFileOrDirectoryPath != null && defaultSelectAFileOrDirectoryPath.length() > 0) {
129                        absoluteFileName = FileUtil.calculateAbsoluteFileName(defaultSelectAFileOrDirectoryPath, baseFileName);
130                        if (FileUtil.exists(absoluteFileName)) {
131                            existingParentPath = FileUtil.calculatePath(absoluteFileName);
132                        }
133                    }
134                }
135            } else {
136                // Directory
137                String absolutePath = FileUtil.calculateAbsolutePath(baseFileName);
138                if (FileUtil.exists(absolutePath)) {
139                    existingParentPath = absolutePath;
140                } else {
141                    if (defaultSelectAFileOrDirectoryPath != null && defaultSelectAFileOrDirectoryPath.length() > 0) {
142                        absolutePath = FileUtil.calculateAbsolutePath(defaultSelectAFileOrDirectoryPath, baseFileName);
143                        if (FileUtil.exists(absolutePath)) {
144                            existingParentPath = absolutePath;
145                        }
146                    }
147                }
148            }
149            
150            if (existingParentPath == null) {
151                String path = FileUtil.calculatePath(baseFileName);
152                String absolutePath = null;
153                if (defaultSelectAFileOrDirectoryPath != null && defaultSelectAFileOrDirectoryPath.length() > 0) {
154                    absolutePath = FileUtil.calculateAbsolutePath(defaultSelectAFileOrDirectoryPath, path);
155                } else {
156                    absolutePath = FileUtil.calculateAbsolutePath(path);
157                }
158                existingParentPath = FileUtil.findExistingParent(absolutePath);
159            }
160            
161            chooser.setCurrentDirectory(new File(existingParentPath));
162            
163            if (fileSelectionMode != JFileChooser.DIRECTORIES_ONLY) {
164                // File
165                String fileName = existingParentPath + FileUtil.calculateFileName(baseFileName);
166                chooser.setSelectedFile(new File(fileName));
167                if (useFilter) {
168                    String extension = FileUtil.calculateExtension(baseFileName, false);
169                    if (extension.length() > 0 && !extension.equals("*")) {
170                        setFilter(chooser, extension);
171                    }
172                }
173            }
174        } else {
175            if (defaultSelectAFileOrDirectoryPath != null && defaultSelectAFileOrDirectoryPath.length() > 0) {
176                chooser.setCurrentDirectory(new File(defaultSelectAFileOrDirectoryPath));
177            } else {
178                chooser.setCurrentDirectory(new File(FileUtil.currentPath()));
179            }
180        }
181        
182        int returnValue = chooser.showDialog(parentComponent, buttonText);
183        if (returnValue != JFileChooser.APPROVE_OPTION) {
184            return null;
185        }
186        
187        String result = chooser.getSelectedFile().getAbsolutePath();
188        switch (fileSelectionMode) {
189            case JFileChooser.FILES_ONLY :
190                ;
191            break;
192            case JFileChooser.DIRECTORIES_ONLY :
193                if (!result.endsWith(File.separator)) {
194                    result += File.separator;
195                }
196            break;
197            case JFileChooser.FILES_AND_DIRECTORIES :
198                ;
199            break;
200            default :
201                throw new RuntimeException("Unknown fileSelectionMode " + fileSelectionMode);
202//          break;
203        }
204        
205        return result;
206    }
207    
208    private static void setFilter(JFileChooser chooser, String extension) {
209        String description = StringUtil.firstCharUpperCase(extension) + " files";
210        FileNameExtensionFilter filter = new FileNameExtensionFilter(description, extension);
211        chooser.addChoosableFileFilter(filter);
212        // This remove and add is done to move the "All files" filter last, and to make the created filter the selected one.
213        chooser.removeChoosableFileFilter(chooser.getAcceptAllFileFilter());
214        chooser.addChoosableFileFilter(chooser.getAcceptAllFileFilter());
215    }
216
217    public static void showInformation(String information) {
218        showInformation(information, "Information");
219    }
220
221    private static void showInformation(String information, String title) {
222        showMessage(information, title, JOptionPane.INFORMATION_MESSAGE);
223    }
224
225    public static void showWarning(String warning) {
226        showWarning(warning, "Warning");
227    }
228
229    public static void showWarning(String warning, String title) {
230        showMessage(warning, title, JOptionPane.WARNING_MESSAGE);
231    }
232    
233    public static void showErrorMessage(String errorMessage) {
234        showErrorMessage(errorMessage, "Error");
235    }
236
237    public static void showErrorMessage(String errorMessage, String title) {
238        showMessage(errorMessage, title, JOptionPane.ERROR_MESSAGE);
239    }
240
241    private static void showMessage(String message, String title, int type) {
242        validateDialogTextSize(message);
243        validateDialogTextSize(title);
244        JOptionPane.showMessageDialog(getFocusedWindow(), message, title, type);
245    }
246
247    public static boolean showInformationInTime(String message, int timeInMilliseconds) {
248        return showMessageInTime(message, "Information", JOptionPane.INFORMATION_MESSAGE, timeInMilliseconds);
249    }
250    
251    public static boolean showErrorMessageInTime(String message, int timeInMilliseconds) {
252        return showMessageInTime(message, "Error", JOptionPane.ERROR_MESSAGE, timeInMilliseconds);
253    }
254    
255    public static boolean showMessageInTime(final String message, final String title, final int type, final int timeInMilliseconds) {
256        if (type > JOptionPane.QUESTION_MESSAGE) {
257            throw new IllegalArgumentException("type > JOptionPane.QUESTION_MESSAGE " + type);
258        }
259        MyThread thread = new MyThread("Show in time thread") {
260            int count = 0;
261            public void doLoop() {
262                if (count < timeInMilliseconds / DateUtil.SECOND_IN_MILLISECONDS) {
263                    count++;
264                    Util.sleep(DateUtil.SECOND_IN_MILLISECONDS);
265                    return ;
266                }
267                Runnable runnable = new Runnable() {
268                    public void run() {
269                        Window[] windows = Frame.getWindows();
270                        for (int k = 0; k < windows.length; k++) {
271                            Window window = windows[k];
272                            if (!window.isVisible()) {
273                                continue;
274                            }
275                            if (!(window instanceof JDialog)) {
276                                continue;
277                            }
278                            JDialog jdialog = (JDialog)window;
279                            if (jdialog.getTitle().equals(title)) {
280                                jdialog.setVisible(false);
281                                break;
282                            }
283                        }
284                    }
285                };
286                SwingUtilities.invokeLater(runnable);
287                
288                setRequestedToStop(true);
289            }
290        };
291        thread.start();
292        
293        showMessage(message, title, type);
294        
295        if (thread.isAlive()) {
296            thread.setRequestedToStop(true);
297            return true;
298        }
299        return false;
300    }
301
302    public static Object showSelectDialog(String title, String label, Object[] options, Object currentValue) {
303        validateDialogTextSize(title);
304        validateDialogTextSize(label);
305        return JOptionPane.showInputDialog(getFocusedWindow(), label, title, JOptionPane.QUESTION_MESSAGE, null, options, currentValue);
306    }
307    
308    public static YesNoDialogAnswer showYesNoCancelDialog(String question) {
309        return showYesNoCancelDialog(question, "Question");
310    }
311
312    public static YesNoDialogAnswer showYesNoCancelDialog(Component parentComponent, String question) {
313        return showYesNoCancelDialog(parentComponent, question, "Question");
314    }
315
316    public static YesNoDialogAnswer showYesNoCancelDialog(String question, String title) {
317        return showYesNoCancelDialog(getFocusedWindow(), question, title);
318    }
319
320    private static YesNoDialogAnswer showYesNoCancelDialog(Component parentComponent, String question, String title) {
321        validateDialogTextSize(question);
322        validateDialogTextSize(title);
323        int result = JOptionPane.showConfirmDialog(parentComponent, question, title, JOptionPane.YES_NO_CANCEL_OPTION);
324        switch (result) {
325            case JOptionPane.YES_OPTION :
326                return YesNoDialogAnswer.YES;
327//          break;
328            case JOptionPane.NO_OPTION :
329                return YesNoDialogAnswer.NO;
330//          break;
331            case JOptionPane.CANCEL_OPTION :
332                return YesNoDialogAnswer.CANCEL;
333//          break;
334            case JOptionPane.CLOSED_OPTION :
335                return YesNoDialogAnswer.CANCEL;
336//          break;
337            default :
338                throw new RuntimeException("Unknown option: " + result);
339//          break;
340        }
341    }
342
343    public static boolean showOkCancelDialog(String question) {
344        return showOkCancelDialog(question, "Question");
345    }
346
347    public static boolean showOkCancelDialog(String question, String title) {
348        return showOkCancelDialog(getFocusedWindow(), question, title);
349    }
350
351    private static boolean showOkCancelDialog(Component parentComponent, String question, String title) {
352        validateDialogTextSize(question);
353        validateDialogTextSize(title);
354        return JOptionPane.showConfirmDialog(parentComponent, question, title, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
355    }
356    
357    public static final int MAX_DIALOG_TEXT_LENGTH = 10000;
358    public static final int MAX_DIALOG_TEXT_HEIGHT = 200;
359    public static final int MAX_DIALOG_TEXT_WIDTH = 1000;
360    
361    private static void validateDialogTextSize(String dialogText) {
362        if (dialogText == null) {
363            throw new NullPointerException("dialogText == null");
364        }
365        if (dialogText.length() > MAX_DIALOG_TEXT_LENGTH) {
366            throw new RuntimeException("dialogText.length() > MAX_DIALOG_TEXT_LENGTH (" + dialogText.length() + " > " + MAX_DIALOG_TEXT_LENGTH + ")\n"
367                    + "dialogText: " + dialogText);
368        }
369        
370        String[] split = dialogText.split("\n", StringUtil.STRING_SPLIT_INCLUDING_TRAILING_EMPTY_STRINGS);
371        if (split.length > MAX_DIALOG_TEXT_HEIGHT) {
372            throw new RuntimeException("Lines in dialogText > MAX_DIALOG_TEXT_HEIGHT (" + split.length + " > " + MAX_DIALOG_TEXT_HEIGHT + ")\n"
373                    + "dialogText: " + dialogText);
374        }
375        for (int i = 0; i < split.length; i++) {
376            if (split[i].length() > MAX_DIALOG_TEXT_WIDTH) {
377                throw new RuntimeException("Line " + i + " in dialogText's width > MAX_DIALOG_TEXT_WIDTH (" + split[i].length() + " > " + MAX_DIALOG_TEXT_WIDTH + ")\n"
378                        + "dialogText: " + dialogText);
379            }
380        }
381    }
382    
383    public static void addAccelerator(JButton button, int keyCode, int modifiers, String keyName) {
384        addAccelerator(button, button, keyCode, modifiers, keyName);
385    }
386
387    public static void addAccelerator(JComponent component, JButton button, int keyCode, int modifiers, String keyName) {
388        addAccelerator(component, keyCode, modifiers, keyName, new GUIUtilAction(button));
389    }
390    
391    public static void addAccelerator(JComponent component, int keyCode, int modifiers, String keyName, AbstractAction abstractAction) {
392        InputMap inputMap = new InputMap();
393        inputMap.setParent(component.getInputMap());
394    
395        KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
396        inputMap.put(keyStroke, keyName);
397        
398        component.setInputMap(JComponent.WHEN_FOCUSED, inputMap);
399    
400        ActionMap actionMap = new ActionMap();
401        actionMap.setParent(component.getActionMap());
402        
403        actionMap.put(keyName, abstractAction);
404        
405        component.setActionMap(actionMap);
406    }
407
408    private static class GUIUtilAction extends AbstractAction {
409        public JButton button;
410
411        public GUIUtilAction(JButton pbutton) {
412            button = pbutton;
413        }
414        
415        public void actionPerformed(ActionEvent e) {
416            ActionListener[] actionListeners = button.getActionListeners();
417            for (int i = 0; i < actionListeners.length; i++) {
418                actionListeners[i].actionPerformed(e);
419            }
420        }
421    
422        private static final long serialVersionUID = -7259480592949440746L;
423    }
424    
425    public static void removeAccelerator(JComponent component, int keyCode, int modifiers) {
426        KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
427        component.getInputMap().put(keyStroke, "none");
428    }
429
430    public static void printActionMap(ActionMap actionMap) {
431        Object[] allKeys = actionMap.allKeys();
432        for (int i = 0; i < allKeys.length; i++) {
433            Action action = actionMap.get(allKeys[i]);
434            System.out.println("  " + allKeys[i].toString() + " " + allKeys[i].getClass() + " " + action);
435        }
436        
437        ActionMap parent = actionMap.getParent();
438        if (parent != null) {
439            printActionMap(parent);
440        }
441    }
442
443    public static void printInputMap(InputMap inputMap) {
444        Object[] allKeys = inputMap.allKeys();
445        if (allKeys != null) {
446            for (int i = 0; i < allKeys.length; i++) {
447                System.out.println(" " + allKeys[i].toString() + " " + allKeys[i].getClass());
448            }
449        }
450        
451        InputMap parent = inputMap.getParent();
452        if (parent != null) {
453            printInputMap(parent);
454        }
455    }
456    
457/*    // Code copied from https://stackoverflow.com/questions/16121245/start-editing-in-a-jtable-cell-only-with-reasonable-keys
458    // and modified a lot
459    public static KeyStroke getKeyStrokeForAction(JComponent component, String actionName) {
460        InputMap bindings = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
461        for (KeyStroke key : bindings.allKeys()) {
462            Object binding = bindings.get(key);
463            if (actionName.equals(binding)) {
464                return KeyStroke.getKeyStroke(key.getKeyCode(), key.getModifiers(), true);
465            }
466        }
467        return null;
468    }*/
469    
470    public static void expandTree(JTree tree) {
471        expandTree(tree, (DefaultMutableTreeNode)tree.getModel().getRoot());
472    }
473    
474    public static void expandTree(JTree tree, DefaultMutableTreeNode node) {
475        expandTreeNode(tree, node);
476        for (int i = 0; i < node.getChildCount(); i++) {
477            DefaultMutableTreeNode child = (DefaultMutableTreeNode)node.getChildAt(i);
478            expandTree(tree, child);
479        }
480    }
481
482    public static void expandTreeNode(JTree tree, DefaultMutableTreeNode node) {
483        TreePath selectedPath = new TreePath(node.getPath());
484        tree.expandPath(selectedPath);
485    }
486
487    public static void collapseTree(JTree tree) {
488        int endIndex = tree.isRootVisible() ? 0 : -1;
489        int row = tree.getRowCount() - 1;
490        while (row >= endIndex) {
491            tree.collapseRow(row);
492            row--;
493        }
494    }
495    
496    public static Dimension getScreenSize() {
497        return Toolkit.getDefaultToolkit().getScreenSize();
498    }
499
500    public static void packAndCenterOnScreen(Window window) {
501        window.pack();
502        centerOnScreen(window);
503    }
504    
505    public static void centerOnScreen(Window window) {
506        window.setLocationRelativeTo(null);
507    }
508    
509    public static void centerOnParent(Dialog dialog) {
510        Container parent = dialog.getParent();
511        dialog.setLocationRelativeTo(parent);
512    }
513   
514    private static GraphicsDevice device;
515    private static JFrame fullScreenFrame;
516    
517    public static boolean inFullScreenMode() {
518        return fullScreenFrame != null;
519    }
520    
521    public static void showFullScreen(JFrame frame) {
522        showFullScreen(frame, null);
523    }
524    
525    public static void showFullScreen(JFrame frame, Point location) {
526        if (!frame.isUndecorated() && !frame.isDisplayable()) {
527            frame.setUndecorated(true);
528        }
529        fullScreenFrame = frame;
530        if (location == null) {
531            location = frame.getLocation();
532        }
533        GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
534        for (int i = 0; i < screenDevices.length; i++) {
535            GraphicsDevice screenDevice = screenDevices[i];
536            GraphicsConfiguration[] configurations = screenDevice.getConfigurations();
537            for (GraphicsConfiguration configuration : configurations) {
538                Rectangle bounds = configuration.getBounds();
539                if (bounds.contains(location)) {
540                    device = screenDevice;
541                    break;
542                }
543            }
544        }
545        if (device == null) {
546            device = screenDevices[0];
547        }
548        device.setFullScreenWindow(frame);
549    }
550    
551    public static void cancelFullScreen() {
552        device.setFullScreenWindow(null);
553        fullScreenFrame = null;
554    }
555    
556    public static int calculateTextHeight(Graphics g, String text) {
557        Graphics2D g2d = (Graphics2D)g;
558        return (int)new TextLayout(text, g2d.getFont(), g2d.getFontRenderContext()).getBounds().getHeight();
559    }
560
561    public static int calculateTextWidth(Graphics g, String text) {
562        Graphics2D g2d = (Graphics2D)g;
563        return (int)new TextLayout(text, g2d.getFont(), g2d.getFontRenderContext()).getBounds().getWidth();
564    }
565    
566/*    public static int calculateTextHeight(JComponent component, String text) {
567        return component.getFontMetrics(component.getFont()).stringHeight(text); // Missing???
568    }*/
569
570    public static int calculateTextWidth(JComponent component, String text) {
571        return component.getFontMetrics(component.getFont()).stringWidth(text);
572    }
573    
574    public static Rectangle2D calculateTextBounds(Graphics g, String text) {
575        Rectangle2D result = g.getFont().getStringBounds(text, ((Graphics2D)g).getFontRenderContext());
576        return result;
577    }
578    
579    public static Rectangle2D calculateTextBounds(JComponent component, Graphics g, String text) {
580        return component.getFontMetrics(component.getFont()).getStringBounds(text, 0, text.length(), g);
581    }
582    
583    public static Rectangle calculateComponentRectangle(Component c) {
584        return calculateComponentRectangle(c, null);
585    }
586    
587    public static Rectangle calculateComponentRectangle(Component c, Component rootPane) {
588        if (rootPane == null) {
589            rootPane = SwingUtilities.getRootPane(c);
590        }
591        Point topLeftLocation = SwingUtilities.convertPoint(c, new Point(0, 0), rootPane);
592        Rectangle rectangle = new Rectangle((int)topLeftLocation.getX(), (int)topLeftLocation.getY(), c.getWidth() - 1, c.getHeight() - 1);
593        return rectangle;
594    }
595    
596    public static void setFontSize(Graphics g, int fontSize) {
597        g.setFont(new Font(g.getFont().getFontName(), Font.PLAIN, fontSize));
598    }
599    
600    public static void setFontSize(Graphics g, float fontSize) {
601        Font font = deriveFontSize(g.getFont(), fontSize);
602        g.setFont(font);
603    }
604
605    public static Font deriveBoldFont(Font font) {
606        return font.deriveFont(Font.BOLD);
607    }
608    
609    public static Font deriveFontSize(Font font, float fontSize) {
610        return font.deriveFont(fontSize);
611    }
612    
613    public static Component findChildComponentWithName(Container container, String name) {
614        int count = container.getComponentCount();
615        for (int i = 0; i < count; i++) {
616            Component component = container.getComponent(i);
617            if (component.getName() != null && component.getName().equals(name)) {
618                return component;
619            }
620            if (component instanceof Container) {
621                Component componentWithName = findChildComponentWithName((Container)component, name);
622                if (componentWithName != null) {
623                    return componentWithName;
624                }
625            }
626        }
627        return null;
628    }
629    
630    public static void requestWindowToClose(Window window) {
631        // Code copied from https://stackoverflow.com/questions/1234912/how-to-programmatically-close-a-jframe
632        // and modified
633        WindowEvent windowEvent = new WindowEvent(window, WindowEvent.WINDOW_CLOSING);
634        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(windowEvent);
635    }
636    
637    public static void runInEventThread(Runnable runnable) {
638        if (SwingUtilities.isEventDispatchThread()) {
639            runnable.run();
640        } else {
641            SwingUtilities.invokeLater(runnable);
642        }
643    }
644    
645    public static void runInEventThreadAndWait(Runnable runnable) {
646        if (SwingUtilities.isEventDispatchThread()) {
647            runnable.run();
648        } else {
649            try {
650                SwingUtilities.invokeAndWait(runnable);
651            } catch (InvocationTargetException e) {
652                throw new RuntimeException(e);
653            } catch (InterruptedException e) {
654                throw new RuntimeException(e);
655            }
656        }
657    }
658    
659    private static ComponentListener containerComponentListener;
660    
661    public static void makePanelChangeSizeOnTooManyComponents(Container container) {
662        if (!(container.getLayout() instanceof FlowLayout)) {
663            throw new RuntimeException("!container.getLayout() instanceof FlowLayout " + container.getLayout());
664        }
665        
666        if (containerComponentListener == null) {
667            containerComponentListener = new ComponentListener() {
668                public void componentShown(ComponentEvent e) {
669                    ;
670                }
671                
672                public void componentResized(final ComponentEvent e) {
673                    Container pcontainer = (Container)e.getComponent();
674                    updatePanelSize(pcontainer);
675                }
676                
677                public void componentMoved(ComponentEvent e) {
678                    ;
679                }
680                
681                public void componentHidden(ComponentEvent e) {
682                    ;
683                }
684            };
685        }
686        
687        container.addComponentListener(containerComponentListener);
688    }
689    
690    private static void updatePanelSize(Container container) {
691        Component[] components = container.getComponents();
692        int maxHeight = -1;
693        for (int i = 0; i < components.length; i++) {
694            Component component = components[i];
695            int h = component.getY() + component.getHeight() + ((FlowLayout)container.getLayout()).getVgap();
696            maxHeight = Math.max(maxHeight, h);
697        }
698        container.setPreferredSize(new Dimension(container.getPreferredSize().width, maxHeight));
699        container.invalidate();
700//        container.revalidate(); // 1.7 only
701
702        Container parent = container.getParent();
703        if (parent == null) {
704            return ;
705        }
706        Container parentParent = parent.getParent();
707        if (parentParent == null) {
708            return ;
709        }
710        parentParent.invalidate();
711        parentParent.validate();
712    }
713    
714    public static boolean getScrollLockSate() {
715        return Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
716    }
717    
718    public static void setLinuxApplicationName(String applicationName) {
719        if (!Util.isLinuxOS()) {
720            return ;
721        }
722        
723        if (applicationName == null) {
724            throw new RuntimeException("applicationName == null");
725        }
726        
727        if (applicationName.length() == 0) {
728            throw new RuntimeException("applicationName.length() == 0");
729        }
730        
731        try {
732            // Code copied from https://stackoverflow.com/questions/46454995/how-to-hide-warning-illegal-reflective-access-in-java-9-without-jvm-argument
733            // and modified
734            Class<?> classInPackage = ReflectionUtil.loadClass("sun.awt.X11.XToolkit");
735            ReflectionUtil.openPackageForModuleAccess(classInPackage, GUIUtil.class);
736        
737            Toolkit toolkit = Toolkit.getDefaultToolkit();
738            Field awtAppClassNameField = toolkit.getClass().getDeclaredField("awtAppClassName");
739            awtAppClassNameField.setAccessible(true);
740            awtAppClassNameField.set(toolkit, applicationName);
741        } catch (Exception e) {
742            // As this hack is only a cosmetic thing all exceptions from it will be ignored.
743            Util.printMessageWithSourceLine("GUIUtil.setLinuxApplicationName(): Throw an " + e.getClass().getName() + " exception with message \"" + e.getMessage() + "\" => Ignored");
744        }
745    }
746}